@open-mercato/core 0.6.4-develop.4217.1.c9aa050183 → 0.6.4-develop.4236.1.9fa6806b34
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 +37 -0
- package/dist/generated/entities/staff_time_entry/index.js.map +7 -0
- package/dist/generated/entities/staff_time_entry_segment/index.js +23 -0
- package/dist/generated/entities/staff_time_entry_segment/index.js.map +7 -0
- package/dist/generated/entities/staff_time_project/index.js +35 -0
- package/dist/generated/entities/staff_time_project/index.js.map +7 -0
- package/dist/generated/entities/staff_time_project_member/index.js +29 -0
- package/dist/generated/entities/staff_time_project_member/index.js.map +7 -0
- package/dist/generated/entities.ids.generated.js +5 -1
- package/dist/generated/entities.ids.generated.js.map +2 -2
- package/dist/generated/entity-fields-registry.js +64 -0
- package/dist/generated/entity-fields-registry.js.map +2 -2
- package/dist/helpers/integration/timesheetFixtures.js +50 -0
- package/dist/helpers/integration/timesheetFixtures.js.map +7 -0
- package/dist/modules/attachments/api/library/[id]/route.js +20 -16
- package/dist/modules/attachments/api/library/[id]/route.js.map +2 -2
- package/dist/modules/attachments/api/route.js +18 -14
- package/dist/modules/attachments/api/route.js.map +2 -2
- package/dist/modules/auth/api/roles/acl/route.js +10 -4
- package/dist/modules/auth/api/roles/acl/route.js.map +2 -2
- package/dist/modules/auth/api/sidebar/preferences/route.js +27 -20
- package/dist/modules/auth/api/sidebar/preferences/route.js.map +2 -2
- package/dist/modules/auth/api/users/acl/route.js +16 -11
- package/dist/modules/auth/api/users/acl/route.js.map +2 -2
- package/dist/modules/auth/commands/users.js +87 -71
- package/dist/modules/auth/commands/users.js.map +2 -2
- package/dist/modules/auth/services/sidebarPreferencesService.js +39 -30
- package/dist/modules/auth/services/sidebarPreferencesService.js.map +2 -2
- package/dist/modules/catalog/commands/categories.js +61 -12
- package/dist/modules/catalog/commands/categories.js.map +2 -2
- package/dist/modules/catalog/commands/products.js +79 -54
- package/dist/modules/catalog/commands/products.js.map +2 -2
- package/dist/modules/catalog/commands/variants.js +29 -16
- package/dist/modules/catalog/commands/variants.js.map +2 -2
- package/dist/modules/currencies/commands/currencies.js +15 -8
- package/dist/modules/currencies/commands/currencies.js.map +2 -2
- package/dist/modules/customer_accounts/api/admin/users.js +27 -26
- package/dist/modules/customer_accounts/api/admin/users.js.map +2 -2
- package/dist/modules/customer_accounts/api/password/reset-confirm.js +5 -5
- package/dist/modules/customer_accounts/api/password/reset-confirm.js.map +2 -2
- package/dist/modules/customer_accounts/api/portal/users/[id]/roles.js +11 -10
- package/dist/modules/customer_accounts/api/portal/users/[id]/roles.js.map +2 -2
- package/dist/modules/customers/commands/addresses.js +35 -21
- package/dist/modules/customers/commands/addresses.js.map +2 -2
- package/dist/modules/customers/commands/companies.js +163 -162
- package/dist/modules/customers/commands/companies.js.map +2 -2
- package/dist/modules/customers/commands/deals.js +3 -4
- package/dist/modules/customers/commands/deals.js.map +2 -2
- package/dist/modules/customers/commands/interactions.js +19 -22
- package/dist/modules/customers/commands/interactions.js.map +2 -2
- package/dist/modules/customers/commands/people.js +18 -15
- package/dist/modules/customers/commands/people.js.map +2 -2
- package/dist/modules/customers/commands/personCompanyLinks.js +105 -94
- package/dist/modules/customers/commands/personCompanyLinks.js.map +2 -2
- package/dist/modules/customers/commands/pipeline-stages.js +30 -23
- package/dist/modules/customers/commands/pipeline-stages.js.map +2 -2
- package/dist/modules/customers/commands/pipelines.js +27 -20
- package/dist/modules/customers/commands/pipelines.js.map +2 -2
- package/dist/modules/customers/commands/tags.js +13 -5
- package/dist/modules/customers/commands/tags.js.map +2 -2
- package/dist/modules/dashboards/api/users/widgets/route.js +0 -1
- package/dist/modules/dashboards/api/users/widgets/route.js.map +2 -2
- package/dist/modules/dashboards/api/widgets/data/route.js +29 -1
- package/dist/modules/dashboards/api/widgets/data/route.js.map +2 -2
- package/dist/modules/data_sync/lib/sync-engine.js +4 -4
- package/dist/modules/data_sync/lib/sync-engine.js.map +2 -2
- package/dist/modules/data_sync/lib/sync-run-service.js +51 -27
- package/dist/modules/data_sync/lib/sync-run-service.js.map +2 -2
- package/dist/modules/directory/commands/organizations.js +192 -158
- package/dist/modules/directory/commands/organizations.js.map +3 -3
- package/dist/modules/inbox_ops/api/emails/[id]/reprocess/route.js +22 -16
- package/dist/modules/inbox_ops/api/emails/[id]/reprocess/route.js.map +2 -2
- package/dist/modules/messages/commands/messages.js +77 -75
- package/dist/modules/messages/commands/messages.js.map +2 -2
- package/dist/modules/messages/commands/shared.js +132 -132
- package/dist/modules/messages/commands/shared.js.map +2 -2
- package/dist/modules/perspectives/api/[tableId]/route.js +37 -26
- package/dist/modules/perspectives/api/[tableId]/route.js.map +2 -2
- package/dist/modules/resources/commands/resources.js +125 -117
- package/dist/modules/resources/commands/resources.js.map +2 -2
- package/dist/modules/resources/commands/tags.js +7 -3
- package/dist/modules/resources/commands/tags.js.map +2 -2
- package/dist/modules/sales/api/quotes/send/route.js +12 -11
- package/dist/modules/sales/api/quotes/send/route.js.map +2 -2
- package/dist/modules/sales/commands/documents.js +629 -478
- package/dist/modules/sales/commands/documents.js.map +2 -2
- package/dist/modules/sales/commands/payments.js +146 -146
- package/dist/modules/sales/commands/payments.js.map +2 -2
- package/dist/modules/sales/commands/returns.js +68 -60
- package/dist/modules/sales/commands/returns.js.map +2 -2
- package/dist/modules/staff/acl.js +10 -1
- package/dist/modules/staff/acl.js.map +2 -2
- package/dist/modules/staff/analytics.js +33 -0
- package/dist/modules/staff/analytics.js.map +7 -0
- package/dist/modules/staff/api/guards.js +31 -0
- package/dist/modules/staff/api/guards.js.map +7 -0
- package/dist/modules/staff/api/interceptors.js +96 -0
- package/dist/modules/staff/api/interceptors.js.map +7 -0
- package/dist/modules/staff/api/timesheets/my-projects/[projectId]/route.js +170 -0
- package/dist/modules/staff/api/timesheets/my-projects/[projectId]/route.js.map +7 -0
- package/dist/modules/staff/api/timesheets/my-projects/route.js +103 -0
- package/dist/modules/staff/api/timesheets/my-projects/route.js.map +7 -0
- package/dist/modules/staff/api/timesheets/projects/kpis/route.js +147 -0
- package/dist/modules/staff/api/timesheets/projects/kpis/route.js.map +7 -0
- package/dist/modules/staff/api/timesheets/time-entries/[id]/segments/[segmentId]/route.js +171 -0
- package/dist/modules/staff/api/timesheets/time-entries/[id]/segments/[segmentId]/route.js.map +7 -0
- package/dist/modules/staff/api/timesheets/time-entries/[id]/segments/route.js +180 -0
- package/dist/modules/staff/api/timesheets/time-entries/[id]/segments/route.js.map +7 -0
- package/dist/modules/staff/api/timesheets/time-entries/[id]/timer-start/route.js +155 -0
- package/dist/modules/staff/api/timesheets/time-entries/[id]/timer-start/route.js.map +7 -0
- package/dist/modules/staff/api/timesheets/time-entries/[id]/timer-stop/route.js +173 -0
- package/dist/modules/staff/api/timesheets/time-entries/[id]/timer-stop/route.js.map +7 -0
- package/dist/modules/staff/api/timesheets/time-entries/bulk/route.js +260 -0
- package/dist/modules/staff/api/timesheets/time-entries/bulk/route.js.map +7 -0
- package/dist/modules/staff/api/timesheets/time-entries/route.js +188 -0
- package/dist/modules/staff/api/timesheets/time-entries/route.js.map +7 -0
- package/dist/modules/staff/api/timesheets/time-projects/[id]/employees/route.js +159 -0
- package/dist/modules/staff/api/timesheets/time-projects/[id]/employees/route.js.map +7 -0
- package/dist/modules/staff/api/timesheets/time-projects/route.js +230 -0
- package/dist/modules/staff/api/timesheets/time-projects/route.js.map +7 -0
- package/dist/modules/staff/backend/staff/timesheets/page.js +710 -0
- package/dist/modules/staff/backend/staff/timesheets/page.js.map +7 -0
- package/dist/modules/staff/backend/staff/timesheets/page.meta.js +22 -0
- package/dist/modules/staff/backend/staff/timesheets/page.meta.js.map +7 -0
- package/dist/modules/staff/backend/staff/timesheets/projects/[id]/edit/page.js +125 -0
- package/dist/modules/staff/backend/staff/timesheets/projects/[id]/edit/page.js.map +7 -0
- package/dist/modules/staff/backend/staff/timesheets/projects/[id]/edit/page.meta.js +16 -0
- package/dist/modules/staff/backend/staff/timesheets/projects/[id]/edit/page.meta.js.map +7 -0
- package/dist/modules/staff/backend/staff/timesheets/projects/[id]/page.js +418 -0
- package/dist/modules/staff/backend/staff/timesheets/projects/[id]/page.js.map +7 -0
- package/dist/modules/staff/backend/staff/timesheets/projects/[id]/page.meta.js +16 -0
- package/dist/modules/staff/backend/staff/timesheets/projects/[id]/page.meta.js.map +7 -0
- package/dist/modules/staff/backend/staff/timesheets/projects/create/page.js +79 -0
- package/dist/modules/staff/backend/staff/timesheets/projects/create/page.js.map +7 -0
- package/dist/modules/staff/backend/staff/timesheets/projects/create/page.meta.js +16 -0
- package/dist/modules/staff/backend/staff/timesheets/projects/create/page.meta.js.map +7 -0
- package/dist/modules/staff/backend/staff/timesheets/projects/page.js +602 -0
- package/dist/modules/staff/backend/staff/timesheets/projects/page.js.map +7 -0
- package/dist/modules/staff/backend/staff/timesheets/projects/page.meta.js +25 -0
- package/dist/modules/staff/backend/staff/timesheets/projects/page.meta.js.map +7 -0
- package/dist/modules/staff/backend/staff/timesheets/projects/projectFormConfig.js +123 -0
- package/dist/modules/staff/backend/staff/timesheets/projects/projectFormConfig.js.map +7 -0
- package/dist/modules/staff/cli.js +38 -1
- package/dist/modules/staff/cli.js.map +2 -2
- package/dist/modules/staff/commands/index.js +2 -0
- package/dist/modules/staff/commands/index.js.map +2 -2
- package/dist/modules/staff/commands/leave-requests.js +30 -28
- package/dist/modules/staff/commands/leave-requests.js.map +3 -3
- package/dist/modules/staff/commands/team-members.js +21 -20
- package/dist/modules/staff/commands/team-members.js.map +2 -2
- package/dist/modules/staff/commands/timesheets-entries.js +409 -0
- package/dist/modules/staff/commands/timesheets-entries.js.map +7 -0
- package/dist/modules/staff/commands/timesheets-projects.js +618 -0
- package/dist/modules/staff/commands/timesheets-projects.js.map +7 -0
- package/dist/modules/staff/data/enrichers.js +104 -0
- package/dist/modules/staff/data/enrichers.js.map +7 -0
- package/dist/modules/staff/data/entities.js +226 -1
- package/dist/modules/staff/data/entities.js.map +2 -2
- package/dist/modules/staff/data/validators.js +113 -1
- package/dist/modules/staff/data/validators.js.map +2 -2
- package/dist/modules/staff/events.js +13 -1
- package/dist/modules/staff/events.js.map +2 -2
- package/dist/modules/staff/lib/crud.js +7 -1
- package/dist/modules/staff/lib/crud.js.map +2 -2
- package/dist/modules/staff/lib/staffMemberResolver.js +15 -0
- package/dist/modules/staff/lib/staffMemberResolver.js.map +7 -0
- package/dist/modules/staff/lib/timesheets-projects/computeProjectHoursTrend.js +60 -0
- package/dist/modules/staff/lib/timesheets-projects/computeProjectHoursTrend.js.map +7 -0
- package/dist/modules/staff/lib/timesheets-projects/computeProjectsKpis.js +260 -0
- package/dist/modules/staff/lib/timesheets-projects/computeProjectsKpis.js.map +7 -0
- package/dist/modules/staff/lib/timesheets-projects/dateBuckets.js +41 -0
- package/dist/modules/staff/lib/timesheets-projects/dateBuckets.js.map +7 -0
- package/dist/modules/staff/lib/timesheets-projects/initials.js +10 -0
- package/dist/modules/staff/lib/timesheets-projects/initials.js.map +7 -0
- package/dist/modules/staff/lib/timesheets-projects/kpiMath.js +12 -0
- package/dist/modules/staff/lib/timesheets-projects/kpiMath.js.map +7 -0
- package/dist/modules/staff/lib/timesheets-projects/listProjectMembersPreview.js +55 -0
- package/dist/modules/staff/lib/timesheets-projects/listProjectMembersPreview.js.map +7 -0
- package/dist/modules/staff/lib/timesheets-projects-ui/HoursSparkline.js +66 -0
- package/dist/modules/staff/lib/timesheets-projects-ui/HoursSparkline.js.map +7 -0
- package/dist/modules/staff/lib/timesheets-projects-ui/ProjectCard.js +81 -0
- package/dist/modules/staff/lib/timesheets-projects-ui/ProjectCard.js.map +7 -0
- package/dist/modules/staff/lib/timesheets-projects-ui/ProjectMembersAvatarStack.js +58 -0
- package/dist/modules/staff/lib/timesheets-projects-ui/ProjectMembersAvatarStack.js.map +7 -0
- package/dist/modules/staff/lib/timesheets-projects-ui/ProjectsKpiStrip.js +152 -0
- package/dist/modules/staff/lib/timesheets-projects-ui/ProjectsKpiStrip.js.map +7 -0
- package/dist/modules/staff/lib/timesheets-projects-ui/SavedViewTabs.js +37 -0
- package/dist/modules/staff/lib/timesheets-projects-ui/SavedViewTabs.js.map +7 -0
- package/dist/modules/staff/lib/timesheets-projects-ui/ViewModeToggle.js +57 -0
- package/dist/modules/staff/lib/timesheets-projects-ui/ViewModeToggle.js.map +7 -0
- package/dist/modules/staff/lib/timesheets-projects-ui/useProjectsViewMode.js +50 -0
- package/dist/modules/staff/lib/timesheets-projects-ui/useProjectsViewMode.js.map +7 -0
- package/dist/modules/staff/lib/timesheets-ui/AddRowDropdown.js +163 -0
- package/dist/modules/staff/lib/timesheets-ui/AddRowDropdown.js.map +7 -0
- package/dist/modules/staff/lib/timesheets-ui/CalendarPicker.js +209 -0
- package/dist/modules/staff/lib/timesheets-ui/CalendarPicker.js.map +7 -0
- package/dist/modules/staff/lib/timesheets-ui/ColorPicker.js +52 -0
- package/dist/modules/staff/lib/timesheets-ui/ColorPicker.js.map +7 -0
- package/dist/modules/staff/lib/timesheets-ui/CreateProjectDialog.js +77 -0
- package/dist/modules/staff/lib/timesheets-ui/CreateProjectDialog.js.map +7 -0
- package/dist/modules/staff/lib/timesheets-ui/ListView.js +173 -0
- package/dist/modules/staff/lib/timesheets-ui/ListView.js.map +7 -0
- package/dist/modules/staff/lib/timesheets-ui/ProjectColorDot.js +32 -0
- package/dist/modules/staff/lib/timesheets-ui/ProjectColorDot.js.map +7 -0
- package/dist/modules/staff/lib/timesheets-ui/TimerBar.js +270 -0
- package/dist/modules/staff/lib/timesheets-ui/TimerBar.js.map +7 -0
- package/dist/modules/staff/lib/timesheets-ui/ViewSwitcher.js +57 -0
- package/dist/modules/staff/lib/timesheets-ui/ViewSwitcher.js.map +7 -0
- package/dist/modules/staff/lib/timesheets-ui/colors.js +43 -0
- package/dist/modules/staff/lib/timesheets-ui/colors.js.map +7 -0
- package/dist/modules/staff/migrations/Migration20260326135612.js +24 -0
- package/dist/modules/staff/migrations/Migration20260326135612.js.map +7 -0
- package/dist/modules/staff/migrations/Migration20260413102715.js +23 -0
- package/dist/modules/staff/migrations/Migration20260413102715.js.map +7 -0
- package/dist/modules/staff/migrations/Migration20260413111602.js +13 -0
- package/dist/modules/staff/migrations/Migration20260413111602.js.map +7 -0
- package/dist/modules/staff/migrations/Migration20260511112759.js +19 -0
- package/dist/modules/staff/migrations/Migration20260511112759.js.map +7 -0
- package/dist/modules/staff/search.js +35 -0
- package/dist/modules/staff/search.js.map +2 -2
- package/dist/modules/staff/setup.js +15 -1
- package/dist/modules/staff/setup.js.map +2 -2
- package/dist/modules/staff/widgets/dashboard/timesheets-hours-by-project/config.js +16 -0
- package/dist/modules/staff/widgets/dashboard/timesheets-hours-by-project/config.js.map +7 -0
- package/dist/modules/staff/widgets/dashboard/timesheets-hours-by-project/widget.client.js +126 -0
- package/dist/modules/staff/widgets/dashboard/timesheets-hours-by-project/widget.client.js.map +7 -0
- package/dist/modules/staff/widgets/dashboard/timesheets-hours-by-project/widget.js +26 -0
- package/dist/modules/staff/widgets/dashboard/timesheets-hours-by-project/widget.js.map +7 -0
- package/dist/modules/staff/widgets/dashboard/timesheets-time-reporting/config.js +15 -0
- package/dist/modules/staff/widgets/dashboard/timesheets-time-reporting/config.js.map +7 -0
- package/dist/modules/staff/widgets/dashboard/timesheets-time-reporting/widget.client.js +238 -0
- package/dist/modules/staff/widgets/dashboard/timesheets-time-reporting/widget.client.js.map +7 -0
- package/dist/modules/staff/widgets/dashboard/timesheets-time-reporting/widget.js +26 -0
- package/dist/modules/staff/widgets/dashboard/timesheets-time-reporting/widget.js.map +7 -0
- package/dist/modules/staff/widgets/injection/timer-sidebar-indicator/widget.js +145 -0
- package/dist/modules/staff/widgets/injection/timer-sidebar-indicator/widget.js.map +7 -0
- package/dist/modules/staff/widgets/injection-table.js +12 -0
- package/dist/modules/staff/widgets/injection-table.js.map +7 -0
- package/dist/modules/sync_excel/api/import/route.js +19 -17
- package/dist/modules/sync_excel/api/import/route.js.map +2 -2
- package/dist/modules/translations/commands/translations.js +22 -19
- package/dist/modules/translations/commands/translations.js.map +2 -2
- package/generated/entities/staff_time_entry/index.ts +17 -0
- package/generated/entities/staff_time_entry_segment/index.ts +10 -0
- package/generated/entities/staff_time_project/index.ts +16 -0
- package/generated/entities/staff_time_project_member/index.ts +13 -0
- package/generated/entities.ids.generated.ts +5 -1
- package/generated/entity-fields-registry.ts +64 -0
- package/package.json +7 -7
- package/src/helpers/integration/timesheetFixtures.ts +61 -0
- package/src/modules/attachments/api/library/[id]/route.ts +24 -17
- package/src/modules/attachments/api/route.ts +20 -14
- package/src/modules/auth/api/roles/acl/route.ts +11 -5
- package/src/modules/auth/api/sidebar/preferences/route.ts +33 -24
- package/src/modules/auth/api/users/acl/route.ts +17 -12
- package/src/modules/auth/commands/users.ts +96 -80
- package/src/modules/auth/services/sidebarPreferencesService.ts +40 -32
- package/src/modules/catalog/commands/categories.ts +61 -12
- package/src/modules/catalog/commands/products.ts +93 -60
- package/src/modules/catalog/commands/variants.ts +29 -16
- package/src/modules/currencies/commands/currencies.ts +27 -14
- package/src/modules/customer_accounts/api/admin/users.ts +31 -26
- package/src/modules/customer_accounts/api/password/reset-confirm.ts +5 -6
- package/src/modules/customer_accounts/api/portal/users/[id]/roles.ts +14 -13
- package/src/modules/customers/commands/addresses.ts +35 -23
- package/src/modules/customers/commands/companies.ts +166 -165
- package/src/modules/customers/commands/deals.ts +2 -4
- package/src/modules/customers/commands/interactions.ts +20 -26
- package/src/modules/customers/commands/people.ts +18 -15
- package/src/modules/customers/commands/personCompanyLinks.ts +109 -100
- package/src/modules/customers/commands/pipeline-stages.ts +31 -27
- package/src/modules/customers/commands/pipelines.ts +29 -23
- package/src/modules/customers/commands/tags.ts +13 -5
- package/src/modules/dashboards/api/users/widgets/route.ts +0 -1
- package/src/modules/dashboards/api/widgets/data/route.ts +36 -1
- package/src/modules/data_sync/lib/sync-engine.ts +4 -5
- package/src/modules/data_sync/lib/sync-run-service.ts +57 -28
- package/src/modules/directory/commands/organizations.ts +203 -166
- package/src/modules/inbox_ops/api/emails/[id]/reprocess/route.ts +26 -18
- package/src/modules/messages/commands/messages.ts +82 -80
- package/src/modules/messages/commands/shared.ts +138 -133
- package/src/modules/perspectives/api/[tableId]/route.ts +38 -27
- package/src/modules/resources/commands/resources.ts +127 -117
- package/src/modules/resources/commands/tags.ts +7 -3
- package/src/modules/sales/api/quotes/send/route.ts +17 -12
- package/src/modules/sales/commands/documents.ts +673 -481
- package/src/modules/sales/commands/payments.ts +158 -152
- package/src/modules/sales/commands/returns.ts +74 -63
- package/src/modules/staff/acl.ts +11 -0
- package/src/modules/staff/analytics.ts +30 -0
- package/src/modules/staff/api/guards.ts +59 -0
- package/src/modules/staff/api/interceptors.ts +122 -0
- package/src/modules/staff/api/timesheets/my-projects/[projectId]/route.ts +191 -0
- package/src/modules/staff/api/timesheets/my-projects/route.ts +115 -0
- package/src/modules/staff/api/timesheets/projects/kpis/route.ts +159 -0
- package/src/modules/staff/api/timesheets/time-entries/[id]/segments/[segmentId]/route.ts +187 -0
- package/src/modules/staff/api/timesheets/time-entries/[id]/segments/route.ts +191 -0
- package/src/modules/staff/api/timesheets/time-entries/[id]/timer-start/route.ts +168 -0
- package/src/modules/staff/api/timesheets/time-entries/[id]/timer-stop/route.ts +191 -0
- package/src/modules/staff/api/timesheets/time-entries/bulk/route.ts +292 -0
- package/src/modules/staff/api/timesheets/time-entries/route.ts +193 -0
- package/src/modules/staff/api/timesheets/time-projects/[id]/employees/route.ts +167 -0
- package/src/modules/staff/api/timesheets/time-projects/route.ts +244 -0
- package/src/modules/staff/backend/staff/timesheets/page.meta.ts +20 -0
- package/src/modules/staff/backend/staff/timesheets/page.tsx +899 -0
- package/src/modules/staff/backend/staff/timesheets/projects/[id]/edit/page.meta.ts +12 -0
- package/src/modules/staff/backend/staff/timesheets/projects/[id]/edit/page.tsx +141 -0
- package/src/modules/staff/backend/staff/timesheets/projects/[id]/page.meta.ts +12 -0
- package/src/modules/staff/backend/staff/timesheets/projects/[id]/page.tsx +579 -0
- package/src/modules/staff/backend/staff/timesheets/projects/create/page.meta.ts +12 -0
- package/src/modules/staff/backend/staff/timesheets/projects/create/page.tsx +90 -0
- package/src/modules/staff/backend/staff/timesheets/projects/page.meta.ts +23 -0
- package/src/modules/staff/backend/staff/timesheets/projects/page.tsx +765 -0
- package/src/modules/staff/backend/staff/timesheets/projects/projectFormConfig.ts +138 -0
- package/src/modules/staff/cli.ts +40 -1
- package/src/modules/staff/commands/index.ts +2 -0
- package/src/modules/staff/commands/leave-requests.ts +37 -29
- package/src/modules/staff/commands/team-members.ts +25 -20
- package/src/modules/staff/commands/timesheets-entries.ts +504 -0
- package/src/modules/staff/commands/timesheets-projects.ts +699 -0
- package/src/modules/staff/data/enrichers.ts +134 -0
- package/src/modules/staff/data/entities.ts +198 -0
- package/src/modules/staff/data/validators.ts +129 -0
- package/src/modules/staff/events.ts +13 -0
- package/src/modules/staff/i18n/de.json +209 -1
- package/src/modules/staff/i18n/en.json +209 -1
- package/src/modules/staff/i18n/es.json +209 -1
- package/src/modules/staff/i18n/pl.json +209 -1
- package/src/modules/staff/lib/crud.ts +8 -0
- package/src/modules/staff/lib/staffMemberResolver.ts +22 -0
- package/src/modules/staff/lib/timesheets-projects/computeProjectHoursTrend.ts +89 -0
- package/src/modules/staff/lib/timesheets-projects/computeProjectsKpis.ts +311 -0
- package/src/modules/staff/lib/timesheets-projects/dateBuckets.ts +37 -0
- package/src/modules/staff/lib/timesheets-projects/initials.ts +6 -0
- package/src/modules/staff/lib/timesheets-projects/kpiMath.ts +8 -0
- package/src/modules/staff/lib/timesheets-projects/listProjectMembersPreview.ts +83 -0
- package/src/modules/staff/lib/timesheets-projects-ui/HoursSparkline.tsx +75 -0
- package/src/modules/staff/lib/timesheets-projects-ui/ProjectCard.tsx +110 -0
- package/src/modules/staff/lib/timesheets-projects-ui/ProjectMembersAvatarStack.tsx +73 -0
- package/src/modules/staff/lib/timesheets-projects-ui/ProjectsKpiStrip.tsx +185 -0
- package/src/modules/staff/lib/timesheets-projects-ui/SavedViewTabs.tsx +53 -0
- package/src/modules/staff/lib/timesheets-projects-ui/ViewModeToggle.tsx +63 -0
- package/src/modules/staff/lib/timesheets-projects-ui/useProjectsViewMode.ts +63 -0
- package/src/modules/staff/lib/timesheets-ui/AddRowDropdown.tsx +188 -0
- package/src/modules/staff/lib/timesheets-ui/CalendarPicker.tsx +229 -0
- package/src/modules/staff/lib/timesheets-ui/ColorPicker.tsx +65 -0
- package/src/modules/staff/lib/timesheets-ui/CreateProjectDialog.tsx +99 -0
- package/src/modules/staff/lib/timesheets-ui/ListView.tsx +230 -0
- package/src/modules/staff/lib/timesheets-ui/ProjectColorDot.tsx +40 -0
- package/src/modules/staff/lib/timesheets-ui/TimerBar.tsx +327 -0
- package/src/modules/staff/lib/timesheets-ui/ViewSwitcher.tsx +60 -0
- package/src/modules/staff/lib/timesheets-ui/colors.ts +58 -0
- package/src/modules/staff/migrations/.snapshot-open-mercato.json +1148 -0
- package/src/modules/staff/migrations/Migration20260326135612.ts +26 -0
- package/src/modules/staff/migrations/Migration20260413102715.ts +25 -0
- package/src/modules/staff/migrations/Migration20260413111602.ts +13 -0
- package/src/modules/staff/migrations/Migration20260511112759.ts +21 -0
- package/src/modules/staff/search.ts +35 -0
- package/src/modules/staff/setup.ts +15 -0
- package/src/modules/staff/widgets/dashboard/timesheets-hours-by-project/config.ts +17 -0
- package/src/modules/staff/widgets/dashboard/timesheets-hours-by-project/widget.client.tsx +158 -0
- package/src/modules/staff/widgets/dashboard/timesheets-hours-by-project/widget.ts +25 -0
- package/src/modules/staff/widgets/dashboard/timesheets-time-reporting/config.ts +15 -0
- package/src/modules/staff/widgets/dashboard/timesheets-time-reporting/widget.client.tsx +297 -0
- package/src/modules/staff/widgets/dashboard/timesheets-time-reporting/widget.ts +25 -0
- package/src/modules/staff/widgets/injection/timer-sidebar-indicator/widget.tsx +161 -0
- package/src/modules/staff/widgets/injection-table.ts +10 -0
- package/src/modules/sync_excel/api/import/route.ts +23 -18
- package/src/modules/translations/commands/translations.ts +49 -41
|
@@ -0,0 +1,209 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
3
|
+
import * as React from "react";
|
|
4
|
+
import { Button } from "@open-mercato/ui/primitives/button";
|
|
5
|
+
import { IconButton } from "@open-mercato/ui/primitives/icon-button";
|
|
6
|
+
import { useT } from "@open-mercato/shared/lib/i18n/context";
|
|
7
|
+
import { CalendarDays, ChevronLeft, ChevronRight } from "lucide-react";
|
|
8
|
+
function getMonday(date) {
|
|
9
|
+
const result = new Date(date);
|
|
10
|
+
const day = result.getDay();
|
|
11
|
+
const diff = day === 0 ? -6 : 1 - day;
|
|
12
|
+
result.setDate(result.getDate() + diff);
|
|
13
|
+
result.setHours(0, 0, 0, 0);
|
|
14
|
+
return result;
|
|
15
|
+
}
|
|
16
|
+
function getWeekNumber(date) {
|
|
17
|
+
const target = new Date(Date.UTC(date.getFullYear(), date.getMonth(), date.getDate()));
|
|
18
|
+
target.setUTCDate(target.getUTCDate() + 4 - (target.getUTCDay() || 7));
|
|
19
|
+
const yearStart = new Date(Date.UTC(target.getUTCFullYear(), 0, 1));
|
|
20
|
+
return Math.ceil(((target.getTime() - yearStart.getTime()) / 864e5 + 1) / 7);
|
|
21
|
+
}
|
|
22
|
+
function isSameDay(first, second) {
|
|
23
|
+
return first.getFullYear() === second.getFullYear() && first.getMonth() === second.getMonth() && first.getDate() === second.getDate();
|
|
24
|
+
}
|
|
25
|
+
function isSameWeek(first, second) {
|
|
26
|
+
return isSameDay(getMonday(first), getMonday(second));
|
|
27
|
+
}
|
|
28
|
+
function buildWeeks(year, month) {
|
|
29
|
+
const firstDay = new Date(year, month, 1);
|
|
30
|
+
const start = getMonday(firstDay);
|
|
31
|
+
const weeks = [];
|
|
32
|
+
const current = new Date(start);
|
|
33
|
+
for (let weekIdx = 0; weekIdx < 6; weekIdx++) {
|
|
34
|
+
const week = [];
|
|
35
|
+
for (let dayIdx = 0; dayIdx < 7; dayIdx++) {
|
|
36
|
+
week.push(new Date(current));
|
|
37
|
+
current.setDate(current.getDate() + 1);
|
|
38
|
+
}
|
|
39
|
+
weeks.push(week);
|
|
40
|
+
if (current.getMonth() !== month && weekIdx >= 3) break;
|
|
41
|
+
}
|
|
42
|
+
return weeks;
|
|
43
|
+
}
|
|
44
|
+
function getLocalizedDayHeaders() {
|
|
45
|
+
const baseMonday = new Date(2024, 0, 1);
|
|
46
|
+
return Array.from({ length: 7 }, (_, idx) => {
|
|
47
|
+
const date = new Date(baseMonday);
|
|
48
|
+
date.setDate(date.getDate() + idx);
|
|
49
|
+
return date.toLocaleDateString(void 0, { weekday: "narrow" });
|
|
50
|
+
});
|
|
51
|
+
}
|
|
52
|
+
function CalendarPicker({ selectedWeekStart, onWeekSelect }) {
|
|
53
|
+
const t = useT();
|
|
54
|
+
const [open, setOpen] = React.useState(false);
|
|
55
|
+
const [viewYear, setViewYear] = React.useState(selectedWeekStart.getFullYear());
|
|
56
|
+
const [viewMonth, setViewMonth] = React.useState(selectedWeekStart.getMonth());
|
|
57
|
+
const containerRef = React.useRef(null);
|
|
58
|
+
const dayHeaders = React.useMemo(() => getLocalizedDayHeaders(), []);
|
|
59
|
+
React.useEffect(() => {
|
|
60
|
+
function handleClickOutside(event) {
|
|
61
|
+
if (containerRef.current && !containerRef.current.contains(event.target)) {
|
|
62
|
+
setOpen(false);
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
if (open) document.addEventListener("mousedown", handleClickOutside);
|
|
66
|
+
return () => document.removeEventListener("mousedown", handleClickOutside);
|
|
67
|
+
}, [open]);
|
|
68
|
+
React.useEffect(() => {
|
|
69
|
+
setViewYear(selectedWeekStart.getFullYear());
|
|
70
|
+
setViewMonth(selectedWeekStart.getMonth());
|
|
71
|
+
}, [selectedWeekStart]);
|
|
72
|
+
const weeks = React.useMemo(() => buildWeeks(viewYear, viewMonth), [viewYear, viewMonth]);
|
|
73
|
+
const monthLabel = React.useMemo(() => {
|
|
74
|
+
return new Date(viewYear, viewMonth, 1).toLocaleString(void 0, { month: "long", year: "numeric" });
|
|
75
|
+
}, [viewYear, viewMonth]);
|
|
76
|
+
const today = React.useMemo(() => /* @__PURE__ */ new Date(), []);
|
|
77
|
+
const handleWeekClick = React.useCallback((monday) => {
|
|
78
|
+
onWeekSelect(monday);
|
|
79
|
+
setOpen(false);
|
|
80
|
+
}, [onWeekSelect]);
|
|
81
|
+
const goToPrevMonth = React.useCallback(() => {
|
|
82
|
+
setViewMonth((prev) => {
|
|
83
|
+
if (prev === 0) {
|
|
84
|
+
setViewYear((yr) => yr - 1);
|
|
85
|
+
return 11;
|
|
86
|
+
}
|
|
87
|
+
return prev - 1;
|
|
88
|
+
});
|
|
89
|
+
}, []);
|
|
90
|
+
const goToNextMonth = React.useCallback(() => {
|
|
91
|
+
setViewMonth((prev) => {
|
|
92
|
+
if (prev === 11) {
|
|
93
|
+
setViewYear((yr) => yr + 1);
|
|
94
|
+
return 0;
|
|
95
|
+
}
|
|
96
|
+
return prev + 1;
|
|
97
|
+
});
|
|
98
|
+
}, []);
|
|
99
|
+
return /* @__PURE__ */ jsxs("div", { ref: containerRef, className: "relative inline-block", children: [
|
|
100
|
+
/* @__PURE__ */ jsx(
|
|
101
|
+
IconButton,
|
|
102
|
+
{
|
|
103
|
+
variant: "outline",
|
|
104
|
+
size: "sm",
|
|
105
|
+
type: "button",
|
|
106
|
+
onClick: () => setOpen((prev) => !prev),
|
|
107
|
+
"aria-label": t("staff.timesheets.my.calendar.open", "Open calendar"),
|
|
108
|
+
children: /* @__PURE__ */ jsx(CalendarDays, { className: "size-4" })
|
|
109
|
+
}
|
|
110
|
+
),
|
|
111
|
+
open && /* @__PURE__ */ jsxs("div", { className: "absolute left-0 z-20 mt-1 w-[280px] rounded-lg border bg-popover p-3 shadow-lg", children: [
|
|
112
|
+
/* @__PURE__ */ jsxs("div", { className: "mb-3 flex gap-2 border-b pb-2", children: [
|
|
113
|
+
/* @__PURE__ */ jsx(
|
|
114
|
+
Button,
|
|
115
|
+
{
|
|
116
|
+
type: "button",
|
|
117
|
+
variant: "ghost",
|
|
118
|
+
size: "sm",
|
|
119
|
+
className: "h-auto px-2 py-1 text-xs",
|
|
120
|
+
onClick: () => handleWeekClick(getMonday(/* @__PURE__ */ new Date())),
|
|
121
|
+
children: t("staff.timesheets.my.calendar.thisWeek", "This week")
|
|
122
|
+
}
|
|
123
|
+
),
|
|
124
|
+
/* @__PURE__ */ jsx(
|
|
125
|
+
Button,
|
|
126
|
+
{
|
|
127
|
+
type: "button",
|
|
128
|
+
variant: "ghost",
|
|
129
|
+
size: "sm",
|
|
130
|
+
className: "h-auto px-2 py-1 text-xs",
|
|
131
|
+
onClick: () => {
|
|
132
|
+
const lastWeek = /* @__PURE__ */ new Date();
|
|
133
|
+
lastWeek.setDate(lastWeek.getDate() - 7);
|
|
134
|
+
handleWeekClick(getMonday(lastWeek));
|
|
135
|
+
},
|
|
136
|
+
children: t("staff.timesheets.my.calendar.lastWeek", "Last week")
|
|
137
|
+
}
|
|
138
|
+
)
|
|
139
|
+
] }),
|
|
140
|
+
/* @__PURE__ */ jsxs("div", { className: "mb-2 flex items-center justify-between", children: [
|
|
141
|
+
/* @__PURE__ */ jsx(
|
|
142
|
+
IconButton,
|
|
143
|
+
{
|
|
144
|
+
variant: "ghost",
|
|
145
|
+
size: "xs",
|
|
146
|
+
type: "button",
|
|
147
|
+
onClick: goToPrevMonth,
|
|
148
|
+
"aria-label": t("staff.timesheets.my.calendar.prevMonth", "Previous month"),
|
|
149
|
+
children: /* @__PURE__ */ jsx(ChevronLeft, { className: "size-3.5" })
|
|
150
|
+
}
|
|
151
|
+
),
|
|
152
|
+
/* @__PURE__ */ jsx("span", { className: "text-sm font-medium", children: monthLabel }),
|
|
153
|
+
/* @__PURE__ */ jsx(
|
|
154
|
+
IconButton,
|
|
155
|
+
{
|
|
156
|
+
variant: "ghost",
|
|
157
|
+
size: "xs",
|
|
158
|
+
type: "button",
|
|
159
|
+
onClick: goToNextMonth,
|
|
160
|
+
"aria-label": t("staff.timesheets.my.calendar.nextMonth", "Next month"),
|
|
161
|
+
children: /* @__PURE__ */ jsx(ChevronRight, { className: "size-3.5" })
|
|
162
|
+
}
|
|
163
|
+
)
|
|
164
|
+
] }),
|
|
165
|
+
/* @__PURE__ */ jsxs("div", { className: "grid grid-cols-[32px_repeat(7,1fr)] gap-0 mb-1", children: [
|
|
166
|
+
/* @__PURE__ */ jsx("div", {}),
|
|
167
|
+
dayHeaders.map((day, idx) => /* @__PURE__ */ jsx("div", { className: "text-center text-[11px] font-medium text-muted-foreground py-1", children: day }, idx))
|
|
168
|
+
] }),
|
|
169
|
+
weeks.map((week) => {
|
|
170
|
+
const monday = week[0];
|
|
171
|
+
const weekNum = getWeekNumber(monday);
|
|
172
|
+
const isSelected = isSameWeek(monday, selectedWeekStart);
|
|
173
|
+
return /* @__PURE__ */ jsxs(
|
|
174
|
+
Button,
|
|
175
|
+
{
|
|
176
|
+
type: "button",
|
|
177
|
+
variant: "ghost",
|
|
178
|
+
className: `grid grid-cols-[32px_repeat(7,1fr)] gap-0 w-full h-auto rounded-md px-0 py-0 hover:bg-muted
|
|
179
|
+
${isSelected ? "bg-primary text-primary-foreground hover:bg-primary/90" : ""}`,
|
|
180
|
+
onClick: () => handleWeekClick(monday),
|
|
181
|
+
children: [
|
|
182
|
+
/* @__PURE__ */ jsx("span", { className: `text-[10px] font-medium py-1.5 text-center ${isSelected ? "text-primary-foreground/70" : "text-muted-foreground"}`, children: weekNum }),
|
|
183
|
+
week.map((date) => {
|
|
184
|
+
const inMonth = date.getMonth() === viewMonth;
|
|
185
|
+
const isTodayDate = isSameDay(date, today);
|
|
186
|
+
return /* @__PURE__ */ jsx(
|
|
187
|
+
"span",
|
|
188
|
+
{
|
|
189
|
+
className: `text-xs py-1.5 text-center
|
|
190
|
+
${!inMonth && !isSelected ? "text-muted-foreground/40" : ""}
|
|
191
|
+
${isTodayDate && !isSelected ? "font-bold text-primary" : ""}
|
|
192
|
+
${isTodayDate && isSelected ? "font-bold underline" : ""}`,
|
|
193
|
+
children: date.getDate()
|
|
194
|
+
},
|
|
195
|
+
date.toISOString()
|
|
196
|
+
);
|
|
197
|
+
})
|
|
198
|
+
]
|
|
199
|
+
},
|
|
200
|
+
monday.toISOString()
|
|
201
|
+
);
|
|
202
|
+
})
|
|
203
|
+
] })
|
|
204
|
+
] });
|
|
205
|
+
}
|
|
206
|
+
export {
|
|
207
|
+
CalendarPicker
|
|
208
|
+
};
|
|
209
|
+
//# sourceMappingURL=CalendarPicker.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../../../../src/modules/staff/lib/timesheets-ui/CalendarPicker.tsx"],
|
|
4
|
+
"sourcesContent": ["\"use client\"\n\nimport * as React from 'react'\nimport { Button } from '@open-mercato/ui/primitives/button'\nimport { IconButton } from '@open-mercato/ui/primitives/icon-button'\nimport { useT } from '@open-mercato/shared/lib/i18n/context'\nimport { CalendarDays, ChevronLeft, ChevronRight } from 'lucide-react'\n\ntype CalendarPickerProps = {\n selectedWeekStart: Date\n onWeekSelect: (weekStart: Date) => void\n}\n\nfunction getMonday(date: Date): Date {\n const result = new Date(date)\n const day = result.getDay()\n const diff = day === 0 ? -6 : 1 - day\n result.setDate(result.getDate() + diff)\n result.setHours(0, 0, 0, 0)\n return result\n}\n\nfunction getWeekNumber(date: Date): number {\n const target = new Date(Date.UTC(date.getFullYear(), date.getMonth(), date.getDate()))\n target.setUTCDate(target.getUTCDate() + 4 - (target.getUTCDay() || 7))\n const yearStart = new Date(Date.UTC(target.getUTCFullYear(), 0, 1))\n return Math.ceil(((target.getTime() - yearStart.getTime()) / 86400000 + 1) / 7)\n}\n\nfunction isSameDay(first: Date, second: Date): boolean {\n return first.getFullYear() === second.getFullYear() && first.getMonth() === second.getMonth() && first.getDate() === second.getDate()\n}\n\nfunction isSameWeek(first: Date, second: Date): boolean {\n return isSameDay(getMonday(first), getMonday(second))\n}\n\nfunction buildWeeks(year: number, month: number): Date[][] {\n const firstDay = new Date(year, month, 1)\n const start = getMonday(firstDay)\n const weeks: Date[][] = []\n const current = new Date(start)\n\n for (let weekIdx = 0; weekIdx < 6; weekIdx++) {\n const week: Date[] = []\n for (let dayIdx = 0; dayIdx < 7; dayIdx++) {\n week.push(new Date(current))\n current.setDate(current.getDate() + 1)\n }\n weeks.push(week)\n if (current.getMonth() !== month && weekIdx >= 3) break\n }\n return weeks\n}\n\nfunction getLocalizedDayHeaders(): string[] {\n const baseMonday = new Date(2024, 0, 1) // Known Monday\n return Array.from({ length: 7 }, (_, idx) => {\n const date = new Date(baseMonday)\n date.setDate(date.getDate() + idx)\n return date.toLocaleDateString(undefined, { weekday: 'narrow' })\n })\n}\n\nexport function CalendarPicker({ selectedWeekStart, onWeekSelect }: CalendarPickerProps) {\n const t = useT()\n const [open, setOpen] = React.useState(false)\n const [viewYear, setViewYear] = React.useState(selectedWeekStart.getFullYear())\n const [viewMonth, setViewMonth] = React.useState(selectedWeekStart.getMonth())\n const containerRef = React.useRef<HTMLDivElement>(null)\n\n const dayHeaders = React.useMemo(() => getLocalizedDayHeaders(), [])\n\n React.useEffect(() => {\n function handleClickOutside(event: MouseEvent) {\n if (containerRef.current && !containerRef.current.contains(event.target as Node)) {\n setOpen(false)\n }\n }\n if (open) document.addEventListener('mousedown', handleClickOutside)\n return () => document.removeEventListener('mousedown', handleClickOutside)\n }, [open])\n\n React.useEffect(() => {\n setViewYear(selectedWeekStart.getFullYear())\n setViewMonth(selectedWeekStart.getMonth())\n }, [selectedWeekStart])\n\n const weeks = React.useMemo(() => buildWeeks(viewYear, viewMonth), [viewYear, viewMonth])\n\n const monthLabel = React.useMemo(() => {\n return new Date(viewYear, viewMonth, 1).toLocaleString(undefined, { month: 'long', year: 'numeric' })\n }, [viewYear, viewMonth])\n\n const today = React.useMemo(() => new Date(), [])\n\n const handleWeekClick = React.useCallback((monday: Date) => {\n onWeekSelect(monday)\n setOpen(false)\n }, [onWeekSelect])\n\n const goToPrevMonth = React.useCallback(() => {\n setViewMonth((prev) => {\n if (prev === 0) { setViewYear((yr) => yr - 1); return 11 }\n return prev - 1\n })\n }, [])\n\n const goToNextMonth = React.useCallback(() => {\n setViewMonth((prev) => {\n if (prev === 11) { setViewYear((yr) => yr + 1); return 0 }\n return prev + 1\n })\n }, [])\n\n return (\n <div ref={containerRef} className=\"relative inline-block\">\n <IconButton\n variant=\"outline\"\n size=\"sm\"\n type=\"button\"\n onClick={() => setOpen((prev) => !prev)}\n aria-label={t('staff.timesheets.my.calendar.open', 'Open calendar')}\n >\n <CalendarDays className=\"size-4\" />\n </IconButton>\n\n {open && (\n <div className=\"absolute left-0 z-20 mt-1 w-[280px] rounded-lg border bg-popover p-3 shadow-lg\">\n {/* Quick links */}\n <div className=\"mb-3 flex gap-2 border-b pb-2\">\n <Button\n type=\"button\"\n variant=\"ghost\"\n size=\"sm\"\n className=\"h-auto px-2 py-1 text-xs\"\n onClick={() => handleWeekClick(getMonday(new Date()))}\n >\n {t('staff.timesheets.my.calendar.thisWeek', 'This week')}\n </Button>\n <Button\n type=\"button\"\n variant=\"ghost\"\n size=\"sm\"\n className=\"h-auto px-2 py-1 text-xs\"\n onClick={() => {\n const lastWeek = new Date()\n lastWeek.setDate(lastWeek.getDate() - 7)\n handleWeekClick(getMonday(lastWeek))\n }}\n >\n {t('staff.timesheets.my.calendar.lastWeek', 'Last week')}\n </Button>\n </div>\n\n {/* Month navigation */}\n <div className=\"mb-2 flex items-center justify-between\">\n <IconButton\n variant=\"ghost\"\n size=\"xs\"\n type=\"button\"\n onClick={goToPrevMonth}\n aria-label={t('staff.timesheets.my.calendar.prevMonth', 'Previous month')}\n >\n <ChevronLeft className=\"size-3.5\" />\n </IconButton>\n <span className=\"text-sm font-medium\">{monthLabel}</span>\n <IconButton\n variant=\"ghost\"\n size=\"xs\"\n type=\"button\"\n onClick={goToNextMonth}\n aria-label={t('staff.timesheets.my.calendar.nextMonth', 'Next month')}\n >\n <ChevronRight className=\"size-3.5\" />\n </IconButton>\n </div>\n\n {/* Day headers */}\n <div className=\"grid grid-cols-[32px_repeat(7,1fr)] gap-0 mb-1\">\n <div />\n {dayHeaders.map((day, idx) => (\n <div key={idx} className=\"text-center text-[11px] font-medium text-muted-foreground py-1\">\n {day}\n </div>\n ))}\n </div>\n\n {/* Week rows */}\n {weeks.map((week) => {\n const monday = week[0]\n const weekNum = getWeekNumber(monday)\n const isSelected = isSameWeek(monday, selectedWeekStart)\n\n return (\n <Button\n key={monday.toISOString()}\n type=\"button\"\n variant=\"ghost\"\n className={`grid grid-cols-[32px_repeat(7,1fr)] gap-0 w-full h-auto rounded-md px-0 py-0 hover:bg-muted\n ${isSelected ? 'bg-primary text-primary-foreground hover:bg-primary/90' : ''}`}\n onClick={() => handleWeekClick(monday)}\n >\n <span className={`text-[10px] font-medium py-1.5 text-center ${isSelected ? 'text-primary-foreground/70' : 'text-muted-foreground'}`}>\n {weekNum}\n </span>\n {week.map((date) => {\n const inMonth = date.getMonth() === viewMonth\n const isTodayDate = isSameDay(date, today)\n return (\n <span\n key={date.toISOString()}\n className={`text-xs py-1.5 text-center\n ${!inMonth && !isSelected ? 'text-muted-foreground/40' : ''}\n ${isTodayDate && !isSelected ? 'font-bold text-primary' : ''}\n ${isTodayDate && isSelected ? 'font-bold underline' : ''}`}\n >\n {date.getDate()}\n </span>\n )\n })}\n </Button>\n )\n })}\n </div>\n )}\n </div>\n )\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
3
|
+
import { Check } from "lucide-react";
|
|
4
|
+
import { PROJECT_COLORS } from "./colors.js";
|
|
5
|
+
function ColorPicker({
|
|
6
|
+
value,
|
|
7
|
+
onChange,
|
|
8
|
+
allowReset = true,
|
|
9
|
+
resetLabel = "Auto",
|
|
10
|
+
id,
|
|
11
|
+
disabled = false
|
|
12
|
+
}) {
|
|
13
|
+
return /* @__PURE__ */ jsxs("div", { id, className: "flex flex-wrap items-center gap-2", role: "radiogroup", children: [
|
|
14
|
+
PROJECT_COLORS.map((color) => {
|
|
15
|
+
const selected = value === color.key;
|
|
16
|
+
return /* @__PURE__ */ jsx(
|
|
17
|
+
"button",
|
|
18
|
+
{
|
|
19
|
+
type: "button",
|
|
20
|
+
role: "radio",
|
|
21
|
+
"aria-checked": selected,
|
|
22
|
+
"aria-label": color.key,
|
|
23
|
+
title: color.key,
|
|
24
|
+
disabled,
|
|
25
|
+
onClick: () => onChange(color.key),
|
|
26
|
+
className: `inline-flex h-6 w-6 shrink-0 cursor-pointer items-center justify-center rounded-full transition-transform hover:scale-110 focus:outline-none focus:ring-2 focus:ring-offset-1 focus:ring-ring disabled:cursor-not-allowed disabled:opacity-50 ${selected ? "ring-2 ring-offset-1 ring-ring" : ""}`,
|
|
27
|
+
style: { backgroundColor: color.hex },
|
|
28
|
+
children: selected ? /* @__PURE__ */ jsx(Check, { className: "h-3.5 w-3.5 text-white", strokeWidth: 3 }) : null
|
|
29
|
+
},
|
|
30
|
+
color.key
|
|
31
|
+
);
|
|
32
|
+
}),
|
|
33
|
+
allowReset ? /* @__PURE__ */ jsx(
|
|
34
|
+
"button",
|
|
35
|
+
{
|
|
36
|
+
type: "button",
|
|
37
|
+
role: "radio",
|
|
38
|
+
"aria-checked": value == null,
|
|
39
|
+
"aria-label": resetLabel,
|
|
40
|
+
title: resetLabel,
|
|
41
|
+
disabled,
|
|
42
|
+
onClick: () => onChange(null),
|
|
43
|
+
className: `inline-flex h-6 items-center cursor-pointer rounded-full border border-dashed border-muted-foreground/50 bg-transparent px-2 text-[11px] text-muted-foreground transition-colors hover:bg-muted focus:outline-none focus:ring-2 focus:ring-offset-1 focus:ring-ring disabled:cursor-not-allowed disabled:opacity-50 ${value == null ? "ring-2 ring-offset-1 ring-ring" : ""}`,
|
|
44
|
+
children: resetLabel
|
|
45
|
+
}
|
|
46
|
+
) : null
|
|
47
|
+
] });
|
|
48
|
+
}
|
|
49
|
+
export {
|
|
50
|
+
ColorPicker
|
|
51
|
+
};
|
|
52
|
+
//# sourceMappingURL=ColorPicker.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../../../../src/modules/staff/lib/timesheets-ui/ColorPicker.tsx"],
|
|
4
|
+
"sourcesContent": ["\"use client\"\n\nimport * as React from 'react'\nimport { Check } from 'lucide-react'\nimport { PROJECT_COLORS } from './colors'\n\nexport type ColorPickerProps = {\n value: string | null | undefined\n onChange: (value: string | null) => void\n allowReset?: boolean\n resetLabel?: string\n id?: string\n disabled?: boolean\n}\n\nexport function ColorPicker({\n value,\n onChange,\n allowReset = true,\n resetLabel = 'Auto',\n id,\n disabled = false,\n}: ColorPickerProps) {\n return (\n <div id={id} className=\"flex flex-wrap items-center gap-2\" role=\"radiogroup\">\n {PROJECT_COLORS.map((color) => {\n const selected = value === color.key\n return (\n <button\n key={color.key}\n type=\"button\"\n role=\"radio\"\n aria-checked={selected}\n aria-label={color.key}\n title={color.key}\n disabled={disabled}\n onClick={() => onChange(color.key)}\n className={`inline-flex h-6 w-6 shrink-0 cursor-pointer items-center justify-center rounded-full transition-transform hover:scale-110 focus:outline-none focus:ring-2 focus:ring-offset-1 focus:ring-ring disabled:cursor-not-allowed disabled:opacity-50 ${\n selected ? 'ring-2 ring-offset-1 ring-ring' : ''\n }`}\n style={{ backgroundColor: color.hex }}\n >\n {selected ? <Check className=\"h-3.5 w-3.5 text-white\" strokeWidth={3} /> : null}\n </button>\n )\n })}\n {allowReset ? (\n <button\n type=\"button\"\n role=\"radio\"\n aria-checked={value == null}\n aria-label={resetLabel}\n title={resetLabel}\n disabled={disabled}\n onClick={() => onChange(null)}\n className={`inline-flex h-6 items-center cursor-pointer rounded-full border border-dashed border-muted-foreground/50 bg-transparent px-2 text-[11px] text-muted-foreground transition-colors hover:bg-muted focus:outline-none focus:ring-2 focus:ring-offset-1 focus:ring-ring disabled:cursor-not-allowed disabled:opacity-50 ${\n value == null ? 'ring-2 ring-offset-1 ring-ring' : ''\n }`}\n >\n {resetLabel}\n </button>\n ) : null}\n </div>\n )\n}\n"],
|
|
5
|
+
"mappings": ";AAwBI,SAkBoB,KAlBpB;AArBJ,SAAS,aAAa;AACtB,SAAS,sBAAsB;AAWxB,SAAS,YAAY;AAAA,EAC1B;AAAA,EACA;AAAA,EACA,aAAa;AAAA,EACb,aAAa;AAAA,EACb;AAAA,EACA,WAAW;AACb,GAAqB;AACnB,SACE,qBAAC,SAAI,IAAQ,WAAU,qCAAoC,MAAK,cAC7D;AAAA,mBAAe,IAAI,CAAC,UAAU;AAC7B,YAAM,WAAW,UAAU,MAAM;AACjC,aACE;AAAA,QAAC;AAAA;AAAA,UAEC,MAAK;AAAA,UACL,MAAK;AAAA,UACL,gBAAc;AAAA,UACd,cAAY,MAAM;AAAA,UAClB,OAAO,MAAM;AAAA,UACb;AAAA,UACA,SAAS,MAAM,SAAS,MAAM,GAAG;AAAA,UACjC,WAAW,iPACT,WAAW,mCAAmC,EAChD;AAAA,UACA,OAAO,EAAE,iBAAiB,MAAM,IAAI;AAAA,UAEnC,qBAAW,oBAAC,SAAM,WAAU,0BAAyB,aAAa,GAAG,IAAK;AAAA;AAAA,QAbtE,MAAM;AAAA,MAcb;AAAA,IAEJ,CAAC;AAAA,IACA,aACC;AAAA,MAAC;AAAA;AAAA,QACC,MAAK;AAAA,QACL,MAAK;AAAA,QACL,gBAAc,SAAS;AAAA,QACvB,cAAY;AAAA,QACZ,OAAO;AAAA,QACP;AAAA,QACA,SAAS,MAAM,SAAS,IAAI;AAAA,QAC5B,WAAW,uTACT,SAAS,OAAO,mCAAmC,EACrD;AAAA,QAEC;AAAA;AAAA,IACH,IACE;AAAA,KACN;AAEJ;",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
3
|
+
import * as React from "react";
|
|
4
|
+
import { Dialog, DialogContent, DialogHeader, DialogTitle } from "@open-mercato/ui/primitives/dialog";
|
|
5
|
+
import { Button } from "@open-mercato/ui/primitives/button";
|
|
6
|
+
import { CrudForm } from "@open-mercato/ui/backend/CrudForm";
|
|
7
|
+
import { createCrud } from "@open-mercato/ui/backend/utils/crud";
|
|
8
|
+
import { apiCall } from "@open-mercato/ui/backend/utils/apiCall";
|
|
9
|
+
import { createCrudFormError } from "@open-mercato/ui/backend/utils/serverErrors";
|
|
10
|
+
import { flash } from "@open-mercato/ui/backend/FlashMessages";
|
|
11
|
+
import { useT } from "@open-mercato/shared/lib/i18n/context";
|
|
12
|
+
import { E } from "../../../../generated/entities.ids.generated.js";
|
|
13
|
+
import {
|
|
14
|
+
buildProjectPayload,
|
|
15
|
+
createProjectFormFields,
|
|
16
|
+
createProjectFormGroups,
|
|
17
|
+
createProjectFormSchema
|
|
18
|
+
} from "../../backend/staff/timesheets/projects/projectFormConfig.js";
|
|
19
|
+
function CreateProjectDialog({ open, onOpenChange, onProjectCreated }) {
|
|
20
|
+
const t = useT();
|
|
21
|
+
const formSchema = React.useMemo(() => createProjectFormSchema(), []);
|
|
22
|
+
const fields = React.useMemo(() => createProjectFormFields(t), [t]);
|
|
23
|
+
const groups = React.useMemo(() => createProjectFormGroups(t), [t]);
|
|
24
|
+
return /* @__PURE__ */ jsx(Dialog, { open, onOpenChange, children: /* @__PURE__ */ jsxs(DialogContent, { className: "sm:max-w-lg", children: [
|
|
25
|
+
/* @__PURE__ */ jsx(DialogHeader, { children: /* @__PURE__ */ jsx(DialogTitle, { children: t("staff.timesheets.projects.form.createTitle", "Create project") }) }),
|
|
26
|
+
/* @__PURE__ */ jsx(
|
|
27
|
+
CrudForm,
|
|
28
|
+
{
|
|
29
|
+
embedded: true,
|
|
30
|
+
fields,
|
|
31
|
+
groups,
|
|
32
|
+
schema: formSchema,
|
|
33
|
+
initialValues: {},
|
|
34
|
+
entityIds: [E.staff.staff_time_project],
|
|
35
|
+
submitLabel: t("staff.timesheets.projects.form.actions.create", "Create"),
|
|
36
|
+
extraActions: /* @__PURE__ */ jsx(Button, { type: "button", variant: "ghost", onClick: () => onOpenChange(false), children: t("staff.timesheets.projects.form.actions.cancel", "Cancel") }),
|
|
37
|
+
onSubmit: async (values) => {
|
|
38
|
+
const payload = buildProjectPayload(values);
|
|
39
|
+
const { result: created } = await createCrud(
|
|
40
|
+
"staff/timesheets/time-projects",
|
|
41
|
+
payload,
|
|
42
|
+
{ errorMessage: t("staff.timesheets.projects.errors.save", "Failed to save project.") }
|
|
43
|
+
);
|
|
44
|
+
const newId = created?.id;
|
|
45
|
+
if (!newId) {
|
|
46
|
+
throw createCrudFormError(
|
|
47
|
+
t("staff.timesheets.projects.errors.save", "Failed to save project.")
|
|
48
|
+
);
|
|
49
|
+
}
|
|
50
|
+
try {
|
|
51
|
+
const selfRes = await apiCall("/api/staff/team-members/self");
|
|
52
|
+
const staffMemberId = selfRes.result?.member?.id;
|
|
53
|
+
if (staffMemberId) {
|
|
54
|
+
await createCrud("staff/timesheets/time-projects/" + newId + "/employees", {
|
|
55
|
+
staffMemberId,
|
|
56
|
+
assignedStartDate: (/* @__PURE__ */ new Date()).toISOString().slice(0, 10),
|
|
57
|
+
status: "active"
|
|
58
|
+
}, { errorMessage: "" });
|
|
59
|
+
}
|
|
60
|
+
} catch {
|
|
61
|
+
}
|
|
62
|
+
onProjectCreated({
|
|
63
|
+
id: newId,
|
|
64
|
+
name: created?.name ?? values.name.trim(),
|
|
65
|
+
code: created?.code ?? values.code?.trim() ?? null
|
|
66
|
+
});
|
|
67
|
+
flash(t("staff.timesheets.projects.messages.saved", "Project saved."), "success");
|
|
68
|
+
onOpenChange(false);
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
)
|
|
72
|
+
] }) });
|
|
73
|
+
}
|
|
74
|
+
export {
|
|
75
|
+
CreateProjectDialog
|
|
76
|
+
};
|
|
77
|
+
//# sourceMappingURL=CreateProjectDialog.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../../../../src/modules/staff/lib/timesheets-ui/CreateProjectDialog.tsx"],
|
|
4
|
+
"sourcesContent": ["\"use client\"\n\nimport * as React from 'react'\nimport { Dialog, DialogContent, DialogHeader, DialogTitle } from '@open-mercato/ui/primitives/dialog'\nimport { Button } from '@open-mercato/ui/primitives/button'\nimport { CrudForm } from '@open-mercato/ui/backend/CrudForm'\nimport { createCrud } from '@open-mercato/ui/backend/utils/crud'\nimport { apiCall } from '@open-mercato/ui/backend/utils/apiCall'\nimport { createCrudFormError } from '@open-mercato/ui/backend/utils/serverErrors'\nimport { flash } from '@open-mercato/ui/backend/FlashMessages'\nimport { useT } from '@open-mercato/shared/lib/i18n/context'\nimport { E } from '#generated/entities.ids.generated'\nimport {\n buildProjectPayload,\n createProjectFormFields,\n createProjectFormGroups,\n createProjectFormSchema,\n type ProjectFormValues,\n} from '../../backend/staff/timesheets/projects/projectFormConfig'\n\ntype CreateProjectDialogProps = {\n open: boolean\n onOpenChange: (open: boolean) => void\n onProjectCreated: (project: { id: string; name: string; code: string | null }) => void\n}\n\nexport function CreateProjectDialog({ open, onOpenChange, onProjectCreated }: CreateProjectDialogProps) {\n const t = useT()\n\n const formSchema = React.useMemo(() => createProjectFormSchema(), [])\n const fields = React.useMemo(() => createProjectFormFields(t), [t])\n const groups = React.useMemo(() => createProjectFormGroups(t), [t])\n\n return (\n <Dialog open={open} onOpenChange={onOpenChange}>\n <DialogContent className=\"sm:max-w-lg\">\n <DialogHeader>\n <DialogTitle>\n {t('staff.timesheets.projects.form.createTitle', 'Create project')}\n </DialogTitle>\n </DialogHeader>\n <CrudForm<ProjectFormValues>\n embedded\n fields={fields}\n groups={groups}\n schema={formSchema}\n initialValues={{}}\n entityIds={[E.staff.staff_time_project]}\n submitLabel={t('staff.timesheets.projects.form.actions.create', 'Create')}\n extraActions={(\n <Button type=\"button\" variant=\"ghost\" onClick={() => onOpenChange(false)}>\n {t('staff.timesheets.projects.form.actions.cancel', 'Cancel')}\n </Button>\n )}\n onSubmit={async (values) => {\n const payload = buildProjectPayload(values)\n\n const { result: created } = await createCrud<{ id?: string; name?: string; code?: string | null }>(\n 'staff/timesheets/time-projects',\n payload,\n { errorMessage: t('staff.timesheets.projects.errors.save', 'Failed to save project.') },\n )\n\n const newId = created?.id\n if (!newId) {\n throw createCrudFormError(\n t('staff.timesheets.projects.errors.save', 'Failed to save project.'),\n )\n }\n\n // Auto-assign creator to the project (best-effort)\n try {\n const selfRes = await apiCall<{ member?: { id?: string } | null }>('/api/staff/team-members/self')\n const staffMemberId = selfRes.result?.member?.id\n if (staffMemberId) {\n await createCrud('staff/timesheets/time-projects/' + newId + '/employees', {\n staffMemberId,\n assignedStartDate: new Date().toISOString().slice(0, 10),\n status: 'active',\n }, { errorMessage: '' })\n }\n } catch {\n // non-critical \u2014 project created, self-assignment is best-effort\n }\n\n onProjectCreated({\n id: newId,\n name: created?.name ?? values.name.trim(),\n code: created?.code ?? values.code?.trim() ?? null,\n })\n\n flash(t('staff.timesheets.projects.messages.saved', 'Project saved.'), 'success')\n onOpenChange(false)\n }}\n />\n </DialogContent>\n </Dialog>\n )\n}\n"],
|
|
5
|
+
"mappings": ";AAmCM,SAEI,KAFJ;AAjCN,YAAY,WAAW;AACvB,SAAS,QAAQ,eAAe,cAAc,mBAAmB;AACjE,SAAS,cAAc;AACvB,SAAS,gBAAgB;AACzB,SAAS,kBAAkB;AAC3B,SAAS,eAAe;AACxB,SAAS,2BAA2B;AACpC,SAAS,aAAa;AACtB,SAAS,YAAY;AACrB,SAAS,SAAS;AAClB;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OAEK;AAQA,SAAS,oBAAoB,EAAE,MAAM,cAAc,iBAAiB,GAA6B;AACtG,QAAM,IAAI,KAAK;AAEf,QAAM,aAAa,MAAM,QAAQ,MAAM,wBAAwB,GAAG,CAAC,CAAC;AACpE,QAAM,SAAS,MAAM,QAAQ,MAAM,wBAAwB,CAAC,GAAG,CAAC,CAAC,CAAC;AAClE,QAAM,SAAS,MAAM,QAAQ,MAAM,wBAAwB,CAAC,GAAG,CAAC,CAAC,CAAC;AAElE,SACE,oBAAC,UAAO,MAAY,cAClB,+BAAC,iBAAc,WAAU,eACvB;AAAA,wBAAC,gBACC,8BAAC,eACE,YAAE,8CAA8C,gBAAgB,GACnE,GACF;AAAA,IACA;AAAA,MAAC;AAAA;AAAA,QACC,UAAQ;AAAA,QACR;AAAA,QACA;AAAA,QACA,QAAQ;AAAA,QACR,eAAe,CAAC;AAAA,QAChB,WAAW,CAAC,EAAE,MAAM,kBAAkB;AAAA,QACtC,aAAa,EAAE,iDAAiD,QAAQ;AAAA,QACxE,cACE,oBAAC,UAAO,MAAK,UAAS,SAAQ,SAAQ,SAAS,MAAM,aAAa,KAAK,GACpE,YAAE,iDAAiD,QAAQ,GAC9D;AAAA,QAEF,UAAU,OAAO,WAAW;AAC1B,gBAAM,UAAU,oBAAoB,MAAM;AAE1C,gBAAM,EAAE,QAAQ,QAAQ,IAAI,MAAM;AAAA,YAChC;AAAA,YACA;AAAA,YACA,EAAE,cAAc,EAAE,yCAAyC,yBAAyB,EAAE;AAAA,UACxF;AAEA,gBAAM,QAAQ,SAAS;AACvB,cAAI,CAAC,OAAO;AACV,kBAAM;AAAA,cACJ,EAAE,yCAAyC,yBAAyB;AAAA,YACtE;AAAA,UACF;AAGA,cAAI;AACF,kBAAM,UAAU,MAAM,QAA6C,8BAA8B;AACjG,kBAAM,gBAAgB,QAAQ,QAAQ,QAAQ;AAC9C,gBAAI,eAAe;AACjB,oBAAM,WAAW,oCAAoC,QAAQ,cAAc;AAAA,gBACzE;AAAA,gBACA,oBAAmB,oBAAI,KAAK,GAAE,YAAY,EAAE,MAAM,GAAG,EAAE;AAAA,gBACvD,QAAQ;AAAA,cACV,GAAG,EAAE,cAAc,GAAG,CAAC;AAAA,YACzB;AAAA,UACF,QAAQ;AAAA,UAER;AAEA,2BAAiB;AAAA,YACf,IAAI;AAAA,YACJ,MAAM,SAAS,QAAQ,OAAO,KAAK,KAAK;AAAA,YACxC,MAAM,SAAS,QAAQ,OAAO,MAAM,KAAK,KAAK;AAAA,UAChD,CAAC;AAED,gBAAM,EAAE,4CAA4C,gBAAgB,GAAG,SAAS;AAChF,uBAAa,KAAK;AAAA,QACpB;AAAA;AAAA,IACF;AAAA,KACF,GACF;AAEJ;",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|
|
@@ -0,0 +1,173 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
3
|
+
import * as React from "react";
|
|
4
|
+
import { useT } from "@open-mercato/shared/lib/i18n/context";
|
|
5
|
+
import { apiCall } from "@open-mercato/ui/backend/utils/apiCall";
|
|
6
|
+
import { ProjectColorDot } from "./ProjectColorDot.js";
|
|
7
|
+
function getLocalDateStr(date) {
|
|
8
|
+
const year = date.getFullYear();
|
|
9
|
+
const month = String(date.getMonth() + 1).padStart(2, "0");
|
|
10
|
+
const day = String(date.getDate()).padStart(2, "0");
|
|
11
|
+
return `${year}-${month}-${day}`;
|
|
12
|
+
}
|
|
13
|
+
function formatDayLabel(dateStr, translate) {
|
|
14
|
+
const today = /* @__PURE__ */ new Date();
|
|
15
|
+
const todayStr = getLocalDateStr(today);
|
|
16
|
+
const yesterday = new Date(today);
|
|
17
|
+
yesterday.setDate(yesterday.getDate() - 1);
|
|
18
|
+
const yesterdayStr = getLocalDateStr(yesterday);
|
|
19
|
+
if (dateStr === todayStr) return translate("staff.timesheets.my.list.today", "Today");
|
|
20
|
+
if (dateStr === yesterdayStr) return translate("staff.timesheets.my.list.yesterday", "Yesterday");
|
|
21
|
+
const date = /* @__PURE__ */ new Date(dateStr + "T00:00:00");
|
|
22
|
+
const dayName = date.toLocaleDateString(void 0, { weekday: "short" });
|
|
23
|
+
const day = date.getDate();
|
|
24
|
+
const month = date.toLocaleDateString(void 0, { month: "short" });
|
|
25
|
+
return `${dayName}, ${day} ${month}`;
|
|
26
|
+
}
|
|
27
|
+
function formatDuration(minutes) {
|
|
28
|
+
if (minutes < 60) return `${minutes}m`;
|
|
29
|
+
const hours = Math.floor(minutes / 60);
|
|
30
|
+
const remainingMinutes = minutes % 60;
|
|
31
|
+
if (remainingMinutes === 0) return `${hours}h`;
|
|
32
|
+
return `${hours}h ${remainingMinutes}m`;
|
|
33
|
+
}
|
|
34
|
+
function formatTimeRange(startedAt, endedAt) {
|
|
35
|
+
const start = new Date(startedAt);
|
|
36
|
+
const end = new Date(endedAt);
|
|
37
|
+
const format = (date) => date.toLocaleTimeString(void 0, {
|
|
38
|
+
hour: "numeric",
|
|
39
|
+
minute: "2-digit",
|
|
40
|
+
hour12: true
|
|
41
|
+
});
|
|
42
|
+
return `${format(start)} - ${format(end)}`;
|
|
43
|
+
}
|
|
44
|
+
function InlineDescription({
|
|
45
|
+
entryId,
|
|
46
|
+
initialValue,
|
|
47
|
+
placeholder,
|
|
48
|
+
onSaved
|
|
49
|
+
}) {
|
|
50
|
+
const [editing, setEditing] = React.useState(false);
|
|
51
|
+
const [value, setValue] = React.useState(initialValue ?? "");
|
|
52
|
+
const [saving, setSaving] = React.useState(false);
|
|
53
|
+
const inputRef = React.useRef(null);
|
|
54
|
+
React.useEffect(() => {
|
|
55
|
+
if (editing && inputRef.current) inputRef.current.focus();
|
|
56
|
+
}, [editing]);
|
|
57
|
+
const save = React.useCallback(async () => {
|
|
58
|
+
const trimmed = value.trim();
|
|
59
|
+
if (trimmed === (initialValue ?? "")) {
|
|
60
|
+
setEditing(false);
|
|
61
|
+
return;
|
|
62
|
+
}
|
|
63
|
+
setSaving(true);
|
|
64
|
+
try {
|
|
65
|
+
await apiCall(`/api/staff/timesheets/time-entries`, {
|
|
66
|
+
method: "PUT",
|
|
67
|
+
headers: { "Content-Type": "application/json" },
|
|
68
|
+
body: JSON.stringify({ id: entryId, notes: trimmed || null })
|
|
69
|
+
});
|
|
70
|
+
onSaved?.();
|
|
71
|
+
} catch {
|
|
72
|
+
} finally {
|
|
73
|
+
setSaving(false);
|
|
74
|
+
setEditing(false);
|
|
75
|
+
}
|
|
76
|
+
}, [entryId, value, initialValue, onSaved]);
|
|
77
|
+
if (!editing) {
|
|
78
|
+
return /* @__PURE__ */ jsx(
|
|
79
|
+
"button",
|
|
80
|
+
{
|
|
81
|
+
type: "button",
|
|
82
|
+
onClick: () => setEditing(true),
|
|
83
|
+
className: `text-sm text-left cursor-pointer hover:underline ${initialValue ? "" : "text-muted-foreground italic"}`,
|
|
84
|
+
children: initialValue || placeholder
|
|
85
|
+
}
|
|
86
|
+
);
|
|
87
|
+
}
|
|
88
|
+
return /* @__PURE__ */ jsx(
|
|
89
|
+
"input",
|
|
90
|
+
{
|
|
91
|
+
ref: inputRef,
|
|
92
|
+
type: "text",
|
|
93
|
+
value,
|
|
94
|
+
disabled: saving,
|
|
95
|
+
onChange: (e) => setValue(e.target.value),
|
|
96
|
+
onBlur: () => {
|
|
97
|
+
void save();
|
|
98
|
+
},
|
|
99
|
+
onKeyDown: (e) => {
|
|
100
|
+
if (e.key === "Enter") {
|
|
101
|
+
void save();
|
|
102
|
+
}
|
|
103
|
+
if (e.key === "Escape") {
|
|
104
|
+
setValue(initialValue ?? "");
|
|
105
|
+
setEditing(false);
|
|
106
|
+
}
|
|
107
|
+
},
|
|
108
|
+
placeholder,
|
|
109
|
+
className: "text-sm w-full bg-transparent border-b border-primary/40 outline-none py-0.5 placeholder:text-muted-foreground/50"
|
|
110
|
+
}
|
|
111
|
+
);
|
|
112
|
+
}
|
|
113
|
+
function ListView({ entries, onEntryUpdated }) {
|
|
114
|
+
const t = useT();
|
|
115
|
+
if (entries.length === 0) {
|
|
116
|
+
return /* @__PURE__ */ jsx("div", { className: "flex items-center justify-center py-12 text-sm text-muted-foreground", children: t("staff.timesheets.my.list.noEntries", "No entries for this period.") });
|
|
117
|
+
}
|
|
118
|
+
const grouped = /* @__PURE__ */ new Map();
|
|
119
|
+
for (const entry of entries) {
|
|
120
|
+
const existing = grouped.get(entry.date);
|
|
121
|
+
if (existing) {
|
|
122
|
+
existing.push(entry);
|
|
123
|
+
} else {
|
|
124
|
+
grouped.set(entry.date, [entry]);
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
const sortedDays = [...grouped.keys()].sort((a, b) => b.localeCompare(a));
|
|
128
|
+
return /* @__PURE__ */ jsx("div", { children: sortedDays.map((dateStr) => {
|
|
129
|
+
const dayEntries = grouped.get(dateStr);
|
|
130
|
+
const dailyTotal = dayEntries.reduce(
|
|
131
|
+
(sum, entry) => sum + entry.durationMinutes,
|
|
132
|
+
0
|
|
133
|
+
);
|
|
134
|
+
return /* @__PURE__ */ jsxs("div", { className: "rounded-lg border mb-4", children: [
|
|
135
|
+
/* @__PURE__ */ jsxs("div", { className: "flex justify-between items-center p-4 border-b bg-muted/30 font-medium text-sm", children: [
|
|
136
|
+
/* @__PURE__ */ jsx("span", { children: formatDayLabel(dateStr, t) }),
|
|
137
|
+
/* @__PURE__ */ jsx("span", { className: "font-mono tabular-nums", children: formatDuration(dailyTotal) })
|
|
138
|
+
] }),
|
|
139
|
+
dayEntries.map((entry) => /* @__PURE__ */ jsxs(
|
|
140
|
+
"div",
|
|
141
|
+
{
|
|
142
|
+
className: "flex items-center justify-between p-3 border-b last:border-0 hover:bg-muted/20",
|
|
143
|
+
children: [
|
|
144
|
+
/* @__PURE__ */ jsxs("div", { className: "flex flex-col gap-1 min-w-0 flex-1 mr-4", children: [
|
|
145
|
+
/* @__PURE__ */ jsx(
|
|
146
|
+
InlineDescription,
|
|
147
|
+
{
|
|
148
|
+
entryId: entry.id,
|
|
149
|
+
initialValue: entry.notes,
|
|
150
|
+
placeholder: t("staff.timesheets.my.list.addDescription", "Add description"),
|
|
151
|
+
onSaved: onEntryUpdated
|
|
152
|
+
}
|
|
153
|
+
),
|
|
154
|
+
/* @__PURE__ */ jsxs("span", { className: "inline-flex items-center gap-1 text-xs font-medium text-muted-foreground", children: [
|
|
155
|
+
/* @__PURE__ */ jsx(ProjectColorDot, { colorKey: entry.projectColor, projectName: entry.projectName, size: "xs" }),
|
|
156
|
+
entry.projectName
|
|
157
|
+
] })
|
|
158
|
+
] }),
|
|
159
|
+
/* @__PURE__ */ jsxs("div", { className: "flex flex-col items-end gap-1 shrink-0", children: [
|
|
160
|
+
entry.source === "timer" && entry.startedAt && entry.endedAt ? /* @__PURE__ */ jsx("span", { className: "text-xs text-muted-foreground", children: formatTimeRange(entry.startedAt, entry.endedAt) }) : null,
|
|
161
|
+
/* @__PURE__ */ jsx("span", { className: "text-sm font-mono tabular-nums", children: formatDuration(entry.durationMinutes) })
|
|
162
|
+
] })
|
|
163
|
+
]
|
|
164
|
+
},
|
|
165
|
+
entry.id
|
|
166
|
+
))
|
|
167
|
+
] }, dateStr);
|
|
168
|
+
}) });
|
|
169
|
+
}
|
|
170
|
+
export {
|
|
171
|
+
ListView
|
|
172
|
+
};
|
|
173
|
+
//# sourceMappingURL=ListView.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../../../../src/modules/staff/lib/timesheets-ui/ListView.tsx"],
|
|
4
|
+
"sourcesContent": ["'use client'\n\nimport * as React from 'react'\nimport { useT } from '@open-mercato/shared/lib/i18n/context'\nimport type { TranslateFn } from '@open-mercato/shared/lib/i18n/context'\nimport { apiCall } from '@open-mercato/ui/backend/utils/apiCall'\nimport { ProjectColorDot } from './ProjectColorDot'\n\ntype TimeEntry = {\n id: string\n date: string\n durationMinutes: number\n projectId: string\n projectName: string\n projectCode: string | null\n projectColor: string | null\n notes: string | null\n source: string\n startedAt: string | null\n endedAt: string | null\n}\n\ntype ListViewProps = {\n entries: TimeEntry[]\n onEntryUpdated?: () => void\n}\n\nfunction getLocalDateStr(date: Date): string {\n const year = date.getFullYear()\n const month = String(date.getMonth() + 1).padStart(2, '0')\n const day = String(date.getDate()).padStart(2, '0')\n return `${year}-${month}-${day}`\n}\n\nfunction formatDayLabel(dateStr: string, translate: TranslateFn): string {\n const today = new Date()\n const todayStr = getLocalDateStr(today)\n\n const yesterday = new Date(today)\n yesterday.setDate(yesterday.getDate() - 1)\n const yesterdayStr = getLocalDateStr(yesterday)\n\n if (dateStr === todayStr) return translate('staff.timesheets.my.list.today', 'Today')\n if (dateStr === yesterdayStr) return translate('staff.timesheets.my.list.yesterday', 'Yesterday')\n\n const date = new Date(dateStr + 'T00:00:00')\n const dayName = date.toLocaleDateString(undefined, { weekday: 'short' })\n const day = date.getDate()\n const month = date.toLocaleDateString(undefined, { month: 'short' })\n\n return `${dayName}, ${day} ${month}`\n}\n\nfunction formatDuration(minutes: number): string {\n if (minutes < 60) return `${minutes}m`\n\n const hours = Math.floor(minutes / 60)\n const remainingMinutes = minutes % 60\n\n if (remainingMinutes === 0) return `${hours}h`\n return `${hours}h ${remainingMinutes}m`\n}\n\nfunction formatTimeRange(startedAt: string, endedAt: string): string {\n const start = new Date(startedAt)\n const end = new Date(endedAt)\n\n const format = (date: Date) =>\n date.toLocaleTimeString(undefined, {\n hour: 'numeric',\n minute: '2-digit',\n hour12: true,\n })\n\n return `${format(start)} - ${format(end)}`\n}\n\nfunction InlineDescription({\n entryId,\n initialValue,\n placeholder,\n onSaved,\n}: {\n entryId: string\n initialValue: string | null\n placeholder: string\n onSaved?: () => void\n}) {\n const [editing, setEditing] = React.useState(false)\n const [value, setValue] = React.useState(initialValue ?? '')\n const [saving, setSaving] = React.useState(false)\n const inputRef = React.useRef<HTMLInputElement>(null)\n\n React.useEffect(() => {\n if (editing && inputRef.current) inputRef.current.focus()\n }, [editing])\n\n const save = React.useCallback(async () => {\n const trimmed = value.trim()\n if (trimmed === (initialValue ?? '')) {\n setEditing(false)\n return\n }\n setSaving(true)\n try {\n await apiCall(`/api/staff/timesheets/time-entries`, {\n method: 'PUT',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({ id: entryId, notes: trimmed || null }),\n })\n onSaved?.()\n } catch {\n // silent\n } finally {\n setSaving(false)\n setEditing(false)\n }\n }, [entryId, value, initialValue, onSaved])\n\n if (!editing) {\n return (\n <button\n type=\"button\"\n onClick={() => setEditing(true)}\n className={`text-sm text-left cursor-pointer hover:underline ${\n initialValue ? '' : 'text-muted-foreground italic'\n }`}\n >\n {initialValue || placeholder}\n </button>\n )\n }\n\n return (\n <input\n ref={inputRef}\n type=\"text\"\n value={value}\n disabled={saving}\n onChange={(e) => setValue(e.target.value)}\n onBlur={() => { void save() }}\n onKeyDown={(e) => {\n if (e.key === 'Enter') { void save() }\n if (e.key === 'Escape') { setValue(initialValue ?? ''); setEditing(false) }\n }}\n placeholder={placeholder}\n className=\"text-sm w-full bg-transparent border-b border-primary/40 outline-none py-0.5 placeholder:text-muted-foreground/50\"\n />\n )\n}\n\nexport function ListView({ entries, onEntryUpdated }: ListViewProps) {\n const t = useT()\n\n if (entries.length === 0) {\n return (\n <div className=\"flex items-center justify-center py-12 text-sm text-muted-foreground\">\n {t('staff.timesheets.my.list.noEntries', 'No entries for this period.')}\n </div>\n )\n }\n\n const grouped = new Map<string, TimeEntry[]>()\n for (const entry of entries) {\n const existing = grouped.get(entry.date)\n if (existing) {\n existing.push(entry)\n } else {\n grouped.set(entry.date, [entry])\n }\n }\n\n const sortedDays = [...grouped.keys()].sort((a, b) => b.localeCompare(a))\n\n return (\n <div>\n {sortedDays.map((dateStr) => {\n const dayEntries = grouped.get(dateStr)!\n const dailyTotal = dayEntries.reduce(\n (sum, entry) => sum + entry.durationMinutes,\n 0,\n )\n\n return (\n <div key={dateStr} className=\"rounded-lg border mb-4\">\n <div className=\"flex justify-between items-center p-4 border-b bg-muted/30 font-medium text-sm\">\n <span>{formatDayLabel(dateStr, t)}</span>\n <span className=\"font-mono tabular-nums\">\n {formatDuration(dailyTotal)}\n </span>\n </div>\n\n {dayEntries.map((entry) => (\n <div\n key={entry.id}\n className=\"flex items-center justify-between p-3 border-b last:border-0 hover:bg-muted/20\"\n >\n <div className=\"flex flex-col gap-1 min-w-0 flex-1 mr-4\">\n <InlineDescription\n entryId={entry.id}\n initialValue={entry.notes}\n placeholder={t('staff.timesheets.my.list.addDescription', 'Add description')}\n onSaved={onEntryUpdated}\n />\n <span className=\"inline-flex items-center gap-1 text-xs font-medium text-muted-foreground\">\n <ProjectColorDot colorKey={entry.projectColor} projectName={entry.projectName} size=\"xs\" />\n {entry.projectName}\n </span>\n </div>\n\n <div className=\"flex flex-col items-end gap-1 shrink-0\">\n {entry.source === 'timer' &&\n entry.startedAt &&\n entry.endedAt ? (\n <span className=\"text-xs text-muted-foreground\">\n {formatTimeRange(entry.startedAt, entry.endedAt)}\n </span>\n ) : null}\n <span className=\"text-sm font-mono tabular-nums\">\n {formatDuration(entry.durationMinutes)}\n </span>\n </div>\n </div>\n ))}\n </div>\n )\n })}\n </div>\n )\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { jsx } from "react/jsx-runtime";
|
|
3
|
+
import { resolveProjectColorHex } from "./colors.js";
|
|
4
|
+
const SIZE_CLASS = {
|
|
5
|
+
xs: "h-2 w-2",
|
|
6
|
+
sm: "h-2.5 w-2.5",
|
|
7
|
+
md: "h-3 w-3"
|
|
8
|
+
};
|
|
9
|
+
function ProjectColorDot({
|
|
10
|
+
colorKey,
|
|
11
|
+
projectName,
|
|
12
|
+
size = "sm",
|
|
13
|
+
className,
|
|
14
|
+
title
|
|
15
|
+
}) {
|
|
16
|
+
const hex = resolveProjectColorHex(colorKey, projectName);
|
|
17
|
+
const base = "inline-block shrink-0 rounded-full";
|
|
18
|
+
const classes = [base, SIZE_CLASS[size], className].filter(Boolean).join(" ");
|
|
19
|
+
return /* @__PURE__ */ jsx(
|
|
20
|
+
"span",
|
|
21
|
+
{
|
|
22
|
+
"aria-hidden": "true",
|
|
23
|
+
title,
|
|
24
|
+
className: classes,
|
|
25
|
+
style: { backgroundColor: hex }
|
|
26
|
+
}
|
|
27
|
+
);
|
|
28
|
+
}
|
|
29
|
+
export {
|
|
30
|
+
ProjectColorDot
|
|
31
|
+
};
|
|
32
|
+
//# sourceMappingURL=ProjectColorDot.js.map
|