From 7ba68d333bc20b5795ccfd3870546a05eee60470 Mon Sep 17 00:00:00 2001 From: T Date: Mon, 29 Sep 2025 15:04:44 -0600 Subject: Support for archiving clients and projects. --- internal/tui/commands.go | 141 ++++++++++++++++++++++++++++++++--------------- 1 file changed, 96 insertions(+), 45 deletions(-) (limited to 'internal/tui/commands.go') diff --git a/internal/tui/commands.go b/internal/tui/commands.go index 4fdd9e0..90bc05f 100644 --- a/internal/tui/commands.go +++ b/internal/tui/commands.go @@ -2,9 +2,7 @@ package tui import ( "context" - "fmt" - "strings" - "time" + "errors" "git.tjp.lol/punchcard/internal/actions" "git.tjp.lol/punchcard/internal/queries" @@ -32,6 +30,26 @@ type ( openHistoryFilterModal struct{} openReportModal struct{} updateHistoryFilter HistoryFilter + archiveSelectedMsg struct { + clientID int64 + projectID *int64 + restoreClientID int64 + restoreProjectID *int64 + } + unarchiveSelectedMsg struct { + clientID int64 + projectID *int64 + restoreClientID int64 + restoreProjectID *int64 + } + toggleShowArchivedMsg struct { + restoreClientID int64 + restoreProjectID *int64 + } + restoreSelectionMsg struct{ clientID int64; projectID *int64 } + showArchivedWarningMsg struct{ params *ArchivedPunchInParams } + reportGenerationSucceeded struct{} + reportGenerationFailed struct{ err error } ) func navigate(forward bool) tea.Cmd { @@ -40,8 +58,25 @@ func navigate(forward bool) tea.Cmd { func punchIn(m AppModel) tea.Cmd { return func() tea.Msg { - _, _ = actions.New(m.queries).PunchInMostRecent(context.Background(), "", nil) - // TODO: use the returned TimerSession instead of re-querying everything + a := actions.New(m.queries) + _, err := a.PunchInMostRecent(context.Background(), "", nil, false) + // Handle archived errors by showing modal + if err != nil { + if errors.Is(err, actions.ErrArchivedClient) { + return showArchivedWarningMsg{ + params: &ArchivedPunchInParams{ + EntityType: "client", + }, + } + } else if errors.Is(err, actions.ErrArchivedProject) { + return showArchivedWarningMsg{ + params: &ArchivedPunchInParams{ + EntityType: "project", + }, + } + } + } + return m.refreshCmd() } } @@ -69,8 +104,33 @@ func punchInOnSelection(m AppModel) tea.Cmd { return nil } - _, _ = actions.New(m.queries).PunchIn(context.Background(), clientID, projectID, description, entryRate) - // TODO: use the returned TimerSession instead of re-querying everything + a := actions.New(m.queries) + _, err := a.PunchIn(context.Background(), clientID, projectID, description, entryRate, false) + // Handle archived errors by showing modal + if err != nil { + if errors.Is(err, actions.ErrArchivedClient) { + return showArchivedWarningMsg{ + params: &ArchivedPunchInParams{ + ClientID: clientID, + ProjectID: projectID, + Description: description, + Rate: entryRate, + EntityType: "client", + }, + } + } else if errors.Is(err, actions.ErrArchivedProject) { + return showArchivedWarningMsg{ + params: &ArchivedPunchInParams{ + ClientID: clientID, + ProjectID: projectID, + Description: description, + Rate: entryRate, + EntityType: "project", + }, + } + } + } + return m.refreshCmd() } } @@ -139,51 +199,42 @@ func createReportModal() tea.Cmd { return func() tea.Msg { return openReportModal{} } } -func generateReport(m *ModalBoxModel, am AppModel) tea.Cmd { +func generateReport(am AppModel, genFunc func(context.Context, *queries.Queries, reports.ReportParams) (*reports.ReportResult, error), params reports.ReportParams) tea.Cmd { return func() tea.Msg { - form := &m.form - - dateRange, err := reports.ParseDateRange(form.fields[1].Value()) - if err != nil { - form.fields[1].Err = fmt.Errorf("invalid date range: %v", err) - return reOpenModal() + if _, err := genFunc(context.Background(), am.queries, params); err != nil { + return reportGenerationFailed{err: err} } + return reportGenerationSucceeded{} + } +} - var tz *time.Location - tzstr := form.fields[5].Value() - if tzstr == "" { - tz = time.Local - } else { - zone, err := time.LoadLocation(tzstr) - if err != nil { - form.fields[5].Err = err - return reOpenModal() - } - tz = zone +func archiveClientOrProject(clientID int64, projectID *int64) tea.Cmd { + return func() tea.Msg { + return archiveSelectedMsg{ + clientID: clientID, + projectID: projectID, + restoreClientID: clientID, + restoreProjectID: projectID, } + } +} - var genFunc func(context.Context, *queries.Queries, reports.ReportParams) (*reports.ReportResult, error) - switch strings.ToLower(form.fields[0].Value()) { - case "invoice": - genFunc = reports.GenerateInvoice - case "timesheet": - genFunc = reports.GenerateTimesheet - case "unified": - genFunc = reports.GenerateUnifiedReport +func unarchiveClientOrProject(clientID int64, projectID *int64) tea.Cmd { + return func() tea.Msg { + return unarchiveSelectedMsg{ + clientID: clientID, + projectID: projectID, + restoreClientID: clientID, + restoreProjectID: projectID, } + } +} - params := reports.ReportParams{ - ClientName: form.fields[2].Value(), - ProjectName: form.fields[3].Value(), - DateRange: dateRange, - OutputPath: form.fields[4].Value(), - Timezone: tz, - } - if _, err := genFunc(context.Background(), am.queries, params); err != nil { - form.err = err - return reOpenModal() +func toggleShowArchived(clientID int64, projectID *int64) tea.Cmd { + return func() tea.Msg { + return toggleShowArchivedMsg{ + restoreClientID: clientID, + restoreProjectID: projectID, } - - return nil } } -- cgit v1.2.3