@open-mercato/core 0.10.1-develop.7836.1.0ef0f453c6 → 0.10.1-develop.7841.1.af87ac614d
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/customer_accounts/api/admin/roles.js +25 -2
- package/dist/modules/customer_accounts/api/admin/roles.js.map +2 -2
- package/dist/modules/customer_accounts/api/admin/users.js +24 -7
- package/dist/modules/customer_accounts/api/admin/users.js.map +2 -2
- package/dist/modules/customer_accounts/backend/customer_accounts/users/PortalUsersPageClient.js +77 -15
- package/dist/modules/customer_accounts/backend/customer_accounts/users/PortalUsersPageClient.js.map +2 -2
- package/dist/modules/customer_accounts/backend/customer_accounts/users/page.js +12 -1
- package/dist/modules/customer_accounts/backend/customer_accounts/users/page.js.map +2 -2
- package/dist/modules/customer_accounts/data/validators.js +2 -1
- package/dist/modules/customer_accounts/data/validators.js.map +2 -2
- package/dist/modules/customer_accounts/lib/adminOrganizationScope.js +46 -0
- package/dist/modules/customer_accounts/lib/adminOrganizationScope.js.map +7 -0
- package/dist/modules/customers/api/customer-dictionary-entries/route.js +86 -0
- package/dist/modules/customers/api/customer-dictionary-entries/route.js.map +7 -0
- package/dist/modules/customers/api/dictionaries/[kind]/route.js +29 -7
- package/dist/modules/customers/api/dictionaries/[kind]/route.js.map +2 -2
- package/dist/modules/customers/cli.js +28 -110
- package/dist/modules/customers/cli.js.map +3 -3
- package/dist/modules/customers/components/DictionarySettings.js +1 -1
- package/dist/modules/customers/components/DictionarySettings.js.map +1 -1
- package/dist/modules/customers/components/detail/ManageTagsDialog.js +1 -1
- package/dist/modules/customers/components/detail/ManageTagsDialog.js.map +2 -2
- package/dist/modules/customers/components/detail/TaskDialog.js +14 -1
- package/dist/modules/customers/components/detail/TaskDialog.js.map +2 -2
- package/dist/modules/customers/components/detail/TaskForm.js +1 -16
- package/dist/modules/customers/components/detail/TaskForm.js.map +2 -2
- package/dist/modules/customers/components/detail/TasksSection.js +38 -10
- package/dist/modules/customers/components/detail/TasksSection.js.map +2 -2
- package/dist/modules/customers/components/detail/hooks/useCustomerDictionary.js +12 -5
- package/dist/modules/customers/components/detail/hooks/useCustomerDictionary.js.map +2 -2
- package/dist/modules/customers/components/detail/hooks/useInteractions.js +3 -13
- package/dist/modules/customers/components/detail/hooks/useInteractions.js.map +2 -2
- package/dist/modules/customers/lib/dictionaryDefaults.js +134 -0
- package/dist/modules/customers/lib/dictionaryDefaults.js.map +7 -0
- package/dist/modules/customers/lib/dictionaryLabels.js +59 -0
- package/dist/modules/customers/lib/dictionaryLabels.js.map +7 -0
- package/dist/modules/customers/lib/interactionCompatibility.js +1 -0
- package/dist/modules/customers/lib/interactionCompatibility.js.map +2 -2
- package/dist/modules/customers/translations.js +9 -0
- package/dist/modules/customers/translations.js.map +7 -0
- package/dist/modules/eudr/index.js +13 -0
- package/dist/modules/eudr/index.js.map +2 -2
- package/dist/modules/staff/api/timesheets/access-requests/route.js +11 -5
- package/dist/modules/staff/api/timesheets/access-requests/route.js.map +2 -2
- package/dist/modules/staff/api/timesheets/time-projects/route.js +19 -12
- package/dist/modules/staff/api/timesheets/time-projects/route.js.map +2 -2
- package/dist/modules/staff/backend/staff/time-tracking/projects/[id]/edit/page.js +21 -12
- package/dist/modules/staff/backend/staff/time-tracking/projects/[id]/edit/page.js.map +2 -2
- package/dist/modules/staff/backend/staff/time-tracking/projects/[id]/page.js +14 -9
- package/dist/modules/staff/backend/staff/time-tracking/projects/[id]/page.js.map +2 -2
- package/dist/modules/staff/lib/time-tracking-ui/NoProjectAccess.js +31 -10
- package/dist/modules/staff/lib/time-tracking-ui/NoProjectAccess.js.map +2 -2
- package/dist/modules/staff/lib/time-tracking-ui/projectAccessScope.js +64 -0
- package/dist/modules/staff/lib/time-tracking-ui/projectAccessScope.js.map +7 -0
- package/dist/modules/staff/lib/time-tracking-ui/projectAccessScopeHeaders.js +13 -0
- package/dist/modules/staff/lib/time-tracking-ui/projectAccessScopeHeaders.js.map +7 -0
- package/dist/modules/sync_excel/index.js +11 -1
- package/dist/modules/sync_excel/index.js.map +2 -2
- package/dist/modules/translations/di.js +2 -0
- package/dist/modules/translations/di.js.map +2 -2
- package/package.json +7 -7
- package/src/modules/customer_accounts/api/admin/roles.ts +26 -2
- package/src/modules/customer_accounts/api/admin/users.ts +26 -7
- package/src/modules/customer_accounts/backend/customer_accounts/users/PortalUsersPageClient.tsx +96 -13
- package/src/modules/customer_accounts/backend/customer_accounts/users/page.tsx +9 -1
- package/src/modules/customer_accounts/data/validators.ts +1 -0
- package/src/modules/customer_accounts/i18n/de.json +10 -0
- package/src/modules/customer_accounts/i18n/en.json +10 -0
- package/src/modules/customer_accounts/i18n/es.json +10 -0
- package/src/modules/customer_accounts/i18n/ko.json +10 -0
- package/src/modules/customer_accounts/i18n/pl.json +10 -0
- package/src/modules/customer_accounts/lib/adminOrganizationScope.ts +71 -0
- package/src/modules/customers/api/customer-dictionary-entries/route.ts +82 -0
- package/src/modules/customers/api/dictionaries/[kind]/route.ts +29 -6
- package/src/modules/customers/cli.ts +23 -124
- package/src/modules/customers/components/DictionarySettings.tsx +1 -1
- package/src/modules/customers/components/detail/ManageTagsDialog.tsx +1 -1
- package/src/modules/customers/components/detail/TaskDialog.tsx +15 -1
- package/src/modules/customers/components/detail/TaskForm.tsx +1 -17
- package/src/modules/customers/components/detail/TasksSection.tsx +44 -14
- package/src/modules/customers/components/detail/hooks/useCustomerDictionary.ts +14 -8
- package/src/modules/customers/components/detail/hooks/useInteractions.ts +3 -4
- package/src/modules/customers/i18n/de.json +81 -0
- package/src/modules/customers/i18n/en.json +81 -0
- package/src/modules/customers/i18n/es.json +81 -0
- package/src/modules/customers/i18n/ko.json +81 -0
- package/src/modules/customers/i18n/pl.json +81 -0
- package/src/modules/customers/lib/dictionaryDefaults.ts +137 -0
- package/src/modules/customers/lib/dictionaryLabels.ts +73 -0
- package/src/modules/customers/lib/interactionCompatibility.ts +1 -0
- package/src/modules/customers/translations.ts +5 -0
- package/src/modules/eudr/index.ts +13 -0
- package/src/modules/staff/api/timesheets/access-requests/route.ts +13 -14
- package/src/modules/staff/api/timesheets/time-projects/route.ts +22 -13
- package/src/modules/staff/backend/staff/time-tracking/projects/[id]/edit/page.tsx +25 -14
- package/src/modules/staff/backend/staff/time-tracking/projects/[id]/page.tsx +14 -13
- package/src/modules/staff/lib/time-tracking-ui/NoProjectAccess.tsx +30 -8
- package/src/modules/staff/lib/time-tracking-ui/projectAccessScope.ts +102 -0
- package/src/modules/staff/lib/time-tracking-ui/projectAccessScopeHeaders.ts +15 -0
- package/src/modules/sync_excel/index.ts +10 -0
- package/src/modules/translations/di.ts +2 -0
|
@@ -185,7 +185,7 @@ function CustomerDictionarySection({ kind, title, description }) {
|
|
|
185
185
|
setLoading(true);
|
|
186
186
|
try {
|
|
187
187
|
const data = await readApiResultOrThrow(
|
|
188
|
-
`/api/customers/dictionaries/${kind}`,
|
|
188
|
+
`/api/customers/dictionaries/${kind}?labels=base`,
|
|
189
189
|
void 0,
|
|
190
190
|
{ errorMessage: errorLoad }
|
|
191
191
|
);
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../../../src/modules/customers/components/DictionarySettings.tsx"],
|
|
4
|
-
"sourcesContent": ["\"use client\"\n\nimport * as React from 'react'\nimport dynamic from 'next/dynamic'\nimport {\n Dialog,\n DialogContent,\n DialogHeader,\n DialogTitle,\n} from '@open-mercato/ui/primitives/dialog'\nimport { flash } from '@open-mercato/ui/backend/FlashMessages'\nimport { apiCallOrThrow, readApiResultOrThrow, withScopedApiRequestHeaders } from '@open-mercato/ui/backend/utils/apiCall'\nimport { buildOptimisticLockHeader } from '@open-mercato/ui/backend/utils/optimisticLock'\nimport { useGuardedMutation } from '@open-mercato/ui/backend/injection/useGuardedMutation'\nimport { useOrganizationScopeVersion } from '@open-mercato/shared/lib/frontend/useOrganizationScope'\nimport { useT } from '@open-mercato/shared/lib/i18n/context'\nimport { useConfirmDialog } from '@open-mercato/ui/backend/confirm-dialog'\nimport {\n getCustomerDictionarySettingsSectionId,\n type CustomerDictionaryKind,\n} from '../lib/dictionaries'\nimport { ICON_SUGGESTIONS } from '@open-mercato/core/modules/dictionaries/components/dictionaryAppearance'\nimport {\n DictionaryForm,\n type DictionaryFormValues,\n} from '@open-mercato/core/modules/dictionaries/components/DictionaryForm'\nimport {\n DictionaryTable,\n type DictionaryTableEntry,\n} from '@open-mercato/core/modules/dictionaries/components/DictionaryTable'\nimport { createLogger } from '@open-mercato/shared/lib/logger'\n\nconst logger = createLogger('customers')\nconst ActivityTypeEditor = dynamic(() => import('./ActivityTypeEditor').then((module) => module.ActivityTypeEditor), { ssr: false })\n\ntype SectionDefinition = {\n kind: CustomerDictionaryKind\n title: string\n description: string\n}\n\ntype DictionaryDeleteError = Error & {\n code?: string\n usageCount?: number\n}\n\ntype DialogState =\n | { mode: 'create' }\n | { mode: 'edit'; entry: DictionaryTableEntry }\n\nconst DEFAULT_FORM_VALUES: DictionaryFormValues = {\n value: '',\n label: '',\n color: null,\n icon: null,\n}\n\nconst DICTIONARY_HASH_SCROLL_RETRY_DELAYS_MS = [100, 350, 1000] as const\n\nfunction getCurrentDictionaryHashTarget(): HTMLElement | null {\n if (typeof window === 'undefined') return null\n const hash = window.location.hash\n if (!hash.startsWith('#customer-dictionary-')) return null\n try {\n return document.getElementById(decodeURIComponent(hash.slice(1)))\n } catch {\n return document.getElementById(hash.slice(1))\n }\n}\n\nexport default function DictionarySettings() {\n const t = useT()\n\n const sections = React.useMemo<SectionDefinition[]>(() => [\n {\n kind: 'person-company-roles',\n title: t('customers.config.dictionaries.sections.personCompanyRoles.title', 'Role types'),\n description: t('customers.config.dictionaries.sections.personCompanyRoles.description', 'Manage the ownership roles available in People tab assignments.'),\n },\n {\n kind: 'statuses',\n title: t('customers.config.dictionaries.sections.statuses.title', 'Statuses'),\n description: t('customers.config.dictionaries.sections.statuses.description', 'Define the statuses available for customer records.'),\n },\n {\n kind: 'deal-statuses',\n title: t('customers.config.dictionaries.sections.dealStatuses.title', 'Deal statuses'),\n description: t('customers.config.dictionaries.sections.dealStatuses.description', 'Manage the statuses available for deals.'),\n },\n {\n kind: 'job-titles',\n title: t('customers.config.dictionaries.sections.jobTitles.title', 'Job titles'),\n description: t('customers.config.dictionaries.sections.jobTitles.description', 'Configure job titles with their appearance.'),\n },\n {\n kind: 'sources',\n title: t('customers.config.dictionaries.sections.sources.title', 'Sources'),\n description: t('customers.config.dictionaries.sections.sources.description', 'Capture how customers were acquired.'),\n },\n {\n kind: 'industries',\n title: t('customers.config.dictionaries.sections.industries.title', 'Industries'),\n description: t('customers.config.dictionaries.sections.industries.description', 'Manage the industries used by companies.'),\n },\n {\n kind: 'lifecycle-stages',\n title: t('customers.config.dictionaries.sections.lifecycle.title', 'Lifecycle stages'),\n description: t('customers.config.dictionaries.sections.lifecycle.description', 'Configure lifecycle stages to track customer progress.'),\n },\n {\n kind: 'activity-types',\n title: t('customers.config.dictionaries.sections.activityTypes.title', 'Activity types'),\n description: t('customers.config.dictionaries.sections.activityTypes.description', 'Define the activity types used for customer interactions.'),\n },\n {\n kind: 'interaction-statuses',\n title: t('customers.config.dictionaries.sections.interactionStatuses.title', 'Interaction statuses'),\n description: t('customers.config.dictionaries.sections.interactionStatuses.description', 'Manage the statuses available for tasks and logged interactions.'),\n },\n {\n kind: 'address-types',\n title: t('customers.config.dictionaries.sections.addressTypes.title', 'Address types'),\n description: t('customers.config.dictionaries.sections.addressTypes.description', 'Define the available address types.'),\n },\n ], [t])\n\n React.useEffect(() => {\n const timeoutIds: number[] = []\n let frameId: number | null = null\n\n const clearScheduledScrolls = () => {\n if (frameId !== null) {\n window.cancelAnimationFrame(frameId)\n frameId = null\n }\n while (timeoutIds.length > 0) {\n const timeoutId = timeoutIds.pop()\n if (timeoutId !== undefined) window.clearTimeout(timeoutId)\n }\n }\n\n const scrollToHashTarget = () => {\n getCurrentDictionaryHashTarget()?.scrollIntoView({ block: 'start' })\n }\n\n const scheduleHashScroll = () => {\n clearScheduledScrolls()\n if (!window.location.hash.startsWith('#customer-dictionary-')) return\n scrollToHashTarget()\n if (typeof window.requestAnimationFrame === 'function') {\n frameId = window.requestAnimationFrame(scrollToHashTarget)\n }\n DICTIONARY_HASH_SCROLL_RETRY_DELAYS_MS.forEach((delay) => {\n timeoutIds.push(window.setTimeout(scrollToHashTarget, delay))\n })\n }\n\n scheduleHashScroll()\n window.addEventListener('hashchange', scheduleHashScroll)\n return () => {\n window.removeEventListener('hashchange', scheduleHashScroll)\n clearScheduledScrolls()\n }\n }, [])\n\n return (\n <div className=\"space-y-8\">\n <header className=\"space-y-2\">\n <h1 className=\"text-2xl font-semibold\">\n {t('customers.config.dictionaries.title', 'Customers dictionaries')}\n </h1>\n <p className=\"text-sm text-muted-foreground\">\n {t('customers.config.dictionaries.description', 'Manage the dictionaries used by the customers module.')}\n </p>\n </header>\n\n <div className=\"space-y-6\">\n {sections.map((section) => section.kind === 'activity-types'\n ? <ActivityTypeEditor key={section.kind} title={section.title} description={section.description} />\n : <CustomerDictionarySection key={section.kind} {...section} />)}\n </div>\n </div>\n )\n}\n\ntype CustomerDictionarySectionProps = SectionDefinition\n\nfunction CustomerDictionarySection({ kind, title, description }: CustomerDictionarySectionProps) {\n const t = useT()\n const { confirm, ConfirmDialogElement } = useConfirmDialog()\n const scopeVersion = useOrganizationScopeVersion()\n const [entries, setEntries] = React.useState<DictionaryTableEntry[]>([])\n const [loading, setLoading] = React.useState<boolean>(true)\n const [dialog, setDialog] = React.useState<DialogState | null>(null)\n const [submitting, setSubmitting] = React.useState(false)\n\n const saveContextId = `customers-dictionary-${kind}`\n const { runMutation, retryLastMutation } = useGuardedMutation<{\n formId: string\n resourceKind: string\n retryLastMutation: () => Promise<boolean>\n }>({\n contextId: saveContextId,\n blockedMessage: t('ui.forms.flash.saveBlocked', 'Save blocked by validation'),\n })\n const mutationContext = React.useMemo(\n () => ({\n formId: saveContextId,\n resourceKind: 'customers.dictionary',\n retryLastMutation,\n }),\n [retryLastMutation, saveContextId],\n )\n\n const inheritedActionBlocked = t('customers.config.dictionaries.inherited.blocked', 'Inherited entries can only be edited from the parent organization.')\n const inheritedTooltip = t('customers.config.dictionaries.inherited.tooltip', 'Managed in parent organization')\n const inheritedLabel = t('customers.config.dictionaries.inherited.label', 'Inherited')\n const errorLoad = t('customers.config.dictionaries.error.load', 'Failed to load dictionary entries.')\n const errorSave = t('customers.config.dictionaries.error.save', 'Failed to save dictionary entry.')\n const errorDelete = t('customers.config.dictionaries.error.delete', 'Failed to delete dictionary entry.')\n const successSave = t('customers.config.dictionaries.success.save', 'Dictionary entry saved.')\n const successDelete = t('customers.config.dictionaries.success.delete', 'Dictionary entry deleted.')\n const deleteConfirmTemplate = t('customers.config.dictionaries.deleteConfirm', 'Delete \"{{value}}\"?')\n const roleTypeDeleteBlockedTitle = t('customers.config.dictionaries.roleTypes.deleteBlocked.title', 'Role type is in use')\n const roleTypeDeleteBlockedText = t(\n 'customers.config.dictionaries.roleTypes.deleteBlocked.text',\n 'This role type is assigned to {{count}} records. Remove or replace those assignments before deleting it.',\n )\n const usageCountLabel = t('customers.config.dictionaries.roleTypes.usageCount', 'In use on {{count}} records')\n const searchPlaceholder = t('customers.config.dictionaries.searchPlaceholder', 'Search entries\u2026')\n\n const formatCountMessage = React.useCallback((template: string, count: number) => {\n return template.replace('{{count}}', String(count))\n }, [])\n\n const loadEntries = React.useCallback(async () => {\n setLoading(true)\n try {\n const data = await readApiResultOrThrow<{ items?: unknown[] }>(\n `/api/customers/dictionaries/${kind}`,\n undefined,\n { errorMessage: errorLoad },\n )\n if (!Array.isArray(data?.items)) throw new Error(errorLoad)\n const mapped: DictionaryTableEntry[] = data.items.map((item: any) => ({\n id: String(item.id),\n value: String(item.value ?? ''),\n label: typeof item.label === 'string' ? item.label : '',\n color: typeof item.color === 'string' ? item.color : null,\n icon: typeof item.icon === 'string' ? item.icon : null,\n usageCount: typeof item.usageCount === 'number' ? item.usageCount : undefined,\n organizationId: typeof item.organizationId === 'string' ? item.organizationId : null,\n tenantId: typeof item.tenantId === 'string' ? item.tenantId : null,\n isInherited: item.isInherited === true,\n createdAt: typeof item.createdAt === 'string' ? item.createdAt : null,\n updatedAt: typeof item.updatedAt === 'string' ? item.updatedAt : null,\n }))\n setEntries(mapped)\n } catch (err) {\n logger.error('customers.dictionaries.list failed', { err })\n flash(errorLoad, 'error')\n } finally {\n setLoading(false)\n }\n }, [errorLoad, kind, scopeVersion])\n\n React.useEffect(() => {\n loadEntries().catch(() => {})\n }, [loadEntries])\n\n const closeDialog = React.useCallback(() => {\n setDialog(null)\n }, [])\n\n const handleCreate = React.useCallback(() => {\n setDialog({ mode: 'create' })\n }, [])\n\n const handleEdit = React.useCallback((entry: DictionaryTableEntry) => {\n if (entry.isInherited) {\n flash(inheritedActionBlocked, 'info')\n return\n }\n setDialog({ mode: 'edit', entry })\n }, [inheritedActionBlocked])\n\n const handleDelete = React.useCallback(async (entry: DictionaryTableEntry) => {\n if (entry.isInherited) {\n flash(inheritedActionBlocked, 'info')\n return\n }\n if (kind === 'person-company-roles' && (entry.usageCount ?? 0) > 0) {\n await confirm({\n title: roleTypeDeleteBlockedTitle,\n description: formatCountMessage(roleTypeDeleteBlockedText, entry.usageCount ?? 0),\n confirmText: false,\n cancelText: t('customers.config.dictionaries.dialog.cancel', 'Cancel'),\n })\n return\n }\n const message = deleteConfirmTemplate.replace('{{value}}', entry.label || entry.value)\n const confirmed = await confirm({\n title: message,\n variant: 'destructive',\n })\n if (!confirmed) return\n try {\n await runMutation({\n operation: () =>\n withScopedApiRequestHeaders(buildOptimisticLockHeader(entry.updatedAt), () =>\n apiCallOrThrow(\n `/api/customers/dictionaries/${kind}/${encodeURIComponent(entry.id)}`,\n { method: 'DELETE' },\n { errorMessage: errorDelete },\n ),\n ),\n context: mutationContext,\n mutationPayload: { action: 'delete', id: entry.id, kind },\n })\n flash(successDelete, 'success')\n await loadEntries()\n } catch (err) {\n logger.error('customers.dictionaries.delete failed', { err })\n if (kind === 'person-company-roles' && (err as DictionaryDeleteError)?.code === 'role_type_in_use') {\n const usageCount = Number((err as DictionaryDeleteError).usageCount ?? 0)\n await confirm({\n title: roleTypeDeleteBlockedTitle,\n description: formatCountMessage(roleTypeDeleteBlockedText, usageCount),\n confirmText: false,\n cancelText: t('customers.config.dictionaries.dialog.cancel', 'Cancel'),\n })\n return\n }\n const messageValue = err instanceof Error ? err.message : errorDelete\n flash(messageValue, 'error')\n }\n }, [confirm, deleteConfirmTemplate, errorDelete, formatCountMessage, inheritedActionBlocked, kind, loadEntries, mutationContext, roleTypeDeleteBlockedText, roleTypeDeleteBlockedTitle, runMutation, successDelete, t])\n\n const submitForm = React.useCallback(async (values: DictionaryFormValues) => {\n const payload = {\n value: values.value,\n label: values.label,\n color: values.color,\n icon: values.icon,\n }\n setSubmitting(true)\n try {\n if (!dialog || dialog.mode === 'create') {\n await runMutation({\n operation: () =>\n apiCallOrThrow(\n `/api/customers/dictionaries/${kind}`,\n {\n method: 'POST',\n headers: { 'content-type': 'application/json' },\n body: JSON.stringify(payload),\n },\n { errorMessage: errorSave },\n ),\n context: mutationContext,\n mutationPayload: { action: 'create', kind, ...payload },\n })\n flash(successSave, 'success')\n } else if (dialog.mode === 'edit') {\n const target = dialog.entry\n if (target.isInherited) {\n flash(inheritedActionBlocked, 'info')\n return\n }\n const body: Record<string, unknown> = {}\n if (values.value !== target.value) body.value = values.value\n if (values.label !== target.label) body.label = values.label\n const nextColor = values.color ?? null\n if (nextColor !== (target.color ?? null)) body.color = nextColor\n const nextIcon = values.icon ?? null\n if (nextIcon !== (target.icon ?? null)) body.icon = nextIcon\n if (Object.keys(body).length === 0) {\n closeDialog()\n return\n }\n await runMutation({\n operation: () =>\n withScopedApiRequestHeaders(buildOptimisticLockHeader(target.updatedAt), () =>\n apiCallOrThrow(\n `/api/customers/dictionaries/${kind}/${encodeURIComponent(target.id)}`,\n {\n method: 'PATCH',\n headers: { 'content-type': 'application/json' },\n body: JSON.stringify(body),\n },\n { errorMessage: errorSave },\n ),\n ),\n context: mutationContext,\n mutationPayload: { action: 'update', id: target.id, kind, ...body },\n })\n flash(successSave, 'success')\n }\n closeDialog()\n await loadEntries()\n } catch (err) {\n logger.error('customers.dictionaries.submit failed', { err })\n throw err instanceof Error ? err : new Error(errorSave)\n } finally {\n setSubmitting(false)\n }\n }, [closeDialog, dialog, errorSave, inheritedActionBlocked, kind, loadEntries, mutationContext, runMutation, successSave])\n\n const currentValues = React.useMemo<DictionaryFormValues>(() => {\n if (dialog && dialog.mode === 'edit') {\n return {\n value: dialog.entry.value,\n label: dialog.entry.label,\n color: dialog.entry.color,\n icon: dialog.entry.icon,\n }\n }\n return DEFAULT_FORM_VALUES\n }, [dialog])\n\n const tableTranslations = React.useMemo(() => ({\n title,\n valueColumn: t('customers.config.dictionaries.columns.value', 'Value'),\n labelColumn: t('customers.config.dictionaries.columns.label', 'Label'),\n appearanceColumn: t('customers.config.dictionaries.columns.appearance', 'Appearance'),\n addLabel: t('customers.config.dictionaries.actions.add', 'Add entry'),\n editLabel: t('customers.config.dictionaries.actions.edit', 'Edit'),\n deleteLabel: t('customers.config.dictionaries.actions.delete', 'Delete'),\n refreshLabel: t('customers.config.dictionaries.actions.refresh', 'Refresh'),\n inheritedLabel,\n inheritedTooltip,\n emptyLabel: t('customers.config.dictionaries.empty', 'No entries yet.'),\n usageCountLabel: kind === 'person-company-roles' ? usageCountLabel : undefined,\n searchPlaceholder,\n }), [inheritedLabel, inheritedTooltip, kind, searchPlaceholder, t, title, usageCountLabel])\n\n const formTranslations = React.useMemo(() => ({\n title: dialog?.mode === 'edit'\n ? t('customers.config.dictionaries.dialog.editTitle', 'Edit entry')\n : t('customers.config.dictionaries.dialog.addTitle', 'Add entry'),\n valueLabel: t('customers.config.dictionaries.dialog.valueLabel', 'Value'),\n labelLabel: t('customers.config.dictionaries.dialog.labelLabel', 'Label'),\n saveLabel: t('customers.config.dictionaries.dialog.save', 'Save'),\n cancelLabel: t('customers.config.dictionaries.dialog.cancel', 'Cancel'),\n appearance: {\n colorLabel: t('customers.config.dictionaries.dialog.colorLabel', 'Color'),\n colorHelp: t('customers.config.dictionaries.dialog.colorHelp', 'Pick a highlight color for this entry.'),\n colorClearLabel: t('customers.config.dictionaries.dialog.colorClear', 'Remove color'),\n iconLabel: t('customers.config.dictionaries.dialog.iconLabel', 'Icon'),\n iconPlaceholder: t('customers.config.dictionaries.dialog.iconPlaceholder', 'Type an emoji or pick one of the suggestions.'),\n iconPickerTriggerLabel: t('customers.config.dictionaries.dialog.iconBrowse', 'Browse icons and emojis'),\n iconSearchPlaceholder: t('customers.config.dictionaries.dialog.iconSearchPlaceholder', 'Search icons or emojis\u2026'),\n iconSearchEmptyLabel: t('customers.config.dictionaries.dialog.iconSearchEmpty', 'No icons match your search.'),\n iconSuggestionsLabel: t('customers.config.dictionaries.dialog.iconSuggestions', 'Suggestions'),\n iconClearLabel: t('customers.config.dictionaries.dialog.iconClear', 'Remove icon'),\n previewEmptyLabel: t('customers.config.dictionaries.appearance.empty', 'None'),\n },\n }), [dialog, t])\n\n return (\n <section\n id={getCustomerDictionarySettingsSectionId(kind)}\n className=\"scroll-mt-24 rounded border bg-card text-card-foreground shadow-sm\"\n >\n <div className=\"border-b px-6 py-4 space-y-1\">\n <h2 className=\"text-lg font-medium\">{title}</h2>\n <p className=\"text-sm text-muted-foreground\">{description}</p>\n </div>\n <div className=\"px-2 py-4 sm:px-4\">\n <DictionaryTable\n entries={entries}\n loading={loading}\n canManage\n onCreate={handleCreate}\n onEdit={handleEdit}\n onDelete={handleDelete}\n onRefresh={loadEntries}\n translations={tableTranslations}\n />\n </div>\n <Dialog open={dialog !== null} onOpenChange={(open) => { if (!open) closeDialog() }}>\n <DialogContent className=\"max-w-lg\">\n <DialogHeader>\n <DialogTitle>{formTranslations.title}</DialogTitle>\n </DialogHeader>\n <DictionaryForm\n mode={dialog?.mode === 'edit' ? 'edit' : 'create'}\n initialValues={currentValues}\n onSubmit={submitForm}\n onCancel={closeDialog}\n submitting={submitting}\n translations={formTranslations}\n iconSuggestions={ICON_SUGGESTIONS}\n />\n </DialogContent>\n </Dialog>\n {ConfirmDialogElement}\n </section>\n )\n}\n"],
|
|
4
|
+
"sourcesContent": ["\"use client\"\n\nimport * as React from 'react'\nimport dynamic from 'next/dynamic'\nimport {\n Dialog,\n DialogContent,\n DialogHeader,\n DialogTitle,\n} from '@open-mercato/ui/primitives/dialog'\nimport { flash } from '@open-mercato/ui/backend/FlashMessages'\nimport { apiCallOrThrow, readApiResultOrThrow, withScopedApiRequestHeaders } from '@open-mercato/ui/backend/utils/apiCall'\nimport { buildOptimisticLockHeader } from '@open-mercato/ui/backend/utils/optimisticLock'\nimport { useGuardedMutation } from '@open-mercato/ui/backend/injection/useGuardedMutation'\nimport { useOrganizationScopeVersion } from '@open-mercato/shared/lib/frontend/useOrganizationScope'\nimport { useT } from '@open-mercato/shared/lib/i18n/context'\nimport { useConfirmDialog } from '@open-mercato/ui/backend/confirm-dialog'\nimport {\n getCustomerDictionarySettingsSectionId,\n type CustomerDictionaryKind,\n} from '../lib/dictionaries'\nimport { ICON_SUGGESTIONS } from '@open-mercato/core/modules/dictionaries/components/dictionaryAppearance'\nimport {\n DictionaryForm,\n type DictionaryFormValues,\n} from '@open-mercato/core/modules/dictionaries/components/DictionaryForm'\nimport {\n DictionaryTable,\n type DictionaryTableEntry,\n} from '@open-mercato/core/modules/dictionaries/components/DictionaryTable'\nimport { createLogger } from '@open-mercato/shared/lib/logger'\n\nconst logger = createLogger('customers')\nconst ActivityTypeEditor = dynamic(() => import('./ActivityTypeEditor').then((module) => module.ActivityTypeEditor), { ssr: false })\n\ntype SectionDefinition = {\n kind: CustomerDictionaryKind\n title: string\n description: string\n}\n\ntype DictionaryDeleteError = Error & {\n code?: string\n usageCount?: number\n}\n\ntype DialogState =\n | { mode: 'create' }\n | { mode: 'edit'; entry: DictionaryTableEntry }\n\nconst DEFAULT_FORM_VALUES: DictionaryFormValues = {\n value: '',\n label: '',\n color: null,\n icon: null,\n}\n\nconst DICTIONARY_HASH_SCROLL_RETRY_DELAYS_MS = [100, 350, 1000] as const\n\nfunction getCurrentDictionaryHashTarget(): HTMLElement | null {\n if (typeof window === 'undefined') return null\n const hash = window.location.hash\n if (!hash.startsWith('#customer-dictionary-')) return null\n try {\n return document.getElementById(decodeURIComponent(hash.slice(1)))\n } catch {\n return document.getElementById(hash.slice(1))\n }\n}\n\nexport default function DictionarySettings() {\n const t = useT()\n\n const sections = React.useMemo<SectionDefinition[]>(() => [\n {\n kind: 'person-company-roles',\n title: t('customers.config.dictionaries.sections.personCompanyRoles.title', 'Role types'),\n description: t('customers.config.dictionaries.sections.personCompanyRoles.description', 'Manage the ownership roles available in People tab assignments.'),\n },\n {\n kind: 'statuses',\n title: t('customers.config.dictionaries.sections.statuses.title', 'Statuses'),\n description: t('customers.config.dictionaries.sections.statuses.description', 'Define the statuses available for customer records.'),\n },\n {\n kind: 'deal-statuses',\n title: t('customers.config.dictionaries.sections.dealStatuses.title', 'Deal statuses'),\n description: t('customers.config.dictionaries.sections.dealStatuses.description', 'Manage the statuses available for deals.'),\n },\n {\n kind: 'job-titles',\n title: t('customers.config.dictionaries.sections.jobTitles.title', 'Job titles'),\n description: t('customers.config.dictionaries.sections.jobTitles.description', 'Configure job titles with their appearance.'),\n },\n {\n kind: 'sources',\n title: t('customers.config.dictionaries.sections.sources.title', 'Sources'),\n description: t('customers.config.dictionaries.sections.sources.description', 'Capture how customers were acquired.'),\n },\n {\n kind: 'industries',\n title: t('customers.config.dictionaries.sections.industries.title', 'Industries'),\n description: t('customers.config.dictionaries.sections.industries.description', 'Manage the industries used by companies.'),\n },\n {\n kind: 'lifecycle-stages',\n title: t('customers.config.dictionaries.sections.lifecycle.title', 'Lifecycle stages'),\n description: t('customers.config.dictionaries.sections.lifecycle.description', 'Configure lifecycle stages to track customer progress.'),\n },\n {\n kind: 'activity-types',\n title: t('customers.config.dictionaries.sections.activityTypes.title', 'Activity types'),\n description: t('customers.config.dictionaries.sections.activityTypes.description', 'Define the activity types used for customer interactions.'),\n },\n {\n kind: 'interaction-statuses',\n title: t('customers.config.dictionaries.sections.interactionStatuses.title', 'Interaction statuses'),\n description: t('customers.config.dictionaries.sections.interactionStatuses.description', 'Manage the statuses available for tasks and logged interactions.'),\n },\n {\n kind: 'address-types',\n title: t('customers.config.dictionaries.sections.addressTypes.title', 'Address types'),\n description: t('customers.config.dictionaries.sections.addressTypes.description', 'Define the available address types.'),\n },\n ], [t])\n\n React.useEffect(() => {\n const timeoutIds: number[] = []\n let frameId: number | null = null\n\n const clearScheduledScrolls = () => {\n if (frameId !== null) {\n window.cancelAnimationFrame(frameId)\n frameId = null\n }\n while (timeoutIds.length > 0) {\n const timeoutId = timeoutIds.pop()\n if (timeoutId !== undefined) window.clearTimeout(timeoutId)\n }\n }\n\n const scrollToHashTarget = () => {\n getCurrentDictionaryHashTarget()?.scrollIntoView({ block: 'start' })\n }\n\n const scheduleHashScroll = () => {\n clearScheduledScrolls()\n if (!window.location.hash.startsWith('#customer-dictionary-')) return\n scrollToHashTarget()\n if (typeof window.requestAnimationFrame === 'function') {\n frameId = window.requestAnimationFrame(scrollToHashTarget)\n }\n DICTIONARY_HASH_SCROLL_RETRY_DELAYS_MS.forEach((delay) => {\n timeoutIds.push(window.setTimeout(scrollToHashTarget, delay))\n })\n }\n\n scheduleHashScroll()\n window.addEventListener('hashchange', scheduleHashScroll)\n return () => {\n window.removeEventListener('hashchange', scheduleHashScroll)\n clearScheduledScrolls()\n }\n }, [])\n\n return (\n <div className=\"space-y-8\">\n <header className=\"space-y-2\">\n <h1 className=\"text-2xl font-semibold\">\n {t('customers.config.dictionaries.title', 'Customers dictionaries')}\n </h1>\n <p className=\"text-sm text-muted-foreground\">\n {t('customers.config.dictionaries.description', 'Manage the dictionaries used by the customers module.')}\n </p>\n </header>\n\n <div className=\"space-y-6\">\n {sections.map((section) => section.kind === 'activity-types'\n ? <ActivityTypeEditor key={section.kind} title={section.title} description={section.description} />\n : <CustomerDictionarySection key={section.kind} {...section} />)}\n </div>\n </div>\n )\n}\n\ntype CustomerDictionarySectionProps = SectionDefinition\n\nfunction CustomerDictionarySection({ kind, title, description }: CustomerDictionarySectionProps) {\n const t = useT()\n const { confirm, ConfirmDialogElement } = useConfirmDialog()\n const scopeVersion = useOrganizationScopeVersion()\n const [entries, setEntries] = React.useState<DictionaryTableEntry[]>([])\n const [loading, setLoading] = React.useState<boolean>(true)\n const [dialog, setDialog] = React.useState<DialogState | null>(null)\n const [submitting, setSubmitting] = React.useState(false)\n\n const saveContextId = `customers-dictionary-${kind}`\n const { runMutation, retryLastMutation } = useGuardedMutation<{\n formId: string\n resourceKind: string\n retryLastMutation: () => Promise<boolean>\n }>({\n contextId: saveContextId,\n blockedMessage: t('ui.forms.flash.saveBlocked', 'Save blocked by validation'),\n })\n const mutationContext = React.useMemo(\n () => ({\n formId: saveContextId,\n resourceKind: 'customers.dictionary',\n retryLastMutation,\n }),\n [retryLastMutation, saveContextId],\n )\n\n const inheritedActionBlocked = t('customers.config.dictionaries.inherited.blocked', 'Inherited entries can only be edited from the parent organization.')\n const inheritedTooltip = t('customers.config.dictionaries.inherited.tooltip', 'Managed in parent organization')\n const inheritedLabel = t('customers.config.dictionaries.inherited.label', 'Inherited')\n const errorLoad = t('customers.config.dictionaries.error.load', 'Failed to load dictionary entries.')\n const errorSave = t('customers.config.dictionaries.error.save', 'Failed to save dictionary entry.')\n const errorDelete = t('customers.config.dictionaries.error.delete', 'Failed to delete dictionary entry.')\n const successSave = t('customers.config.dictionaries.success.save', 'Dictionary entry saved.')\n const successDelete = t('customers.config.dictionaries.success.delete', 'Dictionary entry deleted.')\n const deleteConfirmTemplate = t('customers.config.dictionaries.deleteConfirm', 'Delete \"{{value}}\"?')\n const roleTypeDeleteBlockedTitle = t('customers.config.dictionaries.roleTypes.deleteBlocked.title', 'Role type is in use')\n const roleTypeDeleteBlockedText = t(\n 'customers.config.dictionaries.roleTypes.deleteBlocked.text',\n 'This role type is assigned to {{count}} records. Remove or replace those assignments before deleting it.',\n )\n const usageCountLabel = t('customers.config.dictionaries.roleTypes.usageCount', 'In use on {{count}} records')\n const searchPlaceholder = t('customers.config.dictionaries.searchPlaceholder', 'Search entries\u2026')\n\n const formatCountMessage = React.useCallback((template: string, count: number) => {\n return template.replace('{{count}}', String(count))\n }, [])\n\n const loadEntries = React.useCallback(async () => {\n setLoading(true)\n try {\n const data = await readApiResultOrThrow<{ items?: unknown[] }>(\n `/api/customers/dictionaries/${kind}?labels=base`,\n undefined,\n { errorMessage: errorLoad },\n )\n if (!Array.isArray(data?.items)) throw new Error(errorLoad)\n const mapped: DictionaryTableEntry[] = data.items.map((item: any) => ({\n id: String(item.id),\n value: String(item.value ?? ''),\n label: typeof item.label === 'string' ? item.label : '',\n color: typeof item.color === 'string' ? item.color : null,\n icon: typeof item.icon === 'string' ? item.icon : null,\n usageCount: typeof item.usageCount === 'number' ? item.usageCount : undefined,\n organizationId: typeof item.organizationId === 'string' ? item.organizationId : null,\n tenantId: typeof item.tenantId === 'string' ? item.tenantId : null,\n isInherited: item.isInherited === true,\n createdAt: typeof item.createdAt === 'string' ? item.createdAt : null,\n updatedAt: typeof item.updatedAt === 'string' ? item.updatedAt : null,\n }))\n setEntries(mapped)\n } catch (err) {\n logger.error('customers.dictionaries.list failed', { err })\n flash(errorLoad, 'error')\n } finally {\n setLoading(false)\n }\n }, [errorLoad, kind, scopeVersion])\n\n React.useEffect(() => {\n loadEntries().catch(() => {})\n }, [loadEntries])\n\n const closeDialog = React.useCallback(() => {\n setDialog(null)\n }, [])\n\n const handleCreate = React.useCallback(() => {\n setDialog({ mode: 'create' })\n }, [])\n\n const handleEdit = React.useCallback((entry: DictionaryTableEntry) => {\n if (entry.isInherited) {\n flash(inheritedActionBlocked, 'info')\n return\n }\n setDialog({ mode: 'edit', entry })\n }, [inheritedActionBlocked])\n\n const handleDelete = React.useCallback(async (entry: DictionaryTableEntry) => {\n if (entry.isInherited) {\n flash(inheritedActionBlocked, 'info')\n return\n }\n if (kind === 'person-company-roles' && (entry.usageCount ?? 0) > 0) {\n await confirm({\n title: roleTypeDeleteBlockedTitle,\n description: formatCountMessage(roleTypeDeleteBlockedText, entry.usageCount ?? 0),\n confirmText: false,\n cancelText: t('customers.config.dictionaries.dialog.cancel', 'Cancel'),\n })\n return\n }\n const message = deleteConfirmTemplate.replace('{{value}}', entry.label || entry.value)\n const confirmed = await confirm({\n title: message,\n variant: 'destructive',\n })\n if (!confirmed) return\n try {\n await runMutation({\n operation: () =>\n withScopedApiRequestHeaders(buildOptimisticLockHeader(entry.updatedAt), () =>\n apiCallOrThrow(\n `/api/customers/dictionaries/${kind}/${encodeURIComponent(entry.id)}`,\n { method: 'DELETE' },\n { errorMessage: errorDelete },\n ),\n ),\n context: mutationContext,\n mutationPayload: { action: 'delete', id: entry.id, kind },\n })\n flash(successDelete, 'success')\n await loadEntries()\n } catch (err) {\n logger.error('customers.dictionaries.delete failed', { err })\n if (kind === 'person-company-roles' && (err as DictionaryDeleteError)?.code === 'role_type_in_use') {\n const usageCount = Number((err as DictionaryDeleteError).usageCount ?? 0)\n await confirm({\n title: roleTypeDeleteBlockedTitle,\n description: formatCountMessage(roleTypeDeleteBlockedText, usageCount),\n confirmText: false,\n cancelText: t('customers.config.dictionaries.dialog.cancel', 'Cancel'),\n })\n return\n }\n const messageValue = err instanceof Error ? err.message : errorDelete\n flash(messageValue, 'error')\n }\n }, [confirm, deleteConfirmTemplate, errorDelete, formatCountMessage, inheritedActionBlocked, kind, loadEntries, mutationContext, roleTypeDeleteBlockedText, roleTypeDeleteBlockedTitle, runMutation, successDelete, t])\n\n const submitForm = React.useCallback(async (values: DictionaryFormValues) => {\n const payload = {\n value: values.value,\n label: values.label,\n color: values.color,\n icon: values.icon,\n }\n setSubmitting(true)\n try {\n if (!dialog || dialog.mode === 'create') {\n await runMutation({\n operation: () =>\n apiCallOrThrow(\n `/api/customers/dictionaries/${kind}`,\n {\n method: 'POST',\n headers: { 'content-type': 'application/json' },\n body: JSON.stringify(payload),\n },\n { errorMessage: errorSave },\n ),\n context: mutationContext,\n mutationPayload: { action: 'create', kind, ...payload },\n })\n flash(successSave, 'success')\n } else if (dialog.mode === 'edit') {\n const target = dialog.entry\n if (target.isInherited) {\n flash(inheritedActionBlocked, 'info')\n return\n }\n const body: Record<string, unknown> = {}\n if (values.value !== target.value) body.value = values.value\n if (values.label !== target.label) body.label = values.label\n const nextColor = values.color ?? null\n if (nextColor !== (target.color ?? null)) body.color = nextColor\n const nextIcon = values.icon ?? null\n if (nextIcon !== (target.icon ?? null)) body.icon = nextIcon\n if (Object.keys(body).length === 0) {\n closeDialog()\n return\n }\n await runMutation({\n operation: () =>\n withScopedApiRequestHeaders(buildOptimisticLockHeader(target.updatedAt), () =>\n apiCallOrThrow(\n `/api/customers/dictionaries/${kind}/${encodeURIComponent(target.id)}`,\n {\n method: 'PATCH',\n headers: { 'content-type': 'application/json' },\n body: JSON.stringify(body),\n },\n { errorMessage: errorSave },\n ),\n ),\n context: mutationContext,\n mutationPayload: { action: 'update', id: target.id, kind, ...body },\n })\n flash(successSave, 'success')\n }\n closeDialog()\n await loadEntries()\n } catch (err) {\n logger.error('customers.dictionaries.submit failed', { err })\n throw err instanceof Error ? err : new Error(errorSave)\n } finally {\n setSubmitting(false)\n }\n }, [closeDialog, dialog, errorSave, inheritedActionBlocked, kind, loadEntries, mutationContext, runMutation, successSave])\n\n const currentValues = React.useMemo<DictionaryFormValues>(() => {\n if (dialog && dialog.mode === 'edit') {\n return {\n value: dialog.entry.value,\n label: dialog.entry.label,\n color: dialog.entry.color,\n icon: dialog.entry.icon,\n }\n }\n return DEFAULT_FORM_VALUES\n }, [dialog])\n\n const tableTranslations = React.useMemo(() => ({\n title,\n valueColumn: t('customers.config.dictionaries.columns.value', 'Value'),\n labelColumn: t('customers.config.dictionaries.columns.label', 'Label'),\n appearanceColumn: t('customers.config.dictionaries.columns.appearance', 'Appearance'),\n addLabel: t('customers.config.dictionaries.actions.add', 'Add entry'),\n editLabel: t('customers.config.dictionaries.actions.edit', 'Edit'),\n deleteLabel: t('customers.config.dictionaries.actions.delete', 'Delete'),\n refreshLabel: t('customers.config.dictionaries.actions.refresh', 'Refresh'),\n inheritedLabel,\n inheritedTooltip,\n emptyLabel: t('customers.config.dictionaries.empty', 'No entries yet.'),\n usageCountLabel: kind === 'person-company-roles' ? usageCountLabel : undefined,\n searchPlaceholder,\n }), [inheritedLabel, inheritedTooltip, kind, searchPlaceholder, t, title, usageCountLabel])\n\n const formTranslations = React.useMemo(() => ({\n title: dialog?.mode === 'edit'\n ? t('customers.config.dictionaries.dialog.editTitle', 'Edit entry')\n : t('customers.config.dictionaries.dialog.addTitle', 'Add entry'),\n valueLabel: t('customers.config.dictionaries.dialog.valueLabel', 'Value'),\n labelLabel: t('customers.config.dictionaries.dialog.labelLabel', 'Label'),\n saveLabel: t('customers.config.dictionaries.dialog.save', 'Save'),\n cancelLabel: t('customers.config.dictionaries.dialog.cancel', 'Cancel'),\n appearance: {\n colorLabel: t('customers.config.dictionaries.dialog.colorLabel', 'Color'),\n colorHelp: t('customers.config.dictionaries.dialog.colorHelp', 'Pick a highlight color for this entry.'),\n colorClearLabel: t('customers.config.dictionaries.dialog.colorClear', 'Remove color'),\n iconLabel: t('customers.config.dictionaries.dialog.iconLabel', 'Icon'),\n iconPlaceholder: t('customers.config.dictionaries.dialog.iconPlaceholder', 'Type an emoji or pick one of the suggestions.'),\n iconPickerTriggerLabel: t('customers.config.dictionaries.dialog.iconBrowse', 'Browse icons and emojis'),\n iconSearchPlaceholder: t('customers.config.dictionaries.dialog.iconSearchPlaceholder', 'Search icons or emojis\u2026'),\n iconSearchEmptyLabel: t('customers.config.dictionaries.dialog.iconSearchEmpty', 'No icons match your search.'),\n iconSuggestionsLabel: t('customers.config.dictionaries.dialog.iconSuggestions', 'Suggestions'),\n iconClearLabel: t('customers.config.dictionaries.dialog.iconClear', 'Remove icon'),\n previewEmptyLabel: t('customers.config.dictionaries.appearance.empty', 'None'),\n },\n }), [dialog, t])\n\n return (\n <section\n id={getCustomerDictionarySettingsSectionId(kind)}\n className=\"scroll-mt-24 rounded border bg-card text-card-foreground shadow-sm\"\n >\n <div className=\"border-b px-6 py-4 space-y-1\">\n <h2 className=\"text-lg font-medium\">{title}</h2>\n <p className=\"text-sm text-muted-foreground\">{description}</p>\n </div>\n <div className=\"px-2 py-4 sm:px-4\">\n <DictionaryTable\n entries={entries}\n loading={loading}\n canManage\n onCreate={handleCreate}\n onEdit={handleEdit}\n onDelete={handleDelete}\n onRefresh={loadEntries}\n translations={tableTranslations}\n />\n </div>\n <Dialog open={dialog !== null} onOpenChange={(open) => { if (!open) closeDialog() }}>\n <DialogContent className=\"max-w-lg\">\n <DialogHeader>\n <DialogTitle>{formTranslations.title}</DialogTitle>\n </DialogHeader>\n <DictionaryForm\n mode={dialog?.mode === 'edit' ? 'edit' : 'create'}\n initialValues={currentValues}\n onSubmit={submitForm}\n onCancel={closeDialog}\n submitting={submitting}\n translations={formTranslations}\n iconSuggestions={ICON_SUGGESTIONS}\n />\n </DialogContent>\n </Dialog>\n {ConfirmDialogElement}\n </section>\n )\n}\n"],
|
|
5
5
|
"mappings": "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",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
|
@@ -431,7 +431,7 @@ function ManageTagsDialog({ open, onClose }) {
|
|
|
431
431
|
const counts = {};
|
|
432
432
|
for (const category of resolvedCategories) {
|
|
433
433
|
try {
|
|
434
|
-
const data = await readApiResultOrThrow(`/api/customers/dictionaries/${category.kind}`, { cache: "no-store" });
|
|
434
|
+
const data = await readApiResultOrThrow(`/api/customers/dictionaries/${category.kind}?labels=base`, { cache: "no-store" });
|
|
435
435
|
const entries = Array.isArray(data?.items) ? data.items.map(makeDraftEntry).filter((entry) => entry !== null) : [];
|
|
436
436
|
loadedDrafts[category.kind] = entries;
|
|
437
437
|
counts[category.kind] = entries.length;
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../../../../src/modules/customers/components/detail/ManageTagsDialog.tsx"],
|
|
4
|
-
"sourcesContent": ["'use client'\n\nimport * as React from 'react'\nimport {\n CalendarDays,\n ChevronsLeft,\n ChevronsRight,\n ChevronDown,\n ChevronUp,\n Check,\n GripVertical,\n Hash,\n Info,\n Plus,\n Radio,\n RefreshCw,\n Save,\n Search,\n Tag,\n Thermometer,\n Trash2,\n Users,\n X,\n} from 'lucide-react'\nimport {\n DndContext,\n closestCenter,\n PointerSensor,\n KeyboardSensor,\n useSensor,\n useSensors,\n type DragEndEvent,\n} from '@dnd-kit/core'\nimport {\n SortableContext,\n verticalListSortingStrategy,\n useSortable,\n arrayMove,\n} from '@dnd-kit/sortable'\nimport { CSS } from '@dnd-kit/utilities'\nimport { useT } from '@open-mercato/shared/lib/i18n/context'\nimport { apiCallOrThrow, readApiResultOrThrow } from '@open-mercato/ui/backend/utils/apiCall'\nimport { flash } from '@open-mercato/ui/backend/FlashMessages'\nimport { useGuardedMutation } from '@open-mercato/ui/backend/injection/useGuardedMutation'\nimport { Badge } from '@open-mercato/ui/primitives/badge'\nimport { Button } from '@open-mercato/ui/primitives/button'\nimport { ColorPicker } from '@open-mercato/ui/primitives/color-picker'\nimport { IconButton } from '@open-mercato/ui/primitives/icon-button'\nimport { ScrollArea } from '@open-mercato/ui/primitives/scroll-area'\nimport {\n Dialog,\n DialogContent,\n DialogTitle,\n} from '@open-mercato/ui/primitives/dialog'\nimport { VisuallyHidden } from '@radix-ui/react-visually-hidden'\nimport { createLogger } from '@open-mercato/shared/lib/logger'\n\nconst logger = createLogger('customers')\n\n// ---------------------------------------------------------------------------\n// Types\n// ---------------------------------------------------------------------------\n\ntype TagEntryDraft = {\n localId: string\n id: string | null\n value: string\n label: string\n color: string\n icon: string\n isDefault: boolean\n isInherited: boolean\n manualValue: boolean\n deleted: boolean\n}\n\ntype KindSetting = {\n kind: string\n selectionMode: 'single' | 'multi'\n visibleInTags: boolean\n sortOrder: number\n}\n\n// ---------------------------------------------------------------------------\n// Category definitions \u2014 maps to /api/customers/dictionaries/{kind}\n// ---------------------------------------------------------------------------\n\ntype CategoryDef = {\n kind: string // route param for /api/customers/dictionaries/{kind}\n icon: React.ComponentType<{ className?: string }>\n shortLabelKey: string\n shortLabelFallback: string\n descriptionKey: string\n descriptionFallback: string\n badges?: string[]\n noteTitleKey: string\n noteTitleFallback: string\n noteDescriptionKey: string\n noteDescriptionFallback: string\n isCustom?: boolean\n}\n\nconst BUILTIN_CATEGORIES: CategoryDef[] = [\n {\n kind: 'statuses',\n icon: Tag,\n shortLabelKey: 'customers.personTags.category.statuses',\n shortLabelFallback: 'Status',\n descriptionKey: 'customers.tags.manage.description.customers.status',\n descriptionFallback: 'Single-select values visible on the hero area of person, company, and deal cards.',\n badges: ['system', 'required'],\n noteTitleKey: 'customers.tags.manage.noteTitle.customers.status',\n noteTitleFallback: 'System category',\n noteDescriptionKey: 'customers.tags.manage.noteDescription.customers.status',\n noteDescriptionFallback:\n 'Status is required on customer cards. Existing rows can be edited, but this category should remain available tenant-wide.',\n },\n {\n kind: 'lifecycle-stages',\n icon: RefreshCw,\n shortLabelKey: 'customers.personTags.category.lifecycle-stages',\n shortLabelFallback: 'Lifecycle',\n descriptionKey: 'customers.tags.manage.description.customers.lifecycle_stage',\n descriptionFallback: 'Pipeline-aligned lifecycle values shared across CRM detail pages.',\n badges: ['system'],\n noteTitleKey: 'customers.tags.manage.noteTitle.customers.lifecycle_stage',\n noteTitleFallback: 'Shared lifecycle values',\n noteDescriptionKey: 'customers.tags.manage.noteDescription.customers.lifecycle_stage',\n noteDescriptionFallback:\n 'Use lifecycle stages to keep person and company headers visually consistent across CRM detail views.',\n },\n {\n kind: 'sources',\n icon: Radio,\n shortLabelKey: 'customers.personTags.category.sources',\n shortLabelFallback: 'Source',\n descriptionKey: 'customers.tags.manage.description.customers.source',\n descriptionFallback: 'Acquisition source labels used in Zone 1 forms and CRM summary badges.',\n badges: ['system'],\n noteTitleKey: 'customers.tags.manage.noteTitle.customers.source',\n noteTitleFallback: 'Source dictionary',\n noteDescriptionKey: 'customers.tags.manage.noteDescription.customers.source',\n noteDescriptionFallback: 'These values are reused by customer forms and reporting filters.',\n },\n {\n kind: 'temperature',\n icon: Thermometer,\n shortLabelKey: 'customers.personTags.category.temperature',\n shortLabelFallback: 'Temperature',\n descriptionKey: 'customers.tags.manage.description.customers.temperature',\n descriptionFallback: 'Temperature / interest level for leads and contacts.',\n noteTitleKey: 'customers.tags.manage.noteTitle.customers.temperature',\n noteTitleFallback: 'Temperature / Interest',\n noteDescriptionKey: 'customers.tags.manage.noteDescription.customers.temperature',\n noteDescriptionFallback: 'Use temperature to quickly classify contact interest level from hot to cold.',\n },\n {\n kind: 'renewal-quarters',\n icon: CalendarDays,\n shortLabelKey: 'customers.personTags.category.renewal-quarters',\n shortLabelFallback: 'Renewal',\n descriptionKey: 'customers.tags.manage.description.customers.renewal_quarter',\n descriptionFallback: 'Renewal quarter labels for tracking contract renewal timing.',\n noteTitleKey: 'customers.tags.manage.noteTitle.customers.renewal_quarter',\n noteTitleFallback: 'Renewal quarter',\n noteDescriptionKey: 'customers.tags.manage.noteDescription.customers.renewal_quarter',\n noteDescriptionFallback: 'Assign renewal quarters to track when contracts or subscriptions are up for renewal.',\n },\n {\n kind: 'person-company-roles',\n icon: Users,\n shortLabelKey: 'customers.personTags.category.person-company-roles',\n shortLabelFallback: 'Roles',\n descriptionKey: 'customers.tags.manage.description.customers.person_company_role',\n descriptionFallback: 'Person-company relationship roles such as decision maker, influencer, or budget holder.',\n noteTitleKey: 'customers.tags.manage.noteTitle.customers.person_company_role',\n noteTitleFallback: 'Person-company roles',\n noteDescriptionKey: 'customers.tags.manage.noteDescription.customers.person_company_role',\n noteDescriptionFallback: 'Use roles to classify how a person relates to a company, for example decision maker, technical evaluator, or primary contact.',\n },\n {\n kind: 'activity-types',\n icon: CalendarDays,\n shortLabelKey: 'customers.personTags.category.activity-types',\n shortLabelFallback: 'Activity',\n descriptionKey: 'customers.tags.manage.description.customers.activity_type',\n descriptionFallback: 'Activity types for calls, emails, meetings, and other CRM interactions.',\n noteTitleKey: 'customers.tags.manage.noteTitle.customers.activity_type',\n noteTitleFallback: 'Activity types',\n noteDescriptionKey: 'customers.tags.manage.noteDescription.customers.activity_type',\n noteDescriptionFallback: 'Keep activity type names consistent so timeline filters remain readable across CRM views.',\n },\n {\n kind: 'deal-statuses',\n icon: Tag,\n shortLabelKey: 'customers.personTags.category.deal-statuses',\n shortLabelFallback: 'Deal status',\n descriptionKey: 'customers.tags.manage.description.customers.deal_status',\n descriptionFallback: 'Deal status labels used in pipeline views and deal detail cards.',\n noteTitleKey: 'customers.tags.manage.noteTitle.customers.deal_status',\n noteTitleFallback: 'Deal statuses',\n noteDescriptionKey: 'customers.tags.manage.noteDescription.customers.deal_status',\n noteDescriptionFallback: 'Deal status values affect pipeline filtering and reporting groupings.',\n },\n {\n kind: 'industries',\n icon: Hash,\n shortLabelKey: 'customers.personTags.category.industries',\n shortLabelFallback: 'Industry',\n descriptionKey: 'customers.config.dictionaries.sections.industries.description',\n descriptionFallback: 'Industry classification labels for companies and contacts.',\n noteTitleKey: 'customers.tags.manage.noteTitle.customers.industry',\n noteTitleFallback: 'Industry labels',\n noteDescriptionKey: 'customers.tags.manage.noteDescription.customers.industry',\n noteDescriptionFallback: 'Add industry categories that match your target market segments for consistent CRM classification.',\n },\n]\n\nconst BUILTIN_CATEGORY_KINDS = new Set(BUILTIN_CATEGORIES.map((category) => category.kind))\n\n// ---------------------------------------------------------------------------\n// Helpers\n// ---------------------------------------------------------------------------\n\nfunction createLocalId(): string {\n if (typeof crypto !== 'undefined' && typeof crypto.randomUUID === 'function') {\n return crypto.randomUUID()\n }\n return `draft-${Math.random().toString(36).slice(2)}`\n}\n\nfunction normalizeColor(value: string | null | undefined): string {\n if (typeof value !== 'string') return '#D1D5DB'\n const trimmed = value.trim()\n if (/^#([0-9a-fA-F]{6})$/.test(trimmed)) {\n return `#${trimmed.slice(1).toLowerCase()}`\n }\n return '#D1D5DB'\n}\n\nfunction slugifyLabel(value: string): string {\n return value\n .trim()\n .toLowerCase()\n .replace(/[^a-z0-9]+/g, '-')\n .replace(/^-+|-+$/g, '')\n}\n\nfunction sanitizeIcon(value: string | null | undefined): string {\n return typeof value === 'string' ? value.trim() : ''\n}\n\nfunction humanizeCategoryKind(kind: string): string {\n return kind\n .split(/[-_]+/)\n .filter((part) => part.trim().length > 0)\n .map((part) => `${part.slice(0, 1).toUpperCase()}${part.slice(1)}`)\n .join(' ')\n}\n\nfunction createCustomCategoryDef(kind: string): CategoryDef {\n const label = humanizeCategoryKind(kind)\n return {\n kind,\n icon: Hash,\n shortLabelKey: '',\n shortLabelFallback: label,\n descriptionKey: 'customers.tags.manage.customCategoryDescription',\n descriptionFallback: `Custom CRM category: ${label}.`,\n noteTitleKey: 'customers.tags.manage.customCategoryNoteTitle',\n noteTitleFallback: 'Custom category',\n noteDescriptionKey: 'customers.tags.manage.customCategoryNoteDescription',\n noteDescriptionFallback: 'Use this category to group and manage additional CRM values for your team.',\n isCustom: true,\n }\n}\n\nfunction makeDraftEntry(entry: Record<string, unknown>): TagEntryDraft | null {\n const id = typeof entry.id === 'string' ? entry.id : null\n const value = typeof entry.value === 'string' ? entry.value.trim() : ''\n if (!value.length) return null\n return {\n localId: id ?? createLocalId(),\n id,\n value,\n label:\n typeof entry.label === 'string' && entry.label.trim().length\n ? entry.label.trim()\n : value,\n color: normalizeColor(typeof entry.color === 'string' ? entry.color : null),\n icon: sanitizeIcon(typeof entry.icon === 'string' ? entry.icon : null),\n isDefault: false,\n isInherited: typeof entry.isInherited === 'boolean' ? entry.isInherited : false,\n manualValue: true,\n deleted: false,\n }\n}\n\nfunction cloneDrafts(entries: TagEntryDraft[]): TagEntryDraft[] {\n return entries.map((entry) => ({ ...entry }))\n}\n\nfunction serializeEntries(entries: TagEntryDraft[]): string {\n return JSON.stringify(\n entries\n .filter((entry) => !entry.deleted)\n .map((entry) => ({\n id: entry.id,\n value: entry.value,\n label: entry.label,\n color: entry.color,\n icon: entry.icon,\n })),\n )\n}\n\n// ---------------------------------------------------------------------------\n// Sortable entry row\n// ---------------------------------------------------------------------------\n\nfunction SortableEntryRow({\n entry,\n index,\n total,\n isDefault,\n onLabelChange,\n onValueChange,\n onColorChange,\n onDelete,\n onMoveUp,\n onMoveDown,\n t,\n}: {\n entry: TagEntryDraft\n index: number\n total: number\n isDefault: boolean\n onLabelChange: (value: string) => void\n onValueChange: (value: string) => void\n onColorChange: (value: string) => void\n onDelete: () => void\n onMoveUp: () => void\n onMoveDown: () => void\n t: ReturnType<typeof useT>\n}) {\n const { attributes, listeners, setNodeRef, transform, transition, isDragging } = useSortable({\n id: entry.localId,\n })\n const style: React.CSSProperties = {\n transform: CSS.Transform.toString(transform),\n transition,\n opacity: isDragging ? 0.5 : 1,\n }\n\n const isFirst = index === 0\n const isLast = index === total - 1\n\n return (\n <div\n ref={setNodeRef}\n style={style}\n className=\"flex items-center gap-2.5 rounded-md border border-border bg-card px-2.5 py-2\"\n >\n {/* Grip handle */}\n <div\n aria-label={t('customers.tags.manage.dragHandle', 'Drag to reorder')}\n title={t('customers.tags.manage.dragHandle', 'Drag to reorder')}\n className=\"flex size-5 shrink-0 cursor-grab items-center justify-center text-muted-foreground/70\"\n {...attributes}\n {...listeners}\n >\n <GripVertical className=\"size-3.5\" />\n </div>\n\n {/* Arrow reorder buttons \u2014 additive alternative to drag */}\n <div className=\"flex shrink-0 flex-col gap-0.5\">\n <IconButton\n type=\"button\"\n variant=\"ghost\"\n size=\"xs\"\n aria-label={t('customers.tags.manage.moveUp', 'Move up')}\n disabled={isFirst}\n onClick={onMoveUp}\n className=\"h-5 w-5 text-muted-foreground hover:text-foreground disabled:opacity-40\"\n >\n <ChevronUp className=\"size-3\" />\n </IconButton>\n <IconButton\n type=\"button\"\n variant=\"ghost\"\n size=\"xs\"\n aria-label={t('customers.tags.manage.moveDown', 'Move down')}\n disabled={isLast}\n onClick={onMoveDown}\n className=\"h-5 w-5 text-muted-foreground hover:text-foreground disabled:opacity-40\"\n >\n <ChevronDown className=\"size-3\" />\n </IconButton>\n </div>\n\n {/* Label + default indicator */}\n <div className=\"flex min-w-0 flex-1 flex-col gap-1\">\n <input\n type=\"text\"\n value={entry.label}\n onChange={(e) => onLabelChange(e.target.value)}\n className=\"w-full rounded-md border border-input bg-background px-3 py-2 text-sm font-medium text-foreground outline-none focus:border-foreground\"\n />\n {isDefault && (\n <div className=\"flex items-center gap-1.5\">\n <Check className=\"size-2.5 text-status-success-icon\" />\n <span className=\"text-xs text-muted-foreground\">\n {t('customers.tags.manage.defaultEntry', 'default when creating new records')}\n </span>\n </div>\n )}\n </div>\n\n {/* Slug (read-only style) */}\n <div className=\"w-[140px] shrink-0\">\n <input\n type=\"text\"\n value={entry.value}\n onChange={(e) => onValueChange(slugifyLabel(e.target.value))}\n className=\"w-full rounded-md bg-muted px-2.5 py-2 text-xs font-medium text-muted-foreground outline-none\"\n />\n </div>\n\n {/* Color picker */}\n <ColorPicker\n value={normalizeColor(entry.color)}\n onChange={(next) => onColorChange(normalizeColor(next))}\n size=\"sm\"\n className=\"shrink-0\"\n />\n\n {/* Delete */}\n <IconButton\n type=\"button\"\n variant=\"ghost\"\n size=\"sm\"\n className=\"size-8 shrink-0 text-muted-foreground hover:text-destructive\"\n onClick={onDelete}\n aria-label={t('customers.tags.manage.delete', 'Delete')}\n >\n <Trash2 className=\"size-3.5\" />\n </IconButton>\n </div>\n )\n}\n\n// ---------------------------------------------------------------------------\n// Main dialog\n// ---------------------------------------------------------------------------\n\ninterface ManageTagsDialogProps {\n open: boolean\n onClose: () => void\n}\n\nexport function ManageTagsDialog({ open, onClose }: ManageTagsDialogProps) {\n const t = useT()\n const [categories, setCategories] = React.useState<CategoryDef[]>(BUILTIN_CATEGORIES)\n const [activeTab, setActiveTab] = React.useState(BUILTIN_CATEGORIES[0].kind)\n const [loading, setLoading] = React.useState(true)\n const [saving, setSaving] = React.useState(false)\n const [searchValue, setSearchValue] = React.useState('')\n const [entryCounts, setEntryCounts] = React.useState<Record<string, number>>({})\n const [draftsByKind, setDraftsByKind] = React.useState<Record<string, TagEntryDraft[]>>({})\n const [originalByKind, setOriginalByKind] = React.useState<Record<string, TagEntryDraft[]>>({})\n const [createCategoryOpen, setCreateCategoryOpen] = React.useState(false)\n const [newCategoryName, setNewCategoryName] = React.useState('')\n const [newCategorySelectionMode, setNewCategorySelectionMode] = React.useState<'single' | 'multi'>('multi')\n const [creatingCategory, setCreatingCategory] = React.useState(false)\n const [canScrollLeft, setCanScrollLeft] = React.useState(false)\n const [canScrollRight, setCanScrollRight] = React.useState(false)\n const categoryRailRef = React.useRef<HTMLDivElement | null>(null)\n const { runMutation, retryLastMutation } = useGuardedMutation<{\n formId: string\n resourceKind: string\n resourceId: string\n retryLastMutation: () => Promise<boolean>\n }>({\n contextId: 'customers-manage-tags',\n blockedMessage: t('ui.forms.flash.saveBlocked', 'Save blocked by validation'),\n })\n const mutationContext = React.useMemo(\n () => ({\n formId: 'customers-manage-tags',\n resourceKind: 'customers.dictionary_entry',\n resourceId: 'bulk',\n retryLastMutation,\n }),\n [retryLastMutation],\n )\n const runGuardedMutation = React.useCallback(\n async <T,>(operation: () => Promise<T>, mutationPayload: Record<string, unknown>) =>\n runMutation({\n operation,\n mutationPayload,\n context: mutationContext,\n }),\n [mutationContext, runMutation],\n )\n const translatedCategories = React.useMemo(\n () =>\n categories.map((category) => ({\n ...category,\n shortLabel: category.shortLabelKey\n ? t(category.shortLabelKey, category.shortLabelFallback)\n : category.shortLabelFallback,\n description: category.descriptionKey\n ? t(\n category.descriptionKey,\n category.descriptionFallback,\n category.isCustom ? { name: category.shortLabelFallback } : undefined,\n )\n : category.descriptionFallback,\n noteTitle: category.noteTitleKey\n ? t(category.noteTitleKey, category.noteTitleFallback)\n : category.noteTitleFallback,\n noteDescription: category.noteDescriptionKey\n ? t(category.noteDescriptionKey, category.noteDescriptionFallback)\n : category.noteDescriptionFallback,\n })),\n [categories, t],\n )\n\n const sensors = useSensors(\n useSensor(PointerSensor, { activationConstraint: { distance: 5 } }),\n useSensor(KeyboardSensor),\n )\n\n // --- data loading ---\n\n const loadData = React.useCallback(async () => {\n setLoading(true)\n try {\n let kindSettings: KindSetting[] = []\n try {\n const settings = await readApiResultOrThrow<{ items?: KindSetting[] }>(\n '/api/customers/dictionaries/kind-settings',\n { cache: 'no-store' },\n )\n kindSettings = Array.isArray(settings?.items) ? settings.items : []\n } catch {\n kindSettings = []\n }\n\n const customCategories = kindSettings\n .filter((setting) => !BUILTIN_CATEGORY_KINDS.has(setting.kind))\n .sort((left, right) => left.sortOrder - right.sortOrder || left.kind.localeCompare(right.kind))\n .map((setting) => createCustomCategoryDef(setting.kind))\n const resolvedCategories = [...BUILTIN_CATEGORIES, ...customCategories]\n\n const loadedDrafts: Record<string, TagEntryDraft[]> = {}\n const counts: Record<string, number> = {}\n\n for (const category of resolvedCategories) {\n try {\n const data = await readApiResultOrThrow<{\n items?: Array<Record<string, unknown>>\n }>(`/api/customers/dictionaries/${category.kind}`, { cache: 'no-store' })\n const entries = Array.isArray(data?.items)\n ? data.items\n .map(makeDraftEntry)\n .filter((entry): entry is TagEntryDraft => entry !== null)\n : []\n loadedDrafts[category.kind] = entries\n counts[category.kind] = entries.length\n } catch {\n loadedDrafts[category.kind] = []\n counts[category.kind] = 0\n }\n }\n\n setCategories(resolvedCategories)\n setDraftsByKind(\n Object.fromEntries(\n Object.entries(loadedDrafts).map(([k, v]) => [k, cloneDrafts(v)]),\n ),\n )\n setOriginalByKind(\n Object.fromEntries(\n Object.entries(loadedDrafts).map(([k, v]) => [k, cloneDrafts(v)]),\n ),\n )\n setEntryCounts(counts)\n setActiveTab((current) =>\n resolvedCategories.some((category) => category.kind === current)\n ? current\n : resolvedCategories[0]?.kind ?? BUILTIN_CATEGORIES[0].kind,\n )\n } catch (error) {\n const message =\n error instanceof Error\n ? error.message\n : t('customers.tags.manage.loadError', 'Failed to load tag dictionaries.')\n flash(message, 'error')\n } finally {\n setLoading(false)\n }\n }, [t])\n\n React.useEffect(() => {\n if (!open) return\n setSearchValue('')\n setCreateCategoryOpen(false)\n setNewCategoryName('')\n setNewCategorySelectionMode('multi')\n loadData().catch((err) => logger.warn('loadData failed', { component: 'ManageTagsDialog', err }))\n }, [loadData, open])\n\n // --- derived state ---\n\n const activeMeta = translatedCategories.find((category) => category.kind === activeTab) ?? null\n const activeEntries = draftsByKind[activeTab] ?? []\n const visibleEntries = React.useMemo(() => {\n const query = searchValue.trim().toLowerCase()\n return activeEntries.filter((entry) => {\n if (entry.deleted) return false\n if (!query) return true\n return (\n entry.label.toLowerCase().includes(query) || entry.value.toLowerCase().includes(query)\n )\n })\n }, [activeEntries, searchValue])\n\n const hasChanges = React.useMemo(\n () =>\n categories.some((category) => {\n const original = originalByKind[category.kind] ?? []\n const current = draftsByKind[category.kind] ?? []\n return serializeEntries(original) !== serializeEntries(current)\n }),\n [categories, draftsByKind, originalByKind],\n )\n\n // --- draft mutations ---\n\n const updateDraftEntry = React.useCallback(\n (kind: string, localId: string, updater: (e: TagEntryDraft) => TagEntryDraft) => {\n setDraftsByKind((current) => ({\n ...current,\n [kind]: (current[kind] ?? []).map((entry) =>\n entry.localId === localId ? updater(entry) : entry,\n ),\n }))\n },\n [],\n )\n\n const handleAddEntry = React.useCallback(() => {\n setDraftsByKind((current) => ({\n ...current,\n [activeTab]: [\n ...(current[activeTab] ?? []),\n {\n localId: createLocalId(),\n id: null,\n value: '',\n label: '',\n color: '#D1D5DB',\n icon: '',\n isDefault: false,\n isInherited: false,\n manualValue: false,\n deleted: false,\n },\n ],\n }))\n }, [activeTab])\n\n const handleDeleteEntry = React.useCallback(\n (kind: string, localId: string) => {\n setDraftsByKind((current) => {\n const nextEntries = (current[kind] ?? [])\n .map((entry) => {\n if (entry.localId !== localId) return entry\n if (!entry.id) return null\n return { ...entry, deleted: true }\n })\n .filter((entry): entry is TagEntryDraft => entry !== null)\n return { ...current, [kind]: nextEntries }\n })\n },\n [],\n )\n\n // --- drag & drop ---\n\n const handleDragEnd = React.useCallback(\n (event: DragEndEvent) => {\n const { active, over } = event\n if (!over || active.id === over.id) return\n\n setDraftsByKind((current) => {\n const entries = current[activeTab] ?? []\n const liveEntries = entries.filter((e) => !e.deleted)\n const oldIndex = liveEntries.findIndex((e) => e.localId === active.id)\n const newIndex = liveEntries.findIndex((e) => e.localId === over.id)\n if (oldIndex === -1 || newIndex === -1) return current\n const reordered = arrayMove(liveEntries, oldIndex, newIndex)\n const deletedEntries = entries.filter((e) => e.deleted)\n return { ...current, [activeTab]: [...reordered, ...deletedEntries] }\n })\n },\n [activeTab],\n )\n\n const moveEntryByDelta = React.useCallback(\n (localId: string, delta: -1 | 1) => {\n setDraftsByKind((current) => {\n const entries = current[activeTab] ?? []\n const liveEntries = entries.filter((entry) => !entry.deleted)\n const oldIndex = liveEntries.findIndex((entry) => entry.localId === localId)\n if (oldIndex < 0) return current\n const newIndex = oldIndex + delta\n if (newIndex < 0 || newIndex >= liveEntries.length) return current\n const reordered = arrayMove(liveEntries, oldIndex, newIndex)\n const deletedEntries = entries.filter((entry) => entry.deleted)\n return { ...current, [activeTab]: [...reordered, ...deletedEntries] }\n })\n },\n [activeTab],\n )\n\n // --- save ---\n\n const handleSave = React.useCallback(async () => {\n if (saving) return\n\n for (const category of categories) {\n const entries = draftsByKind[category.kind] ?? []\n for (const entry of entries) {\n if (entry.deleted) continue\n const nextLabel = entry.label.trim()\n const nextValue = entry.value.trim()\n if (!nextLabel || !nextValue) {\n flash(\n t(\n 'customers.tags.manage.validation.required',\n 'Each entry must have both a label and a slug before saving.',\n ),\n 'error',\n )\n return\n }\n }\n }\n\n setSaving(true)\n try {\n for (const category of categories) {\n const currentEntries = draftsByKind[category.kind] ?? []\n const originalEntries = originalByKind[category.kind] ?? []\n const originalById = new Map(\n originalEntries.filter((e) => e.id).map((e) => [e.id as string, e]),\n )\n\n for (const entry of currentEntries) {\n if (entry.deleted) {\n if (entry.id) {\n await runGuardedMutation(\n () =>\n // optimistic-lock-exempt: tag dictionary entry assignment add/remove\n apiCallOrThrow(`/api/customers/dictionaries/${category.kind}/${entry.id}`, {\n method: 'DELETE',\n }),\n { kind: category.kind, entryId: entry.id, operation: 'delete' },\n )\n }\n continue\n }\n\n const payload: Record<string, unknown> = {\n value: entry.value.trim(),\n label: entry.label.trim(),\n color: normalizeColor(entry.color),\n icon: sanitizeIcon(entry.icon) || null,\n }\n\n if (!entry.id) {\n await runGuardedMutation(\n () =>\n // optimistic-lock-exempt: tag dictionary entry create-only\n apiCallOrThrow(`/api/customers/dictionaries/${category.kind}`, {\n method: 'POST',\n headers: { 'content-type': 'application/json' },\n body: JSON.stringify(payload),\n }),\n { kind: category.kind, value: payload.value, operation: 'create' },\n )\n continue\n }\n\n // Skip inherited entries \u2014 they belong to a parent org and can't be PATCHed\n if (entry.isInherited) continue\n\n const originalEntry = originalById.get(entry.id)\n if (\n originalEntry &&\n originalEntry.value === (payload.value as string) &&\n originalEntry.label === (payload.label as string) &&\n normalizeColor(originalEntry.color) === normalizeColor(payload.color as string) &&\n sanitizeIcon(originalEntry.icon) === sanitizeIcon(payload.icon as string)\n ) {\n continue\n }\n\n await runGuardedMutation(\n () =>\n // optimistic-lock-exempt: tag dictionary entry assignment add/remove\n apiCallOrThrow(`/api/customers/dictionaries/${category.kind}/${entry.id}`, {\n method: 'PATCH',\n headers: { 'content-type': 'application/json' },\n body: JSON.stringify(payload),\n }),\n { kind: category.kind, entryId: entry.id, value: payload.value, operation: 'update' },\n )\n }\n }\n\n flash(t('customers.tags.manage.saveSuccess', 'Tag dictionaries updated.'), 'success')\n await loadData()\n } catch (error) {\n const message =\n error instanceof Error\n ? error.message\n : t('customers.tags.manage.saveError', 'Failed to save tag dictionaries.')\n flash(message, 'error')\n } finally {\n setSaving(false)\n }\n }, [categories, draftsByKind, loadData, originalByKind, runGuardedMutation, saving, t])\n\n const updateCategoryRailState = React.useCallback(() => {\n const rail = categoryRailRef.current\n if (!rail) {\n setCanScrollLeft(false)\n setCanScrollRight(false)\n return\n }\n const maxScrollLeft = Math.max(0, rail.scrollWidth - rail.clientWidth)\n setCanScrollLeft(rail.scrollLeft > 4)\n setCanScrollRight(rail.scrollLeft < maxScrollLeft - 4)\n }, [])\n\n const scrollCategoryRail = React.useCallback(\n (direction: 'left' | 'right') => {\n const rail = categoryRailRef.current\n if (!rail) return\n const offset = Math.max(180, Math.round(rail.clientWidth * 0.65))\n rail.scrollBy({\n left: direction === 'left' ? -offset : offset,\n behavior: 'smooth',\n })\n },\n [],\n )\n\n React.useEffect(() => {\n if (!open) return\n const rail = categoryRailRef.current\n if (!rail) return\n updateCategoryRailState()\n rail.addEventListener('scroll', updateCategoryRailState, { passive: true })\n const resizeObserver =\n typeof ResizeObserver !== 'undefined'\n ? new ResizeObserver(() => updateCategoryRailState())\n : null\n resizeObserver?.observe(rail)\n return () => {\n rail.removeEventListener('scroll', updateCategoryRailState)\n resizeObserver?.disconnect()\n }\n }, [open, translatedCategories, updateCategoryRailState])\n\n const handleCreateCategory = React.useCallback(async () => {\n if (creatingCategory) return\n const trimmedName = newCategoryName.trim()\n const kind = slugifyLabel(trimmedName)\n if (!kind.length) {\n flash(\n t('customers.tags.manage.addCategoryRequired', 'Enter a category name first.'),\n 'error',\n )\n return\n }\n if (categories.some((category) => category.kind === kind)) {\n setActiveTab(kind)\n setCreateCategoryOpen(false)\n return\n }\n\n setCreatingCategory(true)\n try {\n await runGuardedMutation(\n () =>\n // optimistic-lock-exempt: tag dictionary kind-settings assignment add/remove\n apiCallOrThrow('/api/customers/dictionaries/kind-settings', {\n method: 'PATCH',\n headers: { 'content-type': 'application/json' },\n body: JSON.stringify({\n kind,\n selectionMode: newCategorySelectionMode,\n visibleInTags: true,\n sortOrder: categories.length + 1,\n }),\n }),\n {\n kind,\n selectionMode: newCategorySelectionMode,\n operation: 'createCategory',\n },\n )\n await loadData()\n setActiveTab(kind)\n setCreateCategoryOpen(false)\n setNewCategoryName('')\n setNewCategorySelectionMode('multi')\n flash(t('customers.tags.manage.createCategorySuccess', 'Category created.'), 'success')\n } catch (error) {\n const message =\n error instanceof Error\n ? error.message\n : t('customers.tags.manage.createCategoryError', 'Failed to create category.')\n flash(message, 'error')\n } finally {\n setCreatingCategory(false)\n }\n }, [categories, creatingCategory, loadData, newCategoryName, newCategorySelectionMode, runGuardedMutation, t])\n\n // --- keyboard shortcut ---\n\n React.useEffect(() => {\n if (!open) return\n const handler = (e: KeyboardEvent) => {\n if ((e.metaKey || e.ctrlKey) && e.key === 'Enter') {\n e.preventDefault()\n void handleSave()\n }\n }\n window.addEventListener('keydown', handler)\n return () => window.removeEventListener('keydown', handler)\n }, [open, handleSave])\n\n // --- render ---\n\n return (\n <Dialog\n open={open}\n onOpenChange={(nextOpen) => {\n if (!nextOpen) onClose()\n }}\n >\n <DialogContent\n className=\"flex max-h-[90vh] flex-col overflow-hidden border-border p-0 shadow-[0px_20px_48px_0px_rgba(0,0,0,0.18)] sm:max-w-[820px] sm:rounded-lg [&>[data-dialog-close]]:hidden\"\n aria-describedby={undefined}\n >\n <VisuallyHidden>\n <DialogTitle>{t('customers.tags.manage.title', 'Manage tags')}</DialogTitle>\n </VisuallyHidden>\n\n {/* Header */}\n <div className=\"flex shrink-0 items-center justify-between pb-2.5 pl-6 pr-5 pt-4\">\n <div className=\"flex flex-col gap-1\">\n <h2 className=\"text-base font-bold leading-tight text-foreground\">\n {t('customers.tags.manage.title', 'Manage tags')}\n </h2>\n <p className=\"text-xs leading-tight text-muted-foreground\">\n {t(\n 'customers.tags.manage.subtitle',\n 'Tag dictionaries for the entire tenant',\n )}\n </p>\n </div>\n <div className=\"flex items-center gap-2\">\n <Button\n type=\"button\"\n variant=\"outline\"\n size=\"sm\"\n className=\"h-auto gap-1.5 rounded-md px-3 py-2 text-xs font-semibold\"\n onClick={() => {\n setCreateCategoryOpen((current) => !current)\n setNewCategoryName('')\n setNewCategorySelectionMode('multi')\n }}\n >\n <Plus className=\"size-3.5\" />\n {t('customers.tags.manage.addCategory', 'New category')}\n </Button>\n <IconButton\n type=\"button\"\n variant=\"outline\"\n size=\"sm\"\n className=\"size-7 shrink-0 rounded-md border-border\"\n onClick={onClose}\n aria-label={t('customers.tags.manage.closeDialog', 'Close')}\n >\n <X className=\"size-3.5\" />\n </IconButton>\n </div>\n </div>\n <div className=\"h-px shrink-0 bg-border\" />\n\n {loading ? (\n <div className=\"px-7 py-12 text-center text-sm text-muted-foreground\">\n {t('customers.tags.manage.loading', 'Loading...')}\n </div>\n ) : (\n <>\n {createCategoryOpen ? (\n <div className=\"flex shrink-0 flex-wrap items-center gap-2.5 border-b border-border px-6 py-3\">\n <div className=\"min-w-[220px] flex-1 rounded-md border border-input bg-background px-3 py-2\">\n <input\n type=\"text\"\n value={newCategoryName}\n onChange={(event) => setNewCategoryName(event.target.value)}\n onKeyDown={(event) => {\n if (event.key === 'Enter') {\n event.preventDefault()\n void handleCreateCategory()\n }\n }}\n placeholder={t('customers.tags.manage.addCategoryPlaceholder', 'Category name...')}\n className=\"w-full bg-transparent text-sm text-foreground outline-none placeholder:text-muted-foreground\"\n />\n </div>\n <div className=\"flex items-center gap-1 rounded-md border border-border bg-muted/60 p-1\">\n <Button\n type=\"button\"\n variant={newCategorySelectionMode === 'single' ? 'secondary' : 'ghost'}\n size=\"sm\"\n className=\"h-auto rounded-md px-2.5 py-1.5 text-xs\"\n onClick={() => setNewCategorySelectionMode('single')}\n >\n {t('customers.tags.manage.categoryMode.single', 'Single')}\n </Button>\n <Button\n type=\"button\"\n variant={newCategorySelectionMode === 'multi' ? 'secondary' : 'ghost'}\n size=\"sm\"\n className=\"h-auto rounded-md px-2.5 py-1.5 text-xs\"\n onClick={() => setNewCategorySelectionMode('multi')}\n >\n {t('customers.tags.manage.categoryMode.multi', 'Multi')}\n </Button>\n </div>\n <Button\n type=\"button\"\n size=\"sm\"\n className=\"h-auto rounded-md px-3 py-2 text-xs font-semibold\"\n onClick={() => {\n void handleCreateCategory()\n }}\n disabled={creatingCategory}\n >\n {creatingCategory\n ? t('customers.tags.manage.creatingCategory', 'Creating...')\n : t('customers.tags.manage.createCategory', 'Create category')}\n </Button>\n </div>\n ) : null}\n\n {/* Tab bar */}\n <div className=\"flex shrink-0 items-end gap-2 border-b border-input px-4 pt-1.5\">\n <IconButton\n type=\"button\"\n variant=\"ghost\"\n size=\"sm\"\n className=\"size-8 shrink-0 self-center rounded-full\"\n onClick={() => scrollCategoryRail('left')}\n disabled={!canScrollLeft}\n aria-label={t('customers.tags.manage.scrollLeft', 'Scroll categories left')}\n >\n <ChevronsLeft className=\"size-3.5\" />\n </IconButton>\n <div\n ref={categoryRailRef}\n className=\"scrollbar-hide min-w-0 flex-1 overflow-x-auto\"\n >\n <div className=\"flex items-end gap-0.5\">\n {translatedCategories.map((category) => {\n const Icon = category.icon\n const isActive = category.kind === activeTab\n const count = entryCounts[category.kind] ?? 0\n return (\n <Button\n key={category.kind}\n type=\"button\"\n variant=\"ghost\"\n size=\"sm\"\n onClick={() => {\n setActiveTab(category.kind)\n setSearchValue('')\n }}\n className={`flex h-auto shrink-0 items-center gap-1.5 rounded-none border-b-2 px-2.5 py-2 hover:bg-transparent -mb-px ${\n isActive\n ? 'border-accent-indigo text-foreground'\n : 'border-transparent text-muted-foreground'\n }`}\n >\n <Icon className=\"size-3.5\" />\n <span\n className={`whitespace-nowrap text-xs ${isActive ? 'font-semibold' : 'font-medium'}`}\n >\n {category.shortLabel}\n </span>\n <span className=\"rounded-sm bg-muted px-1 py-px text-overline font-semibold text-foreground\">\n {count}\n </span>\n </Button>\n )\n })}\n </div>\n </div>\n <IconButton\n type=\"button\"\n variant=\"ghost\"\n size=\"sm\"\n className=\"size-8 shrink-0 self-center rounded-full\"\n onClick={() => scrollCategoryRail('right')}\n disabled={!canScrollRight}\n aria-label={t('customers.tags.manage.scrollRight', 'Scroll categories right')}\n >\n <ChevronsRight className=\"size-3.5\" />\n </IconButton>\n </div>\n\n {/* Content */}\n <ScrollArea className=\"min-h-0 flex-1\" viewportClassName=\"px-6 py-3.5\">\n <div className=\"flex flex-col gap-3\">\n {activeMeta ? (\n <>\n {/* Category header + search */}\n <div className=\"flex items-center justify-between\">\n <div className=\"flex flex-col gap-1\">\n <div className=\"flex items-center gap-1.5\">\n <span className=\"text-sm font-bold text-foreground\">\n {activeMeta.shortLabel}\n </span>\n {(activeMeta.badges ?? []).map((badge) => (\n <Badge\n key={badge}\n variant={badge === 'required' ? 'warning' : 'muted'}\n size=\"sm\"\n >\n {badge === 'required'\n ? t('customers.tags.manage.badge.required', 'REQUIRED')\n : t('customers.tags.manage.badge.system', 'SYSTEM')}\n </Badge>\n ))}\n </div>\n <div className=\"flex items-center gap-1.5\">\n <Info className=\"size-3 shrink-0 text-muted-foreground\" />\n <span className=\"text-xs text-muted-foreground\">\n {activeMeta.description}\n </span>\n </div>\n </div>\n <div className=\"relative w-[220px] shrink-0\">\n <Search className=\"absolute left-3 top-1/2 size-3.5 -translate-y-1/2 text-muted-foreground/70\" />\n <input\n type=\"text\"\n value={searchValue}\n onChange={(e) => setSearchValue(e.target.value)}\n placeholder={t('customers.tags.manage.search', 'Search values...')}\n className=\"w-full rounded-md border border-input bg-background py-2 pl-9 pr-3 text-xs text-foreground outline-none placeholder:text-muted-foreground/70 focus:border-foreground\"\n />\n </div>\n </div>\n\n {/* Column headers */}\n <div className=\"flex items-center gap-3 px-3 py-1.5\">\n <div className=\"w-[18px] shrink-0\" />\n <div className=\"min-w-0 flex-1\">\n <span className=\"text-overline font-medium uppercase tracking-wider text-muted-foreground\">\n {t('customers.tags.manage.columns.label', 'LABEL')}\n </span>\n </div>\n <div className=\"w-[140px] shrink-0\">\n <span className=\"text-overline font-medium uppercase tracking-wider text-muted-foreground\">\n {t('customers.tags.manage.columns.slug', 'SLUG')}\n </span>\n </div>\n <div className=\"w-[80px] shrink-0\">\n <span className=\"text-overline font-medium uppercase tracking-wider text-muted-foreground\">\n {t('customers.tags.manage.columns.color', 'COLOR')}\n </span>\n </div>\n <div className=\"w-[32px] shrink-0\" />\n </div>\n\n {/* Entry rows */}\n <div className=\"flex flex-col gap-2\">\n <DndContext\n sensors={sensors}\n collisionDetection={closestCenter}\n onDragEnd={handleDragEnd}\n >\n <SortableContext\n items={visibleEntries.map((e) => e.localId)}\n strategy={verticalListSortingStrategy}\n >\n {visibleEntries.map((entry, index) => (\n <SortableEntryRow\n key={entry.localId}\n entry={entry}\n index={index}\n total={visibleEntries.length}\n isDefault={index === 0 && entry.id !== null}\n onLabelChange={(value) => {\n updateDraftEntry(activeTab, entry.localId, (current) => ({\n ...current,\n label: value,\n value: current.manualValue\n ? current.value\n : slugifyLabel(value),\n }))\n }}\n onValueChange={(value) => {\n updateDraftEntry(activeTab, entry.localId, (current) => ({\n ...current,\n value,\n manualValue: true,\n }))\n }}\n onColorChange={(value) => {\n updateDraftEntry(activeTab, entry.localId, (current) => ({\n ...current,\n color: value,\n }))\n }}\n onDelete={() => handleDeleteEntry(activeTab, entry.localId)}\n onMoveUp={() => moveEntryByDelta(entry.localId, -1)}\n onMoveDown={() => moveEntryByDelta(entry.localId, 1)}\n t={t}\n />\n ))}\n </SortableContext>\n </DndContext>\n\n {visibleEntries.length === 0 && (\n <div className=\"px-4 py-8 text-center text-sm text-muted-foreground\">\n {t(\n 'customers.tags.manage.noMatches',\n 'No entries match the current search.',\n )}\n </div>\n )}\n </div>\n\n {/* Add new value */}\n <Button\n type=\"button\"\n variant=\"ghost\"\n onClick={handleAddEntry}\n className=\"flex h-auto w-full items-center justify-center gap-2 rounded-md border border-dashed border-border bg-background p-3 text-xs font-semibold text-foreground hover:bg-muted\"\n >\n <Plus className=\"size-3.5\" />\n {t('customers.tags.manage.addValue', 'Add new value')}\n </Button>\n\n {/* Info note */}\n <div className=\"flex items-start gap-2.5 rounded-md bg-muted px-3.5 py-3\">\n <Info className=\"mt-0.5 size-3.5 shrink-0 text-muted-foreground\" />\n <div className=\"flex flex-1 flex-col gap-1\">\n <span className=\"text-xs font-semibold text-foreground\">\n {activeMeta.noteTitle}\n </span>\n <span className=\"text-xs text-muted-foreground\">\n {activeMeta.noteDescription}\n </span>\n </div>\n </div>\n </>\n ) : (\n <div className=\"py-8 text-center text-sm text-muted-foreground\">\n {t('customers.tags.manage.noDictionaries', 'No tag categories found.')}\n </div>\n )}\n </div>\n </ScrollArea>\n\n {/* Separator */}\n <div className=\"h-px shrink-0 bg-border\" />\n\n {/* Footer */}\n <div className=\"flex shrink-0 items-center justify-between px-6 py-3\">\n <div className=\"flex items-center gap-1.5\">\n <Info className=\"size-3 shrink-0 text-muted-foreground\" />\n <span className=\"text-xs text-muted-foreground\">\n {t(\n 'customers.tags.manage.tenantNotice',\n 'Changes apply to the entire tenant \\u00b7 visible immediately',\n )}\n </span>\n </div>\n <div className=\"flex items-center gap-2.5\">\n <Button\n type=\"button\"\n variant=\"outline\"\n onClick={onClose}\n className=\"rounded-md border-border px-4 py-2.5 text-sm font-semibold text-foreground\"\n >\n {t('customers.tags.manage.close', 'Cancel')}\n </Button>\n <Button\n type=\"button\"\n onClick={() => {\n void handleSave()\n }}\n disabled={saving || !hasChanges}\n className=\"rounded-md bg-foreground px-4 py-2.5 text-sm font-semibold text-background hover:bg-foreground/90\"\n >\n <Save className=\"mr-2 size-4\" />\n {saving\n ? t('customers.tags.manage.saving', 'Saving...')\n : t('customers.tags.manage.save', 'Save changes')}\n </Button>\n </div>\n </div>\n </>\n )}\n </DialogContent>\n </Dialog>\n )\n}\n"],
|
|
5
|
-
"mappings": "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",
|
|
4
|
+
"sourcesContent": ["'use client'\n\nimport * as React from 'react'\nimport {\n CalendarDays,\n ChevronsLeft,\n ChevronsRight,\n ChevronDown,\n ChevronUp,\n Check,\n GripVertical,\n Hash,\n Info,\n Plus,\n Radio,\n RefreshCw,\n Save,\n Search,\n Tag,\n Thermometer,\n Trash2,\n Users,\n X,\n} from 'lucide-react'\nimport {\n DndContext,\n closestCenter,\n PointerSensor,\n KeyboardSensor,\n useSensor,\n useSensors,\n type DragEndEvent,\n} from '@dnd-kit/core'\nimport {\n SortableContext,\n verticalListSortingStrategy,\n useSortable,\n arrayMove,\n} from '@dnd-kit/sortable'\nimport { CSS } from '@dnd-kit/utilities'\nimport { useT } from '@open-mercato/shared/lib/i18n/context'\nimport { apiCallOrThrow, readApiResultOrThrow } from '@open-mercato/ui/backend/utils/apiCall'\nimport { flash } from '@open-mercato/ui/backend/FlashMessages'\nimport { useGuardedMutation } from '@open-mercato/ui/backend/injection/useGuardedMutation'\nimport { Badge } from '@open-mercato/ui/primitives/badge'\nimport { Button } from '@open-mercato/ui/primitives/button'\nimport { ColorPicker } from '@open-mercato/ui/primitives/color-picker'\nimport { IconButton } from '@open-mercato/ui/primitives/icon-button'\nimport { ScrollArea } from '@open-mercato/ui/primitives/scroll-area'\nimport {\n Dialog,\n DialogContent,\n DialogTitle,\n} from '@open-mercato/ui/primitives/dialog'\nimport { VisuallyHidden } from '@radix-ui/react-visually-hidden'\nimport { createLogger } from '@open-mercato/shared/lib/logger'\n\nconst logger = createLogger('customers')\n\n// ---------------------------------------------------------------------------\n// Types\n// ---------------------------------------------------------------------------\n\ntype TagEntryDraft = {\n localId: string\n id: string | null\n value: string\n label: string\n color: string\n icon: string\n isDefault: boolean\n isInherited: boolean\n manualValue: boolean\n deleted: boolean\n}\n\ntype KindSetting = {\n kind: string\n selectionMode: 'single' | 'multi'\n visibleInTags: boolean\n sortOrder: number\n}\n\n// ---------------------------------------------------------------------------\n// Category definitions \u2014 maps to /api/customers/dictionaries/{kind}\n// ---------------------------------------------------------------------------\n\ntype CategoryDef = {\n kind: string // route param for /api/customers/dictionaries/{kind}\n icon: React.ComponentType<{ className?: string }>\n shortLabelKey: string\n shortLabelFallback: string\n descriptionKey: string\n descriptionFallback: string\n badges?: string[]\n noteTitleKey: string\n noteTitleFallback: string\n noteDescriptionKey: string\n noteDescriptionFallback: string\n isCustom?: boolean\n}\n\nconst BUILTIN_CATEGORIES: CategoryDef[] = [\n {\n kind: 'statuses',\n icon: Tag,\n shortLabelKey: 'customers.personTags.category.statuses',\n shortLabelFallback: 'Status',\n descriptionKey: 'customers.tags.manage.description.customers.status',\n descriptionFallback: 'Single-select values visible on the hero area of person, company, and deal cards.',\n badges: ['system', 'required'],\n noteTitleKey: 'customers.tags.manage.noteTitle.customers.status',\n noteTitleFallback: 'System category',\n noteDescriptionKey: 'customers.tags.manage.noteDescription.customers.status',\n noteDescriptionFallback:\n 'Status is required on customer cards. Existing rows can be edited, but this category should remain available tenant-wide.',\n },\n {\n kind: 'lifecycle-stages',\n icon: RefreshCw,\n shortLabelKey: 'customers.personTags.category.lifecycle-stages',\n shortLabelFallback: 'Lifecycle',\n descriptionKey: 'customers.tags.manage.description.customers.lifecycle_stage',\n descriptionFallback: 'Pipeline-aligned lifecycle values shared across CRM detail pages.',\n badges: ['system'],\n noteTitleKey: 'customers.tags.manage.noteTitle.customers.lifecycle_stage',\n noteTitleFallback: 'Shared lifecycle values',\n noteDescriptionKey: 'customers.tags.manage.noteDescription.customers.lifecycle_stage',\n noteDescriptionFallback:\n 'Use lifecycle stages to keep person and company headers visually consistent across CRM detail views.',\n },\n {\n kind: 'sources',\n icon: Radio,\n shortLabelKey: 'customers.personTags.category.sources',\n shortLabelFallback: 'Source',\n descriptionKey: 'customers.tags.manage.description.customers.source',\n descriptionFallback: 'Acquisition source labels used in Zone 1 forms and CRM summary badges.',\n badges: ['system'],\n noteTitleKey: 'customers.tags.manage.noteTitle.customers.source',\n noteTitleFallback: 'Source dictionary',\n noteDescriptionKey: 'customers.tags.manage.noteDescription.customers.source',\n noteDescriptionFallback: 'These values are reused by customer forms and reporting filters.',\n },\n {\n kind: 'temperature',\n icon: Thermometer,\n shortLabelKey: 'customers.personTags.category.temperature',\n shortLabelFallback: 'Temperature',\n descriptionKey: 'customers.tags.manage.description.customers.temperature',\n descriptionFallback: 'Temperature / interest level for leads and contacts.',\n noteTitleKey: 'customers.tags.manage.noteTitle.customers.temperature',\n noteTitleFallback: 'Temperature / Interest',\n noteDescriptionKey: 'customers.tags.manage.noteDescription.customers.temperature',\n noteDescriptionFallback: 'Use temperature to quickly classify contact interest level from hot to cold.',\n },\n {\n kind: 'renewal-quarters',\n icon: CalendarDays,\n shortLabelKey: 'customers.personTags.category.renewal-quarters',\n shortLabelFallback: 'Renewal',\n descriptionKey: 'customers.tags.manage.description.customers.renewal_quarter',\n descriptionFallback: 'Renewal quarter labels for tracking contract renewal timing.',\n noteTitleKey: 'customers.tags.manage.noteTitle.customers.renewal_quarter',\n noteTitleFallback: 'Renewal quarter',\n noteDescriptionKey: 'customers.tags.manage.noteDescription.customers.renewal_quarter',\n noteDescriptionFallback: 'Assign renewal quarters to track when contracts or subscriptions are up for renewal.',\n },\n {\n kind: 'person-company-roles',\n icon: Users,\n shortLabelKey: 'customers.personTags.category.person-company-roles',\n shortLabelFallback: 'Roles',\n descriptionKey: 'customers.tags.manage.description.customers.person_company_role',\n descriptionFallback: 'Person-company relationship roles such as decision maker, influencer, or budget holder.',\n noteTitleKey: 'customers.tags.manage.noteTitle.customers.person_company_role',\n noteTitleFallback: 'Person-company roles',\n noteDescriptionKey: 'customers.tags.manage.noteDescription.customers.person_company_role',\n noteDescriptionFallback: 'Use roles to classify how a person relates to a company, for example decision maker, technical evaluator, or primary contact.',\n },\n {\n kind: 'activity-types',\n icon: CalendarDays,\n shortLabelKey: 'customers.personTags.category.activity-types',\n shortLabelFallback: 'Activity',\n descriptionKey: 'customers.tags.manage.description.customers.activity_type',\n descriptionFallback: 'Activity types for calls, emails, meetings, and other CRM interactions.',\n noteTitleKey: 'customers.tags.manage.noteTitle.customers.activity_type',\n noteTitleFallback: 'Activity types',\n noteDescriptionKey: 'customers.tags.manage.noteDescription.customers.activity_type',\n noteDescriptionFallback: 'Keep activity type names consistent so timeline filters remain readable across CRM views.',\n },\n {\n kind: 'deal-statuses',\n icon: Tag,\n shortLabelKey: 'customers.personTags.category.deal-statuses',\n shortLabelFallback: 'Deal status',\n descriptionKey: 'customers.tags.manage.description.customers.deal_status',\n descriptionFallback: 'Deal status labels used in pipeline views and deal detail cards.',\n noteTitleKey: 'customers.tags.manage.noteTitle.customers.deal_status',\n noteTitleFallback: 'Deal statuses',\n noteDescriptionKey: 'customers.tags.manage.noteDescription.customers.deal_status',\n noteDescriptionFallback: 'Deal status values affect pipeline filtering and reporting groupings.',\n },\n {\n kind: 'industries',\n icon: Hash,\n shortLabelKey: 'customers.personTags.category.industries',\n shortLabelFallback: 'Industry',\n descriptionKey: 'customers.config.dictionaries.sections.industries.description',\n descriptionFallback: 'Industry classification labels for companies and contacts.',\n noteTitleKey: 'customers.tags.manage.noteTitle.customers.industry',\n noteTitleFallback: 'Industry labels',\n noteDescriptionKey: 'customers.tags.manage.noteDescription.customers.industry',\n noteDescriptionFallback: 'Add industry categories that match your target market segments for consistent CRM classification.',\n },\n]\n\nconst BUILTIN_CATEGORY_KINDS = new Set(BUILTIN_CATEGORIES.map((category) => category.kind))\n\n// ---------------------------------------------------------------------------\n// Helpers\n// ---------------------------------------------------------------------------\n\nfunction createLocalId(): string {\n if (typeof crypto !== 'undefined' && typeof crypto.randomUUID === 'function') {\n return crypto.randomUUID()\n }\n return `draft-${Math.random().toString(36).slice(2)}`\n}\n\nfunction normalizeColor(value: string | null | undefined): string {\n if (typeof value !== 'string') return '#D1D5DB'\n const trimmed = value.trim()\n if (/^#([0-9a-fA-F]{6})$/.test(trimmed)) {\n return `#${trimmed.slice(1).toLowerCase()}`\n }\n return '#D1D5DB'\n}\n\nfunction slugifyLabel(value: string): string {\n return value\n .trim()\n .toLowerCase()\n .replace(/[^a-z0-9]+/g, '-')\n .replace(/^-+|-+$/g, '')\n}\n\nfunction sanitizeIcon(value: string | null | undefined): string {\n return typeof value === 'string' ? value.trim() : ''\n}\n\nfunction humanizeCategoryKind(kind: string): string {\n return kind\n .split(/[-_]+/)\n .filter((part) => part.trim().length > 0)\n .map((part) => `${part.slice(0, 1).toUpperCase()}${part.slice(1)}`)\n .join(' ')\n}\n\nfunction createCustomCategoryDef(kind: string): CategoryDef {\n const label = humanizeCategoryKind(kind)\n return {\n kind,\n icon: Hash,\n shortLabelKey: '',\n shortLabelFallback: label,\n descriptionKey: 'customers.tags.manage.customCategoryDescription',\n descriptionFallback: `Custom CRM category: ${label}.`,\n noteTitleKey: 'customers.tags.manage.customCategoryNoteTitle',\n noteTitleFallback: 'Custom category',\n noteDescriptionKey: 'customers.tags.manage.customCategoryNoteDescription',\n noteDescriptionFallback: 'Use this category to group and manage additional CRM values for your team.',\n isCustom: true,\n }\n}\n\nfunction makeDraftEntry(entry: Record<string, unknown>): TagEntryDraft | null {\n const id = typeof entry.id === 'string' ? entry.id : null\n const value = typeof entry.value === 'string' ? entry.value.trim() : ''\n if (!value.length) return null\n return {\n localId: id ?? createLocalId(),\n id,\n value,\n label:\n typeof entry.label === 'string' && entry.label.trim().length\n ? entry.label.trim()\n : value,\n color: normalizeColor(typeof entry.color === 'string' ? entry.color : null),\n icon: sanitizeIcon(typeof entry.icon === 'string' ? entry.icon : null),\n isDefault: false,\n isInherited: typeof entry.isInherited === 'boolean' ? entry.isInherited : false,\n manualValue: true,\n deleted: false,\n }\n}\n\nfunction cloneDrafts(entries: TagEntryDraft[]): TagEntryDraft[] {\n return entries.map((entry) => ({ ...entry }))\n}\n\nfunction serializeEntries(entries: TagEntryDraft[]): string {\n return JSON.stringify(\n entries\n .filter((entry) => !entry.deleted)\n .map((entry) => ({\n id: entry.id,\n value: entry.value,\n label: entry.label,\n color: entry.color,\n icon: entry.icon,\n })),\n )\n}\n\n// ---------------------------------------------------------------------------\n// Sortable entry row\n// ---------------------------------------------------------------------------\n\nfunction SortableEntryRow({\n entry,\n index,\n total,\n isDefault,\n onLabelChange,\n onValueChange,\n onColorChange,\n onDelete,\n onMoveUp,\n onMoveDown,\n t,\n}: {\n entry: TagEntryDraft\n index: number\n total: number\n isDefault: boolean\n onLabelChange: (value: string) => void\n onValueChange: (value: string) => void\n onColorChange: (value: string) => void\n onDelete: () => void\n onMoveUp: () => void\n onMoveDown: () => void\n t: ReturnType<typeof useT>\n}) {\n const { attributes, listeners, setNodeRef, transform, transition, isDragging } = useSortable({\n id: entry.localId,\n })\n const style: React.CSSProperties = {\n transform: CSS.Transform.toString(transform),\n transition,\n opacity: isDragging ? 0.5 : 1,\n }\n\n const isFirst = index === 0\n const isLast = index === total - 1\n\n return (\n <div\n ref={setNodeRef}\n style={style}\n className=\"flex items-center gap-2.5 rounded-md border border-border bg-card px-2.5 py-2\"\n >\n {/* Grip handle */}\n <div\n aria-label={t('customers.tags.manage.dragHandle', 'Drag to reorder')}\n title={t('customers.tags.manage.dragHandle', 'Drag to reorder')}\n className=\"flex size-5 shrink-0 cursor-grab items-center justify-center text-muted-foreground/70\"\n {...attributes}\n {...listeners}\n >\n <GripVertical className=\"size-3.5\" />\n </div>\n\n {/* Arrow reorder buttons \u2014 additive alternative to drag */}\n <div className=\"flex shrink-0 flex-col gap-0.5\">\n <IconButton\n type=\"button\"\n variant=\"ghost\"\n size=\"xs\"\n aria-label={t('customers.tags.manage.moveUp', 'Move up')}\n disabled={isFirst}\n onClick={onMoveUp}\n className=\"h-5 w-5 text-muted-foreground hover:text-foreground disabled:opacity-40\"\n >\n <ChevronUp className=\"size-3\" />\n </IconButton>\n <IconButton\n type=\"button\"\n variant=\"ghost\"\n size=\"xs\"\n aria-label={t('customers.tags.manage.moveDown', 'Move down')}\n disabled={isLast}\n onClick={onMoveDown}\n className=\"h-5 w-5 text-muted-foreground hover:text-foreground disabled:opacity-40\"\n >\n <ChevronDown className=\"size-3\" />\n </IconButton>\n </div>\n\n {/* Label + default indicator */}\n <div className=\"flex min-w-0 flex-1 flex-col gap-1\">\n <input\n type=\"text\"\n value={entry.label}\n onChange={(e) => onLabelChange(e.target.value)}\n className=\"w-full rounded-md border border-input bg-background px-3 py-2 text-sm font-medium text-foreground outline-none focus:border-foreground\"\n />\n {isDefault && (\n <div className=\"flex items-center gap-1.5\">\n <Check className=\"size-2.5 text-status-success-icon\" />\n <span className=\"text-xs text-muted-foreground\">\n {t('customers.tags.manage.defaultEntry', 'default when creating new records')}\n </span>\n </div>\n )}\n </div>\n\n {/* Slug (read-only style) */}\n <div className=\"w-[140px] shrink-0\">\n <input\n type=\"text\"\n value={entry.value}\n onChange={(e) => onValueChange(slugifyLabel(e.target.value))}\n className=\"w-full rounded-md bg-muted px-2.5 py-2 text-xs font-medium text-muted-foreground outline-none\"\n />\n </div>\n\n {/* Color picker */}\n <ColorPicker\n value={normalizeColor(entry.color)}\n onChange={(next) => onColorChange(normalizeColor(next))}\n size=\"sm\"\n className=\"shrink-0\"\n />\n\n {/* Delete */}\n <IconButton\n type=\"button\"\n variant=\"ghost\"\n size=\"sm\"\n className=\"size-8 shrink-0 text-muted-foreground hover:text-destructive\"\n onClick={onDelete}\n aria-label={t('customers.tags.manage.delete', 'Delete')}\n >\n <Trash2 className=\"size-3.5\" />\n </IconButton>\n </div>\n )\n}\n\n// ---------------------------------------------------------------------------\n// Main dialog\n// ---------------------------------------------------------------------------\n\ninterface ManageTagsDialogProps {\n open: boolean\n onClose: () => void\n}\n\nexport function ManageTagsDialog({ open, onClose }: ManageTagsDialogProps) {\n const t = useT()\n const [categories, setCategories] = React.useState<CategoryDef[]>(BUILTIN_CATEGORIES)\n const [activeTab, setActiveTab] = React.useState(BUILTIN_CATEGORIES[0].kind)\n const [loading, setLoading] = React.useState(true)\n const [saving, setSaving] = React.useState(false)\n const [searchValue, setSearchValue] = React.useState('')\n const [entryCounts, setEntryCounts] = React.useState<Record<string, number>>({})\n const [draftsByKind, setDraftsByKind] = React.useState<Record<string, TagEntryDraft[]>>({})\n const [originalByKind, setOriginalByKind] = React.useState<Record<string, TagEntryDraft[]>>({})\n const [createCategoryOpen, setCreateCategoryOpen] = React.useState(false)\n const [newCategoryName, setNewCategoryName] = React.useState('')\n const [newCategorySelectionMode, setNewCategorySelectionMode] = React.useState<'single' | 'multi'>('multi')\n const [creatingCategory, setCreatingCategory] = React.useState(false)\n const [canScrollLeft, setCanScrollLeft] = React.useState(false)\n const [canScrollRight, setCanScrollRight] = React.useState(false)\n const categoryRailRef = React.useRef<HTMLDivElement | null>(null)\n const { runMutation, retryLastMutation } = useGuardedMutation<{\n formId: string\n resourceKind: string\n resourceId: string\n retryLastMutation: () => Promise<boolean>\n }>({\n contextId: 'customers-manage-tags',\n blockedMessage: t('ui.forms.flash.saveBlocked', 'Save blocked by validation'),\n })\n const mutationContext = React.useMemo(\n () => ({\n formId: 'customers-manage-tags',\n resourceKind: 'customers.dictionary_entry',\n resourceId: 'bulk',\n retryLastMutation,\n }),\n [retryLastMutation],\n )\n const runGuardedMutation = React.useCallback(\n async <T,>(operation: () => Promise<T>, mutationPayload: Record<string, unknown>) =>\n runMutation({\n operation,\n mutationPayload,\n context: mutationContext,\n }),\n [mutationContext, runMutation],\n )\n const translatedCategories = React.useMemo(\n () =>\n categories.map((category) => ({\n ...category,\n shortLabel: category.shortLabelKey\n ? t(category.shortLabelKey, category.shortLabelFallback)\n : category.shortLabelFallback,\n description: category.descriptionKey\n ? t(\n category.descriptionKey,\n category.descriptionFallback,\n category.isCustom ? { name: category.shortLabelFallback } : undefined,\n )\n : category.descriptionFallback,\n noteTitle: category.noteTitleKey\n ? t(category.noteTitleKey, category.noteTitleFallback)\n : category.noteTitleFallback,\n noteDescription: category.noteDescriptionKey\n ? t(category.noteDescriptionKey, category.noteDescriptionFallback)\n : category.noteDescriptionFallback,\n })),\n [categories, t],\n )\n\n const sensors = useSensors(\n useSensor(PointerSensor, { activationConstraint: { distance: 5 } }),\n useSensor(KeyboardSensor),\n )\n\n // --- data loading ---\n\n const loadData = React.useCallback(async () => {\n setLoading(true)\n try {\n let kindSettings: KindSetting[] = []\n try {\n const settings = await readApiResultOrThrow<{ items?: KindSetting[] }>(\n '/api/customers/dictionaries/kind-settings',\n { cache: 'no-store' },\n )\n kindSettings = Array.isArray(settings?.items) ? settings.items : []\n } catch {\n kindSettings = []\n }\n\n const customCategories = kindSettings\n .filter((setting) => !BUILTIN_CATEGORY_KINDS.has(setting.kind))\n .sort((left, right) => left.sortOrder - right.sortOrder || left.kind.localeCompare(right.kind))\n .map((setting) => createCustomCategoryDef(setting.kind))\n const resolvedCategories = [...BUILTIN_CATEGORIES, ...customCategories]\n\n const loadedDrafts: Record<string, TagEntryDraft[]> = {}\n const counts: Record<string, number> = {}\n\n for (const category of resolvedCategories) {\n try {\n const data = await readApiResultOrThrow<{\n items?: Array<Record<string, unknown>>\n }>(`/api/customers/dictionaries/${category.kind}?labels=base`, { cache: 'no-store' })\n const entries = Array.isArray(data?.items)\n ? data.items\n .map(makeDraftEntry)\n .filter((entry): entry is TagEntryDraft => entry !== null)\n : []\n loadedDrafts[category.kind] = entries\n counts[category.kind] = entries.length\n } catch {\n loadedDrafts[category.kind] = []\n counts[category.kind] = 0\n }\n }\n\n setCategories(resolvedCategories)\n setDraftsByKind(\n Object.fromEntries(\n Object.entries(loadedDrafts).map(([k, v]) => [k, cloneDrafts(v)]),\n ),\n )\n setOriginalByKind(\n Object.fromEntries(\n Object.entries(loadedDrafts).map(([k, v]) => [k, cloneDrafts(v)]),\n ),\n )\n setEntryCounts(counts)\n setActiveTab((current) =>\n resolvedCategories.some((category) => category.kind === current)\n ? current\n : resolvedCategories[0]?.kind ?? BUILTIN_CATEGORIES[0].kind,\n )\n } catch (error) {\n const message =\n error instanceof Error\n ? error.message\n : t('customers.tags.manage.loadError', 'Failed to load tag dictionaries.')\n flash(message, 'error')\n } finally {\n setLoading(false)\n }\n }, [t])\n\n React.useEffect(() => {\n if (!open) return\n setSearchValue('')\n setCreateCategoryOpen(false)\n setNewCategoryName('')\n setNewCategorySelectionMode('multi')\n loadData().catch((err) => logger.warn('loadData failed', { component: 'ManageTagsDialog', err }))\n }, [loadData, open])\n\n // --- derived state ---\n\n const activeMeta = translatedCategories.find((category) => category.kind === activeTab) ?? null\n const activeEntries = draftsByKind[activeTab] ?? []\n const visibleEntries = React.useMemo(() => {\n const query = searchValue.trim().toLowerCase()\n return activeEntries.filter((entry) => {\n if (entry.deleted) return false\n if (!query) return true\n return (\n entry.label.toLowerCase().includes(query) || entry.value.toLowerCase().includes(query)\n )\n })\n }, [activeEntries, searchValue])\n\n const hasChanges = React.useMemo(\n () =>\n categories.some((category) => {\n const original = originalByKind[category.kind] ?? []\n const current = draftsByKind[category.kind] ?? []\n return serializeEntries(original) !== serializeEntries(current)\n }),\n [categories, draftsByKind, originalByKind],\n )\n\n // --- draft mutations ---\n\n const updateDraftEntry = React.useCallback(\n (kind: string, localId: string, updater: (e: TagEntryDraft) => TagEntryDraft) => {\n setDraftsByKind((current) => ({\n ...current,\n [kind]: (current[kind] ?? []).map((entry) =>\n entry.localId === localId ? updater(entry) : entry,\n ),\n }))\n },\n [],\n )\n\n const handleAddEntry = React.useCallback(() => {\n setDraftsByKind((current) => ({\n ...current,\n [activeTab]: [\n ...(current[activeTab] ?? []),\n {\n localId: createLocalId(),\n id: null,\n value: '',\n label: '',\n color: '#D1D5DB',\n icon: '',\n isDefault: false,\n isInherited: false,\n manualValue: false,\n deleted: false,\n },\n ],\n }))\n }, [activeTab])\n\n const handleDeleteEntry = React.useCallback(\n (kind: string, localId: string) => {\n setDraftsByKind((current) => {\n const nextEntries = (current[kind] ?? [])\n .map((entry) => {\n if (entry.localId !== localId) return entry\n if (!entry.id) return null\n return { ...entry, deleted: true }\n })\n .filter((entry): entry is TagEntryDraft => entry !== null)\n return { ...current, [kind]: nextEntries }\n })\n },\n [],\n )\n\n // --- drag & drop ---\n\n const handleDragEnd = React.useCallback(\n (event: DragEndEvent) => {\n const { active, over } = event\n if (!over || active.id === over.id) return\n\n setDraftsByKind((current) => {\n const entries = current[activeTab] ?? []\n const liveEntries = entries.filter((e) => !e.deleted)\n const oldIndex = liveEntries.findIndex((e) => e.localId === active.id)\n const newIndex = liveEntries.findIndex((e) => e.localId === over.id)\n if (oldIndex === -1 || newIndex === -1) return current\n const reordered = arrayMove(liveEntries, oldIndex, newIndex)\n const deletedEntries = entries.filter((e) => e.deleted)\n return { ...current, [activeTab]: [...reordered, ...deletedEntries] }\n })\n },\n [activeTab],\n )\n\n const moveEntryByDelta = React.useCallback(\n (localId: string, delta: -1 | 1) => {\n setDraftsByKind((current) => {\n const entries = current[activeTab] ?? []\n const liveEntries = entries.filter((entry) => !entry.deleted)\n const oldIndex = liveEntries.findIndex((entry) => entry.localId === localId)\n if (oldIndex < 0) return current\n const newIndex = oldIndex + delta\n if (newIndex < 0 || newIndex >= liveEntries.length) return current\n const reordered = arrayMove(liveEntries, oldIndex, newIndex)\n const deletedEntries = entries.filter((entry) => entry.deleted)\n return { ...current, [activeTab]: [...reordered, ...deletedEntries] }\n })\n },\n [activeTab],\n )\n\n // --- save ---\n\n const handleSave = React.useCallback(async () => {\n if (saving) return\n\n for (const category of categories) {\n const entries = draftsByKind[category.kind] ?? []\n for (const entry of entries) {\n if (entry.deleted) continue\n const nextLabel = entry.label.trim()\n const nextValue = entry.value.trim()\n if (!nextLabel || !nextValue) {\n flash(\n t(\n 'customers.tags.manage.validation.required',\n 'Each entry must have both a label and a slug before saving.',\n ),\n 'error',\n )\n return\n }\n }\n }\n\n setSaving(true)\n try {\n for (const category of categories) {\n const currentEntries = draftsByKind[category.kind] ?? []\n const originalEntries = originalByKind[category.kind] ?? []\n const originalById = new Map(\n originalEntries.filter((e) => e.id).map((e) => [e.id as string, e]),\n )\n\n for (const entry of currentEntries) {\n if (entry.deleted) {\n if (entry.id) {\n await runGuardedMutation(\n () =>\n // optimistic-lock-exempt: tag dictionary entry assignment add/remove\n apiCallOrThrow(`/api/customers/dictionaries/${category.kind}/${entry.id}`, {\n method: 'DELETE',\n }),\n { kind: category.kind, entryId: entry.id, operation: 'delete' },\n )\n }\n continue\n }\n\n const payload: Record<string, unknown> = {\n value: entry.value.trim(),\n label: entry.label.trim(),\n color: normalizeColor(entry.color),\n icon: sanitizeIcon(entry.icon) || null,\n }\n\n if (!entry.id) {\n await runGuardedMutation(\n () =>\n // optimistic-lock-exempt: tag dictionary entry create-only\n apiCallOrThrow(`/api/customers/dictionaries/${category.kind}`, {\n method: 'POST',\n headers: { 'content-type': 'application/json' },\n body: JSON.stringify(payload),\n }),\n { kind: category.kind, value: payload.value, operation: 'create' },\n )\n continue\n }\n\n // Skip inherited entries \u2014 they belong to a parent org and can't be PATCHed\n if (entry.isInherited) continue\n\n const originalEntry = originalById.get(entry.id)\n if (\n originalEntry &&\n originalEntry.value === (payload.value as string) &&\n originalEntry.label === (payload.label as string) &&\n normalizeColor(originalEntry.color) === normalizeColor(payload.color as string) &&\n sanitizeIcon(originalEntry.icon) === sanitizeIcon(payload.icon as string)\n ) {\n continue\n }\n\n await runGuardedMutation(\n () =>\n // optimistic-lock-exempt: tag dictionary entry assignment add/remove\n apiCallOrThrow(`/api/customers/dictionaries/${category.kind}/${entry.id}`, {\n method: 'PATCH',\n headers: { 'content-type': 'application/json' },\n body: JSON.stringify(payload),\n }),\n { kind: category.kind, entryId: entry.id, value: payload.value, operation: 'update' },\n )\n }\n }\n\n flash(t('customers.tags.manage.saveSuccess', 'Tag dictionaries updated.'), 'success')\n await loadData()\n } catch (error) {\n const message =\n error instanceof Error\n ? error.message\n : t('customers.tags.manage.saveError', 'Failed to save tag dictionaries.')\n flash(message, 'error')\n } finally {\n setSaving(false)\n }\n }, [categories, draftsByKind, loadData, originalByKind, runGuardedMutation, saving, t])\n\n const updateCategoryRailState = React.useCallback(() => {\n const rail = categoryRailRef.current\n if (!rail) {\n setCanScrollLeft(false)\n setCanScrollRight(false)\n return\n }\n const maxScrollLeft = Math.max(0, rail.scrollWidth - rail.clientWidth)\n setCanScrollLeft(rail.scrollLeft > 4)\n setCanScrollRight(rail.scrollLeft < maxScrollLeft - 4)\n }, [])\n\n const scrollCategoryRail = React.useCallback(\n (direction: 'left' | 'right') => {\n const rail = categoryRailRef.current\n if (!rail) return\n const offset = Math.max(180, Math.round(rail.clientWidth * 0.65))\n rail.scrollBy({\n left: direction === 'left' ? -offset : offset,\n behavior: 'smooth',\n })\n },\n [],\n )\n\n React.useEffect(() => {\n if (!open) return\n const rail = categoryRailRef.current\n if (!rail) return\n updateCategoryRailState()\n rail.addEventListener('scroll', updateCategoryRailState, { passive: true })\n const resizeObserver =\n typeof ResizeObserver !== 'undefined'\n ? new ResizeObserver(() => updateCategoryRailState())\n : null\n resizeObserver?.observe(rail)\n return () => {\n rail.removeEventListener('scroll', updateCategoryRailState)\n resizeObserver?.disconnect()\n }\n }, [open, translatedCategories, updateCategoryRailState])\n\n const handleCreateCategory = React.useCallback(async () => {\n if (creatingCategory) return\n const trimmedName = newCategoryName.trim()\n const kind = slugifyLabel(trimmedName)\n if (!kind.length) {\n flash(\n t('customers.tags.manage.addCategoryRequired', 'Enter a category name first.'),\n 'error',\n )\n return\n }\n if (categories.some((category) => category.kind === kind)) {\n setActiveTab(kind)\n setCreateCategoryOpen(false)\n return\n }\n\n setCreatingCategory(true)\n try {\n await runGuardedMutation(\n () =>\n // optimistic-lock-exempt: tag dictionary kind-settings assignment add/remove\n apiCallOrThrow('/api/customers/dictionaries/kind-settings', {\n method: 'PATCH',\n headers: { 'content-type': 'application/json' },\n body: JSON.stringify({\n kind,\n selectionMode: newCategorySelectionMode,\n visibleInTags: true,\n sortOrder: categories.length + 1,\n }),\n }),\n {\n kind,\n selectionMode: newCategorySelectionMode,\n operation: 'createCategory',\n },\n )\n await loadData()\n setActiveTab(kind)\n setCreateCategoryOpen(false)\n setNewCategoryName('')\n setNewCategorySelectionMode('multi')\n flash(t('customers.tags.manage.createCategorySuccess', 'Category created.'), 'success')\n } catch (error) {\n const message =\n error instanceof Error\n ? error.message\n : t('customers.tags.manage.createCategoryError', 'Failed to create category.')\n flash(message, 'error')\n } finally {\n setCreatingCategory(false)\n }\n }, [categories, creatingCategory, loadData, newCategoryName, newCategorySelectionMode, runGuardedMutation, t])\n\n // --- keyboard shortcut ---\n\n React.useEffect(() => {\n if (!open) return\n const handler = (e: KeyboardEvent) => {\n if ((e.metaKey || e.ctrlKey) && e.key === 'Enter') {\n e.preventDefault()\n void handleSave()\n }\n }\n window.addEventListener('keydown', handler)\n return () => window.removeEventListener('keydown', handler)\n }, [open, handleSave])\n\n // --- render ---\n\n return (\n <Dialog\n open={open}\n onOpenChange={(nextOpen) => {\n if (!nextOpen) onClose()\n }}\n >\n <DialogContent\n className=\"flex max-h-[90vh] flex-col overflow-hidden border-border p-0 shadow-[0px_20px_48px_0px_rgba(0,0,0,0.18)] sm:max-w-[820px] sm:rounded-lg [&>[data-dialog-close]]:hidden\"\n aria-describedby={undefined}\n >\n <VisuallyHidden>\n <DialogTitle>{t('customers.tags.manage.title', 'Manage tags')}</DialogTitle>\n </VisuallyHidden>\n\n {/* Header */}\n <div className=\"flex shrink-0 items-center justify-between pb-2.5 pl-6 pr-5 pt-4\">\n <div className=\"flex flex-col gap-1\">\n <h2 className=\"text-base font-bold leading-tight text-foreground\">\n {t('customers.tags.manage.title', 'Manage tags')}\n </h2>\n <p className=\"text-xs leading-tight text-muted-foreground\">\n {t(\n 'customers.tags.manage.subtitle',\n 'Tag dictionaries for the entire tenant',\n )}\n </p>\n </div>\n <div className=\"flex items-center gap-2\">\n <Button\n type=\"button\"\n variant=\"outline\"\n size=\"sm\"\n className=\"h-auto gap-1.5 rounded-md px-3 py-2 text-xs font-semibold\"\n onClick={() => {\n setCreateCategoryOpen((current) => !current)\n setNewCategoryName('')\n setNewCategorySelectionMode('multi')\n }}\n >\n <Plus className=\"size-3.5\" />\n {t('customers.tags.manage.addCategory', 'New category')}\n </Button>\n <IconButton\n type=\"button\"\n variant=\"outline\"\n size=\"sm\"\n className=\"size-7 shrink-0 rounded-md border-border\"\n onClick={onClose}\n aria-label={t('customers.tags.manage.closeDialog', 'Close')}\n >\n <X className=\"size-3.5\" />\n </IconButton>\n </div>\n </div>\n <div className=\"h-px shrink-0 bg-border\" />\n\n {loading ? (\n <div className=\"px-7 py-12 text-center text-sm text-muted-foreground\">\n {t('customers.tags.manage.loading', 'Loading...')}\n </div>\n ) : (\n <>\n {createCategoryOpen ? (\n <div className=\"flex shrink-0 flex-wrap items-center gap-2.5 border-b border-border px-6 py-3\">\n <div className=\"min-w-[220px] flex-1 rounded-md border border-input bg-background px-3 py-2\">\n <input\n type=\"text\"\n value={newCategoryName}\n onChange={(event) => setNewCategoryName(event.target.value)}\n onKeyDown={(event) => {\n if (event.key === 'Enter') {\n event.preventDefault()\n void handleCreateCategory()\n }\n }}\n placeholder={t('customers.tags.manage.addCategoryPlaceholder', 'Category name...')}\n className=\"w-full bg-transparent text-sm text-foreground outline-none placeholder:text-muted-foreground\"\n />\n </div>\n <div className=\"flex items-center gap-1 rounded-md border border-border bg-muted/60 p-1\">\n <Button\n type=\"button\"\n variant={newCategorySelectionMode === 'single' ? 'secondary' : 'ghost'}\n size=\"sm\"\n className=\"h-auto rounded-md px-2.5 py-1.5 text-xs\"\n onClick={() => setNewCategorySelectionMode('single')}\n >\n {t('customers.tags.manage.categoryMode.single', 'Single')}\n </Button>\n <Button\n type=\"button\"\n variant={newCategorySelectionMode === 'multi' ? 'secondary' : 'ghost'}\n size=\"sm\"\n className=\"h-auto rounded-md px-2.5 py-1.5 text-xs\"\n onClick={() => setNewCategorySelectionMode('multi')}\n >\n {t('customers.tags.manage.categoryMode.multi', 'Multi')}\n </Button>\n </div>\n <Button\n type=\"button\"\n size=\"sm\"\n className=\"h-auto rounded-md px-3 py-2 text-xs font-semibold\"\n onClick={() => {\n void handleCreateCategory()\n }}\n disabled={creatingCategory}\n >\n {creatingCategory\n ? t('customers.tags.manage.creatingCategory', 'Creating...')\n : t('customers.tags.manage.createCategory', 'Create category')}\n </Button>\n </div>\n ) : null}\n\n {/* Tab bar */}\n <div className=\"flex shrink-0 items-end gap-2 border-b border-input px-4 pt-1.5\">\n <IconButton\n type=\"button\"\n variant=\"ghost\"\n size=\"sm\"\n className=\"size-8 shrink-0 self-center rounded-full\"\n onClick={() => scrollCategoryRail('left')}\n disabled={!canScrollLeft}\n aria-label={t('customers.tags.manage.scrollLeft', 'Scroll categories left')}\n >\n <ChevronsLeft className=\"size-3.5\" />\n </IconButton>\n <div\n ref={categoryRailRef}\n className=\"scrollbar-hide min-w-0 flex-1 overflow-x-auto\"\n >\n <div className=\"flex items-end gap-0.5\">\n {translatedCategories.map((category) => {\n const Icon = category.icon\n const isActive = category.kind === activeTab\n const count = entryCounts[category.kind] ?? 0\n return (\n <Button\n key={category.kind}\n type=\"button\"\n variant=\"ghost\"\n size=\"sm\"\n onClick={() => {\n setActiveTab(category.kind)\n setSearchValue('')\n }}\n className={`flex h-auto shrink-0 items-center gap-1.5 rounded-none border-b-2 px-2.5 py-2 hover:bg-transparent -mb-px ${\n isActive\n ? 'border-accent-indigo text-foreground'\n : 'border-transparent text-muted-foreground'\n }`}\n >\n <Icon className=\"size-3.5\" />\n <span\n className={`whitespace-nowrap text-xs ${isActive ? 'font-semibold' : 'font-medium'}`}\n >\n {category.shortLabel}\n </span>\n <span className=\"rounded-sm bg-muted px-1 py-px text-overline font-semibold text-foreground\">\n {count}\n </span>\n </Button>\n )\n })}\n </div>\n </div>\n <IconButton\n type=\"button\"\n variant=\"ghost\"\n size=\"sm\"\n className=\"size-8 shrink-0 self-center rounded-full\"\n onClick={() => scrollCategoryRail('right')}\n disabled={!canScrollRight}\n aria-label={t('customers.tags.manage.scrollRight', 'Scroll categories right')}\n >\n <ChevronsRight className=\"size-3.5\" />\n </IconButton>\n </div>\n\n {/* Content */}\n <ScrollArea className=\"min-h-0 flex-1\" viewportClassName=\"px-6 py-3.5\">\n <div className=\"flex flex-col gap-3\">\n {activeMeta ? (\n <>\n {/* Category header + search */}\n <div className=\"flex items-center justify-between\">\n <div className=\"flex flex-col gap-1\">\n <div className=\"flex items-center gap-1.5\">\n <span className=\"text-sm font-bold text-foreground\">\n {activeMeta.shortLabel}\n </span>\n {(activeMeta.badges ?? []).map((badge) => (\n <Badge\n key={badge}\n variant={badge === 'required' ? 'warning' : 'muted'}\n size=\"sm\"\n >\n {badge === 'required'\n ? t('customers.tags.manage.badge.required', 'REQUIRED')\n : t('customers.tags.manage.badge.system', 'SYSTEM')}\n </Badge>\n ))}\n </div>\n <div className=\"flex items-center gap-1.5\">\n <Info className=\"size-3 shrink-0 text-muted-foreground\" />\n <span className=\"text-xs text-muted-foreground\">\n {activeMeta.description}\n </span>\n </div>\n </div>\n <div className=\"relative w-[220px] shrink-0\">\n <Search className=\"absolute left-3 top-1/2 size-3.5 -translate-y-1/2 text-muted-foreground/70\" />\n <input\n type=\"text\"\n value={searchValue}\n onChange={(e) => setSearchValue(e.target.value)}\n placeholder={t('customers.tags.manage.search', 'Search values...')}\n className=\"w-full rounded-md border border-input bg-background py-2 pl-9 pr-3 text-xs text-foreground outline-none placeholder:text-muted-foreground/70 focus:border-foreground\"\n />\n </div>\n </div>\n\n {/* Column headers */}\n <div className=\"flex items-center gap-3 px-3 py-1.5\">\n <div className=\"w-[18px] shrink-0\" />\n <div className=\"min-w-0 flex-1\">\n <span className=\"text-overline font-medium uppercase tracking-wider text-muted-foreground\">\n {t('customers.tags.manage.columns.label', 'LABEL')}\n </span>\n </div>\n <div className=\"w-[140px] shrink-0\">\n <span className=\"text-overline font-medium uppercase tracking-wider text-muted-foreground\">\n {t('customers.tags.manage.columns.slug', 'SLUG')}\n </span>\n </div>\n <div className=\"w-[80px] shrink-0\">\n <span className=\"text-overline font-medium uppercase tracking-wider text-muted-foreground\">\n {t('customers.tags.manage.columns.color', 'COLOR')}\n </span>\n </div>\n <div className=\"w-[32px] shrink-0\" />\n </div>\n\n {/* Entry rows */}\n <div className=\"flex flex-col gap-2\">\n <DndContext\n sensors={sensors}\n collisionDetection={closestCenter}\n onDragEnd={handleDragEnd}\n >\n <SortableContext\n items={visibleEntries.map((e) => e.localId)}\n strategy={verticalListSortingStrategy}\n >\n {visibleEntries.map((entry, index) => (\n <SortableEntryRow\n key={entry.localId}\n entry={entry}\n index={index}\n total={visibleEntries.length}\n isDefault={index === 0 && entry.id !== null}\n onLabelChange={(value) => {\n updateDraftEntry(activeTab, entry.localId, (current) => ({\n ...current,\n label: value,\n value: current.manualValue\n ? current.value\n : slugifyLabel(value),\n }))\n }}\n onValueChange={(value) => {\n updateDraftEntry(activeTab, entry.localId, (current) => ({\n ...current,\n value,\n manualValue: true,\n }))\n }}\n onColorChange={(value) => {\n updateDraftEntry(activeTab, entry.localId, (current) => ({\n ...current,\n color: value,\n }))\n }}\n onDelete={() => handleDeleteEntry(activeTab, entry.localId)}\n onMoveUp={() => moveEntryByDelta(entry.localId, -1)}\n onMoveDown={() => moveEntryByDelta(entry.localId, 1)}\n t={t}\n />\n ))}\n </SortableContext>\n </DndContext>\n\n {visibleEntries.length === 0 && (\n <div className=\"px-4 py-8 text-center text-sm text-muted-foreground\">\n {t(\n 'customers.tags.manage.noMatches',\n 'No entries match the current search.',\n )}\n </div>\n )}\n </div>\n\n {/* Add new value */}\n <Button\n type=\"button\"\n variant=\"ghost\"\n onClick={handleAddEntry}\n className=\"flex h-auto w-full items-center justify-center gap-2 rounded-md border border-dashed border-border bg-background p-3 text-xs font-semibold text-foreground hover:bg-muted\"\n >\n <Plus className=\"size-3.5\" />\n {t('customers.tags.manage.addValue', 'Add new value')}\n </Button>\n\n {/* Info note */}\n <div className=\"flex items-start gap-2.5 rounded-md bg-muted px-3.5 py-3\">\n <Info className=\"mt-0.5 size-3.5 shrink-0 text-muted-foreground\" />\n <div className=\"flex flex-1 flex-col gap-1\">\n <span className=\"text-xs font-semibold text-foreground\">\n {activeMeta.noteTitle}\n </span>\n <span className=\"text-xs text-muted-foreground\">\n {activeMeta.noteDescription}\n </span>\n </div>\n </div>\n </>\n ) : (\n <div className=\"py-8 text-center text-sm text-muted-foreground\">\n {t('customers.tags.manage.noDictionaries', 'No tag categories found.')}\n </div>\n )}\n </div>\n </ScrollArea>\n\n {/* Separator */}\n <div className=\"h-px shrink-0 bg-border\" />\n\n {/* Footer */}\n <div className=\"flex shrink-0 items-center justify-between px-6 py-3\">\n <div className=\"flex items-center gap-1.5\">\n <Info className=\"size-3 shrink-0 text-muted-foreground\" />\n <span className=\"text-xs text-muted-foreground\">\n {t(\n 'customers.tags.manage.tenantNotice',\n 'Changes apply to the entire tenant \\u00b7 visible immediately',\n )}\n </span>\n </div>\n <div className=\"flex items-center gap-2.5\">\n <Button\n type=\"button\"\n variant=\"outline\"\n onClick={onClose}\n className=\"rounded-md border-border px-4 py-2.5 text-sm font-semibold text-foreground\"\n >\n {t('customers.tags.manage.close', 'Cancel')}\n </Button>\n <Button\n type=\"button\"\n onClick={() => {\n void handleSave()\n }}\n disabled={saving || !hasChanges}\n className=\"rounded-md bg-foreground px-4 py-2.5 text-sm font-semibold text-background hover:bg-foreground/90\"\n >\n <Save className=\"mr-2 size-4\" />\n {saving\n ? t('customers.tags.manage.saving', 'Saving...')\n : t('customers.tags.manage.save', 'Save changes')}\n </Button>\n </div>\n </div>\n </>\n )}\n </DialogContent>\n </Dialog>\n )\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
|
@@ -17,7 +17,20 @@ function TaskDialog({ open, mode, onOpenChange, initialValues, onSubmit, isSubmi
|
|
|
17
17
|
},
|
|
18
18
|
[onOpenChange, onSubmit]
|
|
19
19
|
);
|
|
20
|
-
|
|
20
|
+
const handleKeyDown = React.useCallback(
|
|
21
|
+
(event) => {
|
|
22
|
+
if ((event.metaKey || event.ctrlKey) && event.key === "Enter") {
|
|
23
|
+
event.preventDefault();
|
|
24
|
+
event.currentTarget.querySelector("form")?.requestSubmit();
|
|
25
|
+
}
|
|
26
|
+
if (event.key === "Escape") {
|
|
27
|
+
event.preventDefault();
|
|
28
|
+
handleCancel();
|
|
29
|
+
}
|
|
30
|
+
},
|
|
31
|
+
[handleCancel]
|
|
32
|
+
);
|
|
33
|
+
return /* @__PURE__ */ jsx(Dialog, { open, onOpenChange, children: /* @__PURE__ */ jsxs(DialogContent, { className: "sm:max-w-2xl", onKeyDown: handleKeyDown, children: [
|
|
21
34
|
/* @__PURE__ */ jsxs(DialogHeader, { children: [
|
|
22
35
|
/* @__PURE__ */ jsx(DialogTitle, { children: dialogTitle }),
|
|
23
36
|
contextMessage ? /* @__PURE__ */ jsx(DialogDescription, { children: contextMessage }) : null
|