@open-mercato/core 0.10.1-develop.7716.1.cefe7975df → 0.10.1-develop.7721.1.b5c4a00a68
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_support_routing_rule/index.js +21 -0
- package/dist/generated/entities/staff_support_routing_rule/index.js.map +7 -0
- package/dist/generated/entities/staff_support_ticket/index.js +37 -0
- package/dist/generated/entities/staff_support_ticket/index.js.map +7 -0
- package/dist/generated/entities/staff_time_project/index.js +2 -0
- package/dist/generated/entities/staff_time_project/index.js.map +2 -2
- package/dist/generated/entities/staff_time_project_member/index.js +2 -0
- package/dist/generated/entities/staff_time_project_member/index.js.map +2 -2
- package/dist/generated/entities/staff_time_task/index.js +4 -0
- package/dist/generated/entities/staff_time_task/index.js.map +2 -2
- package/dist/generated/entities/staff_time_task_comment/index.js +9 -1
- package/dist/generated/entities/staff_time_task_comment/index.js.map +2 -2
- package/dist/generated/entities.ids.generated.js +2 -0
- package/dist/generated/entities.ids.generated.js.map +2 -2
- package/dist/generated/entity-fields-registry.js +38 -0
- package/dist/generated/entity-fields-registry.js.map +2 -2
- package/dist/helpers/integration/auth.js +1 -0
- package/dist/helpers/integration/auth.js.map +2 -2
- package/dist/helpers/integration/communicationChannelsFixtures.js +20 -0
- package/dist/helpers/integration/communicationChannelsFixtures.js.map +2 -2
- package/dist/modules/catalog/api/bulk-delete/route.js +12 -2
- package/dist/modules/catalog/api/bulk-delete/route.js.map +2 -2
- package/dist/modules/communication_channels/api/post/test-seed/route.js +17 -3
- package/dist/modules/communication_channels/api/post/test-seed/route.js.map +2 -2
- package/dist/modules/communication_channels/lib/test-seed.js +38 -5
- package/dist/modules/communication_channels/lib/test-seed.js.map +2 -2
- package/dist/modules/messages/commands/messages.js +2 -3
- package/dist/modules/messages/commands/messages.js.map +2 -2
- package/dist/modules/portal/frontend/[orgSlug]/portal/dashboard/page.js +2 -2
- package/dist/modules/portal/frontend/[orgSlug]/portal/dashboard/page.js.map +2 -2
- package/dist/modules/staff/api/portal/support-tickets/[id]/attachments/route.js +299 -0
- package/dist/modules/staff/api/portal/support-tickets/[id]/attachments/route.js.map +7 -0
- package/dist/modules/staff/api/portal/support-tickets/[id]/messages/route.js +91 -0
- package/dist/modules/staff/api/portal/support-tickets/[id]/messages/route.js.map +7 -0
- package/dist/modules/staff/api/portal/support-tickets/[id]/route.js +146 -0
- package/dist/modules/staff/api/portal/support-tickets/[id]/route.js.map +7 -0
- package/dist/modules/staff/api/portal/support-tickets/options/route.js +60 -0
- package/dist/modules/staff/api/portal/support-tickets/options/route.js.map +7 -0
- package/dist/modules/staff/api/portal/support-tickets/route.js +220 -0
- package/dist/modules/staff/api/portal/support-tickets/route.js.map +7 -0
- package/dist/modules/staff/api/timesheets/settings/route.js +7 -1
- package/dist/modules/staff/api/timesheets/settings/route.js.map +2 -2
- package/dist/modules/staff/api/timesheets/support-routing-rules/fallback-project/route.js +122 -0
- package/dist/modules/staff/api/timesheets/support-routing-rules/fallback-project/route.js.map +7 -0
- package/dist/modules/staff/api/timesheets/support-routing-rules/preview/route.js +180 -0
- package/dist/modules/staff/api/timesheets/support-routing-rules/preview/route.js.map +7 -0
- package/dist/modules/staff/api/timesheets/support-routing-rules/route.js +147 -0
- package/dist/modules/staff/api/timesheets/support-routing-rules/route.js.map +7 -0
- package/dist/modules/staff/api/timesheets/support-tickets/options/route.js +133 -0
- package/dist/modules/staff/api/timesheets/support-tickets/options/route.js.map +7 -0
- package/dist/modules/staff/api/timesheets/tasks/[id]/claim/route.js +33 -0
- package/dist/modules/staff/api/timesheets/tasks/[id]/claim/route.js.map +7 -0
- package/dist/modules/staff/api/timesheets/tasks/[id]/comments/route.js +25 -7
- package/dist/modules/staff/api/timesheets/tasks/[id]/comments/route.js.map +2 -2
- package/dist/modules/staff/api/timesheets/tasks/[id]/release/route.js +32 -0
- package/dist/modules/staff/api/timesheets/tasks/[id]/release/route.js.map +7 -0
- package/dist/modules/staff/api/timesheets/tasks/route.js +94 -47
- package/dist/modules/staff/api/timesheets/tasks/route.js.map +2 -2
- package/dist/modules/staff/api/timesheets/time-projects/[id]/employees/route.js +3 -0
- package/dist/modules/staff/api/timesheets/time-projects/[id]/employees/route.js.map +2 -2
- package/dist/modules/staff/api/timesheets/time-projects/route.js +3 -0
- package/dist/modules/staff/api/timesheets/time-projects/route.js.map +2 -2
- package/dist/modules/staff/backend/staff/time-tracking/projects/[id]/edit/page.js +1 -0
- package/dist/modules/staff/backend/staff/time-tracking/projects/[id]/edit/page.js.map +2 -2
- package/dist/modules/staff/backend/staff/time-tracking/projects/[id]/page.js +54 -4
- package/dist/modules/staff/backend/staff/time-tracking/projects/[id]/page.js.map +2 -2
- package/dist/modules/staff/backend/staff/time-tracking/projects/projectFormConfig.js +21 -5
- package/dist/modules/staff/backend/staff/time-tracking/projects/projectFormConfig.js.map +2 -2
- package/dist/modules/staff/backend/staff/time-tracking/settings/page.js +319 -254
- package/dist/modules/staff/backend/staff/time-tracking/settings/page.js.map +2 -2
- package/dist/modules/staff/backend/staff/time-tracking/support/page.js +363 -0
- package/dist/modules/staff/backend/staff/time-tracking/support/page.js.map +7 -0
- package/dist/modules/staff/backend/staff/time-tracking/support/page.meta.js +18 -0
- package/dist/modules/staff/backend/staff/time-tracking/support/page.meta.js.map +7 -0
- package/dist/modules/staff/cli.js +27 -0
- package/dist/modules/staff/cli.js.map +2 -2
- package/dist/modules/staff/commands/index.js +3 -0
- package/dist/modules/staff/commands/index.js.map +2 -2
- package/dist/modules/staff/commands/shared.js +10 -1
- package/dist/modules/staff/commands/shared.js.map +2 -2
- package/dist/modules/staff/commands/support-routing-rules.js +505 -0
- package/dist/modules/staff/commands/support-routing-rules.js.map +7 -0
- package/dist/modules/staff/commands/support-tickets.js +513 -0
- package/dist/modules/staff/commands/support-tickets.js.map +7 -0
- package/dist/modules/staff/commands/timesheets-projects.js +72 -14
- package/dist/modules/staff/commands/timesheets-projects.js.map +2 -2
- package/dist/modules/staff/commands/timesheets-task-assignment.js +270 -0
- package/dist/modules/staff/commands/timesheets-task-assignment.js.map +7 -0
- package/dist/modules/staff/commands/timesheets-task-comments.js +58 -12
- package/dist/modules/staff/commands/timesheets-task-comments.js.map +2 -2
- package/dist/modules/staff/commands/timesheets-tasks.js +91 -9
- package/dist/modules/staff/commands/timesheets-tasks.js.map +3 -3
- package/dist/modules/staff/data/enrichers.js +141 -0
- package/dist/modules/staff/data/enrichers.js.map +2 -2
- package/dist/modules/staff/data/entities.js +143 -2
- package/dist/modules/staff/data/entities.js.map +2 -2
- package/dist/modules/staff/data/extensions.js +8 -0
- package/dist/modules/staff/data/extensions.js.map +2 -2
- package/dist/modules/staff/data/validators.js +34 -5
- package/dist/modules/staff/data/validators.js.map +2 -2
- package/dist/modules/staff/encryption.js +6 -0
- package/dist/modules/staff/encryption.js.map +2 -2
- package/dist/modules/staff/events.js +25 -0
- package/dist/modules/staff/events.js.map +2 -2
- package/dist/modules/staff/events.payloads.js +54 -0
- package/dist/modules/staff/events.payloads.js.map +2 -2
- package/dist/modules/staff/extension-points.js +1 -0
- package/dist/modules/staff/extension-points.js.map +2 -2
- package/dist/modules/staff/frontend/[orgSlug]/portal/support/[id]/page.js +468 -0
- package/dist/modules/staff/frontend/[orgSlug]/portal/support/[id]/page.js.map +7 -0
- package/dist/modules/staff/frontend/[orgSlug]/portal/support/[id]/page.meta.js +12 -0
- package/dist/modules/staff/frontend/[orgSlug]/portal/support/[id]/page.meta.js.map +7 -0
- package/dist/modules/staff/frontend/[orgSlug]/portal/support/new/page.js +316 -0
- package/dist/modules/staff/frontend/[orgSlug]/portal/support/new/page.js.map +7 -0
- package/dist/modules/staff/frontend/[orgSlug]/portal/support/new/page.meta.js +12 -0
- package/dist/modules/staff/frontend/[orgSlug]/portal/support/new/page.meta.js.map +7 -0
- package/dist/modules/staff/frontend/[orgSlug]/portal/support/page.js +190 -0
- package/dist/modules/staff/frontend/[orgSlug]/portal/support/page.js.map +7 -0
- package/dist/modules/staff/frontend/[orgSlug]/portal/support/page.meta.js +19 -0
- package/dist/modules/staff/frontend/[orgSlug]/portal/support/page.meta.js.map +7 -0
- package/dist/modules/staff/index.js +1 -1
- package/dist/modules/staff/index.js.map +2 -2
- package/dist/modules/staff/lib/crud.js +2 -0
- package/dist/modules/staff/lib/crud.js.map +2 -2
- package/dist/modules/staff/lib/support/attachments.js +36 -0
- package/dist/modules/staff/lib/support/attachments.js.map +7 -0
- package/dist/modules/staff/lib/support/dictionaries.js +87 -0
- package/dist/modules/staff/lib/support/dictionaries.js.map +7 -0
- package/dist/modules/staff/lib/support/notifications.js +111 -0
- package/dist/modules/staff/lib/support/notifications.js.map +7 -0
- package/dist/modules/staff/lib/support/portalAttachments.js +100 -0
- package/dist/modules/staff/lib/support/portalAttachments.js.map +7 -0
- package/dist/modules/staff/lib/support/portalBroadcast.js +117 -0
- package/dist/modules/staff/lib/support/portalBroadcast.js.map +7 -0
- package/dist/modules/staff/lib/support/portalRoute.js +99 -0
- package/dist/modules/staff/lib/support/portalRoute.js.map +7 -0
- package/dist/modules/staff/lib/support/portalStatus.js +12 -0
- package/dist/modules/staff/lib/support/portalStatus.js.map +7 -0
- package/dist/modules/staff/lib/support/portalTickets.js +148 -0
- package/dist/modules/staff/lib/support/portalTickets.js.map +7 -0
- package/dist/modules/staff/lib/support/routing.js +81 -0
- package/dist/modules/staff/lib/support/routing.js.map +7 -0
- package/dist/modules/staff/lib/support/routingRuleDraft.js +25 -0
- package/dist/modules/staff/lib/support/routingRuleDraft.js.map +7 -0
- package/dist/modules/staff/lib/support/seedDefaults.js +114 -0
- package/dist/modules/staff/lib/support/seedDefaults.js.map +7 -0
- package/dist/modules/staff/lib/support/supportActivity.js +26 -0
- package/dist/modules/staff/lib/support/supportActivity.js.map +7 -0
- package/dist/modules/staff/lib/support-ui/PortalSupportNotLinked.js +23 -0
- package/dist/modules/staff/lib/support-ui/PortalSupportNotLinked.js.map +7 -0
- package/dist/modules/staff/lib/support-ui/portalAttachmentValidation.js +112 -0
- package/dist/modules/staff/lib/support-ui/portalAttachmentValidation.js.map +7 -0
- package/dist/modules/staff/lib/support-ui/portalPendingUploads.js +45 -0
- package/dist/modules/staff/lib/support-ui/portalPendingUploads.js.map +7 -0
- package/dist/modules/staff/lib/support-ui/portalSupport.js +51 -0
- package/dist/modules/staff/lib/support-ui/portalSupport.js.map +7 -0
- package/dist/modules/staff/lib/time-tracking/access.js +3 -39
- package/dist/modules/staff/lib/time-tracking/access.js.map +2 -2
- package/dist/modules/staff/lib/time-tracking/listProjectAccess.js +54 -0
- package/dist/modules/staff/lib/time-tracking/listProjectAccess.js.map +7 -0
- package/dist/modules/staff/lib/time-tracking/portalRecipients.js +92 -2
- package/dist/modules/staff/lib/time-tracking/portalRecipients.js.map +2 -2
- package/dist/modules/staff/lib/time-tracking/projectAssignment.js +112 -0
- package/dist/modules/staff/lib/time-tracking/projectAssignment.js.map +7 -0
- package/dist/modules/staff/lib/time-tracking/settingKeys.js +14 -0
- package/dist/modules/staff/lib/time-tracking/settingKeys.js.map +2 -2
- package/dist/modules/staff/lib/time-tracking/settings.js +28 -1
- package/dist/modules/staff/lib/time-tracking/settings.js.map +2 -2
- package/dist/modules/staff/lib/time-tracking/taskAssignmentRoute.js +143 -0
- package/dist/modules/staff/lib/time-tracking/taskAssignmentRoute.js.map +7 -0
- package/dist/modules/staff/lib/time-tracking/workRecipients.js +92 -0
- package/dist/modules/staff/lib/time-tracking/workRecipients.js.map +7 -0
- package/dist/modules/staff/lib/time-tracking-ui/BoardFilterChips.js +32 -3
- package/dist/modules/staff/lib/time-tracking-ui/BoardFilterChips.js.map +2 -2
- package/dist/modules/staff/lib/time-tracking-ui/KanbanBoard.js +29 -0
- package/dist/modules/staff/lib/time-tracking-ui/KanbanBoard.js.map +2 -2
- package/dist/modules/staff/lib/time-tracking-ui/KanbanCard.js +4 -0
- package/dist/modules/staff/lib/time-tracking-ui/KanbanCard.js.map +2 -2
- package/dist/modules/staff/lib/time-tracking-ui/ProjectFormSections.js +141 -1
- package/dist/modules/staff/lib/time-tracking-ui/ProjectFormSections.js.map +2 -2
- package/dist/modules/staff/lib/time-tracking-ui/SupportAttachmentsSection.js +153 -0
- package/dist/modules/staff/lib/time-tracking-ui/SupportAttachmentsSection.js.map +7 -0
- package/dist/modules/staff/lib/time-tracking-ui/SupportRequestPanel.js +67 -0
- package/dist/modules/staff/lib/time-tracking-ui/SupportRequestPanel.js.map +7 -0
- package/dist/modules/staff/lib/time-tracking-ui/TaskBoardScreen.js +3 -1
- package/dist/modules/staff/lib/time-tracking-ui/TaskBoardScreen.js.map +2 -2
- package/dist/modules/staff/lib/time-tracking-ui/TaskCommentThread.js +80 -17
- package/dist/modules/staff/lib/time-tracking-ui/TaskCommentThread.js.map +2 -2
- package/dist/modules/staff/lib/time-tracking-ui/TaskDrawer.js +230 -41
- package/dist/modules/staff/lib/time-tracking-ui/TaskDrawer.js.map +2 -2
- package/dist/modules/staff/lib/time-tracking-ui/boardDirectory.js +21 -1
- package/dist/modules/staff/lib/time-tracking-ui/boardDirectory.js.map +2 -2
- package/dist/modules/staff/lib/time-tracking-ui/boardFilters.js +16 -4
- package/dist/modules/staff/lib/time-tracking-ui/boardFilters.js.map +2 -2
- package/dist/modules/staff/lib/time-tracking-ui/kanbanBoardData.js +3 -1
- package/dist/modules/staff/lib/time-tracking-ui/kanbanBoardData.js.map +2 -2
- package/dist/modules/staff/lib/time-tracking-ui/projectDefaultAssignee.js +68 -0
- package/dist/modules/staff/lib/time-tracking-ui/projectDefaultAssignee.js.map +7 -0
- package/dist/modules/staff/lib/time-tracking-ui/settingsTabs.js +7 -0
- package/dist/modules/staff/lib/time-tracking-ui/settingsTabs.js.map +7 -0
- package/dist/modules/staff/lib/time-tracking-ui/supportTicketData.js +50 -0
- package/dist/modules/staff/lib/time-tracking-ui/supportTicketData.js.map +7 -0
- package/dist/modules/staff/lib/time-tracking-ui/taskDrawerData.js +13 -1
- package/dist/modules/staff/lib/time-tracking-ui/taskDrawerData.js.map +2 -2
- package/dist/modules/staff/lib/time-tracking-ui/timeTrackingSettingsForm.js +36 -2
- package/dist/modules/staff/lib/time-tracking-ui/timeTrackingSettingsForm.js.map +2 -2
- package/dist/modules/staff/migrations/Migration20261009180543_staff.js +42 -0
- package/dist/modules/staff/migrations/Migration20261009180543_staff.js.map +7 -0
- package/dist/modules/staff/notifications.js +92 -0
- package/dist/modules/staff/notifications.js.map +2 -2
- package/dist/modules/staff/setup.js +6 -2
- package/dist/modules/staff/setup.js.map +2 -2
- package/dist/modules/staff/subscribers/support-portal-comment-created.js +26 -0
- package/dist/modules/staff/subscribers/support-portal-comment-created.js.map +7 -0
- package/dist/modules/staff/subscribers/support-portal-comment-deleted.js +26 -0
- package/dist/modules/staff/subscribers/support-portal-comment-deleted.js.map +7 -0
- package/dist/modules/staff/subscribers/support-portal-comment-updated.js +26 -0
- package/dist/modules/staff/subscribers/support-portal-comment-updated.js.map +7 -0
- package/dist/modules/staff/subscribers/support-portal-status-column-updated.js +24 -0
- package/dist/modules/staff/subscribers/support-portal-status-column-updated.js.map +7 -0
- package/dist/modules/staff/subscribers/support-portal-task-status-changed.js +25 -0
- package/dist/modules/staff/subscribers/support-portal-task-status-changed.js.map +7 -0
- package/dist/modules/staff/subscribers/support-portal-task-updated.js +25 -0
- package/dist/modules/staff/subscribers/support-portal-task-updated.js.map +7 -0
- package/dist/modules/staff/subscribers/support-ticket-created-notification.js +25 -0
- package/dist/modules/staff/subscribers/support-ticket-created-notification.js.map +7 -0
- package/dist/modules/staff/subscribers/support-ticket-customer-replied-notification.js +25 -0
- package/dist/modules/staff/subscribers/support-ticket-customer-replied-notification.js.map +7 -0
- package/dist/modules/staff/subscribers/support-ticket-unnotified-clear.js +26 -0
- package/dist/modules/staff/subscribers/support-ticket-unnotified-clear.js.map +7 -0
- package/dist/modules/staff/subscribers/time-task-assignee-notification.js +83 -0
- package/dist/modules/staff/subscribers/time-task-assignee-notification.js.map +7 -0
- package/dist/modules/staff/widgets/injection/portal-support-tickets/widget.client.js +112 -0
- package/dist/modules/staff/widgets/injection/portal-support-tickets/widget.client.js.map +7 -0
- package/dist/modules/staff/widgets/injection/portal-support-tickets/widget.js +17 -0
- package/dist/modules/staff/widgets/injection/portal-support-tickets/widget.js.map +7 -0
- package/dist/modules/staff/widgets/injection/time-tracking-settings-support/widget.client.js +870 -0
- package/dist/modules/staff/widgets/injection/time-tracking-settings-support/widget.client.js.map +7 -0
- package/dist/modules/staff/widgets/injection/time-tracking-settings-support/widget.js +17 -0
- package/dist/modules/staff/widgets/injection/time-tracking-settings-support/widget.js.map +7 -0
- package/dist/modules/staff/widgets/injection-table.js +16 -1
- package/dist/modules/staff/widgets/injection-table.js.map +2 -2
- package/dist/modules/wms/components/backend/WmsLocationDetailPage.js +4 -18
- package/dist/modules/wms/components/backend/WmsLocationDetailPage.js.map +2 -2
- package/dist/modules/wms/components/backend/WmsLotDetailPage.js +4 -18
- package/dist/modules/wms/components/backend/WmsLotDetailPage.js.map +2 -2
- package/dist/modules/wms/components/backend/WmsSkuDetailPage.js +4 -18
- package/dist/modules/wms/components/backend/WmsSkuDetailPage.js.map +2 -2
- package/dist/modules/wms/lib/inventoryCsvExport.js +19 -0
- package/dist/modules/wms/lib/inventoryCsvExport.js.map +7 -0
- package/generated/entities/staff_support_routing_rule/index.ts +9 -0
- package/generated/entities/staff_support_ticket/index.ts +17 -0
- package/generated/entities/staff_time_project/index.ts +1 -0
- package/generated/entities/staff_time_project_member/index.ts +1 -0
- package/generated/entities/staff_time_task/index.ts +2 -0
- package/generated/entities/staff_time_task_comment/index.ts +4 -0
- package/generated/entities.ids.generated.ts +2 -0
- package/generated/entity-fields-registry.ts +38 -0
- package/package.json +8 -8
- package/src/helpers/integration/auth.ts +1 -1
- package/src/helpers/integration/communicationChannelsFixtures.ts +60 -2
- package/src/modules/catalog/api/bulk-delete/route.ts +12 -2
- package/src/modules/communication_channels/api/post/test-seed/route.ts +29 -12
- package/src/modules/communication_channels/lib/test-seed.ts +49 -10
- package/src/modules/messages/commands/messages.ts +8 -10
- package/src/modules/portal/frontend/[orgSlug]/portal/dashboard/page.tsx +2 -2
- package/src/modules/staff/AGENTS.md +22 -6
- package/src/modules/staff/api/portal/support-tickets/[id]/attachments/route.ts +327 -0
- package/src/modules/staff/api/portal/support-tickets/[id]/messages/route.ts +94 -0
- package/src/modules/staff/api/portal/support-tickets/[id]/route.ts +160 -0
- package/src/modules/staff/api/portal/support-tickets/options/route.ts +60 -0
- package/src/modules/staff/api/portal/support-tickets/route.ts +253 -0
- package/src/modules/staff/api/timesheets/settings/route.ts +7 -1
- package/src/modules/staff/api/timesheets/support-routing-rules/fallback-project/route.ts +131 -0
- package/src/modules/staff/api/timesheets/support-routing-rules/preview/route.ts +248 -0
- package/src/modules/staff/api/timesheets/support-routing-rules/route.ts +150 -0
- package/src/modules/staff/api/timesheets/support-tickets/options/route.ts +181 -0
- package/src/modules/staff/api/timesheets/tasks/[id]/claim/route.ts +31 -0
- package/src/modules/staff/api/timesheets/tasks/[id]/comments/route.ts +25 -7
- package/src/modules/staff/api/timesheets/tasks/[id]/release/route.ts +30 -0
- package/src/modules/staff/api/timesheets/tasks/route.ts +119 -67
- package/src/modules/staff/api/timesheets/time-projects/[id]/employees/route.ts +3 -0
- package/src/modules/staff/api/timesheets/time-projects/route.ts +3 -0
- package/src/modules/staff/backend/staff/time-tracking/projects/[id]/edit/page.tsx +1 -0
- package/src/modules/staff/backend/staff/time-tracking/projects/[id]/page.tsx +67 -2
- package/src/modules/staff/backend/staff/time-tracking/projects/projectFormConfig.ts +22 -3
- package/src/modules/staff/backend/staff/time-tracking/settings/page.tsx +405 -308
- package/src/modules/staff/backend/staff/time-tracking/support/page.meta.ts +14 -0
- package/src/modules/staff/backend/staff/time-tracking/support/page.tsx +428 -0
- package/src/modules/staff/cli.ts +28 -0
- package/src/modules/staff/commands/index.ts +3 -0
- package/src/modules/staff/commands/shared.ts +22 -1
- package/src/modules/staff/commands/support-routing-rules.ts +595 -0
- package/src/modules/staff/commands/support-tickets.ts +580 -0
- package/src/modules/staff/commands/timesheets-projects.ts +84 -13
- package/src/modules/staff/commands/timesheets-task-assignment.ts +319 -0
- package/src/modules/staff/commands/timesheets-task-comments.ts +70 -15
- package/src/modules/staff/commands/timesheets-tasks.ts +106 -15
- package/src/modules/staff/data/enrichers.ts +203 -0
- package/src/modules/staff/data/entities.ts +133 -2
- package/src/modules/staff/data/extensions.ts +9 -1
- package/src/modules/staff/data/validators.ts +36 -0
- package/src/modules/staff/encryption.ts +6 -0
- package/src/modules/staff/events.payloads.ts +87 -1
- package/src/modules/staff/events.ts +26 -0
- package/src/modules/staff/extension-points.ts +1 -0
- package/src/modules/staff/frontend/[orgSlug]/portal/support/[id]/page.meta.ts +10 -0
- package/src/modules/staff/frontend/[orgSlug]/portal/support/[id]/page.tsx +553 -0
- package/src/modules/staff/frontend/[orgSlug]/portal/support/new/page.meta.ts +10 -0
- package/src/modules/staff/frontend/[orgSlug]/portal/support/new/page.tsx +346 -0
- package/src/modules/staff/frontend/[orgSlug]/portal/support/page.meta.ts +17 -0
- package/src/modules/staff/frontend/[orgSlug]/portal/support/page.tsx +253 -0
- package/src/modules/staff/i18n/de.json +291 -0
- package/src/modules/staff/i18n/en.json +291 -0
- package/src/modules/staff/i18n/es.json +291 -0
- package/src/modules/staff/i18n/ko.json +291 -0
- package/src/modules/staff/i18n/pl.json +291 -0
- package/src/modules/staff/index.ts +1 -1
- package/src/modules/staff/lib/crud.ts +2 -1
- package/src/modules/staff/lib/support/attachments.ts +43 -0
- package/src/modules/staff/lib/support/dictionaries.ts +120 -0
- package/src/modules/staff/lib/support/notifications.ts +138 -0
- package/src/modules/staff/lib/support/portalAttachments.ts +140 -0
- package/src/modules/staff/lib/support/portalBroadcast.ts +161 -0
- package/src/modules/staff/lib/support/portalRoute.ts +108 -0
- package/src/modules/staff/lib/support/portalStatus.ts +15 -0
- package/src/modules/staff/lib/support/portalTickets.ts +233 -0
- package/src/modules/staff/lib/support/routing.ts +117 -0
- package/src/modules/staff/lib/support/routingRuleDraft.ts +45 -0
- package/src/modules/staff/lib/support/seedDefaults.ts +151 -0
- package/src/modules/staff/lib/support/supportActivity.ts +39 -0
- package/src/modules/staff/lib/support-ui/PortalSupportNotLinked.tsx +20 -0
- package/src/modules/staff/lib/support-ui/portalAttachmentValidation.ts +125 -0
- package/src/modules/staff/lib/support-ui/portalPendingUploads.ts +49 -0
- package/src/modules/staff/lib/support-ui/portalSupport.ts +116 -0
- package/src/modules/staff/lib/time-tracking/access.ts +2 -62
- package/src/modules/staff/lib/time-tracking/listProjectAccess.ts +78 -0
- package/src/modules/staff/lib/time-tracking/portalRecipients.ts +173 -5
- package/src/modules/staff/lib/time-tracking/projectAssignment.ts +172 -0
- package/src/modules/staff/lib/time-tracking/settingKeys.ts +11 -0
- package/src/modules/staff/lib/time-tracking/settings.ts +46 -1
- package/src/modules/staff/lib/time-tracking/taskAssignmentRoute.ts +165 -0
- package/src/modules/staff/lib/time-tracking/workRecipients.ts +183 -0
- package/src/modules/staff/lib/time-tracking-ui/BoardFilterChips.tsx +36 -1
- package/src/modules/staff/lib/time-tracking-ui/KanbanBoard.tsx +30 -0
- package/src/modules/staff/lib/time-tracking-ui/KanbanCard.tsx +9 -0
- package/src/modules/staff/lib/time-tracking-ui/ProjectFormSections.tsx +185 -6
- package/src/modules/staff/lib/time-tracking-ui/SupportAttachmentsSection.tsx +188 -0
- package/src/modules/staff/lib/time-tracking-ui/SupportRequestPanel.tsx +102 -0
- package/src/modules/staff/lib/time-tracking-ui/TaskBoardScreen.tsx +2 -0
- package/src/modules/staff/lib/time-tracking-ui/TaskCommentThread.tsx +105 -22
- package/src/modules/staff/lib/time-tracking-ui/TaskDrawer.tsx +278 -43
- package/src/modules/staff/lib/time-tracking-ui/boardDirectory.ts +21 -0
- package/src/modules/staff/lib/time-tracking-ui/boardFilters.ts +16 -2
- package/src/modules/staff/lib/time-tracking-ui/kanbanBoardData.ts +4 -0
- package/src/modules/staff/lib/time-tracking-ui/projectDefaultAssignee.ts +117 -0
- package/src/modules/staff/lib/time-tracking-ui/settingsTabs.ts +17 -0
- package/src/modules/staff/lib/time-tracking-ui/supportTicketData.ts +71 -0
- package/src/modules/staff/lib/time-tracking-ui/taskDrawerData.ts +15 -0
- package/src/modules/staff/lib/time-tracking-ui/timeTrackingSettingsForm.ts +48 -1
- package/src/modules/staff/migrations/.snapshot-open-mercato.json +714 -0
- package/src/modules/staff/migrations/Migration20261009180543_staff.ts +50 -0
- package/src/modules/staff/notifications.ts +92 -0
- package/src/modules/staff/setup.ts +6 -2
- package/src/modules/staff/subscribers/support-portal-comment-created.ts +35 -0
- package/src/modules/staff/subscribers/support-portal-comment-deleted.ts +35 -0
- package/src/modules/staff/subscribers/support-portal-comment-updated.ts +35 -0
- package/src/modules/staff/subscribers/support-portal-status-column-updated.ts +36 -0
- package/src/modules/staff/subscribers/support-portal-task-status-changed.ts +34 -0
- package/src/modules/staff/subscribers/support-portal-task-updated.ts +34 -0
- package/src/modules/staff/subscribers/support-ticket-created-notification.ts +27 -0
- package/src/modules/staff/subscribers/support-ticket-customer-replied-notification.ts +27 -0
- package/src/modules/staff/subscribers/support-ticket-unnotified-clear.ts +29 -0
- package/src/modules/staff/subscribers/time-task-assignee-notification.ts +102 -0
- package/src/modules/staff/widgets/injection/portal-support-tickets/widget.client.tsx +151 -0
- package/src/modules/staff/widgets/injection/portal-support-tickets/widget.ts +16 -0
- package/src/modules/staff/widgets/injection/time-tracking-settings-support/widget.client.tsx +1058 -0
- package/src/modules/staff/widgets/injection/time-tracking-settings-support/widget.ts +17 -0
- package/src/modules/staff/widgets/injection-table.ts +15 -0
- package/src/modules/wms/components/backend/WmsLocationDetailPage.tsx +4 -20
- package/src/modules/wms/components/backend/WmsLotDetailPage.tsx +4 -20
- package/src/modules/wms/components/backend/WmsSkuDetailPage.tsx +4 -19
- package/src/modules/wms/lib/inventoryCsvExport.ts +18 -0
|
@@ -0,0 +1,190 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
3
|
+
import * as React from "react";
|
|
4
|
+
import Link from "next/link";
|
|
5
|
+
import { Plus } from "lucide-react";
|
|
6
|
+
import { replaceWithPageReload } from "@open-mercato/shared/lib/navigation/pageReload";
|
|
7
|
+
import { useLocale, useT } from "@open-mercato/shared/lib/i18n/context";
|
|
8
|
+
import { getTelemetryRuntime } from "@open-mercato/shared/lib/telemetry/runtime";
|
|
9
|
+
import { Button } from "@open-mercato/ui/primitives/button";
|
|
10
|
+
import { Spinner } from "@open-mercato/ui/primitives/spinner";
|
|
11
|
+
import { StatusBadge } from "@open-mercato/ui/primitives/status-badge";
|
|
12
|
+
import { EmptyState } from "@open-mercato/ui/primitives/empty-state";
|
|
13
|
+
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@open-mercato/ui/primitives/select";
|
|
14
|
+
import { ErrorMessage } from "@open-mercato/ui/backend/detail";
|
|
15
|
+
import { apiCall } from "@open-mercato/ui/backend/utils/apiCall";
|
|
16
|
+
import { usePortalContext } from "@open-mercato/ui/portal/PortalContext";
|
|
17
|
+
import { PortalPageHeader } from "@open-mercato/ui/portal/components/PortalPageHeader";
|
|
18
|
+
import { PortalCard } from "@open-mercato/ui/portal/components/PortalCard";
|
|
19
|
+
import { usePortalAppEvent } from "@open-mercato/ui/portal/hooks/usePortalAppEvent";
|
|
20
|
+
import {
|
|
21
|
+
PORTAL_SUPPORT_STATUS_VARIANTS,
|
|
22
|
+
PORTAL_SUPPORT_UPDATED_EVENT,
|
|
23
|
+
formatPortalDate,
|
|
24
|
+
isPortalSupportAccountNotLinked,
|
|
25
|
+
portalSupportStatusLabel
|
|
26
|
+
} from "@open-mercato/core/modules/staff/lib/support-ui/portalSupport";
|
|
27
|
+
import { PortalSupportNotLinked } from "@open-mercato/core/modules/staff/lib/support-ui/PortalSupportNotLinked";
|
|
28
|
+
const PAGE_SIZE = 20;
|
|
29
|
+
const ALL_STATUSES = "all";
|
|
30
|
+
function PortalSupportTicketsPage({ params }) {
|
|
31
|
+
const t = useT();
|
|
32
|
+
const locale = useLocale();
|
|
33
|
+
const { auth } = usePortalContext();
|
|
34
|
+
const { user, loading } = auth;
|
|
35
|
+
const [tickets, setTickets] = React.useState([]);
|
|
36
|
+
const [options, setOptions] = React.useState(null);
|
|
37
|
+
const [status, setStatus] = React.useState(ALL_STATUSES);
|
|
38
|
+
const [page, setPage] = React.useState(1);
|
|
39
|
+
const [totalPages, setTotalPages] = React.useState(1);
|
|
40
|
+
const [isLoading, setIsLoading] = React.useState(true);
|
|
41
|
+
const [error, setError] = React.useState(null);
|
|
42
|
+
const [notLinked, setNotLinked] = React.useState(false);
|
|
43
|
+
React.useEffect(() => {
|
|
44
|
+
if (!loading && !user) replaceWithPageReload(`/${params.orgSlug}/portal/login`);
|
|
45
|
+
}, [loading, user, params.orgSlug]);
|
|
46
|
+
const loadSeqRef = React.useRef(0);
|
|
47
|
+
const load = React.useCallback(async (options2 = {}) => {
|
|
48
|
+
if (!user) return;
|
|
49
|
+
const requestSeq = loadSeqRef.current + 1;
|
|
50
|
+
loadSeqRef.current = requestSeq;
|
|
51
|
+
const isStale = () => requestSeq !== loadSeqRef.current;
|
|
52
|
+
if (!options2.silent) setIsLoading(true);
|
|
53
|
+
setError(null);
|
|
54
|
+
try {
|
|
55
|
+
const statusQuery = status === ALL_STATUSES ? "" : `&status=${encodeURIComponent(status)}`;
|
|
56
|
+
const [list, opts] = await Promise.all([
|
|
57
|
+
apiCall(`/api/staff/portal/support-tickets?page=${page}&pageSize=${PAGE_SIZE}${statusQuery}`, {
|
|
58
|
+
credentials: "include"
|
|
59
|
+
}),
|
|
60
|
+
apiCall("/api/staff/portal/support-tickets/options", { credentials: "include" })
|
|
61
|
+
]);
|
|
62
|
+
if (isStale()) return;
|
|
63
|
+
if (isPortalSupportAccountNotLinked(list)) {
|
|
64
|
+
setNotLinked(true);
|
|
65
|
+
setTickets([]);
|
|
66
|
+
setOptions(null);
|
|
67
|
+
return;
|
|
68
|
+
}
|
|
69
|
+
setNotLinked(false);
|
|
70
|
+
if (!list.ok) {
|
|
71
|
+
setError(t("staff.portal.support.loadFailed", "Could not load your support tickets."));
|
|
72
|
+
if (!options2.silent) setTickets([]);
|
|
73
|
+
return;
|
|
74
|
+
}
|
|
75
|
+
setTickets(list.result?.items ?? []);
|
|
76
|
+
setTotalPages(Math.max(1, list.result?.totalPages ?? 1));
|
|
77
|
+
setOptions(opts.ok ? opts.result : null);
|
|
78
|
+
} catch (err) {
|
|
79
|
+
if (isStale()) return;
|
|
80
|
+
getTelemetryRuntime()?.reportError(err, { module: "staff", code: "staff.portal_support_list_failed" });
|
|
81
|
+
setError(t("staff.portal.support.loadFailed", "Could not load your support tickets."));
|
|
82
|
+
if (!options2.silent) setTickets([]);
|
|
83
|
+
} finally {
|
|
84
|
+
if (!isStale()) setIsLoading(false);
|
|
85
|
+
}
|
|
86
|
+
}, [user, page, status, t]);
|
|
87
|
+
React.useEffect(() => {
|
|
88
|
+
void load();
|
|
89
|
+
}, [load]);
|
|
90
|
+
usePortalAppEvent(PORTAL_SUPPORT_UPDATED_EVENT, () => {
|
|
91
|
+
void load({ silent: true });
|
|
92
|
+
}, [load]);
|
|
93
|
+
if (loading) {
|
|
94
|
+
return /* @__PURE__ */ jsx("div", { className: "flex items-center justify-center py-20", children: /* @__PURE__ */ jsx(Spinner, {}) });
|
|
95
|
+
}
|
|
96
|
+
if (!user) return null;
|
|
97
|
+
const canCreate = options?.canCreate === true;
|
|
98
|
+
const noCategories = options !== null && (options.categories ?? []).length === 0;
|
|
99
|
+
const newTicketHref = `/${params.orgSlug}/portal/support/new`;
|
|
100
|
+
return /* @__PURE__ */ jsxs("div", { className: "space-y-6", children: [
|
|
101
|
+
/* @__PURE__ */ jsx(
|
|
102
|
+
PortalPageHeader,
|
|
103
|
+
{
|
|
104
|
+
title: t("staff.portal.support.title", "Support"),
|
|
105
|
+
description: t("staff.portal.support.description", "Ask for help and follow the progress of your requests."),
|
|
106
|
+
action: canCreate && !notLinked ? /* @__PURE__ */ jsx(Button, { asChild: true, children: /* @__PURE__ */ jsxs(Link, { href: newTicketHref, children: [
|
|
107
|
+
/* @__PURE__ */ jsx(Plus, { className: "mr-2 h-4 w-4" }),
|
|
108
|
+
t("staff.portal.support.newTicket", "New ticket")
|
|
109
|
+
] }) }) : void 0
|
|
110
|
+
}
|
|
111
|
+
),
|
|
112
|
+
notLinked ? /* @__PURE__ */ jsx(PortalSupportNotLinked, {}) : null,
|
|
113
|
+
notLinked ? null : /* @__PURE__ */ jsxs("div", { className: "flex flex-wrap items-center gap-3", children: [
|
|
114
|
+
/* @__PURE__ */ jsx("span", { className: "text-sm text-muted-foreground", children: t("staff.portal.support.filter.status", "Status") }),
|
|
115
|
+
/* @__PURE__ */ jsxs(
|
|
116
|
+
Select,
|
|
117
|
+
{
|
|
118
|
+
value: status,
|
|
119
|
+
onValueChange: (value) => {
|
|
120
|
+
setStatus(value);
|
|
121
|
+
setPage(1);
|
|
122
|
+
},
|
|
123
|
+
children: [
|
|
124
|
+
/* @__PURE__ */ jsx(SelectTrigger, { className: "w-48", "aria-label": t("staff.portal.support.filter.status", "Status"), children: /* @__PURE__ */ jsx(SelectValue, {}) }),
|
|
125
|
+
/* @__PURE__ */ jsxs(SelectContent, { children: [
|
|
126
|
+
/* @__PURE__ */ jsx(SelectItem, { value: ALL_STATUSES, children: t("staff.portal.support.filter.all", "All statuses") }),
|
|
127
|
+
["received", "in_progress", "resolved"].map((value) => /* @__PURE__ */ jsx(SelectItem, { value, children: portalSupportStatusLabel(t, value) }, value))
|
|
128
|
+
] })
|
|
129
|
+
]
|
|
130
|
+
}
|
|
131
|
+
)
|
|
132
|
+
] }),
|
|
133
|
+
error ? /* @__PURE__ */ jsx(
|
|
134
|
+
ErrorMessage,
|
|
135
|
+
{
|
|
136
|
+
label: error,
|
|
137
|
+
action: /* @__PURE__ */ jsx(Button, { type: "button", variant: "outline", size: "sm", onClick: () => void load(), children: t("staff.portal.support.retry", "Try again") })
|
|
138
|
+
}
|
|
139
|
+
) : null,
|
|
140
|
+
notLinked ? null : isLoading ? /* @__PURE__ */ jsx("div", { className: "flex items-center justify-center py-16", children: /* @__PURE__ */ jsx(Spinner, {}) }) : tickets.length === 0 ? /* @__PURE__ */ jsx(
|
|
141
|
+
EmptyState,
|
|
142
|
+
{
|
|
143
|
+
variant: "subtle",
|
|
144
|
+
size: "lg",
|
|
145
|
+
title: noCategories ? t("staff.portal.support.unavailableTitle", "Support requests are not available yet") : t("staff.portal.support.emptyTitle", "No support tickets yet"),
|
|
146
|
+
description: noCategories ? t("staff.portal.support.unavailableDescription", "Please contact us directly until support requests are enabled.") : t("staff.portal.support.emptyDescription", "When you file a ticket, you can follow it here."),
|
|
147
|
+
actions: canCreate ? /* @__PURE__ */ jsx(Button, { asChild: true, children: /* @__PURE__ */ jsx(Link, { href: newTicketHref, children: t("staff.portal.support.newTicket", "New ticket") }) }) : void 0
|
|
148
|
+
}
|
|
149
|
+
) : /* @__PURE__ */ jsx("div", { className: "space-y-3", children: tickets.map((ticket) => /* @__PURE__ */ jsx(PortalCard, { children: /* @__PURE__ */ jsxs("div", { className: "flex flex-wrap items-start justify-between gap-4", children: [
|
|
150
|
+
/* @__PURE__ */ jsxs("div", { className: "min-w-0 space-y-1", children: [
|
|
151
|
+
/* @__PURE__ */ jsx(
|
|
152
|
+
Link,
|
|
153
|
+
{
|
|
154
|
+
href: `/${params.orgSlug}/portal/support/${ticket.id}`,
|
|
155
|
+
className: "text-base font-medium text-foreground hover:underline",
|
|
156
|
+
children: ticket.subject
|
|
157
|
+
}
|
|
158
|
+
),
|
|
159
|
+
/* @__PURE__ */ jsxs("p", { className: "text-sm text-muted-foreground", children: [
|
|
160
|
+
ticket.supportReference,
|
|
161
|
+
" \xB7 ",
|
|
162
|
+
ticket.category.label
|
|
163
|
+
] })
|
|
164
|
+
] }),
|
|
165
|
+
/* @__PURE__ */ jsxs("div", { className: "flex flex-col items-end gap-1", children: [
|
|
166
|
+
/* @__PURE__ */ jsx(StatusBadge, { variant: PORTAL_SUPPORT_STATUS_VARIANTS[ticket.portalStatus] ?? "neutral", dot: true, children: portalSupportStatusLabel(t, ticket.portalStatus) }),
|
|
167
|
+
/* @__PURE__ */ jsx("span", { className: "text-xs text-muted-foreground", children: t("staff.portal.support.lastActivityAt", "Last activity: {date}", {
|
|
168
|
+
date: formatPortalDate(ticket.lastActivityAt, locale)
|
|
169
|
+
}) })
|
|
170
|
+
] })
|
|
171
|
+
] }) }, ticket.id)) }),
|
|
172
|
+
totalPages > 1 ? /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between gap-3", children: [
|
|
173
|
+
/* @__PURE__ */ jsx(Button, { variant: "outline", disabled: page <= 1, onClick: () => setPage((current) => Math.max(1, current - 1)), children: t("staff.portal.support.previous", "Previous") }),
|
|
174
|
+
/* @__PURE__ */ jsx("span", { className: "text-sm text-muted-foreground", children: t("staff.portal.support.pageIndicator", "Page {page} / {total}", { page, total: totalPages }) }),
|
|
175
|
+
/* @__PURE__ */ jsx(
|
|
176
|
+
Button,
|
|
177
|
+
{
|
|
178
|
+
variant: "outline",
|
|
179
|
+
disabled: page >= totalPages,
|
|
180
|
+
onClick: () => setPage((current) => Math.min(totalPages, current + 1)),
|
|
181
|
+
children: t("staff.portal.support.next", "Next")
|
|
182
|
+
}
|
|
183
|
+
)
|
|
184
|
+
] }) : null
|
|
185
|
+
] });
|
|
186
|
+
}
|
|
187
|
+
export {
|
|
188
|
+
PortalSupportTicketsPage as default
|
|
189
|
+
};
|
|
190
|
+
//# sourceMappingURL=page.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../../../../../../src/modules/staff/frontend/%5BorgSlug%5D/portal/support/page.tsx"],
|
|
4
|
+
"sourcesContent": ["\"use client\"\n\nimport * as React from 'react'\nimport Link from 'next/link'\nimport { Plus } from 'lucide-react'\nimport { replaceWithPageReload } from '@open-mercato/shared/lib/navigation/pageReload'\nimport { useLocale, useT } from '@open-mercato/shared/lib/i18n/context'\nimport { getTelemetryRuntime } from '@open-mercato/shared/lib/telemetry/runtime'\nimport { Button } from '@open-mercato/ui/primitives/button'\nimport { Spinner } from '@open-mercato/ui/primitives/spinner'\nimport { StatusBadge } from '@open-mercato/ui/primitives/status-badge'\nimport { EmptyState } from '@open-mercato/ui/primitives/empty-state'\nimport { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@open-mercato/ui/primitives/select'\nimport { ErrorMessage } from '@open-mercato/ui/backend/detail'\nimport { apiCall } from '@open-mercato/ui/backend/utils/apiCall'\nimport { usePortalContext } from '@open-mercato/ui/portal/PortalContext'\nimport { PortalPageHeader } from '@open-mercato/ui/portal/components/PortalPageHeader'\nimport { PortalCard } from '@open-mercato/ui/portal/components/PortalCard'\nimport { usePortalAppEvent } from '@open-mercato/ui/portal/hooks/usePortalAppEvent'\nimport {\n PORTAL_SUPPORT_STATUS_VARIANTS,\n PORTAL_SUPPORT_UPDATED_EVENT,\n formatPortalDate,\n isPortalSupportAccountNotLinked,\n portalSupportStatusLabel,\n type PortalSupportOptions,\n type PortalSupportStatus,\n type PortalSupportTicketList,\n type PortalSupportTicketListItem,\n} from '@open-mercato/core/modules/staff/lib/support-ui/portalSupport'\nimport { PortalSupportNotLinked } from '@open-mercato/core/modules/staff/lib/support-ui/PortalSupportNotLinked'\n\ntype Props = { params: { orgSlug: string } }\n\nconst PAGE_SIZE = 20\nconst ALL_STATUSES = 'all'\n\nexport default function PortalSupportTicketsPage({ params }: Props) {\n const t = useT()\n const locale = useLocale()\n const { auth } = usePortalContext()\n const { user, loading } = auth\n\n const [tickets, setTickets] = React.useState<PortalSupportTicketListItem[]>([])\n const [options, setOptions] = React.useState<PortalSupportOptions | null>(null)\n const [status, setStatus] = React.useState<string>(ALL_STATUSES)\n const [page, setPage] = React.useState(1)\n const [totalPages, setTotalPages] = React.useState(1)\n const [isLoading, setIsLoading] = React.useState(true)\n const [error, setError] = React.useState<string | null>(null)\n const [notLinked, setNotLinked] = React.useState(false)\n\n React.useEffect(() => {\n if (!loading && !user) replaceWithPageReload(`/${params.orgSlug}/portal/login`)\n }, [loading, user, params.orgSlug])\n\n const loadSeqRef = React.useRef(0)\n\n const load = React.useCallback(async (options: { silent?: boolean } = {}) => {\n if (!user) return\n const requestSeq = loadSeqRef.current + 1\n loadSeqRef.current = requestSeq\n const isStale = () => requestSeq !== loadSeqRef.current\n if (!options.silent) setIsLoading(true)\n setError(null)\n try {\n const statusQuery = status === ALL_STATUSES ? '' : `&status=${encodeURIComponent(status)}`\n const [list, opts] = await Promise.all([\n apiCall<PortalSupportTicketList>(`/api/staff/portal/support-tickets?page=${page}&pageSize=${PAGE_SIZE}${statusQuery}`, {\n credentials: 'include',\n }),\n apiCall<PortalSupportOptions>('/api/staff/portal/support-tickets/options', { credentials: 'include' }),\n ])\n if (isStale()) return\n if (isPortalSupportAccountNotLinked(list)) {\n setNotLinked(true)\n setTickets([])\n setOptions(null)\n return\n }\n setNotLinked(false)\n if (!list.ok) {\n setError(t('staff.portal.support.loadFailed', 'Could not load your support tickets.'))\n if (!options.silent) setTickets([])\n return\n }\n setTickets(list.result?.items ?? [])\n setTotalPages(Math.max(1, list.result?.totalPages ?? 1))\n setOptions(opts.ok ? opts.result : null)\n } catch (err) {\n if (isStale()) return\n getTelemetryRuntime()?.reportError(err, { module: 'staff', code: 'staff.portal_support_list_failed' })\n setError(t('staff.portal.support.loadFailed', 'Could not load your support tickets.'))\n if (!options.silent) setTickets([])\n } finally {\n if (!isStale()) setIsLoading(false)\n }\n }, [user, page, status, t])\n\n React.useEffect(() => {\n void load()\n }, [load])\n\n usePortalAppEvent(PORTAL_SUPPORT_UPDATED_EVENT, () => {\n void load({ silent: true })\n }, [load])\n\n if (loading) {\n return (\n <div className=\"flex items-center justify-center py-20\">\n <Spinner />\n </div>\n )\n }\n if (!user) return null\n\n const canCreate = options?.canCreate === true\n const noCategories = options !== null && (options.categories ?? []).length === 0\n const newTicketHref = `/${params.orgSlug}/portal/support/new`\n\n return (\n <div className=\"space-y-6\">\n <PortalPageHeader\n title={t('staff.portal.support.title', 'Support')}\n description={t('staff.portal.support.description', 'Ask for help and follow the progress of your requests.')}\n action={\n canCreate && !notLinked ? (\n <Button asChild>\n <Link href={newTicketHref}>\n <Plus className=\"mr-2 h-4 w-4\" />\n {t('staff.portal.support.newTicket', 'New ticket')}\n </Link>\n </Button>\n ) : undefined\n }\n />\n\n {notLinked ? <PortalSupportNotLinked /> : null}\n\n {notLinked ? null : (\n <div className=\"flex flex-wrap items-center gap-3\">\n <span className=\"text-sm text-muted-foreground\">{t('staff.portal.support.filter.status', 'Status')}</span>\n <Select\n value={status}\n onValueChange={(value) => {\n setStatus(value)\n setPage(1)\n }}\n >\n <SelectTrigger className=\"w-48\" aria-label={t('staff.portal.support.filter.status', 'Status')}>\n <SelectValue />\n </SelectTrigger>\n <SelectContent>\n <SelectItem value={ALL_STATUSES}>{t('staff.portal.support.filter.all', 'All statuses')}</SelectItem>\n {(['received', 'in_progress', 'resolved'] as PortalSupportStatus[]).map((value) => (\n <SelectItem key={value} value={value}>\n {portalSupportStatusLabel(t, value)}\n </SelectItem>\n ))}\n </SelectContent>\n </Select>\n </div>\n )}\n\n {error ? (\n <ErrorMessage\n label={error}\n action={\n <Button type=\"button\" variant=\"outline\" size=\"sm\" onClick={() => void load()}>\n {t('staff.portal.support.retry', 'Try again')}\n </Button>\n }\n />\n ) : null}\n\n {notLinked ? null : isLoading ? (\n <div className=\"flex items-center justify-center py-16\">\n <Spinner />\n </div>\n ) : tickets.length === 0 ? (\n <EmptyState\n variant=\"subtle\"\n size=\"lg\"\n title={\n noCategories\n ? t('staff.portal.support.unavailableTitle', 'Support requests are not available yet')\n : t('staff.portal.support.emptyTitle', 'No support tickets yet')\n }\n description={\n noCategories\n ? t('staff.portal.support.unavailableDescription', 'Please contact us directly until support requests are enabled.')\n : t('staff.portal.support.emptyDescription', 'When you file a ticket, you can follow it here.')\n }\n actions={\n canCreate ? (\n <Button asChild>\n <Link href={newTicketHref}>{t('staff.portal.support.newTicket', 'New ticket')}</Link>\n </Button>\n ) : undefined\n }\n />\n ) : (\n <div className=\"space-y-3\">\n {tickets.map((ticket) => (\n <PortalCard key={ticket.id}>\n <div className=\"flex flex-wrap items-start justify-between gap-4\">\n <div className=\"min-w-0 space-y-1\">\n <Link\n href={`/${params.orgSlug}/portal/support/${ticket.id}`}\n className=\"text-base font-medium text-foreground hover:underline\"\n >\n {ticket.subject}\n </Link>\n <p className=\"text-sm text-muted-foreground\">\n {ticket.supportReference} \u00B7 {ticket.category.label}\n </p>\n </div>\n <div className=\"flex flex-col items-end gap-1\">\n <StatusBadge variant={PORTAL_SUPPORT_STATUS_VARIANTS[ticket.portalStatus] ?? 'neutral'} dot>\n {portalSupportStatusLabel(t, ticket.portalStatus)}\n </StatusBadge>\n <span className=\"text-xs text-muted-foreground\">\n {t('staff.portal.support.lastActivityAt', 'Last activity: {date}', {\n date: formatPortalDate(ticket.lastActivityAt, locale),\n })}\n </span>\n </div>\n </div>\n </PortalCard>\n ))}\n </div>\n )}\n\n {totalPages > 1 ? (\n <div className=\"flex items-center justify-between gap-3\">\n <Button variant=\"outline\" disabled={page <= 1} onClick={() => setPage((current) => Math.max(1, current - 1))}>\n {t('staff.portal.support.previous', 'Previous')}\n </Button>\n <span className=\"text-sm text-muted-foreground\">\n {t('staff.portal.support.pageIndicator', 'Page {page} / {total}', { page, total: totalPages })}\n </span>\n <Button\n variant=\"outline\"\n disabled={page >= totalPages}\n onClick={() => setPage((current) => Math.min(totalPages, current + 1))}\n >\n {t('staff.portal.support.next', 'Next')}\n </Button>\n </div>\n ) : null}\n </div>\n )\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
|
+
"names": ["options"]
|
|
7
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
const metadata = {
|
|
2
|
+
requireCustomerAuth: true,
|
|
3
|
+
requireCustomerFeatures: ["portal.support_tickets.view"],
|
|
4
|
+
titleKey: "staff.portal.support.title",
|
|
5
|
+
title: "Support",
|
|
6
|
+
nav: {
|
|
7
|
+
label: "Support",
|
|
8
|
+
labelKey: "staff.portal.support.nav",
|
|
9
|
+
group: "main",
|
|
10
|
+
order: 45,
|
|
11
|
+
icon: "life-buoy"
|
|
12
|
+
}
|
|
13
|
+
};
|
|
14
|
+
var page_meta_default = metadata;
|
|
15
|
+
export {
|
|
16
|
+
page_meta_default as default,
|
|
17
|
+
metadata
|
|
18
|
+
};
|
|
19
|
+
//# sourceMappingURL=page.meta.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../../../../../../src/modules/staff/frontend/%5BorgSlug%5D/portal/support/page.meta.ts"],
|
|
4
|
+
"sourcesContent": ["import type { PageMetadata } from '@open-mercato/shared/modules/registry'\n\nexport const metadata: PageMetadata = {\n requireCustomerAuth: true,\n requireCustomerFeatures: ['portal.support_tickets.view'],\n titleKey: 'staff.portal.support.title',\n title: 'Support',\n nav: {\n label: 'Support',\n labelKey: 'staff.portal.support.nav',\n group: 'main',\n order: 45,\n icon: 'life-buoy',\n },\n}\n\nexport default metadata\n"],
|
|
5
|
+
"mappings": "AAEO,MAAM,WAAyB;AAAA,EACpC,qBAAqB;AAAA,EACrB,yBAAyB,CAAC,6BAA6B;AAAA,EACvD,UAAU;AAAA,EACV,OAAO;AAAA,EACP,KAAK;AAAA,IACH,OAAO;AAAA,IACP,UAAU;AAAA,IACV,OAAO;AAAA,IACP,OAAO;AAAA,IACP,MAAM;AAAA,EACR;AACF;AAEA,IAAO,oBAAQ;",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|
|
@@ -5,7 +5,7 @@ const metadata = {
|
|
|
5
5
|
description: "Teams, roles, and employee rosters.",
|
|
6
6
|
author: "Open Mercato Team",
|
|
7
7
|
license: "MIT",
|
|
8
|
-
requires: ["planner", "resources"],
|
|
8
|
+
requires: ["planner", "resources", "dictionaries"],
|
|
9
9
|
ejectable: true
|
|
10
10
|
};
|
|
11
11
|
import { features } from "./acl.js";
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../../src/modules/staff/index.ts"],
|
|
4
|
-
"sourcesContent": ["import type { ModuleInfo } from '@open-mercato/shared/modules/registry'\n\nexport const metadata: ModuleInfo = {\n name: 'staff',\n title: 'Employees',\n version: '0.1.0',\n description: 'Teams, roles, and employee rosters.',\n author: 'Open Mercato Team',\n license: 'MIT',\n requires: ['planner', 'resources'],\n ejectable: true,\n}\n\nexport { features } from './acl'\n"],
|
|
5
|
-
"mappings": "AAEO,MAAM,WAAuB;AAAA,EAClC,MAAM;AAAA,EACN,OAAO;AAAA,EACP,SAAS;AAAA,EACT,aAAa;AAAA,EACb,QAAQ;AAAA,EACR,SAAS;AAAA,EACT,UAAU,CAAC,WAAW,
|
|
4
|
+
"sourcesContent": ["import type { ModuleInfo } from '@open-mercato/shared/modules/registry'\n\nexport const metadata: ModuleInfo = {\n name: 'staff',\n title: 'Employees',\n version: '0.1.0',\n description: 'Teams, roles, and employee rosters.',\n author: 'Open Mercato Team',\n license: 'MIT',\n requires: ['planner', 'resources', 'dictionaries'],\n ejectable: true,\n}\n\nexport { features } from './acl'\n"],
|
|
5
|
+
"mappings": "AAEO,MAAM,WAAuB;AAAA,EAClC,MAAM;AAAA,EACN,OAAO;AAAA,EACP,SAAS;AAAA,EACT,aAAa;AAAA,EACb,QAAQ;AAAA,EACR,SAAS;AAAA,EACT,UAAU,CAAC,WAAW,aAAa,cAAc;AAAA,EACjD,WAAW;AACb;AAEA,SAAS,gBAAgB;",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
|
@@ -25,6 +25,7 @@ const staffTimeEntryCommandIds = {
|
|
|
25
25
|
};
|
|
26
26
|
const staffTimeEntryCrudEvents = buildCrudEvents("timesheets.time_entry");
|
|
27
27
|
const staffTimeProjectCrudEvents = buildCrudEvents("timesheets.time_project");
|
|
28
|
+
const staffTimeProjectCrudIndexer = { entityType: "staff:staff_time_project" };
|
|
28
29
|
const staffTimeProjectMemberCrudEvents = buildCrudEvents("timesheets.time_project_member");
|
|
29
30
|
const staffTimeTaskCrudEvents = buildCrudEvents("timesheets.time_task");
|
|
30
31
|
const staffTimeTaskStatusCrudEvents = buildCrudEvents("timesheets.time_task_status");
|
|
@@ -43,6 +44,7 @@ export {
|
|
|
43
44
|
staffTimeEntryCommandIds,
|
|
44
45
|
staffTimeEntryCrudEvents,
|
|
45
46
|
staffTimeProjectCrudEvents,
|
|
47
|
+
staffTimeProjectCrudIndexer,
|
|
46
48
|
staffTimeProjectMemberCrudEvents,
|
|
47
49
|
staffTimeReportCrudEvents,
|
|
48
50
|
staffTimeTagCrudEvents,
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../../../src/modules/staff/lib/crud.ts"],
|
|
4
|
-
"sourcesContent": ["import type { CrudEventsConfig } from '@open-mercato/shared/lib/crud/types'\nimport type {\n StaffLeaveRequest,\n StaffTeam,\n StaffTeamMember,\n StaffTeamMemberActivity,\n StaffTeamMemberAddress,\n StaffTeamMemberComment,\n StaffTeamMemberJobHistory,\n StaffTeamRole,\n StaffTimeEntry,\n StaffTimeProject,\n StaffTimeProjectMember,\n StaffTimeReport,\n StaffTimeTag,\n StaffTimeTask,\n StaffTimeTaskComment,\n StaffTimeTaskStatus,\n} from '../data/entities'\n\nfunction buildCrudEvents<TEntity>(entity: string): CrudEventsConfig<TEntity> {\n return {\n module: 'staff',\n entity,\n persistent: true,\n buildPayload: (ctx) => ({\n id: ctx.identifiers.id,\n organizationId: ctx.identifiers.organizationId,\n tenantId: ctx.identifiers.tenantId,\n }),\n }\n}\n\nexport const staffTeamCrudEvents = buildCrudEvents<StaffTeam>('team')\nexport const staffTeamRoleCrudEvents = buildCrudEvents<StaffTeamRole>('team_role')\nexport const staffTeamMemberCrudEvents = buildCrudEvents<StaffTeamMember>('team_member')\nexport const staffLeaveRequestCrudEvents = buildCrudEvents<StaffLeaveRequest>('leave_request')\nexport const staffTeamMemberAddressCrudEvents = buildCrudEvents<StaffTeamMemberAddress>('address')\nexport const staffTeamMemberCommentCrudEvents = buildCrudEvents<StaffTeamMemberComment>('comment')\nexport const staffTeamMemberActivityCrudEvents = buildCrudEvents<StaffTeamMemberActivity>('activity')\nexport const staffTeamMemberJobHistoryCrudEvents = buildCrudEvents<StaffTeamMemberJobHistory>('job_history')\n\n// Timesheets\n/**\n * Command ids the time-entries CRUD route registers. Exported so the route and the\n * custom write routes that drive the same commands share one string instead of\n * re-typing it (#4970). The CRUD cache resource tag no longer follows these ids \u2014\n * the route declares an `events` config, which the factory prefers; see\n * `lib/timesheets/timeEntryCacheInvalidation.ts`.\n */\nexport const staffTimeEntryCommandIds = {\n create: 'staff.timesheets.time_entries.create',\n update: 'staff.timesheets.time_entries.update',\n delete: 'staff.timesheets.time_entries.delete',\n} as const\n\nexport const staffTimeEntryCrudEvents = buildCrudEvents<StaffTimeEntry>('timesheets.time_entry')\nexport const staffTimeProjectCrudEvents = buildCrudEvents<StaffTimeProject>('timesheets.time_project')\nexport const staffTimeProjectMemberCrudEvents = buildCrudEvents<StaffTimeProjectMember>('timesheets.time_project_member')\nexport const staffTimeTaskCrudEvents = buildCrudEvents<StaffTimeTask>('timesheets.time_task')\nexport const staffTimeTaskStatusCrudEvents = buildCrudEvents<StaffTimeTaskStatus>('timesheets.time_task_status')\nexport const staffTimeTaskCommentCrudEvents = buildCrudEvents<StaffTimeTaskComment>('timesheets.time_task_comment')\nexport const staffTimeTagCrudEvents = buildCrudEvents<StaffTimeTag>('timesheets.time_tag')\nexport const staffTimeReportCrudEvents = buildCrudEvents<StaffTimeReport>('timesheets.time_report')\n"],
|
|
5
|
-
"mappings": "AAoBA,SAAS,gBAAyB,QAA2C;AAC3E,SAAO;AAAA,IACL,QAAQ;AAAA,IACR;AAAA,IACA,YAAY;AAAA,IACZ,cAAc,CAAC,SAAS;AAAA,MACtB,IAAI,IAAI,YAAY;AAAA,MACpB,gBAAgB,IAAI,YAAY;AAAA,MAChC,UAAU,IAAI,YAAY;AAAA,IAC5B;AAAA,EACF;AACF;AAEO,MAAM,sBAAsB,gBAA2B,MAAM;AAC7D,MAAM,0BAA0B,gBAA+B,WAAW;AAC1E,MAAM,4BAA4B,gBAAiC,aAAa;AAChF,MAAM,8BAA8B,gBAAmC,eAAe;AACtF,MAAM,mCAAmC,gBAAwC,SAAS;AAC1F,MAAM,mCAAmC,gBAAwC,SAAS;AAC1F,MAAM,oCAAoC,gBAAyC,UAAU;AAC7F,MAAM,sCAAsC,gBAA2C,aAAa;AAUpG,MAAM,2BAA2B;AAAA,EACtC,QAAQ;AAAA,EACR,QAAQ;AAAA,EACR,QAAQ;AACV;AAEO,MAAM,2BAA2B,gBAAgC,uBAAuB;AACxF,MAAM,6BAA6B,gBAAkC,yBAAyB;AAC9F,MAAM,mCAAmC,gBAAwC,gCAAgC;AACjH,MAAM,0BAA0B,gBAA+B,sBAAsB;AACrF,MAAM,gCAAgC,gBAAqC,6BAA6B;AACxG,MAAM,iCAAiC,gBAAsC,8BAA8B;AAC3G,MAAM,yBAAyB,gBAA8B,qBAAqB;AAClF,MAAM,4BAA4B,gBAAiC,wBAAwB;",
|
|
4
|
+
"sourcesContent": ["import type { CrudEventsConfig, CrudIndexerConfig } from '@open-mercato/shared/lib/crud/types'\nimport type {\n StaffLeaveRequest,\n StaffTeam,\n StaffTeamMember,\n StaffTeamMemberActivity,\n StaffTeamMemberAddress,\n StaffTeamMemberComment,\n StaffTeamMemberJobHistory,\n StaffTeamRole,\n StaffTimeEntry,\n StaffTimeProject,\n StaffTimeProjectMember,\n StaffTimeReport,\n StaffTimeTag,\n StaffTimeTask,\n StaffTimeTaskComment,\n StaffTimeTaskStatus,\n} from '../data/entities'\n\nfunction buildCrudEvents<TEntity>(entity: string): CrudEventsConfig<TEntity> {\n return {\n module: 'staff',\n entity,\n persistent: true,\n buildPayload: (ctx) => ({\n id: ctx.identifiers.id,\n organizationId: ctx.identifiers.organizationId,\n tenantId: ctx.identifiers.tenantId,\n }),\n }\n}\n\nexport const staffTeamCrudEvents = buildCrudEvents<StaffTeam>('team')\nexport const staffTeamRoleCrudEvents = buildCrudEvents<StaffTeamRole>('team_role')\nexport const staffTeamMemberCrudEvents = buildCrudEvents<StaffTeamMember>('team_member')\nexport const staffLeaveRequestCrudEvents = buildCrudEvents<StaffLeaveRequest>('leave_request')\nexport const staffTeamMemberAddressCrudEvents = buildCrudEvents<StaffTeamMemberAddress>('address')\nexport const staffTeamMemberCommentCrudEvents = buildCrudEvents<StaffTeamMemberComment>('comment')\nexport const staffTeamMemberActivityCrudEvents = buildCrudEvents<StaffTeamMemberActivity>('activity')\nexport const staffTeamMemberJobHistoryCrudEvents = buildCrudEvents<StaffTeamMemberJobHistory>('job_history')\n\n// Timesheets\n/**\n * Command ids the time-entries CRUD route registers. Exported so the route and the\n * custom write routes that drive the same commands share one string instead of\n * re-typing it (#4970). The CRUD cache resource tag no longer follows these ids \u2014\n * the route declares an `events` config, which the factory prefers; see\n * `lib/timesheets/timeEntryCacheInvalidation.ts`.\n */\nexport const staffTimeEntryCommandIds = {\n create: 'staff.timesheets.time_entries.create',\n update: 'staff.timesheets.time_entries.update',\n delete: 'staff.timesheets.time_entries.delete',\n} as const\n\nexport const staffTimeEntryCrudEvents = buildCrudEvents<StaffTimeEntry>('timesheets.time_entry')\nexport const staffTimeProjectCrudEvents = buildCrudEvents<StaffTimeProject>('timesheets.time_project')\nexport const staffTimeProjectCrudIndexer: CrudIndexerConfig<StaffTimeProject> = { entityType: 'staff:staff_time_project' }\nexport const staffTimeProjectMemberCrudEvents = buildCrudEvents<StaffTimeProjectMember>('timesheets.time_project_member')\nexport const staffTimeTaskCrudEvents = buildCrudEvents<StaffTimeTask>('timesheets.time_task')\nexport const staffTimeTaskStatusCrudEvents = buildCrudEvents<StaffTimeTaskStatus>('timesheets.time_task_status')\nexport const staffTimeTaskCommentCrudEvents = buildCrudEvents<StaffTimeTaskComment>('timesheets.time_task_comment')\nexport const staffTimeTagCrudEvents = buildCrudEvents<StaffTimeTag>('timesheets.time_tag')\nexport const staffTimeReportCrudEvents = buildCrudEvents<StaffTimeReport>('timesheets.time_report')\n"],
|
|
5
|
+
"mappings": "AAoBA,SAAS,gBAAyB,QAA2C;AAC3E,SAAO;AAAA,IACL,QAAQ;AAAA,IACR;AAAA,IACA,YAAY;AAAA,IACZ,cAAc,CAAC,SAAS;AAAA,MACtB,IAAI,IAAI,YAAY;AAAA,MACpB,gBAAgB,IAAI,YAAY;AAAA,MAChC,UAAU,IAAI,YAAY;AAAA,IAC5B;AAAA,EACF;AACF;AAEO,MAAM,sBAAsB,gBAA2B,MAAM;AAC7D,MAAM,0BAA0B,gBAA+B,WAAW;AAC1E,MAAM,4BAA4B,gBAAiC,aAAa;AAChF,MAAM,8BAA8B,gBAAmC,eAAe;AACtF,MAAM,mCAAmC,gBAAwC,SAAS;AAC1F,MAAM,mCAAmC,gBAAwC,SAAS;AAC1F,MAAM,oCAAoC,gBAAyC,UAAU;AAC7F,MAAM,sCAAsC,gBAA2C,aAAa;AAUpG,MAAM,2BAA2B;AAAA,EACtC,QAAQ;AAAA,EACR,QAAQ;AAAA,EACR,QAAQ;AACV;AAEO,MAAM,2BAA2B,gBAAgC,uBAAuB;AACxF,MAAM,6BAA6B,gBAAkC,yBAAyB;AAC9F,MAAM,8BAAmE,EAAE,YAAY,2BAA2B;AAClH,MAAM,mCAAmC,gBAAwC,gCAAgC;AACjH,MAAM,0BAA0B,gBAA+B,sBAAsB;AACrF,MAAM,gCAAgC,gBAAqC,6BAA6B;AACxG,MAAM,iCAAiC,gBAAsC,8BAA8B;AAC3G,MAAM,yBAAyB,gBAA8B,qBAAqB;AAClF,MAAM,4BAA4B,gBAAiC,wBAAwB;",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
const SUPPORT_TICKET_ATTACHMENT_ENTITY_ID = "staff:staff_time_task";
|
|
2
|
+
const CUSTOMER_VISIBLE_ATTACHMENT_TAG = "customer-visible";
|
|
3
|
+
const CUSTOMER_UPLOAD_TAG_PREFIX = "customer-upload:";
|
|
4
|
+
function customerUploadTag(customerUserId) {
|
|
5
|
+
return `${CUSTOMER_UPLOAD_TAG_PREFIX}${customerUserId}`;
|
|
6
|
+
}
|
|
7
|
+
function isCustomerVisibleAttachmentTags(tags) {
|
|
8
|
+
return Array.isArray(tags) && tags.includes(CUSTOMER_VISIBLE_ATTACHMENT_TAG);
|
|
9
|
+
}
|
|
10
|
+
const SUPPORT_UPLOAD_ERROR_TRANSLATIONS = {
|
|
11
|
+
dangerous_executable: ["attachments.errors.dangerousExecutable", "Executable file types are not allowed as attachments."],
|
|
12
|
+
max_upload_size: ["attachments.errors.maxUploadSize", "Attachment exceeds the maximum upload size."],
|
|
13
|
+
active_content: ["attachments.errors.activeContentBlocked", "Active content uploads are not allowed."],
|
|
14
|
+
partition_unavailable: ["attachments.errors.partitionUnavailable", "Attachment partition is not available for this scope."],
|
|
15
|
+
quota_exceeded: ["attachments.errors.quotaExceeded", "Attachment storage quota exceeded for this tenant."],
|
|
16
|
+
quota_target_exists: ["attachments.errors.storagePathExists", "The target storage path already exists."],
|
|
17
|
+
quota_unavailable: ["attachments.errors.quotaUnavailable", "Storage quota accounting is unavailable."],
|
|
18
|
+
quota_recovery_unsupported: ["attachments.errors.quotaRecoveryUnsupported", "Storage driver cannot participate in quota recovery."],
|
|
19
|
+
storage_failed: ["attachments.errors.storageFailed", "Failed to store the attachment file."],
|
|
20
|
+
persistence_failed: ["attachments.errors.persistenceFailed", "Failed to persist attachment."]
|
|
21
|
+
};
|
|
22
|
+
const UPLOAD_FAILED_TRANSLATION = ["attachments.errors.uploadFailed", "Attachment upload failed."];
|
|
23
|
+
function translateSupportUploadError(code, translate) {
|
|
24
|
+
const entry = SUPPORT_UPLOAD_ERROR_TRANSLATIONS[code] ?? UPLOAD_FAILED_TRANSLATION;
|
|
25
|
+
return translate(entry[0], entry[1]);
|
|
26
|
+
}
|
|
27
|
+
export {
|
|
28
|
+
CUSTOMER_UPLOAD_TAG_PREFIX,
|
|
29
|
+
CUSTOMER_VISIBLE_ATTACHMENT_TAG,
|
|
30
|
+
SUPPORT_TICKET_ATTACHMENT_ENTITY_ID,
|
|
31
|
+
SUPPORT_UPLOAD_ERROR_TRANSLATIONS,
|
|
32
|
+
customerUploadTag,
|
|
33
|
+
isCustomerVisibleAttachmentTags,
|
|
34
|
+
translateSupportUploadError
|
|
35
|
+
};
|
|
36
|
+
//# sourceMappingURL=attachments.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../../../../src/modules/staff/lib/support/attachments.ts"],
|
|
4
|
+
"sourcesContent": ["import type { ScopedAttachmentUploadErrorCode } from '@open-mercato/core/modules/attachments/lib/scoped-upload-service'\n\nexport const SUPPORT_TICKET_ATTACHMENT_ENTITY_ID = 'staff:staff_time_task'\nexport const CUSTOMER_VISIBLE_ATTACHMENT_TAG = 'customer-visible'\nexport const CUSTOMER_UPLOAD_TAG_PREFIX = 'customer-upload:'\n\nexport function customerUploadTag(customerUserId: string): string {\n return `${CUSTOMER_UPLOAD_TAG_PREFIX}${customerUserId}`\n}\n\nexport function isCustomerVisibleAttachmentTags(tags: readonly string[] | null | undefined): boolean {\n return Array.isArray(tags) && tags.includes(CUSTOMER_VISIBLE_ATTACHMENT_TAG)\n}\n\ntype ErrorTranslation = readonly [key: string, fallback: string]\n\n/**\n * The scoped upload service reports failures as machine codes; the portal answers\n * with the attachments module's own translated message for the same condition, the\n * way `warranty_claims` does, so the client can show `error` verbatim.\n */\nexport const SUPPORT_UPLOAD_ERROR_TRANSLATIONS: Record<ScopedAttachmentUploadErrorCode, ErrorTranslation> = {\n dangerous_executable: ['attachments.errors.dangerousExecutable', 'Executable file types are not allowed as attachments.'],\n max_upload_size: ['attachments.errors.maxUploadSize', 'Attachment exceeds the maximum upload size.'],\n active_content: ['attachments.errors.activeContentBlocked', 'Active content uploads are not allowed.'],\n partition_unavailable: ['attachments.errors.partitionUnavailable', 'Attachment partition is not available for this scope.'],\n quota_exceeded: ['attachments.errors.quotaExceeded', 'Attachment storage quota exceeded for this tenant.'],\n quota_target_exists: ['attachments.errors.storagePathExists', 'The target storage path already exists.'],\n quota_unavailable: ['attachments.errors.quotaUnavailable', 'Storage quota accounting is unavailable.'],\n quota_recovery_unsupported: ['attachments.errors.quotaRecoveryUnsupported', 'Storage driver cannot participate in quota recovery.'],\n storage_failed: ['attachments.errors.storageFailed', 'Failed to store the attachment file.'],\n persistence_failed: ['attachments.errors.persistenceFailed', 'Failed to persist attachment.'],\n}\n\nconst UPLOAD_FAILED_TRANSLATION: ErrorTranslation = ['attachments.errors.uploadFailed', 'Attachment upload failed.']\n\nexport function translateSupportUploadError(\n code: string,\n translate: (key: string, fallback: string) => string,\n): string {\n const entry = (SUPPORT_UPLOAD_ERROR_TRANSLATIONS as Record<string, ErrorTranslation | undefined>)[code] ?? UPLOAD_FAILED_TRANSLATION\n return translate(entry[0], entry[1])\n}\n"],
|
|
5
|
+
"mappings": "AAEO,MAAM,sCAAsC;AAC5C,MAAM,kCAAkC;AACxC,MAAM,6BAA6B;AAEnC,SAAS,kBAAkB,gBAAgC;AAChE,SAAO,GAAG,0BAA0B,GAAG,cAAc;AACvD;AAEO,SAAS,gCAAgC,MAAqD;AACnG,SAAO,MAAM,QAAQ,IAAI,KAAK,KAAK,SAAS,+BAA+B;AAC7E;AASO,MAAM,oCAA+F;AAAA,EAC1G,sBAAsB,CAAC,0CAA0C,uDAAuD;AAAA,EACxH,iBAAiB,CAAC,oCAAoC,6CAA6C;AAAA,EACnG,gBAAgB,CAAC,2CAA2C,yCAAyC;AAAA,EACrG,uBAAuB,CAAC,2CAA2C,uDAAuD;AAAA,EAC1H,gBAAgB,CAAC,oCAAoC,oDAAoD;AAAA,EACzG,qBAAqB,CAAC,wCAAwC,yCAAyC;AAAA,EACvG,mBAAmB,CAAC,uCAAuC,0CAA0C;AAAA,EACrG,4BAA4B,CAAC,+CAA+C,sDAAsD;AAAA,EAClI,gBAAgB,CAAC,oCAAoC,sCAAsC;AAAA,EAC3F,oBAAoB,CAAC,wCAAwC,+BAA+B;AAC9F;AAEA,MAAM,4BAA8C,CAAC,mCAAmC,2BAA2B;AAE5G,SAAS,4BACd,MACA,WACQ;AACR,QAAM,QAAS,kCAAmF,IAAI,KAAK;AAC3G,SAAO,UAAU,MAAM,CAAC,GAAG,MAAM,CAAC,CAAC;AACrC;",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
import { Dictionary, DictionaryEntry } from "@open-mercato/core/modules/dictionaries/data/entities";
|
|
2
|
+
import { normalizeDictionaryValue } from "@open-mercato/core/modules/dictionaries/lib/utils";
|
|
3
|
+
import { isUniqueViolation } from "@open-mercato/shared/lib/crud/errors";
|
|
4
|
+
const SUPPORT_TICKET_CATEGORY_DICTIONARY_KEY = "staff.support_ticket_category";
|
|
5
|
+
const CATEGORY_DEFAULTS = [
|
|
6
|
+
{ value: "general-question", label: "General question", color: "#6366f1", icon: "lucide:message-circle-question" },
|
|
7
|
+
{ value: "technical-issue", label: "Technical issue", color: "#ef4444", icon: "lucide:bug" },
|
|
8
|
+
{ value: "change-request", label: "Change request", color: "#0d9488", icon: "lucide:git-pull-request" },
|
|
9
|
+
{ value: "billing", label: "Billing", color: "#f59e0b", icon: "lucide:receipt" }
|
|
10
|
+
];
|
|
11
|
+
async function findSupportCategoryDictionary(em, scope) {
|
|
12
|
+
return em.findOne(Dictionary, {
|
|
13
|
+
tenantId: scope.tenantId,
|
|
14
|
+
organizationId: scope.organizationId,
|
|
15
|
+
key: SUPPORT_TICKET_CATEGORY_DICTIONARY_KEY,
|
|
16
|
+
deletedAt: null
|
|
17
|
+
});
|
|
18
|
+
}
|
|
19
|
+
async function loadSupportTicketCategories(em, scope) {
|
|
20
|
+
const dictionary = await findSupportCategoryDictionary(em, scope);
|
|
21
|
+
if (!dictionary || !dictionary.isActive) return [];
|
|
22
|
+
const entries = await em.find(
|
|
23
|
+
DictionaryEntry,
|
|
24
|
+
{ dictionary, tenantId: scope.tenantId, organizationId: scope.organizationId },
|
|
25
|
+
{ orderBy: { position: "asc", label: "asc" } }
|
|
26
|
+
);
|
|
27
|
+
return entries.map((entry) => ({ value: entry.value, label: entry.label }));
|
|
28
|
+
}
|
|
29
|
+
async function findSupportTicketCategory(em, scope, value) {
|
|
30
|
+
const categories = await loadSupportTicketCategories(em, scope);
|
|
31
|
+
return categories.find((category) => category.value === value) ?? null;
|
|
32
|
+
}
|
|
33
|
+
async function seedSupportTicketCategories(em, scope) {
|
|
34
|
+
const forked = em.fork();
|
|
35
|
+
const existing = await forked.findOne(Dictionary, {
|
|
36
|
+
tenantId: scope.tenantId,
|
|
37
|
+
organizationId: scope.organizationId,
|
|
38
|
+
key: SUPPORT_TICKET_CATEGORY_DICTIONARY_KEY
|
|
39
|
+
});
|
|
40
|
+
if (existing) return false;
|
|
41
|
+
const now = /* @__PURE__ */ new Date();
|
|
42
|
+
const dictionary = forked.create(Dictionary, {
|
|
43
|
+
tenantId: scope.tenantId,
|
|
44
|
+
organizationId: scope.organizationId,
|
|
45
|
+
key: SUPPORT_TICKET_CATEGORY_DICTIONARY_KEY,
|
|
46
|
+
name: "Support ticket categories",
|
|
47
|
+
description: "Categories customers pick when they file a support ticket in the portal.",
|
|
48
|
+
isSystem: true,
|
|
49
|
+
isActive: true,
|
|
50
|
+
managerVisibility: "hidden",
|
|
51
|
+
createdAt: now,
|
|
52
|
+
updatedAt: now
|
|
53
|
+
});
|
|
54
|
+
forked.persist(dictionary);
|
|
55
|
+
CATEGORY_DEFAULTS.forEach((seed, index) => {
|
|
56
|
+
forked.persist(
|
|
57
|
+
forked.create(DictionaryEntry, {
|
|
58
|
+
dictionary,
|
|
59
|
+
tenantId: scope.tenantId,
|
|
60
|
+
organizationId: scope.organizationId,
|
|
61
|
+
value: seed.value,
|
|
62
|
+
normalizedValue: normalizeDictionaryValue(seed.value),
|
|
63
|
+
label: seed.label,
|
|
64
|
+
color: seed.color,
|
|
65
|
+
icon: seed.icon,
|
|
66
|
+
position: index,
|
|
67
|
+
createdAt: now,
|
|
68
|
+
updatedAt: now
|
|
69
|
+
})
|
|
70
|
+
);
|
|
71
|
+
});
|
|
72
|
+
try {
|
|
73
|
+
await forked.flush();
|
|
74
|
+
} catch (err) {
|
|
75
|
+
if (isUniqueViolation(err)) return false;
|
|
76
|
+
throw err;
|
|
77
|
+
}
|
|
78
|
+
return true;
|
|
79
|
+
}
|
|
80
|
+
export {
|
|
81
|
+
SUPPORT_TICKET_CATEGORY_DICTIONARY_KEY,
|
|
82
|
+
findSupportCategoryDictionary,
|
|
83
|
+
findSupportTicketCategory,
|
|
84
|
+
loadSupportTicketCategories,
|
|
85
|
+
seedSupportTicketCategories
|
|
86
|
+
};
|
|
87
|
+
//# sourceMappingURL=dictionaries.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../../../../src/modules/staff/lib/support/dictionaries.ts"],
|
|
4
|
+
"sourcesContent": ["import type { EntityManager } from '@mikro-orm/postgresql'\nimport { Dictionary, DictionaryEntry } from '@open-mercato/core/modules/dictionaries/data/entities'\nimport { normalizeDictionaryValue } from '@open-mercato/core/modules/dictionaries/lib/utils'\nimport { isUniqueViolation } from '@open-mercato/shared/lib/crud/errors'\n\nexport const SUPPORT_TICKET_CATEGORY_DICTIONARY_KEY = 'staff.support_ticket_category'\n\ntype SupportScope = {\n tenantId: string\n organizationId: string\n}\n\nexport type SupportTicketCategoryOption = {\n value: string\n label: string\n}\n\ntype CategorySeed = {\n value: string\n label: string\n color: string\n icon: string\n}\n\nconst CATEGORY_DEFAULTS: CategorySeed[] = [\n { value: 'general-question', label: 'General question', color: '#6366f1', icon: 'lucide:message-circle-question' },\n { value: 'technical-issue', label: 'Technical issue', color: '#ef4444', icon: 'lucide:bug' },\n { value: 'change-request', label: 'Change request', color: '#0d9488', icon: 'lucide:git-pull-request' },\n { value: 'billing', label: 'Billing', color: '#f59e0b', icon: 'lucide:receipt' },\n]\n\nexport async function findSupportCategoryDictionary(\n em: EntityManager,\n scope: SupportScope,\n): Promise<Dictionary | null> {\n return em.findOne(Dictionary, {\n tenantId: scope.tenantId,\n organizationId: scope.organizationId,\n key: SUPPORT_TICKET_CATEGORY_DICTIONARY_KEY,\n deletedAt: null,\n })\n}\n\nexport async function loadSupportTicketCategories(\n em: EntityManager,\n scope: SupportScope,\n): Promise<SupportTicketCategoryOption[]> {\n const dictionary = await findSupportCategoryDictionary(em, scope)\n if (!dictionary || !dictionary.isActive) return []\n const entries = await em.find(\n DictionaryEntry,\n { dictionary, tenantId: scope.tenantId, organizationId: scope.organizationId },\n { orderBy: { position: 'asc', label: 'asc' } },\n )\n return entries.map((entry) => ({ value: entry.value, label: entry.label }))\n}\n\nexport async function findSupportTicketCategory(\n em: EntityManager,\n scope: SupportScope,\n value: string,\n): Promise<SupportTicketCategoryOption | null> {\n const categories = await loadSupportTicketCategories(em, scope)\n return categories.find((category) => category.value === value) ?? null\n}\n\n/**\n * Seeds the category dictionary together with its default entries, once per organization.\n * Runs only when no support-category dictionary row exists yet (live or soft-deleted), so\n * a re-run never re-adds a default category an admin deleted. Dictionary and entries are\n * written in one flush on a fork, so a failed or lost race leaves nothing half-seeded.\n * Returns `true` when this call created the dictionary.\n */\nexport async function seedSupportTicketCategories(em: EntityManager, scope: SupportScope): Promise<boolean> {\n const forked = em.fork()\n const existing = await forked.findOne(Dictionary, {\n tenantId: scope.tenantId,\n organizationId: scope.organizationId,\n key: SUPPORT_TICKET_CATEGORY_DICTIONARY_KEY,\n })\n if (existing) return false\n const now = new Date()\n const dictionary = forked.create(Dictionary, {\n tenantId: scope.tenantId,\n organizationId: scope.organizationId,\n key: SUPPORT_TICKET_CATEGORY_DICTIONARY_KEY,\n name: 'Support ticket categories',\n description: 'Categories customers pick when they file a support ticket in the portal.',\n isSystem: true,\n isActive: true,\n managerVisibility: 'hidden',\n createdAt: now,\n updatedAt: now,\n })\n forked.persist(dictionary)\n CATEGORY_DEFAULTS.forEach((seed, index) => {\n forked.persist(\n forked.create(DictionaryEntry, {\n dictionary,\n tenantId: scope.tenantId,\n organizationId: scope.organizationId,\n value: seed.value,\n normalizedValue: normalizeDictionaryValue(seed.value),\n label: seed.label,\n color: seed.color,\n icon: seed.icon,\n position: index,\n createdAt: now,\n updatedAt: now,\n }),\n )\n })\n try {\n await forked.flush()\n } catch (err) {\n if (isUniqueViolation(err)) return false\n throw err\n }\n return true\n}\n"],
|
|
5
|
+
"mappings": "AACA,SAAS,YAAY,uBAAuB;AAC5C,SAAS,gCAAgC;AACzC,SAAS,yBAAyB;AAE3B,MAAM,yCAAyC;AAmBtD,MAAM,oBAAoC;AAAA,EACxC,EAAE,OAAO,oBAAoB,OAAO,oBAAoB,OAAO,WAAW,MAAM,iCAAiC;AAAA,EACjH,EAAE,OAAO,mBAAmB,OAAO,mBAAmB,OAAO,WAAW,MAAM,aAAa;AAAA,EAC3F,EAAE,OAAO,kBAAkB,OAAO,kBAAkB,OAAO,WAAW,MAAM,0BAA0B;AAAA,EACtG,EAAE,OAAO,WAAW,OAAO,WAAW,OAAO,WAAW,MAAM,iBAAiB;AACjF;AAEA,eAAsB,8BACpB,IACA,OAC4B;AAC5B,SAAO,GAAG,QAAQ,YAAY;AAAA,IAC5B,UAAU,MAAM;AAAA,IAChB,gBAAgB,MAAM;AAAA,IACtB,KAAK;AAAA,IACL,WAAW;AAAA,EACb,CAAC;AACH;AAEA,eAAsB,4BACpB,IACA,OACwC;AACxC,QAAM,aAAa,MAAM,8BAA8B,IAAI,KAAK;AAChE,MAAI,CAAC,cAAc,CAAC,WAAW,SAAU,QAAO,CAAC;AACjD,QAAM,UAAU,MAAM,GAAG;AAAA,IACvB;AAAA,IACA,EAAE,YAAY,UAAU,MAAM,UAAU,gBAAgB,MAAM,eAAe;AAAA,IAC7E,EAAE,SAAS,EAAE,UAAU,OAAO,OAAO,MAAM,EAAE;AAAA,EAC/C;AACA,SAAO,QAAQ,IAAI,CAAC,WAAW,EAAE,OAAO,MAAM,OAAO,OAAO,MAAM,MAAM,EAAE;AAC5E;AAEA,eAAsB,0BACpB,IACA,OACA,OAC6C;AAC7C,QAAM,aAAa,MAAM,4BAA4B,IAAI,KAAK;AAC9D,SAAO,WAAW,KAAK,CAAC,aAAa,SAAS,UAAU,KAAK,KAAK;AACpE;AASA,eAAsB,4BAA4B,IAAmB,OAAuC;AAC1G,QAAM,SAAS,GAAG,KAAK;AACvB,QAAM,WAAW,MAAM,OAAO,QAAQ,YAAY;AAAA,IAChD,UAAU,MAAM;AAAA,IAChB,gBAAgB,MAAM;AAAA,IACtB,KAAK;AAAA,EACP,CAAC;AACD,MAAI,SAAU,QAAO;AACrB,QAAM,MAAM,oBAAI,KAAK;AACrB,QAAM,aAAa,OAAO,OAAO,YAAY;AAAA,IAC3C,UAAU,MAAM;AAAA,IAChB,gBAAgB,MAAM;AAAA,IACtB,KAAK;AAAA,IACL,MAAM;AAAA,IACN,aAAa;AAAA,IACb,UAAU;AAAA,IACV,UAAU;AAAA,IACV,mBAAmB;AAAA,IACnB,WAAW;AAAA,IACX,WAAW;AAAA,EACb,CAAC;AACD,SAAO,QAAQ,UAAU;AACzB,oBAAkB,QAAQ,CAAC,MAAM,UAAU;AACzC,WAAO;AAAA,MACL,OAAO,OAAO,iBAAiB;AAAA,QAC7B;AAAA,QACA,UAAU,MAAM;AAAA,QAChB,gBAAgB,MAAM;AAAA,QACtB,OAAO,KAAK;AAAA,QACZ,iBAAiB,yBAAyB,KAAK,KAAK;AAAA,QACpD,OAAO,KAAK;AAAA,QACZ,OAAO,KAAK;AAAA,QACZ,MAAM,KAAK;AAAA,QACX,UAAU;AAAA,QACV,WAAW;AAAA,QACX,WAAW;AAAA,MACb,CAAC;AAAA,IACH;AAAA,EACF,CAAC;AACD,MAAI;AACF,UAAM,OAAO,MAAM;AAAA,EACrB,SAAS,KAAK;AACZ,QAAI,kBAAkB,GAAG,EAAG,QAAO;AACnC,UAAM;AAAA,EACR;AACA,SAAO;AACT;",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
import { createLogger } from "@open-mercato/shared/lib/logger";
|
|
2
|
+
import { findOneWithDecryption } from "@open-mercato/shared/lib/encryption/find";
|
|
3
|
+
import { notificationTypes } from "../../notifications.js";
|
|
4
|
+
import { StaffSupportTicket, StaffTeamMember, StaffTimeProject, StaffTimeTask } from "../../data/entities.js";
|
|
5
|
+
import { resolveAssignmentGraceDaysFromContainer } from "../time-tracking/projectAssignment.js";
|
|
6
|
+
import {
|
|
7
|
+
loadActiveAssignerUserIds,
|
|
8
|
+
notifyProjectWork,
|
|
9
|
+
resolveProjectWorkRecipients
|
|
10
|
+
} from "../time-tracking/workRecipients.js";
|
|
11
|
+
const logger = createLogger("staff").child({ component: "lib/support/notifications" });
|
|
12
|
+
const TYPE_BY_KIND = {
|
|
13
|
+
created: "staff.support_ticket.created",
|
|
14
|
+
customer_replied: "staff.support_ticket.customer_replied"
|
|
15
|
+
};
|
|
16
|
+
async function resolveAssigneeUserId(em, task) {
|
|
17
|
+
if (!task.assigneeStaffMemberId) return null;
|
|
18
|
+
const member = await findOneWithDecryption(
|
|
19
|
+
em,
|
|
20
|
+
StaffTeamMember,
|
|
21
|
+
{ id: task.assigneeStaffMemberId, tenantId: task.tenantId, organizationId: task.organizationId, deletedAt: null },
|
|
22
|
+
void 0,
|
|
23
|
+
{ tenantId: task.tenantId, organizationId: task.organizationId }
|
|
24
|
+
);
|
|
25
|
+
return member?.userId ?? null;
|
|
26
|
+
}
|
|
27
|
+
async function notifySupportTicket(container, input) {
|
|
28
|
+
const typeDef = notificationTypes.find((type) => type.type === TYPE_BY_KIND[input.kind]);
|
|
29
|
+
if (!typeDef) return null;
|
|
30
|
+
const scope = { tenantId: input.tenantId, organizationId: input.organizationId };
|
|
31
|
+
const em = container.resolve("em").fork();
|
|
32
|
+
const task = await em.findOne(StaffTimeTask, { id: input.taskId, ...scope, source: "portal", deletedAt: null });
|
|
33
|
+
if (!task) return null;
|
|
34
|
+
const ticket = await findOneWithDecryption(em, StaffSupportTicket, { taskId: task.id, ...scope, deletedAt: null }, void 0, scope);
|
|
35
|
+
if (!ticket) return null;
|
|
36
|
+
const project = await em.findOne(StaffTimeProject, { id: task.timeProjectId, ...scope }, { fields: ["id", "name"] });
|
|
37
|
+
const graceDays = await resolveAssignmentGraceDaysFromContainer(container, input.tenantId);
|
|
38
|
+
const assigneeUserId = await resolveAssigneeUserId(em, task);
|
|
39
|
+
const audience = input.kind === "created" ? [assigneeUserId, ...await loadActiveAssignerUserIds(em, scope, { timeProjectId: task.timeProjectId, graceDays })] : [assigneeUserId];
|
|
40
|
+
const recipients = await resolveProjectWorkRecipients(em, scope, {
|
|
41
|
+
timeProjectId: task.timeProjectId,
|
|
42
|
+
audienceUserIds: audience,
|
|
43
|
+
graceDays
|
|
44
|
+
});
|
|
45
|
+
const snapshot = ticket.customerSnapshot ?? {};
|
|
46
|
+
const customerName = typeof snapshot.reporterDisplayName === "string" && snapshot.reporterDisplayName || typeof snapshot.displayName === "string" && snapshot.displayName || "";
|
|
47
|
+
const request = ticket.customerRequestSnapshot;
|
|
48
|
+
const subject = request && typeof request.subject === "string" ? request.subject : task.title;
|
|
49
|
+
const result = await notifyProjectWork(container, scope, {
|
|
50
|
+
typeDef,
|
|
51
|
+
recipients,
|
|
52
|
+
build: {
|
|
53
|
+
titleVariables: { reference: ticket.supportReference },
|
|
54
|
+
bodyVariables: {
|
|
55
|
+
reference: ticket.supportReference,
|
|
56
|
+
customer: customerName,
|
|
57
|
+
subject,
|
|
58
|
+
category: ticket.categorySnapshot?.label ?? ticket.categoryValue,
|
|
59
|
+
project: project?.name ?? ""
|
|
60
|
+
},
|
|
61
|
+
sourceEntityType: "staff:staff_time_task",
|
|
62
|
+
sourceEntityId: task.id,
|
|
63
|
+
linkHref: `/backend/staff/time-tracking/support?task=${encodeURIComponent(task.id)}`,
|
|
64
|
+
groupKey: `staff:support-ticket-${input.kind}:${task.id}`
|
|
65
|
+
}
|
|
66
|
+
});
|
|
67
|
+
const fresh = await findOneWithDecryption(em, StaffSupportTicket, { id: ticket.id, ...scope }, void 0, scope);
|
|
68
|
+
if (fresh) {
|
|
69
|
+
if (result.notifiedCount === 0) {
|
|
70
|
+
if (!fresh.unnotifiedAt) {
|
|
71
|
+
fresh.unnotifiedAt = /* @__PURE__ */ new Date();
|
|
72
|
+
fresh.updatedAt = /* @__PURE__ */ new Date();
|
|
73
|
+
await em.flush();
|
|
74
|
+
}
|
|
75
|
+
logger.warn("staff support ticket notification reached nobody", {
|
|
76
|
+
tenantId: input.tenantId,
|
|
77
|
+
organizationId: input.organizationId,
|
|
78
|
+
timeProjectId: task.timeProjectId,
|
|
79
|
+
ticketId: ticket.id,
|
|
80
|
+
taskId: task.id,
|
|
81
|
+
eventType: typeDef.type,
|
|
82
|
+
emptyStep: result.step
|
|
83
|
+
});
|
|
84
|
+
} else if (fresh.unnotifiedAt) {
|
|
85
|
+
fresh.unnotifiedAt = null;
|
|
86
|
+
fresh.updatedAt = /* @__PURE__ */ new Date();
|
|
87
|
+
await em.flush();
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
return result;
|
|
91
|
+
}
|
|
92
|
+
async function clearSupportTicketUnnotified(em, input) {
|
|
93
|
+
const scope = { tenantId: input.tenantId, organizationId: input.organizationId };
|
|
94
|
+
const ticket = await findOneWithDecryption(
|
|
95
|
+
em,
|
|
96
|
+
StaffSupportTicket,
|
|
97
|
+
{ taskId: input.taskId, ...scope, deletedAt: null, unnotifiedAt: { $ne: null } },
|
|
98
|
+
void 0,
|
|
99
|
+
scope
|
|
100
|
+
);
|
|
101
|
+
if (!ticket) return false;
|
|
102
|
+
ticket.unnotifiedAt = null;
|
|
103
|
+
ticket.updatedAt = /* @__PURE__ */ new Date();
|
|
104
|
+
await em.flush();
|
|
105
|
+
return true;
|
|
106
|
+
}
|
|
107
|
+
export {
|
|
108
|
+
clearSupportTicketUnnotified,
|
|
109
|
+
notifySupportTicket
|
|
110
|
+
};
|
|
111
|
+
//# sourceMappingURL=notifications.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../../../../src/modules/staff/lib/support/notifications.ts"],
|
|
4
|
+
"sourcesContent": ["import type { EntityManager } from '@mikro-orm/postgresql'\nimport { createLogger } from '@open-mercato/shared/lib/logger'\nimport { findOneWithDecryption } from '@open-mercato/shared/lib/encryption/find'\nimport { notificationTypes } from '../../notifications'\nimport { StaffSupportTicket, StaffTeamMember, StaffTimeProject, StaffTimeTask } from '../../data/entities'\nimport { resolveAssignmentGraceDaysFromContainer } from '../time-tracking/projectAssignment'\nimport {\n loadActiveAssignerUserIds,\n notifyProjectWork,\n resolveProjectWorkRecipients,\n} from '../time-tracking/workRecipients'\n\nconst logger = createLogger('staff').child({ component: 'lib/support/notifications' })\n\ntype ContainerLike = { resolve: <T = unknown>(name: string) => T }\n\nexport type SupportNotificationKind = 'created' | 'customer_replied'\n\nconst TYPE_BY_KIND: Record<SupportNotificationKind, string> = {\n created: 'staff.support_ticket.created',\n customer_replied: 'staff.support_ticket.customer_replied',\n}\n\nasync function resolveAssigneeUserId(em: EntityManager, task: StaffTimeTask): Promise<string | null> {\n if (!task.assigneeStaffMemberId) return null\n const member = await findOneWithDecryption(\n em,\n StaffTeamMember,\n { id: task.assigneeStaffMemberId, tenantId: task.tenantId, organizationId: task.organizationId, deletedAt: null },\n undefined,\n { tenantId: task.tenantId, organizationId: task.organizationId },\n )\n return member?.userId ?? null\n}\n\n/**\n * Notifies staff about a support ticket through the project escalation chain and\n * records the outcome on the ticket: `unnotified_at` is set (earliest value kept)\n * when the chain reached nobody, and cleared when a notification reaches someone.\n * Notifying is best-effort \u2014 the ticket or reply that triggered it is never undone.\n */\nexport async function notifySupportTicket(\n container: ContainerLike,\n input: { taskId: string; tenantId: string; organizationId: string; kind: SupportNotificationKind },\n): Promise<{ notifiedCount: number; step: number } | null> {\n const typeDef = notificationTypes.find((type) => type.type === TYPE_BY_KIND[input.kind])\n if (!typeDef) return null\n const scope = { tenantId: input.tenantId, organizationId: input.organizationId }\n const em = (container.resolve('em') as EntityManager).fork()\n const task = await em.findOne(StaffTimeTask, { id: input.taskId, ...scope, source: 'portal', deletedAt: null })\n if (!task) return null\n const ticket = await findOneWithDecryption(em, StaffSupportTicket, { taskId: task.id, ...scope, deletedAt: null }, undefined, scope)\n if (!ticket) return null\n const project = await em.findOne(StaffTimeProject, { id: task.timeProjectId, ...scope }, { fields: ['id', 'name'] })\n const graceDays = await resolveAssignmentGraceDaysFromContainer(container, input.tenantId)\n\n const assigneeUserId = await resolveAssigneeUserId(em, task)\n const audience =\n input.kind === 'created'\n ? [assigneeUserId, ...(await loadActiveAssignerUserIds(em, scope, { timeProjectId: task.timeProjectId, graceDays }))]\n : [assigneeUserId]\n const recipients = await resolveProjectWorkRecipients(em, scope, {\n timeProjectId: task.timeProjectId,\n audienceUserIds: audience,\n graceDays,\n })\n\n const snapshot = ticket.customerSnapshot ?? {}\n const customerName =\n (typeof snapshot.reporterDisplayName === 'string' && snapshot.reporterDisplayName) ||\n (typeof snapshot.displayName === 'string' && snapshot.displayName) ||\n ''\n const request = ticket.customerRequestSnapshot\n const subject = request && typeof request.subject === 'string' ? request.subject : task.title\n const result = await notifyProjectWork(container, scope, {\n typeDef,\n recipients,\n build: {\n titleVariables: { reference: ticket.supportReference },\n bodyVariables: {\n reference: ticket.supportReference,\n customer: customerName,\n subject,\n category: ticket.categorySnapshot?.label ?? ticket.categoryValue,\n project: project?.name ?? '',\n },\n sourceEntityType: 'staff:staff_time_task',\n sourceEntityId: task.id,\n linkHref: `/backend/staff/time-tracking/support?task=${encodeURIComponent(task.id)}`,\n groupKey: `staff:support-ticket-${input.kind}:${task.id}`,\n },\n })\n\n const fresh = await findOneWithDecryption(em, StaffSupportTicket, { id: ticket.id, ...scope }, undefined, scope)\n if (fresh) {\n if (result.notifiedCount === 0) {\n if (!fresh.unnotifiedAt) {\n fresh.unnotifiedAt = new Date()\n fresh.updatedAt = new Date()\n await em.flush()\n }\n logger.warn('staff support ticket notification reached nobody', {\n tenantId: input.tenantId,\n organizationId: input.organizationId,\n timeProjectId: task.timeProjectId,\n ticketId: ticket.id,\n taskId: task.id,\n eventType: typeDef.type,\n emptyStep: result.step,\n })\n } else if (fresh.unnotifiedAt) {\n fresh.unnotifiedAt = null\n fresh.updatedAt = new Date()\n await em.flush()\n }\n }\n return result\n}\n\n/** The ticket got an assignee, so somebody now owns it: clear the \"nobody notified\" flag. */\nexport async function clearSupportTicketUnnotified(\n em: EntityManager,\n input: { taskId: string; tenantId: string; organizationId: string },\n): Promise<boolean> {\n const scope = { tenantId: input.tenantId, organizationId: input.organizationId }\n const ticket = await findOneWithDecryption(\n em,\n StaffSupportTicket,\n { taskId: input.taskId, ...scope, deletedAt: null, unnotifiedAt: { $ne: null } },\n undefined,\n scope,\n )\n if (!ticket) return false\n ticket.unnotifiedAt = null\n ticket.updatedAt = new Date()\n await em.flush()\n return true\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|