@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,260 @@
|
|
|
1
|
+
import { StaffTimeProject, StaffTimeProjectMember } from "../../data/entities.js";
|
|
2
|
+
import {
|
|
3
|
+
addUtcDays,
|
|
4
|
+
getFirstDayOfMonthUtc,
|
|
5
|
+
getFirstDayOfNextMonthUtc,
|
|
6
|
+
getMondayUtc,
|
|
7
|
+
toDateOnlyString
|
|
8
|
+
} from "./dateBuckets.js";
|
|
9
|
+
import { deltaPct, minutesToHours } from "./kpiMath.js";
|
|
10
|
+
async function computePmProjectsKpis(scope) {
|
|
11
|
+
const em = scope.em.fork();
|
|
12
|
+
const now = scope.now ?? /* @__PURE__ */ new Date();
|
|
13
|
+
const projects = await em.find(StaffTimeProject, {
|
|
14
|
+
tenantId: scope.tenantId,
|
|
15
|
+
organizationId: scope.organizationId,
|
|
16
|
+
deletedAt: null
|
|
17
|
+
});
|
|
18
|
+
const totals = {
|
|
19
|
+
total: projects.length,
|
|
20
|
+
active: 0,
|
|
21
|
+
onHold: 0,
|
|
22
|
+
completed: 0
|
|
23
|
+
};
|
|
24
|
+
for (const p of projects) {
|
|
25
|
+
if (p.status === "active") totals.active += 1;
|
|
26
|
+
else if (p.status === "on_hold") totals.onHold += 1;
|
|
27
|
+
else if (p.status === "completed") totals.completed += 1;
|
|
28
|
+
}
|
|
29
|
+
const monthStart = getFirstDayOfMonthUtc(now);
|
|
30
|
+
const nextMonthStart = getFirstDayOfNextMonthUtc(now);
|
|
31
|
+
const prevMonthStart = getFirstDayOfMonthUtc(addUtcDays(monthStart, -1));
|
|
32
|
+
const weekMonday = getMondayUtc(now);
|
|
33
|
+
const nextMonday = addUtcDays(weekMonday, 7);
|
|
34
|
+
const prevMonday = addUtcDays(weekMonday, -7);
|
|
35
|
+
const hoursRows = await em.getConnection().execute(
|
|
36
|
+
`
|
|
37
|
+
SELECT bucket, COALESCE(SUM(duration_minutes), 0)::bigint AS total_minutes
|
|
38
|
+
FROM (
|
|
39
|
+
SELECT
|
|
40
|
+
CASE
|
|
41
|
+
WHEN date >= ?::date AND date < ?::date THEN 'current'
|
|
42
|
+
WHEN date >= ?::date AND date < ?::date THEN 'previous'
|
|
43
|
+
END AS bucket,
|
|
44
|
+
duration_minutes
|
|
45
|
+
FROM staff_time_entries
|
|
46
|
+
WHERE organization_id = ?
|
|
47
|
+
AND tenant_id = ?
|
|
48
|
+
AND deleted_at IS NULL
|
|
49
|
+
AND date >= ?::date
|
|
50
|
+
AND date < ?::date
|
|
51
|
+
) t
|
|
52
|
+
WHERE bucket IS NOT NULL
|
|
53
|
+
GROUP BY bucket
|
|
54
|
+
`,
|
|
55
|
+
[
|
|
56
|
+
toDateOnlyString(monthStart),
|
|
57
|
+
toDateOnlyString(nextMonthStart),
|
|
58
|
+
toDateOnlyString(prevMonthStart),
|
|
59
|
+
toDateOnlyString(monthStart),
|
|
60
|
+
scope.organizationId,
|
|
61
|
+
scope.tenantId,
|
|
62
|
+
toDateOnlyString(prevMonthStart),
|
|
63
|
+
toDateOnlyString(nextMonthStart)
|
|
64
|
+
]
|
|
65
|
+
);
|
|
66
|
+
const weekRows = await em.getConnection().execute(
|
|
67
|
+
`
|
|
68
|
+
SELECT bucket, COALESCE(SUM(duration_minutes), 0)::bigint AS total_minutes
|
|
69
|
+
FROM (
|
|
70
|
+
SELECT
|
|
71
|
+
CASE
|
|
72
|
+
WHEN date >= ?::date AND date < ?::date THEN 'current'
|
|
73
|
+
WHEN date >= ?::date AND date < ?::date THEN 'previous'
|
|
74
|
+
END AS bucket,
|
|
75
|
+
duration_minutes
|
|
76
|
+
FROM staff_time_entries
|
|
77
|
+
WHERE organization_id = ?
|
|
78
|
+
AND tenant_id = ?
|
|
79
|
+
AND deleted_at IS NULL
|
|
80
|
+
AND date >= ?::date
|
|
81
|
+
AND date < ?::date
|
|
82
|
+
) t
|
|
83
|
+
WHERE bucket IS NOT NULL
|
|
84
|
+
GROUP BY bucket
|
|
85
|
+
`,
|
|
86
|
+
[
|
|
87
|
+
toDateOnlyString(weekMonday),
|
|
88
|
+
toDateOnlyString(nextMonday),
|
|
89
|
+
toDateOnlyString(prevMonday),
|
|
90
|
+
toDateOnlyString(weekMonday),
|
|
91
|
+
scope.organizationId,
|
|
92
|
+
scope.tenantId,
|
|
93
|
+
toDateOnlyString(prevMonday),
|
|
94
|
+
toDateOnlyString(nextMonday)
|
|
95
|
+
]
|
|
96
|
+
);
|
|
97
|
+
let currentHours = 0;
|
|
98
|
+
let previousHours = 0;
|
|
99
|
+
for (const row of hoursRows) {
|
|
100
|
+
const minutes = typeof row.total_minutes === "string" ? Number(row.total_minutes) : row.total_minutes;
|
|
101
|
+
if (row.bucket === "current") currentHours = minutesToHours(minutes);
|
|
102
|
+
else previousHours = minutesToHours(minutes);
|
|
103
|
+
}
|
|
104
|
+
let weekCurrentHours = 0;
|
|
105
|
+
let weekPreviousHours = 0;
|
|
106
|
+
for (const row of weekRows) {
|
|
107
|
+
const minutes = typeof row.total_minutes === "string" ? Number(row.total_minutes) : row.total_minutes;
|
|
108
|
+
if (row.bucket === "current") weekCurrentHours = minutesToHours(minutes);
|
|
109
|
+
else weekPreviousHours = minutesToHours(minutes);
|
|
110
|
+
}
|
|
111
|
+
const teamRows = await em.getConnection().execute(
|
|
112
|
+
`
|
|
113
|
+
SELECT COUNT(DISTINCT staff_member_id)::bigint AS count
|
|
114
|
+
FROM staff_time_entries
|
|
115
|
+
WHERE organization_id = ?
|
|
116
|
+
AND tenant_id = ?
|
|
117
|
+
AND deleted_at IS NULL
|
|
118
|
+
AND date >= ?::date
|
|
119
|
+
AND date < ?::date
|
|
120
|
+
`,
|
|
121
|
+
[
|
|
122
|
+
scope.organizationId,
|
|
123
|
+
scope.tenantId,
|
|
124
|
+
toDateOnlyString(monthStart),
|
|
125
|
+
toDateOnlyString(nextMonthStart)
|
|
126
|
+
]
|
|
127
|
+
);
|
|
128
|
+
const teamCount = teamRows.length ? Number(teamRows[0].count) : 0;
|
|
129
|
+
const assignedToMe = { total: 0, active: 0 };
|
|
130
|
+
if (scope.callerStaffMemberId) {
|
|
131
|
+
const memberships = await em.find(StaffTimeProjectMember, {
|
|
132
|
+
staffMemberId: scope.callerStaffMemberId,
|
|
133
|
+
tenantId: scope.tenantId,
|
|
134
|
+
organizationId: scope.organizationId,
|
|
135
|
+
status: "active",
|
|
136
|
+
deletedAt: null
|
|
137
|
+
});
|
|
138
|
+
const assignedProjectIds = memberships.map((m) => m.timeProjectId);
|
|
139
|
+
const assignedProjects = projects.filter((p) => assignedProjectIds.includes(p.id));
|
|
140
|
+
assignedToMe.total = assignedProjects.length;
|
|
141
|
+
assignedToMe.active = assignedProjects.filter((p) => p.status === "active").length;
|
|
142
|
+
}
|
|
143
|
+
return {
|
|
144
|
+
role: "pm",
|
|
145
|
+
totals,
|
|
146
|
+
hoursWeek: {
|
|
147
|
+
current: weekCurrentHours,
|
|
148
|
+
previous: weekPreviousHours,
|
|
149
|
+
deltaPct: deltaPct(weekCurrentHours, weekPreviousHours)
|
|
150
|
+
},
|
|
151
|
+
hoursMonth: {
|
|
152
|
+
current: currentHours,
|
|
153
|
+
previous: previousHours,
|
|
154
|
+
deltaPct: deltaPct(currentHours, previousHours)
|
|
155
|
+
},
|
|
156
|
+
teamActive: { count: teamCount },
|
|
157
|
+
assignedToMe
|
|
158
|
+
};
|
|
159
|
+
}
|
|
160
|
+
async function computeCollabProjectsKpis(scope) {
|
|
161
|
+
const em = scope.em.fork();
|
|
162
|
+
const now = scope.now ?? /* @__PURE__ */ new Date();
|
|
163
|
+
const memberships = await em.find(StaffTimeProjectMember, {
|
|
164
|
+
staffMemberId: scope.staffMemberId,
|
|
165
|
+
tenantId: scope.tenantId,
|
|
166
|
+
organizationId: scope.organizationId,
|
|
167
|
+
status: "active",
|
|
168
|
+
deletedAt: null
|
|
169
|
+
});
|
|
170
|
+
const projectIds = memberships.map((m) => m.timeProjectId);
|
|
171
|
+
const myProjects = { total: projectIds.length, active: 0 };
|
|
172
|
+
if (projectIds.length > 0) {
|
|
173
|
+
const projects = await em.find(StaffTimeProject, {
|
|
174
|
+
id: { $in: projectIds },
|
|
175
|
+
tenantId: scope.tenantId,
|
|
176
|
+
organizationId: scope.organizationId,
|
|
177
|
+
status: "active",
|
|
178
|
+
deletedAt: null
|
|
179
|
+
});
|
|
180
|
+
myProjects.active = projects.length;
|
|
181
|
+
}
|
|
182
|
+
const weekMonday = getMondayUtc(now);
|
|
183
|
+
const nextMonday = addUtcDays(weekMonday, 7);
|
|
184
|
+
const prevMonday = addUtcDays(weekMonday, -7);
|
|
185
|
+
const monthStart = getFirstDayOfMonthUtc(now);
|
|
186
|
+
const nextMonthStart = getFirstDayOfNextMonthUtc(now);
|
|
187
|
+
const prevMonthStart = getFirstDayOfMonthUtc(addUtcDays(monthStart, -1));
|
|
188
|
+
const rows = await em.getConnection().execute(
|
|
189
|
+
`
|
|
190
|
+
SELECT bucket, COALESCE(SUM(duration_minutes), 0)::bigint AS total_minutes
|
|
191
|
+
FROM (
|
|
192
|
+
SELECT
|
|
193
|
+
CASE
|
|
194
|
+
WHEN date >= ?::date AND date < ?::date THEN 'week_current'
|
|
195
|
+
WHEN date >= ?::date AND date < ?::date THEN 'week_previous'
|
|
196
|
+
WHEN date >= ?::date AND date < ?::date THEN 'month_current'
|
|
197
|
+
WHEN date >= ?::date AND date < ?::date THEN 'month_previous'
|
|
198
|
+
END AS bucket,
|
|
199
|
+
duration_minutes
|
|
200
|
+
FROM staff_time_entries
|
|
201
|
+
WHERE organization_id = ?
|
|
202
|
+
AND tenant_id = ?
|
|
203
|
+
AND staff_member_id = ?
|
|
204
|
+
AND deleted_at IS NULL
|
|
205
|
+
AND date >= LEAST(?::date, ?::date)
|
|
206
|
+
AND date < GREATEST(?::date, ?::date)
|
|
207
|
+
) t
|
|
208
|
+
WHERE bucket IS NOT NULL
|
|
209
|
+
GROUP BY bucket
|
|
210
|
+
`,
|
|
211
|
+
[
|
|
212
|
+
toDateOnlyString(weekMonday),
|
|
213
|
+
toDateOnlyString(nextMonday),
|
|
214
|
+
toDateOnlyString(prevMonday),
|
|
215
|
+
toDateOnlyString(weekMonday),
|
|
216
|
+
toDateOnlyString(monthStart),
|
|
217
|
+
toDateOnlyString(nextMonthStart),
|
|
218
|
+
toDateOnlyString(prevMonthStart),
|
|
219
|
+
toDateOnlyString(monthStart),
|
|
220
|
+
scope.organizationId,
|
|
221
|
+
scope.tenantId,
|
|
222
|
+
scope.staffMemberId,
|
|
223
|
+
toDateOnlyString(prevMonday),
|
|
224
|
+
toDateOnlyString(prevMonthStart),
|
|
225
|
+
toDateOnlyString(nextMonday),
|
|
226
|
+
toDateOnlyString(nextMonthStart)
|
|
227
|
+
]
|
|
228
|
+
);
|
|
229
|
+
let weekCurrent = 0;
|
|
230
|
+
let weekPrevious = 0;
|
|
231
|
+
let monthCurrent = 0;
|
|
232
|
+
let monthPrevious = 0;
|
|
233
|
+
for (const row of rows) {
|
|
234
|
+
const minutes = typeof row.total_minutes === "string" ? Number(row.total_minutes) : row.total_minutes;
|
|
235
|
+
const hours = minutesToHours(minutes);
|
|
236
|
+
if (row.bucket === "week_current") weekCurrent = hours;
|
|
237
|
+
else if (row.bucket === "week_previous") weekPrevious = hours;
|
|
238
|
+
else if (row.bucket === "month_current") monthCurrent = hours;
|
|
239
|
+
else if (row.bucket === "month_previous") monthPrevious = hours;
|
|
240
|
+
}
|
|
241
|
+
return {
|
|
242
|
+
role: "collab",
|
|
243
|
+
myProjects,
|
|
244
|
+
myHoursWeek: {
|
|
245
|
+
current: weekCurrent,
|
|
246
|
+
previous: weekPrevious,
|
|
247
|
+
deltaPct: deltaPct(weekCurrent, weekPrevious)
|
|
248
|
+
},
|
|
249
|
+
myHoursMonth: {
|
|
250
|
+
current: monthCurrent,
|
|
251
|
+
previous: monthPrevious,
|
|
252
|
+
deltaPct: deltaPct(monthCurrent, monthPrevious)
|
|
253
|
+
}
|
|
254
|
+
};
|
|
255
|
+
}
|
|
256
|
+
export {
|
|
257
|
+
computeCollabProjectsKpis,
|
|
258
|
+
computePmProjectsKpis
|
|
259
|
+
};
|
|
260
|
+
//# sourceMappingURL=computeProjectsKpis.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../../../../src/modules/staff/lib/timesheets-projects/computeProjectsKpis.ts"],
|
|
4
|
+
"sourcesContent": ["import type { EntityManager } from '@mikro-orm/postgresql'\nimport { StaffTimeProject, StaffTimeProjectMember } from '../../data/entities'\nimport {\n addUtcDays,\n getFirstDayOfMonthUtc,\n getFirstDayOfNextMonthUtc,\n getMondayUtc,\n toDateOnlyString,\n} from './dateBuckets'\nimport { deltaPct, minutesToHours } from './kpiMath'\n\ntype WeekRow = { bucket: 'current' | 'previous'; total_minutes: string | number }\n\nexport type KpiScope = {\n em: EntityManager\n organizationId: string\n tenantId: string\n now?: Date\n}\n\nexport type KpiDelta = {\n current: number\n previous: number\n deltaPct: number | null\n}\n\nexport type ProjectKpisPmResult = {\n role: 'pm'\n totals: { total: number; active: number; onHold: number; completed: number }\n hoursWeek: KpiDelta\n hoursMonth: KpiDelta\n teamActive: { count: number }\n assignedToMe: { total: number; active: number }\n}\n\nexport type ProjectKpisCollabResult = {\n role: 'collab'\n myProjects: { total: number; active: number }\n myHoursWeek: KpiDelta\n myHoursMonth: KpiDelta\n}\n\nexport async function computePmProjectsKpis(\n scope: KpiScope & { callerStaffMemberId?: string | null },\n): Promise<ProjectKpisPmResult> {\n const em = scope.em.fork()\n const now = scope.now ?? new Date()\n\n const projects = await em.find(StaffTimeProject, {\n tenantId: scope.tenantId,\n organizationId: scope.organizationId,\n deletedAt: null,\n })\n\n const totals = {\n total: projects.length,\n active: 0,\n onHold: 0,\n completed: 0,\n }\n for (const p of projects) {\n if (p.status === 'active') totals.active += 1\n else if (p.status === 'on_hold') totals.onHold += 1\n else if (p.status === 'completed') totals.completed += 1\n }\n\n const monthStart = getFirstDayOfMonthUtc(now)\n const nextMonthStart = getFirstDayOfNextMonthUtc(now)\n const prevMonthStart = getFirstDayOfMonthUtc(addUtcDays(monthStart, -1))\n const weekMonday = getMondayUtc(now)\n const nextMonday = addUtcDays(weekMonday, 7)\n const prevMonday = addUtcDays(weekMonday, -7)\n\n const hoursRows = (await em.getConnection().execute(\n `\n SELECT bucket, COALESCE(SUM(duration_minutes), 0)::bigint AS total_minutes\n FROM (\n SELECT\n CASE\n WHEN date >= ?::date AND date < ?::date THEN 'current'\n WHEN date >= ?::date AND date < ?::date THEN 'previous'\n END AS bucket,\n duration_minutes\n FROM staff_time_entries\n WHERE organization_id = ?\n AND tenant_id = ?\n AND deleted_at IS NULL\n AND date >= ?::date\n AND date < ?::date\n ) t\n WHERE bucket IS NOT NULL\n GROUP BY bucket\n `,\n [\n toDateOnlyString(monthStart),\n toDateOnlyString(nextMonthStart),\n toDateOnlyString(prevMonthStart),\n toDateOnlyString(monthStart),\n scope.organizationId,\n scope.tenantId,\n toDateOnlyString(prevMonthStart),\n toDateOnlyString(nextMonthStart),\n ],\n )) as WeekRow[]\n\n const weekRows = (await em.getConnection().execute(\n `\n SELECT bucket, COALESCE(SUM(duration_minutes), 0)::bigint AS total_minutes\n FROM (\n SELECT\n CASE\n WHEN date >= ?::date AND date < ?::date THEN 'current'\n WHEN date >= ?::date AND date < ?::date THEN 'previous'\n END AS bucket,\n duration_minutes\n FROM staff_time_entries\n WHERE organization_id = ?\n AND tenant_id = ?\n AND deleted_at IS NULL\n AND date >= ?::date\n AND date < ?::date\n ) t\n WHERE bucket IS NOT NULL\n GROUP BY bucket\n `,\n [\n toDateOnlyString(weekMonday),\n toDateOnlyString(nextMonday),\n toDateOnlyString(prevMonday),\n toDateOnlyString(weekMonday),\n scope.organizationId,\n scope.tenantId,\n toDateOnlyString(prevMonday),\n toDateOnlyString(nextMonday),\n ],\n )) as WeekRow[]\n\n let currentHours = 0\n let previousHours = 0\n for (const row of hoursRows) {\n const minutes = typeof row.total_minutes === 'string' ? Number(row.total_minutes) : row.total_minutes\n if (row.bucket === 'current') currentHours = minutesToHours(minutes)\n else previousHours = minutesToHours(minutes)\n }\n\n let weekCurrentHours = 0\n let weekPreviousHours = 0\n for (const row of weekRows) {\n const minutes = typeof row.total_minutes === 'string' ? Number(row.total_minutes) : row.total_minutes\n if (row.bucket === 'current') weekCurrentHours = minutesToHours(minutes)\n else weekPreviousHours = minutesToHours(minutes)\n }\n\n const teamRows = (await em.getConnection().execute(\n `\n SELECT COUNT(DISTINCT staff_member_id)::bigint AS count\n FROM staff_time_entries\n WHERE organization_id = ?\n AND tenant_id = ?\n AND deleted_at IS NULL\n AND date >= ?::date\n AND date < ?::date\n `,\n [\n scope.organizationId,\n scope.tenantId,\n toDateOnlyString(monthStart),\n toDateOnlyString(nextMonthStart),\n ],\n )) as Array<{ count: string | number }>\n const teamCount = teamRows.length ? Number(teamRows[0].count) : 0\n\n const assignedToMe = { total: 0, active: 0 }\n if (scope.callerStaffMemberId) {\n const memberships = await em.find(StaffTimeProjectMember, {\n staffMemberId: scope.callerStaffMemberId,\n tenantId: scope.tenantId,\n organizationId: scope.organizationId,\n status: 'active',\n deletedAt: null,\n })\n const assignedProjectIds = memberships.map((m) => m.timeProjectId)\n const assignedProjects = projects.filter((p) => assignedProjectIds.includes(p.id))\n assignedToMe.total = assignedProjects.length\n assignedToMe.active = assignedProjects.filter((p) => p.status === 'active').length\n }\n\n return {\n role: 'pm',\n totals,\n hoursWeek: {\n current: weekCurrentHours,\n previous: weekPreviousHours,\n deltaPct: deltaPct(weekCurrentHours, weekPreviousHours),\n },\n hoursMonth: {\n current: currentHours,\n previous: previousHours,\n deltaPct: deltaPct(currentHours, previousHours),\n },\n teamActive: { count: teamCount },\n assignedToMe,\n }\n}\n\nexport async function computeCollabProjectsKpis(\n scope: KpiScope & { staffMemberId: string },\n): Promise<ProjectKpisCollabResult> {\n const em = scope.em.fork()\n const now = scope.now ?? new Date()\n\n const memberships = await em.find(StaffTimeProjectMember, {\n staffMemberId: scope.staffMemberId,\n tenantId: scope.tenantId,\n organizationId: scope.organizationId,\n status: 'active',\n deletedAt: null,\n })\n\n const projectIds = memberships.map((m) => m.timeProjectId)\n const myProjects = { total: projectIds.length, active: 0 }\n\n if (projectIds.length > 0) {\n const projects = await em.find(StaffTimeProject, {\n id: { $in: projectIds },\n tenantId: scope.tenantId,\n organizationId: scope.organizationId,\n status: 'active',\n deletedAt: null,\n })\n myProjects.active = projects.length\n }\n\n const weekMonday = getMondayUtc(now)\n const nextMonday = addUtcDays(weekMonday, 7)\n const prevMonday = addUtcDays(weekMonday, -7)\n\n const monthStart = getFirstDayOfMonthUtc(now)\n const nextMonthStart = getFirstDayOfNextMonthUtc(now)\n const prevMonthStart = getFirstDayOfMonthUtc(addUtcDays(monthStart, -1))\n\n const rows = (await em.getConnection().execute(\n `\n SELECT bucket, COALESCE(SUM(duration_minutes), 0)::bigint AS total_minutes\n FROM (\n SELECT\n CASE\n WHEN date >= ?::date AND date < ?::date THEN 'week_current'\n WHEN date >= ?::date AND date < ?::date THEN 'week_previous'\n WHEN date >= ?::date AND date < ?::date THEN 'month_current'\n WHEN date >= ?::date AND date < ?::date THEN 'month_previous'\n END AS bucket,\n duration_minutes\n FROM staff_time_entries\n WHERE organization_id = ?\n AND tenant_id = ?\n AND staff_member_id = ?\n AND deleted_at IS NULL\n AND date >= LEAST(?::date, ?::date)\n AND date < GREATEST(?::date, ?::date)\n ) t\n WHERE bucket IS NOT NULL\n GROUP BY bucket\n `,\n [\n toDateOnlyString(weekMonday),\n toDateOnlyString(nextMonday),\n toDateOnlyString(prevMonday),\n toDateOnlyString(weekMonday),\n toDateOnlyString(monthStart),\n toDateOnlyString(nextMonthStart),\n toDateOnlyString(prevMonthStart),\n toDateOnlyString(monthStart),\n scope.organizationId,\n scope.tenantId,\n scope.staffMemberId,\n toDateOnlyString(prevMonday),\n toDateOnlyString(prevMonthStart),\n toDateOnlyString(nextMonday),\n toDateOnlyString(nextMonthStart),\n ],\n )) as Array<{ bucket: string; total_minutes: string | number }>\n\n let weekCurrent = 0\n let weekPrevious = 0\n let monthCurrent = 0\n let monthPrevious = 0\n for (const row of rows) {\n const minutes = typeof row.total_minutes === 'string' ? Number(row.total_minutes) : row.total_minutes\n const hours = minutesToHours(minutes)\n if (row.bucket === 'week_current') weekCurrent = hours\n else if (row.bucket === 'week_previous') weekPrevious = hours\n else if (row.bucket === 'month_current') monthCurrent = hours\n else if (row.bucket === 'month_previous') monthPrevious = hours\n }\n\n return {\n role: 'collab',\n myProjects,\n myHoursWeek: {\n current: weekCurrent,\n previous: weekPrevious,\n deltaPct: deltaPct(weekCurrent, weekPrevious),\n },\n myHoursMonth: {\n current: monthCurrent,\n previous: monthPrevious,\n deltaPct: deltaPct(monthCurrent, monthPrevious),\n },\n }\n}\n"],
|
|
5
|
+
"mappings": "AACA,SAAS,kBAAkB,8BAA8B;AACzD;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OACK;AACP,SAAS,UAAU,sBAAsB;AAiCzC,eAAsB,sBACpB,OAC8B;AAC9B,QAAM,KAAK,MAAM,GAAG,KAAK;AACzB,QAAM,MAAM,MAAM,OAAO,oBAAI,KAAK;AAElC,QAAM,WAAW,MAAM,GAAG,KAAK,kBAAkB;AAAA,IAC/C,UAAU,MAAM;AAAA,IAChB,gBAAgB,MAAM;AAAA,IACtB,WAAW;AAAA,EACb,CAAC;AAED,QAAM,SAAS;AAAA,IACb,OAAO,SAAS;AAAA,IAChB,QAAQ;AAAA,IACR,QAAQ;AAAA,IACR,WAAW;AAAA,EACb;AACA,aAAW,KAAK,UAAU;AACxB,QAAI,EAAE,WAAW,SAAU,QAAO,UAAU;AAAA,aACnC,EAAE,WAAW,UAAW,QAAO,UAAU;AAAA,aACzC,EAAE,WAAW,YAAa,QAAO,aAAa;AAAA,EACzD;AAEA,QAAM,aAAa,sBAAsB,GAAG;AAC5C,QAAM,iBAAiB,0BAA0B,GAAG;AACpD,QAAM,iBAAiB,sBAAsB,WAAW,YAAY,EAAE,CAAC;AACvE,QAAM,aAAa,aAAa,GAAG;AACnC,QAAM,aAAa,WAAW,YAAY,CAAC;AAC3C,QAAM,aAAa,WAAW,YAAY,EAAE;AAE5C,QAAM,YAAa,MAAM,GAAG,cAAc,EAAE;AAAA,IAC1C;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,IAmBA;AAAA,MACE,iBAAiB,UAAU;AAAA,MAC3B,iBAAiB,cAAc;AAAA,MAC/B,iBAAiB,cAAc;AAAA,MAC/B,iBAAiB,UAAU;AAAA,MAC3B,MAAM;AAAA,MACN,MAAM;AAAA,MACN,iBAAiB,cAAc;AAAA,MAC/B,iBAAiB,cAAc;AAAA,IACjC;AAAA,EACF;AAEA,QAAM,WAAY,MAAM,GAAG,cAAc,EAAE;AAAA,IACzC;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,IAmBA;AAAA,MACE,iBAAiB,UAAU;AAAA,MAC3B,iBAAiB,UAAU;AAAA,MAC3B,iBAAiB,UAAU;AAAA,MAC3B,iBAAiB,UAAU;AAAA,MAC3B,MAAM;AAAA,MACN,MAAM;AAAA,MACN,iBAAiB,UAAU;AAAA,MAC3B,iBAAiB,UAAU;AAAA,IAC7B;AAAA,EACF;AAEA,MAAI,eAAe;AACnB,MAAI,gBAAgB;AACpB,aAAW,OAAO,WAAW;AAC3B,UAAM,UAAU,OAAO,IAAI,kBAAkB,WAAW,OAAO,IAAI,aAAa,IAAI,IAAI;AACxF,QAAI,IAAI,WAAW,UAAW,gBAAe,eAAe,OAAO;AAAA,QAC9D,iBAAgB,eAAe,OAAO;AAAA,EAC7C;AAEA,MAAI,mBAAmB;AACvB,MAAI,oBAAoB;AACxB,aAAW,OAAO,UAAU;AAC1B,UAAM,UAAU,OAAO,IAAI,kBAAkB,WAAW,OAAO,IAAI,aAAa,IAAI,IAAI;AACxF,QAAI,IAAI,WAAW,UAAW,oBAAmB,eAAe,OAAO;AAAA,QAClE,qBAAoB,eAAe,OAAO;AAAA,EACjD;AAEA,QAAM,WAAY,MAAM,GAAG,cAAc,EAAE;AAAA,IACzC;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,IASA;AAAA,MACE,MAAM;AAAA,MACN,MAAM;AAAA,MACN,iBAAiB,UAAU;AAAA,MAC3B,iBAAiB,cAAc;AAAA,IACjC;AAAA,EACF;AACA,QAAM,YAAY,SAAS,SAAS,OAAO,SAAS,CAAC,EAAE,KAAK,IAAI;AAEhE,QAAM,eAAe,EAAE,OAAO,GAAG,QAAQ,EAAE;AAC3C,MAAI,MAAM,qBAAqB;AAC7B,UAAM,cAAc,MAAM,GAAG,KAAK,wBAAwB;AAAA,MACxD,eAAe,MAAM;AAAA,MACrB,UAAU,MAAM;AAAA,MAChB,gBAAgB,MAAM;AAAA,MACtB,QAAQ;AAAA,MACR,WAAW;AAAA,IACb,CAAC;AACD,UAAM,qBAAqB,YAAY,IAAI,CAAC,MAAM,EAAE,aAAa;AACjE,UAAM,mBAAmB,SAAS,OAAO,CAAC,MAAM,mBAAmB,SAAS,EAAE,EAAE,CAAC;AACjF,iBAAa,QAAQ,iBAAiB;AACtC,iBAAa,SAAS,iBAAiB,OAAO,CAAC,MAAM,EAAE,WAAW,QAAQ,EAAE;AAAA,EAC9E;AAEA,SAAO;AAAA,IACL,MAAM;AAAA,IACN;AAAA,IACA,WAAW;AAAA,MACT,SAAS;AAAA,MACT,UAAU;AAAA,MACV,UAAU,SAAS,kBAAkB,iBAAiB;AAAA,IACxD;AAAA,IACA,YAAY;AAAA,MACV,SAAS;AAAA,MACT,UAAU;AAAA,MACV,UAAU,SAAS,cAAc,aAAa;AAAA,IAChD;AAAA,IACA,YAAY,EAAE,OAAO,UAAU;AAAA,IAC/B;AAAA,EACF;AACF;AAEA,eAAsB,0BACpB,OACkC;AAClC,QAAM,KAAK,MAAM,GAAG,KAAK;AACzB,QAAM,MAAM,MAAM,OAAO,oBAAI,KAAK;AAElC,QAAM,cAAc,MAAM,GAAG,KAAK,wBAAwB;AAAA,IACxD,eAAe,MAAM;AAAA,IACrB,UAAU,MAAM;AAAA,IAChB,gBAAgB,MAAM;AAAA,IACtB,QAAQ;AAAA,IACR,WAAW;AAAA,EACb,CAAC;AAED,QAAM,aAAa,YAAY,IAAI,CAAC,MAAM,EAAE,aAAa;AACzD,QAAM,aAAa,EAAE,OAAO,WAAW,QAAQ,QAAQ,EAAE;AAEzD,MAAI,WAAW,SAAS,GAAG;AACzB,UAAM,WAAW,MAAM,GAAG,KAAK,kBAAkB;AAAA,MAC/C,IAAI,EAAE,KAAK,WAAW;AAAA,MACtB,UAAU,MAAM;AAAA,MAChB,gBAAgB,MAAM;AAAA,MACtB,QAAQ;AAAA,MACR,WAAW;AAAA,IACb,CAAC;AACD,eAAW,SAAS,SAAS;AAAA,EAC/B;AAEA,QAAM,aAAa,aAAa,GAAG;AACnC,QAAM,aAAa,WAAW,YAAY,CAAC;AAC3C,QAAM,aAAa,WAAW,YAAY,EAAE;AAE5C,QAAM,aAAa,sBAAsB,GAAG;AAC5C,QAAM,iBAAiB,0BAA0B,GAAG;AACpD,QAAM,iBAAiB,sBAAsB,WAAW,YAAY,EAAE,CAAC;AAEvE,QAAM,OAAQ,MAAM,GAAG,cAAc,EAAE;AAAA,IACrC;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,IAsBA;AAAA,MACE,iBAAiB,UAAU;AAAA,MAC3B,iBAAiB,UAAU;AAAA,MAC3B,iBAAiB,UAAU;AAAA,MAC3B,iBAAiB,UAAU;AAAA,MAC3B,iBAAiB,UAAU;AAAA,MAC3B,iBAAiB,cAAc;AAAA,MAC/B,iBAAiB,cAAc;AAAA,MAC/B,iBAAiB,UAAU;AAAA,MAC3B,MAAM;AAAA,MACN,MAAM;AAAA,MACN,MAAM;AAAA,MACN,iBAAiB,UAAU;AAAA,MAC3B,iBAAiB,cAAc;AAAA,MAC/B,iBAAiB,UAAU;AAAA,MAC3B,iBAAiB,cAAc;AAAA,IACjC;AAAA,EACF;AAEA,MAAI,cAAc;AAClB,MAAI,eAAe;AACnB,MAAI,eAAe;AACnB,MAAI,gBAAgB;AACpB,aAAW,OAAO,MAAM;AACtB,UAAM,UAAU,OAAO,IAAI,kBAAkB,WAAW,OAAO,IAAI,aAAa,IAAI,IAAI;AACxF,UAAM,QAAQ,eAAe,OAAO;AACpC,QAAI,IAAI,WAAW,eAAgB,eAAc;AAAA,aACxC,IAAI,WAAW,gBAAiB,gBAAe;AAAA,aAC/C,IAAI,WAAW,gBAAiB,gBAAe;AAAA,aAC/C,IAAI,WAAW,iBAAkB,iBAAgB;AAAA,EAC5D;AAEA,SAAO;AAAA,IACL,MAAM;AAAA,IACN;AAAA,IACA,aAAa;AAAA,MACX,SAAS;AAAA,MACT,UAAU;AAAA,MACV,UAAU,SAAS,aAAa,YAAY;AAAA,IAC9C;AAAA,IACA,cAAc;AAAA,MACZ,SAAS;AAAA,MACT,UAAU;AAAA,MACV,UAAU,SAAS,cAAc,aAAa;AAAA,IAChD;AAAA,EACF;AACF;",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
function getMondayUtc(input) {
|
|
2
|
+
const d = new Date(Date.UTC(input.getUTCFullYear(), input.getUTCMonth(), input.getUTCDate()));
|
|
3
|
+
const day = d.getUTCDay();
|
|
4
|
+
const diff = day === 0 ? -6 : 1 - day;
|
|
5
|
+
d.setUTCDate(d.getUTCDate() + diff);
|
|
6
|
+
return d;
|
|
7
|
+
}
|
|
8
|
+
function addUtcDays(input, days) {
|
|
9
|
+
const d = new Date(input);
|
|
10
|
+
d.setUTCDate(d.getUTCDate() + days);
|
|
11
|
+
return d;
|
|
12
|
+
}
|
|
13
|
+
function getFirstDayOfMonthUtc(input) {
|
|
14
|
+
return new Date(Date.UTC(input.getUTCFullYear(), input.getUTCMonth(), 1));
|
|
15
|
+
}
|
|
16
|
+
function getFirstDayOfNextMonthUtc(input) {
|
|
17
|
+
return new Date(Date.UTC(input.getUTCFullYear(), input.getUTCMonth() + 1, 1));
|
|
18
|
+
}
|
|
19
|
+
function getLastNWeekStarts(n, now = /* @__PURE__ */ new Date()) {
|
|
20
|
+
const currentMonday = getMondayUtc(now);
|
|
21
|
+
const starts = [];
|
|
22
|
+
for (let i = n - 1; i >= 0; i--) {
|
|
23
|
+
starts.push(addUtcDays(currentMonday, -i * 7));
|
|
24
|
+
}
|
|
25
|
+
return starts;
|
|
26
|
+
}
|
|
27
|
+
function toDateOnlyString(date) {
|
|
28
|
+
const y = date.getUTCFullYear();
|
|
29
|
+
const m = String(date.getUTCMonth() + 1).padStart(2, "0");
|
|
30
|
+
const d = String(date.getUTCDate()).padStart(2, "0");
|
|
31
|
+
return `${y}-${m}-${d}`;
|
|
32
|
+
}
|
|
33
|
+
export {
|
|
34
|
+
addUtcDays,
|
|
35
|
+
getFirstDayOfMonthUtc,
|
|
36
|
+
getFirstDayOfNextMonthUtc,
|
|
37
|
+
getLastNWeekStarts,
|
|
38
|
+
getMondayUtc,
|
|
39
|
+
toDateOnlyString
|
|
40
|
+
};
|
|
41
|
+
//# sourceMappingURL=dateBuckets.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../../../../src/modules/staff/lib/timesheets-projects/dateBuckets.ts"],
|
|
4
|
+
"sourcesContent": ["export function getMondayUtc(input: Date): Date {\n const d = new Date(Date.UTC(input.getUTCFullYear(), input.getUTCMonth(), input.getUTCDate()))\n const day = d.getUTCDay()\n const diff = day === 0 ? -6 : 1 - day\n d.setUTCDate(d.getUTCDate() + diff)\n return d\n}\n\nexport function addUtcDays(input: Date, days: number): Date {\n const d = new Date(input)\n d.setUTCDate(d.getUTCDate() + days)\n return d\n}\n\nexport function getFirstDayOfMonthUtc(input: Date): Date {\n return new Date(Date.UTC(input.getUTCFullYear(), input.getUTCMonth(), 1))\n}\n\nexport function getFirstDayOfNextMonthUtc(input: Date): Date {\n return new Date(Date.UTC(input.getUTCFullYear(), input.getUTCMonth() + 1, 1))\n}\n\nexport function getLastNWeekStarts(n: number, now: Date = new Date()): Date[] {\n const currentMonday = getMondayUtc(now)\n const starts: Date[] = []\n for (let i = n - 1; i >= 0; i--) {\n starts.push(addUtcDays(currentMonday, -i * 7))\n }\n return starts\n}\n\nexport function toDateOnlyString(date: Date): string {\n const y = date.getUTCFullYear()\n const m = String(date.getUTCMonth() + 1).padStart(2, '0')\n const d = String(date.getUTCDate()).padStart(2, '0')\n return `${y}-${m}-${d}`\n}\n"],
|
|
5
|
+
"mappings": "AAAO,SAAS,aAAa,OAAmB;AAC9C,QAAM,IAAI,IAAI,KAAK,KAAK,IAAI,MAAM,eAAe,GAAG,MAAM,YAAY,GAAG,MAAM,WAAW,CAAC,CAAC;AAC5F,QAAM,MAAM,EAAE,UAAU;AACxB,QAAM,OAAO,QAAQ,IAAI,KAAK,IAAI;AAClC,IAAE,WAAW,EAAE,WAAW,IAAI,IAAI;AAClC,SAAO;AACT;AAEO,SAAS,WAAW,OAAa,MAAoB;AAC1D,QAAM,IAAI,IAAI,KAAK,KAAK;AACxB,IAAE,WAAW,EAAE,WAAW,IAAI,IAAI;AAClC,SAAO;AACT;AAEO,SAAS,sBAAsB,OAAmB;AACvD,SAAO,IAAI,KAAK,KAAK,IAAI,MAAM,eAAe,GAAG,MAAM,YAAY,GAAG,CAAC,CAAC;AAC1E;AAEO,SAAS,0BAA0B,OAAmB;AAC3D,SAAO,IAAI,KAAK,KAAK,IAAI,MAAM,eAAe,GAAG,MAAM,YAAY,IAAI,GAAG,CAAC,CAAC;AAC9E;AAEO,SAAS,mBAAmB,GAAW,MAAY,oBAAI,KAAK,GAAW;AAC5E,QAAM,gBAAgB,aAAa,GAAG;AACtC,QAAM,SAAiB,CAAC;AACxB,WAAS,IAAI,IAAI,GAAG,KAAK,GAAG,KAAK;AAC/B,WAAO,KAAK,WAAW,eAAe,CAAC,IAAI,CAAC,CAAC;AAAA,EAC/C;AACA,SAAO;AACT;AAEO,SAAS,iBAAiB,MAAoB;AACnD,QAAM,IAAI,KAAK,eAAe;AAC9B,QAAM,IAAI,OAAO,KAAK,YAAY,IAAI,CAAC,EAAE,SAAS,GAAG,GAAG;AACxD,QAAM,IAAI,OAAO,KAAK,WAAW,CAAC,EAAE,SAAS,GAAG,GAAG;AACnD,SAAO,GAAG,CAAC,IAAI,CAAC,IAAI,CAAC;AACvB;",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
function computeInitials(displayName) {
|
|
2
|
+
const parts = displayName.trim().split(/\s+/).filter(Boolean);
|
|
3
|
+
if (parts.length === 0) return "?";
|
|
4
|
+
if (parts.length === 1) return parts[0].slice(0, 2).toUpperCase();
|
|
5
|
+
return `${parts[0][0]}${parts[parts.length - 1][0]}`.toUpperCase();
|
|
6
|
+
}
|
|
7
|
+
export {
|
|
8
|
+
computeInitials
|
|
9
|
+
};
|
|
10
|
+
//# sourceMappingURL=initials.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../../../../src/modules/staff/lib/timesheets-projects/initials.ts"],
|
|
4
|
+
"sourcesContent": ["export function computeInitials(displayName: string): string {\n const parts = displayName.trim().split(/\\s+/).filter(Boolean)\n if (parts.length === 0) return '?'\n if (parts.length === 1) return parts[0].slice(0, 2).toUpperCase()\n return `${parts[0][0]}${parts[parts.length - 1][0]}`.toUpperCase()\n}\n"],
|
|
5
|
+
"mappings": "AAAO,SAAS,gBAAgB,aAA6B;AAC3D,QAAM,QAAQ,YAAY,KAAK,EAAE,MAAM,KAAK,EAAE,OAAO,OAAO;AAC5D,MAAI,MAAM,WAAW,EAAG,QAAO;AAC/B,MAAI,MAAM,WAAW,EAAG,QAAO,MAAM,CAAC,EAAE,MAAM,GAAG,CAAC,EAAE,YAAY;AAChE,SAAO,GAAG,MAAM,CAAC,EAAE,CAAC,CAAC,GAAG,MAAM,MAAM,SAAS,CAAC,EAAE,CAAC,CAAC,GAAG,YAAY;AACnE;",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
function deltaPct(current, previous) {
|
|
2
|
+
if (previous <= 0) return null;
|
|
3
|
+
return Math.round((current - previous) / previous * 100 * 10) / 10;
|
|
4
|
+
}
|
|
5
|
+
function minutesToHours(minutes) {
|
|
6
|
+
return Math.round(minutes / 60 * 10) / 10;
|
|
7
|
+
}
|
|
8
|
+
export {
|
|
9
|
+
deltaPct,
|
|
10
|
+
minutesToHours
|
|
11
|
+
};
|
|
12
|
+
//# sourceMappingURL=kpiMath.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../../../../src/modules/staff/lib/timesheets-projects/kpiMath.ts"],
|
|
4
|
+
"sourcesContent": ["export function deltaPct(current: number, previous: number): number | null {\n if (previous <= 0) return null\n return Math.round(((current - previous) / previous) * 100 * 10) / 10\n}\n\nexport function minutesToHours(minutes: number): number {\n return Math.round((minutes / 60) * 10) / 10\n}\n"],
|
|
5
|
+
"mappings": "AAAO,SAAS,SAAS,SAAiB,UAAiC;AACzE,MAAI,YAAY,EAAG,QAAO;AAC1B,SAAO,KAAK,OAAQ,UAAU,YAAY,WAAY,MAAM,EAAE,IAAI;AACpE;AAEO,SAAS,eAAe,SAAyB;AACtD,SAAO,KAAK,MAAO,UAAU,KAAM,EAAE,IAAI;AAC3C;",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import { StaffTeamMember, StaffTimeProjectMember } from "../../data/entities.js";
|
|
2
|
+
import { computeInitials } from "./initials.js";
|
|
3
|
+
const DEFAULT_MAX = 4;
|
|
4
|
+
async function listProjectMembersPreview(scope) {
|
|
5
|
+
const maxPerProject = scope.maxPerProject ?? DEFAULT_MAX;
|
|
6
|
+
const result = /* @__PURE__ */ new Map();
|
|
7
|
+
for (const id of scope.projectIds) {
|
|
8
|
+
result.set(id, { total: 0, preview: [], myRole: null });
|
|
9
|
+
}
|
|
10
|
+
if (scope.projectIds.length === 0) return result;
|
|
11
|
+
const em = scope.em.fork();
|
|
12
|
+
const memberships = await em.find(
|
|
13
|
+
StaffTimeProjectMember,
|
|
14
|
+
{
|
|
15
|
+
timeProjectId: { $in: scope.projectIds },
|
|
16
|
+
tenantId: scope.tenantId,
|
|
17
|
+
organizationId: scope.organizationId,
|
|
18
|
+
status: "active",
|
|
19
|
+
deletedAt: null
|
|
20
|
+
},
|
|
21
|
+
{ orderBy: { createdAt: "asc" } }
|
|
22
|
+
);
|
|
23
|
+
const staffMemberIds = Array.from(new Set(memberships.map((m) => m.staffMemberId)));
|
|
24
|
+
if (staffMemberIds.length === 0) return result;
|
|
25
|
+
const teamMembers = await em.find(StaffTeamMember, {
|
|
26
|
+
id: { $in: staffMemberIds },
|
|
27
|
+
tenantId: scope.tenantId,
|
|
28
|
+
organizationId: scope.organizationId,
|
|
29
|
+
deletedAt: null
|
|
30
|
+
});
|
|
31
|
+
const teamById = new Map(teamMembers.map((tm) => [tm.id, tm]));
|
|
32
|
+
for (const membership of memberships) {
|
|
33
|
+
const bucket = result.get(membership.timeProjectId);
|
|
34
|
+
if (!bucket) continue;
|
|
35
|
+
bucket.total += 1;
|
|
36
|
+
if (scope.callerStaffMemberId && membership.staffMemberId === scope.callerStaffMemberId) {
|
|
37
|
+
bucket.myRole = membership.role ?? null;
|
|
38
|
+
}
|
|
39
|
+
if (bucket.preview.length >= maxPerProject) continue;
|
|
40
|
+
const team = teamById.get(membership.staffMemberId);
|
|
41
|
+
if (!team) continue;
|
|
42
|
+
bucket.preview.push({
|
|
43
|
+
id: team.id,
|
|
44
|
+
name: team.displayName,
|
|
45
|
+
initials: computeInitials(team.displayName),
|
|
46
|
+
avatarUrl: null
|
|
47
|
+
});
|
|
48
|
+
}
|
|
49
|
+
return result;
|
|
50
|
+
}
|
|
51
|
+
export {
|
|
52
|
+
computeInitials,
|
|
53
|
+
listProjectMembersPreview
|
|
54
|
+
};
|
|
55
|
+
//# sourceMappingURL=listProjectMembersPreview.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../../../../src/modules/staff/lib/timesheets-projects/listProjectMembersPreview.ts"],
|
|
4
|
+
"sourcesContent": ["import type { EntityManager } from '@mikro-orm/postgresql'\nimport { StaffTeamMember, StaffTimeProjectMember } from '../../data/entities'\nimport { computeInitials } from './initials'\n\nexport { computeInitials }\n\nexport type MembersPreviewScope = {\n em: EntityManager\n organizationId: string\n tenantId: string\n projectIds: string[]\n maxPerProject?: number\n}\n\nexport type MemberPreview = {\n id: string\n name: string\n initials: string\n avatarUrl: string | null\n}\n\nexport type ProjectMembersPreview = {\n total: number\n preview: MemberPreview[]\n myRole: string | null\n}\n\nconst DEFAULT_MAX = 4\n\nexport async function listProjectMembersPreview(\n scope: MembersPreviewScope & { callerStaffMemberId?: string | null },\n): Promise<Map<string, ProjectMembersPreview>> {\n const maxPerProject = scope.maxPerProject ?? DEFAULT_MAX\n const result = new Map<string, ProjectMembersPreview>()\n for (const id of scope.projectIds) {\n result.set(id, { total: 0, preview: [], myRole: null })\n }\n if (scope.projectIds.length === 0) return result\n\n const em = scope.em.fork()\n const memberships = await em.find(\n StaffTimeProjectMember,\n {\n timeProjectId: { $in: scope.projectIds },\n tenantId: scope.tenantId,\n organizationId: scope.organizationId,\n status: 'active',\n deletedAt: null,\n },\n { orderBy: { createdAt: 'asc' } },\n )\n\n const staffMemberIds = Array.from(new Set(memberships.map((m) => m.staffMemberId)))\n if (staffMemberIds.length === 0) return result\n\n const teamMembers = await em.find(StaffTeamMember, {\n id: { $in: staffMemberIds },\n tenantId: scope.tenantId,\n organizationId: scope.organizationId,\n deletedAt: null,\n })\n const teamById = new Map(teamMembers.map((tm) => [tm.id, tm]))\n\n for (const membership of memberships) {\n const bucket = result.get(membership.timeProjectId)\n if (!bucket) continue\n bucket.total += 1\n if (scope.callerStaffMemberId && membership.staffMemberId === scope.callerStaffMemberId) {\n bucket.myRole = membership.role ?? null\n }\n if (bucket.preview.length >= maxPerProject) continue\n const team = teamById.get(membership.staffMemberId)\n if (!team) continue\n bucket.preview.push({\n id: team.id,\n name: team.displayName,\n initials: computeInitials(team.displayName),\n avatarUrl: null,\n })\n }\n\n return result\n}\n"],
|
|
5
|
+
"mappings": "AACA,SAAS,iBAAiB,8BAA8B;AACxD,SAAS,uBAAuB;AAyBhC,MAAM,cAAc;AAEpB,eAAsB,0BACpB,OAC6C;AAC7C,QAAM,gBAAgB,MAAM,iBAAiB;AAC7C,QAAM,SAAS,oBAAI,IAAmC;AACtD,aAAW,MAAM,MAAM,YAAY;AACjC,WAAO,IAAI,IAAI,EAAE,OAAO,GAAG,SAAS,CAAC,GAAG,QAAQ,KAAK,CAAC;AAAA,EACxD;AACA,MAAI,MAAM,WAAW,WAAW,EAAG,QAAO;AAE1C,QAAM,KAAK,MAAM,GAAG,KAAK;AACzB,QAAM,cAAc,MAAM,GAAG;AAAA,IAC3B;AAAA,IACA;AAAA,MACE,eAAe,EAAE,KAAK,MAAM,WAAW;AAAA,MACvC,UAAU,MAAM;AAAA,MAChB,gBAAgB,MAAM;AAAA,MACtB,QAAQ;AAAA,MACR,WAAW;AAAA,IACb;AAAA,IACA,EAAE,SAAS,EAAE,WAAW,MAAM,EAAE;AAAA,EAClC;AAEA,QAAM,iBAAiB,MAAM,KAAK,IAAI,IAAI,YAAY,IAAI,CAAC,MAAM,EAAE,aAAa,CAAC,CAAC;AAClF,MAAI,eAAe,WAAW,EAAG,QAAO;AAExC,QAAM,cAAc,MAAM,GAAG,KAAK,iBAAiB;AAAA,IACjD,IAAI,EAAE,KAAK,eAAe;AAAA,IAC1B,UAAU,MAAM;AAAA,IAChB,gBAAgB,MAAM;AAAA,IACtB,WAAW;AAAA,EACb,CAAC;AACD,QAAM,WAAW,IAAI,IAAI,YAAY,IAAI,CAAC,OAAO,CAAC,GAAG,IAAI,EAAE,CAAC,CAAC;AAE7D,aAAW,cAAc,aAAa;AACpC,UAAM,SAAS,OAAO,IAAI,WAAW,aAAa;AAClD,QAAI,CAAC,OAAQ;AACb,WAAO,SAAS;AAChB,QAAI,MAAM,uBAAuB,WAAW,kBAAkB,MAAM,qBAAqB;AACvF,aAAO,SAAS,WAAW,QAAQ;AAAA,IACrC;AACA,QAAI,OAAO,QAAQ,UAAU,cAAe;AAC5C,UAAM,OAAO,SAAS,IAAI,WAAW,aAAa;AAClD,QAAI,CAAC,KAAM;AACX,WAAO,QAAQ,KAAK;AAAA,MAClB,IAAI,KAAK;AAAA,MACT,MAAM,KAAK;AAAA,MACX,UAAU,gBAAgB,KAAK,WAAW;AAAA,MAC1C,WAAW;AAAA,IACb,CAAC;AAAA,EACH;AAEA,SAAO;AACT;",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
3
|
+
const DEFAULT_COLOR = "#6366f1";
|
|
4
|
+
function HoursSparkline({
|
|
5
|
+
values,
|
|
6
|
+
color = DEFAULT_COLOR,
|
|
7
|
+
width = 54,
|
|
8
|
+
height = 14,
|
|
9
|
+
ariaLabel,
|
|
10
|
+
className
|
|
11
|
+
}) {
|
|
12
|
+
const nonEmpty = values.length > 0;
|
|
13
|
+
const max = nonEmpty ? Math.max(...values, 0) : 0;
|
|
14
|
+
const allZero = !nonEmpty || max === 0;
|
|
15
|
+
if (allZero) {
|
|
16
|
+
return /* @__PURE__ */ jsx(
|
|
17
|
+
"svg",
|
|
18
|
+
{
|
|
19
|
+
role: "img",
|
|
20
|
+
"aria-label": ariaLabel,
|
|
21
|
+
width,
|
|
22
|
+
height,
|
|
23
|
+
viewBox: `0 0 ${width} ${height}`,
|
|
24
|
+
className,
|
|
25
|
+
children: /* @__PURE__ */ jsx(
|
|
26
|
+
"line",
|
|
27
|
+
{
|
|
28
|
+
x1: 0,
|
|
29
|
+
x2: width,
|
|
30
|
+
y1: height / 2,
|
|
31
|
+
y2: height / 2,
|
|
32
|
+
className: "stroke-border",
|
|
33
|
+
strokeWidth: 1
|
|
34
|
+
}
|
|
35
|
+
)
|
|
36
|
+
}
|
|
37
|
+
);
|
|
38
|
+
}
|
|
39
|
+
const stepX = values.length === 1 ? 0 : width / (values.length - 1);
|
|
40
|
+
const points = values.map((value, idx) => {
|
|
41
|
+
const x = values.length === 1 ? width / 2 : idx * stepX;
|
|
42
|
+
const y = height - value / max * (height - 2) - 1;
|
|
43
|
+
return { x, y };
|
|
44
|
+
});
|
|
45
|
+
const linePath = points.map((p, idx) => idx === 0 ? `M${p.x},${p.y}` : `L${p.x},${p.y}`).join(" ");
|
|
46
|
+
const areaPath = `${linePath} L${points[points.length - 1].x},${height} L${points[0].x},${height} Z`;
|
|
47
|
+
return /* @__PURE__ */ jsxs(
|
|
48
|
+
"svg",
|
|
49
|
+
{
|
|
50
|
+
role: "img",
|
|
51
|
+
"aria-label": ariaLabel,
|
|
52
|
+
width,
|
|
53
|
+
height,
|
|
54
|
+
viewBox: `0 0 ${width} ${height}`,
|
|
55
|
+
className,
|
|
56
|
+
children: [
|
|
57
|
+
/* @__PURE__ */ jsx("path", { d: areaPath, fill: color, opacity: 0.15 }),
|
|
58
|
+
/* @__PURE__ */ jsx("path", { d: linePath, fill: "none", stroke: color, strokeWidth: 1.25, strokeLinecap: "round", strokeLinejoin: "round" })
|
|
59
|
+
]
|
|
60
|
+
}
|
|
61
|
+
);
|
|
62
|
+
}
|
|
63
|
+
export {
|
|
64
|
+
HoursSparkline
|
|
65
|
+
};
|
|
66
|
+
//# sourceMappingURL=HoursSparkline.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../../../../src/modules/staff/lib/timesheets-projects-ui/HoursSparkline.tsx"],
|
|
4
|
+
"sourcesContent": ["\"use client\"\n\nimport * as React from 'react'\n\nexport type HoursSparklineProps = {\n values: number[]\n color?: string\n width?: number\n height?: number\n ariaLabel: string\n className?: string\n}\n\nconst DEFAULT_COLOR = '#6366f1'\n\nexport function HoursSparkline({\n values,\n color = DEFAULT_COLOR,\n width = 54,\n height = 14,\n ariaLabel,\n className,\n}: HoursSparklineProps) {\n const nonEmpty = values.length > 0\n const max = nonEmpty ? Math.max(...values, 0) : 0\n const allZero = !nonEmpty || max === 0\n\n if (allZero) {\n return (\n <svg\n role=\"img\"\n aria-label={ariaLabel}\n width={width}\n height={height}\n viewBox={`0 0 ${width} ${height}`}\n className={className}\n >\n <line\n x1={0}\n x2={width}\n y1={height / 2}\n y2={height / 2}\n className=\"stroke-border\"\n strokeWidth={1}\n />\n </svg>\n )\n }\n\n const stepX = values.length === 1 ? 0 : width / (values.length - 1)\n const points = values.map((value, idx) => {\n const x = values.length === 1 ? width / 2 : idx * stepX\n const y = height - (value / max) * (height - 2) - 1\n return { x, y }\n })\n\n const linePath = points\n .map((p, idx) => (idx === 0 ? `M${p.x},${p.y}` : `L${p.x},${p.y}`))\n .join(' ')\n const areaPath = `${linePath} L${points[points.length - 1].x},${height} L${points[0].x},${height} Z`\n\n return (\n <svg\n role=\"img\"\n aria-label={ariaLabel}\n width={width}\n height={height}\n viewBox={`0 0 ${width} ${height}`}\n className={className}\n >\n <path d={areaPath} fill={color} opacity={0.15} />\n <path d={linePath} fill=\"none\" stroke={color} strokeWidth={1.25} strokeLinecap=\"round\" strokeLinejoin=\"round\" />\n </svg>\n )\n}\n"],
|
|
5
|
+
"mappings": ";AAqCQ,cAyBJ,YAzBI;AAxBR,MAAM,gBAAgB;AAEf,SAAS,eAAe;AAAA,EAC7B;AAAA,EACA,QAAQ;AAAA,EACR,QAAQ;AAAA,EACR,SAAS;AAAA,EACT;AAAA,EACA;AACF,GAAwB;AACtB,QAAM,WAAW,OAAO,SAAS;AACjC,QAAM,MAAM,WAAW,KAAK,IAAI,GAAG,QAAQ,CAAC,IAAI;AAChD,QAAM,UAAU,CAAC,YAAY,QAAQ;AAErC,MAAI,SAAS;AACX,WACE;AAAA,MAAC;AAAA;AAAA,QACC,MAAK;AAAA,QACL,cAAY;AAAA,QACZ;AAAA,QACA;AAAA,QACA,SAAS,OAAO,KAAK,IAAI,MAAM;AAAA,QAC/B;AAAA,QAEA;AAAA,UAAC;AAAA;AAAA,YACC,IAAI;AAAA,YACJ,IAAI;AAAA,YACJ,IAAI,SAAS;AAAA,YACb,IAAI,SAAS;AAAA,YACb,WAAU;AAAA,YACV,aAAa;AAAA;AAAA,QACf;AAAA;AAAA,IACF;AAAA,EAEJ;AAEA,QAAM,QAAQ,OAAO,WAAW,IAAI,IAAI,SAAS,OAAO,SAAS;AACjE,QAAM,SAAS,OAAO,IAAI,CAAC,OAAO,QAAQ;AACxC,UAAM,IAAI,OAAO,WAAW,IAAI,QAAQ,IAAI,MAAM;AAClD,UAAM,IAAI,SAAU,QAAQ,OAAQ,SAAS,KAAK;AAClD,WAAO,EAAE,GAAG,EAAE;AAAA,EAChB,CAAC;AAED,QAAM,WAAW,OACd,IAAI,CAAC,GAAG,QAAS,QAAQ,IAAI,IAAI,EAAE,CAAC,IAAI,EAAE,CAAC,KAAK,IAAI,EAAE,CAAC,IAAI,EAAE,CAAC,EAAG,EACjE,KAAK,GAAG;AACX,QAAM,WAAW,GAAG,QAAQ,KAAK,OAAO,OAAO,SAAS,CAAC,EAAE,CAAC,IAAI,MAAM,KAAK,OAAO,CAAC,EAAE,CAAC,IAAI,MAAM;AAEhG,SACE;AAAA,IAAC;AAAA;AAAA,MACC,MAAK;AAAA,MACL,cAAY;AAAA,MACZ;AAAA,MACA;AAAA,MACA,SAAS,OAAO,KAAK,IAAI,MAAM;AAAA,MAC/B;AAAA,MAEA;AAAA,4BAAC,UAAK,GAAG,UAAU,MAAM,OAAO,SAAS,MAAM;AAAA,QAC/C,oBAAC,UAAK,GAAG,UAAU,MAAK,QAAO,QAAQ,OAAO,aAAa,MAAM,eAAc,SAAQ,gBAAe,SAAQ;AAAA;AAAA;AAAA,EAChH;AAEJ;",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
3
|
+
import Link from "next/link";
|
|
4
|
+
import { ProjectColorDot } from "../timesheets-ui/ProjectColorDot.js";
|
|
5
|
+
import { resolveProjectColorHex } from "../timesheets-ui/colors.js";
|
|
6
|
+
import { HoursSparkline } from "./HoursSparkline.js";
|
|
7
|
+
import { ProjectMembersAvatarStack } from "./ProjectMembersAvatarStack.js";
|
|
8
|
+
const STATUS_BADGE_CLASSES = {
|
|
9
|
+
active: "bg-lime-100 text-lime-800 dark:bg-lime-900/30 dark:text-lime-300",
|
|
10
|
+
on_hold: "bg-amber-100 text-amber-800 dark:bg-amber-900/30 dark:text-amber-300",
|
|
11
|
+
completed: "bg-muted text-muted-foreground"
|
|
12
|
+
};
|
|
13
|
+
function ProjectCard({ data, labels, showTeam, href }) {
|
|
14
|
+
const badgeClass = STATUS_BADGE_CLASSES[data.status] ?? "bg-muted text-muted-foreground";
|
|
15
|
+
const statusLabel = labels.statuses[data.status] ?? data.status;
|
|
16
|
+
const stripeColor = resolveProjectColorHex(data.color, data.name);
|
|
17
|
+
const hoursPanelLabel = showTeam ? labels.hoursPanelPm : labels.hoursPanelCollab;
|
|
18
|
+
return /* @__PURE__ */ jsxs(
|
|
19
|
+
Link,
|
|
20
|
+
{
|
|
21
|
+
href,
|
|
22
|
+
className: "group relative flex flex-col overflow-hidden rounded-lg border border-border bg-card transition-colors hover:border-foreground/30",
|
|
23
|
+
children: [
|
|
24
|
+
/* @__PURE__ */ jsx("div", { className: "h-[3px] w-full", style: { backgroundColor: stripeColor }, "aria-hidden": "true" }),
|
|
25
|
+
/* @__PURE__ */ jsxs("div", { className: "flex flex-1 flex-col gap-3 p-4", children: [
|
|
26
|
+
/* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between gap-2", children: [
|
|
27
|
+
/* @__PURE__ */ jsx(
|
|
28
|
+
"span",
|
|
29
|
+
{
|
|
30
|
+
className: `inline-flex items-center rounded-full px-2 py-0.5 text-[11px] font-medium ${badgeClass}`,
|
|
31
|
+
children: statusLabel
|
|
32
|
+
}
|
|
33
|
+
),
|
|
34
|
+
/* @__PURE__ */ jsx(ProjectColorDot, { colorKey: data.color, projectName: data.name, size: "sm" })
|
|
35
|
+
] }),
|
|
36
|
+
/* @__PURE__ */ jsxs("div", { className: "flex flex-col gap-0.5", children: [
|
|
37
|
+
/* @__PURE__ */ jsx("h3", { className: "truncate text-sm font-semibold text-foreground", title: data.name, children: data.name }),
|
|
38
|
+
/* @__PURE__ */ jsxs("p", { className: "truncate font-mono text-[11px] text-muted-foreground", children: [
|
|
39
|
+
data.code ?? "\u2014",
|
|
40
|
+
data.customerName ? ` \xB7 ${data.customerName}` : ""
|
|
41
|
+
] })
|
|
42
|
+
] }),
|
|
43
|
+
/* @__PURE__ */ jsxs("div", { className: "rounded-md border border-border/50 bg-muted/40 p-3", children: [
|
|
44
|
+
/* @__PURE__ */ jsx("p", { className: "text-[10px] uppercase tracking-wide text-muted-foreground", children: hoursPanelLabel }),
|
|
45
|
+
/* @__PURE__ */ jsxs("div", { className: "mt-1 flex items-end justify-between gap-3", children: [
|
|
46
|
+
/* @__PURE__ */ jsx("p", { className: "text-xl font-semibold tabular-nums text-foreground", children: data.hoursWeek > 0 ? `${data.hoursWeek}h` : "\u2014" }),
|
|
47
|
+
/* @__PURE__ */ jsx(
|
|
48
|
+
HoursSparkline,
|
|
49
|
+
{
|
|
50
|
+
values: data.hoursTrend,
|
|
51
|
+
color: stripeColor,
|
|
52
|
+
width: 80,
|
|
53
|
+
height: 26,
|
|
54
|
+
ariaLabel: labels.sparklineAria
|
|
55
|
+
}
|
|
56
|
+
)
|
|
57
|
+
] })
|
|
58
|
+
] }),
|
|
59
|
+
/* @__PURE__ */ jsx("div", { className: "mt-auto flex items-center justify-between pt-1", children: showTeam ? /* @__PURE__ */ jsx(
|
|
60
|
+
ProjectMembersAvatarStack,
|
|
61
|
+
{
|
|
62
|
+
members: data.members,
|
|
63
|
+
total: data.memberCount,
|
|
64
|
+
peopleCountLabel: labels.peopleCount(data.memberCount)
|
|
65
|
+
}
|
|
66
|
+
) : /* @__PURE__ */ jsxs("p", { className: "text-xs text-muted-foreground", children: [
|
|
67
|
+
/* @__PURE__ */ jsxs("span", { className: "text-muted-foreground/70", children: [
|
|
68
|
+
labels.role,
|
|
69
|
+
": "
|
|
70
|
+
] }),
|
|
71
|
+
data.myRole ?? "\u2014"
|
|
72
|
+
] }) })
|
|
73
|
+
] })
|
|
74
|
+
]
|
|
75
|
+
}
|
|
76
|
+
);
|
|
77
|
+
}
|
|
78
|
+
export {
|
|
79
|
+
ProjectCard
|
|
80
|
+
};
|
|
81
|
+
//# sourceMappingURL=ProjectCard.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../../../../src/modules/staff/lib/timesheets-projects-ui/ProjectCard.tsx"],
|
|
4
|
+
"sourcesContent": ["\"use client\"\n\nimport * as React from 'react'\nimport Link from 'next/link'\nimport { ProjectColorDot } from '../timesheets-ui/ProjectColorDot'\nimport { resolveProjectColorHex } from '../timesheets-ui/colors'\nimport { HoursSparkline } from './HoursSparkline'\nimport { ProjectMembersAvatarStack, type AvatarMember } from './ProjectMembersAvatarStack'\n\nexport type ProjectCardData = {\n id: string\n name: string\n code: string | null\n customerName: string | null\n color: string | null\n status: string\n hoursWeek: number\n hoursTrend: number[]\n members: AvatarMember[]\n memberCount: number\n myRole: string | null\n updatedAt: string | null\n}\n\nexport type ProjectCardLabels = {\n hoursPanelPm: string\n hoursPanelCollab: string\n sparklineAria: string\n peopleCount: (count: number) => string\n role: string\n noCustomer: string\n statuses: Record<string, string>\n}\n\nexport type ProjectCardProps = {\n data: ProjectCardData\n labels: ProjectCardLabels\n showTeam: boolean\n href: string\n}\n\nconst STATUS_BADGE_CLASSES: Record<string, string> = {\n active: 'bg-lime-100 text-lime-800 dark:bg-lime-900/30 dark:text-lime-300',\n on_hold: 'bg-amber-100 text-amber-800 dark:bg-amber-900/30 dark:text-amber-300',\n completed: 'bg-muted text-muted-foreground',\n}\n\nexport function ProjectCard({ data, labels, showTeam, href }: ProjectCardProps) {\n const badgeClass = STATUS_BADGE_CLASSES[data.status] ?? 'bg-muted text-muted-foreground'\n const statusLabel = labels.statuses[data.status] ?? data.status\n const stripeColor = resolveProjectColorHex(data.color, data.name)\n const hoursPanelLabel = showTeam ? labels.hoursPanelPm : labels.hoursPanelCollab\n\n return (\n <Link\n href={href}\n className=\"group relative flex flex-col overflow-hidden rounded-lg border border-border bg-card transition-colors hover:border-foreground/30\"\n >\n <div className=\"h-[3px] w-full\" style={{ backgroundColor: stripeColor }} aria-hidden=\"true\" />\n <div className=\"flex flex-1 flex-col gap-3 p-4\">\n <div className=\"flex items-center justify-between gap-2\">\n <span\n className={`inline-flex items-center rounded-full px-2 py-0.5 text-[11px] font-medium ${badgeClass}`}\n >\n {statusLabel}\n </span>\n <ProjectColorDot colorKey={data.color} projectName={data.name} size=\"sm\" />\n </div>\n <div className=\"flex flex-col gap-0.5\">\n <h3 className=\"truncate text-sm font-semibold text-foreground\" title={data.name}>\n {data.name}\n </h3>\n <p className=\"truncate font-mono text-[11px] text-muted-foreground\">\n {data.code ?? '\u2014'}\n {data.customerName ? ` \u00B7 ${data.customerName}` : ''}\n </p>\n </div>\n <div className=\"rounded-md border border-border/50 bg-muted/40 p-3\">\n <p className=\"text-[10px] uppercase tracking-wide text-muted-foreground\">{hoursPanelLabel}</p>\n <div className=\"mt-1 flex items-end justify-between gap-3\">\n <p className=\"text-xl font-semibold tabular-nums text-foreground\">\n {data.hoursWeek > 0 ? `${data.hoursWeek}h` : '\u2014'}\n </p>\n <HoursSparkline\n values={data.hoursTrend}\n color={stripeColor}\n width={80}\n height={26}\n ariaLabel={labels.sparklineAria}\n />\n </div>\n </div>\n <div className=\"mt-auto flex items-center justify-between pt-1\">\n {showTeam ? (\n <ProjectMembersAvatarStack\n members={data.members}\n total={data.memberCount}\n peopleCountLabel={labels.peopleCount(data.memberCount)}\n />\n ) : (\n <p className=\"text-xs text-muted-foreground\">\n <span className=\"text-muted-foreground/70\">{labels.role}: </span>\n {data.myRole ?? '\u2014'}\n </p>\n )}\n </div>\n </div>\n </Link>\n )\n}\n"],
|
|
5
|
+
"mappings": ";AA0DM,cAEE,YAFF;AAvDN,OAAO,UAAU;AACjB,SAAS,uBAAuB;AAChC,SAAS,8BAA8B;AACvC,SAAS,sBAAsB;AAC/B,SAAS,iCAAoD;AAkC7D,MAAM,uBAA+C;AAAA,EACnD,QAAQ;AAAA,EACR,SAAS;AAAA,EACT,WAAW;AACb;AAEO,SAAS,YAAY,EAAE,MAAM,QAAQ,UAAU,KAAK,GAAqB;AAC9E,QAAM,aAAa,qBAAqB,KAAK,MAAM,KAAK;AACxD,QAAM,cAAc,OAAO,SAAS,KAAK,MAAM,KAAK,KAAK;AACzD,QAAM,cAAc,uBAAuB,KAAK,OAAO,KAAK,IAAI;AAChE,QAAM,kBAAkB,WAAW,OAAO,eAAe,OAAO;AAEhE,SACE;AAAA,IAAC;AAAA;AAAA,MACC;AAAA,MACA,WAAU;AAAA,MAEV;AAAA,4BAAC,SAAI,WAAU,kBAAiB,OAAO,EAAE,iBAAiB,YAAY,GAAG,eAAY,QAAO;AAAA,QAC5F,qBAAC,SAAI,WAAU,kCACb;AAAA,+BAAC,SAAI,WAAU,2CACb;AAAA;AAAA,cAAC;AAAA;AAAA,gBACC,WAAW,6EAA6E,UAAU;AAAA,gBAEjG;AAAA;AAAA,YACH;AAAA,YACA,oBAAC,mBAAgB,UAAU,KAAK,OAAO,aAAa,KAAK,MAAM,MAAK,MAAK;AAAA,aAC3E;AAAA,UACA,qBAAC,SAAI,WAAU,yBACb;AAAA,gCAAC,QAAG,WAAU,kDAAiD,OAAO,KAAK,MACxE,eAAK,MACR;AAAA,YACA,qBAAC,OAAE,WAAU,wDACV;AAAA,mBAAK,QAAQ;AAAA,cACb,KAAK,eAAe,SAAM,KAAK,YAAY,KAAK;AAAA,eACnD;AAAA,aACF;AAAA,UACA,qBAAC,SAAI,WAAU,sDACb;AAAA,gCAAC,OAAE,WAAU,6DAA6D,2BAAgB;AAAA,YAC1F,qBAAC,SAAI,WAAU,6CACb;AAAA,kCAAC,OAAE,WAAU,sDACV,eAAK,YAAY,IAAI,GAAG,KAAK,SAAS,MAAM,UAC/C;AAAA,cACA;AAAA,gBAAC;AAAA;AAAA,kBACC,QAAQ,KAAK;AAAA,kBACb,OAAO;AAAA,kBACP,OAAO;AAAA,kBACP,QAAQ;AAAA,kBACR,WAAW,OAAO;AAAA;AAAA,cACpB;AAAA,eACF;AAAA,aACF;AAAA,UACA,oBAAC,SAAI,WAAU,kDACZ,qBACC;AAAA,YAAC;AAAA;AAAA,cACC,SAAS,KAAK;AAAA,cACd,OAAO,KAAK;AAAA,cACZ,kBAAkB,OAAO,YAAY,KAAK,WAAW;AAAA;AAAA,UACvD,IAEA,qBAAC,OAAE,WAAU,iCACX;AAAA,iCAAC,UAAK,WAAU,4BAA4B;AAAA,qBAAO;AAAA,cAAK;AAAA,eAAE;AAAA,YACzD,KAAK,UAAU;AAAA,aAClB,GAEJ;AAAA,WACF;AAAA;AAAA;AAAA,EACF;AAEJ;",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|