@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
|
@@ -11,12 +11,14 @@ import {
|
|
|
11
11
|
useStaffMemberDirectory,
|
|
12
12
|
useTagDirectory,
|
|
13
13
|
useTagLabels,
|
|
14
|
+
useUnassignedTaskCount,
|
|
14
15
|
type BoardSelf,
|
|
15
16
|
} from './boardDirectory'
|
|
16
17
|
import {
|
|
17
18
|
countActiveBoardFilters,
|
|
18
19
|
withAssigneeFilter,
|
|
19
20
|
withStatusFilter,
|
|
21
|
+
withUnassignedFilter,
|
|
20
22
|
withTagFilter,
|
|
21
23
|
withoutTagFilter,
|
|
22
24
|
type BoardFilters,
|
|
@@ -43,6 +45,7 @@ type BoardFilterSurfaceProps = {
|
|
|
43
45
|
onChange: (next: BoardFilters) => void
|
|
44
46
|
self: BoardSelf | null
|
|
45
47
|
statuses: readonly BoardStatus[]
|
|
48
|
+
timeProjectId?: string | null
|
|
46
49
|
}
|
|
47
50
|
|
|
48
51
|
function chipClassName(dashed: boolean): string {
|
|
@@ -122,6 +125,7 @@ export function AddBoardFilterPopover({
|
|
|
122
125
|
onChange,
|
|
123
126
|
self,
|
|
124
127
|
statuses,
|
|
128
|
+
timeProjectId,
|
|
125
129
|
trigger,
|
|
126
130
|
testId = 'board-filter-add',
|
|
127
131
|
}: BoardFilterSurfaceProps & {
|
|
@@ -140,6 +144,11 @@ export function AddBoardFilterPopover({
|
|
|
140
144
|
)
|
|
141
145
|
|
|
142
146
|
const assignedToMeLabel = t('staff.time_tracking.board.filters.assignedToMe', 'Assigned to me')
|
|
147
|
+
const unassignedCount = useUnassignedTaskCount(timeProjectId)
|
|
148
|
+
const unassignedLabel =
|
|
149
|
+
typeof unassignedCount.data === 'number'
|
|
150
|
+
? t('staff.time_tracking.board.filters.unassignedWithCount', 'Unassigned ({count})', { count: unassignedCount.data })
|
|
151
|
+
: t('staff.time_tracking.board.filters.unassigned', 'Unassigned')
|
|
143
152
|
|
|
144
153
|
return (
|
|
145
154
|
<Popover open={open} onOpenChange={setOpen}>
|
|
@@ -164,6 +173,16 @@ export function AddBoardFilterPopover({
|
|
|
164
173
|
/>
|
|
165
174
|
) : null}
|
|
166
175
|
|
|
176
|
+
<PickerRow
|
|
177
|
+
label={unassignedLabel}
|
|
178
|
+
selected={filters.unassigned}
|
|
179
|
+
testId="board-filter-unassigned"
|
|
180
|
+
onSelect={() => {
|
|
181
|
+
onChange(withUnassignedFilter(filters, !filters.unassigned))
|
|
182
|
+
setOpen(false)
|
|
183
|
+
}}
|
|
184
|
+
/>
|
|
185
|
+
|
|
167
186
|
{otherMembers.length > 0 ? (
|
|
168
187
|
<div className="mt-2 flex flex-col gap-0.5">
|
|
169
188
|
<span className="px-2.5 py-1 text-xs font-semibold uppercase leading-normal tracking-wide text-muted-foreground">
|
|
@@ -256,8 +275,10 @@ export function BoardFilterChips({
|
|
|
256
275
|
onChange,
|
|
257
276
|
self,
|
|
258
277
|
statuses,
|
|
278
|
+
timeProjectId,
|
|
259
279
|
}: BoardFilterSurfaceProps): React.ReactElement {
|
|
260
280
|
const t = useT()
|
|
281
|
+
const unassignedCount = useUnassignedTaskCount(filters.unassigned ? timeProjectId : null)
|
|
261
282
|
|
|
262
283
|
const assigneeIds = React.useMemo(
|
|
263
284
|
() =>
|
|
@@ -300,6 +321,19 @@ export function BoardFilterChips({
|
|
|
300
321
|
/>
|
|
301
322
|
) : null}
|
|
302
323
|
|
|
324
|
+
{filters.unassigned ? (
|
|
325
|
+
<FilterChip
|
|
326
|
+
label={
|
|
327
|
+
typeof unassignedCount.data === 'number'
|
|
328
|
+
? t('staff.time_tracking.board.filters.unassignedWithCount', 'Unassigned ({count})', { count: unassignedCount.data })
|
|
329
|
+
: t('staff.time_tracking.board.filters.unassigned', 'Unassigned')
|
|
330
|
+
}
|
|
331
|
+
removeLabel={removeLabel(t('staff.time_tracking.board.filters.unassigned', 'Unassigned'))}
|
|
332
|
+
testId="board-filter-chip-unassigned"
|
|
333
|
+
onRemove={() => onChange(withUnassignedFilter(filters, false))}
|
|
334
|
+
/>
|
|
335
|
+
) : null}
|
|
336
|
+
|
|
303
337
|
{activeStatus ? (
|
|
304
338
|
<FilterChip
|
|
305
339
|
label={activeStatus.name}
|
|
@@ -327,6 +361,7 @@ export function BoardFilterChips({
|
|
|
327
361
|
onChange={onChange}
|
|
328
362
|
self={self}
|
|
329
363
|
statuses={statuses}
|
|
364
|
+
timeProjectId={timeProjectId}
|
|
330
365
|
trigger={
|
|
331
366
|
<Button
|
|
332
367
|
type="button"
|
|
@@ -346,7 +381,7 @@ export function BoardFilterChips({
|
|
|
346
381
|
type="button"
|
|
347
382
|
variant="link"
|
|
348
383
|
size="2xs"
|
|
349
|
-
onClick={() => onChange({ assigneeStaffMemberId: null, taskStatusId: null, tagIds: [] })}
|
|
384
|
+
onClick={() => onChange({ assigneeStaffMemberId: null, taskStatusId: null, tagIds: [], unassigned: false })}
|
|
350
385
|
data-testid="board-filter-clear"
|
|
351
386
|
className="h-auto px-1 text-xs leading-normal text-muted-foreground hover:text-foreground"
|
|
352
387
|
>
|
|
@@ -71,6 +71,7 @@ import {
|
|
|
71
71
|
type BoardApiRow,
|
|
72
72
|
type BoardTask,
|
|
73
73
|
} from './kanbanBoardData'
|
|
74
|
+
import { SUPPORT_ACTIVITY_EVENT } from './supportTicketData'
|
|
74
75
|
|
|
75
76
|
const logger = createLogger('staff').child({ component: 'KanbanBoard' })
|
|
76
77
|
|
|
@@ -81,6 +82,7 @@ export const CREATE_MUTATION_CONTEXT_ID = 'staff.time_tracking.board.create'
|
|
|
81
82
|
export const MANAGE_COLUMNS_FEATURE = 'staff.timesheets.projects.manage'
|
|
82
83
|
export const MANAGE_TASKS_FEATURE = 'staff.timesheets.tasks.manage'
|
|
83
84
|
export const TASK_STATUS_CHANGED_EVENT = 'staff.timesheets.time_task.status_changed'
|
|
85
|
+
export const TASK_ASSIGNEE_CHANGED_EVENT = 'staff.timesheets.time_task.assignee_changed'
|
|
84
86
|
|
|
85
87
|
/** Window a locally-originated move stays "mine" for, so a lost SSE cannot wedge the filter. */
|
|
86
88
|
const OWN_MOVE_ECHO_TTL_MS = 15_000
|
|
@@ -364,6 +366,10 @@ export function KanbanBoard({
|
|
|
364
366
|
queryClient.invalidateQueries({ queryKey: [...BOARD_QUERY_ROOT, 'children'] }).catch(() => {})
|
|
365
367
|
}, [queryClient])
|
|
366
368
|
|
|
369
|
+
const invalidateUnassignedCount = React.useCallback(() => {
|
|
370
|
+
queryClient.invalidateQueries({ queryKey: [...BOARD_QUERY_ROOT, 'unassigned-count'] }).catch(() => {})
|
|
371
|
+
}, [queryClient])
|
|
372
|
+
|
|
367
373
|
const invalidateStatuses = React.useCallback(() => {
|
|
368
374
|
queryClient.invalidateQueries({ queryKey: [...BOARD_QUERY_ROOT, 'statuses'] }).catch(() => {})
|
|
369
375
|
}, [queryClient])
|
|
@@ -406,6 +412,30 @@ export function KanbanBoard({
|
|
|
406
412
|
[consumeOwnMove, invalidateBoard, timeProjectId],
|
|
407
413
|
)
|
|
408
414
|
|
|
415
|
+
useAppEvent(
|
|
416
|
+
TASK_ASSIGNEE_CHANGED_EVENT,
|
|
417
|
+
(event) => {
|
|
418
|
+
const eventPayload = event.payload as Record<string, unknown> | null | undefined
|
|
419
|
+
const projectId = typeof eventPayload?.timeProjectId === 'string' ? eventPayload.timeProjectId : null
|
|
420
|
+
if (projectId && projectId !== timeProjectId) return
|
|
421
|
+
invalidateBoard()
|
|
422
|
+
invalidateUnassignedCount()
|
|
423
|
+
},
|
|
424
|
+
[invalidateBoard, invalidateUnassignedCount, timeProjectId],
|
|
425
|
+
)
|
|
426
|
+
|
|
427
|
+
useAppEvent(
|
|
428
|
+
SUPPORT_ACTIVITY_EVENT,
|
|
429
|
+
(event) => {
|
|
430
|
+
const eventPayload = event.payload as Record<string, unknown> | null | undefined
|
|
431
|
+
const projectId = typeof eventPayload?.projectId === 'string' ? eventPayload.projectId : null
|
|
432
|
+
if (projectId && projectId !== timeProjectId) return
|
|
433
|
+
invalidateBoard()
|
|
434
|
+
invalidateUnassignedCount()
|
|
435
|
+
},
|
|
436
|
+
[invalidateBoard, invalidateUnassignedCount, timeProjectId],
|
|
437
|
+
)
|
|
438
|
+
|
|
409
439
|
const setTaskPending = React.useCallback((taskId: string, pending: boolean) => {
|
|
410
440
|
setPendingTaskIds((previous) => {
|
|
411
441
|
const next = new Set(previous)
|
|
@@ -227,6 +227,15 @@ function KanbanCardImpl({
|
|
|
227
227
|
>
|
|
228
228
|
<div className="text-sm font-semibold leading-normal text-foreground">{task.title}</div>
|
|
229
229
|
|
|
230
|
+
{task.supportReference ? (
|
|
231
|
+
<div className="flex flex-wrap items-center gap-1" data-testid="task-card-support">
|
|
232
|
+
<Badge variant="brand" size="sm">
|
|
233
|
+
{t('staff.time_tracking.board.card.customerRequest', 'Customer request')}
|
|
234
|
+
</Badge>
|
|
235
|
+
<span className="text-xs font-medium text-muted-foreground">{task.supportReference}</span>
|
|
236
|
+
</div>
|
|
237
|
+
) : null}
|
|
238
|
+
|
|
230
239
|
{tags.length > 0 ? (
|
|
231
240
|
<div className="flex flex-wrap gap-1">
|
|
232
241
|
{tags.map((tag) => (
|
|
@@ -8,9 +8,21 @@ import { Avatar } from '@open-mercato/ui/primitives/avatar'
|
|
|
8
8
|
import { Button } from '@open-mercato/ui/primitives/button'
|
|
9
9
|
import { Input } from '@open-mercato/ui/primitives/input'
|
|
10
10
|
import { Label } from '@open-mercato/ui/primitives/label'
|
|
11
|
+
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@open-mercato/ui/primitives/select'
|
|
11
12
|
import { SegmentedControl, SegmentedControlItem } from '@open-mercato/ui/primitives/segmented-control'
|
|
12
13
|
import { SwitchField } from '@open-mercato/ui/primitives/switch-field'
|
|
13
14
|
import { apiCall } from '@open-mercato/ui/backend/utils/apiCall'
|
|
15
|
+
import { ErrorMessage } from '@open-mercato/ui/backend/detail'
|
|
16
|
+
import { resolveGraceDays } from './projectTeamAssignment'
|
|
17
|
+
import {
|
|
18
|
+
DEFAULT_ASSIGNEE_PAGE_SIZE,
|
|
19
|
+
chunkIds,
|
|
20
|
+
classifyDefaultAssigneeCandidates,
|
|
21
|
+
collectProjectMemberships,
|
|
22
|
+
readProjectMembershipPage,
|
|
23
|
+
resolveSelectedDefaultAssignee,
|
|
24
|
+
type ProjectMemberRow,
|
|
25
|
+
} from './projectDefaultAssignee'
|
|
14
26
|
import { useT } from '@open-mercato/shared/lib/i18n/context'
|
|
15
27
|
import type { TranslateFn } from '@open-mercato/shared/lib/i18n/context'
|
|
16
28
|
|
|
@@ -122,12 +134,7 @@ export function ProjectBudgetSection({
|
|
|
122
134
|
)
|
|
123
135
|
}
|
|
124
136
|
|
|
125
|
-
|
|
126
|
-
id?: unknown
|
|
127
|
-
staffMemberId?: unknown
|
|
128
|
-
staff_member_id?: unknown
|
|
129
|
-
status?: unknown
|
|
130
|
-
}
|
|
137
|
+
export { isUsableDefaultAssigneeMembership } from './projectDefaultAssignee'
|
|
131
138
|
|
|
132
139
|
type TeamMemberRow = {
|
|
133
140
|
id?: unknown
|
|
@@ -321,3 +328,175 @@ export function ProjectRoundingNotice() {
|
|
|
321
328
|
|
|
322
329
|
/** Exported for unit tests only. */
|
|
323
330
|
export const __testing = { memberLabel }
|
|
331
|
+
|
|
332
|
+
const NO_DEFAULT_ASSIGNEE = '__none__'
|
|
333
|
+
|
|
334
|
+
export type ProjectDefaultAssigneeFieldProps = {
|
|
335
|
+
projectId: string | null
|
|
336
|
+
value: string | null
|
|
337
|
+
onChange: (value: string | null) => void
|
|
338
|
+
disabled?: boolean
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
type DefaultAssigneeOption = { id: string; label: string; active: boolean }
|
|
342
|
+
|
|
343
|
+
/**
|
|
344
|
+
* The project's default assignee for customer portal tickets. Only active project
|
|
345
|
+
* members are offered; a stored default whose membership has since gone inactive or
|
|
346
|
+
* been removed stays visible with a warning, because new tickets then fall to the
|
|
347
|
+
* project's assigners instead of failing.
|
|
348
|
+
*/
|
|
349
|
+
export function ProjectDefaultAssigneeField({ projectId, value, onChange, disabled }: ProjectDefaultAssigneeFieldProps) {
|
|
350
|
+
const t = useT()
|
|
351
|
+
const [options, setOptions] = React.useState<DefaultAssigneeOption[]>([])
|
|
352
|
+
const [loadState, setLoadState] = React.useState<'loading' | 'loaded' | 'failed'>('loading')
|
|
353
|
+
const [reloadToken, setReloadToken] = React.useState(0)
|
|
354
|
+
const [truncated, setTruncated] = React.useState(false)
|
|
355
|
+
const [extraLabels, setExtraLabels] = React.useState<Map<string, string>>(new Map())
|
|
356
|
+
const valueRef = React.useRef(value)
|
|
357
|
+
valueRef.current = value
|
|
358
|
+
|
|
359
|
+
React.useEffect(() => {
|
|
360
|
+
if (!projectId) {
|
|
361
|
+
setOptions([])
|
|
362
|
+
setTruncated(false)
|
|
363
|
+
setExtraLabels(new Map())
|
|
364
|
+
setLoadState('loaded')
|
|
365
|
+
return
|
|
366
|
+
}
|
|
367
|
+
let cancelled = false
|
|
368
|
+
setLoadState('loading')
|
|
369
|
+
async function load() {
|
|
370
|
+
try {
|
|
371
|
+
const [collection, settingsRes] = await Promise.all([
|
|
372
|
+
collectProjectMemberships(async (page) => {
|
|
373
|
+
const membersRes = await apiCall<{ items?: unknown; totalPages?: unknown }>(
|
|
374
|
+
`/api/staff/timesheets/time-projects/${projectId}/employees?page=${page}&pageSize=${DEFAULT_ASSIGNEE_PAGE_SIZE}`,
|
|
375
|
+
)
|
|
376
|
+
return readProjectMembershipPage(membersRes)
|
|
377
|
+
}),
|
|
378
|
+
apiCall<{ access?: { assignmentGraceDays?: unknown } | null }>('/api/staff/timesheets/settings'),
|
|
379
|
+
])
|
|
380
|
+
const graceDays = resolveGraceDays(settingsRes.ok ? settingsRes.result?.access?.assignmentGraceDays : null)
|
|
381
|
+
const candidates = classifyDefaultAssigneeCandidates(collection.rows, graceDays)
|
|
382
|
+
if (!cancelled) setTruncated(collection.truncated)
|
|
383
|
+
const selectedId = valueRef.current
|
|
384
|
+
const labelIds = Array.from(new Set([
|
|
385
|
+
...candidates.map((candidate) => candidate.staffMemberId),
|
|
386
|
+
...(selectedId ? [selectedId] : []),
|
|
387
|
+
]))
|
|
388
|
+
if (labelIds.length === 0) {
|
|
389
|
+
if (!cancelled) {
|
|
390
|
+
setOptions([])
|
|
391
|
+
setExtraLabels(new Map())
|
|
392
|
+
setLoadState('loaded')
|
|
393
|
+
}
|
|
394
|
+
return
|
|
395
|
+
}
|
|
396
|
+
const staffResults = await Promise.all(
|
|
397
|
+
chunkIds(labelIds).map((ids) =>
|
|
398
|
+
apiCall<{ items?: TeamMemberRow[] }>(
|
|
399
|
+
`/api/staff/team-members?ids=${ids.join(',')}&pageSize=${DEFAULT_ASSIGNEE_PAGE_SIZE}`,
|
|
400
|
+
),
|
|
401
|
+
),
|
|
402
|
+
)
|
|
403
|
+
const staffRows = staffResults.flatMap((staffRes) => (Array.isArray(staffRes.result?.items) ? staffRes.result.items : []))
|
|
404
|
+
const labels = new Map(
|
|
405
|
+
staffRows
|
|
406
|
+
.map((row) => [readString(row.id), memberLabel(row)] as const)
|
|
407
|
+
.filter((entry): entry is readonly [string, string] => entry[0] !== null),
|
|
408
|
+
)
|
|
409
|
+
if (!cancelled) {
|
|
410
|
+
setOptions(candidates.map((candidate) => ({
|
|
411
|
+
id: candidate.staffMemberId,
|
|
412
|
+
label: labels.get(candidate.staffMemberId) || candidate.staffMemberId,
|
|
413
|
+
active: candidate.active,
|
|
414
|
+
})))
|
|
415
|
+
setExtraLabels(labels)
|
|
416
|
+
setLoadState('loaded')
|
|
417
|
+
}
|
|
418
|
+
} catch {
|
|
419
|
+
if (!cancelled) setLoadState('failed')
|
|
420
|
+
}
|
|
421
|
+
}
|
|
422
|
+
void load()
|
|
423
|
+
return () => { cancelled = true }
|
|
424
|
+
}, [projectId, reloadToken])
|
|
425
|
+
|
|
426
|
+
const retryLoad = React.useCallback(() => setReloadToken((token) => token + 1), [])
|
|
427
|
+
|
|
428
|
+
const selection = resolveSelectedDefaultAssignee(
|
|
429
|
+
options.map((option) => ({ staffMemberId: option.id, active: option.active })),
|
|
430
|
+
value,
|
|
431
|
+
truncated,
|
|
432
|
+
)
|
|
433
|
+
const selected = value ? options.find((option) => option.id === value) ?? null : null
|
|
434
|
+
const selectedLabel = selected?.label ?? (value ? extraLabels.get(value) || value : null)
|
|
435
|
+
const loadFailed = loadState === 'failed'
|
|
436
|
+
const activeOptions = loadFailed ? [] : options.filter((option) => option.active)
|
|
437
|
+
const showWarning = loadState === 'loaded' && selection.unusable
|
|
438
|
+
|
|
439
|
+
return (
|
|
440
|
+
<div className="flex flex-col gap-2" data-testid="project-default-assignee">
|
|
441
|
+
<Label htmlFor="project-default-assignee-select">
|
|
442
|
+
{t('staff.time_tracking.assigners.defaultAssignee', 'Default assignee for portal tickets')}
|
|
443
|
+
</Label>
|
|
444
|
+
<Select
|
|
445
|
+
value={value ?? NO_DEFAULT_ASSIGNEE}
|
|
446
|
+
disabled={disabled || !projectId}
|
|
447
|
+
onValueChange={(next) => onChange(next === NO_DEFAULT_ASSIGNEE ? null : next)}
|
|
448
|
+
>
|
|
449
|
+
<SelectTrigger id="project-default-assignee-select" data-testid="project-default-assignee-select">
|
|
450
|
+
<SelectValue />
|
|
451
|
+
</SelectTrigger>
|
|
452
|
+
<SelectContent>
|
|
453
|
+
<SelectItem value={NO_DEFAULT_ASSIGNEE}>
|
|
454
|
+
{t('staff.time_tracking.assigners.defaultAssigneeNone', 'No default assignee')}
|
|
455
|
+
</SelectItem>
|
|
456
|
+
{activeOptions.map((option) => (
|
|
457
|
+
<SelectItem key={option.id} value={option.id}>
|
|
458
|
+
{option.label}
|
|
459
|
+
</SelectItem>
|
|
460
|
+
))}
|
|
461
|
+
{value && (loadFailed || !selected || !selected.active) ? (
|
|
462
|
+
<SelectItem value={value}>{selectedLabel ?? value}</SelectItem>
|
|
463
|
+
) : null}
|
|
464
|
+
</SelectContent>
|
|
465
|
+
</Select>
|
|
466
|
+
<p className="text-xs text-muted-foreground">
|
|
467
|
+
{t(
|
|
468
|
+
'staff.time_tracking.assigners.defaultAssigneeHelp',
|
|
469
|
+
'New customer portal tickets routed to this project are assigned to this member. Tasks created on the board stay assigned to their creator.',
|
|
470
|
+
)}
|
|
471
|
+
</p>
|
|
472
|
+
{loadFailed ? (
|
|
473
|
+
<ErrorMessage
|
|
474
|
+
label={t(
|
|
475
|
+
'staff.time_tracking.assigners.defaultAssigneeLoadFailed',
|
|
476
|
+
'Could not load the project members, so the default assignee cannot be checked right now.',
|
|
477
|
+
)}
|
|
478
|
+
action={(
|
|
479
|
+
<Button type="button" variant="outline" size="sm" onClick={retryLoad} data-testid="project-default-assignee-retry">
|
|
480
|
+
{t('staff.time_tracking.assigners.defaultAssigneeRetry', 'Retry')}
|
|
481
|
+
</Button>
|
|
482
|
+
)}
|
|
483
|
+
/>
|
|
484
|
+
) : null}
|
|
485
|
+
{showWarning ? (
|
|
486
|
+
<Alert status="warning">
|
|
487
|
+
<AlertDescription>
|
|
488
|
+
{t(
|
|
489
|
+
'staff.time_tracking.assigners.defaultAssigneeInactive',
|
|
490
|
+
'This member is no longer an active project member. New tickets will be left unassigned and the assigners notified.',
|
|
491
|
+
)}
|
|
492
|
+
</AlertDescription>
|
|
493
|
+
</Alert>
|
|
494
|
+
) : null}
|
|
495
|
+
{!projectId ? (
|
|
496
|
+
<p className="text-xs text-muted-foreground">
|
|
497
|
+
{t('staff.time_tracking.assigners.defaultAssigneeAfterCreate', 'Save the project and add members to choose a default assignee.')}
|
|
498
|
+
</p>
|
|
499
|
+
) : null}
|
|
500
|
+
</div>
|
|
501
|
+
)
|
|
502
|
+
}
|
|
@@ -0,0 +1,188 @@
|
|
|
1
|
+
"use client"
|
|
2
|
+
|
|
3
|
+
import * as React from 'react'
|
|
4
|
+
import { useQuery, useQueryClient } from '@tanstack/react-query'
|
|
5
|
+
import { Download, Paperclip } from 'lucide-react'
|
|
6
|
+
import { Badge } from '@open-mercato/ui/primitives/badge'
|
|
7
|
+
import { Button } from '@open-mercato/ui/primitives/button'
|
|
8
|
+
import { Checkbox } from '@open-mercato/ui/primitives/checkbox'
|
|
9
|
+
import { Spinner } from '@open-mercato/ui/primitives/spinner'
|
|
10
|
+
import { flash } from '@open-mercato/ui/backend/FlashMessages'
|
|
11
|
+
import { ErrorMessage, LoadingMessage } from '@open-mercato/ui/backend/detail'
|
|
12
|
+
import { useBackendChrome } from '@open-mercato/ui/backend/BackendChromeProvider'
|
|
13
|
+
import { hasFeature } from '@open-mercato/shared/security/features'
|
|
14
|
+
import { apiCall } from '@open-mercato/ui/backend/utils/apiCall'
|
|
15
|
+
import { useGuardedMutation } from '@open-mercato/ui/backend/injection/useGuardedMutation'
|
|
16
|
+
import { useT } from '@open-mercato/shared/lib/i18n/context'
|
|
17
|
+
import { useOrganizationScopeVersion } from '@open-mercato/shared/lib/frontend/useOrganizationScope'
|
|
18
|
+
import { TASK_DRAWER_QUERY_ROOT } from './taskDrawerData'
|
|
19
|
+
|
|
20
|
+
const ENTITY_ID = 'staff:staff_time_task'
|
|
21
|
+
const CUSTOMER_VISIBLE_TAG = 'customer-visible'
|
|
22
|
+
const CUSTOMER_UPLOAD_PREFIX = 'customer-upload:'
|
|
23
|
+
const MUTATION_CONTEXT_ID = 'staff.task_drawer.support_attachments'
|
|
24
|
+
const ATTACHMENTS_MANAGE_FEATURE = 'attachments.manage'
|
|
25
|
+
|
|
26
|
+
type StaffAttachment = {
|
|
27
|
+
id: string
|
|
28
|
+
url: string
|
|
29
|
+
fileName: string
|
|
30
|
+
fileSize: number
|
|
31
|
+
tags?: string[]
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
type SupportAttachmentsSectionProps = {
|
|
35
|
+
taskId: string
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function formatSize(bytes: number): string {
|
|
39
|
+
if (!Number.isFinite(bytes) || bytes <= 0) return '0 B'
|
|
40
|
+
if (bytes < 1024) return `${bytes} B`
|
|
41
|
+
if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} KB`
|
|
42
|
+
return `${(bytes / (1024 * 1024)).toFixed(1)} MB`
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Files on a portal ticket. Customer uploads and files staff chose to share carry
|
|
47
|
+
* the `customer-visible` tag and are the only ones the portal lists; everything
|
|
48
|
+
* else stays internal. "Share with customer" is unchecked by default.
|
|
49
|
+
*/
|
|
50
|
+
export function SupportAttachmentsSection({ taskId }: SupportAttachmentsSectionProps) {
|
|
51
|
+
const t = useT()
|
|
52
|
+
const { runMutation, retryLastMutation } = useGuardedMutation<Record<string, unknown>>({
|
|
53
|
+
contextId: MUTATION_CONTEXT_ID,
|
|
54
|
+
blockedMessage: t('staff.support.attachments.uploadFailed', 'Could not upload the file.'),
|
|
55
|
+
})
|
|
56
|
+
const { payload } = useBackendChrome()
|
|
57
|
+
const canUpload = hasFeature(payload?.grantedFeatures, ATTACHMENTS_MANAGE_FEATURE)
|
|
58
|
+
const queryClient = useQueryClient()
|
|
59
|
+
const scopeVersion = useOrganizationScopeVersion()
|
|
60
|
+
const [uploading, setUploading] = React.useState(false)
|
|
61
|
+
const [share, setShare] = React.useState(false)
|
|
62
|
+
const inputRef = React.useRef<HTMLInputElement | null>(null)
|
|
63
|
+
|
|
64
|
+
const attachmentsQueryKey = React.useMemo(
|
|
65
|
+
() => [...TASK_DRAWER_QUERY_ROOT, 'attachments', `scope:${scopeVersion}`, taskId],
|
|
66
|
+
[scopeVersion, taskId],
|
|
67
|
+
)
|
|
68
|
+
const attachmentsQuery = useQuery<StaffAttachment[]>({
|
|
69
|
+
queryKey: attachmentsQueryKey,
|
|
70
|
+
enabled: taskId.length > 0,
|
|
71
|
+
queryFn: async () => {
|
|
72
|
+
const response = await apiCall<{ items?: StaffAttachment[] }>(
|
|
73
|
+
`/api/attachments?entityId=${encodeURIComponent(ENTITY_ID)}&recordId=${encodeURIComponent(taskId)}`,
|
|
74
|
+
)
|
|
75
|
+
if (!response.ok) throw new Error('[internal] attachments request failed')
|
|
76
|
+
return response.result?.items ?? []
|
|
77
|
+
},
|
|
78
|
+
})
|
|
79
|
+
const items = attachmentsQuery.data ?? []
|
|
80
|
+
|
|
81
|
+
const refreshAttachments = React.useCallback(async () => {
|
|
82
|
+
await queryClient.invalidateQueries({ queryKey: attachmentsQueryKey })
|
|
83
|
+
}, [attachmentsQueryKey, queryClient])
|
|
84
|
+
|
|
85
|
+
const upload = React.useCallback(async (list: FileList | null) => {
|
|
86
|
+
if (!list || !list.length || !canUpload) return
|
|
87
|
+
setUploading(true)
|
|
88
|
+
try {
|
|
89
|
+
for (const file of Array.from(list)) {
|
|
90
|
+
const form = new FormData()
|
|
91
|
+
form.set('entityId', ENTITY_ID)
|
|
92
|
+
form.set('recordId', taskId)
|
|
93
|
+
form.set('file', file)
|
|
94
|
+
if (share) form.set('tags', JSON.stringify([CUSTOMER_VISIBLE_TAG]))
|
|
95
|
+
const result = await runMutation({
|
|
96
|
+
operation: () => apiCall('/api/attachments', { method: 'POST', body: form }),
|
|
97
|
+
// Like the rest of the task drawer, an upload carries no record-lock
|
|
98
|
+
// resource: it adds a row rather than editing one.
|
|
99
|
+
context: {
|
|
100
|
+
formId: `${MUTATION_CONTEXT_ID}:${taskId}`,
|
|
101
|
+
retryLastMutation,
|
|
102
|
+
},
|
|
103
|
+
mutationPayload: { taskId, fileName: file.name, shared: share },
|
|
104
|
+
})
|
|
105
|
+
if (!result.ok) flash(t('staff.support.attachments.uploadFailed', 'Could not upload the file.'), 'error')
|
|
106
|
+
}
|
|
107
|
+
await refreshAttachments()
|
|
108
|
+
} catch {
|
|
109
|
+
flash(t('staff.support.attachments.uploadFailed', 'Could not upload the file.'), 'error')
|
|
110
|
+
} finally {
|
|
111
|
+
setUploading(false)
|
|
112
|
+
if (inputRef.current) inputRef.current.value = ''
|
|
113
|
+
}
|
|
114
|
+
}, [canUpload, refreshAttachments, retryLastMutation, runMutation, share, t, taskId])
|
|
115
|
+
|
|
116
|
+
return (
|
|
117
|
+
<section className="space-y-2" data-testid="support-attachments-section">
|
|
118
|
+
<div className="flex items-center justify-between gap-2">
|
|
119
|
+
<h3 className="text-sm font-semibold text-foreground">{t('staff.support.attachments.title', 'Ticket files')}</h3>
|
|
120
|
+
{canUpload ? (
|
|
121
|
+
<div className="flex items-center gap-3">
|
|
122
|
+
<label className="flex items-center gap-2 text-sm text-muted-foreground">
|
|
123
|
+
<Checkbox
|
|
124
|
+
checked={share}
|
|
125
|
+
onCheckedChange={(value) => setShare(value === true)}
|
|
126
|
+
aria-label={t('staff.support.attachments.shareWithCustomer', 'Share with customer')}
|
|
127
|
+
/>
|
|
128
|
+
{t('staff.support.attachments.shareWithCustomer', 'Share with customer')}
|
|
129
|
+
</label>
|
|
130
|
+
<input
|
|
131
|
+
ref={inputRef}
|
|
132
|
+
type="file"
|
|
133
|
+
multiple
|
|
134
|
+
className="sr-only"
|
|
135
|
+
data-testid="support-attachments-input"
|
|
136
|
+
onChange={(event) => void upload(event.target.files)}
|
|
137
|
+
/>
|
|
138
|
+
<Button type="button" variant="outline" size="sm" disabled={uploading} onClick={() => inputRef.current?.click()}>
|
|
139
|
+
{uploading ? <Spinner className="mr-2 h-4 w-4" /> : <Paperclip className="mr-2 h-4 w-4" />}
|
|
140
|
+
{t('staff.support.attachments.upload', 'Upload')}
|
|
141
|
+
</Button>
|
|
142
|
+
</div>
|
|
143
|
+
) : null}
|
|
144
|
+
</div>
|
|
145
|
+
{attachmentsQuery.isLoading ? (
|
|
146
|
+
<LoadingMessage label={t('staff.support.attachments.loading', 'Loading files…')} />
|
|
147
|
+
) : attachmentsQuery.isError ? (
|
|
148
|
+
<ErrorMessage
|
|
149
|
+
label={t('staff.support.attachments.loadFailed', 'Could not load the ticket files.')}
|
|
150
|
+
action={(
|
|
151
|
+
<Button type="button" variant="outline" size="sm" onClick={() => { void attachmentsQuery.refetch() }}>
|
|
152
|
+
{t('staff.support.attachments.retry', 'Try again')}
|
|
153
|
+
</Button>
|
|
154
|
+
)}
|
|
155
|
+
/>
|
|
156
|
+
) : items.length === 0 ? (
|
|
157
|
+
<p className="text-sm text-muted-foreground">{t('staff.support.attachments.empty', 'No files on this ticket.')}</p>
|
|
158
|
+
) : (
|
|
159
|
+
<ul className="space-y-1">
|
|
160
|
+
{items.map((item) => {
|
|
161
|
+
const tags = item.tags ?? []
|
|
162
|
+
const visible = tags.includes(CUSTOMER_VISIBLE_TAG)
|
|
163
|
+
const fromCustomer = tags.some((tag) => tag.startsWith(CUSTOMER_UPLOAD_PREFIX))
|
|
164
|
+
return (
|
|
165
|
+
<li key={item.id} className="flex items-center justify-between gap-2 text-sm">
|
|
166
|
+
<a className="flex min-w-0 items-center gap-2 text-foreground hover:underline" href={item.url}>
|
|
167
|
+
<Download className="h-4 w-4 shrink-0" />
|
|
168
|
+
<span className="truncate">{item.fileName}</span>
|
|
169
|
+
<span className="shrink-0 text-muted-foreground">{formatSize(item.fileSize)}</span>
|
|
170
|
+
</a>
|
|
171
|
+
<div className="flex shrink-0 gap-1">
|
|
172
|
+
{fromCustomer ? (
|
|
173
|
+
<Badge variant="outline">{t('staff.support.attachments.fromCustomer', 'From customer')}</Badge>
|
|
174
|
+
) : null}
|
|
175
|
+
<Badge variant={visible ? 'secondary' : 'outline'}>
|
|
176
|
+
{visible
|
|
177
|
+
? t('staff.support.attachments.visible', 'Visible to customer')
|
|
178
|
+
: t('staff.support.attachments.internal', 'Internal')}
|
|
179
|
+
</Badge>
|
|
180
|
+
</div>
|
|
181
|
+
</li>
|
|
182
|
+
)
|
|
183
|
+
})}
|
|
184
|
+
</ul>
|
|
185
|
+
)}
|
|
186
|
+
</section>
|
|
187
|
+
)
|
|
188
|
+
}
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
"use client"
|
|
2
|
+
|
|
3
|
+
import * as React from 'react'
|
|
4
|
+
import { Badge } from '@open-mercato/ui/primitives/badge'
|
|
5
|
+
import { StatusBadge } from '@open-mercato/ui/primitives/status-badge'
|
|
6
|
+
import { useT } from '@open-mercato/shared/lib/i18n/context'
|
|
7
|
+
import { SUPPORT_PORTAL_STATUS_VARIANT, type SupportTicketView } from './supportTicketData'
|
|
8
|
+
|
|
9
|
+
export function supportPortalStatusLabel(
|
|
10
|
+
t: ReturnType<typeof useT>,
|
|
11
|
+
status: SupportTicketView['portalStatus'],
|
|
12
|
+
): string {
|
|
13
|
+
if (status === 'resolved') return t('staff.support.tickets.status.resolved', 'Resolved')
|
|
14
|
+
if (status === 'in_progress') return t('staff.support.tickets.status.inProgress', 'In progress')
|
|
15
|
+
return t('staff.support.tickets.status.received', 'Received')
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
type SupportRequestPanelProps = {
|
|
19
|
+
ticket: SupportTicketView
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* The customer request behind a portal ticket, as the customer wrote it. The task
|
|
24
|
+
* title and description above are staff-editable working copies; the portal only
|
|
25
|
+
* ever shows this original.
|
|
26
|
+
*/
|
|
27
|
+
export function SupportRequestPanel({ ticket }: SupportRequestPanelProps) {
|
|
28
|
+
const t = useT()
|
|
29
|
+
const rows: Array<{ label: string; value: React.ReactNode }> = [
|
|
30
|
+
{ label: t('staff.time_tracking.taskDrawer.support.reference', 'Ticket'), value: ticket.supportReference },
|
|
31
|
+
{
|
|
32
|
+
label: t('staff.time_tracking.taskDrawer.support.customer', 'Customer'),
|
|
33
|
+
value: ticket.customerName ?? t('staff.time_tracking.taskDrawer.support.unknown', 'Unknown'),
|
|
34
|
+
},
|
|
35
|
+
{
|
|
36
|
+
label: t('staff.time_tracking.taskDrawer.support.reporter', 'Reported by'),
|
|
37
|
+
value: ticket.reporterName ?? t('staff.time_tracking.taskDrawer.support.unknown', 'Unknown'),
|
|
38
|
+
},
|
|
39
|
+
{ label: t('staff.time_tracking.taskDrawer.support.category', 'Category'), value: ticket.categoryLabel },
|
|
40
|
+
{
|
|
41
|
+
label: t('staff.time_tracking.taskDrawer.support.routedVia', 'Routed by'),
|
|
42
|
+
value:
|
|
43
|
+
ticket.routedVia === 'category'
|
|
44
|
+
? t('staff.time_tracking.taskDrawer.support.routedViaCategory', 'Category rule')
|
|
45
|
+
: t('staff.time_tracking.taskDrawer.support.routedViaFallback', 'Fallback project'),
|
|
46
|
+
},
|
|
47
|
+
{
|
|
48
|
+
label: t('staff.time_tracking.taskDrawer.support.portalStatus', 'Customer sees'),
|
|
49
|
+
value: (
|
|
50
|
+
<StatusBadge variant={SUPPORT_PORTAL_STATUS_VARIANT[ticket.portalStatus]} dot>
|
|
51
|
+
{supportPortalStatusLabel(t, ticket.portalStatus)}
|
|
52
|
+
</StatusBadge>
|
|
53
|
+
),
|
|
54
|
+
},
|
|
55
|
+
]
|
|
56
|
+
return (
|
|
57
|
+
<section
|
|
58
|
+
className="flex flex-col gap-3 rounded-lg border border-border bg-muted px-4 py-3"
|
|
59
|
+
data-testid="task-drawer-support-panel"
|
|
60
|
+
>
|
|
61
|
+
<div className="flex flex-wrap items-center gap-2">
|
|
62
|
+
<h3 className="text-xs font-semibold uppercase tracking-wide text-muted-foreground">
|
|
63
|
+
{t('staff.time_tracking.taskDrawer.support.title', 'Customer request')}
|
|
64
|
+
</h3>
|
|
65
|
+
{ticket.unnotifiedAt ? (
|
|
66
|
+
<Badge variant="warning" size="sm">
|
|
67
|
+
{t('staff.support.tickets.nobodyNotified', 'Nobody notified')}
|
|
68
|
+
</Badge>
|
|
69
|
+
) : null}
|
|
70
|
+
</div>
|
|
71
|
+
<dl className="grid grid-cols-1 gap-x-4 gap-y-2 text-sm sm:grid-cols-2">
|
|
72
|
+
{rows.map((row) => (
|
|
73
|
+
<div key={row.label} className="flex flex-col gap-0.5">
|
|
74
|
+
<dt className="text-xs text-muted-foreground">{row.label}</dt>
|
|
75
|
+
<dd className="text-foreground">{row.value}</dd>
|
|
76
|
+
</div>
|
|
77
|
+
))}
|
|
78
|
+
</dl>
|
|
79
|
+
{ticket.requestSubject || ticket.requestBody ? (
|
|
80
|
+
<div className="flex flex-col gap-1 border-t border-border pt-3">
|
|
81
|
+
<span className="text-xs text-muted-foreground">
|
|
82
|
+
{t('staff.time_tracking.taskDrawer.support.originalRequest', 'Original request')}
|
|
83
|
+
</span>
|
|
84
|
+
{ticket.requestSubject ? (
|
|
85
|
+
<p className="text-sm font-medium text-foreground">{ticket.requestSubject}</p>
|
|
86
|
+
) : null}
|
|
87
|
+
{ticket.requestBody ? (
|
|
88
|
+
<p className="whitespace-pre-wrap break-words text-sm text-foreground">{ticket.requestBody}</p>
|
|
89
|
+
) : null}
|
|
90
|
+
</div>
|
|
91
|
+
) : null}
|
|
92
|
+
<p className="text-xs text-muted-foreground" data-testid="task-drawer-support-hint">
|
|
93
|
+
{t(
|
|
94
|
+
'staff.time_tracking.taskDrawer.support.titleHint',
|
|
95
|
+
'The customer sees their original request, not the task title and description.',
|
|
96
|
+
)}
|
|
97
|
+
</p>
|
|
98
|
+
</section>
|
|
99
|
+
)
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
export default SupportRequestPanel
|