summaryrefslogtreecommitdiff
path: root/internal/commands/in.go
blob: 8a08edfd4db3f41c24f5b69ab515b2b50d9cb609 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
package commands

import (
	"errors"
	"fmt"

	"git.tjp.lol/punchcard/internal/actions"
	punchctx "git.tjp.lol/punchcard/internal/context"

	"github.com/spf13/cobra"
)

func NewInCmd() *cobra.Command {
	var clientFlag, projectFlag string

	cmd := &cobra.Command{
		Use:     "in [<description>]",
		Aliases: []string{"i"},
		Short:   "Start a timer",
		Long: `Start tracking time for the current work session.
	
If no flags are provided, copies the most recent time entry.
If -p/--project is provided without -c/--client, uses the project's client.
If a timer is already active:
  - Same parameters: no-op
  - Different parameters: stops current timer and starts new one

Examples:
  punch in                                               # Copy most recent entry
  punch in "Working on website redesign"                 # Copy most recent but change description
  punch in -c "Acme Corp" "Client meeting"               # Specific client
  punch in -p "Website Redesign" "Frontend development"  # Project (client auto-selected)
  punch in --client 1 --project "Website Redesign"       # Explicit client and project`,
		Args: cobra.MaximumNArgs(1),
		RunE: func(cmd *cobra.Command, args []string) error {
			var description string
			if len(args) > 0 {
				description = args[0]
			}

			billableRateFloat, _ := cmd.Flags().GetFloat64("hourly-rate")
			var billableRate *float64
			if billableRateFloat > 0 {
				billableRate = &billableRateFloat
			}

			q := punchctx.GetDB(cmd.Context())
			if q == nil {
				return fmt.Errorf("database not available in context")
			}

			a := actions.New(q)

			// Use the actions package based on what flags were provided
			var session *actions.TimerSession
			var err error

			// Try punching in without auto-unarchive first
			if clientFlag == "" && projectFlag == "" {
				session, err = a.PunchInMostRecent(cmd.Context(), description, billableRate, false)
			} else {
				session, err = a.PunchIn(cmd.Context(), clientFlag, projectFlag, description, billableRate, false)
			}

			// Handle archived errors by prompting user
			if err != nil {
				if errors.Is(err, actions.ErrArchivedClient) || errors.Is(err, actions.ErrArchivedProject) {
					entityType := "client"
					if errors.Is(err, actions.ErrArchivedProject) {
						entityType = "project"
					}

					cmd.Printf("Warning: This %s is archived.\n", entityType)
					cmd.Print("Continue and unarchive? (y/N): ")

					var response string
					_, err := fmt.Scanln(&response)
					if err != nil || (response != "y" && response != "Y") {
						return fmt.Errorf("operation cancelled")
					}

					// Retry with auto-unarchive enabled
					if clientFlag == "" && projectFlag == "" {
						session, err = a.PunchInMostRecent(cmd.Context(), description, billableRate, true)
					} else {
						session, err = a.PunchIn(cmd.Context(), clientFlag, projectFlag, description, billableRate, true)
					}

					if err != nil {
						return err
					}
				} else {
					return err
				}
			}

			// Handle different response types
			if session.WasNoOp {
				cmd.Printf("Timer already active with same parameters (ID: %d)\n", session.ID)
				return nil
			}

			// Print stopped timer message if we stopped one
			if session.StoppedEntryID != nil {
				cmd.Printf("Stopped previous timer (ID: %d)\n", *session.StoppedEntryID)
			}

			// Build output message
			output := fmt.Sprintf("Started timer (ID: %d) for client: %s", session.ID, session.ClientName)

			if session.ProjectName != "" {
				output += fmt.Sprintf(", project: %s", session.ProjectName)
			}

			if session.Description != "" {
				output += fmt.Sprintf(", description: %s", session.Description)
			}

			cmd.Print(output + "\n")
			return nil
		},
	}

	cmd.Flags().StringVarP(&clientFlag, "client", "c", "", "Client name or ID")
	cmd.Flags().StringVarP(&projectFlag, "project", "p", "", "Project name or ID")
	cmd.Flags().Float64("hourly-rate", 0, "Override hourly billable rate for this time entry")

	return cmd
}