@open-mercato/core 0.10.1-develop.7717.1.2befe9c634 → 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,346 @@
|
|
|
1
|
+
"use client"
|
|
2
|
+
|
|
3
|
+
import * as React from 'react'
|
|
4
|
+
import Link from 'next/link'
|
|
5
|
+
import { useRouter } from 'next/navigation'
|
|
6
|
+
import { ArrowLeft, Paperclip, Trash2 } from 'lucide-react'
|
|
7
|
+
import { replaceWithPageReload } from '@open-mercato/shared/lib/navigation/pageReload'
|
|
8
|
+
import { useT } from '@open-mercato/shared/lib/i18n/context'
|
|
9
|
+
import { Button } from '@open-mercato/ui/primitives/button'
|
|
10
|
+
import { IconButton } from '@open-mercato/ui/primitives/icon-button'
|
|
11
|
+
import { Input } from '@open-mercato/ui/primitives/input'
|
|
12
|
+
import { Label } from '@open-mercato/ui/primitives/label'
|
|
13
|
+
import { Spinner } from '@open-mercato/ui/primitives/spinner'
|
|
14
|
+
import { Textarea } from '@open-mercato/ui/primitives/textarea'
|
|
15
|
+
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@open-mercato/ui/primitives/select'
|
|
16
|
+
import { ErrorMessage } from '@open-mercato/ui/backend/detail'
|
|
17
|
+
import { flash } from '@open-mercato/ui/backend/FlashMessages'
|
|
18
|
+
import { useGuardedMutation } from '@open-mercato/ui/backend/injection/useGuardedMutation'
|
|
19
|
+
import { apiCall } from '@open-mercato/ui/backend/utils/apiCall'
|
|
20
|
+
import { usePortalContext } from '@open-mercato/ui/portal/PortalContext'
|
|
21
|
+
import { PortalPageHeader } from '@open-mercato/ui/portal/components/PortalPageHeader'
|
|
22
|
+
import { PortalCard } from '@open-mercato/ui/portal/components/PortalCard'
|
|
23
|
+
import { EmptyState } from '@open-mercato/ui/primitives/empty-state'
|
|
24
|
+
import { getTelemetryRuntime } from '@open-mercato/shared/lib/telemetry/runtime'
|
|
25
|
+
import {
|
|
26
|
+
ATTACHMENT_ACCEPT_TYPES,
|
|
27
|
+
resolvePortalMaxUploadBytes,
|
|
28
|
+
validateAttachmentFile,
|
|
29
|
+
} from '@open-mercato/core/modules/staff/lib/support-ui/portalAttachmentValidation'
|
|
30
|
+
import { rememberFailedUploads } from '@open-mercato/core/modules/staff/lib/support-ui/portalPendingUploads'
|
|
31
|
+
import {
|
|
32
|
+
formatFileSize,
|
|
33
|
+
isPortalSupportAccountNotLinked,
|
|
34
|
+
type PortalSupportOptions,
|
|
35
|
+
} from '@open-mercato/core/modules/staff/lib/support-ui/portalSupport'
|
|
36
|
+
import { PortalSupportNotLinked } from '@open-mercato/core/modules/staff/lib/support-ui/PortalSupportNotLinked'
|
|
37
|
+
|
|
38
|
+
type Props = { params: { orgSlug: string } }
|
|
39
|
+
|
|
40
|
+
type CreateResponse = { ok?: boolean; id?: string; supportReference?: string; error?: string }
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
const MUTATION_CONTEXT_ID = 'staff-portal-support-new'
|
|
44
|
+
|
|
45
|
+
export default function PortalSupportNewTicketPage({ params }: Props) {
|
|
46
|
+
const t = useT()
|
|
47
|
+
const router = useRouter()
|
|
48
|
+
const { auth } = usePortalContext()
|
|
49
|
+
const { user, loading } = auth
|
|
50
|
+
const guardedMutation = useGuardedMutation<Record<string, unknown>>({
|
|
51
|
+
contextId: MUTATION_CONTEXT_ID,
|
|
52
|
+
blockedMessage: t('staff.portal.support.blocked', 'This action is not available right now.'),
|
|
53
|
+
})
|
|
54
|
+
|
|
55
|
+
const [options, setOptions] = React.useState<PortalSupportOptions | null>(null)
|
|
56
|
+
const [optionsLoading, setOptionsLoading] = React.useState(true)
|
|
57
|
+
const [optionsError, setOptionsError] = React.useState<string | null>(null)
|
|
58
|
+
const [notLinked, setNotLinked] = React.useState(false)
|
|
59
|
+
const [optionsAttempt, setOptionsAttempt] = React.useState(0)
|
|
60
|
+
const [categoryValue, setCategoryValue] = React.useState('')
|
|
61
|
+
const [subject, setSubject] = React.useState('')
|
|
62
|
+
const [description, setDescription] = React.useState('')
|
|
63
|
+
const [files, setFiles] = React.useState<File[]>([])
|
|
64
|
+
const [submitting, setSubmitting] = React.useState(false)
|
|
65
|
+
const [error, setError] = React.useState<string | null>(null)
|
|
66
|
+
const fileInputRef = React.useRef<HTMLInputElement | null>(null)
|
|
67
|
+
|
|
68
|
+
React.useEffect(() => {
|
|
69
|
+
if (!loading && !user) replaceWithPageReload(`/${params.orgSlug}/portal/login`)
|
|
70
|
+
}, [loading, user, params.orgSlug])
|
|
71
|
+
|
|
72
|
+
React.useEffect(() => {
|
|
73
|
+
if (!user) return
|
|
74
|
+
let cancelled = false
|
|
75
|
+
void (async () => {
|
|
76
|
+
setOptionsLoading(true)
|
|
77
|
+
setOptionsError(null)
|
|
78
|
+
try {
|
|
79
|
+
const response = await apiCall<PortalSupportOptions>('/api/staff/portal/support-tickets/options', { credentials: 'include' })
|
|
80
|
+
if (cancelled) return
|
|
81
|
+
setNotLinked(isPortalSupportAccountNotLinked(response))
|
|
82
|
+
if (response.ok && response.result) {
|
|
83
|
+
setOptions(response.result)
|
|
84
|
+
} else {
|
|
85
|
+
setOptions(null)
|
|
86
|
+
if (!isPortalSupportAccountNotLinked(response)) {
|
|
87
|
+
setOptionsError(t('staff.portal.support.new.optionsLoadFailed', 'Could not load the support form.'))
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
} catch (err) {
|
|
91
|
+
if (cancelled) return
|
|
92
|
+
getTelemetryRuntime()?.reportError(err, { module: 'staff', code: 'staff.portal_support_options_failed' })
|
|
93
|
+
setOptions(null)
|
|
94
|
+
setOptionsError(t('staff.portal.support.new.optionsLoadFailed', 'Could not load the support form.'))
|
|
95
|
+
} finally {
|
|
96
|
+
if (!cancelled) setOptionsLoading(false)
|
|
97
|
+
}
|
|
98
|
+
})()
|
|
99
|
+
return () => {
|
|
100
|
+
cancelled = true
|
|
101
|
+
}
|
|
102
|
+
}, [user, optionsAttempt, t])
|
|
103
|
+
|
|
104
|
+
const maxUploadBytes = resolvePortalMaxUploadBytes(options?.maxUploadBytes)
|
|
105
|
+
|
|
106
|
+
const addFiles = React.useCallback(async (list: FileList | null) => {
|
|
107
|
+
if (!list) return
|
|
108
|
+
const accepted: File[] = []
|
|
109
|
+
for (const file of Array.from(list)) {
|
|
110
|
+
const validationError = await validateAttachmentFile(file, t, maxUploadBytes)
|
|
111
|
+
if (validationError) {
|
|
112
|
+
flash(`${file.name}: ${validationError}`, 'error')
|
|
113
|
+
continue
|
|
114
|
+
}
|
|
115
|
+
accepted.push(file)
|
|
116
|
+
}
|
|
117
|
+
if (accepted.length) setFiles((current) => [...current, ...accepted])
|
|
118
|
+
if (fileInputRef.current) fileInputRef.current.value = ''
|
|
119
|
+
}, [t, maxUploadBytes])
|
|
120
|
+
|
|
121
|
+
const subjectValid = subject.trim().length >= 3
|
|
122
|
+
const descriptionValid = description.trim().length >= 1
|
|
123
|
+
const canSubmit = Boolean(categoryValue) && subjectValid && descriptionValid && !submitting
|
|
124
|
+
|
|
125
|
+
const submit = React.useCallback(async () => {
|
|
126
|
+
if (!canSubmit) return
|
|
127
|
+
setSubmitting(true)
|
|
128
|
+
setError(null)
|
|
129
|
+
const payload = { categoryValue, subject: subject.trim(), description: description.trim() }
|
|
130
|
+
try {
|
|
131
|
+
const created = await guardedMutation.runMutation({
|
|
132
|
+
operation: () =>
|
|
133
|
+
apiCall<CreateResponse>('/api/staff/portal/support-tickets', {
|
|
134
|
+
method: 'POST',
|
|
135
|
+
headers: { 'Content-Type': 'application/json' },
|
|
136
|
+
credentials: 'include',
|
|
137
|
+
body: JSON.stringify(payload),
|
|
138
|
+
}),
|
|
139
|
+
context: {
|
|
140
|
+
moduleId: 'staff',
|
|
141
|
+
entityId: 'staff.support_ticket',
|
|
142
|
+
operation: 'portal_support_ticket_create',
|
|
143
|
+
formId: MUTATION_CONTEXT_ID,
|
|
144
|
+
retryLastMutation: guardedMutation.retryLastMutation,
|
|
145
|
+
},
|
|
146
|
+
mutationPayload: payload,
|
|
147
|
+
})
|
|
148
|
+
const ticketId = created.ok ? created.result?.id ?? null : null
|
|
149
|
+
if (!ticketId) {
|
|
150
|
+
if (created.status === 429) {
|
|
151
|
+
setError(t('staff.portal.support.new.rateLimited', 'You have filed many tickets recently. Please try again later.'))
|
|
152
|
+
} else if (created.status === 422) {
|
|
153
|
+
setError(t('staff.support.errors.categoryUnavailable', 'Support requests in this category are not available right now.'))
|
|
154
|
+
} else {
|
|
155
|
+
setError(t('staff.portal.support.new.createFailed', 'Could not file the ticket. Please try again.'))
|
|
156
|
+
}
|
|
157
|
+
return
|
|
158
|
+
}
|
|
159
|
+
const failed: File[] = []
|
|
160
|
+
for (const file of files) {
|
|
161
|
+
const form = new FormData()
|
|
162
|
+
form.set('file', file)
|
|
163
|
+
try {
|
|
164
|
+
const uploaded = await guardedMutation.runMutation({
|
|
165
|
+
operation: () =>
|
|
166
|
+
apiCall(`/api/staff/portal/support-tickets/${encodeURIComponent(ticketId)}/attachments`, {
|
|
167
|
+
method: 'POST',
|
|
168
|
+
credentials: 'include',
|
|
169
|
+
body: form,
|
|
170
|
+
}),
|
|
171
|
+
context: {
|
|
172
|
+
moduleId: 'staff',
|
|
173
|
+
entityId: 'attachments.attachment',
|
|
174
|
+
operation: 'portal_support_attachment_upload',
|
|
175
|
+
formId: MUTATION_CONTEXT_ID,
|
|
176
|
+
ticketId,
|
|
177
|
+
retryLastMutation: guardedMutation.retryLastMutation,
|
|
178
|
+
},
|
|
179
|
+
mutationPayload: { ticketId, fileName: file.name },
|
|
180
|
+
})
|
|
181
|
+
if (!uploaded.ok) failed.push(file)
|
|
182
|
+
} catch (err) {
|
|
183
|
+
getTelemetryRuntime()?.reportError(err, { module: 'staff', code: 'staff.portal_support_upload_failed' })
|
|
184
|
+
failed.push(file)
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
rememberFailedUploads(ticketId, failed)
|
|
188
|
+
flash(
|
|
189
|
+
t('staff.portal.support.new.created', 'Ticket {reference} filed.', { reference: created.result?.supportReference ?? '' }),
|
|
190
|
+
'success',
|
|
191
|
+
)
|
|
192
|
+
router.push(`/${params.orgSlug}/portal/support/${ticketId}`)
|
|
193
|
+
} catch (err) {
|
|
194
|
+
getTelemetryRuntime()?.reportError(err, { module: 'staff', code: 'staff.portal_support_create_failed' })
|
|
195
|
+
setError(t('staff.portal.support.new.createFailed', 'Could not file the ticket. Please try again.'))
|
|
196
|
+
} finally {
|
|
197
|
+
setSubmitting(false)
|
|
198
|
+
}
|
|
199
|
+
}, [canSubmit, categoryValue, subject, description, files, guardedMutation, params.orgSlug, router, t])
|
|
200
|
+
|
|
201
|
+
if (loading || optionsLoading) {
|
|
202
|
+
return (
|
|
203
|
+
<div className="flex items-center justify-center py-20">
|
|
204
|
+
<Spinner />
|
|
205
|
+
</div>
|
|
206
|
+
)
|
|
207
|
+
}
|
|
208
|
+
if (!user) return null
|
|
209
|
+
|
|
210
|
+
const categories = options?.categories ?? []
|
|
211
|
+
|
|
212
|
+
return (
|
|
213
|
+
<div className="space-y-6">
|
|
214
|
+
<div>
|
|
215
|
+
<Button variant="ghost" asChild>
|
|
216
|
+
<Link href={`/${params.orgSlug}/portal/support`}>
|
|
217
|
+
<ArrowLeft className="mr-2 h-4 w-4" />
|
|
218
|
+
{t('staff.portal.support.backToList', 'All tickets')}
|
|
219
|
+
</Link>
|
|
220
|
+
</Button>
|
|
221
|
+
</div>
|
|
222
|
+
<PortalPageHeader
|
|
223
|
+
title={t('staff.portal.support.new.title', 'New support ticket')}
|
|
224
|
+
description={t('staff.portal.support.new.description', 'Tell us what you need. Please do not include passwords or other secrets.')}
|
|
225
|
+
/>
|
|
226
|
+
{error ? <ErrorMessage label={error} /> : null}
|
|
227
|
+
{notLinked ? (
|
|
228
|
+
<PortalSupportNotLinked />
|
|
229
|
+
) : optionsError ? (
|
|
230
|
+
<ErrorMessage
|
|
231
|
+
label={optionsError}
|
|
232
|
+
action={
|
|
233
|
+
<Button type="button" variant="outline" size="sm" onClick={() => setOptionsAttempt((attempt) => attempt + 1)}>
|
|
234
|
+
{t('staff.portal.support.retry', 'Try again')}
|
|
235
|
+
</Button>
|
|
236
|
+
}
|
|
237
|
+
/>
|
|
238
|
+
) : !options?.canCreate || categories.length === 0 ? (
|
|
239
|
+
<EmptyState
|
|
240
|
+
variant="subtle"
|
|
241
|
+
size="lg"
|
|
242
|
+
title={t('staff.portal.support.unavailableTitle', 'Support requests are not available yet')}
|
|
243
|
+
description={t('staff.portal.support.unavailableDescription', 'Please contact us directly until support requests are enabled.')}
|
|
244
|
+
/>
|
|
245
|
+
) : (
|
|
246
|
+
<PortalCard>
|
|
247
|
+
<form
|
|
248
|
+
className="space-y-5"
|
|
249
|
+
onSubmit={(event) => {
|
|
250
|
+
event.preventDefault()
|
|
251
|
+
void submit()
|
|
252
|
+
}}
|
|
253
|
+
onKeyDown={(event) => {
|
|
254
|
+
if ((event.metaKey || event.ctrlKey) && event.key === 'Enter') {
|
|
255
|
+
event.preventDefault()
|
|
256
|
+
void submit()
|
|
257
|
+
}
|
|
258
|
+
}}
|
|
259
|
+
>
|
|
260
|
+
<div className="space-y-2">
|
|
261
|
+
<Label htmlFor="support-category">{t('staff.portal.support.new.category', 'Category')}</Label>
|
|
262
|
+
<Select value={categoryValue} onValueChange={setCategoryValue}>
|
|
263
|
+
<SelectTrigger id="support-category" aria-label={t('staff.portal.support.new.category', 'Category')}>
|
|
264
|
+
<SelectValue placeholder={t('staff.portal.support.new.categoryPlaceholder', 'Choose a category')} />
|
|
265
|
+
</SelectTrigger>
|
|
266
|
+
<SelectContent>
|
|
267
|
+
{categories.map((category) => (
|
|
268
|
+
<SelectItem key={category.value} value={category.value}>
|
|
269
|
+
{category.label}
|
|
270
|
+
</SelectItem>
|
|
271
|
+
))}
|
|
272
|
+
</SelectContent>
|
|
273
|
+
</Select>
|
|
274
|
+
</div>
|
|
275
|
+
<div className="space-y-2">
|
|
276
|
+
<Label htmlFor="support-subject">{t('staff.portal.support.new.subject', 'Subject')}</Label>
|
|
277
|
+
<Input
|
|
278
|
+
id="support-subject"
|
|
279
|
+
value={subject}
|
|
280
|
+
maxLength={200}
|
|
281
|
+
onChange={(event) => setSubject(event.target.value)}
|
|
282
|
+
placeholder={t('staff.portal.support.new.subjectPlaceholder', 'Short summary of the problem')}
|
|
283
|
+
/>
|
|
284
|
+
</div>
|
|
285
|
+
<div className="space-y-2">
|
|
286
|
+
<Label htmlFor="support-description">{t('staff.portal.support.new.descriptionLabel', 'Description')}</Label>
|
|
287
|
+
<Textarea
|
|
288
|
+
id="support-description"
|
|
289
|
+
rows={6}
|
|
290
|
+
value={description}
|
|
291
|
+
maxLength={10000}
|
|
292
|
+
onChange={(event) => setDescription(event.target.value)}
|
|
293
|
+
placeholder={t('staff.portal.support.new.descriptionPlaceholder', 'What happened, and what did you expect?')}
|
|
294
|
+
/>
|
|
295
|
+
</div>
|
|
296
|
+
<div className="space-y-2">
|
|
297
|
+
<Label>{t('staff.portal.support.attachments.title', 'Files')}</Label>
|
|
298
|
+
<input
|
|
299
|
+
ref={fileInputRef}
|
|
300
|
+
type="file"
|
|
301
|
+
multiple
|
|
302
|
+
accept={ATTACHMENT_ACCEPT_TYPES}
|
|
303
|
+
className="sr-only"
|
|
304
|
+
data-testid="support-new-file-input"
|
|
305
|
+
onChange={(event) => void addFiles(event.target.files)}
|
|
306
|
+
/>
|
|
307
|
+
<Button type="button" variant="outline" onClick={() => fileInputRef.current?.click()}>
|
|
308
|
+
<Paperclip className="mr-2 h-4 w-4" />
|
|
309
|
+
{t('staff.portal.support.attachments.add', 'Add files')}
|
|
310
|
+
</Button>
|
|
311
|
+
{files.length ? (
|
|
312
|
+
<ul className="space-y-1">
|
|
313
|
+
{files.map((file, index) => (
|
|
314
|
+
<li key={`${file.name}-${index}`} className="flex items-center justify-between gap-2 text-sm">
|
|
315
|
+
<span className="truncate">
|
|
316
|
+
{file.name} · {formatFileSize(t, file.size)}
|
|
317
|
+
</span>
|
|
318
|
+
<IconButton
|
|
319
|
+
type="button"
|
|
320
|
+
variant="ghost"
|
|
321
|
+
size="sm"
|
|
322
|
+
aria-label={t('staff.portal.support.attachments.remove', 'Remove file')}
|
|
323
|
+
onClick={() => setFiles((current) => current.filter((_, position) => position !== index))}
|
|
324
|
+
>
|
|
325
|
+
<Trash2 className="h-4 w-4" />
|
|
326
|
+
</IconButton>
|
|
327
|
+
</li>
|
|
328
|
+
))}
|
|
329
|
+
</ul>
|
|
330
|
+
) : null}
|
|
331
|
+
</div>
|
|
332
|
+
<div className="flex justify-end gap-2">
|
|
333
|
+
<Button type="button" variant="outline" asChild>
|
|
334
|
+
<Link href={`/${params.orgSlug}/portal/support`}>{t('staff.portal.support.new.cancel', 'Cancel')}</Link>
|
|
335
|
+
</Button>
|
|
336
|
+
<Button type="submit" disabled={!canSubmit}>
|
|
337
|
+
{submitting ? <Spinner className="mr-2 h-4 w-4" /> : null}
|
|
338
|
+
{t('staff.portal.support.new.submit', 'File ticket')}
|
|
339
|
+
</Button>
|
|
340
|
+
</div>
|
|
341
|
+
</form>
|
|
342
|
+
</PortalCard>
|
|
343
|
+
)}
|
|
344
|
+
</div>
|
|
345
|
+
)
|
|
346
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import type { PageMetadata } from '@open-mercato/shared/modules/registry'
|
|
2
|
+
|
|
3
|
+
export const metadata: PageMetadata = {
|
|
4
|
+
requireCustomerAuth: true,
|
|
5
|
+
requireCustomerFeatures: ['portal.support_tickets.view'],
|
|
6
|
+
titleKey: 'staff.portal.support.title',
|
|
7
|
+
title: 'Support',
|
|
8
|
+
nav: {
|
|
9
|
+
label: 'Support',
|
|
10
|
+
labelKey: 'staff.portal.support.nav',
|
|
11
|
+
group: 'main',
|
|
12
|
+
order: 45,
|
|
13
|
+
icon: 'life-buoy',
|
|
14
|
+
},
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export default metadata
|
|
@@ -0,0 +1,253 @@
|
|
|
1
|
+
"use client"
|
|
2
|
+
|
|
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
|
+
type PortalSupportOptions,
|
|
27
|
+
type PortalSupportStatus,
|
|
28
|
+
type PortalSupportTicketList,
|
|
29
|
+
type PortalSupportTicketListItem,
|
|
30
|
+
} from '@open-mercato/core/modules/staff/lib/support-ui/portalSupport'
|
|
31
|
+
import { PortalSupportNotLinked } from '@open-mercato/core/modules/staff/lib/support-ui/PortalSupportNotLinked'
|
|
32
|
+
|
|
33
|
+
type Props = { params: { orgSlug: string } }
|
|
34
|
+
|
|
35
|
+
const PAGE_SIZE = 20
|
|
36
|
+
const ALL_STATUSES = 'all'
|
|
37
|
+
|
|
38
|
+
export default function PortalSupportTicketsPage({ params }: Props) {
|
|
39
|
+
const t = useT()
|
|
40
|
+
const locale = useLocale()
|
|
41
|
+
const { auth } = usePortalContext()
|
|
42
|
+
const { user, loading } = auth
|
|
43
|
+
|
|
44
|
+
const [tickets, setTickets] = React.useState<PortalSupportTicketListItem[]>([])
|
|
45
|
+
const [options, setOptions] = React.useState<PortalSupportOptions | null>(null)
|
|
46
|
+
const [status, setStatus] = React.useState<string>(ALL_STATUSES)
|
|
47
|
+
const [page, setPage] = React.useState(1)
|
|
48
|
+
const [totalPages, setTotalPages] = React.useState(1)
|
|
49
|
+
const [isLoading, setIsLoading] = React.useState(true)
|
|
50
|
+
const [error, setError] = React.useState<string | null>(null)
|
|
51
|
+
const [notLinked, setNotLinked] = React.useState(false)
|
|
52
|
+
|
|
53
|
+
React.useEffect(() => {
|
|
54
|
+
if (!loading && !user) replaceWithPageReload(`/${params.orgSlug}/portal/login`)
|
|
55
|
+
}, [loading, user, params.orgSlug])
|
|
56
|
+
|
|
57
|
+
const loadSeqRef = React.useRef(0)
|
|
58
|
+
|
|
59
|
+
const load = React.useCallback(async (options: { silent?: boolean } = {}) => {
|
|
60
|
+
if (!user) return
|
|
61
|
+
const requestSeq = loadSeqRef.current + 1
|
|
62
|
+
loadSeqRef.current = requestSeq
|
|
63
|
+
const isStale = () => requestSeq !== loadSeqRef.current
|
|
64
|
+
if (!options.silent) setIsLoading(true)
|
|
65
|
+
setError(null)
|
|
66
|
+
try {
|
|
67
|
+
const statusQuery = status === ALL_STATUSES ? '' : `&status=${encodeURIComponent(status)}`
|
|
68
|
+
const [list, opts] = await Promise.all([
|
|
69
|
+
apiCall<PortalSupportTicketList>(`/api/staff/portal/support-tickets?page=${page}&pageSize=${PAGE_SIZE}${statusQuery}`, {
|
|
70
|
+
credentials: 'include',
|
|
71
|
+
}),
|
|
72
|
+
apiCall<PortalSupportOptions>('/api/staff/portal/support-tickets/options', { credentials: 'include' }),
|
|
73
|
+
])
|
|
74
|
+
if (isStale()) return
|
|
75
|
+
if (isPortalSupportAccountNotLinked(list)) {
|
|
76
|
+
setNotLinked(true)
|
|
77
|
+
setTickets([])
|
|
78
|
+
setOptions(null)
|
|
79
|
+
return
|
|
80
|
+
}
|
|
81
|
+
setNotLinked(false)
|
|
82
|
+
if (!list.ok) {
|
|
83
|
+
setError(t('staff.portal.support.loadFailed', 'Could not load your support tickets.'))
|
|
84
|
+
if (!options.silent) setTickets([])
|
|
85
|
+
return
|
|
86
|
+
}
|
|
87
|
+
setTickets(list.result?.items ?? [])
|
|
88
|
+
setTotalPages(Math.max(1, list.result?.totalPages ?? 1))
|
|
89
|
+
setOptions(opts.ok ? opts.result : null)
|
|
90
|
+
} catch (err) {
|
|
91
|
+
if (isStale()) return
|
|
92
|
+
getTelemetryRuntime()?.reportError(err, { module: 'staff', code: 'staff.portal_support_list_failed' })
|
|
93
|
+
setError(t('staff.portal.support.loadFailed', 'Could not load your support tickets.'))
|
|
94
|
+
if (!options.silent) setTickets([])
|
|
95
|
+
} finally {
|
|
96
|
+
if (!isStale()) setIsLoading(false)
|
|
97
|
+
}
|
|
98
|
+
}, [user, page, status, t])
|
|
99
|
+
|
|
100
|
+
React.useEffect(() => {
|
|
101
|
+
void load()
|
|
102
|
+
}, [load])
|
|
103
|
+
|
|
104
|
+
usePortalAppEvent(PORTAL_SUPPORT_UPDATED_EVENT, () => {
|
|
105
|
+
void load({ silent: true })
|
|
106
|
+
}, [load])
|
|
107
|
+
|
|
108
|
+
if (loading) {
|
|
109
|
+
return (
|
|
110
|
+
<div className="flex items-center justify-center py-20">
|
|
111
|
+
<Spinner />
|
|
112
|
+
</div>
|
|
113
|
+
)
|
|
114
|
+
}
|
|
115
|
+
if (!user) return null
|
|
116
|
+
|
|
117
|
+
const canCreate = options?.canCreate === true
|
|
118
|
+
const noCategories = options !== null && (options.categories ?? []).length === 0
|
|
119
|
+
const newTicketHref = `/${params.orgSlug}/portal/support/new`
|
|
120
|
+
|
|
121
|
+
return (
|
|
122
|
+
<div className="space-y-6">
|
|
123
|
+
<PortalPageHeader
|
|
124
|
+
title={t('staff.portal.support.title', 'Support')}
|
|
125
|
+
description={t('staff.portal.support.description', 'Ask for help and follow the progress of your requests.')}
|
|
126
|
+
action={
|
|
127
|
+
canCreate && !notLinked ? (
|
|
128
|
+
<Button asChild>
|
|
129
|
+
<Link href={newTicketHref}>
|
|
130
|
+
<Plus className="mr-2 h-4 w-4" />
|
|
131
|
+
{t('staff.portal.support.newTicket', 'New ticket')}
|
|
132
|
+
</Link>
|
|
133
|
+
</Button>
|
|
134
|
+
) : undefined
|
|
135
|
+
}
|
|
136
|
+
/>
|
|
137
|
+
|
|
138
|
+
{notLinked ? <PortalSupportNotLinked /> : null}
|
|
139
|
+
|
|
140
|
+
{notLinked ? null : (
|
|
141
|
+
<div className="flex flex-wrap items-center gap-3">
|
|
142
|
+
<span className="text-sm text-muted-foreground">{t('staff.portal.support.filter.status', 'Status')}</span>
|
|
143
|
+
<Select
|
|
144
|
+
value={status}
|
|
145
|
+
onValueChange={(value) => {
|
|
146
|
+
setStatus(value)
|
|
147
|
+
setPage(1)
|
|
148
|
+
}}
|
|
149
|
+
>
|
|
150
|
+
<SelectTrigger className="w-48" aria-label={t('staff.portal.support.filter.status', 'Status')}>
|
|
151
|
+
<SelectValue />
|
|
152
|
+
</SelectTrigger>
|
|
153
|
+
<SelectContent>
|
|
154
|
+
<SelectItem value={ALL_STATUSES}>{t('staff.portal.support.filter.all', 'All statuses')}</SelectItem>
|
|
155
|
+
{(['received', 'in_progress', 'resolved'] as PortalSupportStatus[]).map((value) => (
|
|
156
|
+
<SelectItem key={value} value={value}>
|
|
157
|
+
{portalSupportStatusLabel(t, value)}
|
|
158
|
+
</SelectItem>
|
|
159
|
+
))}
|
|
160
|
+
</SelectContent>
|
|
161
|
+
</Select>
|
|
162
|
+
</div>
|
|
163
|
+
)}
|
|
164
|
+
|
|
165
|
+
{error ? (
|
|
166
|
+
<ErrorMessage
|
|
167
|
+
label={error}
|
|
168
|
+
action={
|
|
169
|
+
<Button type="button" variant="outline" size="sm" onClick={() => void load()}>
|
|
170
|
+
{t('staff.portal.support.retry', 'Try again')}
|
|
171
|
+
</Button>
|
|
172
|
+
}
|
|
173
|
+
/>
|
|
174
|
+
) : null}
|
|
175
|
+
|
|
176
|
+
{notLinked ? null : isLoading ? (
|
|
177
|
+
<div className="flex items-center justify-center py-16">
|
|
178
|
+
<Spinner />
|
|
179
|
+
</div>
|
|
180
|
+
) : tickets.length === 0 ? (
|
|
181
|
+
<EmptyState
|
|
182
|
+
variant="subtle"
|
|
183
|
+
size="lg"
|
|
184
|
+
title={
|
|
185
|
+
noCategories
|
|
186
|
+
? t('staff.portal.support.unavailableTitle', 'Support requests are not available yet')
|
|
187
|
+
: t('staff.portal.support.emptyTitle', 'No support tickets yet')
|
|
188
|
+
}
|
|
189
|
+
description={
|
|
190
|
+
noCategories
|
|
191
|
+
? t('staff.portal.support.unavailableDescription', 'Please contact us directly until support requests are enabled.')
|
|
192
|
+
: t('staff.portal.support.emptyDescription', 'When you file a ticket, you can follow it here.')
|
|
193
|
+
}
|
|
194
|
+
actions={
|
|
195
|
+
canCreate ? (
|
|
196
|
+
<Button asChild>
|
|
197
|
+
<Link href={newTicketHref}>{t('staff.portal.support.newTicket', 'New ticket')}</Link>
|
|
198
|
+
</Button>
|
|
199
|
+
) : undefined
|
|
200
|
+
}
|
|
201
|
+
/>
|
|
202
|
+
) : (
|
|
203
|
+
<div className="space-y-3">
|
|
204
|
+
{tickets.map((ticket) => (
|
|
205
|
+
<PortalCard key={ticket.id}>
|
|
206
|
+
<div className="flex flex-wrap items-start justify-between gap-4">
|
|
207
|
+
<div className="min-w-0 space-y-1">
|
|
208
|
+
<Link
|
|
209
|
+
href={`/${params.orgSlug}/portal/support/${ticket.id}`}
|
|
210
|
+
className="text-base font-medium text-foreground hover:underline"
|
|
211
|
+
>
|
|
212
|
+
{ticket.subject}
|
|
213
|
+
</Link>
|
|
214
|
+
<p className="text-sm text-muted-foreground">
|
|
215
|
+
{ticket.supportReference} · {ticket.category.label}
|
|
216
|
+
</p>
|
|
217
|
+
</div>
|
|
218
|
+
<div className="flex flex-col items-end gap-1">
|
|
219
|
+
<StatusBadge variant={PORTAL_SUPPORT_STATUS_VARIANTS[ticket.portalStatus] ?? 'neutral'} dot>
|
|
220
|
+
{portalSupportStatusLabel(t, ticket.portalStatus)}
|
|
221
|
+
</StatusBadge>
|
|
222
|
+
<span className="text-xs text-muted-foreground">
|
|
223
|
+
{t('staff.portal.support.lastActivityAt', 'Last activity: {date}', {
|
|
224
|
+
date: formatPortalDate(ticket.lastActivityAt, locale),
|
|
225
|
+
})}
|
|
226
|
+
</span>
|
|
227
|
+
</div>
|
|
228
|
+
</div>
|
|
229
|
+
</PortalCard>
|
|
230
|
+
))}
|
|
231
|
+
</div>
|
|
232
|
+
)}
|
|
233
|
+
|
|
234
|
+
{totalPages > 1 ? (
|
|
235
|
+
<div className="flex items-center justify-between gap-3">
|
|
236
|
+
<Button variant="outline" disabled={page <= 1} onClick={() => setPage((current) => Math.max(1, current - 1))}>
|
|
237
|
+
{t('staff.portal.support.previous', 'Previous')}
|
|
238
|
+
</Button>
|
|
239
|
+
<span className="text-sm text-muted-foreground">
|
|
240
|
+
{t('staff.portal.support.pageIndicator', 'Page {page} / {total}', { page, total: totalPages })}
|
|
241
|
+
</span>
|
|
242
|
+
<Button
|
|
243
|
+
variant="outline"
|
|
244
|
+
disabled={page >= totalPages}
|
|
245
|
+
onClick={() => setPage((current) => Math.min(totalPages, current + 1))}
|
|
246
|
+
>
|
|
247
|
+
{t('staff.portal.support.next', 'Next')}
|
|
248
|
+
</Button>
|
|
249
|
+
</div>
|
|
250
|
+
) : null}
|
|
251
|
+
</div>
|
|
252
|
+
)
|
|
253
|
+
}
|