@open-mercato/core 0.7.1-develop.7178.1.ab93fbaf4e → 0.7.1-develop.7180.1.9717fbbb43
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.turbo/turbo-build.log +2 -2
- package/dist/generated/entities/staff_time_entry/index.js +14 -0
- package/dist/generated/entities/staff_time_entry/index.js.map +2 -2
- package/dist/generated/entities/staff_time_entry_tag/index.js +15 -0
- package/dist/generated/entities/staff_time_entry_tag/index.js.map +7 -0
- package/dist/generated/entities/staff_time_project/index.js +16 -0
- package/dist/generated/entities/staff_time_project/index.js.map +2 -2
- package/dist/generated/entities/staff_time_report/index.js +57 -0
- package/dist/generated/entities/staff_time_report/index.js.map +7 -0
- package/dist/generated/entities/staff_time_report_entry/index.js +27 -0
- package/dist/generated/entities/staff_time_report_entry/index.js.map +7 -0
- package/dist/generated/entities/staff_time_report_event/index.js +21 -0
- package/dist/generated/entities/staff_time_report_event/index.js.map +7 -0
- package/dist/generated/entities/staff_time_report_project/index.js +15 -0
- package/dist/generated/entities/staff_time_report_project/index.js.map +7 -0
- package/dist/generated/entities/staff_time_tag/index.js +21 -0
- package/dist/generated/entities/staff_time_tag/index.js.map +7 -0
- package/dist/generated/entities/staff_time_task/index.js +37 -0
- package/dist/generated/entities/staff_time_task/index.js.map +7 -0
- package/dist/generated/entities/staff_time_task_comment/index.js +21 -0
- package/dist/generated/entities/staff_time_task_comment/index.js.map +7 -0
- package/dist/generated/entities/staff_time_task_status/index.js +29 -0
- package/dist/generated/entities/staff_time_task_status/index.js.map +7 -0
- package/dist/generated/entities/staff_time_task_tag/index.js +15 -0
- package/dist/generated/entities/staff_time_task_tag/index.js.map +7 -0
- package/dist/generated/entities.ids.generated.js +11 -1
- package/dist/generated/entities.ids.generated.js.map +2 -2
- package/dist/generated/entity-fields-registry.js +149 -0
- package/dist/generated/entity-fields-registry.js.map +2 -2
- package/dist/helpers/integration/timesheetFixtures.js +7 -0
- package/dist/helpers/integration/timesheetFixtures.js.map +2 -2
- package/dist/modules/auth/cli.js +16 -0
- package/dist/modules/auth/cli.js.map +2 -2
- package/dist/modules/auth/lib/backendChrome.js +1 -0
- package/dist/modules/auth/lib/backendChrome.js.map +2 -2
- package/dist/modules/customer_accounts/lib/customerRoleAcls.js +44 -0
- package/dist/modules/customer_accounts/lib/customerRoleAcls.js.map +7 -0
- package/dist/modules/customer_accounts/setup.js +1 -28
- package/dist/modules/customer_accounts/setup.js.map +2 -2
- package/dist/modules/data_sync/api/options.js +2 -0
- package/dist/modules/data_sync/api/options.js.map +2 -2
- package/dist/modules/data_sync/backend/data-sync/page.js +39 -17
- package/dist/modules/data_sync/backend/data-sync/page.js.map +2 -2
- package/dist/modules/data_sync/lib/start-controls.js +42 -0
- package/dist/modules/data_sync/lib/start-controls.js.map +7 -0
- package/dist/modules/staff/acl.js +9 -1
- package/dist/modules/staff/acl.js.map +2 -2
- package/dist/modules/staff/ai-agents.js +142 -0
- package/dist/modules/staff/ai-agents.js.map +7 -0
- package/dist/modules/staff/ai-tools/time-tracking-pack.js +348 -0
- package/dist/modules/staff/ai-tools/time-tracking-pack.js.map +7 -0
- package/dist/modules/staff/ai-tools/types.js +54 -0
- package/dist/modules/staff/ai-tools/types.js.map +7 -0
- package/dist/modules/staff/ai-tools.js +8 -0
- package/dist/modules/staff/ai-tools.js.map +7 -0
- package/dist/modules/staff/analytics.js +84 -1
- package/dist/modules/staff/analytics.js.map +2 -2
- package/dist/modules/staff/api/guards.js +37 -3
- package/dist/modules/staff/api/guards.js.map +2 -2
- package/dist/modules/staff/api/leave-requests/accept/route.js +1 -3
- package/dist/modules/staff/api/leave-requests/accept/route.js.map +2 -2
- package/dist/modules/staff/api/leave-requests/reject/route.js +1 -3
- package/dist/modules/staff/api/leave-requests/reject/route.js.map +2 -2
- package/dist/modules/staff/api/portal/time-reports/[id]/route.js +131 -0
- package/dist/modules/staff/api/portal/time-reports/[id]/route.js.map +7 -0
- package/dist/modules/staff/api/portal/time-reports/route.js +179 -0
- package/dist/modules/staff/api/portal/time-reports/route.js.map +7 -0
- package/dist/modules/staff/api/team-members/self/route.js +1 -3
- package/dist/modules/staff/api/team-members/self/route.js.map +2 -2
- package/dist/modules/staff/api/team-members/tags/assign/route.js +1 -3
- package/dist/modules/staff/api/team-members/tags/assign/route.js.map +2 -2
- package/dist/modules/staff/api/team-members/tags/unassign/route.js +1 -3
- package/dist/modules/staff/api/team-members/tags/unassign/route.js.map +2 -2
- package/dist/modules/staff/api/timesheets/_shared/withTimesheetInterceptors.js +174 -0
- package/dist/modules/staff/api/timesheets/_shared/withTimesheetInterceptors.js.map +7 -0
- package/dist/modules/staff/api/timesheets/access-requests/route.js +226 -0
- package/dist/modules/staff/api/timesheets/access-requests/route.js.map +7 -0
- package/dist/modules/staff/api/timesheets/my-projects/[projectId]/route.js +15 -8
- package/dist/modules/staff/api/timesheets/my-projects/[projectId]/route.js.map +2 -2
- package/dist/modules/staff/api/timesheets/my-projects/route.js +13 -1
- package/dist/modules/staff/api/timesheets/my-projects/route.js.map +2 -2
- package/dist/modules/staff/api/timesheets/my-work/myWorkAggregate.js +42 -0
- package/dist/modules/staff/api/timesheets/my-work/myWorkAggregate.js.map +7 -0
- package/dist/modules/staff/api/timesheets/my-work/route.js +383 -0
- package/dist/modules/staff/api/timesheets/my-work/route.js.map +7 -0
- package/dist/modules/staff/api/timesheets/projects/kpis/route.js +14 -2
- package/dist/modules/staff/api/timesheets/projects/kpis/route.js.map +2 -2
- package/dist/modules/staff/api/timesheets/reports/[id]/close/route.js +168 -0
- package/dist/modules/staff/api/timesheets/reports/[id]/close/route.js.map +7 -0
- package/dist/modules/staff/api/timesheets/reports/[id]/export/route.js +246 -0
- package/dist/modules/staff/api/timesheets/reports/[id]/export/route.js.map +7 -0
- package/dist/modules/staff/api/timesheets/reports/[id]/sheet/route.js +183 -0
- package/dist/modules/staff/api/timesheets/reports/[id]/sheet/route.js.map +7 -0
- package/dist/modules/staff/api/timesheets/reports/[id]/unlock/route.js +164 -0
- package/dist/modules/staff/api/timesheets/reports/[id]/unlock/route.js.map +7 -0
- package/dist/modules/staff/api/timesheets/reports/preview/route.js +360 -0
- package/dist/modules/staff/api/timesheets/reports/preview/route.js.map +7 -0
- package/dist/modules/staff/api/timesheets/reports/route.js +316 -0
- package/dist/modules/staff/api/timesheets/reports/route.js.map +7 -0
- package/dist/modules/staff/api/timesheets/reports/shared.js +63 -0
- package/dist/modules/staff/api/timesheets/reports/shared.js.map +7 -0
- package/dist/modules/staff/api/timesheets/settings/reapply-rounding/route.js +164 -0
- package/dist/modules/staff/api/timesheets/settings/reapply-rounding/route.js.map +7 -0
- package/dist/modules/staff/api/timesheets/settings/rounding-impact/route.js +168 -0
- package/dist/modules/staff/api/timesheets/settings/rounding-impact/route.js.map +7 -0
- package/dist/modules/staff/api/timesheets/settings/route.js +199 -0
- package/dist/modules/staff/api/timesheets/settings/route.js.map +7 -0
- package/dist/modules/staff/api/timesheets/tags/access.js +76 -0
- package/dist/modules/staff/api/timesheets/tags/access.js.map +7 -0
- package/dist/modules/staff/api/timesheets/tags/entry-assignments/route.js +235 -0
- package/dist/modules/staff/api/timesheets/tags/entry-assignments/route.js.map +7 -0
- package/dist/modules/staff/api/timesheets/tags/route.js +160 -0
- package/dist/modules/staff/api/timesheets/tags/route.js.map +7 -0
- package/dist/modules/staff/api/timesheets/tags/task-assignments/route.js +233 -0
- package/dist/modules/staff/api/timesheets/tags/task-assignments/route.js.map +7 -0
- package/dist/modules/staff/api/timesheets/task-statuses/route.js +188 -0
- package/dist/modules/staff/api/timesheets/task-statuses/route.js.map +7 -0
- package/dist/modules/staff/api/timesheets/tasks/[id]/comments/route.js +399 -0
- package/dist/modules/staff/api/timesheets/tasks/[id]/comments/route.js.map +7 -0
- package/dist/modules/staff/api/timesheets/tasks/[id]/status/route.js +210 -0
- package/dist/modules/staff/api/timesheets/tasks/[id]/status/route.js.map +7 -0
- package/dist/modules/staff/api/timesheets/tasks/route.js +333 -0
- package/dist/modules/staff/api/timesheets/tasks/route.js.map +7 -0
- package/dist/modules/staff/api/timesheets/time-entries/[id]/duplicate/route.js +228 -0
- package/dist/modules/staff/api/timesheets/time-entries/[id]/duplicate/route.js.map +7 -0
- package/dist/modules/staff/api/timesheets/time-entries/[id]/segments/[segmentId]/route.js +29 -10
- package/dist/modules/staff/api/timesheets/time-entries/[id]/segments/[segmentId]/route.js.map +2 -2
- package/dist/modules/staff/api/timesheets/time-entries/[id]/segments/route.js +31 -18
- package/dist/modules/staff/api/timesheets/time-entries/[id]/segments/route.js.map +2 -2
- package/dist/modules/staff/api/timesheets/time-entries/[id]/timer-start/route.js +21 -7
- package/dist/modules/staff/api/timesheets/time-entries/[id]/timer-start/route.js.map +2 -2
- package/dist/modules/staff/api/timesheets/time-entries/[id]/timer-stop/route.js +27 -7
- package/dist/modules/staff/api/timesheets/time-entries/[id]/timer-stop/route.js.map +2 -2
- package/dist/modules/staff/api/timesheets/time-entries/bulk/route.js +215 -32
- package/dist/modules/staff/api/timesheets/time-entries/bulk/route.js.map +2 -2
- package/dist/modules/staff/api/timesheets/time-entries/copy-day/route.js +378 -0
- package/dist/modules/staff/api/timesheets/time-entries/copy-day/route.js.map +7 -0
- package/dist/modules/staff/api/timesheets/time-entries/overlaps/route.js +340 -0
- package/dist/modules/staff/api/timesheets/time-entries/overlaps/route.js.map +7 -0
- package/dist/modules/staff/api/timesheets/time-entries/route.js +238 -26
- package/dist/modules/staff/api/timesheets/time-entries/route.js.map +2 -2
- package/dist/modules/staff/api/timesheets/time-entries/start-timer/route.js +23 -8
- package/dist/modules/staff/api/timesheets/time-entries/start-timer/route.js.map +2 -2
- package/dist/modules/staff/api/timesheets/time-projects/[id]/change-currency/route.js +209 -0
- package/dist/modules/staff/api/timesheets/time-projects/[id]/change-currency/route.js.map +7 -0
- package/dist/modules/staff/api/timesheets/time-projects/[id]/employees/route.js +59 -2
- package/dist/modules/staff/api/timesheets/time-projects/[id]/employees/route.js.map +2 -2
- package/dist/modules/staff/api/timesheets/time-projects/route.js +264 -22
- package/dist/modules/staff/api/timesheets/time-projects/route.js.map +2 -2
- package/dist/modules/staff/backend/staff/time-tracking/board/page.js +110 -0
- package/dist/modules/staff/backend/staff/time-tracking/board/page.js.map +7 -0
- package/dist/modules/staff/backend/staff/time-tracking/board/page.meta.js +18 -0
- package/dist/modules/staff/backend/staff/time-tracking/board/page.meta.js.map +7 -0
- package/dist/modules/staff/backend/staff/time-tracking/entries/page.js +981 -0
- package/dist/modules/staff/backend/staff/time-tracking/entries/page.js.map +7 -0
- package/dist/modules/staff/backend/staff/time-tracking/entries/page.meta.js +18 -0
- package/dist/modules/staff/backend/staff/time-tracking/entries/page.meta.js.map +7 -0
- package/dist/modules/staff/backend/staff/time-tracking/page.js +583 -0
- package/dist/modules/staff/backend/staff/time-tracking/page.js.map +7 -0
- package/dist/modules/staff/backend/staff/time-tracking/page.meta.js +15 -0
- package/dist/modules/staff/backend/staff/time-tracking/page.meta.js.map +7 -0
- package/dist/modules/staff/backend/staff/time-tracking/projects/[id]/board/page.js +65 -0
- package/dist/modules/staff/backend/staff/time-tracking/projects/[id]/board/page.js.map +7 -0
- package/dist/modules/staff/backend/staff/time-tracking/projects/[id]/board/page.meta.js +16 -0
- package/dist/modules/staff/backend/staff/time-tracking/projects/[id]/board/page.meta.js.map +7 -0
- package/dist/modules/staff/backend/staff/time-tracking/projects/[id]/edit/page.js +229 -0
- package/dist/modules/staff/backend/staff/time-tracking/projects/[id]/edit/page.js.map +7 -0
- package/dist/modules/staff/backend/staff/time-tracking/projects/[id]/edit/page.meta.js +16 -0
- package/dist/modules/staff/backend/staff/time-tracking/projects/[id]/edit/page.meta.js.map +7 -0
- package/dist/modules/staff/backend/staff/time-tracking/projects/[id]/page.js +1014 -0
- package/dist/modules/staff/backend/staff/time-tracking/projects/[id]/page.js.map +7 -0
- package/dist/modules/staff/backend/staff/time-tracking/projects/[id]/page.meta.js +16 -0
- package/dist/modules/staff/backend/staff/time-tracking/projects/[id]/page.meta.js.map +7 -0
- package/dist/modules/staff/backend/staff/time-tracking/projects/create/page.js +97 -0
- package/dist/modules/staff/backend/staff/time-tracking/projects/create/page.js.map +7 -0
- package/dist/modules/staff/backend/staff/time-tracking/projects/create/page.meta.js +16 -0
- package/dist/modules/staff/backend/staff/time-tracking/projects/create/page.meta.js.map +7 -0
- package/dist/modules/staff/backend/staff/time-tracking/projects/page.js +1040 -0
- package/dist/modules/staff/backend/staff/time-tracking/projects/page.js.map +7 -0
- package/dist/modules/staff/backend/staff/time-tracking/projects/page.meta.js +19 -0
- package/dist/modules/staff/backend/staff/time-tracking/projects/page.meta.js.map +7 -0
- package/dist/modules/staff/backend/staff/time-tracking/projects/projectFormConfig.js +353 -0
- package/dist/modules/staff/backend/staff/time-tracking/projects/projectFormConfig.js.map +7 -0
- package/dist/modules/staff/backend/staff/time-tracking/reports/[id]/page.js +457 -0
- package/dist/modules/staff/backend/staff/time-tracking/reports/[id]/page.js.map +7 -0
- package/dist/modules/staff/backend/staff/time-tracking/reports/[id]/page.meta.js +16 -0
- package/dist/modules/staff/backend/staff/time-tracking/reports/[id]/page.meta.js.map +7 -0
- package/dist/modules/staff/backend/staff/time-tracking/reports/create/page.js +697 -0
- package/dist/modules/staff/backend/staff/time-tracking/reports/create/page.js.map +7 -0
- package/dist/modules/staff/backend/staff/time-tracking/reports/create/page.meta.js +16 -0
- package/dist/modules/staff/backend/staff/time-tracking/reports/create/page.meta.js.map +7 -0
- package/dist/modules/staff/backend/staff/time-tracking/reports/page.js +174 -0
- package/dist/modules/staff/backend/staff/time-tracking/reports/page.js.map +7 -0
- package/dist/modules/staff/backend/staff/time-tracking/reports/page.meta.js +18 -0
- package/dist/modules/staff/backend/staff/time-tracking/reports/page.meta.js.map +7 -0
- package/dist/modules/staff/backend/staff/time-tracking/settings/page.js +546 -0
- package/dist/modules/staff/backend/staff/time-tracking/settings/page.js.map +7 -0
- package/dist/modules/staff/backend/staff/time-tracking/settings/page.meta.js +18 -0
- package/dist/modules/staff/backend/staff/time-tracking/settings/page.meta.js.map +7 -0
- package/dist/modules/staff/backend/staff/time-tracking/timesheet/GridView.js +710 -0
- package/dist/modules/staff/backend/staff/time-tracking/timesheet/GridView.js.map +7 -0
- package/dist/modules/staff/backend/staff/time-tracking/timesheet/page.js +646 -0
- package/dist/modules/staff/backend/staff/time-tracking/timesheet/page.js.map +7 -0
- package/dist/modules/staff/backend/staff/time-tracking/timesheet/page.meta.js +18 -0
- package/dist/modules/staff/backend/staff/time-tracking/timesheet/page.meta.js.map +7 -0
- package/dist/modules/staff/backend/staff/timesheets/page.js +4 -793
- package/dist/modules/staff/backend/staff/timesheets/page.js.map +2 -2
- package/dist/modules/staff/backend/staff/timesheets/page.meta.js +2 -7
- package/dist/modules/staff/backend/staff/timesheets/page.meta.js.map +2 -2
- package/dist/modules/staff/backend/staff/timesheets/projects/[id]/edit/page.js +5 -136
- package/dist/modules/staff/backend/staff/timesheets/projects/[id]/edit/page.js.map +2 -2
- package/dist/modules/staff/backend/staff/timesheets/projects/[id]/edit/page.meta.js +1 -7
- package/dist/modules/staff/backend/staff/timesheets/projects/[id]/edit/page.meta.js.map +2 -2
- package/dist/modules/staff/backend/staff/timesheets/projects/[id]/page.js +5 -445
- package/dist/modules/staff/backend/staff/timesheets/projects/[id]/page.js.map +3 -3
- package/dist/modules/staff/backend/staff/timesheets/projects/[id]/page.meta.js +1 -7
- package/dist/modules/staff/backend/staff/timesheets/projects/[id]/page.meta.js.map +2 -2
- package/dist/modules/staff/backend/staff/timesheets/projects/create/page.js +4 -76
- package/dist/modules/staff/backend/staff/timesheets/projects/create/page.js.map +2 -2
- package/dist/modules/staff/backend/staff/timesheets/projects/create/page.meta.js +1 -7
- package/dist/modules/staff/backend/staff/timesheets/projects/create/page.meta.js.map +2 -2
- package/dist/modules/staff/backend/staff/timesheets/projects/page.js +4 -613
- package/dist/modules/staff/backend/staff/timesheets/projects/page.js.map +2 -2
- package/dist/modules/staff/backend/staff/timesheets/projects/page.meta.js +2 -17
- package/dist/modules/staff/backend/staff/timesheets/projects/page.meta.js.map +2 -2
- package/dist/modules/staff/ce.js +40 -0
- package/dist/modules/staff/ce.js.map +2 -2
- package/dist/modules/staff/cli.js +166 -1
- package/dist/modules/staff/cli.js.map +2 -2
- package/dist/modules/staff/commands/index.js +5 -0
- package/dist/modules/staff/commands/index.js.map +2 -2
- package/dist/modules/staff/commands/timesheets-entries.js +569 -21
- package/dist/modules/staff/commands/timesheets-entries.js.map +3 -3
- package/dist/modules/staff/commands/timesheets-projects.js +450 -7
- package/dist/modules/staff/commands/timesheets-projects.js.map +2 -2
- package/dist/modules/staff/commands/timesheets-reports.js +1036 -0
- package/dist/modules/staff/commands/timesheets-reports.js.map +7 -0
- package/dist/modules/staff/commands/timesheets-tags.js +1015 -0
- package/dist/modules/staff/commands/timesheets-tags.js.map +7 -0
- package/dist/modules/staff/commands/timesheets-task-comments.js +438 -0
- package/dist/modules/staff/commands/timesheets-task-comments.js.map +7 -0
- package/dist/modules/staff/commands/timesheets-task-statuses.js +723 -0
- package/dist/modules/staff/commands/timesheets-task-statuses.js.map +7 -0
- package/dist/modules/staff/commands/timesheets-tasks.js +834 -0
- package/dist/modules/staff/commands/timesheets-tasks.js.map +7 -0
- package/dist/modules/staff/data/enrichers.js +426 -12
- package/dist/modules/staff/data/enrichers.js.map +2 -2
- package/dist/modules/staff/data/entities.js +552 -3
- package/dist/modules/staff/data/entities.js.map +2 -2
- package/dist/modules/staff/data/extensions.js +41 -0
- package/dist/modules/staff/data/extensions.js.map +7 -0
- package/dist/modules/staff/data/validators.js +254 -9
- package/dist/modules/staff/data/validators.js.map +2 -2
- package/dist/modules/staff/di.js +42 -1
- package/dist/modules/staff/di.js.map +2 -2
- package/dist/modules/staff/events.js +84 -6
- package/dist/modules/staff/events.js.map +2 -2
- package/dist/modules/staff/events.payloads.js +158 -0
- package/dist/modules/staff/events.payloads.js.map +7 -0
- package/dist/modules/staff/extension-points.js +261 -0
- package/dist/modules/staff/extension-points.js.map +7 -0
- package/dist/modules/staff/frontend/[orgSlug]/portal/time-reports/[id]/page.js +138 -0
- package/dist/modules/staff/frontend/[orgSlug]/portal/time-reports/[id]/page.js.map +7 -0
- package/dist/modules/staff/frontend/[orgSlug]/portal/time-reports/[id]/page.meta.js +12 -0
- package/dist/modules/staff/frontend/[orgSlug]/portal/time-reports/[id]/page.meta.js.map +7 -0
- package/dist/modules/staff/frontend/[orgSlug]/portal/time-reports/page.js +133 -0
- package/dist/modules/staff/frontend/[orgSlug]/portal/time-reports/page.js.map +7 -0
- package/dist/modules/staff/frontend/[orgSlug]/portal/time-reports/page.meta.js +19 -0
- package/dist/modules/staff/frontend/[orgSlug]/portal/time-reports/page.meta.js.map +7 -0
- package/dist/modules/staff/lib/crud.js +11 -1
- package/dist/modules/staff/lib/crud.js.map +2 -2
- package/dist/modules/staff/lib/time-tracking/access.js +92 -0
- package/dist/modules/staff/lib/time-tracking/access.js.map +7 -0
- package/dist/modules/staff/lib/time-tracking/billability.js +39 -0
- package/dist/modules/staff/lib/time-tracking/billability.js.map +7 -0
- package/dist/modules/staff/lib/time-tracking/capacity.js +68 -0
- package/dist/modules/staff/lib/time-tracking/capacity.js.map +7 -0
- package/dist/modules/staff/lib/time-tracking/componentContracts.js +15 -0
- package/dist/modules/staff/lib/time-tracking/componentContracts.js.map +7 -0
- package/dist/modules/staff/lib/time-tracking/cost.js +82 -0
- package/dist/modules/staff/lib/time-tracking/cost.js.map +7 -0
- package/dist/modules/staff/lib/time-tracking/duration.js +55 -0
- package/dist/modules/staff/lib/time-tracking/duration.js.map +7 -0
- package/dist/modules/staff/lib/time-tracking/featureAccess.js +41 -0
- package/dist/modules/staff/lib/time-tracking/featureAccess.js.map +7 -0
- package/dist/modules/staff/lib/time-tracking/interval.js +72 -0
- package/dist/modules/staff/lib/time-tracking/interval.js.map +7 -0
- package/dist/modules/staff/lib/time-tracking/migrateProjectCodes.js +88 -0
- package/dist/modules/staff/lib/time-tracking/migrateProjectCodes.js.map +7 -0
- package/dist/modules/staff/lib/time-tracking/moneyVisibility.js +10 -0
- package/dist/modules/staff/lib/time-tracking/moneyVisibility.js.map +7 -0
- package/dist/modules/staff/lib/time-tracking/overlap.js +87 -0
- package/dist/modules/staff/lib/time-tracking/overlap.js.map +7 -0
- package/dist/modules/staff/lib/time-tracking/portalRecipients.js +27 -0
- package/dist/modules/staff/lib/time-tracking/portalRecipients.js.map +7 -0
- package/dist/modules/staff/lib/time-tracking/portalReports.js +16 -0
- package/dist/modules/staff/lib/time-tracking/portalReports.js.map +7 -0
- package/dist/modules/staff/lib/time-tracking/projectCode.js +135 -0
- package/dist/modules/staff/lib/time-tracking/projectCode.js.map +7 -0
- package/dist/modules/staff/lib/time-tracking/reapplyRounding.js +187 -0
- package/dist/modules/staff/lib/time-tracking/reapplyRounding.js.map +7 -0
- package/dist/modules/staff/lib/time-tracking/recalculationRunner.js +80 -0
- package/dist/modules/staff/lib/time-tracking/recalculationRunner.js.map +7 -0
- package/dist/modules/staff/lib/time-tracking/recalculations.js +56 -0
- package/dist/modules/staff/lib/time-tracking/recalculations.js.map +7 -0
- package/dist/modules/staff/lib/time-tracking/registries/invoke.js +63 -0
- package/dist/modules/staff/lib/time-tracking/registries/invoke.js.map +7 -0
- package/dist/modules/staff/lib/time-tracking/registries/registry.js +62 -0
- package/dist/modules/staff/lib/time-tracking/registries/registry.js.map +7 -0
- package/dist/modules/staff/lib/time-tracking/registries/scope.js +21 -0
- package/dist/modules/staff/lib/time-tracking/registries/scope.js.map +7 -0
- package/dist/modules/staff/lib/time-tracking/rollup.js +34 -0
- package/dist/modules/staff/lib/time-tracking/rollup.js.map +7 -0
- package/dist/modules/staff/lib/time-tracking/rounding.js +66 -0
- package/dist/modules/staff/lib/time-tracking/rounding.js.map +7 -0
- package/dist/modules/staff/lib/time-tracking/roundingImpact.js +39 -0
- package/dist/modules/staff/lib/time-tracking/roundingImpact.js.map +7 -0
- package/dist/modules/staff/lib/time-tracking/settingKeys.js +214 -0
- package/dist/modules/staff/lib/time-tracking/settingKeys.js.map +7 -0
- package/dist/modules/staff/lib/time-tracking/settings.js +87 -0
- package/dist/modules/staff/lib/time-tracking/settings.js.map +7 -0
- package/dist/modules/staff/lib/time-tracking/sqlInClause.js +9 -0
- package/dist/modules/staff/lib/time-tracking/sqlInClause.js.map +7 -0
- package/dist/modules/staff/lib/time-tracking/timeEntrySources.js +48 -0
- package/dist/modules/staff/lib/time-tracking/timeEntrySources.js.map +7 -0
- package/dist/modules/staff/lib/time-tracking-ui/BoardFilterChips.js +283 -0
- package/dist/modules/staff/lib/time-tracking-ui/BoardFilterChips.js.map +7 -0
- package/dist/modules/staff/lib/time-tracking-ui/CustomerPicker.js +324 -0
- package/dist/modules/staff/lib/time-tracking-ui/CustomerPicker.js.map +7 -0
- package/dist/modules/staff/lib/time-tracking-ui/DurationInput.js +121 -0
- package/dist/modules/staff/lib/time-tracking-ui/DurationInput.js.map +7 -0
- package/dist/modules/staff/lib/time-tracking-ui/EntryDetailsFields.js +188 -0
- package/dist/modules/staff/lib/time-tracking-ui/EntryDetailsFields.js.map +7 -0
- package/dist/modules/staff/lib/time-tracking-ui/EntryFilterBar.js +89 -0
- package/dist/modules/staff/lib/time-tracking-ui/EntryFilterBar.js.map +7 -0
- package/dist/modules/staff/lib/time-tracking-ui/KanbanBoard.js +855 -0
- package/dist/modules/staff/lib/time-tracking-ui/KanbanBoard.js.map +7 -0
- package/dist/modules/staff/lib/time-tracking-ui/KanbanCard.js +360 -0
- package/dist/modules/staff/lib/time-tracking-ui/KanbanCard.js.map +7 -0
- package/dist/modules/staff/lib/time-tracking-ui/KanbanColumn.js +259 -0
- package/dist/modules/staff/lib/time-tracking-ui/KanbanColumn.js.map +7 -0
- package/dist/modules/staff/lib/time-tracking-ui/NewTaskDialog.js +238 -0
- package/dist/modules/staff/lib/time-tracking-ui/NewTaskDialog.js.map +7 -0
- package/dist/modules/staff/lib/time-tracking-ui/NoProjectAccess.js +130 -0
- package/dist/modules/staff/lib/time-tracking-ui/NoProjectAccess.js.map +7 -0
- package/dist/modules/staff/lib/time-tracking-ui/PeriodSelector.js +98 -0
- package/dist/modules/staff/lib/time-tracking-ui/PeriodSelector.js.map +7 -0
- package/dist/modules/staff/lib/time-tracking-ui/PhasePlaceholder.js +27 -0
- package/dist/modules/staff/lib/time-tracking-ui/PhasePlaceholder.js.map +7 -0
- package/dist/modules/staff/lib/time-tracking-ui/ProjectCodeField.js +166 -0
- package/dist/modules/staff/lib/time-tracking-ui/ProjectCodeField.js.map +7 -0
- package/dist/modules/staff/lib/time-tracking-ui/ProjectCurrencyField.js +295 -0
- package/dist/modules/staff/lib/time-tracking-ui/ProjectCurrencyField.js.map +7 -0
- package/dist/modules/staff/lib/time-tracking-ui/ProjectFormSections.js +212 -0
- package/dist/modules/staff/lib/time-tracking-ui/ProjectFormSections.js.map +7 -0
- package/dist/modules/staff/lib/time-tracking-ui/ProjectTeamDrawer.js +729 -0
- package/dist/modules/staff/lib/time-tracking-ui/ProjectTeamDrawer.js.map +7 -0
- package/dist/modules/staff/lib/time-tracking-ui/ReportSheet.js +217 -0
- package/dist/modules/staff/lib/time-tracking-ui/ReportSheet.js.map +7 -0
- package/dist/modules/staff/lib/time-tracking-ui/TagPicker.js +158 -0
- package/dist/modules/staff/lib/time-tracking-ui/TagPicker.js.map +7 -0
- package/dist/modules/staff/lib/time-tracking-ui/TaskBoardScreen.js +224 -0
- package/dist/modules/staff/lib/time-tracking-ui/TaskBoardScreen.js.map +7 -0
- package/dist/modules/staff/lib/time-tracking-ui/TaskCommentThread.js +93 -0
- package/dist/modules/staff/lib/time-tracking-ui/TaskCommentThread.js.map +7 -0
- package/dist/modules/staff/lib/time-tracking-ui/TaskDrawer.js +1196 -0
- package/dist/modules/staff/lib/time-tracking-ui/TaskDrawer.js.map +7 -0
- package/dist/modules/staff/lib/time-tracking-ui/TaskListView.js +176 -0
- package/dist/modules/staff/lib/time-tracking-ui/TaskListView.js.map +7 -0
- package/dist/modules/staff/lib/time-tracking-ui/TaskPicker.js +307 -0
- package/dist/modules/staff/lib/time-tracking-ui/TaskPicker.js.map +7 -0
- package/dist/modules/staff/lib/time-tracking-ui/TaskQuickLog.js +284 -0
- package/dist/modules/staff/lib/time-tracking-ui/TaskQuickLog.js.map +7 -0
- package/dist/modules/staff/lib/time-tracking-ui/TimeEntriesSummaryFooter.js +73 -0
- package/dist/modules/staff/lib/time-tracking-ui/TimeEntriesSummaryFooter.js.map +7 -0
- package/dist/modules/staff/lib/time-tracking-ui/TimeEntryDialog.js +1499 -0
- package/dist/modules/staff/lib/time-tracking-ui/TimeEntryDialog.js.map +7 -0
- package/dist/modules/staff/lib/time-tracking-ui/TimeEntryDurationCell.js +95 -0
- package/dist/modules/staff/lib/time-tracking-ui/TimeEntryDurationCell.js.map +7 -0
- package/dist/modules/staff/lib/time-tracking-ui/TimesheetCalendar.js +188 -0
- package/dist/modules/staff/lib/time-tracking-ui/TimesheetCalendar.js.map +7 -0
- package/dist/modules/staff/lib/time-tracking-ui/TimesheetPeriodFooter.js +61 -0
- package/dist/modules/staff/lib/time-tracking-ui/TimesheetPeriodFooter.js.map +7 -0
- package/dist/modules/staff/lib/time-tracking-ui/TimesheetViewSwitch.js +45 -0
- package/dist/modules/staff/lib/time-tracking-ui/TimesheetViewSwitch.js.map +7 -0
- package/dist/modules/staff/lib/time-tracking-ui/boardDirectory.js +176 -0
- package/dist/modules/staff/lib/time-tracking-ui/boardDirectory.js.map +7 -0
- package/dist/modules/staff/lib/time-tracking-ui/boardFilters.js +82 -0
- package/dist/modules/staff/lib/time-tracking-ui/boardFilters.js.map +7 -0
- package/dist/modules/staff/lib/time-tracking-ui/boardSummary.js +5 -0
- package/dist/modules/staff/lib/time-tracking-ui/boardSummary.js.map +7 -0
- package/dist/modules/staff/lib/time-tracking-ui/kanbanBoardData.js +152 -0
- package/dist/modules/staff/lib/time-tracking-ui/kanbanBoardData.js.map +7 -0
- package/dist/modules/staff/lib/time-tracking-ui/projectTeamAssignment.js +92 -0
- package/dist/modules/staff/lib/time-tracking-ui/projectTeamAssignment.js.map +7 -0
- package/dist/modules/staff/lib/time-tracking-ui/taskDrawerData.js +111 -0
- package/dist/modules/staff/lib/time-tracking-ui/taskDrawerData.js.map +7 -0
- package/dist/modules/staff/lib/time-tracking-ui/timeEntryDialogState.js +243 -0
- package/dist/modules/staff/lib/time-tracking-ui/timeEntryDialogState.js.map +7 -0
- package/dist/modules/staff/lib/time-tracking-ui/timeEntryListData.js +151 -0
- package/dist/modules/staff/lib/time-tracking-ui/timeEntryListData.js.map +7 -0
- package/dist/modules/staff/lib/time-tracking-ui/timeTrackingSettingsForm.js +105 -0
- package/dist/modules/staff/lib/time-tracking-ui/timeTrackingSettingsForm.js.map +7 -0
- package/dist/modules/staff/lib/time-tracking-ui/timesheetData.js +132 -0
- package/dist/modules/staff/lib/time-tracking-ui/timesheetData.js.map +7 -0
- package/dist/modules/staff/lib/time-tracking-ui/timesheetPeriod.js +205 -0
- package/dist/modules/staff/lib/time-tracking-ui/timesheetPeriod.js.map +7 -0
- package/dist/modules/staff/lib/time-tracking-ui/timesheetTargets.js +53 -0
- package/dist/modules/staff/lib/time-tracking-ui/timesheetTargets.js.map +7 -0
- package/dist/modules/staff/lib/time-tracking-ui/useBoardFilters.js +77 -0
- package/dist/modules/staff/lib/time-tracking-ui/useBoardFilters.js.map +7 -0
- package/dist/modules/staff/lib/time-tracking-ui/useTimesheetPreferences.js +151 -0
- package/dist/modules/staff/lib/time-tracking-ui/useTimesheetPreferences.js.map +7 -0
- package/dist/modules/staff/lib/timeTrackingSeeds.js +809 -0
- package/dist/modules/staff/lib/timeTrackingSeeds.js.map +7 -0
- package/dist/modules/staff/lib/timesheets/timeEntryCacheInvalidation.js +2 -3
- package/dist/modules/staff/lib/timesheets/timeEntryCacheInvalidation.js.map +2 -2
- package/dist/modules/staff/lib/timesheets/timeEntryDecoration.js +152 -0
- package/dist/modules/staff/lib/timesheets/timeEntryDecoration.js.map +7 -0
- package/dist/modules/staff/lib/timesheets-projects/budgetBurn.js +36 -0
- package/dist/modules/staff/lib/timesheets-projects/budgetBurn.js.map +7 -0
- package/dist/modules/staff/lib/timesheets-projects/budgetThreshold.js +66 -0
- package/dist/modules/staff/lib/timesheets-projects/budgetThreshold.js.map +7 -0
- package/dist/modules/staff/lib/timesheets-projects/budgetThresholdState.js +78 -0
- package/dist/modules/staff/lib/timesheets-projects/budgetThresholdState.js.map +7 -0
- package/dist/modules/staff/lib/timesheets-projects/computeProjectFinancials.js +81 -0
- package/dist/modules/staff/lib/timesheets-projects/computeProjectFinancials.js.map +7 -0
- package/dist/modules/staff/lib/timesheets-projects/reportSelection.js +44 -0
- package/dist/modules/staff/lib/timesheets-projects/reportSelection.js.map +7 -0
- package/dist/modules/staff/lib/timesheets-projects-ui/AssignMembersDialog.js +169 -0
- package/dist/modules/staff/lib/timesheets-projects-ui/AssignMembersDialog.js.map +7 -0
- package/dist/modules/staff/lib/timesheets-projects-ui/ProjectBudgetCell.js +22 -0
- package/dist/modules/staff/lib/timesheets-projects-ui/ProjectBudgetCell.js.map +7 -0
- package/dist/modules/staff/lib/timesheets-projects-ui/ProjectCard.js +51 -1
- package/dist/modules/staff/lib/timesheets-projects-ui/ProjectCard.js.map +2 -2
- package/dist/modules/staff/lib/timesheets-projects-ui/ProjectsBulkActionsBar.js +70 -0
- package/dist/modules/staff/lib/timesheets-projects-ui/ProjectsBulkActionsBar.js.map +7 -0
- package/dist/modules/staff/lib/timesheets-reports/buildReportSheet.js +63 -0
- package/dist/modules/staff/lib/timesheets-reports/buildReportSheet.js.map +7 -0
- package/dist/modules/staff/lib/timesheets-reports/loadReportData.js +170 -0
- package/dist/modules/staff/lib/timesheets-reports/loadReportData.js.map +7 -0
- package/dist/modules/staff/lib/timesheets-reports/pdf.js +205 -0
- package/dist/modules/staff/lib/timesheets-reports/pdf.js.map +7 -0
- package/dist/modules/staff/lib/timesheets-reports/reportApprovalPolicies.js +68 -0
- package/dist/modules/staff/lib/timesheets-reports/reportApprovalPolicies.js.map +7 -0
- package/dist/modules/staff/lib/timesheets-reports/reportConfigData.js +135 -0
- package/dist/modules/staff/lib/timesheets-reports/reportConfigData.js.map +7 -0
- package/dist/modules/staff/lib/timesheets-reports/reportConfigState.js +48 -0
- package/dist/modules/staff/lib/timesheets-reports/reportConfigState.js.map +7 -0
- package/dist/modules/staff/lib/timesheets-reports/reportExport.js +262 -0
- package/dist/modules/staff/lib/timesheets-reports/reportExport.js.map +7 -0
- package/dist/modules/staff/lib/timesheets-reports/reportExportFormats.js +34 -0
- package/dist/modules/staff/lib/timesheets-reports/reportExportFormats.js.map +7 -0
- package/dist/modules/staff/lib/timesheets-reports/reportGroupings.js +79 -0
- package/dist/modules/staff/lib/timesheets-reports/reportGroupings.js.map +7 -0
- package/dist/modules/staff/lib/timesheets-reports/reportLabels.js +11 -0
- package/dist/modules/staff/lib/timesheets-reports/reportLabels.js.map +7 -0
- package/dist/modules/staff/lib/timesheets-reports/reportReference.js +83 -0
- package/dist/modules/staff/lib/timesheets-reports/reportReference.js.map +7 -0
- package/dist/modules/staff/lib/timesheets-reports/reportRows.js +42 -0
- package/dist/modules/staff/lib/timesheets-reports/reportRows.js.map +7 -0
- package/dist/modules/staff/lib/timesheets-reports/reportSheetData.js +107 -0
- package/dist/modules/staff/lib/timesheets-reports/reportSheetData.js.map +7 -0
- package/dist/modules/staff/lib/timesheets-reports/reportTotals.js +289 -0
- package/dist/modules/staff/lib/timesheets-reports/reportTotals.js.map +7 -0
- package/dist/modules/staff/lib/timesheets-reports/xlsx.js +145 -0
- package/dist/modules/staff/lib/timesheets-reports/xlsx.js.map +7 -0
- package/dist/modules/staff/lib/timesheets-tasks/computeTaskRollups.js +116 -0
- package/dist/modules/staff/lib/timesheets-tasks/computeTaskRollups.js.map +7 -0
- package/dist/modules/staff/lib/timesheets-tasks/defaultStatusTemplate.js +50 -0
- package/dist/modules/staff/lib/timesheets-tasks/defaultStatusTemplate.js.map +7 -0
- package/dist/modules/staff/lib/timesheets-tasks/seedProjectStatuses.js +27 -0
- package/dist/modules/staff/lib/timesheets-tasks/seedProjectStatuses.js.map +7 -0
- package/dist/modules/staff/lib/timesheets-tasks/statusPositions.js +65 -0
- package/dist/modules/staff/lib/timesheets-tasks/statusPositions.js.map +7 -0
- package/dist/modules/staff/lib/timesheets-tasks/statusSlug.js +31 -0
- package/dist/modules/staff/lib/timesheets-tasks/statusSlug.js.map +7 -0
- package/dist/modules/staff/lib/timesheets-tasks/taskHierarchy.js +31 -0
- package/dist/modules/staff/lib/timesheets-tasks/taskHierarchy.js.map +7 -0
- package/dist/modules/staff/lib/timesheets-tasks/taskHoursTotals.js +17 -0
- package/dist/modules/staff/lib/timesheets-tasks/taskHoursTotals.js.map +7 -0
- package/dist/modules/staff/lib/timesheets-tasks/taskReference.js +51 -0
- package/dist/modules/staff/lib/timesheets-tasks/taskReference.js.map +7 -0
- package/dist/modules/staff/lib/timesheets-ui/CreateProjectDialog.js +11 -4
- package/dist/modules/staff/lib/timesheets-ui/CreateProjectDialog.js.map +2 -2
- package/dist/modules/staff/lib/timesheets-ui/ListView.js +369 -111
- package/dist/modules/staff/lib/timesheets-ui/ListView.js.map +2 -2
- package/dist/modules/staff/lib/timesheets-ui/TimerBar.js +47 -2
- package/dist/modules/staff/lib/timesheets-ui/TimerBar.js.map +2 -2
- package/dist/modules/staff/migrations/Migration20260813073131_staff.js +79 -0
- package/dist/modules/staff/migrations/Migration20260813073131_staff.js.map +7 -0
- package/dist/modules/staff/migrations/Migration20260824143357_staff.js +15 -0
- package/dist/modules/staff/migrations/Migration20260824143357_staff.js.map +7 -0
- package/dist/modules/staff/notifications.client.js +16 -0
- package/dist/modules/staff/notifications.client.js.map +7 -0
- package/dist/modules/staff/notifications.handlers.js +80 -0
- package/dist/modules/staff/notifications.handlers.js.map +7 -0
- package/dist/modules/staff/notifications.js +168 -0
- package/dist/modules/staff/notifications.js.map +2 -2
- package/dist/modules/staff/search.js +159 -1
- package/dist/modules/staff/search.js.map +2 -2
- package/dist/modules/staff/setup.js +20 -1
- package/dist/modules/staff/setup.js.map +2 -2
- package/dist/modules/staff/subscribers/time-project-access-requested-notification.js +68 -0
- package/dist/modules/staff/subscribers/time-project-access-requested-notification.js.map +7 -0
- package/dist/modules/staff/subscribers/time-project-budget-threshold-notification.js +149 -0
- package/dist/modules/staff/subscribers/time-project-budget-threshold-notification.js.map +7 -0
- package/dist/modules/staff/subscribers/time-report-approved-notification.js +78 -0
- package/dist/modules/staff/subscribers/time-report-approved-notification.js.map +7 -0
- package/dist/modules/staff/subscribers/time-report-portal-broadcast.js +78 -0
- package/dist/modules/staff/subscribers/time-report-portal-broadcast.js.map +7 -0
- package/dist/modules/staff/translations.js +4 -1
- package/dist/modules/staff/translations.js.map +2 -2
- package/dist/modules/staff/widgets/components.js +7 -0
- package/dist/modules/staff/widgets/components.js.map +7 -0
- package/dist/modules/staff/widgets/injection/timer-sidebar-indicator/widget.js +5 -4
- package/dist/modules/staff/widgets/injection/timer-sidebar-indicator/widget.js.map +2 -2
- package/dist/modules/staff/widgets/notifications/TimeProjectAccessRequestedRenderer.js +135 -0
- package/dist/modules/staff/widgets/notifications/TimeProjectAccessRequestedRenderer.js.map +7 -0
- package/dist/modules/staff/widgets/notifications/index.js +5 -0
- package/dist/modules/staff/widgets/notifications/index.js.map +7 -0
- package/dist/modules/staff/workers/timesheets-reapply-rounding.js +43 -0
- package/dist/modules/staff/workers/timesheets-reapply-rounding.js.map +7 -0
- package/generated/entities/staff_time_entry/index.ts +7 -0
- package/generated/entities/staff_time_entry_tag/index.ts +6 -0
- package/generated/entities/staff_time_project/index.ts +8 -0
- package/generated/entities/staff_time_report/index.ts +27 -0
- package/generated/entities/staff_time_report_entry/index.ts +12 -0
- package/generated/entities/staff_time_report_event/index.ts +9 -0
- package/generated/entities/staff_time_report_project/index.ts +6 -0
- package/generated/entities/staff_time_tag/index.ts +9 -0
- package/generated/entities/staff_time_task/index.ts +17 -0
- package/generated/entities/staff_time_task_comment/index.ts +9 -0
- package/generated/entities/staff_time_task_status/index.ts +13 -0
- package/generated/entities/staff_time_task_tag/index.ts +6 -0
- package/generated/entities.ids.generated.ts +11 -1
- package/generated/entity-fields-registry.ts +149 -0
- package/package.json +7 -7
- package/src/helpers/integration/timesheetFixtures.ts +31 -1
- package/src/modules/auth/README.md +1 -1
- package/src/modules/auth/cli.ts +35 -1
- package/src/modules/auth/i18n/de.json +7 -0
- package/src/modules/auth/i18n/en.json +7 -0
- package/src/modules/auth/i18n/es.json +7 -0
- package/src/modules/auth/i18n/ko.json +7 -0
- package/src/modules/auth/i18n/pl.json +7 -0
- package/src/modules/auth/lib/backendChrome.tsx +1 -0
- package/src/modules/customer_accounts/lib/customerRoleAcls.ts +79 -0
- package/src/modules/customer_accounts/setup.ts +1 -45
- package/src/modules/data_sync/AGENTS.md +34 -0
- package/src/modules/data_sync/api/options.ts +2 -0
- package/src/modules/data_sync/backend/data-sync/page.tsx +82 -33
- package/src/modules/data_sync/i18n/de.json +1 -0
- package/src/modules/data_sync/i18n/en.json +1 -0
- package/src/modules/data_sync/i18n/es.json +1 -0
- package/src/modules/data_sync/i18n/ko.json +1 -0
- package/src/modules/data_sync/i18n/pl.json +1 -0
- package/src/modules/data_sync/lib/adapter.ts +45 -0
- package/src/modules/data_sync/lib/start-controls.ts +89 -0
- package/src/modules/staff/AGENTS.md +769 -1
- package/src/modules/staff/acl.ts +9 -0
- package/src/modules/staff/ai-agents.ts +189 -0
- package/src/modules/staff/ai-tools/time-tracking-pack.ts +494 -0
- package/src/modules/staff/ai-tools/types.ts +82 -0
- package/src/modules/staff/ai-tools.ts +13 -0
- package/src/modules/staff/analytics.ts +113 -0
- package/src/modules/staff/api/guards.ts +85 -3
- package/src/modules/staff/api/leave-requests/accept/route.ts +0 -2
- package/src/modules/staff/api/leave-requests/reject/route.ts +0 -2
- package/src/modules/staff/api/portal/time-reports/[id]/route.ts +182 -0
- package/src/modules/staff/api/portal/time-reports/route.ts +270 -0
- package/src/modules/staff/api/team-members/self/route.ts +0 -2
- package/src/modules/staff/api/team-members/tags/assign/route.ts +0 -2
- package/src/modules/staff/api/team-members/tags/unassign/route.ts +0 -2
- package/src/modules/staff/api/timesheets/_shared/withTimesheetInterceptors.ts +292 -0
- package/src/modules/staff/api/timesheets/access-requests/route.ts +299 -0
- package/src/modules/staff/api/timesheets/my-projects/[projectId]/route.ts +15 -7
- package/src/modules/staff/api/timesheets/my-projects/route.ts +14 -1
- package/src/modules/staff/api/timesheets/my-work/myWorkAggregate.ts +90 -0
- package/src/modules/staff/api/timesheets/my-work/route.ts +475 -0
- package/src/modules/staff/api/timesheets/projects/kpis/route.ts +15 -2
- package/src/modules/staff/api/timesheets/reports/[id]/close/route.ts +203 -0
- package/src/modules/staff/api/timesheets/reports/[id]/export/route.ts +315 -0
- package/src/modules/staff/api/timesheets/reports/[id]/sheet/route.ts +220 -0
- package/src/modules/staff/api/timesheets/reports/[id]/unlock/route.ts +208 -0
- package/src/modules/staff/api/timesheets/reports/preview/route.ts +449 -0
- package/src/modules/staff/api/timesheets/reports/route.ts +386 -0
- package/src/modules/staff/api/timesheets/reports/shared.ts +121 -0
- package/src/modules/staff/api/timesheets/settings/reapply-rounding/route.ts +205 -0
- package/src/modules/staff/api/timesheets/settings/rounding-impact/route.ts +226 -0
- package/src/modules/staff/api/timesheets/settings/route.ts +242 -0
- package/src/modules/staff/api/timesheets/tags/access.ts +134 -0
- package/src/modules/staff/api/timesheets/tags/entry-assignments/route.ts +290 -0
- package/src/modules/staff/api/timesheets/tags/route.ts +177 -0
- package/src/modules/staff/api/timesheets/tags/task-assignments/route.ts +294 -0
- package/src/modules/staff/api/timesheets/task-statuses/route.ts +232 -0
- package/src/modules/staff/api/timesheets/tasks/[id]/comments/route.ts +532 -0
- package/src/modules/staff/api/timesheets/tasks/[id]/status/route.ts +271 -0
- package/src/modules/staff/api/timesheets/tasks/route.ts +443 -0
- package/src/modules/staff/api/timesheets/time-entries/[id]/duplicate/route.ts +306 -0
- package/src/modules/staff/api/timesheets/time-entries/[id]/segments/[segmentId]/route.ts +32 -10
- package/src/modules/staff/api/timesheets/time-entries/[id]/segments/route.ts +32 -17
- package/src/modules/staff/api/timesheets/time-entries/[id]/timer-start/route.ts +27 -6
- package/src/modules/staff/api/timesheets/time-entries/[id]/timer-stop/route.ts +36 -6
- package/src/modules/staff/api/timesheets/time-entries/bulk/route.ts +331 -34
- package/src/modules/staff/api/timesheets/time-entries/copy-day/route.ts +533 -0
- package/src/modules/staff/api/timesheets/time-entries/overlaps/route.ts +457 -0
- package/src/modules/staff/api/timesheets/time-entries/route.ts +378 -24
- package/src/modules/staff/api/timesheets/time-entries/start-timer/route.ts +31 -9
- package/src/modules/staff/api/timesheets/time-projects/[id]/change-currency/route.ts +246 -0
- package/src/modules/staff/api/timesheets/time-projects/[id]/employees/route.ts +62 -2
- package/src/modules/staff/api/timesheets/time-projects/route.ts +397 -22
- package/src/modules/staff/backend/staff/time-tracking/board/page.meta.ts +14 -0
- package/src/modules/staff/backend/staff/time-tracking/board/page.tsx +151 -0
- package/src/modules/staff/backend/staff/time-tracking/entries/page.meta.ts +14 -0
- package/src/modules/staff/backend/staff/time-tracking/entries/page.tsx +1229 -0
- package/src/modules/staff/backend/staff/time-tracking/page.meta.ts +11 -0
- package/src/modules/staff/backend/staff/time-tracking/page.tsx +823 -0
- package/src/modules/staff/backend/staff/time-tracking/projects/[id]/board/page.meta.ts +12 -0
- package/src/modules/staff/backend/staff/time-tracking/projects/[id]/board/page.tsx +81 -0
- package/src/modules/staff/backend/staff/time-tracking/projects/[id]/edit/page.meta.ts +12 -0
- package/src/modules/staff/backend/staff/time-tracking/projects/[id]/edit/page.tsx +275 -0
- package/src/modules/staff/backend/staff/time-tracking/projects/[id]/page.meta.ts +12 -0
- package/src/modules/staff/backend/staff/time-tracking/projects/[id]/page.tsx +1420 -0
- package/src/modules/staff/backend/staff/time-tracking/projects/create/page.meta.ts +12 -0
- package/src/modules/staff/backend/staff/time-tracking/projects/create/page.tsx +109 -0
- package/src/modules/staff/backend/staff/time-tracking/projects/page.meta.ts +15 -0
- package/src/modules/staff/backend/staff/time-tracking/projects/page.tsx +1254 -0
- package/src/modules/staff/backend/staff/time-tracking/projects/projectFormConfig.ts +466 -0
- package/src/modules/staff/backend/staff/time-tracking/reports/[id]/page.meta.ts +12 -0
- package/src/modules/staff/backend/staff/time-tracking/reports/[id]/page.tsx +632 -0
- package/src/modules/staff/backend/staff/time-tracking/reports/create/page.meta.ts +12 -0
- package/src/modules/staff/backend/staff/time-tracking/reports/create/page.tsx +867 -0
- package/src/modules/staff/backend/staff/time-tracking/reports/page.meta.ts +14 -0
- package/src/modules/staff/backend/staff/time-tracking/reports/page.tsx +246 -0
- package/src/modules/staff/backend/staff/time-tracking/settings/page.meta.ts +14 -0
- package/src/modules/staff/backend/staff/time-tracking/settings/page.tsx +701 -0
- package/src/modules/staff/backend/staff/time-tracking/timesheet/GridView.tsx +953 -0
- package/src/modules/staff/backend/staff/time-tracking/timesheet/page.meta.ts +14 -0
- package/src/modules/staff/backend/staff/time-tracking/timesheet/page.tsx +800 -0
- package/src/modules/staff/backend/staff/timesheets/page.meta.ts +6 -6
- package/src/modules/staff/backend/staff/timesheets/page.tsx +12 -1010
- package/src/modules/staff/backend/staff/timesheets/projects/[id]/edit/page.meta.ts +0 -6
- package/src/modules/staff/backend/staff/timesheets/projects/[id]/edit/page.tsx +4 -158
- package/src/modules/staff/backend/staff/timesheets/projects/[id]/page.meta.ts +0 -6
- package/src/modules/staff/backend/staff/timesheets/projects/[id]/page.tsx +4 -609
- package/src/modules/staff/backend/staff/timesheets/projects/create/page.meta.ts +0 -6
- package/src/modules/staff/backend/staff/timesheets/projects/create/page.tsx +3 -89
- package/src/modules/staff/backend/staff/timesheets/projects/page.meta.ts +2 -19
- package/src/modules/staff/backend/staff/timesheets/projects/page.tsx +3 -778
- package/src/modules/staff/ce.ts +63 -0
- package/src/modules/staff/cli.ts +207 -1
- package/src/modules/staff/commands/index.ts +5 -0
- package/src/modules/staff/commands/timesheets-entries.ts +1006 -28
- package/src/modules/staff/commands/timesheets-projects.ts +593 -7
- package/src/modules/staff/commands/timesheets-reports.ts +1329 -0
- package/src/modules/staff/commands/timesheets-tags.ts +1315 -0
- package/src/modules/staff/commands/timesheets-task-comments.ts +583 -0
- package/src/modules/staff/commands/timesheets-task-statuses.ts +889 -0
- package/src/modules/staff/commands/timesheets-tasks.ts +1121 -0
- package/src/modules/staff/data/enrichers.ts +675 -10
- package/src/modules/staff/data/entities.ts +494 -2
- package/src/modules/staff/data/extensions.ts +68 -0
- package/src/modules/staff/data/validators.ts +348 -3
- package/src/modules/staff/di.ts +96 -0
- package/src/modules/staff/events.payloads.ts +268 -0
- package/src/modules/staff/events.ts +87 -5
- package/src/modules/staff/extension-points.ts +308 -0
- package/src/modules/staff/frontend/[orgSlug]/portal/time-reports/[id]/page.meta.ts +10 -0
- package/src/modules/staff/frontend/[orgSlug]/portal/time-reports/[id]/page.tsx +225 -0
- package/src/modules/staff/frontend/[orgSlug]/portal/time-reports/page.meta.ts +17 -0
- package/src/modules/staff/frontend/[orgSlug]/portal/time-reports/page.tsx +182 -0
- package/src/modules/staff/i18n/de.json +899 -0
- package/src/modules/staff/i18n/en.json +899 -0
- package/src/modules/staff/i18n/es.json +899 -0
- package/src/modules/staff/i18n/ko.json +899 -0
- package/src/modules/staff/i18n/pl.json +899 -0
- package/src/modules/staff/lib/crud.ts +14 -3
- package/src/modules/staff/lib/time-tracking/access.ts +155 -0
- package/src/modules/staff/lib/time-tracking/billability.ts +68 -0
- package/src/modules/staff/lib/time-tracking/capacity.ts +124 -0
- package/src/modules/staff/lib/time-tracking/componentContracts.ts +26 -0
- package/src/modules/staff/lib/time-tracking/cost.ts +144 -0
- package/src/modules/staff/lib/time-tracking/duration.ts +68 -0
- package/src/modules/staff/lib/time-tracking/featureAccess.ts +115 -0
- package/src/modules/staff/lib/time-tracking/interval.ts +90 -0
- package/src/modules/staff/lib/time-tracking/migrateProjectCodes.ts +161 -0
- package/src/modules/staff/lib/time-tracking/moneyVisibility.ts +37 -0
- package/src/modules/staff/lib/time-tracking/overlap.ts +159 -0
- package/src/modules/staff/lib/time-tracking/portalRecipients.ts +76 -0
- package/src/modules/staff/lib/time-tracking/portalReports.ts +31 -0
- package/src/modules/staff/lib/time-tracking/projectCode.ts +221 -0
- package/src/modules/staff/lib/time-tracking/reapplyRounding.ts +313 -0
- package/src/modules/staff/lib/time-tracking/recalculationRunner.ts +118 -0
- package/src/modules/staff/lib/time-tracking/recalculations.ts +135 -0
- package/src/modules/staff/lib/time-tracking/registries/invoke.ts +122 -0
- package/src/modules/staff/lib/time-tracking/registries/registry.ts +124 -0
- package/src/modules/staff/lib/time-tracking/registries/scope.ts +56 -0
- package/src/modules/staff/lib/time-tracking/rollup.ts +59 -0
- package/src/modules/staff/lib/time-tracking/rounding.ts +126 -0
- package/src/modules/staff/lib/time-tracking/roundingImpact.ts +75 -0
- package/src/modules/staff/lib/time-tracking/settingKeys.ts +274 -0
- package/src/modules/staff/lib/time-tracking/settings.ts +168 -0
- package/src/modules/staff/lib/time-tracking/sqlInClause.ts +22 -0
- package/src/modules/staff/lib/time-tracking/timeEntrySources.ts +85 -0
- package/src/modules/staff/lib/time-tracking-ui/BoardFilterChips.tsx +360 -0
- package/src/modules/staff/lib/time-tracking-ui/CustomerPicker.tsx +427 -0
- package/src/modules/staff/lib/time-tracking-ui/DurationInput.tsx +192 -0
- package/src/modules/staff/lib/time-tracking-ui/EntryDetailsFields.tsx +224 -0
- package/src/modules/staff/lib/time-tracking-ui/EntryFilterBar.tsx +144 -0
- package/src/modules/staff/lib/time-tracking-ui/KanbanBoard.tsx +1065 -0
- package/src/modules/staff/lib/time-tracking-ui/KanbanCard.tsx +435 -0
- package/src/modules/staff/lib/time-tracking-ui/KanbanColumn.tsx +316 -0
- package/src/modules/staff/lib/time-tracking-ui/NewTaskDialog.tsx +291 -0
- package/src/modules/staff/lib/time-tracking-ui/NoProjectAccess.tsx +162 -0
- package/src/modules/staff/lib/time-tracking-ui/PeriodSelector.tsx +149 -0
- package/src/modules/staff/lib/time-tracking-ui/PhasePlaceholder.tsx +31 -0
- package/src/modules/staff/lib/time-tracking-ui/ProjectCodeField.tsx +230 -0
- package/src/modules/staff/lib/time-tracking-ui/ProjectCurrencyField.tsx +432 -0
- package/src/modules/staff/lib/time-tracking-ui/ProjectFormSections.tsx +323 -0
- package/src/modules/staff/lib/time-tracking-ui/ProjectTeamDrawer.tsx +930 -0
- package/src/modules/staff/lib/time-tracking-ui/ReportSheet.tsx +357 -0
- package/src/modules/staff/lib/time-tracking-ui/TagPicker.tsx +195 -0
- package/src/modules/staff/lib/time-tracking-ui/TaskBoardScreen.tsx +243 -0
- package/src/modules/staff/lib/time-tracking-ui/TaskCommentThread.tsx +137 -0
- package/src/modules/staff/lib/time-tracking-ui/TaskDrawer.tsx +1405 -0
- package/src/modules/staff/lib/time-tracking-ui/TaskListView.tsx +260 -0
- package/src/modules/staff/lib/time-tracking-ui/TaskPicker.tsx +435 -0
- package/src/modules/staff/lib/time-tracking-ui/TaskQuickLog.tsx +296 -0
- package/src/modules/staff/lib/time-tracking-ui/TimeEntriesSummaryFooter.tsx +97 -0
- package/src/modules/staff/lib/time-tracking-ui/TimeEntryDialog.tsx +1929 -0
- package/src/modules/staff/lib/time-tracking-ui/TimeEntryDurationCell.tsx +126 -0
- package/src/modules/staff/lib/time-tracking-ui/TimesheetCalendar.tsx +265 -0
- package/src/modules/staff/lib/time-tracking-ui/TimesheetPeriodFooter.tsx +78 -0
- package/src/modules/staff/lib/time-tracking-ui/TimesheetViewSwitch.tsx +70 -0
- package/src/modules/staff/lib/time-tracking-ui/boardDirectory.ts +231 -0
- package/src/modules/staff/lib/time-tracking-ui/boardFilters.ts +128 -0
- package/src/modules/staff/lib/time-tracking-ui/boardSummary.ts +20 -0
- package/src/modules/staff/lib/time-tracking-ui/kanbanBoardData.ts +228 -0
- package/src/modules/staff/lib/time-tracking-ui/projectTeamAssignment.ts +143 -0
- package/src/modules/staff/lib/time-tracking-ui/taskDrawerData.ts +194 -0
- package/src/modules/staff/lib/time-tracking-ui/timeEntryDialogState.ts +400 -0
- package/src/modules/staff/lib/time-tracking-ui/timeEntryListData.ts +271 -0
- package/src/modules/staff/lib/time-tracking-ui/timeTrackingSettingsForm.ts +168 -0
- package/src/modules/staff/lib/time-tracking-ui/timesheetData.ts +225 -0
- package/src/modules/staff/lib/time-tracking-ui/timesheetPeriod.ts +278 -0
- package/src/modules/staff/lib/time-tracking-ui/timesheetTargets.ts +93 -0
- package/src/modules/staff/lib/time-tracking-ui/useBoardFilters.ts +107 -0
- package/src/modules/staff/lib/time-tracking-ui/useTimesheetPreferences.ts +245 -0
- package/src/modules/staff/lib/timeTrackingSeeds.ts +1037 -0
- package/src/modules/staff/lib/timesheets/timeEntryCacheInvalidation.ts +11 -10
- package/src/modules/staff/lib/timesheets/timeEntryDecoration.ts +256 -0
- package/src/modules/staff/lib/timesheets-projects/budgetBurn.ts +60 -0
- package/src/modules/staff/lib/timesheets-projects/budgetThreshold.ts +135 -0
- package/src/modules/staff/lib/timesheets-projects/budgetThresholdState.ts +137 -0
- package/src/modules/staff/lib/timesheets-projects/computeProjectFinancials.ts +142 -0
- package/src/modules/staff/lib/timesheets-projects/reportSelection.ts +82 -0
- package/src/modules/staff/lib/timesheets-projects-ui/AssignMembersDialog.tsx +237 -0
- package/src/modules/staff/lib/timesheets-projects-ui/ProjectBudgetCell.tsx +33 -0
- package/src/modules/staff/lib/timesheets-projects-ui/ProjectCard.tsx +56 -1
- package/src/modules/staff/lib/timesheets-projects-ui/ProjectsBulkActionsBar.tsx +90 -0
- package/src/modules/staff/lib/timesheets-reports/buildReportSheet.ts +125 -0
- package/src/modules/staff/lib/timesheets-reports/loadReportData.ts +251 -0
- package/src/modules/staff/lib/timesheets-reports/pdf.ts +263 -0
- package/src/modules/staff/lib/timesheets-reports/reportApprovalPolicies.ts +137 -0
- package/src/modules/staff/lib/timesheets-reports/reportConfigData.ts +247 -0
- package/src/modules/staff/lib/timesheets-reports/reportConfigState.ts +89 -0
- package/src/modules/staff/lib/timesheets-reports/reportExport.ts +373 -0
- package/src/modules/staff/lib/timesheets-reports/reportExportFormats.ts +71 -0
- package/src/modules/staff/lib/timesheets-reports/reportGroupings.ts +153 -0
- package/src/modules/staff/lib/timesheets-reports/reportLabels.ts +23 -0
- package/src/modules/staff/lib/timesheets-reports/reportReference.ts +146 -0
- package/src/modules/staff/lib/timesheets-reports/reportRows.ts +89 -0
- package/src/modules/staff/lib/timesheets-reports/reportSheetData.ts +196 -0
- package/src/modules/staff/lib/timesheets-reports/reportTotals.ts +544 -0
- package/src/modules/staff/lib/timesheets-reports/xlsx.ts +207 -0
- package/src/modules/staff/lib/timesheets-tasks/computeTaskRollups.ts +238 -0
- package/src/modules/staff/lib/timesheets-tasks/defaultStatusTemplate.ts +90 -0
- package/src/modules/staff/lib/timesheets-tasks/seedProjectStatuses.ts +43 -0
- package/src/modules/staff/lib/timesheets-tasks/statusPositions.ts +132 -0
- package/src/modules/staff/lib/timesheets-tasks/statusSlug.ts +43 -0
- package/src/modules/staff/lib/timesheets-tasks/taskHierarchy.ts +99 -0
- package/src/modules/staff/lib/timesheets-tasks/taskHoursTotals.ts +42 -0
- package/src/modules/staff/lib/timesheets-tasks/taskReference.ts +108 -0
- package/src/modules/staff/lib/timesheets-ui/CreateProjectDialog.tsx +18 -4
- package/src/modules/staff/lib/timesheets-ui/ListView.tsx +504 -149
- package/src/modules/staff/lib/timesheets-ui/TimerBar.tsx +50 -2
- package/src/modules/staff/migrations/.snapshot-open-mercato.json +3244 -228
- package/src/modules/staff/migrations/Migration20260813073131_staff.ts +94 -0
- package/src/modules/staff/migrations/Migration20260824143357_staff.ts +17 -0
- package/src/modules/staff/notifications.client.ts +16 -0
- package/src/modules/staff/notifications.handlers.ts +88 -0
- package/src/modules/staff/notifications.ts +163 -0
- package/src/modules/staff/search.ts +191 -1
- package/src/modules/staff/setup.ts +22 -0
- package/src/modules/staff/subscribers/time-project-access-requested-notification.ts +95 -0
- package/src/modules/staff/subscribers/time-project-budget-threshold-notification.ts +226 -0
- package/src/modules/staff/subscribers/time-report-approved-notification.ts +116 -0
- package/src/modules/staff/subscribers/time-report-portal-broadcast.ts +120 -0
- package/src/modules/staff/translations.ts +17 -0
- package/src/modules/staff/widgets/components.ts +34 -0
- package/src/modules/staff/widgets/injection/timer-sidebar-indicator/widget.tsx +6 -5
- package/src/modules/staff/widgets/notifications/TimeProjectAccessRequestedRenderer.tsx +138 -0
- package/src/modules/staff/widgets/notifications/index.ts +1 -0
- package/src/modules/staff/workers/timesheets-reapply-rounding.ts +59 -0
- package/dist/modules/staff/backend/staff/timesheets/projects/projectFormConfig.js +0 -123
- package/dist/modules/staff/backend/staff/timesheets/projects/projectFormConfig.js.map +0 -7
- package/src/modules/staff/backend/staff/timesheets/projects/projectFormConfig.ts +0 -138
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../../../../src/modules/staff/lib/time-tracking-ui/TimeEntryDialog.tsx"],
|
|
4
|
+
"sourcesContent": ["\"use client\"\n\nimport * as React from 'react'\nimport { z } from 'zod'\nimport { useQuery, useQueryClient } from '@tanstack/react-query'\nimport { Lock, X } from 'lucide-react'\nimport {\n Dialog,\n DialogContent,\n DialogFooter,\n DialogHeader,\n DialogTitle,\n} from '@open-mercato/ui/primitives/dialog'\nimport { Alert, AlertDescription, AlertTitle } from '@open-mercato/ui/primitives/alert'\nimport { Badge } from '@open-mercato/ui/primitives/badge'\nimport { Button } from '@open-mercato/ui/primitives/button'\nimport { Input } from '@open-mercato/ui/primitives/input'\nimport { Kbd } from '@open-mercato/ui/primitives/kbd'\nimport { Label } from '@open-mercato/ui/primitives/label'\nimport {\n Select,\n SelectContent,\n SelectItem,\n SelectTrigger,\n SelectValue,\n} from '@open-mercato/ui/primitives/select'\nimport { Separator } from '@open-mercato/ui/primitives/separator'\nimport { Spinner } from '@open-mercato/ui/primitives/spinner'\nimport { Switch } from '@open-mercato/ui/primitives/switch'\nimport { ErrorMessage, LoadingMessage } from '@open-mercato/ui/backend/detail'\nimport { apiCall, apiCallOrThrow, withScopedApiRequestHeaders } from '@open-mercato/ui/backend/utils/apiCall'\nimport { TagPicker, tagChipStyle } from './TagPicker'\nimport { TaskPicker, type TaskPickerItem, type TaskPickerStatus } from './TaskPicker'\nimport { slugifyProjectName } from '../time-tracking/projectCode'\nimport { autoColorFromName } from '../timesheets-ui/colors'\nimport { buildOptimisticLockHeader } from '@open-mercato/ui/backend/utils/optimisticLock'\nimport { surfaceRecordConflict } from '@open-mercato/ui/backend/conflicts'\nimport { normalizeCrudServerError } from '@open-mercato/ui/backend/utils/serverErrors'\nimport { useBackendChrome } from '@open-mercato/ui/backend/BackendChromeProvider'\nimport { useConfirmDialog } from '@open-mercato/ui/backend/confirm-dialog'\nimport { useGuardedMutation } from '@open-mercato/ui/backend/injection/useGuardedMutation'\nimport { InjectionSpot, useInjectionSpotEvents } from '@open-mercato/ui/backend/injection/InjectionSpot'\nimport { extensionPoints } from '@open-mercato/core/modules/staff/extension-points'\nimport { registerComponent } from '@open-mercato/shared/modules/widgets/component-registry'\nimport { useRegisteredComponent } from '@open-mercato/ui/backend/injection/useRegisteredComponent'\nimport {\n callbackProp,\n opaqueProp,\n optionalCallbackProp,\n} from '../time-tracking/componentContracts'\nimport { extensionSpotChildId } from '@open-mercato/shared/modules/widgets/extension-points'\nimport { flash } from '@open-mercato/ui/backend/FlashMessages'\nimport { formatCurrency } from '@open-mercato/ui/utils/format'\nimport { hasFeature } from '@open-mercato/shared/security/features'\nimport { useT } from '@open-mercato/shared/lib/i18n/context'\nimport { useOrganizationScopeVersion } from '@open-mercato/shared/lib/frontend/useOrganizationScope'\nimport { createLogger } from '@open-mercato/shared/lib/logger'\nimport { DurationInput } from './DurationInput'\nimport { formatDuration } from '../time-tracking/duration'\nimport { parseClock } from '../time-tracking/interval'\nimport { applicableRate, entryAmount } from '../time-tracking/cost'\nimport { roundMinutes, DEFAULT_ROUNDING_SETTINGS, type RoundingSettings } from '../time-tracking/rounding'\nimport {\n combineDateAndClock,\n createIntervalState,\n reduceIntervalState,\n resetIntervalState,\n readRowItems,\n readRowString,\n shiftIsoDate,\n todayIsoDate,\n toOverlapEntry,\n toProjectOption,\n toTaskOption,\n toTimeEntryRecord,\n type OverlapEntry,\n type ProjectOption,\n type TagOption,\n type TaskOption,\n type TimeEntryRecord,\n type TimeIntervalState,\n} from './timeEntryDialogState'\n\nconst logger = createLogger('staff').child({ component: 'TimeEntryDialog' })\n\nexport const TIME_ENTRY_DIALOG_MUTATION_CONTEXT_ID = 'staff.time_tracking.entryDialog'\nconst TIME_ENTRY_ENTITY_ID = 'staff:staff_time_entry'\n\ntype TimeEntryFormInjectionContext = {\n formId: string\n entityId: string\n recordId: string | null\n resourceKind: string\n resourceId: string\n operation: 'create' | 'update'\n locked: boolean\n retryLastMutation: () => Promise<boolean>\n}\n\nfunction fieldValueSnapshot(value: unknown): string {\n return JSON.stringify(value ?? null)\n}\nexport const RATES_FEATURE = 'staff.timesheets.rates.view'\nexport const ENTRIES_LIST_PATH = '/backend/staff/time-tracking/entries'\nexport const REPORTS_PATH = '/backend/staff/time-tracking/reports'\n\nconst DIALOG_QUERY_ROOT = ['staff', 'time-tracking', 'entry-dialog'] as const\nconst DIRECTORY_PAGE_SIZE = 100\nconst TASK_SEARCH_PAGE_SIZE = 50\nconst TASK_SEARCH_DEBOUNCE_MS = 250\n\nexport type TimeEntryDialogDefaults = {\n date?: string\n taskId?: string | null\n timeProjectId?: string | null\n durationMinutes?: number | null\n startClock?: string | null\n endClock?: string | null\n description?: string | null\n isBillable?: boolean | null\n tagIds?: string[]\n}\n\nexport type TimeEntryDialogProps = {\n open: boolean\n onOpenChange: (open: boolean) => void\n /** Present \u2192 edit that entry; absent \u2192 a brand new one. */\n entryId?: string | null\n /** Seed values for a new entry: a stopped timer, a grid cell, a board card. */\n defaults?: TimeEntryDialogDefaults\n /** Sub-title line, e.g. \"Created from a stopped timer \u00B7 01:14:02\". */\n headerHint?: string | null\n /** Fired after every successful write, including \"save and add another\". */\n onSaved?: (result: { id: string | null; keptOpen: boolean }) => void\n /** \"Show that entry\" on the overlap warning; falls back to the entries list. */\n onShowEntry?: (entryId: string) => void\n}\n\ntype SettingsPayload = {\n rounding: RoundingSettings\n defaults: { billable: boolean; chainStartFromPreviousEnd: boolean }\n warnings: { overlap: boolean }\n}\n\nconst FALLBACK_SETTINGS: SettingsPayload = {\n rounding: { ...DEFAULT_ROUNDING_SETTINGS },\n defaults: { billable: true, chainStartFromPreviousEnd: true },\n warnings: { overlap: true },\n}\n\nfunction readSettings(payload: unknown): SettingsPayload {\n const source = (payload ?? {}) as Record<string, unknown>\n const rounding = (source.rounding ?? {}) as Record<string, unknown>\n const defaults = (source.defaults ?? {}) as Record<string, unknown>\n const warnings = (source.warnings ?? {}) as Record<string, unknown>\n return {\n rounding: {\n unitMinutes:\n rounding.unitMinutes === 5 || rounding.unitMinutes === 10 || rounding.unitMinutes === 15\n ? rounding.unitMinutes\n : 0,\n direction: rounding.direction === 'nearest' ? 'nearest' : 'up',\n },\n defaults: {\n billable: typeof defaults.billable === 'boolean' ? defaults.billable : true,\n chainStartFromPreviousEnd:\n typeof defaults.chainStartFromPreviousEnd === 'boolean' ? defaults.chainStartFromPreviousEnd : true,\n },\n warnings: { overlap: typeof warnings.overlap === 'boolean' ? warnings.overlap : true },\n }\n}\n\nfunction parseRateText(value: string): number | null {\n const trimmed = value.trim().replace(',', '.')\n if (!trimmed) return null\n const parsed = Number(trimmed)\n return Number.isFinite(parsed) && parsed >= 0 ? parsed : null\n}\n\n/**\n * Everything a person can type into the form, in the one shape both the seed and\n * the live state can be expressed in. Comparing two of these is how the dialog\n * knows whether closing it would throw work away.\n */\ntype FormValues = {\n taskId: string | null\n description: string\n date: string\n startText: string\n endText: string\n durationMinutes: number | null\n isBillable: boolean\n rateOverrideEnabled: boolean\n rateOverrideAmount: number | null\n tagIds: string[]\n}\n\ntype SaveMode = 'close' | 'again'\n\n/** The two controls a failed save can point at, empty when nothing is wrong. */\ntype FieldIssues = {\n task: string | null\n duration: string | null\n}\n\nconst NO_FIELD_ISSUES: FieldIssues = { task: null, duration: null }\n\n/**\n * A server complaint that names a field belongs under that field, not in a\n * toast. Only the two the form can point at are claimed; anything else stays\n * global and is flashed.\n */\nfunction readFieldIssues(error: unknown): FieldIssues | null {\n const { fieldErrors } = normalizeCrudServerError(error)\n if (!fieldErrors) return null\n const task = fieldErrors.taskId ?? fieldErrors.task ?? null\n const duration = fieldErrors.durationMinutes ?? fieldErrors.duration ?? null\n if (!task && !duration) return null\n return { task, duration }\n}\n\nfunction formSnapshot(values: FormValues): string {\n return JSON.stringify([\n values.taskId,\n values.description.trim(),\n values.date,\n values.startText,\n values.endText,\n values.durationMinutes,\n values.isBillable,\n values.rateOverrideEnabled,\n values.rateOverrideAmount,\n [...values.tagIds].sort((a, b) => (a < b ? -1 : a > b ? 1 : 0)),\n ])\n}\n\n/**\n * Two requests, merged.\n *\n * A single term cannot be routed to one field by shape \u2014 \"Booking\" is a title\n * word and \"AWR\" is a code, and both are one alphanumeric token \u2014 and the query\n * engine has no OR across fields, so the picker asks both questions and unions\n * the answers. This is what makes task 400 of 900 findable: the directory the\n * picker opens on is only ever its first page.\n */\nasync function fetchTaskOptions(term: string): Promise<TaskOption[]> {\n const params = new URLSearchParams({\n page: '1',\n pageSize: String(TASK_SEARCH_PAGE_SIZE),\n sortField: 'title',\n sortDir: 'asc',\n })\n const responses = await Promise.all(\n [\n `/api/staff/timesheets/tasks?${params.toString()}&q=${encodeURIComponent(term)}`,\n `/api/staff/timesheets/tasks?${params.toString()}&reference=${encodeURIComponent(term)}`,\n ].map((url) => apiCall<Record<string, unknown>>(url).catch(() => null)),\n )\n const merged = new Map<string, TaskOption>()\n for (const response of responses) {\n if (!response?.ok) continue\n for (const row of readRowItems(response.result)) {\n const option = toTaskOption(row)\n if (option && !merged.has(option.id)) merged.set(option.id, option)\n }\n }\n return [...merged.values()]\n}\n\n/** Resolves the entry's own task when it sorts outside the directory's first page. */\nasync function fetchTaskById(id: string): Promise<TaskOption | null> {\n const call = await apiCall<Record<string, unknown>>(\n `/api/staff/timesheets/tasks?ids=${encodeURIComponent(id)}&pageSize=1`,\n )\n if (!call.ok) return null\n const row = readRowItems(call.result)[0]\n return row ? toTaskOption(row) : null\n}\n\nfunction readErrorCode(error: unknown): string | null {\n const body = (error as { body?: { code?: unknown; error?: unknown } } | null)?.body\n if (body && typeof body.code === 'string') return body.code\n if (body && typeof body.error === 'string') return body.error\n return null\n}\n\n/**\n * The full time entry form of screen 8, plus the two edge states of screen 9.\n * Every surface of the module funnels through it \u2014 the entries list, the\n * timesheet, the board and the task drawer \u2014 so it takes an entry id (edit) or a\n * set of defaults (create) and owns nothing else about its host.\n *\n * Four decisions carry the design intent:\n *\n * 1. **The 2-of-3 arithmetic lives in one reducer, not in the render path.**\n * `reduceIntervalState` calls `deriveInterval` with exactly the two anchor\n * fields and stores which one came back computed, so the `wyliczone` badge is\n * read off the state rather than recomputed. `start` is the preferred anchor,\n * which is what makes editing the end restate the duration (note 1).\n * 2. **The duration field is never rewritten while it is being typed.**\n * `DurationInput` keeps unparseable text on its own; the dialog only remounts\n * it \u2014 via `durationEpoch` \u2014 when the *derived* duration changed, i.e. when\n * the user was editing some other field.\n * 3. **Overlap is advisory.** The probe is a plain `apiCall`; a non-200, a\n * throw or an empty list all mean \"no warning\", and Save never observes it.\n * Only an unparseable duration disables Save (screen 9 notes 2 and 4).\n * 4. **A locked entry opens read-only.** Every write path answers\n * `409 entry_locked`, so the form shows the lock and a way to the report that\n * closed it instead of letting someone compose a request that cannot succeed.\n */\nfunction DefaultTimeEntryDialog({\n open,\n onOpenChange,\n entryId,\n defaults,\n headerHint,\n onSaved,\n onShowEntry,\n}: TimeEntryDialogProps): React.ReactElement {\n const t = useT()\n const scopeVersion = useOrganizationScopeVersion()\n const { payload } = useBackendChrome()\n const { confirm, ConfirmDialogElement } = useConfirmDialog()\n const canSeeMoney = hasFeature(payload?.grantedFeatures, RATES_FEATURE)\n const isEdit = typeof entryId === 'string' && entryId.length > 0\n\n const [taskId, setTaskId] = React.useState<string | null>(null)\n const [description, setDescription] = React.useState('')\n const [date, setDate] = React.useState(() => todayIsoDate())\n const [interval, setIntervalState] = React.useState<TimeIntervalState>(() => createIntervalState({}))\n const [isBillable, setIsBillable] = React.useState(true)\n const [rateOverrideEnabled, setRateOverrideEnabled] = React.useState(false)\n const [rateText, setRateText] = React.useState('')\n const [tagIds, setTagIds] = React.useState<string[]>([])\n const [overlaps, setOverlaps] = React.useState<OverlapEntry[]>([])\n /** Which button is in flight, so the spinner lands on the one that was pressed. */\n const [savingMode, setSavingMode] = React.useState<SaveMode | null>(null)\n /**\n * What is wrong with a single control, said under that control. A disabled\n * Save cannot explain which of the two required fields it is waiting for, and\n * a server complaint about one field is not a global failure either.\n */\n const [fieldIssues, setFieldIssues] = React.useState<FieldIssues>(NO_FIELD_ISSUES)\n const saving = savingMode !== null\n const [taskSearch, setTaskSearch] = React.useState('')\n const [debouncedTaskSearch, setDebouncedTaskSearch] = React.useState('')\n /**\n * The task the form is on, kept out of the two lists that come and go.\n * The directory holds the first page only and the search results are replaced\n * on every term, so without this the chosen task would vanish from under the\n * selection the moment the search box was cleared.\n */\n const [pinnedTask, setPinnedTask] = React.useState<TaskOption | null>(null)\n /** What the form was seeded with; `null` until it has been seeded at all. */\n const [baseline, setBaseline] = React.useState<FormValues | null>(null)\n /**\n * T7.4 \u2014 the entry loop must be completable without a mouse, so the first field\n * of the loop is also the focus target twice over: when the dialog opens (Radix\n * would otherwise land on the \u00D7 close button, which renders before the body) and\n * after \"save and add another\", where focus would otherwise be stranded on the\n * button and the next entry would start with a handful of Shift+Tabs.\n */\n const taskTriggerRef = React.useRef<HTMLDivElement | null>(null)\n /**\n * The picker is a search box inside a container, not a single focusable\n * control, so the keyboard loop focuses the input within it. Without this,\n * \"save and add another\" left focus stranded and the loop stopped being a loop.\n */\n const focusTaskPicker = React.useCallback(() => {\n const host = taskTriggerRef.current\n if (!host) return false\n const focusable = host.querySelector('input, select, button') as HTMLElement | null\n if (!focusable) return false\n focusable.focus()\n return true\n }, [])\n\n const seedKeyRef = React.useRef<string | null>(null)\n const billableDefaultRef = React.useRef<string | null>(null)\n const versionRef = React.useRef<string | null>(null)\n\n const { runMutation, retryLastMutation } = useGuardedMutation<{\n formId: string\n resourceKind: string\n resourceId: string\n retryLastMutation: () => Promise<boolean>\n }>({\n contextId: TIME_ENTRY_DIALOG_MUTATION_CONTEXT_ID,\n blockedMessage: t('staff.time_tracking.entryDialog.errors.save', 'Could not save the time entry.'),\n })\n\n const settingsQuery = useQuery<SettingsPayload>({\n queryKey: [...DIALOG_QUERY_ROOT, 'settings', `scope:${scopeVersion}`],\n enabled: open,\n staleTime: 300_000,\n queryFn: async () => {\n const call = await apiCall<Record<string, unknown>>('/api/staff/timesheets/settings')\n return call.ok ? readSettings(call.result) : FALLBACK_SETTINGS\n },\n })\n const settings = settingsQuery.data ?? FALLBACK_SETTINGS\n\n const selfQuery = useQuery<{ id: string; displayName: string } | null>({\n queryKey: [...DIALOG_QUERY_ROOT, 'self', `scope:${scopeVersion}`],\n enabled: open,\n staleTime: 300_000,\n queryFn: async () => {\n const call = await apiCall<{ member?: { id?: string; displayName?: string } | null }>(\n '/api/staff/team-members/self',\n )\n const member = call.ok ? call.result?.member ?? null : null\n if (!member?.id) return null\n return { id: member.id, displayName: member.displayName ?? '' }\n },\n })\n\n /**\n * The first page of tasks, which is what the picker opens on. It is a head\n * start, not the corpus \u2014 anything past it is reached through the search below.\n */\n const tasksQuery = useQuery<TaskOption[]>({\n queryKey: [...DIALOG_QUERY_ROOT, 'tasks', `scope:${scopeVersion}`],\n enabled: open,\n staleTime: 60_000,\n queryFn: async () => {\n const call = await apiCall<Record<string, unknown>>(\n `/api/staff/timesheets/tasks?page=1&pageSize=${DIRECTORY_PAGE_SIZE}&sortField=title&sortDir=asc`,\n )\n if (!call.ok) return []\n return readRowItems(call.result)\n .map(toTaskOption)\n .filter((task): task is TaskOption => task !== null)\n },\n })\n\n // Debounced so a typed word is one pair of requests rather than one per letter.\n React.useEffect(() => {\n if (!open) {\n setDebouncedTaskSearch('')\n return\n }\n const handle = setTimeout(() => setDebouncedTaskSearch(taskSearch.trim()), TASK_SEARCH_DEBOUNCE_MS)\n return () => clearTimeout(handle)\n }, [open, taskSearch])\n\n const taskSearchQuery = useQuery<TaskOption[]>({\n queryKey: [...DIALOG_QUERY_ROOT, 'task-search', `scope:${scopeVersion}`, debouncedTaskSearch],\n enabled: open && debouncedTaskSearch.length > 0,\n staleTime: 60_000,\n queryFn: () => fetchTaskOptions(debouncedTaskSearch),\n })\n\n const projectsQuery = useQuery<ProjectOption[]>({\n queryKey: [...DIALOG_QUERY_ROOT, 'projects', `scope:${scopeVersion}`],\n enabled: open,\n staleTime: 60_000,\n queryFn: async () => {\n // Best effort: a consultant without `projects.view` still gets the task\n // picker, only without the customer and project suffix on each option.\n const call = await apiCall<Record<string, unknown>>(\n `/api/staff/timesheets/time-projects?page=1&pageSize=${DIRECTORY_PAGE_SIZE}&sortField=name&sortDir=asc`,\n )\n if (!call.ok) return []\n return readRowItems(call.result)\n .map(toProjectOption)\n .filter((project): project is ProjectOption => project !== null)\n },\n })\n\n const staffMemberId = selfQuery.data?.id ?? null\n\n const statusesQuery = useQuery<Record<string, TaskPickerStatus>>({\n queryKey: [...DIALOG_QUERY_ROOT, 'task-statuses', `scope:${scopeVersion}`],\n enabled: open,\n staleTime: 300_000,\n queryFn: async () => {\n const call = await apiCall<Record<string, unknown>>(\n `/api/staff/timesheets/task-statuses?page=1&pageSize=${DIRECTORY_PAGE_SIZE}`,\n )\n if (!call.ok) return {}\n const map: Record<string, TaskPickerStatus> = {}\n for (const row of readRowItems(call.result)) {\n const id = readRowString(row, 'id')\n if (!id) continue\n map[id] = {\n id,\n name: readRowString(row, 'name') ?? '',\n color: readRowString(row, 'color'),\n isDone: row.is_done === true || row.isDone === true,\n }\n }\n return map\n },\n })\n\n /**\n * The tasks this person logged to recently. Repetition is the norm in time\n * logging, so opening the picker on these turns the common case into one\n * keystroke. No new endpoint \u2014 it is the entries list asked a narrower question.\n */\n const recentQuery = useQuery<string[]>({\n queryKey: [...DIALOG_QUERY_ROOT, 'recent-tasks', `scope:${scopeVersion}`, staffMemberId ?? 'none'],\n enabled: open && !!staffMemberId,\n staleTime: 60_000,\n queryFn: async () => {\n const since = shiftIsoDate(todayIsoDate(), -21)\n const call = await apiCall<Record<string, unknown>>(\n `/api/staff/timesheets/time-entries?page=1&pageSize=100&sortField=date&sortDir=desc`\n + `&staffMemberId=${encodeURIComponent(staffMemberId as string)}&from=${since}`,\n )\n if (!call.ok) return []\n const seen: string[] = []\n for (const row of readRowItems(call.result)) {\n const taskId = readRowString(row, 'task_id', 'taskId')\n if (taskId && !seen.includes(taskId)) seen.push(taskId)\n if (seen.length >= 6) break\n }\n return seen\n },\n })\n\n const tagsQuery = useQuery<TagOption[]>({\n queryKey: [...DIALOG_QUERY_ROOT, 'tags', `scope:${scopeVersion}`],\n enabled: open,\n staleTime: 300_000,\n queryFn: async () => {\n const call = await apiCall<Record<string, unknown>>(\n `/api/staff/timesheets/tags?page=1&pageSize=${DIRECTORY_PAGE_SIZE}&sortField=label&sortDir=asc`,\n )\n if (!call.ok) return []\n return readRowItems(call.result)\n .map((row) => {\n const id = readRowString(row, 'id')\n // The colour has always been stored and never rendered \u2014 the chips were\n // monochrome while `staff_time_tags.color` held a DS palette key.\n return id\n ? { id, label: readRowString(row, 'label') ?? id, color: readRowString(row, 'color') }\n : null\n })\n .filter((tag): tag is TagOption => tag !== null)\n },\n })\n\n const entryQuery = useQuery<TimeEntryRecord | null>({\n queryKey: [...DIALOG_QUERY_ROOT, 'entry', `scope:${scopeVersion}`, entryId ?? 'new'],\n enabled: open && isEdit,\n staleTime: 0,\n queryFn: async () => {\n // `ids`, not `id`: the list schema declares the plural and is `.passthrough()`,\n // so `?id=` was accepted, never read, and the filter silently disappeared \u2014\n // leaving `pageSize=1` to return the first entry of the whole table.\n const call = await apiCall<Record<string, unknown>>(\n `/api/staff/timesheets/time-entries?ids=${encodeURIComponent(entryId as string)}&pageSize=1`,\n )\n if (!call.ok) throw new Error('[internal] time entry request failed')\n const row = readRowItems(call.result)[0]\n if (!row) return null\n const record = toTimeEntryRecord(row)\n if (!record) return null\n // A dialog that edits a record other than the one it was asked for writes\n // one entry's values onto another, so a mismatch fails loudly rather than\n // populating the form. Nothing but a dropped filter can produce it.\n if (record.id !== entryId) {\n throw new Error('[internal] time entry response did not match the requested id')\n }\n return record\n },\n })\n\n const entry = entryQuery.data ?? null\n const locked = !!entry?.isLocked\n const lockedReportId = entry?.lockedReportId ?? null\n const directoryTasks = React.useMemo(() => tasksQuery.data ?? [], [tasksQuery.data])\n const searchedTasks = React.useMemo(() => taskSearchQuery.data ?? [], [taskSearchQuery.data])\n\n React.useEffect(() => {\n if (!taskId) {\n setPinnedTask(null)\n return\n }\n const found =\n directoryTasks.find((task) => task.id === taskId) ??\n searchedTasks.find((task) => task.id === taskId) ??\n null\n if (found) setPinnedTask(found)\n }, [directoryTasks, searchedTasks, taskId])\n\n /**\n * An entry can point at a task that neither the first directory page nor the\n * current search contains \u2014 editing one written months ago, for instance \u2014 and\n * a form that cannot name the task it is on cannot resolve its project or rate\n * either, so that one row is fetched by id.\n */\n const unresolvedTaskId =\n taskId && !pinnedTask && !tasksQuery.isPending && !taskSearchQuery.isFetching ? taskId : null\n\n const unresolvedTaskQuery = useQuery<TaskOption | null>({\n queryKey: [...DIALOG_QUERY_ROOT, 'task', `scope:${scopeVersion}`, unresolvedTaskId ?? 'none'],\n enabled: open && !!unresolvedTaskId,\n staleTime: 60_000,\n queryFn: () => fetchTaskById(unresolvedTaskId as string),\n })\n\n const tasks = React.useMemo(() => {\n const merged = new Map<string, TaskOption>()\n for (const task of directoryTasks) merged.set(task.id, task)\n for (const task of searchedTasks) if (!merged.has(task.id)) merged.set(task.id, task)\n for (const task of [pinnedTask, unresolvedTaskQuery.data ?? null]) {\n if (task && !merged.has(task.id)) merged.set(task.id, task)\n }\n return [...merged.values()]\n }, [directoryTasks, pinnedTask, searchedTasks, unresolvedTaskQuery.data])\n\n const projects = React.useMemo(() => projectsQuery.data ?? [], [projectsQuery.data])\n const tagOptions = React.useMemo(() => tagsQuery.data ?? [], [tagsQuery.data])\n\n const projectById = React.useMemo(() => {\n const map = new Map<string, ProjectOption>()\n for (const project of projects) map.set(project.id, project)\n return map\n }, [projects])\n\n const selectedTask = React.useMemo(\n () => tasks.find((task) => task.id === taskId) ?? null,\n [taskId, tasks],\n )\n\n const projectId = selectedTask?.timeProjectId ?? entry?.timeProjectId ?? defaults?.timeProjectId ?? null\n const project = projectId ? projectById.get(projectId) ?? null : null\n\n const resetForm = React.useCallback(\n (seed: {\n taskId: string | null\n description: string\n date: string\n start: string | null\n end: string | null\n durationMinutes: number | null\n isBillable: boolean\n rateOverrideAmount: number | null\n tagIds: string[]\n }) => {\n setTaskId(seed.taskId)\n setDescription(seed.description)\n setDate(seed.date || todayIsoDate())\n setIntervalState((current) =>\n resetIntervalState(current, {\n start: seed.start,\n end: seed.end,\n durationMinutes: seed.durationMinutes,\n }),\n )\n setIsBillable(seed.isBillable)\n setRateOverrideEnabled(seed.rateOverrideAmount !== null)\n setRateText(seed.rateOverrideAmount !== null ? String(seed.rateOverrideAmount) : '')\n setTagIds(seed.tagIds)\n setOverlaps([])\n setFieldIssues(NO_FIELD_ISSUES)\n // The seed is run through the same derivation the state uses, so the\n // baseline is what the form will actually show rather than what was asked\n // for \u2014 otherwise a seeded start plus duration would read as an edit.\n const derived = createIntervalState({\n start: seed.start,\n end: seed.end,\n durationMinutes: seed.durationMinutes,\n })\n setBaseline({\n taskId: seed.taskId,\n description: seed.description,\n date: seed.date || todayIsoDate(),\n startText: derived.startText,\n endText: derived.endText,\n durationMinutes: derived.durationMinutes,\n isBillable: seed.isBillable,\n rateOverrideEnabled: seed.rateOverrideAmount !== null,\n rateOverrideAmount: seed.rateOverrideAmount,\n tagIds: seed.tagIds,\n })\n },\n [],\n )\n\n React.useEffect(() => {\n if (!open) {\n seedKeyRef.current = null\n billableDefaultRef.current = null\n setBaseline(null)\n setTaskSearch('')\n setPinnedTask(null)\n return\n }\n if (isEdit && !entry) return\n const key = isEdit ? `edit:${entry?.id ?? entryId}:${entry?.updatedAt ?? ''}` : 'create'\n if (seedKeyRef.current === key) return\n seedKeyRef.current = key\n versionRef.current = entry?.updatedAt ?? null\n if (entry) {\n resetForm({\n taskId: entry.taskId,\n description: entry.description,\n date: entry.date,\n start: entry.startText,\n end: entry.endText,\n durationMinutes: entry.durationMinutes,\n isBillable: entry.isBillable,\n rateOverrideAmount: entry.rateOverrideAmount,\n tagIds: entry.tagIds,\n })\n return\n }\n resetForm({\n taskId: defaults?.taskId ?? null,\n description: defaults?.description ?? '',\n date: defaults?.date ?? todayIsoDate(),\n start: defaults?.startClock ?? null,\n end: defaults?.endClock ?? null,\n durationMinutes: defaults?.durationMinutes ?? null,\n isBillable: defaults?.isBillable ?? true,\n rateOverrideAmount: null,\n tagIds: defaults?.tagIds ?? [],\n })\n }, [defaults, entry, entryId, isEdit, open, resetForm])\n\n /**\n * The tenant's billable default lands separately, and only on the one field it\n * owns. Seeding the whole form once the settings request answers would race the\n * first keystrokes and wipe them, which is exactly what this split avoids.\n */\n React.useEffect(() => {\n if (!open || isEdit || settingsQuery.isPending) return\n if (defaults?.isBillable !== undefined && defaults?.isBillable !== null) return\n if (billableDefaultRef.current === seedKeyRef.current) return\n billableDefaultRef.current = seedKeyRef.current\n setIsBillable(settings.defaults.billable)\n // The tenant default is part of the seed, just a late-arriving part, so the\n // baseline moves with it \u2014 otherwise every new entry would open \"dirty\".\n setBaseline((current) =>\n current ? { ...current, isBillable: settings.defaults.billable } : current,\n )\n }, [defaults?.isBillable, isEdit, open, settings.defaults.billable, settingsQuery.isPending])\n\n const durationMinutes = interval.durationMinutes\n const startClock = interval.startText\n const endClock = interval.endText\n\n /**\n * Advisory overlap probe. Every failure mode \u2014 a non-200, a throw, a partial\n * span \u2014 lands on \"no warning\", because the endpoint may not answer and the\n * form must still save.\n */\n React.useEffect(() => {\n if (!open || locked || !settings.warnings.overlap) {\n setOverlaps([])\n return\n }\n if (!date || parseClock(startClock) === null || !durationMinutes) {\n setOverlaps([])\n return\n }\n let cancelled = false\n const params = new URLSearchParams({\n date,\n startedAt: startClock,\n durationMinutes: String(durationMinutes),\n })\n if (parseClock(endClock) !== null) params.set('endedAt', endClock)\n if (isEdit && entryId) params.set('excludeId', entryId)\n void apiCall<Record<string, unknown>>(`/api/staff/timesheets/time-entries/overlaps?${params.toString()}`)\n .then((call) => {\n if (cancelled) return\n if (!call.ok) {\n setOverlaps([])\n return\n }\n setOverlaps(\n readRowItems(call.result)\n .map(toOverlapEntry)\n .filter((item): item is OverlapEntry => item !== null),\n )\n })\n .catch(() => {\n if (!cancelled) setOverlaps([])\n })\n return () => {\n cancelled = true\n }\n }, [date, durationMinutes, endClock, entryId, isEdit, locked, open, settings.warnings.overlap, startClock])\n\n const rateOverrideAmount = rateOverrideEnabled ? parseRateText(rateText) : null\n const currencyCode = project?.currencyCode ?? entry?.currencyCode ?? null\n\n /**\n * The dialog is hand-rolled, so it plays the `CrudForm` host contract by hand:\n * the four render spots below, plus the `onFieldChange` / `onBeforeSave` /\n * `onAfterSave` lifecycle, dispatched with the same semantics `CrudForm` uses \u2014\n * a widget returning `ok: false` from `onBeforeSave` blocks the write and its\n * message is surfaced, and a value returned from `onFieldChange` is written back\n * into the form.\n */\n const entryFormValues = React.useMemo<FormValues>(\n () => ({\n taskId,\n description,\n date,\n startText: startClock,\n endText: endClock,\n durationMinutes,\n isBillable,\n rateOverrideEnabled,\n rateOverrideAmount,\n tagIds,\n }),\n [\n date,\n description,\n durationMinutes,\n endClock,\n isBillable,\n rateOverrideAmount,\n rateOverrideEnabled,\n startClock,\n tagIds,\n taskId,\n ],\n )\n\n const entryInjectionContext = React.useMemo<TimeEntryFormInjectionContext>(\n () => ({\n formId: TIME_ENTRY_DIALOG_MUTATION_CONTEXT_ID,\n entityId: TIME_ENTRY_ENTITY_ID,\n recordId: entryId ?? null,\n resourceKind: 'staff.timesheets.time_entry',\n resourceId: entryId ?? 'new',\n operation: isEdit ? 'update' : 'create',\n locked,\n retryLastMutation,\n }),\n [entryId, isEdit, locked, retryLastMutation],\n )\n\n const { triggerEvent: triggerEntryFormEvent } = useInjectionSpotEvents<\n TimeEntryFormInjectionContext,\n FormValues\n >(extensionPoints.hosts.timeEntryForm.spotId)\n\n /**\n * Fields the last write to came from a widget, not from the person. Without it\n * an `onFieldChange` handler that rewrites its own field would see the rewrite\n * as a fresh edit and be dispatched again on every apply.\n */\n const injectedFieldWritesRef = React.useRef(new Set<string>())\n\n const applyInjectedFieldValue = React.useCallback((fieldId: string, value: unknown) => {\n switch (fieldId) {\n case 'taskId':\n injectedFieldWritesRef.current.add(fieldId)\n setTaskId(typeof value === 'string' && value.length > 0 ? value : null)\n return\n case 'description':\n injectedFieldWritesRef.current.add(fieldId)\n setDescription(typeof value === 'string' ? value : '')\n return\n case 'date':\n if (typeof value !== 'string') return\n injectedFieldWritesRef.current.add(fieldId)\n setDate(value)\n return\n case 'isBillable':\n injectedFieldWritesRef.current.add(fieldId)\n setIsBillable(value === true)\n return\n case 'rateOverrideEnabled':\n injectedFieldWritesRef.current.add(fieldId)\n setRateOverrideEnabled(value === true)\n return\n case 'rateOverrideAmount':\n injectedFieldWritesRef.current.add(fieldId)\n setRateText(typeof value === 'number' || typeof value === 'string' ? String(value) : '')\n return\n case 'tagIds':\n if (!Array.isArray(value)) return\n injectedFieldWritesRef.current.add(fieldId)\n setTagIds(value.filter((id): id is string => typeof id === 'string'))\n return\n case 'startText':\n if (typeof value !== 'string') return\n injectedFieldWritesRef.current.add(fieldId)\n setIntervalState((current) => reduceIntervalState(current, { field: 'start', value }))\n return\n case 'endText':\n if (typeof value !== 'string') return\n injectedFieldWritesRef.current.add(fieldId)\n setIntervalState((current) => reduceIntervalState(current, { field: 'end', value }))\n return\n case 'durationMinutes':\n injectedFieldWritesRef.current.add(fieldId)\n setIntervalState((current) =>\n reduceIntervalState(current, {\n field: 'duration',\n minutes: typeof value === 'number' ? value : null,\n invalid: false,\n }),\n )\n return\n default:\n }\n }, [])\n\n const previousEntryFormValuesRef = React.useRef<FormValues | null>(null)\n\n React.useEffect(() => {\n if (!open || locked) {\n previousEntryFormValuesRef.current = null\n injectedFieldWritesRef.current.clear()\n return\n }\n const previous = previousEntryFormValuesRef.current\n previousEntryFormValuesRef.current = entryFormValues\n if (!previous) return\n const injectedWrites = injectedFieldWritesRef.current\n const changedFieldIds = (Object.keys(entryFormValues) as Array<keyof FormValues>).filter(\n (fieldId) => fieldValueSnapshot(previous[fieldId]) !== fieldValueSnapshot(entryFormValues[fieldId]),\n )\n for (const fieldId of changedFieldIds) {\n if (injectedWrites.delete(fieldId)) continue\n void triggerEntryFormEvent('onFieldChange', entryFormValues, entryInjectionContext, {\n fieldId,\n fieldValue: entryFormValues[fieldId],\n })\n .then((result) => {\n if (!result.ok || !result.fieldChange) return\n const change = result.fieldChange\n if (change.value !== undefined) applyInjectedFieldValue(fieldId, change.value)\n for (const [sideFieldId, sideValue] of Object.entries(change.sideEffects ?? {})) {\n applyInjectedFieldValue(sideFieldId, sideValue)\n }\n for (const message of change.messages ?? []) flash(message.text, message.severity)\n })\n .catch((err) => {\n logger.error('staff.time_tracking entry dialog onFieldChange failed', { err })\n })\n }\n }, [\n applyInjectedFieldValue,\n entryFormValues,\n entryInjectionContext,\n locked,\n open,\n triggerEntryFormEvent,\n ])\n const roundedMinutes = roundMinutes(durationMinutes ?? 0, settings.rounding)\n const rate = applicableRate({ rateOverrideAmount }, { hourlyRate: project?.hourlyRate ?? null })\n const cost = entryAmount(\n { isBillable, roundedMinutes, rateOverrideAmount },\n { hourlyRate: project?.hourlyRate ?? null },\n )\n\n const computedBadge = (field: 'start' | 'end' | 'duration' | 'cost') => (\n <Badge variant=\"neutral\" data-testid={`entry-dialog-computed-${field}`}>\n {t('staff.time_tracking.entryDialog.computed', 'computed')}\n </Badge>\n )\n\n const midnightEndDate = interval.crossesMidnight ? shiftIsoDate(date, 1) : null\n /**\n * Unparseable duration text keeps Save disabled \u2014 the field already explains\n * itself and there is nothing to send. A *missing* required value does not:\n * pressing Save is how the form is asked what it is waiting for, and it\n * answers under the control rather than by staying greyed out and silent.\n */\n const canSubmit = !locked && !interval.durationInvalid && !saving\n\n // A field stops complaining the moment it is filled in.\n React.useEffect(() => {\n if (!taskId) return\n setFieldIssues((current) => (current.task ? { ...current, task: null } : current))\n }, [taskId])\n\n React.useEffect(() => {\n if (!durationMinutes) return\n setFieldIssues((current) => (current.duration ? { ...current, duration: null } : current))\n }, [durationMinutes])\n\n const validateRequired = React.useCallback(() => {\n const next: FieldIssues = {\n task: taskId\n ? null\n : t('staff.time_tracking.entryDialog.errors.taskRequired', 'Pick the task this time belongs to.'),\n duration: durationMinutes\n ? null\n : t('staff.time_tracking.entryDialog.errors.durationRequired', 'Enter how long the work took.'),\n }\n setFieldIssues(next)\n return !next.task && !next.duration\n }, [durationMinutes, t, taskId])\n\n /**\n * A locked entry has nothing editable in it, and a form that has not been\n * seeded yet has nothing to compare against; everywhere else the seed is the\n * baseline and any difference is work that closing would throw away.\n */\n const isDirty = React.useMemo(() => {\n if (locked || !baseline) return false\n return (\n formSnapshot(baseline) !==\n formSnapshot({\n taskId,\n description,\n date,\n startText: startClock,\n endText: endClock,\n durationMinutes,\n isBillable,\n rateOverrideEnabled,\n rateOverrideAmount,\n tagIds,\n })\n )\n }, [\n baseline,\n date,\n description,\n durationMinutes,\n endClock,\n isBillable,\n locked,\n rateOverrideAmount,\n rateOverrideEnabled,\n startClock,\n tagIds,\n taskId,\n ])\n\n /**\n * Every way out of the dialog \u2014 Escape, the \u00D7, the overlay, Cancel \u2014 lands\n * here, because a half-entered entry is lost the same way whichever one is\n * used. A clean form closes silently; only real work is worth an interruption.\n */\n const requestClose = React.useCallback(async () => {\n if (saving) return\n if (!isDirty) {\n onOpenChange(false)\n return\n }\n const confirmed = await confirm({\n title: t('staff.time_tracking.entryDialog.discard.title', 'Discard this time entry?'),\n text: t(\n 'staff.time_tracking.entryDialog.discard.text',\n 'It has not been saved yet, so closing now loses what you entered.',\n ),\n confirmText: t('staff.time_tracking.entryDialog.discard.confirm', 'Discard'),\n cancelText: t('staff.time_tracking.entryDialog.discard.cancel', 'Keep editing'),\n variant: 'destructive',\n })\n if (confirmed) onOpenChange(false)\n }, [confirm, isDirty, onOpenChange, saving, t])\n\n const buildPayload = React.useCallback(() => {\n const startedAt = parseClock(startClock) !== null ? combineDateAndClock(date, startClock) : null\n const endedAt =\n parseClock(endClock) !== null ? combineDateAndClock(date, endClock, interval.crossesMidnight ? 1 : 0) : null\n return {\n date,\n durationMinutes: durationMinutes ?? 0,\n startedAt,\n endedAt,\n taskId,\n timeProjectId: projectId,\n description: description.trim() ? description.trim() : null,\n isBillable,\n tagIds,\n rateOverrideAmount: canSeeMoney ? rateOverrideAmount : undefined,\n }\n }, [\n canSeeMoney,\n date,\n description,\n durationMinutes,\n endClock,\n interval.crossesMidnight,\n isBillable,\n projectId,\n rateOverrideAmount,\n startClock,\n tagIds,\n taskId,\n ])\n\n const reportFailure = React.useCallback(\n (error: unknown) => {\n if (surfaceRecordConflict(error, t)) return\n if (readErrorCode(error) === 'entry_locked') {\n flash(\n t(\n 'staff.time_tracking.entryDialog.locked.notice',\n 'This entry is closed in a report, so it can no longer be edited.',\n ),\n 'error',\n )\n return\n }\n const issues = readFieldIssues(error)\n if (issues) {\n setFieldIssues(issues)\n return\n }\n logger.error('staff.time_tracking entry dialog save failed', { err: error })\n flash(\n error instanceof Error && error.message\n ? error.message\n : t('staff.time_tracking.entryDialog.errors.save', 'Could not save the time entry.'),\n 'error',\n )\n },\n [t],\n )\n\n const submit = React.useCallback(\n async (mode: SaveMode) => {\n if (!canSubmit) return\n if (!validateRequired()) return\n if (!isEdit && !staffMemberId) {\n flash(\n t(\n 'staff.time_tracking.entryDialog.errors.noStaffMember',\n 'Your account is not linked to a staff member, so time cannot be logged.',\n ),\n 'error',\n )\n return\n }\n const body = buildPayload()\n\n const beforeSave = await triggerEntryFormEvent('onBeforeSave', entryFormValues, entryInjectionContext)\n if (!beforeSave.ok) {\n if (beforeSave.fieldErrors) {\n const taskIssue = beforeSave.fieldErrors.taskId ?? beforeSave.fieldErrors.task ?? null\n const durationIssue =\n beforeSave.fieldErrors.durationMinutes ?? beforeSave.fieldErrors.duration ?? null\n if (taskIssue || durationIssue) setFieldIssues({ task: taskIssue, duration: durationIssue })\n }\n flash(\n beforeSave.message || t('ui.forms.flash.saveBlocked', 'Save blocked by validation'),\n 'error',\n )\n return\n }\n const injectedRequestHeaders = beforeSave.requestHeaders ?? {}\n\n setSavingMode(mode)\n try {\n const saved = await runMutation({\n operation: () =>\n withScopedApiRequestHeaders(\n {\n ...injectedRequestHeaders,\n ...(isEdit ? buildOptimisticLockHeader(versionRef.current) : {}),\n },\n () =>\n apiCallOrThrow<Record<string, unknown>>(\n '/api/staff/timesheets/time-entries',\n {\n method: isEdit ? 'PUT' : 'POST',\n headers: { 'content-type': 'application/json' },\n body: JSON.stringify(\n isEdit ? { id: entryId, ...body } : { staffMemberId, source: 'manual', ...body },\n ),\n },\n {\n errorMessage: t(\n 'staff.time_tracking.entryDialog.errors.save',\n 'Could not save the time entry.',\n ),\n },\n ),\n ),\n context: {\n formId: TIME_ENTRY_DIALOG_MUTATION_CONTEXT_ID,\n resourceKind: 'staff.timesheets.time_entry',\n resourceId: entryId ?? 'new',\n retryLastMutation,\n },\n })\n const savedId = typeof saved?.result?.id === 'string' ? saved.result.id : entryId ?? null\n try {\n await triggerEntryFormEvent('onAfterSave', entryFormValues, entryInjectionContext)\n } catch (err) {\n logger.error('staff.time_tracking entry dialog onAfterSave failed', { err })\n }\n flash(t('staff.time_tracking.entryDialog.saved', 'Time entry saved.'), 'success')\n if (mode === 'again') {\n // Note 3: the dialog stays open, the description and the times clear,\n // and the next entry starts where this one ended.\n const chain = settings.defaults.chainStartFromPreviousEnd\n const nextDate = interval.crossesMidnight && midnightEndDate ? midnightEndDate : date\n const nextStart = chain ? endClock || null : null\n seedKeyRef.current = 'create'\n setDescription('')\n setDate(nextDate)\n setIntervalState((current) => resetIntervalState(current, { start: nextStart }))\n setOverlaps([])\n // The next entry starts clean, so this is its baseline \u2014 without it the\n // dialog would still be holding the one that was just written.\n setBaseline({\n taskId,\n description: '',\n date: nextDate,\n startText: nextStart ?? '',\n endText: '',\n durationMinutes: null,\n isBillable,\n rateOverrideEnabled,\n rateOverrideAmount,\n tagIds,\n })\n onSaved?.({ id: savedId, keptOpen: true })\n // Back to the top of the loop: pick task \u2192 duration \u2192 save \u2192 next.\n focusTaskPicker()\n return\n }\n onSaved?.({ id: savedId, keptOpen: false })\n onOpenChange(false)\n } catch (error) {\n reportFailure(error)\n } finally {\n setSavingMode(null)\n }\n },\n [\n buildPayload,\n canSubmit,\n date,\n endClock,\n entryFormValues,\n entryId,\n entryInjectionContext,\n interval.crossesMidnight,\n isBillable,\n isEdit,\n midnightEndDate,\n onOpenChange,\n onSaved,\n rateOverrideAmount,\n rateOverrideEnabled,\n reportFailure,\n retryLastMutation,\n runMutation,\n settings.defaults.chainStartFromPreviousEnd,\n staffMemberId,\n t,\n tagIds,\n taskId,\n triggerEntryFormEvent,\n validateRequired,\n ],\n )\n\n const handleShowOverlap = React.useCallback(\n (overlap: OverlapEntry) => {\n if (onShowEntry) {\n onShowEntry(overlap.id)\n return\n }\n window.open(`${ENTRIES_LIST_PATH}?ids=${encodeURIComponent(overlap.id)}`, '_blank', 'noopener')\n },\n [onShowEntry],\n )\n\n /**\n * The picker needs the project and its customer per row so it can group by\n * them; both already arrive with the projects query the dialog runs for the\n * rate, so this is a join in memory rather than another request.\n */\n const pickerItems = React.useMemo<TaskPickerItem[]>(\n () =>\n tasks.map((task) => {\n const project = task.timeProjectId ? projectById.get(task.timeProjectId) : undefined\n return {\n id: task.id,\n reference: task.reference,\n title: task.title,\n projectId: task.timeProjectId,\n projectName: project?.name ?? null,\n customerName: project?.customerName ?? null,\n statusId: task.taskStatusId ?? null,\n assigneeInitials: null,\n assigneeName: null,\n loggedMinutes: task.loggedMinutes ?? null,\n }\n }),\n [projectById, tasks],\n )\n\n const queryClient = useQueryClient()\n /**\n * Creates the tag, then hands it back so the caller can assign it.\n *\n * The colour is decided here rather than left to the server, which stores\n * whatever it is sent and defaults to null. The picker already tints its dot\n * with the name-derived hue, so sending that same hue is what makes the chip\n * the entry ends up wearing the colour the row promised \u2014 instead of the grey\n * one an unset column renders as.\n */\n const createTag = React.useCallback(\n async (label: string): Promise<TagOption | null> => {\n const color = autoColorFromName(label).key\n try {\n const created = await apiCallOrThrow<Record<string, unknown>>(\n '/api/staff/timesheets/tags',\n {\n method: 'POST',\n headers: { 'content-type': 'application/json' },\n body: JSON.stringify({ label, slug: slugifyProjectName(label).toLowerCase(), color }),\n },\n { errorMessage: t('staff.time_tracking.entryDialog.errors.createTag', 'Could not create the tag.') },\n )\n const id = typeof created?.result?.id === 'string' ? created.result.id : null\n if (!id) return null\n const option: TagOption = { id, label, color }\n // Seeded before the refetch so the chip is never briefly the grey one the\n // fix is about; the invalidation right after replaces it with the row the\n // server actually stored.\n queryClient.setQueryData<TagOption[]>(\n [...DIALOG_QUERY_ROOT, 'tags', `scope:${scopeVersion}`],\n (current) => [...(current ?? []), option],\n )\n await queryClient.invalidateQueries({ queryKey: [...DIALOG_QUERY_ROOT, 'tags'] })\n return option\n } catch (error) {\n logger.warn('staff.time_tracking.entryDialog tag create failed', { err: error })\n flash(t('staff.time_tracking.entryDialog.errors.createTag', 'Could not create the tag.'), 'error')\n return null\n }\n },\n [queryClient, scopeVersion, t],\n )\n\n const overlap = overlaps[0] ?? null\n const availableTags = React.useMemo(\n () => tagOptions.filter((tag) => !tagIds.includes(tag.id)),\n [tagIds, tagOptions],\n )\n const assignedTags = React.useMemo(() => {\n const byId = new Map(tagOptions.map((tag) => [tag.id, tag]))\n return tagIds.map((id) => byId.get(id) ?? { id, label: id, color: null })\n }, [tagIds, tagOptions])\n\n const rateLabel = rate !== null ? formatCurrency(rate, currencyCode) ?? String(rate) : null\n const projectRateLabel =\n project?.hourlyRate !== null && project?.hourlyRate !== undefined\n ? formatCurrency(project.hourlyRate, currencyCode) ?? String(project.hourlyRate)\n : null\n\n const body = (() => {\n if (isEdit && entryQuery.isLoading) {\n return <LoadingMessage label={t('staff.time_tracking.entryDialog.loading', 'Loading the time entry\u2026')} />\n }\n if (isEdit && entryQuery.isError) {\n return <ErrorMessage label={t('staff.time_tracking.entryDialog.errors.load', 'Could not load the time entry.')} />\n }\n if (isEdit && !entry) {\n return <ErrorMessage label={t('staff.time_tracking.entryDialog.errors.notFound', 'Time entry not found or not accessible.')} />\n }\n\n return (\n <div className=\"flex flex-col gap-4\">\n <InjectionSpot\n spotId={extensionSpotChildId(extensionPoints.hosts.timeEntryForm.spotId, 'before-fields')}\n context={entryInjectionContext}\n data={entryFormValues}\n />\n\n {locked ? (\n <Alert status=\"warning\" data-testid=\"entry-dialog-locked\">\n <AlertTitle className=\"flex items-center gap-2\">\n <Lock className=\"size-3.5\" aria-hidden=\"true\" />\n {t('staff.time_tracking.entryDialog.locked.badge', 'Locked')}\n </AlertTitle>\n <AlertDescription>\n {t(\n 'staff.time_tracking.entryDialog.locked.notice',\n 'This entry is closed in a report, so it can no longer be edited.',\n )}\n </AlertDescription>\n {lockedReportId ? (\n <Button\n type=\"button\"\n variant=\"outline\"\n size=\"sm\"\n className=\"mt-2\"\n data-testid=\"entry-dialog-locked-report\"\n onClick={() =>\n window.open(`${REPORTS_PATH}?id=${encodeURIComponent(lockedReportId)}`, '_blank', 'noopener')\n }\n >\n {t('staff.time_tracking.entryDialog.locked.showReport', 'Show the report')}\n </Button>\n ) : null}\n </Alert>\n ) : null}\n\n <div className=\"flex flex-col gap-1.5\">\n <Label htmlFor=\"entry-dialog-task\">\n {t('staff.time_tracking.entryDialog.task', 'Task')}\n <span aria-hidden=\"true\"> *</span>\n </Label>\n <div\n data-testid=\"entry-dialog-task\"\n ref={taskTriggerRef}\n aria-invalid={fieldIssues.task ? true : undefined}\n aria-describedby={fieldIssues.task ? 'entry-dialog-task-message' : undefined}\n >\n <TaskPicker\n value={taskId}\n onChange={(next) => setTaskId(next || null)}\n items={pickerItems}\n recentTaskIds={recentQuery.data ?? []}\n statuses={statusesQuery.data ?? {}}\n onQueryChange={setTaskSearch}\n searching={taskSearchQuery.isFetching}\n loading={tasksQuery.isLoading}\n disabled={locked}\n />\n </div>\n {fieldIssues.task ? (\n <p\n id=\"entry-dialog-task-message\"\n className=\"text-xs text-status-error-text\"\n role=\"alert\"\n data-testid=\"entry-dialog-task-error\"\n >\n {fieldIssues.task}\n </p>\n ) : (\n <p className=\"text-xs text-muted-foreground\">\n {t(\n 'staff.time_tracking.entryDialog.taskHint',\n 'The project and the customer follow from the task \u2014 you do not pick them separately.',\n )}\n </p>\n )}\n </div>\n\n <div className=\"flex flex-col gap-1.5\">\n <Label htmlFor=\"entry-dialog-description\">\n {t('staff.time_tracking.entryDialog.description', 'Description')}\n </Label>\n <Input\n id=\"entry-dialog-description\"\n value={description}\n disabled={locked}\n onChange={(event) => setDescription(event.target.value)}\n data-testid=\"entry-dialog-description\"\n />\n </div>\n\n <div className=\"grid grid-cols-1 gap-4 sm:grid-cols-2\">\n <div className=\"flex flex-col gap-1.5\">\n <Label htmlFor=\"entry-dialog-date\">{t('staff.time_tracking.entryDialog.date', 'Date')}</Label>\n <Input\n id=\"entry-dialog-date\"\n type=\"date\"\n value={date}\n disabled={locked}\n onChange={(event) => setDate(event.target.value)}\n data-testid=\"entry-dialog-date\"\n />\n </div>\n <div className=\"flex flex-col gap-1.5\">\n <Label htmlFor=\"entry-dialog-duration\" className=\"flex items-center gap-2\">\n {t('staff.time_tracking.entryDialog.duration', 'Duration')}\n {interval.computed === 'duration' ? computedBadge('duration') : null}\n </Label>\n <DurationInput\n key={`entry-dialog-duration-${interval.durationEpoch}`}\n id=\"entry-dialog-duration\"\n value={durationMinutes}\n disabled={locked}\n error={fieldIssues.duration ?? undefined}\n ariaLabel={t('staff.time_tracking.entryDialog.duration', 'Duration')}\n onChange={(minutes, state) =>\n setIntervalState((current) =>\n reduceIntervalState(current, {\n field: 'duration',\n minutes,\n invalid: state.status === 'invalid',\n }),\n )\n }\n />\n </div>\n </div>\n\n <div className=\"grid grid-cols-1 gap-4 sm:grid-cols-2\">\n <div className=\"flex flex-col gap-1.5\">\n <Label htmlFor=\"entry-dialog-start\" className=\"flex items-center gap-2\">\n {t('staff.time_tracking.entryDialog.start', 'Start')}\n {interval.computed === 'start' ? computedBadge('start') : null}\n </Label>\n <Input\n id=\"entry-dialog-start\"\n value={startClock}\n disabled={locked}\n inputClassName=\"font-mono tabular-nums\"\n placeholder=\"15:10\"\n onChange={(event) =>\n setIntervalState((current) =>\n reduceIntervalState(current, { field: 'start', value: event.target.value }),\n )\n }\n data-testid=\"entry-dialog-start\"\n />\n </div>\n <div className=\"flex flex-col gap-1.5\">\n <Label htmlFor=\"entry-dialog-end\" className=\"flex items-center gap-2\">\n {t('staff.time_tracking.entryDialog.end', 'End')}\n {interval.computed === 'end' ? computedBadge('end') : null}\n </Label>\n <Input\n id=\"entry-dialog-end\"\n value={endClock}\n disabled={locked}\n inputClassName=\"font-mono tabular-nums\"\n placeholder=\"16:25\"\n onChange={(event) =>\n setIntervalState((current) =>\n reduceIntervalState(current, { field: 'end', value: event.target.value }),\n )\n }\n data-testid=\"entry-dialog-end\"\n />\n <p className=\"text-xs text-muted-foreground\">\n {t(\n 'staff.time_tracking.entryDialog.endHint',\n 'Change this field and the duration recalculates itself.',\n )}\n </p>\n </div>\n </div>\n\n {midnightEndDate ? (\n <p className=\"text-xs text-muted-foreground\" data-testid=\"entry-dialog-midnight\">\n {t('staff.time_tracking.entryDialog.midnight', 'This entry ends the next day, {date}.', {\n date: midnightEndDate,\n })}\n </p>\n ) : null}\n\n {overlap ? (\n <Alert status=\"warning\" data-testid=\"entry-dialog-overlap\">\n <AlertTitle>\n {t('staff.time_tracking.entryDialog.overlap.title', 'This time overlaps another entry')}\n </AlertTitle>\n <AlertDescription>\n {t(\n 'staff.time_tracking.entryDialog.overlap.body',\n '{start} \u2013 {end} \u00B7 \u201E{task}\u201D \u00B7 {project} ({duration}). Overlapping is allowed \u2014 if it is intentional, just save.',\n {\n start: overlap.startText,\n end: overlap.endText,\n task:\n overlap.taskTitle ??\n overlap.description ??\n t('staff.time_tracking.entryDialog.overlap.noTask', 'no task'),\n project:\n overlap.projectName ??\n t('staff.time_tracking.entryDialog.overlap.noProject', 'no project'),\n duration: formatDuration(overlap.durationMinutes, 'clock'),\n },\n )}\n </AlertDescription>\n <div className=\"mt-2 flex flex-wrap gap-2\">\n <Button\n type=\"button\"\n variant=\"outline\"\n size=\"sm\"\n onClick={() => handleShowOverlap(overlap)}\n data-testid=\"entry-dialog-overlap-show\"\n >\n {t('staff.time_tracking.entryDialog.overlap.show', 'Show that entry')}\n </Button>\n {overlap.endText ? (\n <Button\n type=\"button\"\n variant=\"outline\"\n size=\"sm\"\n disabled={locked}\n onClick={() =>\n setIntervalState((current) =>\n reduceIntervalState(current, { field: 'snapStart', value: overlap.endText }),\n )\n }\n data-testid=\"entry-dialog-overlap-snap\"\n >\n {t('staff.time_tracking.entryDialog.overlap.snap', 'Snap start to {time}', {\n time: overlap.endText,\n })}\n </Button>\n ) : null}\n </div>\n </Alert>\n ) : null}\n\n <Separator />\n\n <div className=\"flex items-center gap-3\">\n <Switch\n checked={isBillable}\n disabled={locked}\n onCheckedChange={(next) => setIsBillable(next)}\n aria-label={t('staff.time_tracking.entryDialog.billable', 'Billable')}\n data-testid=\"entry-dialog-billable\"\n />\n <div className=\"flex min-w-0 flex-1 flex-col\">\n <span className=\"text-sm font-medium text-foreground\">\n {t('staff.time_tracking.entryDialog.billable', 'Billable')}\n </span>\n <span className=\"text-xs text-muted-foreground\">\n {t('staff.time_tracking.entryDialog.billableHint', 'On by default for this project.')}\n </span>\n </div>\n {canSeeMoney && rateLabel ? (\n <Badge variant=\"success\" data-testid=\"entry-dialog-rate-badge\">\n {t('staff.time_tracking.entryDialog.ratePerHour', '{amount}/h', { amount: rateLabel })}\n </Badge>\n ) : null}\n </div>\n\n {canSeeMoney ? (\n <>\n <div className=\"flex items-center gap-3\">\n <Switch\n checked={rateOverrideEnabled}\n disabled={locked}\n onCheckedChange={(next) => setRateOverrideEnabled(next)}\n aria-label={t('staff.time_tracking.entryDialog.rateOverride', 'Custom rate for this entry')}\n data-testid=\"entry-dialog-rate-override\"\n />\n <div className=\"flex min-w-0 flex-1 flex-col\">\n <span className=\"text-sm font-medium text-foreground\">\n {t('staff.time_tracking.entryDialog.rateOverride', 'Custom rate for this entry')}\n </span>\n <span className=\"text-xs text-muted-foreground\">\n {t(\n 'staff.time_tracking.entryDialog.rateOverrideHint',\n 'Overrides the project rate here only.',\n )}\n </span>\n </div>\n </div>\n\n <div className=\"grid grid-cols-1 gap-4 sm:grid-cols-2\">\n {rateOverrideEnabled ? (\n <div className=\"flex flex-col gap-1.5\">\n <Label htmlFor=\"entry-dialog-rate\">\n {t('staff.time_tracking.entryDialog.rate', 'Rate')}\n </Label>\n <Input\n id=\"entry-dialog-rate\"\n value={rateText}\n disabled={locked}\n inputMode=\"decimal\"\n onChange={(event) => setRateText(event.target.value)}\n data-testid=\"entry-dialog-rate\"\n />\n {projectRateLabel ? (\n <p className=\"text-xs text-muted-foreground\">\n {t(\n 'staff.time_tracking.entryDialog.rateHint',\n 'Defaults to {amount} (the project rate).',\n { amount: projectRateLabel },\n )}\n </p>\n ) : null}\n </div>\n ) : null}\n <div className=\"flex flex-col gap-1.5\">\n <Label htmlFor=\"entry-dialog-cost\" className=\"flex items-center gap-2\">\n {t('staff.time_tracking.entryDialog.cost', 'Cost')}\n {computedBadge('cost')}\n </Label>\n <Input\n id=\"entry-dialog-cost\"\n readOnly\n disabled\n value={cost === null ? '' : formatCurrency(cost, currencyCode) ?? String(cost)}\n inputClassName=\"font-mono tabular-nums\"\n data-testid=\"entry-dialog-cost\"\n />\n {rateLabel && durationMinutes ? (\n <p className=\"text-xs text-muted-foreground\" data-testid=\"entry-dialog-cost-hint\">\n {t(\n 'staff.time_tracking.entryDialog.costHint',\n '{raw} rounded to {rounded} \u00D7 {rate}',\n {\n raw: formatDuration(durationMinutes, 'clock'),\n rounded: formatDuration(roundedMinutes, 'clock'),\n rate: rateLabel,\n },\n )}\n </p>\n ) : null}\n </div>\n </div>\n </>\n ) : (\n <p className=\"text-xs text-muted-foreground\" data-testid=\"entry-dialog-money-hidden\">\n {t(\n 'staff.time_tracking.entryDialog.moneyHidden',\n 'Rates and cost are hidden \u2014 your permissions do not cover money on time entries.',\n )}\n </p>\n )}\n\n <div className=\"flex flex-col gap-1.5\">\n <span className=\"text-xs font-medium text-muted-foreground\">\n {t('staff.time_tracking.entryDialog.tags', 'Tags')}\n </span>\n <div className=\"flex flex-wrap items-center gap-2\">\n {assignedTags.map((tag) => (\n <Badge\n key={tag.id}\n variant=\"neutral\"\n className=\"gap-1\"\n style={tagChipStyle(tag)}\n >\n {tag.label}\n {locked ? null : (\n <button\n type=\"button\"\n aria-label={t('staff.time_tracking.entryDialog.removeTag', 'Remove tag {label}', {\n label: tag.label,\n })}\n onClick={() => setTagIds((current) => current.filter((id) => id !== tag.id))}\n >\n <X className=\"size-3\" aria-hidden=\"true\" />\n </button>\n )}\n </Badge>\n ))}\n {!locked ? (\n <TagPicker\n available={availableTags}\n onAssign={(id) => setTagIds((current) => (current.includes(id) ? current : [...current, id]))}\n onCreate={createTag}\n labels={{\n add: t('staff.time_tracking.entryDialog.addTag', 'Add tag'),\n create: (name) =>\n t('staff.time_tracking.entryDialog.createTag', 'Create \"{name}\"', { name }),\n empty: t('staff.time_tracking.entryDialog.noTags', 'No matching tag'),\n }}\n />\n ) : null}\n </div>\n </div>\n\n <InjectionSpot\n spotId={extensionSpotChildId(extensionPoints.hosts.timeEntryForm.spotId, 'fields')}\n context={entryInjectionContext}\n data={entryFormValues}\n />\n\n <Alert status=\"information\" data-testid=\"entry-dialog-creator\">\n <AlertDescription>\n {selfQuery.data?.displayName\n ? t(\n 'staff.time_tracking.entryDialog.creator',\n 'Entry author: {name} \u2014 assigned automatically and not editable.',\n { name: selfQuery.data.displayName },\n )\n : t(\n 'staff.time_tracking.entryDialog.creatorUnknown',\n 'The entry author is assigned automatically and is not editable.',\n )}\n </AlertDescription>\n </Alert>\n\n <InjectionSpot\n spotId={extensionSpotChildId(extensionPoints.hosts.timeEntryForm.spotId, 'after-fields')}\n context={entryInjectionContext}\n data={entryFormValues}\n />\n </div>\n )\n })()\n\n return (\n <Dialog\n open={open}\n // Escape, the \u00D7, and a click on the overlay all arrive here, which is why\n // the unsaved-work guard sits on this one callback rather than on each of\n // them. Radix keeps the dialog mounted until `open` actually flips, so\n // declining the prompt simply leaves the form where it was.\n onOpenChange={(next) => {\n if (next) {\n onOpenChange(true)\n return\n }\n void requestClose()\n }}\n >\n <DialogContent\n size=\"lg\"\n data-testid=\"entry-dialog\"\n onOpenAutoFocus={(event) => {\n // A locked entry has nothing to type into, so the default focus (the\n // close button) is the right one there.\n if (locked || !taskTriggerRef.current) return\n event.preventDefault()\n focusTaskPicker()\n }}\n onKeyDown={(event) => {\n if ((event.metaKey || event.ctrlKey) && event.key === 'Enter') {\n event.preventDefault()\n // Shift keeps the dialog open for the next entry, so a run of entries\n // never needs the pointer.\n void submit(event.shiftKey ? 'again' : 'close')\n }\n // Escape is deliberately not handled here. Radix's `DismissableLayer`\n // already dismisses only the topmost layer, so a picker popover open\n // inside the form consumes the key on its own; a handler here closed\n // the whole dialog regardless of what was open on top of it.\n }}\n >\n <DialogHeader>\n <DialogTitle>\n {isEdit\n ? t('staff.time_tracking.entryDialog.titleEdit', 'Time entry')\n : t('staff.time_tracking.entryDialog.titleCreate', 'New time entry')}\n </DialogTitle>\n {headerHint ? <span className=\"text-xs text-muted-foreground\">{headerHint}</span> : null}\n </DialogHeader>\n\n {body}\n\n <DialogFooter>\n {/*\n * The labels are flex items, so without `whitespace-nowrap` \"save and\n * add another\" shrinks into a four-line column rather than staying on\n * the line. Hidden below `sm` as well: on a narrow dialog the hint\n * competes with the buttons for room it does not deserve, and a\n * keyboard hint is useless on a touch device anyway.\n */}\n <span className=\"mr-auto hidden items-center gap-1 whitespace-nowrap text-xs text-muted-foreground sm:flex\">\n <Kbd>\u2318</Kbd>\n <Kbd>\u21B5</Kbd>\n {t('staff.time_tracking.entryDialog.shortcutSave', 'save')}\n {locked ? null : (\n <>\n <span aria-hidden=\"true\">\u00B7</span>\n <Kbd>\u2318</Kbd>\n <Kbd>\u21E7</Kbd>\n <Kbd>\u21B5</Kbd>\n {t('staff.time_tracking.entryDialog.shortcutSaveAgain', 'save and add another')}\n </>\n )}\n <span aria-hidden=\"true\">\u00B7</span>\n <Kbd>esc</Kbd>\n {t('staff.time_tracking.entryDialog.shortcutCancel', 'cancel')}\n </span>\n <InjectionSpot\n spotId={extensionSpotChildId(extensionPoints.hosts.timeEntryForm.spotId, 'footer')}\n context={entryInjectionContext}\n data={entryFormValues}\n />\n <Button type=\"button\" variant=\"ghost\" onClick={() => { void requestClose() }}>\n {locked\n ? t('staff.time_tracking.entryDialog.close', 'Close')\n : t('staff.time_tracking.entryDialog.cancel', 'Cancel')}\n </Button>\n {locked ? null : (\n <>\n <Button\n type=\"button\"\n variant=\"outline\"\n disabled={!canSubmit}\n onClick={() => void submit('again')}\n data-testid=\"entry-dialog-save-again\"\n >\n {savingMode === 'again' ? <Spinner className=\"size-4\" /> : null}\n {t('staff.time_tracking.entryDialog.saveAndNew', 'Save and add another')}\n </Button>\n <Button\n type=\"button\"\n disabled={!canSubmit}\n onClick={() => void submit('close')}\n data-testid=\"entry-dialog-save\"\n >\n {savingMode === 'close' ? <Spinner className=\"size-4\" /> : null}\n {t('staff.time_tracking.entryDialog.save', 'Save')}\n </Button>\n </>\n )}\n </DialogFooter>\n {ConfirmDialogElement}\n </DialogContent>\n </Dialog>\n )\n}\n\nconst timeEntryDialogPropsSchema: z.ZodType<TimeEntryDialogProps> = z.object({\n open: z.boolean(),\n onOpenChange: callbackProp<(open: boolean) => void>(),\n entryId: z.string().nullable().optional(),\n defaults: opaqueProp<TimeEntryDialogDefaults>().optional(),\n headerHint: z.string().nullable().optional(),\n onSaved: optionalCallbackProp<(result: { id: string | null; keptOpen: boolean }) => void>(),\n onShowEntry: optionalCallbackProp<(entryId: string) => void>(),\n})\n\nregisterComponent<TimeEntryDialogProps>({\n id: extensionPoints.hosts.timeEntryDialogComponent.componentId,\n component: DefaultTimeEntryDialog,\n metadata: {\n module: 'staff',\n description: 'Create/edit dialog for a single time entry.',\n propsSchema: timeEntryDialogPropsSchema,\n },\n})\n\nexport function TimeEntryDialog(props: TimeEntryDialogProps) {\n const Resolved = useRegisteredComponent<TimeEntryDialogProps>(\n extensionPoints.hosts.timeEntryDialogComponent.componentId,\n DefaultTimeEntryDialog,\n )\n return <Resolved {...props} />\n}\n\nexport default TimeEntryDialog\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
|
+
"names": ["project", "taskId", "body", "overlap"]
|
|
7
|
+
}
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
3
|
+
import * as React from "react";
|
|
4
|
+
import { Lock } from "lucide-react";
|
|
5
|
+
import { cn } from "@open-mercato/shared/lib/utils";
|
|
6
|
+
import { DurationInput } from "./DurationInput.js";
|
|
7
|
+
import { formatDuration } from "../time-tracking/duration.js";
|
|
8
|
+
function TimeEntryDurationCell({
|
|
9
|
+
value,
|
|
10
|
+
readOnly = false,
|
|
11
|
+
saving = false,
|
|
12
|
+
ariaLabel,
|
|
13
|
+
lockedTitle,
|
|
14
|
+
onCommit
|
|
15
|
+
}) {
|
|
16
|
+
const [draft, setDraft] = React.useState(void 0);
|
|
17
|
+
const [status, setStatus] = React.useState("valid");
|
|
18
|
+
const [fieldKey, setFieldKey] = React.useState(0);
|
|
19
|
+
const revertingRef = React.useRef(false);
|
|
20
|
+
const reset = React.useCallback(() => {
|
|
21
|
+
setDraft(void 0);
|
|
22
|
+
setStatus("valid");
|
|
23
|
+
setFieldKey((key) => key + 1);
|
|
24
|
+
}, []);
|
|
25
|
+
const commit = React.useCallback(async () => {
|
|
26
|
+
const next = draft;
|
|
27
|
+
if (next === void 0) return;
|
|
28
|
+
if (status !== "valid" || next === null || next === value) {
|
|
29
|
+
reset();
|
|
30
|
+
return;
|
|
31
|
+
}
|
|
32
|
+
reset();
|
|
33
|
+
await onCommit(next);
|
|
34
|
+
}, [draft, onCommit, reset, status, value]);
|
|
35
|
+
const handleKeyDown = React.useCallback(
|
|
36
|
+
(event) => {
|
|
37
|
+
if (event.key === "Enter") {
|
|
38
|
+
event.preventDefault();
|
|
39
|
+
void commit();
|
|
40
|
+
return;
|
|
41
|
+
}
|
|
42
|
+
if (event.key === "Escape") {
|
|
43
|
+
event.preventDefault();
|
|
44
|
+
event.stopPropagation();
|
|
45
|
+
revertingRef.current = true;
|
|
46
|
+
reset();
|
|
47
|
+
}
|
|
48
|
+
},
|
|
49
|
+
[commit, reset]
|
|
50
|
+
);
|
|
51
|
+
const handleBlur = React.useCallback(() => {
|
|
52
|
+
if (revertingRef.current) {
|
|
53
|
+
revertingRef.current = false;
|
|
54
|
+
return;
|
|
55
|
+
}
|
|
56
|
+
void commit();
|
|
57
|
+
}, [commit]);
|
|
58
|
+
if (readOnly) {
|
|
59
|
+
return /* @__PURE__ */ jsxs(
|
|
60
|
+
"span",
|
|
61
|
+
{
|
|
62
|
+
className: "flex items-center justify-end gap-1.5 font-mono text-sm font-semibold tabular-nums text-muted-foreground",
|
|
63
|
+
title: lockedTitle,
|
|
64
|
+
"data-testid": "entry-duration-locked",
|
|
65
|
+
children: [
|
|
66
|
+
/* @__PURE__ */ jsx(Lock, { className: "h-3 w-3", "aria-hidden": "true" }),
|
|
67
|
+
formatDuration(value, "clock")
|
|
68
|
+
]
|
|
69
|
+
}
|
|
70
|
+
);
|
|
71
|
+
}
|
|
72
|
+
return /* @__PURE__ */ jsx("div", { className: cn("flex justify-end", saving && "opacity-60"), onClick: (event) => event.stopPropagation(), children: /* @__PURE__ */ jsx(
|
|
73
|
+
DurationInput,
|
|
74
|
+
{
|
|
75
|
+
variant: "compact",
|
|
76
|
+
displayStyle: "clock",
|
|
77
|
+
className: "w-20",
|
|
78
|
+
inputClassName: "text-right",
|
|
79
|
+
ariaLabel,
|
|
80
|
+
disabled: saving,
|
|
81
|
+
value: draft === void 0 ? value : draft,
|
|
82
|
+
onChange: (minutes, state) => {
|
|
83
|
+
setDraft(minutes);
|
|
84
|
+
setStatus(state.status);
|
|
85
|
+
},
|
|
86
|
+
onKeyDown: handleKeyDown,
|
|
87
|
+
onBlur: handleBlur
|
|
88
|
+
},
|
|
89
|
+
fieldKey
|
|
90
|
+
) });
|
|
91
|
+
}
|
|
92
|
+
export {
|
|
93
|
+
TimeEntryDurationCell
|
|
94
|
+
};
|
|
95
|
+
//# sourceMappingURL=TimeEntryDurationCell.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../../../../src/modules/staff/lib/time-tracking-ui/TimeEntryDurationCell.tsx"],
|
|
4
|
+
"sourcesContent": ["\"use client\"\n\nimport * as React from 'react'\nimport { Lock } from 'lucide-react'\nimport { cn } from '@open-mercato/shared/lib/utils'\nimport { DurationInput, type DurationInputStatus } from './DurationInput'\nimport { formatDuration } from '../time-tracking/duration'\n\n/**\n * Screen 10 note 1: the `Czas` cell edits in place \u2014 `Enter` or losing focus\n * saves, `esc` puts the previous value back (US-D5).\n *\n * Three decisions:\n *\n * 1. **The field is `DurationInput`'s compact variant, not a bespoke input.**\n * `1h 40m`, `1.5h`, `90m` and `1:40` must mean the same thing in this cell\n * as they do in the entry dialog and the timesheet grid, and unparseable\n * text must behave the same way too. A second parser would drift.\n * 2. **`esc` remounts the field rather than assigning to it.** `DurationInput`\n * owns the typed text so that garbage is never rewritten mid-keystroke; the\n * only honest way to discard that text from the outside is to give it a new\n * `key`. The same remount runs after a save so the cell settles on the\n * canonical `2:30` rather than on whatever spelling was typed.\n * 3. **Text that does not parse is reverted on blur, never saved.** The parent\n * is only ever handed minutes it can send, so a half-typed duration cannot\n * leave the row in a state the server has to reject.\n */\nexport type TimeEntryDurationCellProps = {\n value: number\n /** Locked rows (US-G3) render the duration as text with a lock affordance. */\n readOnly?: boolean\n saving?: boolean\n ariaLabel: string\n lockedTitle?: string\n onCommit: (minutes: number) => void | Promise<void>\n}\n\nexport function TimeEntryDurationCell({\n value,\n readOnly = false,\n saving = false,\n ariaLabel,\n lockedTitle,\n onCommit,\n}: TimeEntryDurationCellProps) {\n const [draft, setDraft] = React.useState<number | null | undefined>(undefined)\n const [status, setStatus] = React.useState<DurationInputStatus>('valid')\n const [fieldKey, setFieldKey] = React.useState(0)\n const revertingRef = React.useRef(false)\n\n const reset = React.useCallback(() => {\n setDraft(undefined)\n setStatus('valid')\n setFieldKey((key) => key + 1)\n }, [])\n\n const commit = React.useCallback(async () => {\n const next = draft\n if (next === undefined) return\n if (status !== 'valid' || next === null || next === value) {\n reset()\n return\n }\n reset()\n await onCommit(next)\n }, [draft, onCommit, reset, status, value])\n\n const handleKeyDown = React.useCallback(\n (event: React.KeyboardEvent<HTMLInputElement>) => {\n if (event.key === 'Enter') {\n event.preventDefault()\n void commit()\n return\n }\n if (event.key === 'Escape') {\n event.preventDefault()\n event.stopPropagation()\n revertingRef.current = true\n reset()\n }\n },\n [commit, reset],\n )\n\n const handleBlur = React.useCallback(() => {\n if (revertingRef.current) {\n revertingRef.current = false\n return\n }\n void commit()\n }, [commit])\n\n if (readOnly) {\n return (\n <span\n className=\"flex items-center justify-end gap-1.5 font-mono text-sm font-semibold tabular-nums text-muted-foreground\"\n title={lockedTitle}\n data-testid=\"entry-duration-locked\"\n >\n <Lock className=\"h-3 w-3\" aria-hidden=\"true\" />\n {formatDuration(value, 'clock')}\n </span>\n )\n }\n\n return (\n <div className={cn('flex justify-end', saving && 'opacity-60')} onClick={(event) => event.stopPropagation()}>\n <DurationInput\n key={fieldKey}\n variant=\"compact\"\n displayStyle=\"clock\"\n className=\"w-20\"\n inputClassName=\"text-right\"\n ariaLabel={ariaLabel}\n disabled={saving}\n value={draft === undefined ? value : draft}\n onChange={(minutes, state) => {\n setDraft(minutes)\n setStatus(state.status)\n }}\n onKeyDown={handleKeyDown}\n onBlur={handleBlur}\n />\n </div>\n )\n}\n"],
|
|
5
|
+
"mappings": ";AA8FM,SAKE,KALF;AA5FN,YAAY,WAAW;AACvB,SAAS,YAAY;AACrB,SAAS,UAAU;AACnB,SAAS,qBAA+C;AACxD,SAAS,sBAAsB;AA+BxB,SAAS,sBAAsB;AAAA,EACpC;AAAA,EACA,WAAW;AAAA,EACX,SAAS;AAAA,EACT;AAAA,EACA;AAAA,EACA;AACF,GAA+B;AAC7B,QAAM,CAAC,OAAO,QAAQ,IAAI,MAAM,SAAoC,MAAS;AAC7E,QAAM,CAAC,QAAQ,SAAS,IAAI,MAAM,SAA8B,OAAO;AACvE,QAAM,CAAC,UAAU,WAAW,IAAI,MAAM,SAAS,CAAC;AAChD,QAAM,eAAe,MAAM,OAAO,KAAK;AAEvC,QAAM,QAAQ,MAAM,YAAY,MAAM;AACpC,aAAS,MAAS;AAClB,cAAU,OAAO;AACjB,gBAAY,CAAC,QAAQ,MAAM,CAAC;AAAA,EAC9B,GAAG,CAAC,CAAC;AAEL,QAAM,SAAS,MAAM,YAAY,YAAY;AAC3C,UAAM,OAAO;AACb,QAAI,SAAS,OAAW;AACxB,QAAI,WAAW,WAAW,SAAS,QAAQ,SAAS,OAAO;AACzD,YAAM;AACN;AAAA,IACF;AACA,UAAM;AACN,UAAM,SAAS,IAAI;AAAA,EACrB,GAAG,CAAC,OAAO,UAAU,OAAO,QAAQ,KAAK,CAAC;AAE1C,QAAM,gBAAgB,MAAM;AAAA,IAC1B,CAAC,UAAiD;AAChD,UAAI,MAAM,QAAQ,SAAS;AACzB,cAAM,eAAe;AACrB,aAAK,OAAO;AACZ;AAAA,MACF;AACA,UAAI,MAAM,QAAQ,UAAU;AAC1B,cAAM,eAAe;AACrB,cAAM,gBAAgB;AACtB,qBAAa,UAAU;AACvB,cAAM;AAAA,MACR;AAAA,IACF;AAAA,IACA,CAAC,QAAQ,KAAK;AAAA,EAChB;AAEA,QAAM,aAAa,MAAM,YAAY,MAAM;AACzC,QAAI,aAAa,SAAS;AACxB,mBAAa,UAAU;AACvB;AAAA,IACF;AACA,SAAK,OAAO;AAAA,EACd,GAAG,CAAC,MAAM,CAAC;AAEX,MAAI,UAAU;AACZ,WACE;AAAA,MAAC;AAAA;AAAA,QACC,WAAU;AAAA,QACV,OAAO;AAAA,QACP,eAAY;AAAA,QAEZ;AAAA,8BAAC,QAAK,WAAU,WAAU,eAAY,QAAO;AAAA,UAC5C,eAAe,OAAO,OAAO;AAAA;AAAA;AAAA,IAChC;AAAA,EAEJ;AAEA,SACE,oBAAC,SAAI,WAAW,GAAG,oBAAoB,UAAU,YAAY,GAAG,SAAS,CAAC,UAAU,MAAM,gBAAgB,GACxG;AAAA,IAAC;AAAA;AAAA,MAEC,SAAQ;AAAA,MACR,cAAa;AAAA,MACb,WAAU;AAAA,MACV,gBAAe;AAAA,MACf;AAAA,MACA,UAAU;AAAA,MACV,OAAO,UAAU,SAAY,QAAQ;AAAA,MACrC,UAAU,CAAC,SAAS,UAAU;AAC5B,iBAAS,OAAO;AAChB,kBAAU,MAAM,MAAM;AAAA,MACxB;AAAA,MACA,WAAW;AAAA,MACX,QAAQ;AAAA;AAAA,IAbH;AAAA,EAcP,GACF;AAEJ;",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|
|
@@ -0,0 +1,188 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
3
|
+
import * as React from "react";
|
|
4
|
+
import { z } from "zod";
|
|
5
|
+
import { Plus } from "lucide-react";
|
|
6
|
+
import { Progress } from "@open-mercato/ui/primitives/progress";
|
|
7
|
+
import { cn } from "@open-mercato/shared/lib/utils";
|
|
8
|
+
import { useT } from "@open-mercato/shared/lib/i18n/context";
|
|
9
|
+
import {
|
|
10
|
+
InjectionSpot,
|
|
11
|
+
useInjectionWidgets
|
|
12
|
+
} from "@open-mercato/ui/backend/injection/InjectionSpot";
|
|
13
|
+
import { extensionPoints } from "@open-mercato/core/modules/staff/extension-points";
|
|
14
|
+
import { registerComponent } from "@open-mercato/shared/modules/widgets/component-registry";
|
|
15
|
+
import { useRegisteredComponent } from "@open-mercato/ui/backend/injection/useRegisteredComponent";
|
|
16
|
+
import { callbackProp, opaqueProp } from "../time-tracking/componentContracts.js";
|
|
17
|
+
import { formatDuration } from "../time-tracking/duration.js";
|
|
18
|
+
import {
|
|
19
|
+
buildCalendarWeeks,
|
|
20
|
+
formatPeriodLabel,
|
|
21
|
+
parseIsoDay
|
|
22
|
+
} from "./timesheetPeriod.js";
|
|
23
|
+
import { entryChipLabel, loadPercent } from "./timesheetData.js";
|
|
24
|
+
function weekdayLabels(locale) {
|
|
25
|
+
const base = new Date(2024, 0, 1);
|
|
26
|
+
return Array.from({ length: 7 }, (_, index) => {
|
|
27
|
+
const day = new Date(base.getFullYear(), base.getMonth(), base.getDate() + index);
|
|
28
|
+
try {
|
|
29
|
+
return new Intl.DateTimeFormat(locale, { weekday: "short" }).format(day);
|
|
30
|
+
} catch {
|
|
31
|
+
return String(index);
|
|
32
|
+
}
|
|
33
|
+
});
|
|
34
|
+
}
|
|
35
|
+
function DefaultTimesheetCalendar({
|
|
36
|
+
monthAnchors,
|
|
37
|
+
days,
|
|
38
|
+
scaleMinutes,
|
|
39
|
+
todayDate,
|
|
40
|
+
onAddEntry,
|
|
41
|
+
onSelectEntry,
|
|
42
|
+
locale,
|
|
43
|
+
showMonthHeadings = false
|
|
44
|
+
}) {
|
|
45
|
+
const t = useT();
|
|
46
|
+
const weekdays = React.useMemo(() => weekdayLabels(locale), [locale]);
|
|
47
|
+
const addLabel = t("staff.time_tracking.timesheet.calendar.add", "+ add");
|
|
48
|
+
const nonBillableLabel = t("staff.time_tracking.timesheet.calendar.nonBillable", "Non-billable");
|
|
49
|
+
const { widgets: dayCellActionWidgets } = useInjectionWidgets(
|
|
50
|
+
extensionPoints.hosts.timesheetDayCellActions.spotId
|
|
51
|
+
);
|
|
52
|
+
return /* @__PURE__ */ jsx("div", { className: "flex flex-col gap-6", children: monthAnchors.map((anchor) => /* @__PURE__ */ jsxs("section", { className: "flex flex-col gap-2", children: [
|
|
53
|
+
showMonthHeadings ? /* @__PURE__ */ jsx("h3", { className: "text-sm font-semibold text-foreground", children: formatPeriodLabel("month", anchor, locale) }) : null,
|
|
54
|
+
/* @__PURE__ */ jsxs("div", { className: "grid grid-cols-7 gap-1", children: [
|
|
55
|
+
weekdays.map((label) => /* @__PURE__ */ jsx("div", { className: "px-1 pb-1 text-xs font-medium uppercase text-muted-foreground", children: label }, label)),
|
|
56
|
+
buildCalendarWeeks(anchor, todayDate).flat().map((cell) => /* @__PURE__ */ jsx(
|
|
57
|
+
CalendarDayCell,
|
|
58
|
+
{
|
|
59
|
+
cell,
|
|
60
|
+
day: days.get(cell.date) ?? null,
|
|
61
|
+
scaleMinutes,
|
|
62
|
+
addLabel,
|
|
63
|
+
nonBillableLabel,
|
|
64
|
+
onAddEntry,
|
|
65
|
+
onSelectEntry,
|
|
66
|
+
locale,
|
|
67
|
+
dayCellActionWidgets
|
|
68
|
+
},
|
|
69
|
+
cell.date
|
|
70
|
+
))
|
|
71
|
+
] })
|
|
72
|
+
] }, anchor)) });
|
|
73
|
+
}
|
|
74
|
+
function CalendarDayCell({
|
|
75
|
+
cell,
|
|
76
|
+
day,
|
|
77
|
+
scaleMinutes,
|
|
78
|
+
addLabel,
|
|
79
|
+
nonBillableLabel,
|
|
80
|
+
onAddEntry,
|
|
81
|
+
onSelectEntry,
|
|
82
|
+
locale,
|
|
83
|
+
dayCellActionWidgets
|
|
84
|
+
}) {
|
|
85
|
+
const totalMinutes = day?.totalMinutes ?? 0;
|
|
86
|
+
const entries = day?.entries ?? [];
|
|
87
|
+
const percent = loadPercent(totalMinutes, scaleMinutes);
|
|
88
|
+
const parsed = parseIsoDay(cell.date);
|
|
89
|
+
const fullDate = parsed ? (() => {
|
|
90
|
+
try {
|
|
91
|
+
return new Intl.DateTimeFormat(locale, { dateStyle: "full" }).format(parsed);
|
|
92
|
+
} catch {
|
|
93
|
+
return cell.date;
|
|
94
|
+
}
|
|
95
|
+
})() : cell.date;
|
|
96
|
+
return /* @__PURE__ */ jsxs(
|
|
97
|
+
"div",
|
|
98
|
+
{
|
|
99
|
+
className: cn(
|
|
100
|
+
"group flex min-h-24 flex-col gap-1 rounded-md border p-1.5 transition-colors",
|
|
101
|
+
cell.inPeriod ? "bg-card" : "bg-muted/40 text-muted-foreground",
|
|
102
|
+
cell.isWeekend && cell.inPeriod ? "bg-muted/30" : null,
|
|
103
|
+
cell.isToday ? "border-primary" : "border-border"
|
|
104
|
+
),
|
|
105
|
+
children: [
|
|
106
|
+
/* @__PURE__ */ jsxs("div", { className: "flex items-baseline justify-between gap-1", children: [
|
|
107
|
+
/* @__PURE__ */ jsx("span", { className: cn("text-xs", cell.isToday ? "font-semibold text-foreground" : "text-muted-foreground"), children: cell.dayOfMonth }),
|
|
108
|
+
totalMinutes > 0 ? /* @__PURE__ */ jsx("span", { className: "font-mono text-xs tabular-nums text-foreground", children: formatDuration(totalMinutes, "clock") }) : null
|
|
109
|
+
] }),
|
|
110
|
+
cell.inPeriod && totalMinutes > 0 ? /* @__PURE__ */ jsx(Progress, { value: percent, size: "sm" }) : null,
|
|
111
|
+
entries.map((entry) => /* @__PURE__ */ jsxs(
|
|
112
|
+
"button",
|
|
113
|
+
{
|
|
114
|
+
type: "button",
|
|
115
|
+
onClick: () => onSelectEntry(entry),
|
|
116
|
+
title: entry.isBillable ? void 0 : nonBillableLabel,
|
|
117
|
+
className: cn(
|
|
118
|
+
"flex w-full items-center justify-between gap-1 rounded px-1 py-0.5 text-left text-xs",
|
|
119
|
+
"hover:bg-muted focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring",
|
|
120
|
+
entry.isBillable ? "border border-transparent bg-muted/60" : "border border-dashed border-border"
|
|
121
|
+
),
|
|
122
|
+
children: [
|
|
123
|
+
/* @__PURE__ */ jsx("span", { className: "min-w-0 truncate", children: entryChipLabel(entry, "\u2014") }),
|
|
124
|
+
/* @__PURE__ */ jsx("span", { className: "shrink-0 font-mono tabular-nums text-muted-foreground", children: formatDuration(entry.durationMinutes, "clock") })
|
|
125
|
+
]
|
|
126
|
+
},
|
|
127
|
+
entry.id
|
|
128
|
+
)),
|
|
129
|
+
cell.inPeriod ? /* @__PURE__ */ jsxs(
|
|
130
|
+
"button",
|
|
131
|
+
{
|
|
132
|
+
type: "button",
|
|
133
|
+
onClick: () => onAddEntry(cell.date),
|
|
134
|
+
"aria-label": `${addLabel} \u2014 ${fullDate}`,
|
|
135
|
+
className: cn(
|
|
136
|
+
"mt-auto inline-flex items-center gap-1 rounded px-1 py-0.5 text-left text-xs text-muted-foreground",
|
|
137
|
+
"opacity-0 transition-opacity group-hover:opacity-100 focus-visible:opacity-100",
|
|
138
|
+
"hover:text-foreground focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring"
|
|
139
|
+
),
|
|
140
|
+
children: [
|
|
141
|
+
/* @__PURE__ */ jsx(Plus, { className: "size-3", "aria-hidden": "true" }),
|
|
142
|
+
addLabel
|
|
143
|
+
]
|
|
144
|
+
}
|
|
145
|
+
) : null,
|
|
146
|
+
cell.inPeriod ? /* @__PURE__ */ jsx(
|
|
147
|
+
InjectionSpot,
|
|
148
|
+
{
|
|
149
|
+
spotId: extensionPoints.hosts.timesheetDayCellActions.spotId,
|
|
150
|
+
context: { date: cell.date, isToday: cell.isToday, isWeekend: cell.isWeekend, totalMinutes },
|
|
151
|
+
data: entries,
|
|
152
|
+
widgetsOverride: dayCellActionWidgets
|
|
153
|
+
}
|
|
154
|
+
) : null
|
|
155
|
+
]
|
|
156
|
+
}
|
|
157
|
+
);
|
|
158
|
+
}
|
|
159
|
+
const timesheetCalendarPropsSchema = z.object({
|
|
160
|
+
monthAnchors: z.array(z.string()),
|
|
161
|
+
days: opaqueProp(),
|
|
162
|
+
scaleMinutes: z.number().nullable(),
|
|
163
|
+
todayDate: z.string(),
|
|
164
|
+
onAddEntry: callbackProp(),
|
|
165
|
+
onSelectEntry: callbackProp(),
|
|
166
|
+
locale: z.string().optional(),
|
|
167
|
+
showMonthHeadings: z.boolean().optional()
|
|
168
|
+
});
|
|
169
|
+
registerComponent({
|
|
170
|
+
id: extensionPoints.hosts.timesheetCalendarComponent.componentId,
|
|
171
|
+
component: DefaultTimesheetCalendar,
|
|
172
|
+
metadata: {
|
|
173
|
+
module: "staff",
|
|
174
|
+
description: "Timesheet month calendar with per-day load bars and entry chips.",
|
|
175
|
+
propsSchema: timesheetCalendarPropsSchema
|
|
176
|
+
}
|
|
177
|
+
});
|
|
178
|
+
function TimesheetCalendar(props) {
|
|
179
|
+
const Resolved = useRegisteredComponent(
|
|
180
|
+
extensionPoints.hosts.timesheetCalendarComponent.componentId,
|
|
181
|
+
DefaultTimesheetCalendar
|
|
182
|
+
);
|
|
183
|
+
return /* @__PURE__ */ jsx(Resolved, { ...props });
|
|
184
|
+
}
|
|
185
|
+
export {
|
|
186
|
+
TimesheetCalendar
|
|
187
|
+
};
|
|
188
|
+
//# sourceMappingURL=TimesheetCalendar.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../../../../src/modules/staff/lib/time-tracking-ui/TimesheetCalendar.tsx"],
|
|
4
|
+
"sourcesContent": ["\"use client\"\n\nimport * as React from 'react'\nimport { z } from 'zod'\nimport { Plus } from 'lucide-react'\nimport { Progress } from '@open-mercato/ui/primitives/progress'\nimport { cn } from '@open-mercato/shared/lib/utils'\nimport { useT } from '@open-mercato/shared/lib/i18n/context'\nimport {\n InjectionSpot,\n useInjectionWidgets,\n type LoadedInjectionSpotWidget,\n} from '@open-mercato/ui/backend/injection/InjectionSpot'\nimport { extensionPoints } from '@open-mercato/core/modules/staff/extension-points'\nimport { registerComponent } from '@open-mercato/shared/modules/widgets/component-registry'\nimport { useRegisteredComponent } from '@open-mercato/ui/backend/injection/useRegisteredComponent'\nimport { callbackProp, opaqueProp } from '../time-tracking/componentContracts'\nimport { formatDuration } from '../time-tracking/duration'\nimport {\n buildCalendarWeeks,\n formatPeriodLabel,\n parseIsoDay,\n type TimesheetCalendarCell,\n} from './timesheetPeriod'\nimport { entryChipLabel, loadPercent, type TimesheetDayIndex, type TimesheetEntry } from './timesheetData'\n\n/**\n * Screen 11 \u2014 the month calendar (T5.2).\n *\n * The mockup's four notes are the four things this component exists to do:\n *\n * 1. **A load bar under the date** so light and heavy days read without reading\n * numbers (note 1, US-E3). Its scale comes from the caller, which is what\n * lets note 6 work: with `targets.dailyHours` set the bar is \"% of target\",\n * without it the bars are ranked against the longest day of the period.\n * 2. **`+ dodaj` on hover** opens the shared entry dialog pre-dated to that day\n * (note 2, US-E4). It is rendered for every in-period day and revealed on\n * hover *or keyboard focus* \u2014 a hover-only affordance would put adding time\n * out of reach of anybody not using a mouse.\n * 3. **A non-billable chip is dashed, not coloured** (note 3), so the\n * distinction survives greyscale and colour blindness. The chip also carries\n * a `title`, so the meaning is available to a screen reader rather than\n * living in a border style.\n * 4. Periods longer than a month render one grid per month rather than a\n * disabled switch. A quarter and a year are the same question as a month,\n * asked over more days.\n */\n\nexport type TimesheetCalendarProps = {\n /** One anchor day per month to render, in order. */\n monthAnchors: readonly string[]\n days: TimesheetDayIndex\n scaleMinutes: number | null\n todayDate: string\n onAddEntry: (date: string) => void\n onSelectEntry: (entry: TimesheetEntry) => void\n locale?: string\n /** Multi-month periods get a heading per grid; a single month does not need one. */\n showMonthHeadings?: boolean\n}\n\nfunction weekdayLabels(locale: string | undefined): string[] {\n // 2024-01-01 was a Monday, so the seven days from it are Mon\u2026Sun in order.\n const base = new Date(2024, 0, 1)\n return Array.from({ length: 7 }, (_, index) => {\n const day = new Date(base.getFullYear(), base.getMonth(), base.getDate() + index)\n try {\n return new Intl.DateTimeFormat(locale, { weekday: 'short' }).format(day)\n } catch {\n return String(index)\n }\n })\n}\n\nfunction DefaultTimesheetCalendar({\n monthAnchors,\n days,\n scaleMinutes,\n todayDate,\n onAddEntry,\n onSelectEntry,\n locale,\n showMonthHeadings = false,\n}: TimesheetCalendarProps) {\n const t = useT()\n const weekdays = React.useMemo(() => weekdayLabels(locale), [locale])\n const addLabel = t('staff.time_tracking.timesheet.calendar.add', '+ add')\n const nonBillableLabel = t('staff.time_tracking.timesheet.calendar.nonBillable', 'Non-billable')\n /**\n * Loaded once for the whole grid and handed to every cell as an override: a\n * month renders ~35 cells, and letting each one resolve the spot on its own\n * would mean 35 registry reads and 35 state updates for a spot that is empty\n * on every stock install.\n */\n const { widgets: dayCellActionWidgets } = useInjectionWidgets(\n extensionPoints.hosts.timesheetDayCellActions.spotId,\n )\n\n return (\n <div className=\"flex flex-col gap-6\">\n {monthAnchors.map((anchor) => (\n <section key={anchor} className=\"flex flex-col gap-2\">\n {showMonthHeadings ? (\n <h3 className=\"text-sm font-semibold text-foreground\">{formatPeriodLabel('month', anchor, locale)}</h3>\n ) : null}\n <div className=\"grid grid-cols-7 gap-1\">\n {weekdays.map((label) => (\n <div key={label} className=\"px-1 pb-1 text-xs font-medium uppercase text-muted-foreground\">\n {label}\n </div>\n ))}\n {buildCalendarWeeks(anchor, todayDate)\n .flat()\n .map((cell) => (\n <CalendarDayCell\n key={cell.date}\n cell={cell}\n day={days.get(cell.date) ?? null}\n scaleMinutes={scaleMinutes}\n addLabel={addLabel}\n nonBillableLabel={nonBillableLabel}\n onAddEntry={onAddEntry}\n onSelectEntry={onSelectEntry}\n locale={locale}\n dayCellActionWidgets={dayCellActionWidgets}\n />\n ))}\n </div>\n </section>\n ))}\n </div>\n )\n}\n\nfunction CalendarDayCell({\n cell,\n day,\n scaleMinutes,\n addLabel,\n nonBillableLabel,\n onAddEntry,\n onSelectEntry,\n locale,\n dayCellActionWidgets,\n}: {\n cell: TimesheetCalendarCell\n day: { totalMinutes: number; entries: TimesheetEntry[] } | null\n scaleMinutes: number | null\n addLabel: string\n nonBillableLabel: string\n onAddEntry: (date: string) => void\n onSelectEntry: (entry: TimesheetEntry) => void\n locale?: string\n dayCellActionWidgets: LoadedInjectionSpotWidget[]\n}) {\n const totalMinutes = day?.totalMinutes ?? 0\n const entries = day?.entries ?? []\n const percent = loadPercent(totalMinutes, scaleMinutes)\n const parsed = parseIsoDay(cell.date)\n const fullDate = parsed\n ? (() => {\n try {\n return new Intl.DateTimeFormat(locale, { dateStyle: 'full' }).format(parsed)\n } catch {\n return cell.date\n }\n })()\n : cell.date\n\n return (\n <div\n className={cn(\n 'group flex min-h-24 flex-col gap-1 rounded-md border p-1.5 transition-colors',\n cell.inPeriod ? 'bg-card' : 'bg-muted/40 text-muted-foreground',\n cell.isWeekend && cell.inPeriod ? 'bg-muted/30' : null,\n cell.isToday ? 'border-primary' : 'border-border',\n )}\n >\n <div className=\"flex items-baseline justify-between gap-1\">\n <span className={cn('text-xs', cell.isToday ? 'font-semibold text-foreground' : 'text-muted-foreground')}>\n {cell.dayOfMonth}\n </span>\n {totalMinutes > 0 ? (\n <span className=\"font-mono text-xs tabular-nums text-foreground\">\n {formatDuration(totalMinutes, 'clock')}\n </span>\n ) : null}\n </div>\n\n {cell.inPeriod && totalMinutes > 0 ? <Progress value={percent} size=\"sm\" /> : null}\n\n {entries.map((entry) => (\n <button\n key={entry.id}\n type=\"button\"\n onClick={() => onSelectEntry(entry)}\n title={entry.isBillable ? undefined : nonBillableLabel}\n className={cn(\n 'flex w-full items-center justify-between gap-1 rounded px-1 py-0.5 text-left text-xs',\n 'hover:bg-muted focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring',\n entry.isBillable ? 'border border-transparent bg-muted/60' : 'border border-dashed border-border',\n )}\n >\n <span className=\"min-w-0 truncate\">{entryChipLabel(entry, '\u2014')}</span>\n <span className=\"shrink-0 font-mono tabular-nums text-muted-foreground\">\n {formatDuration(entry.durationMinutes, 'clock')}\n </span>\n </button>\n ))}\n\n {cell.inPeriod ? (\n <button\n type=\"button\"\n onClick={() => onAddEntry(cell.date)}\n aria-label={`${addLabel} \u2014 ${fullDate}`}\n className={cn(\n 'mt-auto inline-flex items-center gap-1 rounded px-1 py-0.5 text-left text-xs text-muted-foreground',\n 'opacity-0 transition-opacity group-hover:opacity-100 focus-visible:opacity-100',\n 'hover:text-foreground focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring',\n )}\n >\n <Plus className=\"size-3\" aria-hidden=\"true\" />\n {addLabel}\n </button>\n ) : null}\n {cell.inPeriod ? (\n <InjectionSpot\n spotId={extensionPoints.hosts.timesheetDayCellActions.spotId}\n context={{ date: cell.date, isToday: cell.isToday, isWeekend: cell.isWeekend, totalMinutes }}\n data={entries}\n widgetsOverride={dayCellActionWidgets}\n />\n ) : null}\n </div>\n )\n}\n\nconst timesheetCalendarPropsSchema: z.ZodType<TimesheetCalendarProps> = z.object({\n monthAnchors: z.array(z.string()),\n days: opaqueProp<TimesheetDayIndex>(),\n scaleMinutes: z.number().nullable(),\n todayDate: z.string(),\n onAddEntry: callbackProp<(date: string) => void>(),\n onSelectEntry: callbackProp<(entry: TimesheetEntry) => void>(),\n locale: z.string().optional(),\n showMonthHeadings: z.boolean().optional(),\n})\n\nregisterComponent<TimesheetCalendarProps>({\n id: extensionPoints.hosts.timesheetCalendarComponent.componentId,\n component: DefaultTimesheetCalendar,\n metadata: {\n module: 'staff',\n description: 'Timesheet month calendar with per-day load bars and entry chips.',\n propsSchema: timesheetCalendarPropsSchema,\n },\n})\n\nexport function TimesheetCalendar(props: TimesheetCalendarProps) {\n const Resolved = useRegisteredComponent<TimesheetCalendarProps>(\n extensionPoints.hosts.timesheetCalendarComponent.componentId,\n DefaultTimesheetCalendar,\n )\n return <Resolved {...props} />\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|