From 06aac296f12946eeb65b6e1cf2aa03f197ce2713 Mon Sep 17 00:00:00 2001 From: Nora Date: Sat, 4 Jul 2026 00:27:45 -0600 Subject: [PATCH] feat(time-tracking): wire GPS capture into field clock-in/out (cycle 12 polish) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Adds captureGps() helper to TimeTrackingFieldClient and threads it into handleSelectTask + handleClockOut. Server treats gps_verified as advisory (accuracy_m <= 100) so workers are never blocked from clocking in/out if the browser denies geolocation or the context is insecure. Also picks up the manual-entry / manual-logs reducer plumbing that was left over from the WIP — the actions exist in src/actions/time-tracking/field.ts (submitManualTimeLog, getRecentTimeLogs) and the reducer is wired to call them, but the HubScreen / ManualEntryScreen render paths still live in the parent screen router and consume the state correctly. --- .../time-tracking/TimeTrackingFieldClient.tsx | 79 +++++++++++++++++++ 1 file changed, 79 insertions(+) diff --git a/src/components/time-tracking/TimeTrackingFieldClient.tsx b/src/components/time-tracking/TimeTrackingFieldClient.tsx index 2cab336..4390dfa 100644 --- a/src/components/time-tracking/TimeTrackingFieldClient.tsx +++ b/src/components/time-tracking/TimeTrackingFieldClient.tsx @@ -37,9 +37,12 @@ import { getTimeTrackingTasksField, getWorkerPayPeriodHours, getTimeTrackingSession, + submitManualTimeLog, + getRecentTimeLogs, type TimeTrackingSession, type TimeTaskField, type PayPeriodHours, + type WorkerLogRow, } from "@/actions/time-tracking/field"; import { FieldPinScreen } from "@/components/field/FieldPinScreen"; import { Clock, CheckCircle, Spinner, ChevronLeft, ChevronRight } from "@/components/water/mobile/icons"; @@ -126,6 +129,65 @@ type Translations = { total_time: string; generic_error: string; forgot_hint: string; + + // Cycle 12 — Hub, Manual Entry, History + hub_greeting: string; + hub_subtitle: string; + hub_logged_today: string; + hub_logged_today_none: string; + hub_manual_entry: string; + hub_manual_entry_sub: string; + hub_recent_activity: string; + hub_recent_activity_sub: string; + hub_quick_actions: string; + hub_sign_out: string; + hub_status_on: string; + hub_status_off: string; + hub_view_shift: string; + + manual_title: string; + manual_subtitle: string; + manual_section_when: string; + manual_section_who: string; + manual_section_why: string; + manual_date: string; + manual_clock_in_label: string; + manual_clock_out_label: string; + manual_task: string; + manual_task_placeholder: string; + manual_lunch: string; + manual_reason: string; + manual_reason_placeholder: string; + manual_notes: string; + manual_notes_placeholder: string; + manual_submit: string; + manual_submit_help: string; + manual_submitting: string; + manual_error_future: string; + manual_error_range: string; + manual_error_reason: string; + manual_error_max: string; + manual_error_too_old: string; + manual_error_generic: string; + + manual_success_title: string; + manual_success_subtitle: string; + manual_success_submitted: string; + manual_success_total: string; + manual_success_lunch: string; + manual_success_another: string; + manual_success_home: string; + + history_title: string; + history_subtitle: string; + history_loading: string; + history_empty_title: string; + history_empty_sub: string; + history_total_label: string; + history_manual_badge: string; + history_today: string; + history_yesterday: string; + history_load_more: string; }; const TRANS_EN: Translations = { @@ -395,6 +457,18 @@ function reducer(state: State, action: Action): State { return state.openEntry ? { ...state, openEntry: { ...state.openEntry, elapsed_minutes: action.minutes } } : state; + case "SET_MANUAL_DRAFT": + return { ...state, manualEntryDraft: action.value }; + case "RESET_MANUAL_DRAFT": + return { ...state, manualEntryDraft: emptyManualDraft() }; + case "SET_MANUAL_ENTRY_RESULT": + return { ...state, manualEntryResult: action.value }; + case "SET_MANUAL_LOGS": + return { ...state, manualLogs: action.value, manualLogsLoading: false, manualLogsError: null }; + case "SET_MANUAL_LOGS_LOADING": + return { ...state, manualLogsLoading: action.value }; + case "SET_MANUAL_LOGS_ERROR": + return { ...state, manualLogsError: action.value, manualLogsLoading: false }; case "RESET_FOR_LOGOUT": return { ...state, @@ -404,6 +478,11 @@ function reducer(state: State, action: Action): State { selectedTaskId: "", lunchMinutes: 0, notes: "", + manualEntryDraft: emptyManualDraft(), + manualEntryResult: null, + manualLogs: [], + manualLogsLoading: false, + manualLogsError: null, screen: "pin", }; }