@open-mercato/core 0.8.1-develop.7275.1.f772b944d4 → 0.8.1-develop.7295.1.d0e0e33014
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 +1 -1
- package/dist/modules/auth/api/users/acl/route.js.map +2 -2
- package/dist/modules/auth/components/AclEditor.js +21 -3
- package/dist/modules/auth/components/AclEditor.js.map +2 -2
- package/dist/modules/customers/backend/customers/companies/page.js +6 -4
- package/dist/modules/customers/backend/customers/companies/page.js.map +2 -2
- package/dist/modules/customers/backend/customers/deals/map/components/DealsMapView.js +6 -4
- package/dist/modules/customers/backend/customers/deals/map/components/DealsMapView.js.map +2 -2
- package/dist/modules/customers/backend/customers/deals/page.js +6 -4
- package/dist/modules/customers/backend/customers/deals/page.js.map +2 -2
- package/dist/modules/customers/backend/customers/deals/pipeline/components/ChangeOwnerDialog.js +5 -3
- package/dist/modules/customers/backend/customers/deals/pipeline/components/ChangeOwnerDialog.js.map +2 -2
- package/dist/modules/customers/backend/customers/deals/pipeline/page.js +6 -4
- package/dist/modules/customers/backend/customers/deals/pipeline/page.js.map +2 -2
- package/dist/modules/customers/backend/customers/people/page.js +6 -4
- package/dist/modules/customers/backend/customers/people/page.js.map +2 -2
- package/dist/modules/customers/components/calendar/editor/PeopleField.js +9 -2
- package/dist/modules/customers/components/calendar/editor/PeopleField.js.map +2 -2
- package/dist/modules/customers/components/calendar/editor/hooks.js +4 -2
- package/dist/modules/customers/components/calendar/editor/hooks.js.map +2 -2
- package/dist/modules/customers/components/calendar/editor/lookups.js +7 -3
- package/dist/modules/customers/components/calendar/editor/lookups.js.map +2 -2
- package/dist/modules/customers/components/detail/AssignRoleDialog.js +5 -2
- package/dist/modules/customers/components/detail/AssignRoleDialog.js.map +2 -2
- package/dist/modules/customers/components/detail/RoleAssignmentRow.js +4 -2
- package/dist/modules/customers/components/detail/RoleAssignmentRow.js.map +2 -2
- package/dist/modules/customers/components/detail/ScheduleActivityDialog.js +8 -5
- package/dist/modules/customers/components/detail/ScheduleActivityDialog.js.map +2 -2
- package/dist/modules/customers/components/detail/schedule/DateTimeFields.js +10 -7
- package/dist/modules/customers/components/detail/schedule/DateTimeFields.js.map +2 -2
- package/dist/modules/customers/components/detail/schedule/ParticipantsField.js +4 -2
- package/dist/modules/customers/components/detail/schedule/ParticipantsField.js.map +2 -2
- package/dist/modules/customers/components/detail/schedule/fieldConfig.js +9 -0
- package/dist/modules/customers/components/detail/schedule/fieldConfig.js.map +2 -2
- package/dist/modules/customers/components/detail/schedule/index.js +3 -1
- package/dist/modules/customers/components/detail/schedule/index.js.map +2 -2
- package/dist/modules/customers/components/detail/schedule/useScheduleFormState.js +11 -4
- package/dist/modules/customers/components/detail/schedule/useScheduleFormState.js.map +2 -2
- package/dist/modules/customers/components/formConfig.js +17 -9
- package/dist/modules/customers/components/formConfig.js.map +2 -2
- package/dist/modules/customers/data/validators.js +12 -7
- package/dist/modules/customers/data/validators.js.map +2 -2
- package/dist/modules/customers/lib/assignableStaff.js +103 -22
- package/dist/modules/customers/lib/assignableStaff.js.map +2 -2
- package/dist/modules/customers/setup.js +10 -2
- package/dist/modules/customers/setup.js.map +2 -2
- package/dist/modules/data_sync/api/schedules/[id]/route.js +5 -0
- package/dist/modules/data_sync/api/schedules/[id]/route.js.map +2 -2
- package/dist/modules/data_sync/api/schedules/route.js +5 -0
- package/dist/modules/data_sync/api/schedules/route.js.map +2 -2
- package/dist/modules/data_sync/backend/data-sync/page.js +29 -5
- package/dist/modules/data_sync/backend/data-sync/page.js.map +2 -2
- package/dist/modules/data_sync/components/IntegrationScheduleTab.js +48 -11
- package/dist/modules/data_sync/components/IntegrationScheduleTab.js.map +2 -2
- package/dist/modules/data_sync/data/validators.js +13 -2
- package/dist/modules/data_sync/data/validators.js.map +2 -2
- package/dist/modules/data_sync/lib/schedule-value.js +30 -0
- package/dist/modules/data_sync/lib/schedule-value.js.map +7 -0
- package/dist/modules/eudr/data/validators.js.map +2 -2
- package/dist/modules/portal/frontend/[orgSlug]/portal/dashboard/page.js +3 -4
- package/dist/modules/portal/frontend/[orgSlug]/portal/dashboard/page.js.map +2 -2
- package/dist/modules/portal/frontend/[orgSlug]/portal/invite/page.js +3 -1
- package/dist/modules/portal/frontend/[orgSlug]/portal/invite/page.js.map +2 -2
- package/dist/modules/portal/frontend/[orgSlug]/portal/login/page.js +2 -1
- package/dist/modules/portal/frontend/[orgSlug]/portal/login/page.js.map +2 -2
- package/dist/modules/portal/frontend/[orgSlug]/portal/page.js +3 -4
- package/dist/modules/portal/frontend/[orgSlug]/portal/page.js.map +2 -2
- package/dist/modules/portal/frontend/[orgSlug]/portal/profile/page.js +3 -4
- package/dist/modules/portal/frontend/[orgSlug]/portal/profile/page.js.map +2 -2
- package/dist/modules/portal/lib/navigation.js +3 -4
- package/dist/modules/portal/lib/navigation.js.map +2 -2
- package/dist/modules/query_index/lib/engine.js +155 -15
- package/dist/modules/query_index/lib/engine.js.map +2 -2
- package/dist/modules/staff/backend/staff/time-tracking/page.js +122 -86
- package/dist/modules/staff/backend/staff/time-tracking/page.js.map +2 -2
- package/dist/modules/staff/backend/staff/time-tracking/projects/[id]/page.js +38 -17
- package/dist/modules/staff/backend/staff/time-tracking/projects/[id]/page.js.map +2 -2
- package/dist/modules/staff/backend/staff/time-tracking/reports/[id]/page.js +45 -18
- package/dist/modules/staff/backend/staff/time-tracking/reports/[id]/page.js.map +2 -2
- package/dist/modules/staff/extension-points.js +16 -0
- package/dist/modules/staff/extension-points.js.map +2 -2
- package/dist/modules/staff/frontend/[orgSlug]/portal/time-reports/[id]/page.js +3 -4
- package/dist/modules/staff/frontend/[orgSlug]/portal/time-reports/[id]/page.js.map +2 -2
- package/dist/modules/staff/frontend/[orgSlug]/portal/time-reports/page.js +3 -4
- package/dist/modules/staff/frontend/[orgSlug]/portal/time-reports/page.js.map +2 -2
- package/dist/modules/staff/lib/time-tracking-ui/ReportSheet.js +14 -13
- package/dist/modules/staff/lib/time-tracking-ui/ReportSheet.js.map +2 -2
- package/dist/modules/staff/lib/time-tracking-ui/TaskListView.js +60 -41
- package/dist/modules/staff/lib/time-tracking-ui/TaskListView.js.map +2 -2
- package/dist/modules/warranty_claims/api/ai/assess/route.js +2 -2
- package/dist/modules/warranty_claims/api/ai/assess/route.js.map +2 -2
- package/dist/modules/warranty_claims/api/ai/draft-reply/route.js +3 -4
- package/dist/modules/warranty_claims/api/ai/draft-reply/route.js.map +3 -3
- package/dist/modules/warranty_claims/api/ai/suggest/route.js +3 -3
- package/dist/modules/warranty_claims/api/ai/suggest/route.js.map +2 -2
- package/dist/modules/warranty_claims/api/assign/route.js +3 -3
- package/dist/modules/warranty_claims/api/assign/route.js.map +2 -2
- package/dist/modules/warranty_claims/api/credit-memo/route.js +2 -2
- package/dist/modules/warranty_claims/api/credit-memo/route.js.map +2 -2
- package/dist/modules/warranty_claims/api/entitlement/route.js +2 -2
- package/dist/modules/warranty_claims/api/entitlement/route.js.map +2 -2
- package/dist/modules/warranty_claims/api/events/route.js +4 -4
- package/dist/modules/warranty_claims/api/events/route.js.map +2 -2
- package/dist/modules/warranty_claims/api/external/claims/route.js +3 -3
- package/dist/modules/warranty_claims/api/external/claims/route.js.map +2 -2
- package/dist/modules/warranty_claims/api/portal/claims/[id]/submit/route.js +2 -2
- package/dist/modules/warranty_claims/api/portal/claims/[id]/submit/route.js.map +2 -2
- package/dist/modules/warranty_claims/api/portal/claims/[id]/withdraw/route.js +2 -2
- package/dist/modules/warranty_claims/api/portal/claims/[id]/withdraw/route.js.map +2 -2
- package/dist/modules/warranty_claims/api/portal/claims/route.js +2 -2
- package/dist/modules/warranty_claims/api/portal/claims/route.js.map +2 -2
- package/dist/modules/warranty_claims/api/portal/events/route.js +2 -2
- package/dist/modules/warranty_claims/api/portal/events/route.js.map +2 -2
- package/dist/modules/warranty_claims/api/receiving/route.js +2 -2
- package/dist/modules/warranty_claims/api/receiving/route.js.map +2 -2
- package/dist/modules/warranty_claims/api/replacement-order/route.js +2 -2
- package/dist/modules/warranty_claims/api/replacement-order/route.js.map +2 -2
- package/dist/modules/warranty_claims/api/return-label/route.js +2 -2
- package/dist/modules/warranty_claims/api/return-label/route.js.map +2 -2
- package/dist/modules/warranty_claims/api/risk/route.js +2 -2
- package/dist/modules/warranty_claims/api/risk/route.js.map +2 -2
- package/dist/modules/warranty_claims/api/sales-return/route.js +2 -2
- package/dist/modules/warranty_claims/api/sales-return/route.js.map +2 -2
- package/dist/modules/warranty_claims/api/settings-general/route.js +4 -4
- package/dist/modules/warranty_claims/api/settings-general/route.js.map +2 -2
- package/dist/modules/warranty_claims/api/stats/route.js +2 -2
- package/dist/modules/warranty_claims/api/stats/route.js.map +2 -2
- package/dist/modules/warranty_claims/api/submit/route.js +3 -3
- package/dist/modules/warranty_claims/api/submit/route.js.map +2 -2
- package/dist/modules/warranty_claims/api/transition/route.js +3 -3
- package/dist/modules/warranty_claims/api/transition/route.js.map +2 -2
- package/dist/modules/warranty_claims/api/vendor-recovery/route.js +3 -3
- package/dist/modules/warranty_claims/api/vendor-recovery/route.js.map +2 -2
- package/dist/modules/warranty_claims/api/vendor-recovery-suggestions/route.js +2 -2
- package/dist/modules/warranty_claims/api/vendor-recovery-suggestions/route.js.map +2 -2
- package/dist/modules/warranty_claims/backend/page.js +4 -2
- package/dist/modules/warranty_claims/backend/page.js.map +2 -2
- package/dist/modules/warranty_claims/backend/warranty_claims/[id]/page.js +9 -2
- package/dist/modules/warranty_claims/backend/warranty_claims/[id]/page.js.map +2 -2
- package/dist/modules/warranty_claims/backend/warranty_claims/settings/page.js +4 -2
- package/dist/modules/warranty_claims/backend/warranty_claims/settings/page.js.map +2 -2
- package/dist/modules/warranty_claims/frontend/[orgSlug]/portal/claims/[id]/page.js +3 -4
- package/dist/modules/warranty_claims/frontend/[orgSlug]/portal/claims/[id]/page.js.map +2 -2
- package/dist/modules/warranty_claims/frontend/[orgSlug]/portal/claims/new/page.js +3 -2
- package/dist/modules/warranty_claims/frontend/[orgSlug]/portal/claims/new/page.js.map +2 -2
- package/dist/modules/warranty_claims/frontend/[orgSlug]/portal/claims/page.js +3 -2
- package/dist/modules/warranty_claims/frontend/[orgSlug]/portal/claims/page.js.map +2 -2
- package/dist/modules/workflows/components/DefinitionTriggersEditor.js +1 -1
- package/dist/modules/workflows/components/DefinitionTriggersEditor.js.map +2 -2
- package/dist/modules/workflows/frontend/[orgSlug]/portal/tasks/[id]/page.js +2 -2
- package/dist/modules/workflows/frontend/[orgSlug]/portal/tasks/[id]/page.js.map +2 -2
- package/dist/modules/workflows/lib/event-trigger-service.js +56 -1
- package/dist/modules/workflows/lib/event-trigger-service.js.map +2 -2
- package/package.json +7 -7
- package/src/modules/auth/api/users/acl/route.ts +4 -3
- package/src/modules/auth/components/AclEditor.tsx +38 -2
- package/src/modules/auth/i18n/de.json +5 -1
- package/src/modules/auth/i18n/en.json +5 -1
- package/src/modules/auth/i18n/es.json +5 -1
- package/src/modules/auth/i18n/ko.json +5 -1
- package/src/modules/auth/i18n/pl.json +5 -1
- package/src/modules/customers/backend/customers/companies/page.tsx +6 -4
- package/src/modules/customers/backend/customers/deals/map/components/DealsMapView.tsx +6 -4
- package/src/modules/customers/backend/customers/deals/page.tsx +6 -4
- package/src/modules/customers/backend/customers/deals/pipeline/components/ChangeOwnerDialog.tsx +5 -3
- package/src/modules/customers/backend/customers/deals/pipeline/page.tsx +6 -4
- package/src/modules/customers/backend/customers/people/page.tsx +6 -4
- package/src/modules/customers/components/calendar/editor/PeopleField.tsx +9 -2
- package/src/modules/customers/components/calendar/editor/hooks.ts +4 -2
- package/src/modules/customers/components/calendar/editor/lookups.ts +12 -4
- package/src/modules/customers/components/detail/AssignRoleDialog.tsx +4 -1
- package/src/modules/customers/components/detail/RoleAssignmentRow.tsx +4 -2
- package/src/modules/customers/components/detail/ScheduleActivityDialog.tsx +15 -7
- package/src/modules/customers/components/detail/schedule/DateTimeFields.tsx +16 -7
- package/src/modules/customers/components/detail/schedule/ParticipantsField.tsx +4 -2
- package/src/modules/customers/components/detail/schedule/fieldConfig.ts +15 -0
- package/src/modules/customers/components/detail/schedule/index.ts +1 -1
- package/src/modules/customers/components/detail/schedule/useScheduleFormState.ts +16 -9
- package/src/modules/customers/components/formConfig.tsx +35 -12
- package/src/modules/customers/data/validators.ts +14 -8
- package/src/modules/customers/i18n/de.json +1 -0
- package/src/modules/customers/i18n/en.json +1 -0
- package/src/modules/customers/i18n/es.json +1 -0
- package/src/modules/customers/i18n/ko.json +1 -0
- package/src/modules/customers/i18n/pl.json +1 -0
- package/src/modules/customers/lib/assignableStaff.ts +172 -34
- package/src/modules/customers/setup.ts +8 -0
- package/src/modules/data_sync/api/schedules/[id]/route.ts +5 -0
- package/src/modules/data_sync/api/schedules/route.ts +5 -0
- package/src/modules/data_sync/backend/data-sync/page.tsx +48 -4
- package/src/modules/data_sync/components/IntegrationScheduleTab.tsx +61 -2
- package/src/modules/data_sync/data/validators.ts +26 -2
- package/src/modules/data_sync/i18n/de.json +2 -0
- package/src/modules/data_sync/i18n/en.json +2 -0
- package/src/modules/data_sync/i18n/es.json +2 -0
- package/src/modules/data_sync/i18n/ko.json +2 -0
- package/src/modules/data_sync/i18n/pl.json +2 -0
- package/src/modules/data_sync/lib/schedule-value.ts +45 -0
- package/src/modules/eudr/data/validators.ts +3 -4
- package/src/modules/portal/frontend/[orgSlug]/portal/dashboard/page.tsx +5 -4
- package/src/modules/portal/frontend/[orgSlug]/portal/invite/page.tsx +3 -1
- package/src/modules/portal/frontend/[orgSlug]/portal/login/page.tsx +2 -1
- package/src/modules/portal/frontend/[orgSlug]/portal/page.tsx +5 -5
- package/src/modules/portal/frontend/[orgSlug]/portal/profile/page.tsx +5 -4
- package/src/modules/portal/i18n/de.json +1 -0
- package/src/modules/portal/i18n/en.json +1 -0
- package/src/modules/portal/i18n/es.json +1 -0
- package/src/modules/portal/i18n/ko.json +1 -0
- package/src/modules/portal/i18n/pl.json +1 -0
- package/src/modules/portal/lib/navigation.ts +8 -3
- package/src/modules/query_index/lib/engine.ts +238 -17
- package/src/modules/staff/backend/staff/time-tracking/page.tsx +161 -130
- package/src/modules/staff/backend/staff/time-tracking/projects/[id]/page.tsx +55 -43
- package/src/modules/staff/backend/staff/time-tracking/reports/[id]/page.tsx +61 -38
- package/src/modules/staff/extension-points.ts +16 -0
- package/src/modules/staff/frontend/[orgSlug]/portal/time-reports/[id]/page.tsx +3 -4
- package/src/modules/staff/frontend/[orgSlug]/portal/time-reports/page.tsx +3 -4
- package/src/modules/staff/i18n/de.json +3 -0
- package/src/modules/staff/i18n/en.json +3 -0
- package/src/modules/staff/i18n/es.json +3 -0
- package/src/modules/staff/i18n/ko.json +3 -0
- package/src/modules/staff/i18n/pl.json +3 -0
- package/src/modules/staff/lib/time-tracking-ui/ReportSheet.tsx +32 -29
- package/src/modules/staff/lib/time-tracking-ui/TaskListView.tsx +89 -79
- package/src/modules/warranty_claims/api/ai/assess/route.ts +2 -2
- package/src/modules/warranty_claims/api/ai/draft-reply/route.ts +2 -3
- package/src/modules/warranty_claims/api/ai/suggest/route.ts +3 -3
- package/src/modules/warranty_claims/api/assign/route.ts +3 -3
- package/src/modules/warranty_claims/api/credit-memo/route.ts +2 -2
- package/src/modules/warranty_claims/api/entitlement/route.ts +2 -2
- package/src/modules/warranty_claims/api/events/route.ts +4 -4
- package/src/modules/warranty_claims/api/external/claims/route.ts +3 -3
- package/src/modules/warranty_claims/api/portal/claims/[id]/submit/route.ts +2 -2
- package/src/modules/warranty_claims/api/portal/claims/[id]/withdraw/route.ts +2 -2
- package/src/modules/warranty_claims/api/portal/claims/route.ts +2 -2
- package/src/modules/warranty_claims/api/portal/events/route.ts +2 -2
- package/src/modules/warranty_claims/api/receiving/route.ts +2 -2
- package/src/modules/warranty_claims/api/replacement-order/route.ts +2 -2
- package/src/modules/warranty_claims/api/return-label/route.ts +2 -2
- package/src/modules/warranty_claims/api/risk/route.ts +2 -2
- package/src/modules/warranty_claims/api/sales-return/route.ts +2 -2
- package/src/modules/warranty_claims/api/settings-general/route.ts +4 -4
- package/src/modules/warranty_claims/api/stats/route.ts +2 -2
- package/src/modules/warranty_claims/api/submit/route.ts +3 -3
- package/src/modules/warranty_claims/api/transition/route.ts +3 -3
- package/src/modules/warranty_claims/api/vendor-recovery/route.ts +3 -3
- package/src/modules/warranty_claims/api/vendor-recovery-suggestions/route.ts +2 -2
- package/src/modules/warranty_claims/backend/page.tsx +4 -2
- package/src/modules/warranty_claims/backend/warranty_claims/[id]/page.tsx +9 -2
- package/src/modules/warranty_claims/backend/warranty_claims/settings/page.tsx +4 -2
- package/src/modules/warranty_claims/frontend/[orgSlug]/portal/claims/[id]/page.tsx +5 -4
- package/src/modules/warranty_claims/frontend/[orgSlug]/portal/claims/new/page.tsx +5 -2
- package/src/modules/warranty_claims/frontend/[orgSlug]/portal/claims/page.tsx +5 -2
- package/src/modules/workflows/components/DefinitionTriggersEditor.tsx +1 -1
- package/src/modules/workflows/frontend/[orgSlug]/portal/tasks/[id]/page.tsx +2 -2
- package/src/modules/workflows/i18n/de.json +1 -1
- package/src/modules/workflows/i18n/en.json +1 -1
- package/src/modules/workflows/i18n/es.json +1 -1
- package/src/modules/workflows/i18n/ko.json +1 -1
- package/src/modules/workflows/i18n/pl.json +1 -1
- package/src/modules/workflows/lib/event-trigger-service.ts +139 -0
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../../../../../src/modules/customers/components/calendar/editor/lookups.ts"],
|
|
4
|
-
"sourcesContent": ["import { readApiResultOrThrow } from '@open-mercato/ui/backend/utils/apiCall'\nimport { fetchAssignableStaffMembers } from '../../detail/assignableStaff'\n\nexport type RelatedEntityKind = 'person' | 'company'\n\nexport type RelatedEntityOption = {\n id: string\n kind: RelatedEntityKind\n label: string\n subtitle: string | null\n}\n\nexport type DealOption = {\n id: string\n label: string\n}\n\nexport type PersonOption = {\n userId: string\n name: string\n email: string | null\n isCustomer: boolean\n}\n\nfunction readNonEmptyString(value: unknown): string | null {\n if (typeof value !== 'string') return null\n const trimmed = value.trim()\n return trimmed.length > 0 ? trimmed : null\n}\n\nfunction extractRelatedEntityOption(item: Record<string, unknown>, kind: RelatedEntityKind): RelatedEntityOption | null {\n const id = typeof item.id === 'string' ? item.id : null\n if (!id) return null\n const displayName = readNonEmptyString(item.displayName) ?? readNonEmptyString(item.display_name)\n const detail = kind === 'person'\n ? readNonEmptyString(item.primaryEmail) ?? readNonEmptyString(item.primary_email)\n : readNonEmptyString(item.domain)\n ?? readNonEmptyString(item.websiteUrl)\n ?? readNonEmptyString(item.website_url)\n const label = displayName ?? detail ?? id\n return { id, kind, label, subtitle: detail && detail !== label ? detail : null }\n}\n\n// Same lookup source as the deal-association pattern in DealForm\n// (useDealAssociationLookups): the people/companies CRUD list APIs with\n// `search` + name sorting, merged into a single related-to picker.\nexport async function searchRelatedEntities(\n kind: RelatedEntityKind,\n query: string,\n signal: AbortSignal,\n): Promise<RelatedEntityOption[]> {\n const params = new URLSearchParams({ page: '1', pageSize: '20', sortField: 'name', sortDir: 'asc' })\n if (query.length) params.set('search', query)\n const endpoint = kind === 'person'\n ? `/api/customers/people?${params.toString()}`\n : `/api/customers/companies?${params.toString()}`\n const data = await readApiResultOrThrow<{ items?: Array<Record<string, unknown>> }>(endpoint, { signal })\n const items = Array.isArray(data?.items) ? data.items : []\n return items\n .map((item) => (item && typeof item === 'object' ? extractRelatedEntityOption(item, kind) : null))\n .filter((option): option is RelatedEntityOption => option !== null)\n}\n\nasync function fetchRelatedEntityOfKind(\n kind: RelatedEntityKind,\n entityId: string,\n signal: AbortSignal,\n): Promise<RelatedEntityOption | null> {\n const params = new URLSearchParams({ page: '1', pageSize: '1', ids: entityId })\n const endpoint = kind === 'person'\n ? `/api/customers/people?${params.toString()}`\n : `/api/customers/companies?${params.toString()}`\n try {\n const data = await readApiResultOrThrow<{ items?: Array<Record<string, unknown>> }>(endpoint, { signal })\n const item = Array.isArray(data?.items) ? data.items[0] : null\n return item && typeof item === 'object' ? extractRelatedEntityOption(item, kind) : null\n } catch {\n return null\n }\n}\n\nexport async function fetchRelatedEntityById(entityId: string, signal: AbortSignal): Promise<RelatedEntityOption | null> {\n const [person, company] = await Promise.all([\n fetchRelatedEntityOfKind('person', entityId, signal),\n fetchRelatedEntityOfKind('company', entityId, signal),\n ])\n return person ?? company\n}\n\nfunction extractDealOption(item: Record<string, unknown>): DealOption | null {\n const id = typeof item.id === 'string' ? item.id : null\n if (!id) return null\n const label = readNonEmptyString(item.title) ?? id\n return { id, label }\n}\n\nexport async function fetchDealsForEntity(\n entity: { id: string; kind: RelatedEntityKind },\n signal: AbortSignal,\n): Promise<DealOption[]> {\n const params = new URLSearchParams({ page: '1', pageSize: '20' })\n params.set(entity.kind === 'person' ? 'personId' : 'companyId', entity.id)\n const data = await readApiResultOrThrow<{ items?: Array<Record<string, unknown>> }>(\n `/api/customers/deals?${params.toString()}`,\n { signal },\n )\n const items = Array.isArray(data?.items) ? data.items : []\n return items\n .map((item) => (item && typeof item === 'object' ? extractDealOption(item) : null))\n .filter((option): option is DealOption => option !== null)\n}\n\nexport async function fetchDealById(dealId: string, signal: AbortSignal): Promise<DealOption | null> {\n try {\n const params = new URLSearchParams({ page: '1', pageSize: '1', ids: dealId })\n const data = await readApiResultOrThrow<{ items?: Array<Record<string, unknown>> }>(\n `/api/customers/deals?${params.toString()}`,\n { signal },\n )\n const item = Array.isArray(data?.items) ? data.items[0] : null\n return item && typeof item === 'object' ? extractDealOption(item) : null\n } catch {\n return null\n }\n}\n\nexport async function searchPeopleOptions(\n query: string,\n options: { includeCustomers: boolean; includeStaff?: boolean; signal: AbortSignal },\n): Promise<PersonOption[]> {\n // Staff lookup is optional twice over: skipped when the staff module is not\n // loaded (includeStaff === false) and non-fatal when the endpoint errors, so\n // the people picker always degrades to customer-only options (#3552).\n const [staff, customers] = await Promise.all([\n options.includeStaff === false\n ? Promise.resolve<Awaited<ReturnType<typeof fetchAssignableStaffMembers>>>([])\n : fetchAssignableStaffMembers(query, { pageSize: 10, signal: options.signal }).catch(() => []),\n options.includeCustomers\n ? searchRelatedEntities('person', query, options.signal)\n : Promise.resolve<RelatedEntityOption[]>([]),\n ])\n const staffOptions: PersonOption[] = staff.map((member) => ({\n userId: member.userId,\n name: member.displayName,\n email: member.email,\n isCustomer: false,\n }))\n const customerOptions: PersonOption[] = customers.map((person) => ({\n userId: person.id,\n name: person.label,\n email: person.subtitle,\n isCustomer: true,\n }))\n return [...staffOptions, ...customerOptions]\n}\n\nexport type ResourceOption = {\n id: string\n label: string\n}\n\n// Consumed only when the resources module is loaded (server flag from the\n// calendar page); reads the resources CRUD list API \u2014 never resource entities.\nexport type ResourceType = { id: string; name: string; count: number }\n\nexport type ResourceSearchResult = { items: ResourceOption[]; total: number }\n\nconst RESOURCE_PAGE_SIZE = 20\n\n// Server-side resource search, optionally scoped to one resource type. Returns\n// the (page-limited) options plus the full match count so the UI can show a\n// \"showing N of M\" hint and nudge the user to narrow a large catalog (#3552).\nexport async function searchResourceOptions(\n params: { query: string; resourceTypeId?: string | null },\n signal: AbortSignal,\n): Promise<ResourceSearchResult> {\n const search = new URLSearchParams({\n page: '1',\n pageSize: String(RESOURCE_PAGE_SIZE),\n sortField: 'name',\n sortDir: 'asc',\n isActive: 'true',\n })\n if (params.query.length) search.set('search', params.query)\n if (params.resourceTypeId) search.set('resourceTypeId', params.resourceTypeId)\n const data = await readApiResultOrThrow<{ items?: Array<Record<string, unknown>>; total?: number }>(\n `/api/resources/resources?${search.toString()}`,\n { signal },\n )\n const rows = Array.isArray(data?.items) ? data.items : []\n const items = rows\n .map((item): ResourceOption | null => {\n if (!item || typeof item !== 'object') return null\n const id = typeof item.id === 'string' ? item.id : null\n if (!id) return null\n const label = readNonEmptyString(item.name) ?? id\n return { id, label }\n })\n .filter((option): option is ResourceOption => option !== null)\n const total = typeof data?.total === 'number' && Number.isFinite(data.total) ? data.total : items.length\n return { items, total }\n}\n\n// Resource types with their resource counts, for the dropdown's type filter.\nexport async function fetchResourceTypes(signal: AbortSignal): Promise<ResourceType[]> {\n const params = new URLSearchParams({\n page: '1',\n pageSize: '100',\n sortField: 'name',\n sortDir: 'asc',\n withResourceCounts: 'true',\n })\n try {\n const data = await readApiResultOrThrow<{ items?: Array<Record<string, unknown>> }>(\n `/api/resources/resource-types?${params.toString()}`,\n { signal },\n )\n const rows = Array.isArray(data?.items) ? data.items : []\n return rows\n .map((item): ResourceType | null => {\n if (!item || typeof item !== 'object') return null\n const id = typeof item.id === 'string' ? item.id : null\n if (!id) return null\n const name = readNonEmptyString(item.name) ?? id\n const rawCount = (item as Record<string, unknown>).resourceCount\n const count = typeof rawCount === 'number' && Number.isFinite(rawCount) ? rawCount : 0\n return { id, name, count }\n })\n .filter((type): type is ResourceType => type !== null)\n } catch {\n return []\n }\n}\n\nexport type ContactPhone = { id: string; name: string; phone: string }\n\n// Resolves primary phone numbers for the given person ids (companies/staff have\n// none and simply drop out \u2014 the people query only matches people). Used by the\n// Call editor's \"insert phone from contact\" button (#3552).\nexport async function fetchPeoplePhones(ids: string[], signal: AbortSignal): Promise<ContactPhone[]> {\n const unique = Array.from(new Set(ids.filter((id) => typeof id === 'string' && id.length > 0)))\n if (unique.length === 0) return []\n const params = new URLSearchParams({ page: '1', pageSize: '100', ids: unique.join(',') })\n try {\n const data = await readApiResultOrThrow<{ items?: Array<Record<string, unknown>> }>(\n `/api/customers/people?${params.toString()}`,\n { signal },\n )\n const items = Array.isArray(data?.items) ? data.items : []\n const result: ContactPhone[] = []\n for (const item of items) {\n if (!item || typeof item !== 'object') continue\n const id = typeof item.id === 'string' ? item.id : null\n const phone = readNonEmptyString(item.primaryPhone) ?? readNonEmptyString(item.primary_phone)\n if (!id || !phone) continue\n const name = readNonEmptyString(item.displayName) ?? readNonEmptyString(item.display_name) ?? id\n result.push({ id, name, phone })\n }\n return result\n } catch {\n return []\n }\n}\n\nexport async function findStaffMemberName(userId: string, signal: AbortSignal): Promise<string | null> {\n try {\n const members = await fetchAssignableStaffMembers('', { pageSize: 100, signal })\n const match = members.find((member) => member.userId === userId)\n return match ? match.displayName : null\n } catch {\n return null\n }\n}\n"],
|
|
5
|
-
"mappings": "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
|
|
4
|
+
"sourcesContent": ["import { readApiResultOrThrow } from '@open-mercato/ui/backend/utils/apiCall'\nimport { fetchAssignableStaffMembers } from '../../detail/assignableStaff'\n\nexport type RelatedEntityKind = 'person' | 'company'\n\nexport type RelatedEntityOption = {\n id: string\n kind: RelatedEntityKind\n label: string\n subtitle: string | null\n}\n\nexport type DealOption = {\n id: string\n label: string\n}\n\nexport type PersonOption = {\n userId: string\n name: string\n email: string | null\n isCustomer: boolean\n}\n\nfunction readNonEmptyString(value: unknown): string | null {\n if (typeof value !== 'string') return null\n const trimmed = value.trim()\n return trimmed.length > 0 ? trimmed : null\n}\n\nfunction extractRelatedEntityOption(item: Record<string, unknown>, kind: RelatedEntityKind): RelatedEntityOption | null {\n const id = typeof item.id === 'string' ? item.id : null\n if (!id) return null\n const displayName = readNonEmptyString(item.displayName) ?? readNonEmptyString(item.display_name)\n const detail = kind === 'person'\n ? readNonEmptyString(item.primaryEmail) ?? readNonEmptyString(item.primary_email)\n : readNonEmptyString(item.domain)\n ?? readNonEmptyString(item.websiteUrl)\n ?? readNonEmptyString(item.website_url)\n const label = displayName ?? detail ?? id\n return { id, kind, label, subtitle: detail && detail !== label ? detail : null }\n}\n\n// Same lookup source as the deal-association pattern in DealForm\n// (useDealAssociationLookups): the people/companies CRUD list APIs with\n// `search` + name sorting, merged into a single related-to picker.\nexport async function searchRelatedEntities(\n kind: RelatedEntityKind,\n query: string,\n signal: AbortSignal,\n): Promise<RelatedEntityOption[]> {\n const params = new URLSearchParams({ page: '1', pageSize: '20', sortField: 'name', sortDir: 'asc' })\n if (query.length) params.set('search', query)\n const endpoint = kind === 'person'\n ? `/api/customers/people?${params.toString()}`\n : `/api/customers/companies?${params.toString()}`\n const data = await readApiResultOrThrow<{ items?: Array<Record<string, unknown>> }>(endpoint, { signal })\n const items = Array.isArray(data?.items) ? data.items : []\n return items\n .map((item) => (item && typeof item === 'object' ? extractRelatedEntityOption(item, kind) : null))\n .filter((option): option is RelatedEntityOption => option !== null)\n}\n\nasync function fetchRelatedEntityOfKind(\n kind: RelatedEntityKind,\n entityId: string,\n signal: AbortSignal,\n): Promise<RelatedEntityOption | null> {\n const params = new URLSearchParams({ page: '1', pageSize: '1', ids: entityId })\n const endpoint = kind === 'person'\n ? `/api/customers/people?${params.toString()}`\n : `/api/customers/companies?${params.toString()}`\n try {\n const data = await readApiResultOrThrow<{ items?: Array<Record<string, unknown>> }>(endpoint, { signal })\n const item = Array.isArray(data?.items) ? data.items[0] : null\n return item && typeof item === 'object' ? extractRelatedEntityOption(item, kind) : null\n } catch {\n return null\n }\n}\n\nexport async function fetchRelatedEntityById(entityId: string, signal: AbortSignal): Promise<RelatedEntityOption | null> {\n const [person, company] = await Promise.all([\n fetchRelatedEntityOfKind('person', entityId, signal),\n fetchRelatedEntityOfKind('company', entityId, signal),\n ])\n return person ?? company\n}\n\nfunction extractDealOption(item: Record<string, unknown>): DealOption | null {\n const id = typeof item.id === 'string' ? item.id : null\n if (!id) return null\n const label = readNonEmptyString(item.title) ?? id\n return { id, label }\n}\n\nexport async function fetchDealsForEntity(\n entity: { id: string; kind: RelatedEntityKind },\n signal: AbortSignal,\n): Promise<DealOption[]> {\n const params = new URLSearchParams({ page: '1', pageSize: '20' })\n params.set(entity.kind === 'person' ? 'personId' : 'companyId', entity.id)\n const data = await readApiResultOrThrow<{ items?: Array<Record<string, unknown>> }>(\n `/api/customers/deals?${params.toString()}`,\n { signal },\n )\n const items = Array.isArray(data?.items) ? data.items : []\n return items\n .map((item) => (item && typeof item === 'object' ? extractDealOption(item) : null))\n .filter((option): option is DealOption => option !== null)\n}\n\nexport async function fetchDealById(dealId: string, signal: AbortSignal): Promise<DealOption | null> {\n try {\n const params = new URLSearchParams({ page: '1', pageSize: '1', ids: dealId })\n const data = await readApiResultOrThrow<{ items?: Array<Record<string, unknown>> }>(\n `/api/customers/deals?${params.toString()}`,\n { signal },\n )\n const item = Array.isArray(data?.items) ? data.items[0] : null\n return item && typeof item === 'object' ? extractDealOption(item) : null\n } catch {\n return null\n }\n}\n\nexport async function searchPeopleOptions(\n query: string,\n options: { includeCustomers: boolean; includeStaff?: boolean; signal: AbortSignal; activeOrgId?: string | null },\n): Promise<PersonOption[]> {\n // Staff lookup is optional twice over: skipped when the staff module is not\n // loaded (includeStaff === false) and non-fatal when the endpoint errors, so\n // the people picker always degrades to customer-only options (#3552).\n const [staff, customers] = await Promise.all([\n options.includeStaff === false\n ? Promise.resolve<Awaited<ReturnType<typeof fetchAssignableStaffMembers>>>([])\n : fetchAssignableStaffMembers(query, {\n pageSize: 10,\n activeOrgId: options.activeOrgId,\n signal: options.signal,\n }).catch(() => []),\n options.includeCustomers\n ? searchRelatedEntities('person', query, options.signal)\n : Promise.resolve<RelatedEntityOption[]>([]),\n ])\n const staffOptions: PersonOption[] = staff.map((member) => ({\n userId: member.userId,\n name: member.displayName,\n email: member.email,\n isCustomer: false,\n }))\n const customerOptions: PersonOption[] = customers.map((person) => ({\n userId: person.id,\n name: person.label,\n email: person.subtitle,\n isCustomer: true,\n }))\n return [...staffOptions, ...customerOptions]\n}\n\nexport type ResourceOption = {\n id: string\n label: string\n}\n\n// Consumed only when the resources module is loaded (server flag from the\n// calendar page); reads the resources CRUD list API \u2014 never resource entities.\nexport type ResourceType = { id: string; name: string; count: number }\n\nexport type ResourceSearchResult = { items: ResourceOption[]; total: number }\n\nconst RESOURCE_PAGE_SIZE = 20\n\n// Server-side resource search, optionally scoped to one resource type. Returns\n// the (page-limited) options plus the full match count so the UI can show a\n// \"showing N of M\" hint and nudge the user to narrow a large catalog (#3552).\nexport async function searchResourceOptions(\n params: { query: string; resourceTypeId?: string | null },\n signal: AbortSignal,\n): Promise<ResourceSearchResult> {\n const search = new URLSearchParams({\n page: '1',\n pageSize: String(RESOURCE_PAGE_SIZE),\n sortField: 'name',\n sortDir: 'asc',\n isActive: 'true',\n })\n if (params.query.length) search.set('search', params.query)\n if (params.resourceTypeId) search.set('resourceTypeId', params.resourceTypeId)\n const data = await readApiResultOrThrow<{ items?: Array<Record<string, unknown>>; total?: number }>(\n `/api/resources/resources?${search.toString()}`,\n { signal },\n )\n const rows = Array.isArray(data?.items) ? data.items : []\n const items = rows\n .map((item): ResourceOption | null => {\n if (!item || typeof item !== 'object') return null\n const id = typeof item.id === 'string' ? item.id : null\n if (!id) return null\n const label = readNonEmptyString(item.name) ?? id\n return { id, label }\n })\n .filter((option): option is ResourceOption => option !== null)\n const total = typeof data?.total === 'number' && Number.isFinite(data.total) ? data.total : items.length\n return { items, total }\n}\n\n// Resource types with their resource counts, for the dropdown's type filter.\nexport async function fetchResourceTypes(signal: AbortSignal): Promise<ResourceType[]> {\n const params = new URLSearchParams({\n page: '1',\n pageSize: '100',\n sortField: 'name',\n sortDir: 'asc',\n withResourceCounts: 'true',\n })\n try {\n const data = await readApiResultOrThrow<{ items?: Array<Record<string, unknown>> }>(\n `/api/resources/resource-types?${params.toString()}`,\n { signal },\n )\n const rows = Array.isArray(data?.items) ? data.items : []\n return rows\n .map((item): ResourceType | null => {\n if (!item || typeof item !== 'object') return null\n const id = typeof item.id === 'string' ? item.id : null\n if (!id) return null\n const name = readNonEmptyString(item.name) ?? id\n const rawCount = (item as Record<string, unknown>).resourceCount\n const count = typeof rawCount === 'number' && Number.isFinite(rawCount) ? rawCount : 0\n return { id, name, count }\n })\n .filter((type): type is ResourceType => type !== null)\n } catch {\n return []\n }\n}\n\nexport type ContactPhone = { id: string; name: string; phone: string }\n\n// Resolves primary phone numbers for the given person ids (companies/staff have\n// none and simply drop out \u2014 the people query only matches people). Used by the\n// Call editor's \"insert phone from contact\" button (#3552).\nexport async function fetchPeoplePhones(ids: string[], signal: AbortSignal): Promise<ContactPhone[]> {\n const unique = Array.from(new Set(ids.filter((id) => typeof id === 'string' && id.length > 0)))\n if (unique.length === 0) return []\n const params = new URLSearchParams({ page: '1', pageSize: '100', ids: unique.join(',') })\n try {\n const data = await readApiResultOrThrow<{ items?: Array<Record<string, unknown>> }>(\n `/api/customers/people?${params.toString()}`,\n { signal },\n )\n const items = Array.isArray(data?.items) ? data.items : []\n const result: ContactPhone[] = []\n for (const item of items) {\n if (!item || typeof item !== 'object') continue\n const id = typeof item.id === 'string' ? item.id : null\n const phone = readNonEmptyString(item.primaryPhone) ?? readNonEmptyString(item.primary_phone)\n if (!id || !phone) continue\n const name = readNonEmptyString(item.displayName) ?? readNonEmptyString(item.display_name) ?? id\n result.push({ id, name, phone })\n }\n return result\n } catch {\n return []\n }\n}\n\nexport async function findStaffMemberName(\n userId: string,\n signal: AbortSignal,\n activeOrgId?: string | null,\n): Promise<string | null> {\n try {\n const members = await fetchAssignableStaffMembers('', { pageSize: 100, activeOrgId, signal })\n const match = members.find((member) => member.userId === userId)\n return match ? match.displayName : null\n } catch {\n return null\n }\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
|
@@ -18,6 +18,7 @@ import {
|
|
|
18
18
|
DialogHeader,
|
|
19
19
|
DialogTitle
|
|
20
20
|
} from "@open-mercato/ui/primitives/dialog";
|
|
21
|
+
import { useCurrentOrganization } from "@open-mercato/ui/backend/BackendChromeProvider";
|
|
21
22
|
import { fetchAssignableStaffMembersPage } from "./assignableStaff.js";
|
|
22
23
|
const MANAGE_ROLE_TYPES_HREF = "/backend/config/customers";
|
|
23
24
|
const ASSIGNABLE_STAFF_PAGE_SIZE = 24;
|
|
@@ -33,6 +34,7 @@ function AssignRoleDialog({
|
|
|
33
34
|
canManageRoleTypes = false
|
|
34
35
|
}) {
|
|
35
36
|
const t = useT();
|
|
37
|
+
const activeOrgId = useCurrentOrganization()?.id ?? null;
|
|
36
38
|
const [step, setStep] = React.useState(1);
|
|
37
39
|
const [selectedRoleType, setSelectedRoleType] = React.useState("");
|
|
38
40
|
const [selectedUser, setSelectedUser] = React.useState(null);
|
|
@@ -96,7 +98,8 @@ function AssignRoleDialog({
|
|
|
96
98
|
try {
|
|
97
99
|
const result = await fetchAssignableStaffMembersPage(query, {
|
|
98
100
|
page,
|
|
99
|
-
pageSize: ASSIGNABLE_STAFF_PAGE_SIZE
|
|
101
|
+
pageSize: ASSIGNABLE_STAFF_PAGE_SIZE,
|
|
102
|
+
activeOrgId
|
|
100
103
|
});
|
|
101
104
|
if (requestSequenceRef.current !== requestId) return;
|
|
102
105
|
const nextUsers = result.items.map((member) => ({
|
|
@@ -138,7 +141,7 @@ function AssignRoleDialog({
|
|
|
138
141
|
}
|
|
139
142
|
}
|
|
140
143
|
},
|
|
141
|
-
[t]
|
|
144
|
+
[activeOrgId, t]
|
|
142
145
|
);
|
|
143
146
|
React.useEffect(() => {
|
|
144
147
|
if (step >= 2) {
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../../../../src/modules/customers/components/detail/AssignRoleDialog.tsx"],
|
|
4
|
-
"sourcesContent": ["'use client'\n\nimport * as React from 'react'\nimport Link from 'next/link'\nimport { Search, Check, CheckCheck, Settings2 } from 'lucide-react'\nimport { Avatar } from '@open-mercato/ui/primitives/avatar'\nimport { EmptyState } from '@open-mercato/ui/primitives/empty-state'\nimport { StepIndicator, type StepIndicatorStep } from '@open-mercato/ui/primitives/step-indicator'\nimport { useT } from '@open-mercato/shared/lib/i18n/context'\nimport { hasMoreFromPage } from '@open-mercato/shared/lib/pagination/load-more'\nimport { Button } from '@open-mercato/ui/primitives/button'\nimport { Badge } from '@open-mercato/ui/primitives/badge'\nimport { Input } from '@open-mercato/ui/primitives/input'\nimport {\n Dialog,\n DialogContent,\n DialogFooter,\n DialogHeader,\n DialogTitle,\n} from '@open-mercato/ui/primitives/dialog'\nimport type { DictionaryEntryOption } from '@open-mercato/core/modules/dictionaries/lib/clientEntries'\nimport type { RoleAssignment } from './RoleAssignmentRow'\nimport { fetchAssignableStaffMembersPage } from './assignableStaff'\n\nconst MANAGE_ROLE_TYPES_HREF = '/backend/config/customers'\n\ntype StaffMember = {\n id: string\n displayName: string\n email: string | null\n teamName: string | null\n}\n\ninterface AssignRoleDialogProps {\n open: boolean\n onClose: () => void\n onAssign: (roleType: string, userId: string) => Promise<void>\n roleTypes: DictionaryEntryOption[]\n entityName: string\n existingRoleTypes?: Set<string>\n existingAssignments?: RoleAssignment[]\n initialRoleType?: string | null\n canManageRoleTypes?: boolean\n}\n\ntype StepId = 1 | 2 | 3\n\ntype TeamFilter = {\n id: string\n label: string\n count: number\n}\n\nconst ASSIGNABLE_STAFF_PAGE_SIZE = 24\n\nexport function AssignRoleDialog({\n open,\n onClose,\n onAssign,\n roleTypes,\n entityName,\n existingRoleTypes,\n existingAssignments = [],\n initialRoleType = null,\n canManageRoleTypes = false,\n}: AssignRoleDialogProps) {\n const t = useT()\n const [step, setStep] = React.useState<StepId>(1)\n const [selectedRoleType, setSelectedRoleType] = React.useState('')\n const [selectedUser, setSelectedUser] = React.useState<StaffMember | null>(null)\n const [searchQuery, setSearchQuery] = React.useState('')\n const [users, setUsers] = React.useState<StaffMember[]>([])\n const [loading, setLoading] = React.useState(false)\n const [loadingMore, setLoadingMore] = React.useState(false)\n const [saving, setSaving] = React.useState(false)\n const [activeTeam, setActiveTeam] = React.useState('all')\n const [loadError, setLoadError] = React.useState<string | null>(null)\n const [totalUsers, setTotalUsers] = React.useState(0)\n // Short-page termination instead of `users.length >= totalUsers` \u2014 see\n // `hasMoreFromPage`. Measured on the served count, not on `users`, which is\n // deduped by id.\n const [hasMore, setHasMore] = React.useState(false)\n const [currentPage, setCurrentPage] = React.useState(1)\n const deferredSearchQuery = React.useDeferredValue(searchQuery)\n const requestSequenceRef = React.useRef(0)\n\n const availableRoleTypes = React.useMemo(\n () => roleTypes.filter((roleType) => !existingRoleTypes?.has(roleType.value)),\n [existingRoleTypes, roleTypes],\n )\n\n React.useEffect(() => {\n if (!open) {\n setStep(1)\n setSelectedRoleType('')\n setSelectedUser(null)\n setSearchQuery('')\n setUsers([])\n setActiveTeam('all')\n setLoadError(null)\n setTotalUsers(0)\n setHasMore(false)\n setCurrentPage(1)\n requestSequenceRef.current = 0\n return\n }\n const resolvedInitialRoleType =\n typeof initialRoleType === 'string' && initialRoleType.trim().length > 0\n ? initialRoleType.trim()\n : ''\n setStep(resolvedInitialRoleType ? 2 : 1)\n setSelectedRoleType(resolvedInitialRoleType)\n setSelectedUser(null)\n setSearchQuery('')\n setUsers([])\n setActiveTeam('all')\n setLoadError(null)\n setTotalUsers(0)\n setHasMore(false)\n setCurrentPage(1)\n requestSequenceRef.current = 0\n }, [initialRoleType, open])\n\n const searchUsers = React.useCallback(\n async ({\n query,\n page,\n append,\n }: {\n query: string\n page: number\n append: boolean\n }) => {\n const requestId = append ? requestSequenceRef.current : requestSequenceRef.current + 1\n requestSequenceRef.current = requestId\n\n if (append) {\n setLoadingMore(true)\n } else {\n setLoading(true)\n }\n\n try {\n const result = await fetchAssignableStaffMembersPage(query, {\n page,\n pageSize: ASSIGNABLE_STAFF_PAGE_SIZE,\n })\n if (requestSequenceRef.current !== requestId) return\n\n const nextUsers = result.items.map((member) => ({\n id: member.userId,\n displayName: member.displayName,\n email: member.email,\n teamName: member.teamName,\n }))\n\n setUsers((current) => {\n if (!append) return nextUsers\n const merged = new Map(current.map((user) => [user.id, user]))\n nextUsers.forEach((user) => merged.set(user.id, user))\n return Array.from(merged.values())\n })\n setTotalUsers(result.total)\n setHasMore(hasMoreFromPage(result.servedCount, ASSIGNABLE_STAFF_PAGE_SIZE))\n setCurrentPage(result.page)\n setLoadError(null)\n } catch {\n if (requestSequenceRef.current !== requestId) return\n if (!append) {\n setUsers([])\n setTotalUsers(0)\n setHasMore(false)\n setCurrentPage(1)\n }\n setLoadError(\n t(\n 'customers.assignableStaff.loadError',\n 'Unable to load team members. Check your permissions and try again.',\n ),\n )\n } finally {\n if (requestSequenceRef.current !== requestId) return\n if (append) {\n setLoadingMore(false)\n } else {\n setLoading(false)\n }\n }\n },\n [t],\n )\n\n React.useEffect(() => {\n if (step >= 2) {\n // fire-and-forget: search results populate async; errors shown in list UI\n searchUsers({ query: deferredSearchQuery, page: 1, append: false }).catch(() => {})\n }\n }, [deferredSearchQuery, searchUsers, step])\n\n const handleLoadMore = React.useCallback(() => {\n if (loading || loadingMore || !hasMore) return\n // fire-and-forget: search results populate async; errors shown in list UI\n searchUsers({\n query: deferredSearchQuery,\n page: currentPage + 1,\n append: true,\n }).catch(() => {})\n }, [currentPage, deferredSearchQuery, hasMore, loading, loadingMore, searchUsers])\n\n const selectedRole = React.useMemo(\n () => roleTypes.find((roleType) => roleType.value === selectedRoleType) ?? null,\n [roleTypes, selectedRoleType],\n )\n\n const roleTypeLabelMap = React.useMemo(\n () => new Map(roleTypes.map((roleType) => [roleType.value, roleType.label])),\n [roleTypes],\n )\n\n const conflictsByUserId = React.useMemo(() => {\n const next = new Map<string, string[]>()\n existingAssignments.forEach((assignment) => {\n if (!assignment.userId) return\n if (assignment.roleType === selectedRoleType) return\n const label = roleTypeLabelMap.get(assignment.roleType) ?? assignment.roleType\n const current = next.get(assignment.userId) ?? []\n if (!current.includes(label)) {\n current.push(label)\n next.set(assignment.userId, current)\n }\n })\n return next\n }, [existingAssignments, roleTypeLabelMap, selectedRoleType])\n\n const selectedUserConflict = React.useMemo(() => {\n if (!selectedUser) return null\n return conflictsByUserId.get(selectedUser.id) ?? null\n }, [conflictsByUserId, selectedUser])\n\n const teamFilters = React.useMemo<TeamFilter[]>(() => {\n const counts = new Map<string, number>()\n users.forEach((user) => {\n const key =\n user.teamName?.trim() || t('customers.roles.dialog.team.unassigned', 'No team')\n counts.set(key, (counts.get(key) ?? 0) + 1)\n })\n return [\n { id: 'all', label: t('customers.roles.dialog.team.all', 'All'), count: users.length },\n ...Array.from(counts.entries()).map(([label, count]) => ({ id: label, label, count })),\n ]\n }, [t, users])\n\n const filteredUsers = React.useMemo(() => {\n if (activeTeam === 'all') return users\n return users.filter(\n (user) =>\n (user.teamName?.trim() || t('customers.roles.dialog.team.unassigned', 'No team')) ===\n activeTeam,\n )\n }, [activeTeam, t, users])\n\n const visibleCountLabel = React.useMemo(() => {\n if (totalUsers <= 0) return null\n return t(\n 'customers.roles.dialog.visibleCount',\n 'Showing {{shown}} of {{total}} team members',\n {\n shown: String(users.length),\n total: String(totalUsers),\n },\n )\n }, [t, totalUsers, users.length])\n\n const canLoadMore = hasMore\n\n const handleAssign = React.useCallback(async () => {\n if (!selectedRoleType || !selectedUser) return\n setSaving(true)\n try {\n await onAssign(selectedRoleType, selectedUser.id)\n onClose()\n } finally {\n setSaving(false)\n }\n }, [onAssign, onClose, selectedRoleType, selectedUser])\n\n const handleKeyDown = React.useCallback(\n (e: React.KeyboardEvent) => {\n if (e.key === 'Enter' && (e.metaKey || e.ctrlKey)) {\n e.preventDefault()\n if (step === 3 && !saving && selectedUser && selectedRoleType) {\n handleAssign()\n } else if (step === 1 && selectedRoleType && availableRoleTypes.length) {\n setStep(2)\n } else if (step === 2 && selectedUser) {\n setStep(3)\n }\n }\n },\n [availableRoleTypes.length, handleAssign, saving, selectedRoleType, selectedUser, step],\n )\n\n const previewCard =\n selectedUser && selectedRole ? (\n <div className=\"rounded-lg border border-border/70 bg-muted/30 px-4 py-4\">\n <p className=\"text-overline font-semibold uppercase tracking-[0.12em] text-muted-foreground\">\n {t('customers.roles.dialog.preview', 'Assignment preview')}\n </p>\n <div className=\"mt-3 flex items-center gap-3\">\n <Avatar\n label={selectedUser.displayName}\n size=\"lg\"\n className=\"bg-background text-foreground\"\n />\n <div className=\"min-w-0\">\n <div className=\"flex flex-wrap items-center gap-2 text-sm font-semibold text-foreground\">\n <span>{selectedUser.displayName}</span>\n <span className=\"text-muted-foreground\">\u2192</span>\n <span>{selectedRole.label}</span>\n </div>\n <div className=\"mt-1 flex flex-wrap items-center gap-3 text-xs text-muted-foreground\">\n {selectedUser.email ? <span>{selectedUser.email}</span> : null}\n {selectedUser.teamName ? <span>{selectedUser.teamName}</span> : null}\n </div>\n {selectedUserConflict?.length ? (\n <div className=\"mt-2 flex flex-wrap items-center gap-2\">\n <Badge\n variant=\"outline\"\n className=\"rounded-full border-status-error-border bg-status-error-bg px-2 py-0.5 text-xs font-semibold text-status-error-text\"\n >\n {t('customers.roles.dialog.conflict', 'Conflict: {{roles}}', {\n roles: selectedUserConflict.join(', '),\n })}\n </Badge>\n </div>\n ) : null}\n </div>\n </div>\n </div>\n ) : null\n\n return (\n <Dialog\n open={open}\n onOpenChange={(nextOpen) => {\n if (!nextOpen) onClose()\n }}\n >\n <DialogContent\n className=\"min-h-0 max-h-[min(90vh,760px)] overflow-hidden p-0 sm:max-w-[580px]\"\n onKeyDown={handleKeyDown}\n >\n <DialogHeader className=\"border-b border-border/70 px-6 py-5\">\n <DialogTitle className=\"text-2xl font-semibold leading-none\">\n {t('customers.roles.dialog.title', 'Assign role')}\n </DialogTitle>\n <p className=\"mt-2 text-sm text-muted-foreground\">\n {t('customers.roles.dialog.subtitle', 'Multi-role assignment for {{name}}', {\n name: entityName,\n })}\n </p>\n </DialogHeader>\n\n <div className=\"border-b border-border/70 px-6 py-4\">\n <StepIndicator\n className=\"justify-center\"\n steps={[\n {\n id: '1',\n label: t('customers.roles.dialog.step1', 'Role type'),\n status: step > 1 ? 'complete' : step === 1 ? 'current' : 'pending',\n },\n {\n id: '2',\n label: t('customers.roles.dialog.step2', 'Select person'),\n status: step > 2 ? 'complete' : step === 2 ? 'current' : 'pending',\n },\n {\n id: '3',\n label: t('customers.roles.dialog.step3', 'Confirm'),\n status: step === 3 ? 'current' : 'pending',\n },\n ] satisfies StepIndicatorStep[]}\n />\n </div>\n\n <div className=\"min-h-0 flex-1 overflow-y-auto\">\n <div className=\"space-y-5 px-6 py-5\">\n {step === 1 ? (\n <div className=\"space-y-4\">\n <div className=\"rounded-lg bg-muted/30 px-4 py-4\">\n <p\n id=\"assign-role-dialog-type-label\"\n className=\"text-overline font-semibold uppercase tracking-[0.12em] text-muted-foreground\"\n >\n {t('customers.roles.dialog.roleTypeLabel', 'Role type')}\n </p>\n <div className=\"mt-2 flex items-center gap-2\">\n <select\n aria-labelledby=\"assign-role-dialog-type-label\"\n value={selectedRoleType}\n onChange={(event) => setSelectedRoleType(event.target.value)}\n className=\"h-10 w-full rounded-md border border-border bg-background px-3 text-sm focus:outline-none focus:ring-2 focus:ring-ring/30\"\n >\n <option value=\"\">\n {t('customers.roles.selectRoleType', 'Select role type...')}\n </option>\n {availableRoleTypes.map((roleType) => (\n <option key={roleType.id} value={roleType.value}>\n {roleType.label}\n </option>\n ))}\n </select>\n {selectedRole ? (\n <Badge\n variant=\"outline\"\n className=\"rounded-md px-2 py-1 text-xs\"\n >\n {t('customers.roles.dialog.sourceBadge.dictionary', 'Dictionary')}\n </Badge>\n ) : null}\n </div>\n {canManageRoleTypes ? (\n <Link\n href={MANAGE_ROLE_TYPES_HREF}\n className=\"mt-3 inline-flex items-center gap-1.5 text-xs font-medium text-primary hover:underline\"\n data-testid=\"assign-role-dialog-manage-role-types\"\n >\n <Settings2 className=\"size-3.5\" aria-hidden=\"true\" />\n {t('customers.roles.dialog.manageRoleTypes', 'Manage role types')}\n </Link>\n ) : null}\n </div>\n\n {!availableRoleTypes.length ? (\n <EmptyState\n size=\"sm\"\n icon={<CheckCheck className=\"h-8 w-8\" aria-hidden=\"true\" />}\n title={t(\n 'customers.roles.dialog.noAvailableRoles',\n 'All available role types are already assigned.',\n )}\n />\n ) : null}\n </div>\n ) : null}\n\n {step === 2 ? (\n <div className=\"space-y-4\">\n <div className=\"rounded-lg bg-muted/30 px-4 py-4\">\n <div className=\"flex items-center justify-between gap-3\">\n <div>\n <p className=\"text-overline font-semibold uppercase tracking-[0.12em] text-muted-foreground\">\n {t('customers.roles.dialog.roleTypeLabel', 'Role type')}\n </p>\n <div className=\"mt-1 flex items-center gap-2\">\n <span className=\"text-lg font-semibold text-foreground\">\n {selectedRole?.label ?? selectedRoleType}\n </span>\n <Badge\n variant=\"outline\"\n className=\"rounded-md px-2 py-1 text-xs\"\n >\n {t('customers.roles.dialog.sourceBadge.dictionary', 'Dictionary')}\n </Badge>\n </div>\n </div>\n <div className=\"flex items-center gap-2\">\n {canManageRoleTypes ? (\n <Button asChild variant=\"ghost\" size=\"sm\">\n <Link\n href={MANAGE_ROLE_TYPES_HREF}\n data-testid=\"assign-role-dialog-manage-role-types-step2\"\n >\n <Settings2 className=\"mr-1 size-3.5\" aria-hidden=\"true\" />\n {t('customers.roles.dialog.manageRoleTypes', 'Manage role types')}\n </Link>\n </Button>\n ) : null}\n <Button\n type=\"button\"\n variant=\"outline\"\n size=\"sm\"\n onClick={() => setStep(1)}\n >\n {t('customers.roles.dialog.change', 'Change')}\n </Button>\n </div>\n </div>\n </div>\n\n <div className=\"space-y-2\">\n <p className=\"text-overline font-semibold uppercase tracking-[0.12em] text-muted-foreground\">\n {t('customers.roles.dialog.teamLabel', 'Select a team member')}\n </p>\n <div className=\"relative\">\n <Search className=\"absolute left-3 top-1/2 size-4 -translate-y-1/2 text-muted-foreground\" />\n <Input\n value={searchQuery}\n onChange={(event) => setSearchQuery(event.target.value)}\n placeholder={t(\n 'customers.roles.dialog.searchPlaceholder',\n 'Search by name, e-mail or team...',\n )}\n className=\"h-10 rounded-md border-border/80 pl-9 shadow-none\"\n />\n </div>\n </div>\n\n <div className=\"flex flex-wrap items-center gap-2\">\n {teamFilters.map((teamFilter) => {\n const isActive = activeTeam === teamFilter.id\n return (\n <Button\n key={teamFilter.id}\n type=\"button\"\n variant={isActive ? 'default' : 'outline'}\n size=\"sm\"\n onClick={() => setActiveTeam(teamFilter.id)}\n className=\"h-7 rounded-md px-2.5 text-xs\"\n >\n {teamFilter.label}\n <span className=\"rounded-full bg-background/80 px-1 text-xs text-muted-foreground\">\n {teamFilter.count}\n </span>\n </Button>\n )\n })}\n </div>\n\n <div className=\"space-y-2\">\n {visibleCountLabel ? (\n <p className=\"text-xs text-muted-foreground\">{visibleCountLabel}</p>\n ) : null}\n {loading ? (\n <div className=\"rounded-lg border border-dashed border-border/80 px-4 py-8 text-center text-sm text-muted-foreground\">\n {t('customers.roles.loading', 'Loading...')}\n </div>\n ) : loadError ? (\n <div className=\"rounded-lg border border-dashed border-status-error-border bg-status-error-bg/70 px-4 py-8 text-center text-sm text-status-error-text\">\n {loadError}\n </div>\n ) : filteredUsers.length === 0 ? (\n <div className=\"rounded-lg border border-dashed border-border/80 px-4 py-8 text-center text-sm text-muted-foreground\">\n {t(\n 'customers.roles.dialog.noResults',\n 'No matching team members found.',\n )}\n </div>\n ) : (\n filteredUsers.map((user) => {\n const isSelected = selectedUser?.id === user.id\n const userConflicts = conflictsByUserId.get(user.id) ?? []\n return (\n <Button\n key={user.id}\n type=\"button\"\n variant=\"ghost\"\n onClick={() => setSelectedUser(user)}\n className={`h-auto flex w-full items-center gap-3 rounded-lg border px-4 py-3 text-left transition-colors ${\n isSelected\n ? 'border-foreground bg-background shadow-sm'\n : 'border-border/70 bg-background hover:bg-accent/40'\n }`}\n >\n <Avatar label={user.displayName} size=\"lg\" variant=\"monochrome\" />\n <div className=\"min-w-0 flex-1\">\n <div className=\"flex flex-wrap items-center gap-2\">\n <span className=\"text-sm font-semibold text-foreground\">\n {user.displayName}\n </span>\n {user.teamName ? (\n <Badge\n variant=\"muted\"\n className=\"rounded-md px-2 py-0.5 text-xs font-medium\"\n >\n {user.teamName}\n </Badge>\n ) : null}\n {userConflicts.length ? (\n <Badge\n variant=\"outline\"\n className=\"rounded-full border-status-error-border bg-status-error-bg px-2 py-0.5 text-xs font-semibold text-status-error-text\"\n >\n {t('customers.roles.dialog.conflict', 'Conflict: {{roles}}', {\n roles: userConflicts.join(', '),\n })}\n </Badge>\n ) : null}\n </div>\n {user.email ? (\n <div className=\"mt-1 text-xs text-muted-foreground\">\n {user.email}\n </div>\n ) : null}\n </div>\n <span\n className={`flex size-6 shrink-0 items-center justify-center rounded-full border ${\n isSelected\n ? 'border-foreground bg-foreground text-background'\n : 'border-border/80 bg-background text-transparent'\n }`}\n >\n <Check className=\"size-3.5\" />\n </span>\n </Button>\n )\n })\n )}\n {canLoadMore && !loadError ? (\n <Button\n type=\"button\"\n variant=\"outline\"\n onClick={handleLoadMore}\n disabled={loadingMore}\n className=\"w-full\"\n >\n {loadingMore\n ? t('customers.roles.dialog.loadingMore', 'Loading more...')\n : t('customers.roles.dialog.loadMore', 'Load more')}\n </Button>\n ) : null}\n </div>\n\n {previewCard}\n </div>\n ) : null}\n\n {step === 3 ? (\n <div className=\"space-y-4\">\n {previewCard}\n <p className=\"text-sm text-muted-foreground\">\n {t(\n 'customers.roles.dialog.constraint',\n 'One person per role. The assignment can be changed at any time.',\n )}\n </p>\n </div>\n ) : null}\n </div>\n </div>\n\n <DialogFooter\n bordered={false}\n className=\"shrink-0 border-t border-border/70 px-6 py-4\"\n leading={\n <p className=\"text-xs text-muted-foreground sm:max-w-2xs\">\n {t(\n 'customers.roles.dialog.footerNote',\n 'One person per role \u00B7 can be changed at any time',\n )}\n </p>\n }\n >\n <div className=\"flex items-center gap-2\">\n <Button type=\"button\" variant=\"outline\" onClick={onClose}>\n {t('customers.roles.cancelAdd', 'Cancel')}\n </Button>\n {step === 1 ? (\n <Button\n type=\"button\"\n onClick={() => setStep(2)}\n disabled={!selectedRoleType || !availableRoleTypes.length}\n >\n {t('customers.roles.dialog.next', 'Next')}\n </Button>\n ) : null}\n {step === 2 ? (\n <Button type=\"button\" onClick={() => setStep(3)} disabled={!selectedUser}>\n {t('customers.roles.dialog.next', 'Next')}\n </Button>\n ) : null}\n {step === 3 ? (\n <Button\n type=\"button\"\n onClick={handleAssign}\n disabled={saving || !selectedUser || !selectedRoleType}\n >\n {saving\n ? t('customers.roles.assigning', 'Assigning...')\n : t('customers.roles.dialog.assign', 'Assign role')}\n </Button>\n ) : null}\n </div>\n </DialogFooter>\n </DialogContent>\n </Dialog>\n )\n}\n\n"],
|
|
5
|
-
"mappings": "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",
|
|
4
|
+
"sourcesContent": ["'use client'\n\nimport * as React from 'react'\nimport Link from 'next/link'\nimport { Search, Check, CheckCheck, Settings2 } from 'lucide-react'\nimport { Avatar } from '@open-mercato/ui/primitives/avatar'\nimport { EmptyState } from '@open-mercato/ui/primitives/empty-state'\nimport { StepIndicator, type StepIndicatorStep } from '@open-mercato/ui/primitives/step-indicator'\nimport { useT } from '@open-mercato/shared/lib/i18n/context'\nimport { hasMoreFromPage } from '@open-mercato/shared/lib/pagination/load-more'\nimport { Button } from '@open-mercato/ui/primitives/button'\nimport { Badge } from '@open-mercato/ui/primitives/badge'\nimport { Input } from '@open-mercato/ui/primitives/input'\nimport {\n Dialog,\n DialogContent,\n DialogFooter,\n DialogHeader,\n DialogTitle,\n} from '@open-mercato/ui/primitives/dialog'\nimport type { DictionaryEntryOption } from '@open-mercato/core/modules/dictionaries/lib/clientEntries'\nimport type { RoleAssignment } from './RoleAssignmentRow'\nimport { useCurrentOrganization } from '@open-mercato/ui/backend/BackendChromeProvider'\nimport { fetchAssignableStaffMembersPage } from './assignableStaff'\n\nconst MANAGE_ROLE_TYPES_HREF = '/backend/config/customers'\n\ntype StaffMember = {\n id: string\n displayName: string\n email: string | null\n teamName: string | null\n}\n\ninterface AssignRoleDialogProps {\n open: boolean\n onClose: () => void\n onAssign: (roleType: string, userId: string) => Promise<void>\n roleTypes: DictionaryEntryOption[]\n entityName: string\n existingRoleTypes?: Set<string>\n existingAssignments?: RoleAssignment[]\n initialRoleType?: string | null\n canManageRoleTypes?: boolean\n}\n\ntype StepId = 1 | 2 | 3\n\ntype TeamFilter = {\n id: string\n label: string\n count: number\n}\n\nconst ASSIGNABLE_STAFF_PAGE_SIZE = 24\n\nexport function AssignRoleDialog({\n open,\n onClose,\n onAssign,\n roleTypes,\n entityName,\n existingRoleTypes,\n existingAssignments = [],\n initialRoleType = null,\n canManageRoleTypes = false,\n}: AssignRoleDialogProps) {\n const t = useT()\n const activeOrgId = useCurrentOrganization()?.id ?? null\n const [step, setStep] = React.useState<StepId>(1)\n const [selectedRoleType, setSelectedRoleType] = React.useState('')\n const [selectedUser, setSelectedUser] = React.useState<StaffMember | null>(null)\n const [searchQuery, setSearchQuery] = React.useState('')\n const [users, setUsers] = React.useState<StaffMember[]>([])\n const [loading, setLoading] = React.useState(false)\n const [loadingMore, setLoadingMore] = React.useState(false)\n const [saving, setSaving] = React.useState(false)\n const [activeTeam, setActiveTeam] = React.useState('all')\n const [loadError, setLoadError] = React.useState<string | null>(null)\n const [totalUsers, setTotalUsers] = React.useState(0)\n // Short-page termination instead of `users.length >= totalUsers` \u2014 see\n // `hasMoreFromPage`. Measured on the served count, not on `users`, which is\n // deduped by id.\n const [hasMore, setHasMore] = React.useState(false)\n const [currentPage, setCurrentPage] = React.useState(1)\n const deferredSearchQuery = React.useDeferredValue(searchQuery)\n const requestSequenceRef = React.useRef(0)\n\n const availableRoleTypes = React.useMemo(\n () => roleTypes.filter((roleType) => !existingRoleTypes?.has(roleType.value)),\n [existingRoleTypes, roleTypes],\n )\n\n React.useEffect(() => {\n if (!open) {\n setStep(1)\n setSelectedRoleType('')\n setSelectedUser(null)\n setSearchQuery('')\n setUsers([])\n setActiveTeam('all')\n setLoadError(null)\n setTotalUsers(0)\n setHasMore(false)\n setCurrentPage(1)\n requestSequenceRef.current = 0\n return\n }\n const resolvedInitialRoleType =\n typeof initialRoleType === 'string' && initialRoleType.trim().length > 0\n ? initialRoleType.trim()\n : ''\n setStep(resolvedInitialRoleType ? 2 : 1)\n setSelectedRoleType(resolvedInitialRoleType)\n setSelectedUser(null)\n setSearchQuery('')\n setUsers([])\n setActiveTeam('all')\n setLoadError(null)\n setTotalUsers(0)\n setHasMore(false)\n setCurrentPage(1)\n requestSequenceRef.current = 0\n }, [initialRoleType, open])\n\n const searchUsers = React.useCallback(\n async ({\n query,\n page,\n append,\n }: {\n query: string\n page: number\n append: boolean\n }) => {\n const requestId = append ? requestSequenceRef.current : requestSequenceRef.current + 1\n requestSequenceRef.current = requestId\n\n if (append) {\n setLoadingMore(true)\n } else {\n setLoading(true)\n }\n\n try {\n const result = await fetchAssignableStaffMembersPage(query, {\n page,\n pageSize: ASSIGNABLE_STAFF_PAGE_SIZE,\n activeOrgId,\n })\n if (requestSequenceRef.current !== requestId) return\n\n const nextUsers = result.items.map((member) => ({\n id: member.userId,\n displayName: member.displayName,\n email: member.email,\n teamName: member.teamName,\n }))\n\n setUsers((current) => {\n if (!append) return nextUsers\n const merged = new Map(current.map((user) => [user.id, user]))\n nextUsers.forEach((user) => merged.set(user.id, user))\n return Array.from(merged.values())\n })\n setTotalUsers(result.total)\n setHasMore(hasMoreFromPage(result.servedCount, ASSIGNABLE_STAFF_PAGE_SIZE))\n setCurrentPage(result.page)\n setLoadError(null)\n } catch {\n if (requestSequenceRef.current !== requestId) return\n if (!append) {\n setUsers([])\n setTotalUsers(0)\n setHasMore(false)\n setCurrentPage(1)\n }\n setLoadError(\n t(\n 'customers.assignableStaff.loadError',\n 'Unable to load team members. Check your permissions and try again.',\n ),\n )\n } finally {\n if (requestSequenceRef.current !== requestId) return\n if (append) {\n setLoadingMore(false)\n } else {\n setLoading(false)\n }\n }\n },\n [activeOrgId, t],\n )\n\n React.useEffect(() => {\n if (step >= 2) {\n // fire-and-forget: search results populate async; errors shown in list UI\n searchUsers({ query: deferredSearchQuery, page: 1, append: false }).catch(() => {})\n }\n }, [deferredSearchQuery, searchUsers, step])\n\n const handleLoadMore = React.useCallback(() => {\n if (loading || loadingMore || !hasMore) return\n // fire-and-forget: search results populate async; errors shown in list UI\n searchUsers({\n query: deferredSearchQuery,\n page: currentPage + 1,\n append: true,\n }).catch(() => {})\n }, [currentPage, deferredSearchQuery, hasMore, loading, loadingMore, searchUsers])\n\n const selectedRole = React.useMemo(\n () => roleTypes.find((roleType) => roleType.value === selectedRoleType) ?? null,\n [roleTypes, selectedRoleType],\n )\n\n const roleTypeLabelMap = React.useMemo(\n () => new Map(roleTypes.map((roleType) => [roleType.value, roleType.label])),\n [roleTypes],\n )\n\n const conflictsByUserId = React.useMemo(() => {\n const next = new Map<string, string[]>()\n existingAssignments.forEach((assignment) => {\n if (!assignment.userId) return\n if (assignment.roleType === selectedRoleType) return\n const label = roleTypeLabelMap.get(assignment.roleType) ?? assignment.roleType\n const current = next.get(assignment.userId) ?? []\n if (!current.includes(label)) {\n current.push(label)\n next.set(assignment.userId, current)\n }\n })\n return next\n }, [existingAssignments, roleTypeLabelMap, selectedRoleType])\n\n const selectedUserConflict = React.useMemo(() => {\n if (!selectedUser) return null\n return conflictsByUserId.get(selectedUser.id) ?? null\n }, [conflictsByUserId, selectedUser])\n\n const teamFilters = React.useMemo<TeamFilter[]>(() => {\n const counts = new Map<string, number>()\n users.forEach((user) => {\n const key =\n user.teamName?.trim() || t('customers.roles.dialog.team.unassigned', 'No team')\n counts.set(key, (counts.get(key) ?? 0) + 1)\n })\n return [\n { id: 'all', label: t('customers.roles.dialog.team.all', 'All'), count: users.length },\n ...Array.from(counts.entries()).map(([label, count]) => ({ id: label, label, count })),\n ]\n }, [t, users])\n\n const filteredUsers = React.useMemo(() => {\n if (activeTeam === 'all') return users\n return users.filter(\n (user) =>\n (user.teamName?.trim() || t('customers.roles.dialog.team.unassigned', 'No team')) ===\n activeTeam,\n )\n }, [activeTeam, t, users])\n\n const visibleCountLabel = React.useMemo(() => {\n if (totalUsers <= 0) return null\n return t(\n 'customers.roles.dialog.visibleCount',\n 'Showing {{shown}} of {{total}} team members',\n {\n shown: String(users.length),\n total: String(totalUsers),\n },\n )\n }, [t, totalUsers, users.length])\n\n const canLoadMore = hasMore\n\n const handleAssign = React.useCallback(async () => {\n if (!selectedRoleType || !selectedUser) return\n setSaving(true)\n try {\n await onAssign(selectedRoleType, selectedUser.id)\n onClose()\n } finally {\n setSaving(false)\n }\n }, [onAssign, onClose, selectedRoleType, selectedUser])\n\n const handleKeyDown = React.useCallback(\n (e: React.KeyboardEvent) => {\n if (e.key === 'Enter' && (e.metaKey || e.ctrlKey)) {\n e.preventDefault()\n if (step === 3 && !saving && selectedUser && selectedRoleType) {\n handleAssign()\n } else if (step === 1 && selectedRoleType && availableRoleTypes.length) {\n setStep(2)\n } else if (step === 2 && selectedUser) {\n setStep(3)\n }\n }\n },\n [availableRoleTypes.length, handleAssign, saving, selectedRoleType, selectedUser, step],\n )\n\n const previewCard =\n selectedUser && selectedRole ? (\n <div className=\"rounded-lg border border-border/70 bg-muted/30 px-4 py-4\">\n <p className=\"text-overline font-semibold uppercase tracking-[0.12em] text-muted-foreground\">\n {t('customers.roles.dialog.preview', 'Assignment preview')}\n </p>\n <div className=\"mt-3 flex items-center gap-3\">\n <Avatar\n label={selectedUser.displayName}\n size=\"lg\"\n className=\"bg-background text-foreground\"\n />\n <div className=\"min-w-0\">\n <div className=\"flex flex-wrap items-center gap-2 text-sm font-semibold text-foreground\">\n <span>{selectedUser.displayName}</span>\n <span className=\"text-muted-foreground\">\u2192</span>\n <span>{selectedRole.label}</span>\n </div>\n <div className=\"mt-1 flex flex-wrap items-center gap-3 text-xs text-muted-foreground\">\n {selectedUser.email ? <span>{selectedUser.email}</span> : null}\n {selectedUser.teamName ? <span>{selectedUser.teamName}</span> : null}\n </div>\n {selectedUserConflict?.length ? (\n <div className=\"mt-2 flex flex-wrap items-center gap-2\">\n <Badge\n variant=\"outline\"\n className=\"rounded-full border-status-error-border bg-status-error-bg px-2 py-0.5 text-xs font-semibold text-status-error-text\"\n >\n {t('customers.roles.dialog.conflict', 'Conflict: {{roles}}', {\n roles: selectedUserConflict.join(', '),\n })}\n </Badge>\n </div>\n ) : null}\n </div>\n </div>\n </div>\n ) : null\n\n return (\n <Dialog\n open={open}\n onOpenChange={(nextOpen) => {\n if (!nextOpen) onClose()\n }}\n >\n <DialogContent\n className=\"min-h-0 max-h-[min(90vh,760px)] overflow-hidden p-0 sm:max-w-[580px]\"\n onKeyDown={handleKeyDown}\n >\n <DialogHeader className=\"border-b border-border/70 px-6 py-5\">\n <DialogTitle className=\"text-2xl font-semibold leading-none\">\n {t('customers.roles.dialog.title', 'Assign role')}\n </DialogTitle>\n <p className=\"mt-2 text-sm text-muted-foreground\">\n {t('customers.roles.dialog.subtitle', 'Multi-role assignment for {{name}}', {\n name: entityName,\n })}\n </p>\n </DialogHeader>\n\n <div className=\"border-b border-border/70 px-6 py-4\">\n <StepIndicator\n className=\"justify-center\"\n steps={[\n {\n id: '1',\n label: t('customers.roles.dialog.step1', 'Role type'),\n status: step > 1 ? 'complete' : step === 1 ? 'current' : 'pending',\n },\n {\n id: '2',\n label: t('customers.roles.dialog.step2', 'Select person'),\n status: step > 2 ? 'complete' : step === 2 ? 'current' : 'pending',\n },\n {\n id: '3',\n label: t('customers.roles.dialog.step3', 'Confirm'),\n status: step === 3 ? 'current' : 'pending',\n },\n ] satisfies StepIndicatorStep[]}\n />\n </div>\n\n <div className=\"min-h-0 flex-1 overflow-y-auto\">\n <div className=\"space-y-5 px-6 py-5\">\n {step === 1 ? (\n <div className=\"space-y-4\">\n <div className=\"rounded-lg bg-muted/30 px-4 py-4\">\n <p\n id=\"assign-role-dialog-type-label\"\n className=\"text-overline font-semibold uppercase tracking-[0.12em] text-muted-foreground\"\n >\n {t('customers.roles.dialog.roleTypeLabel', 'Role type')}\n </p>\n <div className=\"mt-2 flex items-center gap-2\">\n <select\n aria-labelledby=\"assign-role-dialog-type-label\"\n value={selectedRoleType}\n onChange={(event) => setSelectedRoleType(event.target.value)}\n className=\"h-10 w-full rounded-md border border-border bg-background px-3 text-sm focus:outline-none focus:ring-2 focus:ring-ring/30\"\n >\n <option value=\"\">\n {t('customers.roles.selectRoleType', 'Select role type...')}\n </option>\n {availableRoleTypes.map((roleType) => (\n <option key={roleType.id} value={roleType.value}>\n {roleType.label}\n </option>\n ))}\n </select>\n {selectedRole ? (\n <Badge\n variant=\"outline\"\n className=\"rounded-md px-2 py-1 text-xs\"\n >\n {t('customers.roles.dialog.sourceBadge.dictionary', 'Dictionary')}\n </Badge>\n ) : null}\n </div>\n {canManageRoleTypes ? (\n <Link\n href={MANAGE_ROLE_TYPES_HREF}\n className=\"mt-3 inline-flex items-center gap-1.5 text-xs font-medium text-primary hover:underline\"\n data-testid=\"assign-role-dialog-manage-role-types\"\n >\n <Settings2 className=\"size-3.5\" aria-hidden=\"true\" />\n {t('customers.roles.dialog.manageRoleTypes', 'Manage role types')}\n </Link>\n ) : null}\n </div>\n\n {!availableRoleTypes.length ? (\n <EmptyState\n size=\"sm\"\n icon={<CheckCheck className=\"h-8 w-8\" aria-hidden=\"true\" />}\n title={t(\n 'customers.roles.dialog.noAvailableRoles',\n 'All available role types are already assigned.',\n )}\n />\n ) : null}\n </div>\n ) : null}\n\n {step === 2 ? (\n <div className=\"space-y-4\">\n <div className=\"rounded-lg bg-muted/30 px-4 py-4\">\n <div className=\"flex items-center justify-between gap-3\">\n <div>\n <p className=\"text-overline font-semibold uppercase tracking-[0.12em] text-muted-foreground\">\n {t('customers.roles.dialog.roleTypeLabel', 'Role type')}\n </p>\n <div className=\"mt-1 flex items-center gap-2\">\n <span className=\"text-lg font-semibold text-foreground\">\n {selectedRole?.label ?? selectedRoleType}\n </span>\n <Badge\n variant=\"outline\"\n className=\"rounded-md px-2 py-1 text-xs\"\n >\n {t('customers.roles.dialog.sourceBadge.dictionary', 'Dictionary')}\n </Badge>\n </div>\n </div>\n <div className=\"flex items-center gap-2\">\n {canManageRoleTypes ? (\n <Button asChild variant=\"ghost\" size=\"sm\">\n <Link\n href={MANAGE_ROLE_TYPES_HREF}\n data-testid=\"assign-role-dialog-manage-role-types-step2\"\n >\n <Settings2 className=\"mr-1 size-3.5\" aria-hidden=\"true\" />\n {t('customers.roles.dialog.manageRoleTypes', 'Manage role types')}\n </Link>\n </Button>\n ) : null}\n <Button\n type=\"button\"\n variant=\"outline\"\n size=\"sm\"\n onClick={() => setStep(1)}\n >\n {t('customers.roles.dialog.change', 'Change')}\n </Button>\n </div>\n </div>\n </div>\n\n <div className=\"space-y-2\">\n <p className=\"text-overline font-semibold uppercase tracking-[0.12em] text-muted-foreground\">\n {t('customers.roles.dialog.teamLabel', 'Select a team member')}\n </p>\n <div className=\"relative\">\n <Search className=\"absolute left-3 top-1/2 size-4 -translate-y-1/2 text-muted-foreground\" />\n <Input\n value={searchQuery}\n onChange={(event) => setSearchQuery(event.target.value)}\n placeholder={t(\n 'customers.roles.dialog.searchPlaceholder',\n 'Search by name, e-mail or team...',\n )}\n className=\"h-10 rounded-md border-border/80 pl-9 shadow-none\"\n />\n </div>\n </div>\n\n <div className=\"flex flex-wrap items-center gap-2\">\n {teamFilters.map((teamFilter) => {\n const isActive = activeTeam === teamFilter.id\n return (\n <Button\n key={teamFilter.id}\n type=\"button\"\n variant={isActive ? 'default' : 'outline'}\n size=\"sm\"\n onClick={() => setActiveTeam(teamFilter.id)}\n className=\"h-7 rounded-md px-2.5 text-xs\"\n >\n {teamFilter.label}\n <span className=\"rounded-full bg-background/80 px-1 text-xs text-muted-foreground\">\n {teamFilter.count}\n </span>\n </Button>\n )\n })}\n </div>\n\n <div className=\"space-y-2\">\n {visibleCountLabel ? (\n <p className=\"text-xs text-muted-foreground\">{visibleCountLabel}</p>\n ) : null}\n {loading ? (\n <div className=\"rounded-lg border border-dashed border-border/80 px-4 py-8 text-center text-sm text-muted-foreground\">\n {t('customers.roles.loading', 'Loading...')}\n </div>\n ) : loadError ? (\n <div className=\"rounded-lg border border-dashed border-status-error-border bg-status-error-bg/70 px-4 py-8 text-center text-sm text-status-error-text\">\n {loadError}\n </div>\n ) : filteredUsers.length === 0 ? (\n <div className=\"rounded-lg border border-dashed border-border/80 px-4 py-8 text-center text-sm text-muted-foreground\">\n {t(\n 'customers.roles.dialog.noResults',\n 'No matching team members found.',\n )}\n </div>\n ) : (\n filteredUsers.map((user) => {\n const isSelected = selectedUser?.id === user.id\n const userConflicts = conflictsByUserId.get(user.id) ?? []\n return (\n <Button\n key={user.id}\n type=\"button\"\n variant=\"ghost\"\n onClick={() => setSelectedUser(user)}\n className={`h-auto flex w-full items-center gap-3 rounded-lg border px-4 py-3 text-left transition-colors ${\n isSelected\n ? 'border-foreground bg-background shadow-sm'\n : 'border-border/70 bg-background hover:bg-accent/40'\n }`}\n >\n <Avatar label={user.displayName} size=\"lg\" variant=\"monochrome\" />\n <div className=\"min-w-0 flex-1\">\n <div className=\"flex flex-wrap items-center gap-2\">\n <span className=\"text-sm font-semibold text-foreground\">\n {user.displayName}\n </span>\n {user.teamName ? (\n <Badge\n variant=\"muted\"\n className=\"rounded-md px-2 py-0.5 text-xs font-medium\"\n >\n {user.teamName}\n </Badge>\n ) : null}\n {userConflicts.length ? (\n <Badge\n variant=\"outline\"\n className=\"rounded-full border-status-error-border bg-status-error-bg px-2 py-0.5 text-xs font-semibold text-status-error-text\"\n >\n {t('customers.roles.dialog.conflict', 'Conflict: {{roles}}', {\n roles: userConflicts.join(', '),\n })}\n </Badge>\n ) : null}\n </div>\n {user.email ? (\n <div className=\"mt-1 text-xs text-muted-foreground\">\n {user.email}\n </div>\n ) : null}\n </div>\n <span\n className={`flex size-6 shrink-0 items-center justify-center rounded-full border ${\n isSelected\n ? 'border-foreground bg-foreground text-background'\n : 'border-border/80 bg-background text-transparent'\n }`}\n >\n <Check className=\"size-3.5\" />\n </span>\n </Button>\n )\n })\n )}\n {canLoadMore && !loadError ? (\n <Button\n type=\"button\"\n variant=\"outline\"\n onClick={handleLoadMore}\n disabled={loadingMore}\n className=\"w-full\"\n >\n {loadingMore\n ? t('customers.roles.dialog.loadingMore', 'Loading more...')\n : t('customers.roles.dialog.loadMore', 'Load more')}\n </Button>\n ) : null}\n </div>\n\n {previewCard}\n </div>\n ) : null}\n\n {step === 3 ? (\n <div className=\"space-y-4\">\n {previewCard}\n <p className=\"text-sm text-muted-foreground\">\n {t(\n 'customers.roles.dialog.constraint',\n 'One person per role. The assignment can be changed at any time.',\n )}\n </p>\n </div>\n ) : null}\n </div>\n </div>\n\n <DialogFooter\n bordered={false}\n className=\"shrink-0 border-t border-border/70 px-6 py-4\"\n leading={\n <p className=\"text-xs text-muted-foreground sm:max-w-2xs\">\n {t(\n 'customers.roles.dialog.footerNote',\n 'One person per role \u00B7 can be changed at any time',\n )}\n </p>\n }\n >\n <div className=\"flex items-center gap-2\">\n <Button type=\"button\" variant=\"outline\" onClick={onClose}>\n {t('customers.roles.cancelAdd', 'Cancel')}\n </Button>\n {step === 1 ? (\n <Button\n type=\"button\"\n onClick={() => setStep(2)}\n disabled={!selectedRoleType || !availableRoleTypes.length}\n >\n {t('customers.roles.dialog.next', 'Next')}\n </Button>\n ) : null}\n {step === 2 ? (\n <Button type=\"button\" onClick={() => setStep(3)} disabled={!selectedUser}>\n {t('customers.roles.dialog.next', 'Next')}\n </Button>\n ) : null}\n {step === 3 ? (\n <Button\n type=\"button\"\n onClick={handleAssign}\n disabled={saving || !selectedUser || !selectedRoleType}\n >\n {saving\n ? t('customers.roles.assigning', 'Assigning...')\n : t('customers.roles.dialog.assign', 'Assign role')}\n </Button>\n ) : null}\n </div>\n </DialogFooter>\n </DialogContent>\n </Dialog>\n )\n}\n\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
|
@@ -11,6 +11,7 @@ import { Avatar } from "@open-mercato/ui/primitives/avatar";
|
|
|
11
11
|
import { Button } from "@open-mercato/ui/primitives/button";
|
|
12
12
|
import { Badge } from "@open-mercato/ui/primitives/badge";
|
|
13
13
|
import { useConfirmDialog } from "@open-mercato/ui/backend/confirm-dialog";
|
|
14
|
+
import { useCurrentOrganization } from "@open-mercato/ui/backend/BackendChromeProvider";
|
|
14
15
|
import { fetchAssignableStaffMembers } from "./assignableStaff.js";
|
|
15
16
|
import { createLogger } from "@open-mercato/shared/lib/logger";
|
|
16
17
|
const logger = createLogger("customers");
|
|
@@ -34,12 +35,13 @@ function RoleAssignmentRow({
|
|
|
34
35
|
onUpdated
|
|
35
36
|
}) {
|
|
36
37
|
const t = useT();
|
|
38
|
+
const activeOrgId = useCurrentOrganization()?.id ?? null;
|
|
37
39
|
const { confirm, ConfirmDialogElement } = useConfirmDialog();
|
|
38
40
|
const [removing, setRemoving] = React.useState(false);
|
|
39
41
|
const [changingUser, setChangingUser] = React.useState(false);
|
|
40
42
|
const searchUsers = React.useCallback(async (query) => {
|
|
41
43
|
try {
|
|
42
|
-
const members = await fetchAssignableStaffMembers(query, { pageSize: 20 });
|
|
44
|
+
const members = await fetchAssignableStaffMembers(query, { pageSize: 20, activeOrgId });
|
|
43
45
|
return members.map((member) => ({
|
|
44
46
|
id: member.userId,
|
|
45
47
|
title: member.displayName,
|
|
@@ -49,7 +51,7 @@ function RoleAssignmentRow({
|
|
|
49
51
|
logger.error("customers.roles.searchUsers failed", { err: error });
|
|
50
52
|
return [];
|
|
51
53
|
}
|
|
52
|
-
}, []);
|
|
54
|
+
}, [activeOrgId]);
|
|
53
55
|
const handleUserChange = React.useCallback(async (userId) => {
|
|
54
56
|
if (!userId || userId === role.userId) return;
|
|
55
57
|
const basePath = entityType === "company" ? "companies" : "people";
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../../../../src/modules/customers/components/detail/RoleAssignmentRow.tsx"],
|
|
4
|
-
"sourcesContent": ["'use client'\n\nimport * as React from 'react'\nimport { Mail, Phone, X } from 'lucide-react'\nimport { useT } from '@open-mercato/shared/lib/i18n/context'\nimport { apiCallOrThrow } from '@open-mercato/ui/backend/utils/apiCall'\nimport { flash } from '@open-mercato/ui/backend/FlashMessages'\nimport { LookupSelect, type LookupSelectItem } from '@open-mercato/ui/backend/inputs/LookupSelect'\nimport { IconButton } from '@open-mercato/ui/primitives/icon-button'\nimport { Avatar } from '@open-mercato/ui/primitives/avatar'\nimport { Button } from '@open-mercato/ui/primitives/button'\nimport { Badge } from '@open-mercato/ui/primitives/badge'\nimport { useConfirmDialog } from '@open-mercato/ui/backend/confirm-dialog'\nimport { fetchAssignableStaffMembers } from './assignableStaff'\nimport { createLogger } from '@open-mercato/shared/lib/logger'\n\nconst logger = createLogger('customers')\n\nexport interface RoleAssignment {\n id: string\n roleType: string\n userId: string\n userName?: string | null\n userEmail?: string | null\n userPhone?: string | null\n createdAt?: string\n updatedAt?: string\n}\n\ninterface RoleAssignmentRowProps {\n role: RoleAssignment\n roleTypeLabel: string\n runMutationWithContext: <T,>(\n operation: () => Promise<T>,\n mutationPayload?: Record<string, unknown>,\n ) => Promise<T>\n entityType: 'company' | 'person'\n entityId: string\n onRemoved: () => void\n onUpdated: () => void\n}\n\nfunction formatAssignedAt(value: string | undefined, t: ReturnType<typeof useT>): string | null {\n if (!value) return null\n const date = new Date(value)\n if (Number.isNaN(date.getTime())) return null\n const diffMs = Date.now() - date.getTime()\n const diffDays = Math.floor(diffMs / 86_400_000)\n if (diffDays <= 0) return t('customers.roles.assignedToday', 'Assigned today')\n if (diffDays === 1) return t('customers.roles.assignedYesterday', 'Assigned yesterday')\n return t('customers.roles.assignedDaysAgo', 'Assigned {{count}} days ago', { count: diffDays })\n}\n\nexport function RoleAssignmentRow({\n role,\n roleTypeLabel,\n runMutationWithContext,\n entityType,\n entityId,\n onRemoved,\n onUpdated,\n}: RoleAssignmentRowProps) {\n const t = useT()\n const { confirm, ConfirmDialogElement } = useConfirmDialog()\n const [removing, setRemoving] = React.useState(false)\n const [changingUser, setChangingUser] = React.useState(false)\n\n const searchUsers = React.useCallback(async (query: string): Promise<LookupSelectItem[]> => {\n try {\n const members = await fetchAssignableStaffMembers(query, { pageSize: 20 })\n return members.map((member) => ({\n id: member.userId,\n title: member.displayName,\n subtitle: member.displayName && member.email ? member.email : null,\n }))\n } catch (error) {\n logger.error('customers.roles.searchUsers failed', { err: error })\n return []\n }\n }, [])\n\n const handleUserChange = React.useCallback(async (userId: string | null) => {\n if (!userId || userId === role.userId) return\n const basePath = entityType === 'company' ? 'companies' : 'people'\n try {\n await runMutationWithContext(\n () =>\n // optimistic-lock-exempt: role assignment add/remove\n apiCallOrThrow(`/api/customers/${basePath}/${entityId}/roles?roleId=${role.id}`, {\n method: 'PUT',\n headers: { 'content-type': 'application/json' },\n body: JSON.stringify({ userId }),\n }),\n { roleId: role.id, userId },\n )\n setChangingUser(false)\n onUpdated()\n } catch (error) {\n logger.error('customers.roles.update failed', { err: error })\n flash(t('customers.roles.updateFailed', 'Failed to update role assignment.'), 'error')\n setChangingUser(false)\n }\n }, [entityId, entityType, onUpdated, role.id, role.userId, runMutationWithContext, t])\n\n const handleRemove = React.useCallback(async () => {\n const confirmed = await confirm({\n title: t('customers.roles.removeConfirm', 'Remove this role assignment?'),\n variant: 'default',\n })\n if (!confirmed) return\n\n setRemoving(true)\n const basePath = entityType === 'company' ? 'companies' : 'people'\n try {\n await runMutationWithContext(\n () =>\n // optimistic-lock-exempt: role assignment add/remove\n apiCallOrThrow(`/api/customers/${basePath}/${entityId}/roles?roleId=${role.id}`, {\n method: 'DELETE',\n }),\n { roleId: role.id },\n )\n onRemoved()\n } catch (error) {\n logger.error('customers.roles.remove failed', { err: error })\n flash(t('customers.roles.removeFailed', 'Failed to remove role assignment.'), 'error')\n } finally {\n setRemoving(false)\n }\n }, [confirm, entityId, entityType, onRemoved, role.id, runMutationWithContext, t])\n\n const currentUserOptions = React.useMemo<LookupSelectItem[]>(\n () =>\n role.userId\n ? [{\n id: role.userId,\n title: role.userName ?? role.userEmail ?? role.userId,\n subtitle: role.userName && role.userEmail ? role.userEmail : null,\n }]\n : [],\n [role.userEmail, role.userId, role.userName],\n )\n\n const userLabel = React.useMemo(() => {\n return role.userName ?? role.userEmail ?? '?'\n }, [role.userEmail, role.userName])\n\n const displayName = role.userName ?? role.userEmail ?? role.userId\n const assignedLabel = formatAssignedAt(role.createdAt, t)\n\n return (\n <>\n {ConfirmDialogElement}\n <div className=\"flex h-full min-w-0 flex-col overflow-hidden rounded-xl border bg-card p-4 shadow-sm\">\n <div className=\"flex flex-wrap items-start justify-between gap-3\">\n <Badge variant=\"outline\" className=\"max-w-full break-words rounded-full px-2 py-0 text-left text-xs font-semibold\">\n {roleTypeLabel}\n </Badge>\n <IconButton\n type=\"button\"\n variant=\"ghost\"\n size=\"sm\"\n onClick={handleRemove}\n disabled={removing}\n aria-label={t('customers.roles.remove', 'Remove role')}\n >\n <X className=\"size-4\" />\n </IconButton>\n </div>\n\n <div className=\"mt-4 flex min-w-0 flex-col gap-3 sm:flex-row sm:items-start\">\n <Avatar label={userLabel} size=\"lg\" variant=\"monochrome\" />\n <div className=\"min-w-0 flex-1\">\n <div className=\"break-all text-sm font-semibold leading-5 text-foreground\">{displayName}</div>\n {role.userEmail ? (\n <div className=\"mt-1 break-all text-xs text-muted-foreground\">{role.userEmail}</div>\n ) : null}\n {assignedLabel ? (\n <div className=\"mt-2 text-overline uppercase tracking-[0.08em] text-muted-foreground\">\n {assignedLabel}\n </div>\n ) : null}\n </div>\n </div>\n\n <div className=\"mt-4 flex flex-wrap items-center gap-2\">\n {role.userEmail ? (\n <IconButton asChild variant=\"outline\" size=\"sm\" className=\"shrink-0\">\n <a href={`mailto:${role.userEmail}`} aria-label={t('customers.roles.email', 'Send email')}>\n <Mail className=\"size-4\" />\n </a>\n </IconButton>\n ) : (\n <IconButton\n type=\"button\"\n variant=\"outline\"\n size=\"sm\"\n className=\"shrink-0\"\n disabled\n aria-label={t('customers.roles.emailUnavailable', 'Email unavailable')}\n >\n <Mail className=\"size-4\" />\n </IconButton>\n )}\n {role.userPhone ? (\n <IconButton asChild variant=\"outline\" size=\"sm\" className=\"shrink-0\">\n <a href={`tel:${role.userPhone}`} aria-label={t('customers.roles.call', 'Call')}>\n <Phone className=\"size-4\" />\n </a>\n </IconButton>\n ) : (\n <IconButton\n type=\"button\"\n variant=\"outline\"\n size=\"sm\"\n className=\"shrink-0\"\n disabled\n aria-label={t('customers.roles.phoneUnavailable', 'Phone unavailable')}\n >\n <Phone className=\"size-4\" />\n </IconButton>\n )}\n <Button\n type=\"button\"\n variant=\"ghost\"\n size=\"sm\"\n className=\"h-auto w-full justify-start px-2 py-1 text-xs sm:ml-auto sm:w-auto\"\n onClick={() => setChangingUser((current) => !current)}\n >\n {t('customers.roles.changeUser', 'Change user')}\n </Button>\n </div>\n\n {changingUser ? (\n <div className=\"mt-4 border-t pt-4\">\n <LookupSelect\n value={role.userId}\n onChange={async (userId) => {\n await handleUserChange(userId)\n }}\n fetchItems={searchUsers}\n options={currentUserOptions}\n placeholder={t('customers.roles.searchPlaceholder', 'Search team member...')}\n />\n </div>\n ) : null}\n </div>\n </>\n )\n}\n"],
|
|
5
|
-
"mappings": ";
|
|
4
|
+
"sourcesContent": ["'use client'\n\nimport * as React from 'react'\nimport { Mail, Phone, X } from 'lucide-react'\nimport { useT } from '@open-mercato/shared/lib/i18n/context'\nimport { apiCallOrThrow } from '@open-mercato/ui/backend/utils/apiCall'\nimport { flash } from '@open-mercato/ui/backend/FlashMessages'\nimport { LookupSelect, type LookupSelectItem } from '@open-mercato/ui/backend/inputs/LookupSelect'\nimport { IconButton } from '@open-mercato/ui/primitives/icon-button'\nimport { Avatar } from '@open-mercato/ui/primitives/avatar'\nimport { Button } from '@open-mercato/ui/primitives/button'\nimport { Badge } from '@open-mercato/ui/primitives/badge'\nimport { useConfirmDialog } from '@open-mercato/ui/backend/confirm-dialog'\nimport { useCurrentOrganization } from '@open-mercato/ui/backend/BackendChromeProvider'\nimport { fetchAssignableStaffMembers } from './assignableStaff'\nimport { createLogger } from '@open-mercato/shared/lib/logger'\n\nconst logger = createLogger('customers')\n\nexport interface RoleAssignment {\n id: string\n roleType: string\n userId: string\n userName?: string | null\n userEmail?: string | null\n userPhone?: string | null\n createdAt?: string\n updatedAt?: string\n}\n\ninterface RoleAssignmentRowProps {\n role: RoleAssignment\n roleTypeLabel: string\n runMutationWithContext: <T,>(\n operation: () => Promise<T>,\n mutationPayload?: Record<string, unknown>,\n ) => Promise<T>\n entityType: 'company' | 'person'\n entityId: string\n onRemoved: () => void\n onUpdated: () => void\n}\n\nfunction formatAssignedAt(value: string | undefined, t: ReturnType<typeof useT>): string | null {\n if (!value) return null\n const date = new Date(value)\n if (Number.isNaN(date.getTime())) return null\n const diffMs = Date.now() - date.getTime()\n const diffDays = Math.floor(diffMs / 86_400_000)\n if (diffDays <= 0) return t('customers.roles.assignedToday', 'Assigned today')\n if (diffDays === 1) return t('customers.roles.assignedYesterday', 'Assigned yesterday')\n return t('customers.roles.assignedDaysAgo', 'Assigned {{count}} days ago', { count: diffDays })\n}\n\nexport function RoleAssignmentRow({\n role,\n roleTypeLabel,\n runMutationWithContext,\n entityType,\n entityId,\n onRemoved,\n onUpdated,\n}: RoleAssignmentRowProps) {\n const t = useT()\n const activeOrgId = useCurrentOrganization()?.id ?? null\n const { confirm, ConfirmDialogElement } = useConfirmDialog()\n const [removing, setRemoving] = React.useState(false)\n const [changingUser, setChangingUser] = React.useState(false)\n\n const searchUsers = React.useCallback(async (query: string): Promise<LookupSelectItem[]> => {\n try {\n const members = await fetchAssignableStaffMembers(query, { pageSize: 20, activeOrgId })\n return members.map((member) => ({\n id: member.userId,\n title: member.displayName,\n subtitle: member.displayName && member.email ? member.email : null,\n }))\n } catch (error) {\n logger.error('customers.roles.searchUsers failed', { err: error })\n return []\n }\n }, [activeOrgId])\n\n const handleUserChange = React.useCallback(async (userId: string | null) => {\n if (!userId || userId === role.userId) return\n const basePath = entityType === 'company' ? 'companies' : 'people'\n try {\n await runMutationWithContext(\n () =>\n // optimistic-lock-exempt: role assignment add/remove\n apiCallOrThrow(`/api/customers/${basePath}/${entityId}/roles?roleId=${role.id}`, {\n method: 'PUT',\n headers: { 'content-type': 'application/json' },\n body: JSON.stringify({ userId }),\n }),\n { roleId: role.id, userId },\n )\n setChangingUser(false)\n onUpdated()\n } catch (error) {\n logger.error('customers.roles.update failed', { err: error })\n flash(t('customers.roles.updateFailed', 'Failed to update role assignment.'), 'error')\n setChangingUser(false)\n }\n }, [entityId, entityType, onUpdated, role.id, role.userId, runMutationWithContext, t])\n\n const handleRemove = React.useCallback(async () => {\n const confirmed = await confirm({\n title: t('customers.roles.removeConfirm', 'Remove this role assignment?'),\n variant: 'default',\n })\n if (!confirmed) return\n\n setRemoving(true)\n const basePath = entityType === 'company' ? 'companies' : 'people'\n try {\n await runMutationWithContext(\n () =>\n // optimistic-lock-exempt: role assignment add/remove\n apiCallOrThrow(`/api/customers/${basePath}/${entityId}/roles?roleId=${role.id}`, {\n method: 'DELETE',\n }),\n { roleId: role.id },\n )\n onRemoved()\n } catch (error) {\n logger.error('customers.roles.remove failed', { err: error })\n flash(t('customers.roles.removeFailed', 'Failed to remove role assignment.'), 'error')\n } finally {\n setRemoving(false)\n }\n }, [confirm, entityId, entityType, onRemoved, role.id, runMutationWithContext, t])\n\n const currentUserOptions = React.useMemo<LookupSelectItem[]>(\n () =>\n role.userId\n ? [{\n id: role.userId,\n title: role.userName ?? role.userEmail ?? role.userId,\n subtitle: role.userName && role.userEmail ? role.userEmail : null,\n }]\n : [],\n [role.userEmail, role.userId, role.userName],\n )\n\n const userLabel = React.useMemo(() => {\n return role.userName ?? role.userEmail ?? '?'\n }, [role.userEmail, role.userName])\n\n const displayName = role.userName ?? role.userEmail ?? role.userId\n const assignedLabel = formatAssignedAt(role.createdAt, t)\n\n return (\n <>\n {ConfirmDialogElement}\n <div className=\"flex h-full min-w-0 flex-col overflow-hidden rounded-xl border bg-card p-4 shadow-sm\">\n <div className=\"flex flex-wrap items-start justify-between gap-3\">\n <Badge variant=\"outline\" className=\"max-w-full break-words rounded-full px-2 py-0 text-left text-xs font-semibold\">\n {roleTypeLabel}\n </Badge>\n <IconButton\n type=\"button\"\n variant=\"ghost\"\n size=\"sm\"\n onClick={handleRemove}\n disabled={removing}\n aria-label={t('customers.roles.remove', 'Remove role')}\n >\n <X className=\"size-4\" />\n </IconButton>\n </div>\n\n <div className=\"mt-4 flex min-w-0 flex-col gap-3 sm:flex-row sm:items-start\">\n <Avatar label={userLabel} size=\"lg\" variant=\"monochrome\" />\n <div className=\"min-w-0 flex-1\">\n <div className=\"break-all text-sm font-semibold leading-5 text-foreground\">{displayName}</div>\n {role.userEmail ? (\n <div className=\"mt-1 break-all text-xs text-muted-foreground\">{role.userEmail}</div>\n ) : null}\n {assignedLabel ? (\n <div className=\"mt-2 text-overline uppercase tracking-[0.08em] text-muted-foreground\">\n {assignedLabel}\n </div>\n ) : null}\n </div>\n </div>\n\n <div className=\"mt-4 flex flex-wrap items-center gap-2\">\n {role.userEmail ? (\n <IconButton asChild variant=\"outline\" size=\"sm\" className=\"shrink-0\">\n <a href={`mailto:${role.userEmail}`} aria-label={t('customers.roles.email', 'Send email')}>\n <Mail className=\"size-4\" />\n </a>\n </IconButton>\n ) : (\n <IconButton\n type=\"button\"\n variant=\"outline\"\n size=\"sm\"\n className=\"shrink-0\"\n disabled\n aria-label={t('customers.roles.emailUnavailable', 'Email unavailable')}\n >\n <Mail className=\"size-4\" />\n </IconButton>\n )}\n {role.userPhone ? (\n <IconButton asChild variant=\"outline\" size=\"sm\" className=\"shrink-0\">\n <a href={`tel:${role.userPhone}`} aria-label={t('customers.roles.call', 'Call')}>\n <Phone className=\"size-4\" />\n </a>\n </IconButton>\n ) : (\n <IconButton\n type=\"button\"\n variant=\"outline\"\n size=\"sm\"\n className=\"shrink-0\"\n disabled\n aria-label={t('customers.roles.phoneUnavailable', 'Phone unavailable')}\n >\n <Phone className=\"size-4\" />\n </IconButton>\n )}\n <Button\n type=\"button\"\n variant=\"ghost\"\n size=\"sm\"\n className=\"h-auto w-full justify-start px-2 py-1 text-xs sm:ml-auto sm:w-auto\"\n onClick={() => setChangingUser((current) => !current)}\n >\n {t('customers.roles.changeUser', 'Change user')}\n </Button>\n </div>\n\n {changingUser ? (\n <div className=\"mt-4 border-t pt-4\">\n <LookupSelect\n value={role.userId}\n onChange={async (userId) => {\n await handleUserChange(userId)\n }}\n fetchItems={searchUsers}\n options={currentUserOptions}\n placeholder={t('customers.roles.searchPlaceholder', 'Search team member...')}\n />\n </div>\n ) : null}\n </div>\n </>\n )\n}\n"],
|
|
5
|
+
"mappings": ";AAyJI,mBAIM,KADF,YAHJ;AAvJJ,YAAY,WAAW;AACvB,SAAS,MAAM,OAAO,SAAS;AAC/B,SAAS,YAAY;AACrB,SAAS,sBAAsB;AAC/B,SAAS,aAAa;AACtB,SAAS,oBAA2C;AACpD,SAAS,kBAAkB;AAC3B,SAAS,cAAc;AACvB,SAAS,cAAc;AACvB,SAAS,aAAa;AACtB,SAAS,wBAAwB;AACjC,SAAS,8BAA8B;AACvC,SAAS,mCAAmC;AAC5C,SAAS,oBAAoB;AAE7B,MAAM,SAAS,aAAa,WAAW;AA0BvC,SAAS,iBAAiB,OAA2B,GAA2C;AAC9F,MAAI,CAAC,MAAO,QAAO;AACnB,QAAM,OAAO,IAAI,KAAK,KAAK;AAC3B,MAAI,OAAO,MAAM,KAAK,QAAQ,CAAC,EAAG,QAAO;AACzC,QAAM,SAAS,KAAK,IAAI,IAAI,KAAK,QAAQ;AACzC,QAAM,WAAW,KAAK,MAAM,SAAS,KAAU;AAC/C,MAAI,YAAY,EAAG,QAAO,EAAE,iCAAiC,gBAAgB;AAC7E,MAAI,aAAa,EAAG,QAAO,EAAE,qCAAqC,oBAAoB;AACtF,SAAO,EAAE,mCAAmC,+BAA+B,EAAE,OAAO,SAAS,CAAC;AAChG;AAEO,SAAS,kBAAkB;AAAA,EAChC;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF,GAA2B;AACzB,QAAM,IAAI,KAAK;AACf,QAAM,cAAc,uBAAuB,GAAG,MAAM;AACpD,QAAM,EAAE,SAAS,qBAAqB,IAAI,iBAAiB;AAC3D,QAAM,CAAC,UAAU,WAAW,IAAI,MAAM,SAAS,KAAK;AACpD,QAAM,CAAC,cAAc,eAAe,IAAI,MAAM,SAAS,KAAK;AAE5D,QAAM,cAAc,MAAM,YAAY,OAAO,UAA+C;AAC1F,QAAI;AACF,YAAM,UAAU,MAAM,4BAA4B,OAAO,EAAE,UAAU,IAAI,YAAY,CAAC;AACtF,aAAO,QAAQ,IAAI,CAAC,YAAY;AAAA,QAC9B,IAAI,OAAO;AAAA,QACX,OAAO,OAAO;AAAA,QACd,UAAU,OAAO,eAAe,OAAO,QAAQ,OAAO,QAAQ;AAAA,MAChE,EAAE;AAAA,IACJ,SAAS,OAAO;AACd,aAAO,MAAM,sCAAsC,EAAE,KAAK,MAAM,CAAC;AACjE,aAAO,CAAC;AAAA,IACV;AAAA,EACF,GAAG,CAAC,WAAW,CAAC;AAEhB,QAAM,mBAAmB,MAAM,YAAY,OAAO,WAA0B;AAC1E,QAAI,CAAC,UAAU,WAAW,KAAK,OAAQ;AACvC,UAAM,WAAW,eAAe,YAAY,cAAc;AAC1D,QAAI;AACF,YAAM;AAAA,QACJ;AAAA;AAAA,UAEE,eAAe,kBAAkB,QAAQ,IAAI,QAAQ,iBAAiB,KAAK,EAAE,IAAI;AAAA,YAC/E,QAAQ;AAAA,YACR,SAAS,EAAE,gBAAgB,mBAAmB;AAAA,YAC9C,MAAM,KAAK,UAAU,EAAE,OAAO,CAAC;AAAA,UACjC,CAAC;AAAA;AAAA,QACH,EAAE,QAAQ,KAAK,IAAI,OAAO;AAAA,MAC5B;AACA,sBAAgB,KAAK;AACrB,gBAAU;AAAA,IACZ,SAAS,OAAO;AACd,aAAO,MAAM,iCAAiC,EAAE,KAAK,MAAM,CAAC;AAC5D,YAAM,EAAE,gCAAgC,mCAAmC,GAAG,OAAO;AACrF,sBAAgB,KAAK;AAAA,IACvB;AAAA,EACF,GAAG,CAAC,UAAU,YAAY,WAAW,KAAK,IAAI,KAAK,QAAQ,wBAAwB,CAAC,CAAC;AAErF,QAAM,eAAe,MAAM,YAAY,YAAY;AACjD,UAAM,YAAY,MAAM,QAAQ;AAAA,MAC9B,OAAO,EAAE,iCAAiC,8BAA8B;AAAA,MACxE,SAAS;AAAA,IACX,CAAC;AACD,QAAI,CAAC,UAAW;AAEhB,gBAAY,IAAI;AAChB,UAAM,WAAW,eAAe,YAAY,cAAc;AAC1D,QAAI;AACF,YAAM;AAAA,QACJ;AAAA;AAAA,UAEE,eAAe,kBAAkB,QAAQ,IAAI,QAAQ,iBAAiB,KAAK,EAAE,IAAI;AAAA,YAC/E,QAAQ;AAAA,UACV,CAAC;AAAA;AAAA,QACH,EAAE,QAAQ,KAAK,GAAG;AAAA,MACpB;AACA,gBAAU;AAAA,IACZ,SAAS,OAAO;AACd,aAAO,MAAM,iCAAiC,EAAE,KAAK,MAAM,CAAC;AAC5D,YAAM,EAAE,gCAAgC,mCAAmC,GAAG,OAAO;AAAA,IACvF,UAAE;AACA,kBAAY,KAAK;AAAA,IACnB;AAAA,EACF,GAAG,CAAC,SAAS,UAAU,YAAY,WAAW,KAAK,IAAI,wBAAwB,CAAC,CAAC;AAEjF,QAAM,qBAAqB,MAAM;AAAA,IAC/B,MACE,KAAK,SACD,CAAC;AAAA,MACC,IAAI,KAAK;AAAA,MACT,OAAO,KAAK,YAAY,KAAK,aAAa,KAAK;AAAA,MAC/C,UAAU,KAAK,YAAY,KAAK,YAAY,KAAK,YAAY;AAAA,IAC/D,CAAC,IACD,CAAC;AAAA,IACP,CAAC,KAAK,WAAW,KAAK,QAAQ,KAAK,QAAQ;AAAA,EAC7C;AAEA,QAAM,YAAY,MAAM,QAAQ,MAAM;AACpC,WAAO,KAAK,YAAY,KAAK,aAAa;AAAA,EAC5C,GAAG,CAAC,KAAK,WAAW,KAAK,QAAQ,CAAC;AAElC,QAAM,cAAc,KAAK,YAAY,KAAK,aAAa,KAAK;AAC5D,QAAM,gBAAgB,iBAAiB,KAAK,WAAW,CAAC;AAExD,SACE,iCACG;AAAA;AAAA,IACD,qBAAC,SAAI,WAAU,wFACb;AAAA,2BAAC,SAAI,WAAU,oDACb;AAAA,4BAAC,SAAM,SAAQ,WAAU,WAAU,iFAChC,yBACH;AAAA,QACA;AAAA,UAAC;AAAA;AAAA,YACC,MAAK;AAAA,YACL,SAAQ;AAAA,YACR,MAAK;AAAA,YACL,SAAS;AAAA,YACT,UAAU;AAAA,YACV,cAAY,EAAE,0BAA0B,aAAa;AAAA,YAErD,8BAAC,KAAE,WAAU,UAAS;AAAA;AAAA,QACxB;AAAA,SACF;AAAA,MAEA,qBAAC,SAAI,WAAU,+DACb;AAAA,4BAAC,UAAO,OAAO,WAAW,MAAK,MAAK,SAAQ,cAAa;AAAA,QACzD,qBAAC,SAAI,WAAU,kBACb;AAAA,8BAAC,SAAI,WAAU,6DAA6D,uBAAY;AAAA,UACvF,KAAK,YACJ,oBAAC,SAAI,WAAU,gDAAgD,eAAK,WAAU,IAC5E;AAAA,UACH,gBACC,oBAAC,SAAI,WAAU,wEACZ,yBACH,IACE;AAAA,WACN;AAAA,SACF;AAAA,MAEA,qBAAC,SAAI,WAAU,0CACZ;AAAA,aAAK,YACJ,oBAAC,cAAW,SAAO,MAAC,SAAQ,WAAU,MAAK,MAAK,WAAU,YACxD,8BAAC,OAAE,MAAM,UAAU,KAAK,SAAS,IAAI,cAAY,EAAE,yBAAyB,YAAY,GACtF,8BAAC,QAAK,WAAU,UAAS,GAC3B,GACF,IAEA;AAAA,UAAC;AAAA;AAAA,YACC,MAAK;AAAA,YACL,SAAQ;AAAA,YACR,MAAK;AAAA,YACL,WAAU;AAAA,YACV,UAAQ;AAAA,YACR,cAAY,EAAE,oCAAoC,mBAAmB;AAAA,YAErE,8BAAC,QAAK,WAAU,UAAS;AAAA;AAAA,QAC3B;AAAA,QAED,KAAK,YACJ,oBAAC,cAAW,SAAO,MAAC,SAAQ,WAAU,MAAK,MAAK,WAAU,YACxD,8BAAC,OAAE,MAAM,OAAO,KAAK,SAAS,IAAI,cAAY,EAAE,wBAAwB,MAAM,GAC5E,8BAAC,SAAM,WAAU,UAAS,GAC5B,GACF,IAEA;AAAA,UAAC;AAAA;AAAA,YACC,MAAK;AAAA,YACL,SAAQ;AAAA,YACR,MAAK;AAAA,YACL,WAAU;AAAA,YACV,UAAQ;AAAA,YACR,cAAY,EAAE,oCAAoC,mBAAmB;AAAA,YAErE,8BAAC,SAAM,WAAU,UAAS;AAAA;AAAA,QAC5B;AAAA,QAEF;AAAA,UAAC;AAAA;AAAA,YACC,MAAK;AAAA,YACL,SAAQ;AAAA,YACR,MAAK;AAAA,YACL,WAAU;AAAA,YACV,SAAS,MAAM,gBAAgB,CAAC,YAAY,CAAC,OAAO;AAAA,YAEnD,YAAE,8BAA8B,aAAa;AAAA;AAAA,QAChD;AAAA,SACF;AAAA,MAEC,eACC,oBAAC,SAAI,WAAU,sBACb;AAAA,QAAC;AAAA;AAAA,UACC,OAAO,KAAK;AAAA,UACZ,UAAU,OAAO,WAAW;AAC1B,kBAAM,iBAAiB,MAAM;AAAA,UAC/B;AAAA,UACA,YAAY;AAAA,UACZ,SAAS;AAAA,UACT,aAAa,EAAE,qCAAqC,uBAAuB;AAAA;AAAA,MAC7E,GACF,IACE;AAAA,OACN;AAAA,KACF;AAEJ;",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
|
@@ -21,6 +21,8 @@ import { useConfirmDialog } from "@open-mercato/ui/backend/confirm-dialog";
|
|
|
21
21
|
import {
|
|
22
22
|
useScheduleFormState,
|
|
23
23
|
FIELD_VISIBILITY,
|
|
24
|
+
isDateRequired,
|
|
25
|
+
isTimeRequired,
|
|
24
26
|
getFieldLabel,
|
|
25
27
|
DateTimeFields,
|
|
26
28
|
ParticipantsField,
|
|
@@ -324,8 +326,8 @@ function ScheduleActivityDialog({
|
|
|
324
326
|
const trimmedDate = state.date.trim();
|
|
325
327
|
const trimmedStartTime = state.startTime.trim();
|
|
326
328
|
const trimmedCallPhone = callPhoneNumber.trim();
|
|
327
|
-
const isDateMissing = !trimmedDate;
|
|
328
|
-
const isTimeMissing = !state.allDay && !trimmedStartTime;
|
|
329
|
+
const isDateMissing = isDateRequired(state.activityType) && !trimmedDate;
|
|
330
|
+
const isTimeMissing = isTimeRequired(state.activityType) && !state.allDay && !trimmedStartTime;
|
|
329
331
|
const isSubmitDisabled = state.saving || !state.title.trim() || isDateMissing || isTimeMissing;
|
|
330
332
|
const handleSave = React.useCallback(async () => {
|
|
331
333
|
if (!state.title.trim()) return;
|
|
@@ -353,7 +355,8 @@ function ScheduleActivityDialog({
|
|
|
353
355
|
}
|
|
354
356
|
state.setSaving(true);
|
|
355
357
|
try {
|
|
356
|
-
const
|
|
358
|
+
const timeForPayload = state.allDay ? "00:00" : trimmedStartTime;
|
|
359
|
+
const scheduledAt = trimmedDate ? (/* @__PURE__ */ new Date(`${trimmedDate}T${timeForPayload || "00:00"}:00`)).toISOString() : null;
|
|
357
360
|
const recurrenceRule = state.recurrenceEnabled ? buildRecurrenceRule(state.recurrenceDays, state.recurrenceEndType, state.recurrenceCount, state.recurrenceEndDate) : null;
|
|
358
361
|
const isSaveEdit = Boolean(editData?.id);
|
|
359
362
|
const customValues = {};
|
|
@@ -370,8 +373,8 @@ function ScheduleActivityDialog({
|
|
|
370
373
|
title: state.title.trim(),
|
|
371
374
|
body: state.description.trim() || null,
|
|
372
375
|
status: "planned",
|
|
373
|
-
date: trimmedDate,
|
|
374
|
-
time:
|
|
376
|
+
date: trimmedDate || null,
|
|
377
|
+
time: trimmedDate ? timeForPayload || null : null,
|
|
375
378
|
phoneNumber: state.activityType === "call" ? phoneNumberForPayload : void 0,
|
|
376
379
|
// Only tasks expose the priority control, so other types leave the column
|
|
377
380
|
// untouched rather than clearing it on a type switch (#5943).
|