@open-mercato/core 0.10.1-develop.7833.1.d6cbf0e3ab → 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/cart/api/current/lines/bulk/route.js +1 -1
- package/dist/modules/cart/api/current/lines/bulk/route.js.map +2 -2
- package/dist/modules/cart/api/current/lines/route.js +1 -1
- package/dist/modules/cart/api/current/lines/route.js.map +2 -2
- package/dist/modules/cart/commands/lines.js +5 -10
- package/dist/modules/cart/commands/lines.js.map +2 -2
- package/dist/modules/cart/lib/currentCartRoute.js +8 -0
- package/dist/modules/cart/lib/currentCartRoute.js.map +3 -3
- package/dist/modules/cart/lib/lineMutationLock.js +52 -0
- package/dist/modules/cart/lib/lineMutationLock.js.map +7 -0
- 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/sales/commands/documents.js +3 -3
- package/dist/modules/sales/commands/documents.js.map +2 -2
- package/dist/modules/sales/components/documents/ItemsSection.js +75 -21
- package/dist/modules/sales/components/documents/ItemsSection.js.map +2 -2
- package/dist/modules/sales/components/documents/lineItemUtils.js +2 -2
- package/dist/modules/sales/components/documents/lineItemUtils.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/cart/api/current/lines/bulk/route.ts +1 -1
- package/src/modules/cart/api/current/lines/route.ts +1 -1
- package/src/modules/cart/commands/lines.ts +5 -10
- package/src/modules/cart/i18n/de.json +1 -0
- package/src/modules/cart/i18n/en.json +1 -0
- package/src/modules/cart/i18n/es.json +1 -0
- package/src/modules/cart/i18n/ko.json +1 -0
- package/src/modules/cart/i18n/pl.json +1 -0
- package/src/modules/cart/lib/currentCartRoute.ts +8 -0
- package/src/modules/cart/lib/lineMutationLock.ts +59 -0
- 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/sales/commands/documents.ts +3 -3
- package/src/modules/sales/components/documents/ItemsSection.tsx +82 -23
- package/src/modules/sales/components/documents/lineItemUtils.ts +2 -2
- package/src/modules/sales/i18n/de.json +1 -0
- package/src/modules/sales/i18n/en.json +1 -0
- package/src/modules/sales/i18n/es.json +1 -0
- package/src/modules/sales/i18n/ko.json +1 -0
- package/src/modules/sales/i18n/pl.json +1 -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
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../../../../src/modules/customers/components/detail/TaskDialog.tsx"],
|
|
4
|
-
"sourcesContent": ["\"use client\"\n\nimport * as React from 'react'\nimport { Dialog, DialogContent, DialogHeader, DialogTitle, DialogDescription } from '@open-mercato/ui/primitives/dialog'\nimport { useT } from '@open-mercato/shared/lib/i18n/context'\nimport type { TaskFormPayload } from './hooks/usePersonTasks'\nimport { TaskForm } from './TaskForm'\n\nexport type TaskDialogProps = {\n open: boolean\n mode: 'create' | 'edit'\n onOpenChange: (next: boolean) => void\n initialValues?: Record<string, unknown>\n onSubmit: (payload: TaskFormPayload) => Promise<void>\n isSubmitting?: boolean\n contextMessage?: string\n /** Forwarded to TaskForm to gate the rich status picker to the canonical interactions path. */\n useCanonicalInteractions?: boolean\n}\n\nexport function TaskDialog({ open, mode, onOpenChange, initialValues, onSubmit, isSubmitting, contextMessage, useCanonicalInteractions = false }: TaskDialogProps) {\n const t = useT()\n\n const dialogTitle =\n mode === 'edit'\n ? t('customers.people.detail.tasks.dialog.editTitle', 'Edit task')\n : t('customers.people.detail.tasks.dialog.createTitle', 'Add task')\n\n const handleCancel = React.useCallback(() => {\n onOpenChange(false)\n }, [onOpenChange])\n\n const handleSubmit = React.useCallback(\n async (payload: TaskFormPayload) => {\n await onSubmit(payload)\n onOpenChange(false)\n },\n [onOpenChange, onSubmit],\n )\n\n return (\n <Dialog open={open} onOpenChange={onOpenChange}>\n <DialogContent className=\"sm:max-w-2xl\">\n <DialogHeader>\n <DialogTitle>{dialogTitle}</DialogTitle>\n {contextMessage ? <DialogDescription>{contextMessage}</DialogDescription> : null}\n </DialogHeader>\n <TaskForm\n mode={mode}\n initialValues={initialValues}\n onSubmit={handleSubmit}\n onCancel={handleCancel}\n isSubmitting={isSubmitting}\n useCanonicalInteractions={useCanonicalInteractions}\n />\n </DialogContent>\n </Dialog>\n )\n}\n"],
|
|
5
|
-
"mappings": ";
|
|
4
|
+
"sourcesContent": ["\"use client\"\n\nimport * as React from 'react'\nimport { Dialog, DialogContent, DialogHeader, DialogTitle, DialogDescription } from '@open-mercato/ui/primitives/dialog'\nimport { useT } from '@open-mercato/shared/lib/i18n/context'\nimport type { TaskFormPayload } from './hooks/usePersonTasks'\nimport { TaskForm } from './TaskForm'\n\nexport type TaskDialogProps = {\n open: boolean\n mode: 'create' | 'edit'\n onOpenChange: (next: boolean) => void\n initialValues?: Record<string, unknown>\n onSubmit: (payload: TaskFormPayload) => Promise<void>\n isSubmitting?: boolean\n contextMessage?: string\n /** Forwarded to TaskForm to gate the rich status picker to the canonical interactions path. */\n useCanonicalInteractions?: boolean\n}\n\nexport function TaskDialog({ open, mode, onOpenChange, initialValues, onSubmit, isSubmitting, contextMessage, useCanonicalInteractions = false }: TaskDialogProps) {\n const t = useT()\n\n const dialogTitle =\n mode === 'edit'\n ? t('customers.people.detail.tasks.dialog.editTitle', 'Edit task')\n : t('customers.people.detail.tasks.dialog.createTitle', 'Add task')\n\n const handleCancel = React.useCallback(() => {\n onOpenChange(false)\n }, [onOpenChange])\n\n const handleSubmit = React.useCallback(\n async (payload: TaskFormPayload) => {\n await onSubmit(payload)\n onOpenChange(false)\n },\n [onOpenChange, onSubmit],\n )\n\n const handleKeyDown = React.useCallback(\n (event: React.KeyboardEvent<HTMLDivElement>) => {\n if ((event.metaKey || event.ctrlKey) && event.key === 'Enter') {\n event.preventDefault()\n event.currentTarget.querySelector('form')?.requestSubmit()\n }\n if (event.key === 'Escape') {\n event.preventDefault()\n handleCancel()\n }\n },\n [handleCancel],\n )\n\n return (\n <Dialog open={open} onOpenChange={onOpenChange}>\n <DialogContent className=\"sm:max-w-2xl\" onKeyDown={handleKeyDown}>\n <DialogHeader>\n <DialogTitle>{dialogTitle}</DialogTitle>\n {contextMessage ? <DialogDescription>{contextMessage}</DialogDescription> : null}\n </DialogHeader>\n <TaskForm\n mode={mode}\n initialValues={initialValues}\n onSubmit={handleSubmit}\n onCancel={handleCancel}\n isSubmitting={isSubmitting}\n useCanonicalInteractions={useCanonicalInteractions}\n />\n </DialogContent>\n </Dialog>\n )\n}\n"],
|
|
5
|
+
"mappings": ";AAyDQ,SACE,KADF;AAvDR,YAAY,WAAW;AACvB,SAAS,QAAQ,eAAe,cAAc,aAAa,yBAAyB;AACpF,SAAS,YAAY;AAErB,SAAS,gBAAgB;AAclB,SAAS,WAAW,EAAE,MAAM,MAAM,cAAc,eAAe,UAAU,cAAc,gBAAgB,2BAA2B,MAAM,GAAoB;AACjK,QAAM,IAAI,KAAK;AAEf,QAAM,cACJ,SAAS,SACL,EAAE,kDAAkD,WAAW,IAC/D,EAAE,oDAAoD,UAAU;AAEtE,QAAM,eAAe,MAAM,YAAY,MAAM;AAC3C,iBAAa,KAAK;AAAA,EACpB,GAAG,CAAC,YAAY,CAAC;AAEjB,QAAM,eAAe,MAAM;AAAA,IACzB,OAAO,YAA6B;AAClC,YAAM,SAAS,OAAO;AACtB,mBAAa,KAAK;AAAA,IACpB;AAAA,IACA,CAAC,cAAc,QAAQ;AAAA,EACzB;AAEA,QAAM,gBAAgB,MAAM;AAAA,IAC1B,CAAC,UAA+C;AAC9C,WAAK,MAAM,WAAW,MAAM,YAAY,MAAM,QAAQ,SAAS;AAC7D,cAAM,eAAe;AACrB,cAAM,cAAc,cAAc,MAAM,GAAG,cAAc;AAAA,MAC3D;AACA,UAAI,MAAM,QAAQ,UAAU;AAC1B,cAAM,eAAe;AACrB,qBAAa;AAAA,MACf;AAAA,IACF;AAAA,IACA,CAAC,YAAY;AAAA,EACf;AAEA,SACE,oBAAC,UAAO,MAAY,cAClB,+BAAC,iBAAc,WAAU,gBAAe,WAAW,eACjD;AAAA,yBAAC,gBACC;AAAA,0BAAC,eAAa,uBAAY;AAAA,MACzB,iBAAiB,oBAAC,qBAAmB,0BAAe,IAAuB;AAAA,OAC9E;AAAA,IACA;AAAA,MAAC;AAAA;AAAA,QACC;AAAA,QACA;AAAA,QACA,UAAU;AAAA,QACV,UAAU;AAAA,QACV;AAAA,QACA;AAAA;AAAA,IACF;AAAA,KACF,GACF;AAEJ;",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
|
@@ -37,7 +37,6 @@ function TaskForm({
|
|
|
37
37
|
useCanonicalInteractions = false
|
|
38
38
|
}) {
|
|
39
39
|
const t = useT();
|
|
40
|
-
const containerRef = React.useRef(null);
|
|
41
40
|
const scopeVersion = useOrganizationScopeVersion();
|
|
42
41
|
const statusDictionary = useCustomerDictionary("interaction-statuses", scopeVersion);
|
|
43
42
|
const statusOptions = React.useMemo(() => {
|
|
@@ -68,20 +67,6 @@ function TaskForm({
|
|
|
68
67
|
);
|
|
69
68
|
const resolvedSubmitLabel = submitLabel ?? (mode === "edit" ? t("customers.people.detail.tasks.form.submitEdit", "Update task (\u2318/Ctrl + Enter)") : t("customers.people.detail.tasks.form.submitCreate", "Save task (\u2318/Ctrl + Enter)"));
|
|
70
69
|
const resolvedCancelLabel = cancelLabel ?? t("customers.people.detail.tasks.cancel", "Cancel");
|
|
71
|
-
const handleKeyDown = React.useCallback(
|
|
72
|
-
(event) => {
|
|
73
|
-
if ((event.metaKey || event.ctrlKey) && event.key === "Enter") {
|
|
74
|
-
event.preventDefault();
|
|
75
|
-
const form = containerRef.current?.querySelector("form");
|
|
76
|
-
form?.requestSubmit();
|
|
77
|
-
}
|
|
78
|
-
if (event.key === "Escape") {
|
|
79
|
-
event.preventDefault();
|
|
80
|
-
onCancel();
|
|
81
|
-
}
|
|
82
|
-
},
|
|
83
|
-
[onCancel]
|
|
84
|
-
);
|
|
85
70
|
const handleSubmit = React.useCallback(
|
|
86
71
|
async (values) => {
|
|
87
72
|
const payload = buildTaskSubmitPayload(values, t);
|
|
@@ -94,7 +79,7 @@ function TaskForm({
|
|
|
94
79
|
if (useCanonicalInteractions) return { status: "planned", ...initialValues ?? {} };
|
|
95
80
|
return initialValues;
|
|
96
81
|
}, [initialValues, mode, useCanonicalInteractions]);
|
|
97
|
-
return /* @__PURE__ */ jsx("div", {
|
|
82
|
+
return /* @__PURE__ */ jsx("div", { children: /* @__PURE__ */ jsx(
|
|
98
83
|
CrudForm,
|
|
99
84
|
{
|
|
100
85
|
embedded: true,
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../../../../src/modules/customers/components/detail/TaskForm.tsx"],
|
|
4
|
-
"sourcesContent": ["\"use client\"\n\nimport * as React from 'react'\nimport {\n CrudForm,\n type CrudCustomFieldRenderProps,\n type CrudField,\n type CrudFormGroup,\n} from '@open-mercato/ui/backend/CrudForm'\nimport { Button } from '@open-mercato/ui/primitives/button'\nimport {\n Select,\n SelectContent,\n SelectItem,\n SelectItemLeading,\n SelectTrigger,\n SelectTriggerLeading,\n SelectValue,\n} from '@open-mercato/ui/primitives/select'\nimport {\n renderDictionaryColor,\n renderDictionaryIcon,\n} from '@open-mercato/core/modules/dictionaries/components/dictionaryAppearance'\nimport { collectCustomFieldValues } from '@open-mercato/ui/backend/utils/customFieldValues'\nimport { createCrudFormError } from '@open-mercato/ui/backend/utils/serverErrors'\nimport { useT } from '@open-mercato/shared/lib/i18n/context'\nimport { useOrganizationScopeVersion } from '@open-mercato/shared/lib/frontend/useOrganizationScope'\nimport { CUSTOMER_INTERACTION_ENTITY_ID } from '../../lib/interactionCompatibility'\nimport type { TaskFormPayload } from './hooks/usePersonTasks'\nimport { normalizeCustomFieldSubmitValue } from './customFieldUtils'\nimport { useCustomerDictionary } from './hooks/useCustomerDictionary'\n\nexport type TaskFormProps = {\n mode: 'create' | 'edit'\n initialValues?: Record<string, unknown>\n onSubmit: (payload: TaskFormPayload) => Promise<void>\n onCancel: () => void\n submitLabel?: string\n cancelLabel?: string\n isSubmitting?: boolean\n formEntityId?: string\n /**\n * Render the rich, dictionary-backed status picker only on the canonical interactions path.\n * On the legacy path the deprecated `/api/customers/todos` bridge round-trips a task through a\n * binary done/not-done shape, so non-binary statuses cannot persist \u2014 keep the done checkbox there.\n */\n useCanonicalInteractions?: boolean\n}\n\nexport function TaskForm({\n mode,\n initialValues,\n onSubmit,\n onCancel,\n submitLabel,\n cancelLabel,\n isSubmitting = false,\n formEntityId = CUSTOMER_INTERACTION_ENTITY_ID,\n useCanonicalInteractions = false,\n}: TaskFormProps) {\n const t = useT()\n const containerRef = React.useRef<HTMLDivElement | null>(null)\n const scopeVersion = useOrganizationScopeVersion()\n const statusDictionary = useCustomerDictionary('interaction-statuses', scopeVersion)\n\n const statusOptions = React.useMemo<TaskStatusOption[]>(() => {\n const entries = statusDictionary.data?.entries ?? []\n if (entries.length > 0) {\n return entries.map((entry) => ({\n value: entry.value,\n label: entry.label,\n color: entry.color ?? null,\n icon: entry.icon ?? null,\n }))\n }\n // Fallback to the canonical seeded set until the dictionary loads (or for existing\n // tenants whose interaction-statuses dictionary has not been backfilled yet).\n return [\n { value: 'planned', label: t('customers.interactions.status.planned', 'Planned') },\n { value: 'in_progress', label: t('customers.interactions.status.in_progress', 'In progress') },\n { value: 'waiting', label: t('customers.interactions.status.waiting', 'Waiting / blocked') },\n { value: 'done', label: t('customers.interactions.status.done', 'Done') },\n { value: 'canceled', label: t('customers.interactions.status.canceled', 'Canceled') },\n ]\n }, [statusDictionary.data, t])\n\n const fields = React.useMemo<CrudField[]>(\n () => buildTaskFormFields({ useCanonicalInteractions, statusOptions, t }),\n [statusOptions, t, useCanonicalInteractions],\n )\n\n const groups = React.useMemo<CrudFormGroup[]>(\n () => buildTaskFormGroups({ useCanonicalInteractions, t }),\n [t, useCanonicalInteractions],\n )\n\n const resolvedSubmitLabel =\n submitLabel ??\n (mode === 'edit'\n ? t('customers.people.detail.tasks.form.submitEdit', 'Update task (\u2318/Ctrl + Enter)')\n : t('customers.people.detail.tasks.form.submitCreate', 'Save task (\u2318/Ctrl + Enter)'))\n const resolvedCancelLabel = cancelLabel ?? t('customers.people.detail.tasks.cancel', 'Cancel')\n\n const handleKeyDown = React.useCallback(\n (event: React.KeyboardEvent<HTMLDivElement>) => {\n if ((event.metaKey || event.ctrlKey) && event.key === 'Enter') {\n event.preventDefault()\n const form = containerRef.current?.querySelector('form')\n form?.requestSubmit()\n }\n if (event.key === 'Escape') {\n event.preventDefault()\n onCancel()\n }\n },\n [onCancel],\n )\n\n const handleSubmit = React.useCallback(\n async (values: Record<string, unknown>) => {\n const payload = buildTaskSubmitPayload(values, t)\n await onSubmit(payload)\n },\n [onSubmit, t],\n )\n\n // On create, default the canonical status select to `planned` so the dropdown is never empty.\n // The legacy path has no status field (just the done checkbox), so it needs no default.\n const resolvedInitialValues = React.useMemo<Record<string, unknown> | undefined>(() => {\n if (mode !== 'create') return initialValues\n if (useCanonicalInteractions) return { status: 'planned', ...(initialValues ?? {}) }\n return initialValues\n }, [initialValues, mode, useCanonicalInteractions])\n\n return (\n <div ref={containerRef} onKeyDown={handleKeyDown}>\n <CrudForm\n embedded\n entityId={formEntityId}\n fields={fields}\n groups={groups}\n initialValues={resolvedInitialValues}\n submitLabel={resolvedSubmitLabel}\n onSubmit={handleSubmit}\n extraActions={\n <Button type=\"button\" variant=\"outline\" onClick={onCancel} disabled={isSubmitting}>\n {resolvedCancelLabel}\n </Button>\n }\n />\n </div>\n )\n}\n\ntype TaskFormTranslator = (key: string, fallback?: string) => string\n\ntype TaskStatusOption = { value: string; label: string; color?: string | null; icon?: string | null }\n\ntype TaskStatusSelectProps = CrudCustomFieldRenderProps & {\n options: TaskStatusOption[]\n placeholder: string\n}\n\n// Dictionary-backed status picker that surfaces each status's icon + color (the dictionary\n// \"appearance\") next to its label, both in the dropdown rows and the trigger. CrudForm's generic\n// `select` renders label-only text, so the canonical status field uses this custom renderer instead.\nfunction TaskStatusSelect({ id, value, setValue, disabled, options, placeholder }: TaskStatusSelectProps) {\n const stringValue = typeof value === 'string' ? value : ''\n const activeOption = options.find((option) => option.value === stringValue) ?? null\n return (\n <Select\n value={stringValue}\n onValueChange={(next) => setValue(next || undefined)}\n disabled={disabled}\n >\n <SelectTrigger id={id} data-crud-focus-target=\"\">\n {activeOption && (activeOption.icon || activeOption.color) ? (\n <SelectTriggerLeading>\n {renderDictionaryIcon(activeOption.icon, 'h-4 w-4')}\n {renderDictionaryColor(activeOption.color, 'h-3.5 w-3.5 rounded-full')}\n </SelectTriggerLeading>\n ) : null}\n <SelectValue placeholder={placeholder}>{activeOption?.label}</SelectValue>\n </SelectTrigger>\n <SelectContent>\n {options.map((option) => (\n <SelectItem key={option.value} value={option.value}>\n {option.icon || option.color ? (\n <SelectItemLeading>\n {renderDictionaryIcon(option.icon, 'h-4 w-4')}\n {renderDictionaryColor(option.color, 'h-3.5 w-3.5 rounded-full')}\n </SelectItemLeading>\n ) : null}\n {option.label}\n </SelectItem>\n ))}\n </SelectContent>\n </Select>\n )\n}\n\nexport function buildTaskFormFields(options: {\n useCanonicalInteractions: boolean\n statusOptions: TaskStatusOption[]\n t: TaskFormTranslator\n}): CrudField[] {\n const { useCanonicalInteractions, statusOptions, t } = options\n // Canonical path: dictionary-backed rich status picker. Legacy path: the binary done checkbox,\n // because the deprecated todos bridge cannot persist non-binary statuses on read-back.\n const statusField: CrudField = useCanonicalInteractions\n ? {\n id: 'status',\n label: t('customers.people.detail.tasks.fields.status', 'Status'),\n type: 'custom',\n component: (props) => (\n <TaskStatusSelect\n {...props}\n options={statusOptions}\n placeholder={t('customers.people.detail.tasks.fields.statusPlaceholder', 'Select a status')}\n />\n ),\n }\n : {\n id: 'is_done',\n label: t('customers.people.detail.tasks.fields.done', 'Mark as done'),\n type: 'checkbox',\n }\n return [\n {\n id: 'title',\n label: t('customers.people.detail.tasks.fields.title', 'Title'),\n type: 'text',\n required: true,\n placeholder: t('customers.people.detail.tasks.fields.titlePlaceholder', 'Task title'),\n },\n {\n id: 'scheduledAt',\n label: t('customers.people.detail.tasks.fields.scheduledAt', 'Scheduled for'),\n type: 'datetime',\n placeholder: t('customers.people.detail.tasks.fields.scheduledAtPlaceholder', 'Choose a date and time'),\n },\n {\n id: 'priority',\n label: t('customers.people.detail.tasks.fields.priority', 'Priority'),\n type: 'number',\n placeholder: t('customers.people.detail.tasks.fields.priorityPlaceholder', '0-100'),\n },\n {\n id: 'description',\n label: t('customers.people.detail.tasks.fields.description', 'Description'),\n type: 'textarea',\n placeholder: t('customers.people.detail.tasks.fields.descriptionPlaceholder', 'Add context, outcome, or follow-up notes'),\n layout: 'full',\n },\n statusField,\n ]\n}\n\nexport function buildTaskFormGroups(options: {\n useCanonicalInteractions: boolean\n t: TaskFormTranslator\n}): CrudFormGroup[] {\n const { useCanonicalInteractions, t } = options\n return [\n {\n id: 'details',\n title: t('customers.people.detail.tasks.form.details', 'Task details'),\n column: 1,\n fields: ['title', 'scheduledAt', 'description'],\n },\n {\n id: 'status',\n title: t('customers.people.detail.tasks.form.status', 'Status'),\n column: 2,\n fields: ['priority', useCanonicalInteractions ? 'status' : 'is_done'],\n },\n {\n id: 'attributes',\n title: t('customers.people.detail.tasks.form.customFields', 'Task attributes'),\n column: 1,\n kind: 'customFields',\n },\n {\n id: 'tips',\n title: t('customers.people.detail.tasks.form.tips', 'Tips'),\n column: 2,\n component: () => (\n <div className=\"text-sm text-muted-foreground\">\n {t(\n 'customers.people.detail.tasks.form.tipsBody',\n 'Tasks save independently from the main customer form. Use clear titles like \"Follow up call\" or \"Send pricing deck\".',\n )}\n </div>\n ),\n },\n ]\n}\n\nexport function buildTaskSubmitPayload(values: Record<string, unknown>, t: (key: string, fallback?: string) => string): TaskFormPayload {\n const rawTitle = typeof values.title === 'string' ? values.title.trim() : ''\n if (!rawTitle.length) {\n const message = t('customers.people.detail.tasks.titleRequired', 'Task name is required.')\n throw createCrudFormError(message, { title: message })\n }\n\n const rawPriority = typeof values.priority === 'number' || typeof values.priority === 'string' ? values.priority : null\n let priority: number | null = null\n if (rawPriority !== null && rawPriority !== '') {\n const parsed = typeof rawPriority === 'number' ? rawPriority : Number(String(rawPriority).trim())\n if (!Number.isInteger(parsed) || parsed < 0 || parsed > 100) {\n const message = t('customers.people.detail.tasks.priorityInvalid', 'Enter a whole-number priority between 0 and 100.')\n throw createCrudFormError(message, { priority: message })\n }\n priority = parsed\n }\n\n const rawDescription = typeof values.description === 'string' ? values.description.trim() : ''\n const rawScheduledAt = typeof values.scheduledAt === 'string' ? values.scheduledAt.trim() : ''\n const rawStatus = typeof values.status === 'string' ? values.status.trim() : ''\n const status = rawStatus.length ? rawStatus : undefined\n const base: TaskFormPayload['base'] = { title: rawTitle }\n if (status !== undefined) {\n base.status = status\n // Keep is_done in sync so the deprecated todos bridge (which only carries a boolean)\n // stays correct; the canonical interactions path uses base.status directly.\n base.is_done = status === 'done'\n } else if (typeof values.is_done === 'boolean') {\n base.is_done = values.is_done\n }\n base.description = rawDescription || null\n base.priority = priority\n base.scheduledAt = rawScheduledAt || null\n const custom = collectCustomFieldValues(values, {\n transform: (value) => normalizeCustomFieldSubmitValue(value),\n })\n return { base, custom }\n}\n"],
|
|
5
|
-
"mappings": ";
|
|
4
|
+
"sourcesContent": ["\"use client\"\n\nimport * as React from 'react'\nimport {\n CrudForm,\n type CrudCustomFieldRenderProps,\n type CrudField,\n type CrudFormGroup,\n} from '@open-mercato/ui/backend/CrudForm'\nimport { Button } from '@open-mercato/ui/primitives/button'\nimport {\n Select,\n SelectContent,\n SelectItem,\n SelectItemLeading,\n SelectTrigger,\n SelectTriggerLeading,\n SelectValue,\n} from '@open-mercato/ui/primitives/select'\nimport {\n renderDictionaryColor,\n renderDictionaryIcon,\n} from '@open-mercato/core/modules/dictionaries/components/dictionaryAppearance'\nimport { collectCustomFieldValues } from '@open-mercato/ui/backend/utils/customFieldValues'\nimport { createCrudFormError } from '@open-mercato/ui/backend/utils/serverErrors'\nimport { useT } from '@open-mercato/shared/lib/i18n/context'\nimport { useOrganizationScopeVersion } from '@open-mercato/shared/lib/frontend/useOrganizationScope'\nimport { CUSTOMER_INTERACTION_ENTITY_ID } from '../../lib/interactionCompatibility'\nimport type { TaskFormPayload } from './hooks/usePersonTasks'\nimport { normalizeCustomFieldSubmitValue } from './customFieldUtils'\nimport { useCustomerDictionary } from './hooks/useCustomerDictionary'\n\nexport type TaskFormProps = {\n mode: 'create' | 'edit'\n initialValues?: Record<string, unknown>\n onSubmit: (payload: TaskFormPayload) => Promise<void>\n onCancel: () => void\n submitLabel?: string\n cancelLabel?: string\n isSubmitting?: boolean\n formEntityId?: string\n /**\n * Render the rich, dictionary-backed status picker only on the canonical interactions path.\n * On the legacy path the deprecated `/api/customers/todos` bridge round-trips a task through a\n * binary done/not-done shape, so non-binary statuses cannot persist \u2014 keep the done checkbox there.\n */\n useCanonicalInteractions?: boolean\n}\n\nexport function TaskForm({\n mode,\n initialValues,\n onSubmit,\n onCancel,\n submitLabel,\n cancelLabel,\n isSubmitting = false,\n formEntityId = CUSTOMER_INTERACTION_ENTITY_ID,\n useCanonicalInteractions = false,\n}: TaskFormProps) {\n const t = useT()\n const scopeVersion = useOrganizationScopeVersion()\n const statusDictionary = useCustomerDictionary('interaction-statuses', scopeVersion)\n\n const statusOptions = React.useMemo<TaskStatusOption[]>(() => {\n const entries = statusDictionary.data?.entries ?? []\n if (entries.length > 0) {\n return entries.map((entry) => ({\n value: entry.value,\n label: entry.label,\n color: entry.color ?? null,\n icon: entry.icon ?? null,\n }))\n }\n // Fallback to the canonical seeded set until the dictionary loads (or for existing\n // tenants whose interaction-statuses dictionary has not been backfilled yet).\n return [\n { value: 'planned', label: t('customers.interactions.status.planned', 'Planned') },\n { value: 'in_progress', label: t('customers.interactions.status.in_progress', 'In progress') },\n { value: 'waiting', label: t('customers.interactions.status.waiting', 'Waiting / blocked') },\n { value: 'done', label: t('customers.interactions.status.done', 'Done') },\n { value: 'canceled', label: t('customers.interactions.status.canceled', 'Canceled') },\n ]\n }, [statusDictionary.data, t])\n\n const fields = React.useMemo<CrudField[]>(\n () => buildTaskFormFields({ useCanonicalInteractions, statusOptions, t }),\n [statusOptions, t, useCanonicalInteractions],\n )\n\n const groups = React.useMemo<CrudFormGroup[]>(\n () => buildTaskFormGroups({ useCanonicalInteractions, t }),\n [t, useCanonicalInteractions],\n )\n\n const resolvedSubmitLabel =\n submitLabel ??\n (mode === 'edit'\n ? t('customers.people.detail.tasks.form.submitEdit', 'Update task (\u2318/Ctrl + Enter)')\n : t('customers.people.detail.tasks.form.submitCreate', 'Save task (\u2318/Ctrl + Enter)'))\n const resolvedCancelLabel = cancelLabel ?? t('customers.people.detail.tasks.cancel', 'Cancel')\n\n const handleSubmit = React.useCallback(\n async (values: Record<string, unknown>) => {\n const payload = buildTaskSubmitPayload(values, t)\n await onSubmit(payload)\n },\n [onSubmit, t],\n )\n\n // On create, default the canonical status select to `planned` so the dropdown is never empty.\n // The legacy path has no status field (just the done checkbox), so it needs no default.\n const resolvedInitialValues = React.useMemo<Record<string, unknown> | undefined>(() => {\n if (mode !== 'create') return initialValues\n if (useCanonicalInteractions) return { status: 'planned', ...(initialValues ?? {}) }\n return initialValues\n }, [initialValues, mode, useCanonicalInteractions])\n\n return (\n <div>\n <CrudForm\n embedded\n entityId={formEntityId}\n fields={fields}\n groups={groups}\n initialValues={resolvedInitialValues}\n submitLabel={resolvedSubmitLabel}\n onSubmit={handleSubmit}\n extraActions={\n <Button type=\"button\" variant=\"outline\" onClick={onCancel} disabled={isSubmitting}>\n {resolvedCancelLabel}\n </Button>\n }\n />\n </div>\n )\n}\n\ntype TaskFormTranslator = (key: string, fallback?: string) => string\n\ntype TaskStatusOption = { value: string; label: string; color?: string | null; icon?: string | null }\n\ntype TaskStatusSelectProps = CrudCustomFieldRenderProps & {\n options: TaskStatusOption[]\n placeholder: string\n}\n\n// Dictionary-backed status picker that surfaces each status's icon + color (the dictionary\n// \"appearance\") next to its label, both in the dropdown rows and the trigger. CrudForm's generic\n// `select` renders label-only text, so the canonical status field uses this custom renderer instead.\nfunction TaskStatusSelect({ id, value, setValue, disabled, options, placeholder }: TaskStatusSelectProps) {\n const stringValue = typeof value === 'string' ? value : ''\n const activeOption = options.find((option) => option.value === stringValue) ?? null\n return (\n <Select\n value={stringValue}\n onValueChange={(next) => setValue(next || undefined)}\n disabled={disabled}\n >\n <SelectTrigger id={id} data-crud-focus-target=\"\">\n {activeOption && (activeOption.icon || activeOption.color) ? (\n <SelectTriggerLeading>\n {renderDictionaryIcon(activeOption.icon, 'h-4 w-4')}\n {renderDictionaryColor(activeOption.color, 'h-3.5 w-3.5 rounded-full')}\n </SelectTriggerLeading>\n ) : null}\n <SelectValue placeholder={placeholder}>{activeOption?.label}</SelectValue>\n </SelectTrigger>\n <SelectContent>\n {options.map((option) => (\n <SelectItem key={option.value} value={option.value}>\n {option.icon || option.color ? (\n <SelectItemLeading>\n {renderDictionaryIcon(option.icon, 'h-4 w-4')}\n {renderDictionaryColor(option.color, 'h-3.5 w-3.5 rounded-full')}\n </SelectItemLeading>\n ) : null}\n {option.label}\n </SelectItem>\n ))}\n </SelectContent>\n </Select>\n )\n}\n\nexport function buildTaskFormFields(options: {\n useCanonicalInteractions: boolean\n statusOptions: TaskStatusOption[]\n t: TaskFormTranslator\n}): CrudField[] {\n const { useCanonicalInteractions, statusOptions, t } = options\n // Canonical path: dictionary-backed rich status picker. Legacy path: the binary done checkbox,\n // because the deprecated todos bridge cannot persist non-binary statuses on read-back.\n const statusField: CrudField = useCanonicalInteractions\n ? {\n id: 'status',\n label: t('customers.people.detail.tasks.fields.status', 'Status'),\n type: 'custom',\n component: (props) => (\n <TaskStatusSelect\n {...props}\n options={statusOptions}\n placeholder={t('customers.people.detail.tasks.fields.statusPlaceholder', 'Select a status')}\n />\n ),\n }\n : {\n id: 'is_done',\n label: t('customers.people.detail.tasks.fields.done', 'Mark as done'),\n type: 'checkbox',\n }\n return [\n {\n id: 'title',\n label: t('customers.people.detail.tasks.fields.title', 'Title'),\n type: 'text',\n required: true,\n placeholder: t('customers.people.detail.tasks.fields.titlePlaceholder', 'Task title'),\n },\n {\n id: 'scheduledAt',\n label: t('customers.people.detail.tasks.fields.scheduledAt', 'Scheduled for'),\n type: 'datetime',\n placeholder: t('customers.people.detail.tasks.fields.scheduledAtPlaceholder', 'Choose a date and time'),\n },\n {\n id: 'priority',\n label: t('customers.people.detail.tasks.fields.priority', 'Priority'),\n type: 'number',\n placeholder: t('customers.people.detail.tasks.fields.priorityPlaceholder', '0-100'),\n },\n {\n id: 'description',\n label: t('customers.people.detail.tasks.fields.description', 'Description'),\n type: 'textarea',\n placeholder: t('customers.people.detail.tasks.fields.descriptionPlaceholder', 'Add context, outcome, or follow-up notes'),\n layout: 'full',\n },\n statusField,\n ]\n}\n\nexport function buildTaskFormGroups(options: {\n useCanonicalInteractions: boolean\n t: TaskFormTranslator\n}): CrudFormGroup[] {\n const { useCanonicalInteractions, t } = options\n return [\n {\n id: 'details',\n title: t('customers.people.detail.tasks.form.details', 'Task details'),\n column: 1,\n fields: ['title', 'scheduledAt', 'description'],\n },\n {\n id: 'status',\n title: t('customers.people.detail.tasks.form.status', 'Status'),\n column: 2,\n fields: ['priority', useCanonicalInteractions ? 'status' : 'is_done'],\n },\n {\n id: 'attributes',\n title: t('customers.people.detail.tasks.form.customFields', 'Task attributes'),\n column: 1,\n kind: 'customFields',\n },\n {\n id: 'tips',\n title: t('customers.people.detail.tasks.form.tips', 'Tips'),\n column: 2,\n component: () => (\n <div className=\"text-sm text-muted-foreground\">\n {t(\n 'customers.people.detail.tasks.form.tipsBody',\n 'Tasks save independently from the main customer form. Use clear titles like \"Follow up call\" or \"Send pricing deck\".',\n )}\n </div>\n ),\n },\n ]\n}\n\nexport function buildTaskSubmitPayload(values: Record<string, unknown>, t: (key: string, fallback?: string) => string): TaskFormPayload {\n const rawTitle = typeof values.title === 'string' ? values.title.trim() : ''\n if (!rawTitle.length) {\n const message = t('customers.people.detail.tasks.titleRequired', 'Task name is required.')\n throw createCrudFormError(message, { title: message })\n }\n\n const rawPriority = typeof values.priority === 'number' || typeof values.priority === 'string' ? values.priority : null\n let priority: number | null = null\n if (rawPriority !== null && rawPriority !== '') {\n const parsed = typeof rawPriority === 'number' ? rawPriority : Number(String(rawPriority).trim())\n if (!Number.isInteger(parsed) || parsed < 0 || parsed > 100) {\n const message = t('customers.people.detail.tasks.priorityInvalid', 'Enter a whole-number priority between 0 and 100.')\n throw createCrudFormError(message, { priority: message })\n }\n priority = parsed\n }\n\n const rawDescription = typeof values.description === 'string' ? values.description.trim() : ''\n const rawScheduledAt = typeof values.scheduledAt === 'string' ? values.scheduledAt.trim() : ''\n const rawStatus = typeof values.status === 'string' ? values.status.trim() : ''\n const status = rawStatus.length ? rawStatus : undefined\n const base: TaskFormPayload['base'] = { title: rawTitle }\n if (status !== undefined) {\n base.status = status\n // Keep is_done in sync so the deprecated todos bridge (which only carries a boolean)\n // stays correct; the canonical interactions path uses base.status directly.\n base.is_done = status === 'done'\n } else if (typeof values.is_done === 'boolean') {\n base.is_done = values.is_done\n }\n base.description = rawDescription || null\n base.priority = priority\n base.scheduledAt = rawScheduledAt || null\n const custom = collectCustomFieldValues(values, {\n transform: (value) => normalizeCustomFieldSubmitValue(value),\n })\n return { base, custom }\n}\n"],
|
|
5
|
+
"mappings": ";AAiIU,cAgCA,YAhCA;AA/HV,YAAY,WAAW;AACvB;AAAA,EACE;AAAA,OAIK;AACP,SAAS,cAAc;AACvB;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OACK;AACP;AAAA,EACE;AAAA,EACA;AAAA,OACK;AACP,SAAS,gCAAgC;AACzC,SAAS,2BAA2B;AACpC,SAAS,YAAY;AACrB,SAAS,mCAAmC;AAC5C,SAAS,sCAAsC;AAE/C,SAAS,uCAAuC;AAChD,SAAS,6BAA6B;AAmB/B,SAAS,SAAS;AAAA,EACvB;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA,eAAe;AAAA,EACf,eAAe;AAAA,EACf,2BAA2B;AAC7B,GAAkB;AAChB,QAAM,IAAI,KAAK;AACf,QAAM,eAAe,4BAA4B;AACjD,QAAM,mBAAmB,sBAAsB,wBAAwB,YAAY;AAEnF,QAAM,gBAAgB,MAAM,QAA4B,MAAM;AAC5D,UAAM,UAAU,iBAAiB,MAAM,WAAW,CAAC;AACnD,QAAI,QAAQ,SAAS,GAAG;AACtB,aAAO,QAAQ,IAAI,CAAC,WAAW;AAAA,QAC7B,OAAO,MAAM;AAAA,QACb,OAAO,MAAM;AAAA,QACb,OAAO,MAAM,SAAS;AAAA,QACtB,MAAM,MAAM,QAAQ;AAAA,MACtB,EAAE;AAAA,IACJ;AAGA,WAAO;AAAA,MACL,EAAE,OAAO,WAAW,OAAO,EAAE,yCAAyC,SAAS,EAAE;AAAA,MACjF,EAAE,OAAO,eAAe,OAAO,EAAE,6CAA6C,aAAa,EAAE;AAAA,MAC7F,EAAE,OAAO,WAAW,OAAO,EAAE,yCAAyC,mBAAmB,EAAE;AAAA,MAC3F,EAAE,OAAO,QAAQ,OAAO,EAAE,sCAAsC,MAAM,EAAE;AAAA,MACxE,EAAE,OAAO,YAAY,OAAO,EAAE,0CAA0C,UAAU,EAAE;AAAA,IACtF;AAAA,EACF,GAAG,CAAC,iBAAiB,MAAM,CAAC,CAAC;AAE7B,QAAM,SAAS,MAAM;AAAA,IACnB,MAAM,oBAAoB,EAAE,0BAA0B,eAAe,EAAE,CAAC;AAAA,IACxE,CAAC,eAAe,GAAG,wBAAwB;AAAA,EAC7C;AAEA,QAAM,SAAS,MAAM;AAAA,IACnB,MAAM,oBAAoB,EAAE,0BAA0B,EAAE,CAAC;AAAA,IACzD,CAAC,GAAG,wBAAwB;AAAA,EAC9B;AAEA,QAAM,sBACJ,gBACC,SAAS,SACN,EAAE,iDAAiD,mCAA8B,IACjF,EAAE,mDAAmD,iCAA4B;AACvF,QAAM,sBAAsB,eAAe,EAAE,wCAAwC,QAAQ;AAE7F,QAAM,eAAe,MAAM;AAAA,IACzB,OAAO,WAAoC;AACzC,YAAM,UAAU,uBAAuB,QAAQ,CAAC;AAChD,YAAM,SAAS,OAAO;AAAA,IACxB;AAAA,IACA,CAAC,UAAU,CAAC;AAAA,EACd;AAIA,QAAM,wBAAwB,MAAM,QAA6C,MAAM;AACrF,QAAI,SAAS,SAAU,QAAO;AAC9B,QAAI,yBAA0B,QAAO,EAAE,QAAQ,WAAW,GAAI,iBAAiB,CAAC,EAAG;AACnF,WAAO;AAAA,EACT,GAAG,CAAC,eAAe,MAAM,wBAAwB,CAAC;AAElD,SACE,oBAAC,SACC;AAAA,IAAC;AAAA;AAAA,MACC,UAAQ;AAAA,MACR,UAAU;AAAA,MACV;AAAA,MACA;AAAA,MACA,eAAe;AAAA,MACf,aAAa;AAAA,MACb,UAAU;AAAA,MACV,cACE,oBAAC,UAAO,MAAK,UAAS,SAAQ,WAAU,SAAS,UAAU,UAAU,cAClE,+BACH;AAAA;AAAA,EAEJ,GACF;AAEJ;AAcA,SAAS,iBAAiB,EAAE,IAAI,OAAO,UAAU,UAAU,SAAS,YAAY,GAA0B;AACxG,QAAM,cAAc,OAAO,UAAU,WAAW,QAAQ;AACxD,QAAM,eAAe,QAAQ,KAAK,CAAC,WAAW,OAAO,UAAU,WAAW,KAAK;AAC/E,SACE;AAAA,IAAC;AAAA;AAAA,MACC,OAAO;AAAA,MACP,eAAe,CAAC,SAAS,SAAS,QAAQ,MAAS;AAAA,MACnD;AAAA,MAEA;AAAA,6BAAC,iBAAc,IAAQ,0BAAuB,IAC3C;AAAA,2BAAiB,aAAa,QAAQ,aAAa,SAClD,qBAAC,wBACE;AAAA,iCAAqB,aAAa,MAAM,SAAS;AAAA,YACjD,sBAAsB,aAAa,OAAO,0BAA0B;AAAA,aACvE,IACE;AAAA,UACJ,oBAAC,eAAY,aAA2B,wBAAc,OAAM;AAAA,WAC9D;AAAA,QACA,oBAAC,iBACE,kBAAQ,IAAI,CAAC,WACZ,qBAAC,cAA8B,OAAO,OAAO,OAC1C;AAAA,iBAAO,QAAQ,OAAO,QACrB,qBAAC,qBACE;AAAA,iCAAqB,OAAO,MAAM,SAAS;AAAA,YAC3C,sBAAsB,OAAO,OAAO,0BAA0B;AAAA,aACjE,IACE;AAAA,UACH,OAAO;AAAA,aAPO,OAAO,KAQxB,CACD,GACH;AAAA;AAAA;AAAA,EACF;AAEJ;AAEO,SAAS,oBAAoB,SAIpB;AACd,QAAM,EAAE,0BAA0B,eAAe,EAAE,IAAI;AAGvD,QAAM,cAAyB,2BAC3B;AAAA,IACE,IAAI;AAAA,IACJ,OAAO,EAAE,+CAA+C,QAAQ;AAAA,IAChE,MAAM;AAAA,IACN,WAAW,CAAC,UACV;AAAA,MAAC;AAAA;AAAA,QACE,GAAG;AAAA,QACJ,SAAS;AAAA,QACT,aAAa,EAAE,0DAA0D,iBAAiB;AAAA;AAAA,IAC5F;AAAA,EAEJ,IACA;AAAA,IACE,IAAI;AAAA,IACJ,OAAO,EAAE,6CAA6C,cAAc;AAAA,IACpE,MAAM;AAAA,EACR;AACJ,SAAO;AAAA,IACL;AAAA,MACE,IAAI;AAAA,MACJ,OAAO,EAAE,8CAA8C,OAAO;AAAA,MAC9D,MAAM;AAAA,MACN,UAAU;AAAA,MACV,aAAa,EAAE,yDAAyD,YAAY;AAAA,IACtF;AAAA,IACA;AAAA,MACE,IAAI;AAAA,MACJ,OAAO,EAAE,oDAAoD,eAAe;AAAA,MAC5E,MAAM;AAAA,MACN,aAAa,EAAE,+DAA+D,wBAAwB;AAAA,IACxG;AAAA,IACA;AAAA,MACE,IAAI;AAAA,MACJ,OAAO,EAAE,iDAAiD,UAAU;AAAA,MACpE,MAAM;AAAA,MACN,aAAa,EAAE,4DAA4D,OAAO;AAAA,IACpF;AAAA,IACA;AAAA,MACE,IAAI;AAAA,MACJ,OAAO,EAAE,oDAAoD,aAAa;AAAA,MAC1E,MAAM;AAAA,MACN,aAAa,EAAE,+DAA+D,0CAA0C;AAAA,MACxH,QAAQ;AAAA,IACV;AAAA,IACA;AAAA,EACF;AACF;AAEO,SAAS,oBAAoB,SAGhB;AAClB,QAAM,EAAE,0BAA0B,EAAE,IAAI;AACxC,SAAO;AAAA,IACL;AAAA,MACE,IAAI;AAAA,MACJ,OAAO,EAAE,8CAA8C,cAAc;AAAA,MACrE,QAAQ;AAAA,MACR,QAAQ,CAAC,SAAS,eAAe,aAAa;AAAA,IAChD;AAAA,IACA;AAAA,MACE,IAAI;AAAA,MACJ,OAAO,EAAE,6CAA6C,QAAQ;AAAA,MAC9D,QAAQ;AAAA,MACR,QAAQ,CAAC,YAAY,2BAA2B,WAAW,SAAS;AAAA,IACtE;AAAA,IACA;AAAA,MACE,IAAI;AAAA,MACJ,OAAO,EAAE,mDAAmD,iBAAiB;AAAA,MAC7E,QAAQ;AAAA,MACR,MAAM;AAAA,IACR;AAAA,IACA;AAAA,MACE,IAAI;AAAA,MACJ,OAAO,EAAE,2CAA2C,MAAM;AAAA,MAC1D,QAAQ;AAAA,MACR,WAAW,MACT,oBAAC,SAAI,WAAU,iCACZ;AAAA,QACC;AAAA,QACA;AAAA,MACF,GACF;AAAA,IAEJ;AAAA,EACF;AACF;AAEO,SAAS,uBAAuB,QAAiC,GAAgE;AACtI,QAAM,WAAW,OAAO,OAAO,UAAU,WAAW,OAAO,MAAM,KAAK,IAAI;AAC1E,MAAI,CAAC,SAAS,QAAQ;AACpB,UAAM,UAAU,EAAE,+CAA+C,wBAAwB;AACzF,UAAM,oBAAoB,SAAS,EAAE,OAAO,QAAQ,CAAC;AAAA,EACvD;AAEA,QAAM,cAAc,OAAO,OAAO,aAAa,YAAY,OAAO,OAAO,aAAa,WAAW,OAAO,WAAW;AACnH,MAAI,WAA0B;AAC9B,MAAI,gBAAgB,QAAQ,gBAAgB,IAAI;AAC9C,UAAM,SAAS,OAAO,gBAAgB,WAAW,cAAc,OAAO,OAAO,WAAW,EAAE,KAAK,CAAC;AAChG,QAAI,CAAC,OAAO,UAAU,MAAM,KAAK,SAAS,KAAK,SAAS,KAAK;AAC3D,YAAM,UAAU,EAAE,iDAAiD,kDAAkD;AACrH,YAAM,oBAAoB,SAAS,EAAE,UAAU,QAAQ,CAAC;AAAA,IAC1D;AACA,eAAW;AAAA,EACb;AAEA,QAAM,iBAAiB,OAAO,OAAO,gBAAgB,WAAW,OAAO,YAAY,KAAK,IAAI;AAC5F,QAAM,iBAAiB,OAAO,OAAO,gBAAgB,WAAW,OAAO,YAAY,KAAK,IAAI;AAC5F,QAAM,YAAY,OAAO,OAAO,WAAW,WAAW,OAAO,OAAO,KAAK,IAAI;AAC7E,QAAM,SAAS,UAAU,SAAS,YAAY;AAC9C,QAAM,OAAgC,EAAE,OAAO,SAAS;AACxD,MAAI,WAAW,QAAW;AACxB,SAAK,SAAS;AAGd,SAAK,UAAU,WAAW;AAAA,EAC5B,WAAW,OAAO,OAAO,YAAY,WAAW;AAC9C,SAAK,UAAU,OAAO;AAAA,EACxB;AACA,OAAK,cAAc,kBAAkB;AACrC,OAAK,WAAW;AAChB,OAAK,cAAc,kBAAkB;AACrC,QAAM,SAAS,yBAAyB,QAAQ;AAAA,IAC9C,WAAW,CAAC,UAAU,gCAAgC,KAAK;AAAA,EAC7D,CAAC;AACD,SAAO,EAAE,MAAM,OAAO;AACxB;",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
|
@@ -9,6 +9,11 @@ import { StatusBadge } from "@open-mercato/ui/primitives/status-badge";
|
|
|
9
9
|
import { mapDictionaryColorToTone } from "@open-mercato/shared/lib/query/advanced-filter";
|
|
10
10
|
import { useOrganizationScopeVersion } from "@open-mercato/shared/lib/frontend/useOrganizationScope";
|
|
11
11
|
import { flash } from "@open-mercato/ui/backend/FlashMessages";
|
|
12
|
+
import { dismissRecordConflict } from "@open-mercato/ui/backend/conflicts";
|
|
13
|
+
import {
|
|
14
|
+
extractOptimisticLockConflict,
|
|
15
|
+
extractRecordLockConflict
|
|
16
|
+
} from "@open-mercato/ui/backend/utils/optimisticLock";
|
|
12
17
|
import { useConfirmDialog } from "@open-mercato/ui/backend/confirm-dialog";
|
|
13
18
|
import { LoadingMessage, TabEmptyState } from "@open-mercato/ui/backend/detail";
|
|
14
19
|
import { cn } from "@open-mercato/shared/lib/utils";
|
|
@@ -23,6 +28,9 @@ import { useCustomerDictionary } from "./hooks/useCustomerDictionary.js";
|
|
|
23
28
|
import { useInteractions } from "./hooks/useInteractions.js";
|
|
24
29
|
import { mapInteractionRecordToTodoSummary } from "../../lib/interactionCompatibility.js";
|
|
25
30
|
const RESERVED_TASK_CUSTOM_KEYS = /* @__PURE__ */ new Set(["priority", "description", "due_at", "dueAt"]);
|
|
31
|
+
function isRecordConflict(error) {
|
|
32
|
+
return Boolean(extractOptimisticLockConflict(error) || extractRecordLockConflict(error));
|
|
33
|
+
}
|
|
26
34
|
function toTimestamp(value) {
|
|
27
35
|
if (!value) return null;
|
|
28
36
|
const timestamp = new Date(value).getTime();
|
|
@@ -189,6 +197,12 @@ function TasksSection({
|
|
|
189
197
|
setDialogOpen(false);
|
|
190
198
|
setEditingTask(null);
|
|
191
199
|
}, []);
|
|
200
|
+
React.useEffect(() => {
|
|
201
|
+
if (!dialogOpen || dialogMode !== "edit" || !editingTask) return;
|
|
202
|
+
const refreshedTask = tasks.find((task) => task.id === editingTask.id);
|
|
203
|
+
if (!refreshedTask || refreshedTask === editingTask) return;
|
|
204
|
+
setEditingTask(refreshedTask);
|
|
205
|
+
}, [dialogMode, dialogOpen, editingTask, tasks]);
|
|
192
206
|
React.useEffect(() => {
|
|
193
207
|
if (!onActionChange) return;
|
|
194
208
|
if (!entityId) {
|
|
@@ -241,8 +255,10 @@ function TasksSection({
|
|
|
241
255
|
flash(t("customers.people.detail.tasks.createSuccess", "Task created"), "success");
|
|
242
256
|
await Promise.resolve(onDataRefresh?.());
|
|
243
257
|
} catch (err) {
|
|
244
|
-
|
|
245
|
-
|
|
258
|
+
if (!isRecordConflict(err)) {
|
|
259
|
+
const message = err instanceof Error ? err.message : t("customers.people.detail.tasks.error", "Failed to create task");
|
|
260
|
+
flash(message, "error");
|
|
261
|
+
}
|
|
246
262
|
throw err;
|
|
247
263
|
}
|
|
248
264
|
},
|
|
@@ -260,11 +276,14 @@ function TasksSection({
|
|
|
260
276
|
isDone: payload.base.is_done ?? void 0
|
|
261
277
|
}
|
|
262
278
|
);
|
|
279
|
+
dismissRecordConflict();
|
|
263
280
|
flash(t("customers.people.detail.tasks.updateSuccess", "Task updated"), "success");
|
|
264
281
|
await Promise.resolve(onDataRefresh?.());
|
|
265
282
|
} catch (err) {
|
|
266
|
-
|
|
267
|
-
|
|
283
|
+
if (!isRecordConflict(err)) {
|
|
284
|
+
const message = err instanceof Error ? err.message : t("customers.people.detail.tasks.updateError", "Failed to update task");
|
|
285
|
+
flash(message, "error");
|
|
286
|
+
}
|
|
268
287
|
throw err;
|
|
269
288
|
}
|
|
270
289
|
},
|
|
@@ -281,14 +300,17 @@ function TasksSection({
|
|
|
281
300
|
isDone: nextIsDone
|
|
282
301
|
}
|
|
283
302
|
);
|
|
303
|
+
dismissRecordConflict();
|
|
284
304
|
flash(
|
|
285
305
|
nextIsDone ? t("customers.people.detail.tasks.completeSuccess", "Task marked as done") : t("customers.people.detail.tasks.reopenSuccess", "Task reopened"),
|
|
286
306
|
"success"
|
|
287
307
|
);
|
|
288
308
|
await Promise.resolve(onDataRefresh?.());
|
|
289
309
|
} catch (err) {
|
|
290
|
-
|
|
291
|
-
|
|
310
|
+
if (!isRecordConflict(err)) {
|
|
311
|
+
const message = err instanceof Error ? err.message : t("customers.people.detail.tasks.toggleError", "Failed to update task status");
|
|
312
|
+
flash(message, "error");
|
|
313
|
+
}
|
|
292
314
|
}
|
|
293
315
|
},
|
|
294
316
|
[onDataRefresh, runWriteMutation, t, toggleTask]
|
|
@@ -313,12 +335,15 @@ function TasksSection({
|
|
|
313
335
|
todoId: task.todoId
|
|
314
336
|
}
|
|
315
337
|
);
|
|
338
|
+
dismissRecordConflict();
|
|
316
339
|
flash(t("customers.people.detail.tasks.deleteSuccess", "Task removed"), "success");
|
|
317
340
|
await Promise.resolve(onDataRefresh?.());
|
|
318
341
|
await refresh();
|
|
319
342
|
} catch (err) {
|
|
320
|
-
|
|
321
|
-
|
|
343
|
+
if (!isRecordConflict(err)) {
|
|
344
|
+
const message = err instanceof Error ? err.message : t("customers.people.detail.tasks.deleteError", "Failed to remove task");
|
|
345
|
+
flash(message, "error");
|
|
346
|
+
}
|
|
322
347
|
}
|
|
323
348
|
},
|
|
324
349
|
[confirm, onDataRefresh, refresh, runWriteMutation, t, unlinkTask]
|
|
@@ -331,11 +356,14 @@ function TasksSection({
|
|
|
331
356
|
() => canonicalResult.cancelInteraction(task.todoId),
|
|
332
357
|
{ id: task.todoId }
|
|
333
358
|
);
|
|
359
|
+
dismissRecordConflict();
|
|
334
360
|
flash(t("customers.people.detail.tasks.cancelSuccess", "Task canceled"), "success");
|
|
335
361
|
await Promise.resolve(onDataRefresh?.());
|
|
336
362
|
} catch (err) {
|
|
337
|
-
|
|
338
|
-
|
|
363
|
+
if (!isRecordConflict(err)) {
|
|
364
|
+
const message = err instanceof Error ? err.message : t("customers.people.detail.tasks.cancelError", "Failed to cancel task");
|
|
365
|
+
flash(message, "error");
|
|
366
|
+
}
|
|
339
367
|
}
|
|
340
368
|
},
|
|
341
369
|
[canonicalResult, onDataRefresh, runWriteMutation, t, useCanonicalInteractions]
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../../../../src/modules/customers/components/detail/TasksSection.tsx"],
|
|
4
|
-
"sourcesContent": ["\"use client\"\n\nimport * as React from 'react'\nimport Link from 'next/link'\nimport { Loader2, Pencil, Trash2 } from 'lucide-react'\nimport { Button } from '@open-mercato/ui/primitives/button'\nimport { IconButton } from '@open-mercato/ui/primitives/icon-button'\nimport { StatusBadge, type StatusBadgeVariant } from '@open-mercato/ui/primitives/status-badge'\nimport { mapDictionaryColorToTone } from '@open-mercato/shared/lib/query/advanced-filter'\nimport { useOrganizationScopeVersion } from '@open-mercato/shared/lib/frontend/useOrganizationScope'\nimport { flash } from '@open-mercato/ui/backend/FlashMessages'\nimport { useConfirmDialog } from '@open-mercato/ui/backend/confirm-dialog'\nimport { LoadingMessage, TabEmptyState } from '@open-mercato/ui/backend/detail'\nimport { cn } from '@open-mercato/shared/lib/utils'\nimport { useT } from '@open-mercato/shared/lib/i18n/context'\nimport type { InteractionSummary, SectionAction, TabEmptyStateConfig, TodoLinkSummary, Translator } from './types'\nimport { createTranslatorWithFallback } from '@open-mercato/shared/lib/i18n/translate'\nimport { formatDate, resolveTodoHref } from './utils'\nimport { formatDateTime } from '@open-mercato/shared/lib/time'\nimport { TimelineItemHeader } from './TimelineItemHeader'\nimport { TaskDialog } from './TaskDialog'\nimport { usePersonTasks, type TaskFormPayload } from './hooks/usePersonTasks'\nimport { useCustomerDictionary } from './hooks/useCustomerDictionary'\nimport { useInteractions, type InteractionCreatePayload } from './hooks/useInteractions'\nimport { mapInteractionRecordToTodoSummary } from '../../lib/interactionCompatibility'\n\ntype GuardedMutationRunner = <T>(\n operation: () => Promise<T>,\n mutationPayload?: Record<string, unknown>,\n) => Promise<T>\n\ntype TasksSectionProps = {\n entityId: string | null\n initialTasks: TodoLinkSummary[]\n emptyLabel: string\n addActionLabel: string\n emptyState: TabEmptyStateConfig\n onActionChange?: (action: SectionAction | null) => void\n onLoadingChange?: (isLoading: boolean) => void\n onDataRefresh?: () => void\n translator?: Translator\n entityName?: string | null\n dialogContextKey?: string\n dialogContextFallback?: string\n /** When true, use the canonical interactions API instead of the legacy todos API. */\n useCanonicalInteractions?: boolean\n runGuardedMutation?: GuardedMutationRunner\n}\n\nconst RESERVED_TASK_CUSTOM_KEYS = new Set(['priority', 'description', 'due_at', 'dueAt'])\n\nfunction toTimestamp(value: string | null | undefined): number | null {\n if (!value) return null\n const timestamp = new Date(value).getTime()\n return Number.isNaN(timestamp) ? null : timestamp\n}\n\nfunction sortTaskSummaries(tasks: TodoLinkSummary[]): TodoLinkSummary[] {\n return [...tasks].sort((left, right) => {\n const leftDue = toTimestamp(left.dueAt)\n const rightDue = toTimestamp(right.dueAt)\n if (leftDue !== null || rightDue !== null) {\n if (leftDue === null) return 1\n if (rightDue === null) return -1\n if (leftDue !== rightDue) return leftDue - rightDue\n }\n const leftCreated = toTimestamp(left.createdAt) ?? 0\n const rightCreated = toTimestamp(right.createdAt) ?? 0\n if (leftCreated !== rightCreated) return rightCreated - leftCreated\n return left.id.localeCompare(right.id)\n })\n}\n\nconst STATUS_BADGE_VARIANTS = new Set<StatusBadgeVariant>(['success', 'warning', 'error', 'info', 'neutral'])\n\nfunction coerceStatusBadgeVariant(tone: ReturnType<typeof mapDictionaryColorToTone>): StatusBadgeVariant {\n return tone && STATUS_BADGE_VARIANTS.has(tone as StatusBadgeVariant) ? (tone as StatusBadgeVariant) : 'neutral'\n}\n\nfunction buildInitialFormValues(task: TodoLinkSummary | null): Record<string, unknown> | undefined {\n if (!task) return undefined\n const values: Record<string, unknown> = {\n title: task.title ?? '',\n is_done: task.isDone ?? false,\n status: task.status ?? 'planned',\n description: task.description ?? '',\n priority: task.priority ?? '',\n scheduledAt: task.dueAt ?? '',\n }\n if (task.customValues) {\n for (const [key, value] of Object.entries(task.customValues)) {\n if (RESERVED_TASK_CUSTOM_KEYS.has(key)) continue\n const formKey = `cf_${key}`\n if (values[formKey] === undefined) values[formKey] = value\n }\n }\n return values\n}\n\nexport function TasksSection({\n entityId,\n initialTasks,\n emptyLabel,\n addActionLabel,\n emptyState,\n onActionChange,\n onLoadingChange,\n onDataRefresh,\n translator,\n entityName,\n dialogContextKey,\n dialogContextFallback,\n useCanonicalInteractions = false,\n runGuardedMutation,\n}: TasksSectionProps) {\n const tHook = useT()\n const fallbackTranslator = React.useMemo<Translator>(() => createTranslatorWithFallback(tHook), [tHook])\n const t: Translator = React.useMemo(() => translator ?? fallbackTranslator, [translator, fallbackTranslator])\n const { confirm, ConfirmDialogElement } = useConfirmDialog()\n const runWriteMutation = React.useCallback(\n async <T,>(operation: () => Promise<T>, mutationPayload?: Record<string, unknown>): Promise<T> => {\n if (!runGuardedMutation) {\n return operation()\n }\n return runGuardedMutation(operation, mutationPayload)\n },\n [runGuardedMutation],\n )\n\n const scopeVersion = useOrganizationScopeVersion()\n const statusDictionary = useCustomerDictionary('interaction-statuses', scopeVersion)\n const statusMap = statusDictionary.data?.map ?? null\n\n // Legacy path: usePersonTasks (default)\n const legacyResult = usePersonTasks({ entityId, initialTasks })\n\n // Canonical path: useInteractions with planned-status filter\n const canonicalResult = useInteractions({\n entityId: useCanonicalInteractions ? entityId : null,\n typeFilter: 'task',\n })\n\n // Map canonical interactions to the TodoLinkSummary shape used by the rendering below\n const canonicalTasks = React.useMemo<TodoLinkSummary[]>(\n () => (useCanonicalInteractions ? canonicalResult.interactions.map(mapInteractionRecordToTodoSummary) : []),\n [useCanonicalInteractions, canonicalResult.interactions],\n )\n\n const canonicalCreateTask = React.useCallback(\n async (payload: TaskFormPayload) => {\n if (!entityId) throw new Error('Task creation requires an entity id')\n const interactionPayload: InteractionCreatePayload = {\n entityId,\n interactionType: 'task',\n title: payload.base.title,\n status: payload.base.status ?? (payload.base.is_done ? 'done' : 'planned'),\n priority: payload.base.priority ?? null,\n body: payload.base.description ?? null,\n scheduledAt: payload.base.scheduledAt ?? null,\n customValues: payload.custom,\n }\n await canonicalResult.createInteraction(interactionPayload)\n },\n [canonicalResult, entityId],\n )\n\n const canonicalUpdateTask = React.useCallback(\n async (task: TodoLinkSummary, payload: TaskFormPayload) => {\n await canonicalResult.updateInteraction(task.todoId, {\n title: payload.base.title,\n status: payload.base.status ?? (payload.base.is_done ? 'done' : 'planned'),\n priority: payload.base.priority ?? null,\n body: payload.base.description ?? null,\n scheduledAt: payload.base.scheduledAt ?? null,\n customValues: payload.custom,\n })\n },\n [canonicalResult],\n )\n\n const canonicalToggleTask = React.useCallback(\n async (task: TodoLinkSummary, nextIsDone: boolean) => {\n if (nextIsDone) {\n await canonicalResult.completeInteraction(task.todoId)\n } else {\n // Reopen: set status back to planned via update\n await canonicalResult.updateInteraction(task.todoId, { status: 'planned' })\n }\n },\n [canonicalResult],\n )\n\n const canonicalUnlinkTask = React.useCallback(\n async (task: TodoLinkSummary) => {\n await canonicalResult.deleteInteraction(task.todoId)\n },\n [canonicalResult],\n )\n\n // Unified interface: pick the active data source based on the flag\n const tasks = useCanonicalInteractions ? canonicalTasks : legacyResult.tasks\n const isInitialLoading = useCanonicalInteractions ? canonicalResult.isInitialLoading : legacyResult.isInitialLoading\n const isLoadingMore = useCanonicalInteractions ? canonicalResult.isLoadingMore : legacyResult.isLoadingMore\n const isMutating = useCanonicalInteractions ? canonicalResult.isMutating : legacyResult.isMutating\n const hasMore = useCanonicalInteractions ? canonicalResult.hasMore : legacyResult.hasMore\n const loadMore = useCanonicalInteractions ? canonicalResult.loadMore : legacyResult.loadMore\n const refresh = useCanonicalInteractions ? canonicalResult.refresh : legacyResult.refresh\n const createTask = useCanonicalInteractions ? canonicalCreateTask : legacyResult.createTask\n const updateTask = useCanonicalInteractions ? canonicalUpdateTask : legacyResult.updateTask\n const toggleTask = useCanonicalInteractions ? canonicalToggleTask : legacyResult.toggleTask\n const unlinkTask = useCanonicalInteractions ? canonicalUnlinkTask : legacyResult.unlinkTask\n const pendingTaskId = useCanonicalInteractions ? canonicalResult.pendingId : legacyResult.pendingTaskId\n const error = useCanonicalInteractions ? canonicalResult.error : legacyResult.error\n const sortedTasks = React.useMemo(() => sortTaskSummaries(tasks), [tasks])\n\n const [dialogOpen, setDialogOpen] = React.useState(false)\n const [dialogMode, setDialogMode] = React.useState<'create' | 'edit'>('create')\n const [editingTask, setEditingTask] = React.useState<TodoLinkSummary | null>(null)\n const sentinelRef = React.useRef<HTMLDivElement | null>(null)\n\n const dialogContextMessage = React.useMemo(() => {\n if (!dialogContextKey || !entityName) return undefined\n return t(dialogContextKey, dialogContextFallback ?? 'This task will be linked to {{name}}', { name: entityName })\n }, [dialogContextFallback, dialogContextKey, entityName, t])\n\n const openCreateDialog = React.useCallback(() => {\n setEditingTask(null)\n setDialogMode('create')\n setDialogOpen(true)\n }, [])\n\n const openEditDialog = React.useCallback((task: TodoLinkSummary) => {\n setEditingTask(task)\n setDialogMode('edit')\n setDialogOpen(true)\n }, [])\n\n const closeDialog = React.useCallback(() => {\n setDialogOpen(false)\n setEditingTask(null)\n }, [])\n\n React.useEffect(() => {\n if (!onActionChange) return\n if (!entityId) {\n onActionChange(null)\n return\n }\n onActionChange({\n label: addActionLabel,\n onClick: openCreateDialog,\n disabled: isMutating,\n })\n return () => {\n onActionChange(null)\n }\n }, [addActionLabel, entityId, isMutating, onActionChange, openCreateDialog])\n\n React.useEffect(() => {\n if (!onLoadingChange) return\n onLoadingChange(isInitialLoading || isMutating)\n }, [isInitialLoading, isMutating, onLoadingChange])\n\n React.useEffect(() => {\n if (!hasMore) return\n if (typeof IntersectionObserver === 'undefined') return\n const el = sentinelRef.current\n if (!el) return\n\n const observer = new IntersectionObserver(\n (entries) => {\n if (entries.some((entry) => entry.isIntersecting)) {\n loadMore().catch(() => {})\n }\n },\n { rootMargin: '200px 0px 200px 0px' },\n )\n observer.observe(el)\n return () => {\n observer.disconnect()\n }\n }, [hasMore, loadMore])\n\n const handleCreate = React.useCallback(\n async (payload: TaskFormPayload) => {\n try {\n await runWriteMutation(\n () => createTask(payload),\n {\n entityId,\n title: payload.base.title,\n isDone: payload.base.is_done ?? undefined,\n },\n )\n flash(t('customers.people.detail.tasks.createSuccess', 'Task created'), 'success')\n await Promise.resolve(onDataRefresh?.())\n } catch (err) {\n const message = err instanceof Error ? err.message : t('customers.people.detail.tasks.error', 'Failed to create task')\n flash(message, 'error')\n throw err\n }\n },\n [createTask, entityId, onDataRefresh, runWriteMutation, t],\n )\n\n const handleUpdate = React.useCallback(\n async (task: TodoLinkSummary, payload: TaskFormPayload) => {\n try {\n await runWriteMutation(\n () => updateTask(task, payload),\n {\n id: task.id,\n todoId: task.todoId,\n title: payload.base.title,\n isDone: payload.base.is_done ?? undefined,\n },\n )\n flash(t('customers.people.detail.tasks.updateSuccess', 'Task updated'), 'success')\n await Promise.resolve(onDataRefresh?.())\n } catch (err) {\n const message =\n err instanceof Error ? err.message : t('customers.people.detail.tasks.updateError', 'Failed to update task')\n flash(message, 'error')\n throw err\n }\n },\n [onDataRefresh, runWriteMutation, t, updateTask],\n )\n\n const handleToggle = React.useCallback(\n async (task: TodoLinkSummary, nextIsDone: boolean) => {\n try {\n await runWriteMutation(\n () => toggleTask(task, nextIsDone),\n {\n id: task.id,\n todoId: task.todoId,\n isDone: nextIsDone,\n },\n )\n flash(\n nextIsDone\n ? t('customers.people.detail.tasks.completeSuccess', 'Task marked as done')\n : t('customers.people.detail.tasks.reopenSuccess', 'Task reopened'),\n 'success',\n )\n await Promise.resolve(onDataRefresh?.())\n } catch (err) {\n const message =\n err instanceof Error ? err.message : t('customers.people.detail.tasks.toggleError', 'Failed to update task status')\n flash(message, 'error')\n }\n },\n [onDataRefresh, runWriteMutation, t, toggleTask],\n )\n\n const handleDelete = React.useCallback(\n async (task: TodoLinkSummary) => {\n const approved = await confirm({\n title: t('customers.people.detail.tasks.deleteConfirmTitle', 'Remove task?'),\n description: t(\n 'customers.people.detail.tasks.deleteConfirmDescription',\n 'This task will be removed from every view it appears in. This action cannot be undone.',\n ),\n confirmText: t('customers.people.detail.tasks.deleteConfirmAction', 'Remove task'),\n variant: 'destructive',\n })\n if (!approved) return\n try {\n await runWriteMutation(\n () => unlinkTask(task),\n {\n id: task.id,\n todoId: task.todoId,\n },\n )\n flash(t('customers.people.detail.tasks.deleteSuccess', 'Task removed'), 'success')\n await Promise.resolve(onDataRefresh?.())\n await refresh()\n } catch (err) {\n const message =\n err instanceof Error ? err.message : t('customers.people.detail.tasks.deleteError', 'Failed to remove task')\n flash(message, 'error')\n }\n },\n [confirm, onDataRefresh, refresh, runWriteMutation, t, unlinkTask],\n )\n\n const handleCancel = React.useCallback(\n async (task: TodoLinkSummary) => {\n if (!useCanonicalInteractions) return\n try {\n await runWriteMutation(\n () => canonicalResult.cancelInteraction(task.todoId),\n { id: task.todoId },\n )\n flash(t('customers.people.detail.tasks.cancelSuccess', 'Task canceled'), 'success')\n await Promise.resolve(onDataRefresh?.())\n } catch (err) {\n const message =\n err instanceof Error ? err.message : t('customers.people.detail.tasks.cancelError', 'Failed to cancel task')\n flash(message, 'error')\n }\n },\n [canonicalResult, onDataRefresh, runWriteMutation, t, useCanonicalInteractions],\n )\n\n const renderTaskMeta = React.useCallback(\n (task: TodoLinkSummary) => {\n const meta: string[] = []\n if (task.status === 'canceled') {\n meta.push(t('customers.people.detail.tasks.status.canceled', 'Canceled'))\n }\n if (typeof task.priority === 'number') {\n meta.push(t('customers.people.detail.tasks.priorityLabel', 'Priority {{priority}}', { priority: task.priority }))\n }\n if (task.severity) {\n meta.push(\n t(\n `customers.people.detail.tasks.severity.${task.severity}`,\n task.severity.charAt(0).toUpperCase() + task.severity.slice(1),\n ),\n )\n }\n if (task.dueAt) {\n const dueLabel =\n formatDate(task.dueAt) ??\n formatDateTime(task.dueAt) ??\n t('customers.people.detail.tasks.dueLabel', 'Due {{date}}', { date: task.dueAt })\n meta.push(t('customers.people.detail.tasks.dueLabel', 'Due {{date}}', { date: dueLabel }))\n }\n return meta\n },\n [t],\n )\n\n const handleDialogSubmit = React.useCallback(\n async (payload: TaskFormPayload) => {\n if (dialogMode === 'edit' && editingTask) {\n await handleUpdate(editingTask, payload)\n } else {\n await handleCreate(payload)\n }\n },\n [dialogMode, editingTask, handleCreate, handleUpdate],\n )\n\n const hasTasks = sortedTasks.length > 0\n\n return (\n <div className=\"mt-0 space-y-6\">\n <div className=\"space-y-4\">\n {isInitialLoading ? (\n <LoadingMessage\n label={t('customers.people.detail.tasks.loading', 'Loading tasks\u2026')}\n className=\"border-0 bg-transparent p-0 py-8 justify-center\"\n />\n ) : null}\n\n {!isInitialLoading && !hasTasks ? (\n <TabEmptyState title={emptyState.title}>\n <Button asChild variant=\"outline\" size=\"sm\" className=\"mt-2\">\n <Link href=\"/backend/customer-tasks\">\n {t('customers.people.detail.tasks.viewAll', 'View all tasks')}\n </Link>\n </Button>\n </TabEmptyState>\n ) : null}\n\n {!isInitialLoading && hasTasks ? (\n <div className=\"space-y-4\">\n {error ? (\n <div className=\"rounded-md border border-destructive/40 bg-destructive/5 px-3 py-2 text-sm text-destructive\">\n {error}\n </div>\n ) : null}\n {sortedTasks.map((task) => {\n const todoHref = task.externalHref ?? resolveTodoHref(task.todoSource, task.todoId)\n const createdLabel = formatDateTime(task.createdAt) ?? emptyLabel\n const meta = renderTaskMeta(task)\n const title = task.title ?? t('customers.people.detail.tasks.untitled', 'Untitled task')\n const isDone = task.isDone === true\n const isCanceled = task.status === 'canceled'\n const checkboxId = `person-task-${task.id}`\n const isPendingToggle = pendingTaskId === task.todoId\n return (\n <article key={task.id} className=\"group space-y-3 rounded-lg border bg-card p-4 transition hover:border-border/70\">\n <div className=\"flex flex-wrap items-start justify-between gap-3\">\n <TimelineItemHeader\n title={\n <span className=\"inline-flex items-center gap-2\">\n <input\n id={checkboxId}\n type=\"checkbox\"\n checked={isDone}\n onChange={(event) => {\n const next = event.target.checked\n void handleToggle(task, next)\n }}\n disabled={isMutating || isPendingToggle || isCanceled}\n className=\"h-4 w-4 rounded border\"\n />\n <span\n className={cn(\n 'text-sm font-semibold',\n isDone ? 'line-through text-muted-foreground' : undefined,\n isCanceled ? 'text-muted-foreground' : undefined,\n )}\n >\n {title}\n </span>\n {task.status ? (\n <StatusBadge\n variant={coerceStatusBadgeVariant(\n mapDictionaryColorToTone(statusMap?.[task.status]?.color ?? null),\n )}\n dot\n >\n {statusMap?.[task.status]?.label ?? task.status}\n </StatusBadge>\n ) : null}\n </span>\n }\n timestamp={task.createdAt}\n fallbackTimestampLabel={createdLabel}\n />\n <div className=\"flex items-center gap-1 opacity-0 transition-opacity group-hover:opacity-100 focus-within:opacity-100\">\n <IconButton\n type=\"button\"\n variant=\"ghost\"\n size=\"sm\"\n onClick={() => openEditDialog(task)}\n disabled={isMutating}\n aria-label={t('ui.actions.edit', 'Edit')}\n >\n {isMutating && editingTask?.id === task.id && dialogMode === 'edit' ? (\n <Loader2 className=\"h-4 w-4 animate-spin\" />\n ) : (\n <Pencil className=\"h-4 w-4\" />\n )}\n </IconButton>\n <IconButton\n type=\"button\"\n variant=\"ghost\"\n size=\"sm\"\n onClick={() => handleDelete(task)}\n disabled={isMutating}\n aria-label={t('ui.actions.delete', 'Delete')}\n >\n {isMutating ? <Loader2 className=\"h-4 w-4 animate-spin text-destructive\" /> : <Trash2 className=\"h-4 w-4\" />}\n </IconButton>\n </div>\n </div>\n {meta.length ? (\n <div className=\"flex flex-wrap items-center gap-3 text-xs text-muted-foreground\">\n {meta.map((entry) => (\n <span key={`${task.id}-${entry}`} className=\"rounded bg-muted px-2 py-1 text-xs font-medium text-muted-foreground\">\n {entry}\n </span>\n ))}\n </div>\n ) : null}\n {task.description ? (\n <p className=\"text-sm text-muted-foreground whitespace-pre-wrap\">{task.description}</p>\n ) : null}\n <div className=\"flex flex-wrap items-center gap-3 text-xs\">\n {useCanonicalInteractions && !isDone && !isCanceled ? (\n <Button\n type=\"button\"\n variant=\"ghost\"\n size=\"sm\"\n className=\"h-auto px-0 text-xs font-medium text-muted-foreground hover:bg-transparent hover:text-foreground\"\n onClick={() => void handleCancel(task)}\n disabled={isMutating || isPendingToggle}\n >\n {t('customers.people.detail.tasks.cancelAction', 'Cancel task')}\n </Button>\n ) : null}\n {todoHref ? (\n <Link href={todoHref} className=\"text-primary hover:underline\">\n {t('customers.people.detail.tasks.openTask', 'Open task')}\n </Link>\n ) : null}\n </div>\n </article>\n )\n })}\n <div ref={sentinelRef} />\n {hasMore ? (\n <div className=\"flex justify-center\">\n <Button type=\"button\" variant=\"outline\" size=\"sm\" onClick={() => loadMore().catch(() => {})} disabled={isLoadingMore}>\n {isLoadingMore ? (\n <>\n <Loader2 className=\"mr-2 h-4 w-4 animate-spin\" />\n {t('customers.people.detail.tasks.loadingMore', 'Loading\u2026')}\n </>\n ) : (\n t('customers.people.detail.tasks.loadMore', 'Load more')\n )}\n </Button>\n </div>\n ) : null}\n {isLoadingMore ? (\n <div className=\"flex justify-center text-xs text-muted-foreground\">\n <Loader2 className=\"mr-2 h-3 w-3 animate-spin\" />\n {t('customers.people.detail.tasks.loadingMore', 'Loading\u2026')}\n </div>\n ) : null}\n </div>\n ) : null}\n {!isInitialLoading && hasTasks ? (\n <div className=\"flex justify-center\">\n <Button asChild variant=\"outline\" size=\"sm\">\n <Link href=\"/backend/customer-tasks\">\n {t('customers.people.detail.tasks.viewAll', 'View all tasks')}\n </Link>\n </Button>\n </div>\n ) : null}\n </div>\n\n <TaskDialog\n open={dialogOpen}\n mode={dialogMode}\n onOpenChange={(next) => {\n if (!next) closeDialog()\n else setDialogOpen(true)\n }}\n initialValues={buildInitialFormValues(editingTask)}\n onSubmit={handleDialogSubmit}\n isSubmitting={isMutating}\n contextMessage={dialogContextMessage}\n useCanonicalInteractions={useCanonicalInteractions}\n />\n\n {ConfirmDialogElement}\n </div>\n )\n}\n\nexport default TasksSection\n"],
|
|
5
|
-
"mappings": "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",
|
|
4
|
+
"sourcesContent": ["\"use client\"\n\nimport * as React from 'react'\nimport Link from 'next/link'\nimport { Loader2, Pencil, Trash2 } from 'lucide-react'\nimport { Button } from '@open-mercato/ui/primitives/button'\nimport { IconButton } from '@open-mercato/ui/primitives/icon-button'\nimport { StatusBadge, type StatusBadgeVariant } from '@open-mercato/ui/primitives/status-badge'\nimport { mapDictionaryColorToTone } from '@open-mercato/shared/lib/query/advanced-filter'\nimport { useOrganizationScopeVersion } from '@open-mercato/shared/lib/frontend/useOrganizationScope'\nimport { flash } from '@open-mercato/ui/backend/FlashMessages'\nimport { dismissRecordConflict } from '@open-mercato/ui/backend/conflicts'\nimport {\n extractOptimisticLockConflict,\n extractRecordLockConflict,\n} from '@open-mercato/ui/backend/utils/optimisticLock'\nimport { useConfirmDialog } from '@open-mercato/ui/backend/confirm-dialog'\nimport { LoadingMessage, TabEmptyState } from '@open-mercato/ui/backend/detail'\nimport { cn } from '@open-mercato/shared/lib/utils'\nimport { useT } from '@open-mercato/shared/lib/i18n/context'\nimport type { InteractionSummary, SectionAction, TabEmptyStateConfig, TodoLinkSummary, Translator } from './types'\nimport { createTranslatorWithFallback } from '@open-mercato/shared/lib/i18n/translate'\nimport { formatDate, resolveTodoHref } from './utils'\nimport { formatDateTime } from '@open-mercato/shared/lib/time'\nimport { TimelineItemHeader } from './TimelineItemHeader'\nimport { TaskDialog } from './TaskDialog'\nimport { usePersonTasks, type TaskFormPayload } from './hooks/usePersonTasks'\nimport { useCustomerDictionary } from './hooks/useCustomerDictionary'\nimport { useInteractions, type InteractionCreatePayload } from './hooks/useInteractions'\nimport { mapInteractionRecordToTodoSummary } from '../../lib/interactionCompatibility'\n\ntype GuardedMutationRunner = <T>(\n operation: () => Promise<T>,\n mutationPayload?: Record<string, unknown>,\n) => Promise<T>\n\ntype TasksSectionProps = {\n entityId: string | null\n initialTasks: TodoLinkSummary[]\n emptyLabel: string\n addActionLabel: string\n emptyState: TabEmptyStateConfig\n onActionChange?: (action: SectionAction | null) => void\n onLoadingChange?: (isLoading: boolean) => void\n onDataRefresh?: () => void\n translator?: Translator\n entityName?: string | null\n dialogContextKey?: string\n dialogContextFallback?: string\n /** When true, use the canonical interactions API instead of the legacy todos API. */\n useCanonicalInteractions?: boolean\n runGuardedMutation?: GuardedMutationRunner\n}\n\nconst RESERVED_TASK_CUSTOM_KEYS = new Set(['priority', 'description', 'due_at', 'dueAt'])\n\nfunction isRecordConflict(error: unknown): boolean {\n return Boolean(extractOptimisticLockConflict(error) || extractRecordLockConflict(error))\n}\n\nfunction toTimestamp(value: string | null | undefined): number | null {\n if (!value) return null\n const timestamp = new Date(value).getTime()\n return Number.isNaN(timestamp) ? null : timestamp\n}\n\nfunction sortTaskSummaries(tasks: TodoLinkSummary[]): TodoLinkSummary[] {\n return [...tasks].sort((left, right) => {\n const leftDue = toTimestamp(left.dueAt)\n const rightDue = toTimestamp(right.dueAt)\n if (leftDue !== null || rightDue !== null) {\n if (leftDue === null) return 1\n if (rightDue === null) return -1\n if (leftDue !== rightDue) return leftDue - rightDue\n }\n const leftCreated = toTimestamp(left.createdAt) ?? 0\n const rightCreated = toTimestamp(right.createdAt) ?? 0\n if (leftCreated !== rightCreated) return rightCreated - leftCreated\n return left.id.localeCompare(right.id)\n })\n}\n\nconst STATUS_BADGE_VARIANTS = new Set<StatusBadgeVariant>(['success', 'warning', 'error', 'info', 'neutral'])\n\nfunction coerceStatusBadgeVariant(tone: ReturnType<typeof mapDictionaryColorToTone>): StatusBadgeVariant {\n return tone && STATUS_BADGE_VARIANTS.has(tone as StatusBadgeVariant) ? (tone as StatusBadgeVariant) : 'neutral'\n}\n\nfunction buildInitialFormValues(task: TodoLinkSummary | null): Record<string, unknown> | undefined {\n if (!task) return undefined\n const values: Record<string, unknown> = {\n title: task.title ?? '',\n is_done: task.isDone ?? false,\n status: task.status ?? 'planned',\n description: task.description ?? '',\n priority: task.priority ?? '',\n scheduledAt: task.dueAt ?? '',\n }\n if (task.customValues) {\n for (const [key, value] of Object.entries(task.customValues)) {\n if (RESERVED_TASK_CUSTOM_KEYS.has(key)) continue\n const formKey = `cf_${key}`\n if (values[formKey] === undefined) values[formKey] = value\n }\n }\n return values\n}\n\nexport function TasksSection({\n entityId,\n initialTasks,\n emptyLabel,\n addActionLabel,\n emptyState,\n onActionChange,\n onLoadingChange,\n onDataRefresh,\n translator,\n entityName,\n dialogContextKey,\n dialogContextFallback,\n useCanonicalInteractions = false,\n runGuardedMutation,\n}: TasksSectionProps) {\n const tHook = useT()\n const fallbackTranslator = React.useMemo<Translator>(() => createTranslatorWithFallback(tHook), [tHook])\n const t: Translator = React.useMemo(() => translator ?? fallbackTranslator, [translator, fallbackTranslator])\n const { confirm, ConfirmDialogElement } = useConfirmDialog()\n const runWriteMutation = React.useCallback(\n async <T,>(operation: () => Promise<T>, mutationPayload?: Record<string, unknown>): Promise<T> => {\n if (!runGuardedMutation) {\n return operation()\n }\n return runGuardedMutation(operation, mutationPayload)\n },\n [runGuardedMutation],\n )\n\n const scopeVersion = useOrganizationScopeVersion()\n const statusDictionary = useCustomerDictionary('interaction-statuses', scopeVersion)\n const statusMap = statusDictionary.data?.map ?? null\n\n // Legacy path: usePersonTasks (default)\n const legacyResult = usePersonTasks({ entityId, initialTasks })\n\n // Canonical path: useInteractions with planned-status filter\n const canonicalResult = useInteractions({\n entityId: useCanonicalInteractions ? entityId : null,\n typeFilter: 'task',\n })\n\n // Map canonical interactions to the TodoLinkSummary shape used by the rendering below\n const canonicalTasks = React.useMemo<TodoLinkSummary[]>(\n () => (useCanonicalInteractions ? canonicalResult.interactions.map(mapInteractionRecordToTodoSummary) : []),\n [useCanonicalInteractions, canonicalResult.interactions],\n )\n\n const canonicalCreateTask = React.useCallback(\n async (payload: TaskFormPayload) => {\n if (!entityId) throw new Error('Task creation requires an entity id')\n const interactionPayload: InteractionCreatePayload = {\n entityId,\n interactionType: 'task',\n title: payload.base.title,\n status: payload.base.status ?? (payload.base.is_done ? 'done' : 'planned'),\n priority: payload.base.priority ?? null,\n body: payload.base.description ?? null,\n scheduledAt: payload.base.scheduledAt ?? null,\n customValues: payload.custom,\n }\n await canonicalResult.createInteraction(interactionPayload)\n },\n [canonicalResult, entityId],\n )\n\n const canonicalUpdateTask = React.useCallback(\n async (task: TodoLinkSummary, payload: TaskFormPayload) => {\n await canonicalResult.updateInteraction(task.todoId, {\n title: payload.base.title,\n status: payload.base.status ?? (payload.base.is_done ? 'done' : 'planned'),\n priority: payload.base.priority ?? null,\n body: payload.base.description ?? null,\n scheduledAt: payload.base.scheduledAt ?? null,\n customValues: payload.custom,\n })\n },\n [canonicalResult],\n )\n\n const canonicalToggleTask = React.useCallback(\n async (task: TodoLinkSummary, nextIsDone: boolean) => {\n if (nextIsDone) {\n await canonicalResult.completeInteraction(task.todoId)\n } else {\n // Reopen: set status back to planned via update\n await canonicalResult.updateInteraction(task.todoId, { status: 'planned' })\n }\n },\n [canonicalResult],\n )\n\n const canonicalUnlinkTask = React.useCallback(\n async (task: TodoLinkSummary) => {\n await canonicalResult.deleteInteraction(task.todoId)\n },\n [canonicalResult],\n )\n\n // Unified interface: pick the active data source based on the flag\n const tasks = useCanonicalInteractions ? canonicalTasks : legacyResult.tasks\n const isInitialLoading = useCanonicalInteractions ? canonicalResult.isInitialLoading : legacyResult.isInitialLoading\n const isLoadingMore = useCanonicalInteractions ? canonicalResult.isLoadingMore : legacyResult.isLoadingMore\n const isMutating = useCanonicalInteractions ? canonicalResult.isMutating : legacyResult.isMutating\n const hasMore = useCanonicalInteractions ? canonicalResult.hasMore : legacyResult.hasMore\n const loadMore = useCanonicalInteractions ? canonicalResult.loadMore : legacyResult.loadMore\n const refresh = useCanonicalInteractions ? canonicalResult.refresh : legacyResult.refresh\n const createTask = useCanonicalInteractions ? canonicalCreateTask : legacyResult.createTask\n const updateTask = useCanonicalInteractions ? canonicalUpdateTask : legacyResult.updateTask\n const toggleTask = useCanonicalInteractions ? canonicalToggleTask : legacyResult.toggleTask\n const unlinkTask = useCanonicalInteractions ? canonicalUnlinkTask : legacyResult.unlinkTask\n const pendingTaskId = useCanonicalInteractions ? canonicalResult.pendingId : legacyResult.pendingTaskId\n const error = useCanonicalInteractions ? canonicalResult.error : legacyResult.error\n const sortedTasks = React.useMemo(() => sortTaskSummaries(tasks), [tasks])\n\n const [dialogOpen, setDialogOpen] = React.useState(false)\n const [dialogMode, setDialogMode] = React.useState<'create' | 'edit'>('create')\n const [editingTask, setEditingTask] = React.useState<TodoLinkSummary | null>(null)\n const sentinelRef = React.useRef<HTMLDivElement | null>(null)\n\n const dialogContextMessage = React.useMemo(() => {\n if (!dialogContextKey || !entityName) return undefined\n return t(dialogContextKey, dialogContextFallback ?? 'This task will be linked to {{name}}', { name: entityName })\n }, [dialogContextFallback, dialogContextKey, entityName, t])\n\n const openCreateDialog = React.useCallback(() => {\n setEditingTask(null)\n setDialogMode('create')\n setDialogOpen(true)\n }, [])\n\n const openEditDialog = React.useCallback((task: TodoLinkSummary) => {\n setEditingTask(task)\n setDialogMode('edit')\n setDialogOpen(true)\n }, [])\n\n const closeDialog = React.useCallback(() => {\n setDialogOpen(false)\n setEditingTask(null)\n }, [])\n\n React.useEffect(() => {\n if (!dialogOpen || dialogMode !== 'edit' || !editingTask) return\n const refreshedTask = tasks.find((task) => task.id === editingTask.id)\n if (!refreshedTask || refreshedTask === editingTask) return\n setEditingTask(refreshedTask)\n }, [dialogMode, dialogOpen, editingTask, tasks])\n\n React.useEffect(() => {\n if (!onActionChange) return\n if (!entityId) {\n onActionChange(null)\n return\n }\n onActionChange({\n label: addActionLabel,\n onClick: openCreateDialog,\n disabled: isMutating,\n })\n return () => {\n onActionChange(null)\n }\n }, [addActionLabel, entityId, isMutating, onActionChange, openCreateDialog])\n\n React.useEffect(() => {\n if (!onLoadingChange) return\n onLoadingChange(isInitialLoading || isMutating)\n }, [isInitialLoading, isMutating, onLoadingChange])\n\n React.useEffect(() => {\n if (!hasMore) return\n if (typeof IntersectionObserver === 'undefined') return\n const el = sentinelRef.current\n if (!el) return\n\n const observer = new IntersectionObserver(\n (entries) => {\n if (entries.some((entry) => entry.isIntersecting)) {\n loadMore().catch(() => {})\n }\n },\n { rootMargin: '200px 0px 200px 0px' },\n )\n observer.observe(el)\n return () => {\n observer.disconnect()\n }\n }, [hasMore, loadMore])\n\n const handleCreate = React.useCallback(\n async (payload: TaskFormPayload) => {\n try {\n await runWriteMutation(\n () => createTask(payload),\n {\n entityId,\n title: payload.base.title,\n isDone: payload.base.is_done ?? undefined,\n },\n )\n flash(t('customers.people.detail.tasks.createSuccess', 'Task created'), 'success')\n await Promise.resolve(onDataRefresh?.())\n } catch (err) {\n if (!isRecordConflict(err)) {\n const message = err instanceof Error ? err.message : t('customers.people.detail.tasks.error', 'Failed to create task')\n flash(message, 'error')\n }\n throw err\n }\n },\n [createTask, entityId, onDataRefresh, runWriteMutation, t],\n )\n\n const handleUpdate = React.useCallback(\n async (task: TodoLinkSummary, payload: TaskFormPayload) => {\n try {\n await runWriteMutation(\n () => updateTask(task, payload),\n {\n id: task.id,\n todoId: task.todoId,\n title: payload.base.title,\n isDone: payload.base.is_done ?? undefined,\n },\n )\n dismissRecordConflict()\n flash(t('customers.people.detail.tasks.updateSuccess', 'Task updated'), 'success')\n await Promise.resolve(onDataRefresh?.())\n } catch (err) {\n if (!isRecordConflict(err)) {\n const message =\n err instanceof Error ? err.message : t('customers.people.detail.tasks.updateError', 'Failed to update task')\n flash(message, 'error')\n }\n throw err\n }\n },\n [onDataRefresh, runWriteMutation, t, updateTask],\n )\n\n const handleToggle = React.useCallback(\n async (task: TodoLinkSummary, nextIsDone: boolean) => {\n try {\n await runWriteMutation(\n () => toggleTask(task, nextIsDone),\n {\n id: task.id,\n todoId: task.todoId,\n isDone: nextIsDone,\n },\n )\n dismissRecordConflict()\n flash(\n nextIsDone\n ? t('customers.people.detail.tasks.completeSuccess', 'Task marked as done')\n : t('customers.people.detail.tasks.reopenSuccess', 'Task reopened'),\n 'success',\n )\n await Promise.resolve(onDataRefresh?.())\n } catch (err) {\n if (!isRecordConflict(err)) {\n const message =\n err instanceof Error ? err.message : t('customers.people.detail.tasks.toggleError', 'Failed to update task status')\n flash(message, 'error')\n }\n }\n },\n [onDataRefresh, runWriteMutation, t, toggleTask],\n )\n\n const handleDelete = React.useCallback(\n async (task: TodoLinkSummary) => {\n const approved = await confirm({\n title: t('customers.people.detail.tasks.deleteConfirmTitle', 'Remove task?'),\n description: t(\n 'customers.people.detail.tasks.deleteConfirmDescription',\n 'This task will be removed from every view it appears in. This action cannot be undone.',\n ),\n confirmText: t('customers.people.detail.tasks.deleteConfirmAction', 'Remove task'),\n variant: 'destructive',\n })\n if (!approved) return\n try {\n await runWriteMutation(\n () => unlinkTask(task),\n {\n id: task.id,\n todoId: task.todoId,\n },\n )\n dismissRecordConflict()\n flash(t('customers.people.detail.tasks.deleteSuccess', 'Task removed'), 'success')\n await Promise.resolve(onDataRefresh?.())\n await refresh()\n } catch (err) {\n if (!isRecordConflict(err)) {\n const message =\n err instanceof Error ? err.message : t('customers.people.detail.tasks.deleteError', 'Failed to remove task')\n flash(message, 'error')\n }\n }\n },\n [confirm, onDataRefresh, refresh, runWriteMutation, t, unlinkTask],\n )\n\n const handleCancel = React.useCallback(\n async (task: TodoLinkSummary) => {\n if (!useCanonicalInteractions) return\n try {\n await runWriteMutation(\n () => canonicalResult.cancelInteraction(task.todoId),\n { id: task.todoId },\n )\n dismissRecordConflict()\n flash(t('customers.people.detail.tasks.cancelSuccess', 'Task canceled'), 'success')\n await Promise.resolve(onDataRefresh?.())\n } catch (err) {\n if (!isRecordConflict(err)) {\n const message =\n err instanceof Error ? err.message : t('customers.people.detail.tasks.cancelError', 'Failed to cancel task')\n flash(message, 'error')\n }\n }\n },\n [canonicalResult, onDataRefresh, runWriteMutation, t, useCanonicalInteractions],\n )\n\n const renderTaskMeta = React.useCallback(\n (task: TodoLinkSummary) => {\n const meta: string[] = []\n if (task.status === 'canceled') {\n meta.push(t('customers.people.detail.tasks.status.canceled', 'Canceled'))\n }\n if (typeof task.priority === 'number') {\n meta.push(t('customers.people.detail.tasks.priorityLabel', 'Priority {{priority}}', { priority: task.priority }))\n }\n if (task.severity) {\n meta.push(\n t(\n `customers.people.detail.tasks.severity.${task.severity}`,\n task.severity.charAt(0).toUpperCase() + task.severity.slice(1),\n ),\n )\n }\n if (task.dueAt) {\n const dueLabel =\n formatDate(task.dueAt) ??\n formatDateTime(task.dueAt) ??\n t('customers.people.detail.tasks.dueLabel', 'Due {{date}}', { date: task.dueAt })\n meta.push(t('customers.people.detail.tasks.dueLabel', 'Due {{date}}', { date: dueLabel }))\n }\n return meta\n },\n [t],\n )\n\n const handleDialogSubmit = React.useCallback(\n async (payload: TaskFormPayload) => {\n if (dialogMode === 'edit' && editingTask) {\n await handleUpdate(editingTask, payload)\n } else {\n await handleCreate(payload)\n }\n },\n [dialogMode, editingTask, handleCreate, handleUpdate],\n )\n\n const hasTasks = sortedTasks.length > 0\n\n return (\n <div className=\"mt-0 space-y-6\">\n <div className=\"space-y-4\">\n {isInitialLoading ? (\n <LoadingMessage\n label={t('customers.people.detail.tasks.loading', 'Loading tasks\u2026')}\n className=\"border-0 bg-transparent p-0 py-8 justify-center\"\n />\n ) : null}\n\n {!isInitialLoading && !hasTasks ? (\n <TabEmptyState title={emptyState.title}>\n <Button asChild variant=\"outline\" size=\"sm\" className=\"mt-2\">\n <Link href=\"/backend/customer-tasks\">\n {t('customers.people.detail.tasks.viewAll', 'View all tasks')}\n </Link>\n </Button>\n </TabEmptyState>\n ) : null}\n\n {!isInitialLoading && hasTasks ? (\n <div className=\"space-y-4\">\n {error ? (\n <div className=\"rounded-md border border-destructive/40 bg-destructive/5 px-3 py-2 text-sm text-destructive\">\n {error}\n </div>\n ) : null}\n {sortedTasks.map((task) => {\n const todoHref = task.externalHref ?? resolveTodoHref(task.todoSource, task.todoId)\n const createdLabel = formatDateTime(task.createdAt) ?? emptyLabel\n const meta = renderTaskMeta(task)\n const title = task.title ?? t('customers.people.detail.tasks.untitled', 'Untitled task')\n const isDone = task.isDone === true\n const isCanceled = task.status === 'canceled'\n const checkboxId = `person-task-${task.id}`\n const isPendingToggle = pendingTaskId === task.todoId\n return (\n <article key={task.id} className=\"group space-y-3 rounded-lg border bg-card p-4 transition hover:border-border/70\">\n <div className=\"flex flex-wrap items-start justify-between gap-3\">\n <TimelineItemHeader\n title={\n <span className=\"inline-flex items-center gap-2\">\n <input\n id={checkboxId}\n type=\"checkbox\"\n checked={isDone}\n onChange={(event) => {\n const next = event.target.checked\n void handleToggle(task, next)\n }}\n disabled={isMutating || isPendingToggle || isCanceled}\n className=\"h-4 w-4 rounded border\"\n />\n <span\n className={cn(\n 'text-sm font-semibold',\n isDone ? 'line-through text-muted-foreground' : undefined,\n isCanceled ? 'text-muted-foreground' : undefined,\n )}\n >\n {title}\n </span>\n {task.status ? (\n <StatusBadge\n variant={coerceStatusBadgeVariant(\n mapDictionaryColorToTone(statusMap?.[task.status]?.color ?? null),\n )}\n dot\n >\n {statusMap?.[task.status]?.label ?? task.status}\n </StatusBadge>\n ) : null}\n </span>\n }\n timestamp={task.createdAt}\n fallbackTimestampLabel={createdLabel}\n />\n <div className=\"flex items-center gap-1 opacity-0 transition-opacity group-hover:opacity-100 focus-within:opacity-100\">\n <IconButton\n type=\"button\"\n variant=\"ghost\"\n size=\"sm\"\n onClick={() => openEditDialog(task)}\n disabled={isMutating}\n aria-label={t('ui.actions.edit', 'Edit')}\n >\n {isMutating && editingTask?.id === task.id && dialogMode === 'edit' ? (\n <Loader2 className=\"h-4 w-4 animate-spin\" />\n ) : (\n <Pencil className=\"h-4 w-4\" />\n )}\n </IconButton>\n <IconButton\n type=\"button\"\n variant=\"ghost\"\n size=\"sm\"\n onClick={() => handleDelete(task)}\n disabled={isMutating}\n aria-label={t('ui.actions.delete', 'Delete')}\n >\n {isMutating ? <Loader2 className=\"h-4 w-4 animate-spin text-destructive\" /> : <Trash2 className=\"h-4 w-4\" />}\n </IconButton>\n </div>\n </div>\n {meta.length ? (\n <div className=\"flex flex-wrap items-center gap-3 text-xs text-muted-foreground\">\n {meta.map((entry) => (\n <span key={`${task.id}-${entry}`} className=\"rounded bg-muted px-2 py-1 text-xs font-medium text-muted-foreground\">\n {entry}\n </span>\n ))}\n </div>\n ) : null}\n {task.description ? (\n <p className=\"text-sm text-muted-foreground whitespace-pre-wrap\">{task.description}</p>\n ) : null}\n <div className=\"flex flex-wrap items-center gap-3 text-xs\">\n {useCanonicalInteractions && !isDone && !isCanceled ? (\n <Button\n type=\"button\"\n variant=\"ghost\"\n size=\"sm\"\n className=\"h-auto px-0 text-xs font-medium text-muted-foreground hover:bg-transparent hover:text-foreground\"\n onClick={() => void handleCancel(task)}\n disabled={isMutating || isPendingToggle}\n >\n {t('customers.people.detail.tasks.cancelAction', 'Cancel task')}\n </Button>\n ) : null}\n {todoHref ? (\n <Link href={todoHref} className=\"text-primary hover:underline\">\n {t('customers.people.detail.tasks.openTask', 'Open task')}\n </Link>\n ) : null}\n </div>\n </article>\n )\n })}\n <div ref={sentinelRef} />\n {hasMore ? (\n <div className=\"flex justify-center\">\n <Button type=\"button\" variant=\"outline\" size=\"sm\" onClick={() => loadMore().catch(() => {})} disabled={isLoadingMore}>\n {isLoadingMore ? (\n <>\n <Loader2 className=\"mr-2 h-4 w-4 animate-spin\" />\n {t('customers.people.detail.tasks.loadingMore', 'Loading\u2026')}\n </>\n ) : (\n t('customers.people.detail.tasks.loadMore', 'Load more')\n )}\n </Button>\n </div>\n ) : null}\n {isLoadingMore ? (\n <div className=\"flex justify-center text-xs text-muted-foreground\">\n <Loader2 className=\"mr-2 h-3 w-3 animate-spin\" />\n {t('customers.people.detail.tasks.loadingMore', 'Loading\u2026')}\n </div>\n ) : null}\n </div>\n ) : null}\n {!isInitialLoading && hasTasks ? (\n <div className=\"flex justify-center\">\n <Button asChild variant=\"outline\" size=\"sm\">\n <Link href=\"/backend/customer-tasks\">\n {t('customers.people.detail.tasks.viewAll', 'View all tasks')}\n </Link>\n </Button>\n </div>\n ) : null}\n </div>\n\n <TaskDialog\n open={dialogOpen}\n mode={dialogMode}\n onOpenChange={(next) => {\n if (!next) closeDialog()\n else setDialogOpen(true)\n }}\n initialValues={buildInitialFormValues(editingTask)}\n onSubmit={handleDialogSubmit}\n isSubmitting={isMutating}\n contextMessage={dialogContextMessage}\n useCanonicalInteractions={useCanonicalInteractions}\n />\n\n {ConfirmDialogElement}\n </div>\n )\n}\n\nexport default TasksSection\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
|
@@ -1,19 +1,25 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
import { readApiResultOrThrow } from "@open-mercato/ui/backend/utils/apiCall";
|
|
3
3
|
import { useQuery } from "@tanstack/react-query";
|
|
4
|
+
import { useOptionalLocale } from "@open-mercato/shared/lib/i18n/context";
|
|
4
5
|
import {
|
|
5
6
|
createDictionaryMap,
|
|
6
7
|
normalizeDictionaryEntries
|
|
7
8
|
} from "../../../lib/dictionaries.js";
|
|
8
9
|
const DICTIONARY_STALE_TIME = 5 * 60 * 1e3;
|
|
9
10
|
const BASE_DICTIONARY_QUERY_KEY = ["customers", "dictionaries"];
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
11
|
+
function resolveDictionaryLocale() {
|
|
12
|
+
return typeof document === "undefined" ? "en" : document.documentElement.lang || "en";
|
|
13
|
+
}
|
|
14
|
+
const customerDictionaryQueryKey = (kind, scopeVersion = 0, organizationId, locale = resolveDictionaryLocale()) => [...BASE_DICTIONARY_QUERY_KEY, kind, `scope:${scopeVersion}`, organizationId ? `org:${organizationId}` : "org:default", `locale:${locale}`];
|
|
15
|
+
const customerDictionaryQueryOptions = (kind, scopeVersion = 0, organizationId, locale = resolveDictionaryLocale()) => ({
|
|
16
|
+
queryKey: customerDictionaryQueryKey(kind, scopeVersion, organizationId, locale),
|
|
13
17
|
staleTime: DICTIONARY_STALE_TIME,
|
|
14
18
|
gcTime: DICTIONARY_STALE_TIME,
|
|
15
19
|
queryFn: async () => {
|
|
16
|
-
const
|
|
20
|
+
const params = new URLSearchParams({ locale });
|
|
21
|
+
if (organizationId) params.set("organizationId", organizationId);
|
|
22
|
+
const url = `/api/customers/dictionaries/${kind}?${params}`;
|
|
17
23
|
const payload = await readApiResultOrThrow(
|
|
18
24
|
url,
|
|
19
25
|
void 0,
|
|
@@ -53,7 +59,8 @@ const customerDictionaryQueryOptions = (kind, scopeVersion = 0, organizationId)
|
|
|
53
59
|
}
|
|
54
60
|
});
|
|
55
61
|
function useCustomerDictionary(kind, scopeVersion = 0, organizationId) {
|
|
56
|
-
|
|
62
|
+
const locale = useOptionalLocale();
|
|
63
|
+
return useQuery(customerDictionaryQueryOptions(kind, scopeVersion, organizationId, locale));
|
|
57
64
|
}
|
|
58
65
|
async function invalidateCustomerDictionary(queryClient, kind) {
|
|
59
66
|
await queryClient.invalidateQueries({ queryKey: [...BASE_DICTIONARY_QUERY_KEY, kind] });
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../../../../../src/modules/customers/components/detail/hooks/useCustomerDictionary.ts"],
|
|
4
|
-
"sourcesContent": ["\"use client\"\n\nimport { readApiResultOrThrow } from '@open-mercato/ui/backend/utils/apiCall'\nimport { useQuery, type QueryClient, type UseQueryResult } from '@tanstack/react-query'\nimport {\n createDictionaryMap,\n normalizeDictionaryEntries,\n type CustomerDictionaryDisplayEntry,\n type CustomerDictionaryKind,\n type CustomerDictionaryMap,\n} from '../../../lib/dictionaries'\n\nexport type CustomerDictionaryEntry = CustomerDictionaryDisplayEntry & {\n id: string\n organizationId: string | null\n isInherited: boolean\n}\n\nexport type CustomerDictionaryQueryData = {\n entries: CustomerDictionaryDisplayEntry[]\n map: CustomerDictionaryMap\n fullEntries: CustomerDictionaryEntry[]\n}\n\nconst DICTIONARY_STALE_TIME = 5 * 60 * 1000\n\nconst BASE_DICTIONARY_QUERY_KEY = ['customers', 'dictionaries'] as const\n\nexport const customerDictionaryQueryKey = (kind: CustomerDictionaryKind, scopeVersion = 0, organizationId?: string | null) =>\n [...BASE_DICTIONARY_QUERY_KEY, kind, `scope:${scopeVersion}`, organizationId ? `org:${organizationId}` : 'org:default'] as const\n\nexport const customerDictionaryQueryOptions = (kind: CustomerDictionaryKind, scopeVersion = 0, organizationId?: string | null) => ({\n queryKey: customerDictionaryQueryKey(kind, scopeVersion, organizationId),\n staleTime: DICTIONARY_STALE_TIME,\n gcTime: DICTIONARY_STALE_TIME,\n queryFn: async (): Promise<CustomerDictionaryQueryData> => {\n const
|
|
5
|
-
"mappings": ";AAEA,SAAS,4BAA4B;AACrC,SAAS,gBAAuD;AAChE;AAAA,EACE;AAAA,EACA;AAAA,OAIK;AAcP,MAAM,wBAAwB,IAAI,KAAK;AAEvC,MAAM,4BAA4B,CAAC,aAAa,cAAc;
|
|
4
|
+
"sourcesContent": ["\"use client\"\n\nimport { readApiResultOrThrow } from '@open-mercato/ui/backend/utils/apiCall'\nimport { useQuery, type QueryClient, type UseQueryResult } from '@tanstack/react-query'\nimport { useOptionalLocale } from '@open-mercato/shared/lib/i18n/context'\nimport {\n createDictionaryMap,\n normalizeDictionaryEntries,\n type CustomerDictionaryDisplayEntry,\n type CustomerDictionaryKind,\n type CustomerDictionaryMap,\n} from '../../../lib/dictionaries'\n\nexport type CustomerDictionaryEntry = CustomerDictionaryDisplayEntry & {\n id: string\n organizationId: string | null\n isInherited: boolean\n}\n\nexport type CustomerDictionaryQueryData = {\n entries: CustomerDictionaryDisplayEntry[]\n map: CustomerDictionaryMap\n fullEntries: CustomerDictionaryEntry[]\n}\n\nconst DICTIONARY_STALE_TIME = 5 * 60 * 1000\n\nconst BASE_DICTIONARY_QUERY_KEY = ['customers', 'dictionaries'] as const\n\nfunction resolveDictionaryLocale(): string {\n return typeof document === 'undefined' ? 'en' : document.documentElement.lang || 'en'\n}\n\nexport const customerDictionaryQueryKey = (kind: CustomerDictionaryKind, scopeVersion = 0, organizationId?: string | null, locale = resolveDictionaryLocale()) =>\n [...BASE_DICTIONARY_QUERY_KEY, kind, `scope:${scopeVersion}`, organizationId ? `org:${organizationId}` : 'org:default', `locale:${locale}`] as const\n\nexport const customerDictionaryQueryOptions = (kind: CustomerDictionaryKind, scopeVersion = 0, organizationId?: string | null, locale = resolveDictionaryLocale()) => ({\n queryKey: customerDictionaryQueryKey(kind, scopeVersion, organizationId, locale),\n staleTime: DICTIONARY_STALE_TIME,\n gcTime: DICTIONARY_STALE_TIME,\n queryFn: async (): Promise<CustomerDictionaryQueryData> => {\n const params = new URLSearchParams({ locale })\n if (organizationId) params.set('organizationId', organizationId)\n const url = `/api/customers/dictionaries/${kind}?${params}`\n const payload = await readApiResultOrThrow<Record<string, unknown>>(\n url,\n undefined,\n { errorMessage: 'Failed to load dictionary entries.' },\n )\n const items = Array.isArray(payload.items) ? payload.items : []\n const parsed = items\n .map((item) => {\n if (!item || typeof item !== 'object') return null\n const data = item as Record<string, unknown>\n const rawValue = typeof data.value === 'string' ? data.value.trim() : ''\n const id = typeof data.id === 'string' ? data.id : ''\n if (!rawValue || !id) return null\n const label =\n typeof data.label === 'string' && data.label.trim().length ? data.label.trim() : rawValue\n const color =\n typeof data.color === 'string' && /^#([0-9a-fA-F]{6})$/.test(data.color)\n ? `#${data.color.slice(1).toLowerCase()}`\n : null\n const icon = typeof data.icon === 'string' && data.icon.trim().length ? data.icon.trim() : null\n const organizationId = typeof data.organizationId === 'string' ? data.organizationId : null\n const isInherited = data.isInherited === true\n return {\n id,\n value: rawValue,\n label,\n color,\n icon,\n organizationId,\n isInherited,\n } as CustomerDictionaryEntry\n })\n .filter((entry): entry is CustomerDictionaryEntry => entry !== null)\n const normalized = normalizeDictionaryEntries(\n parsed.map(({ value, label, color, icon }) => ({ value, label, color, icon })),\n { sort: false },\n )\n return {\n entries: normalized,\n map: createDictionaryMap(normalized),\n fullEntries: parsed,\n }\n },\n})\n\nexport function useCustomerDictionary(\n kind: CustomerDictionaryKind,\n scopeVersion = 0,\n organizationId?: string | null,\n): UseQueryResult<CustomerDictionaryQueryData> {\n const locale = useOptionalLocale()\n return useQuery(customerDictionaryQueryOptions(kind, scopeVersion, organizationId, locale))\n}\n\nexport async function invalidateCustomerDictionary(queryClient: QueryClient, kind: CustomerDictionaryKind) {\n await queryClient.invalidateQueries({ queryKey: [...BASE_DICTIONARY_QUERY_KEY, kind] })\n}\n\nexport async function ensureCustomerDictionary(\n queryClient: QueryClient,\n kind: CustomerDictionaryKind,\n scopeVersion = 0,\n organizationId?: string | null,\n): Promise<CustomerDictionaryQueryData> {\n return queryClient.fetchQuery(customerDictionaryQueryOptions(kind, scopeVersion, organizationId))\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
6
|
"names": ["organizationId"]
|
|
7
7
|
}
|
|
@@ -172,10 +172,7 @@ function useInteractions({
|
|
|
172
172
|
);
|
|
173
173
|
await refresh();
|
|
174
174
|
} catch (err) {
|
|
175
|
-
if (surfaceRecordConflict(err, t))
|
|
176
|
-
await refresh();
|
|
177
|
-
return;
|
|
178
|
-
}
|
|
175
|
+
if (surfaceRecordConflict(err, t)) await refresh();
|
|
179
176
|
throw err;
|
|
180
177
|
} finally {
|
|
181
178
|
setIsMutating(false);
|
|
@@ -203,10 +200,7 @@ function useInteractions({
|
|
|
203
200
|
);
|
|
204
201
|
await refresh();
|
|
205
202
|
} catch (err) {
|
|
206
|
-
if (surfaceRecordConflict(err, t))
|
|
207
|
-
await refresh();
|
|
208
|
-
return;
|
|
209
|
-
}
|
|
203
|
+
if (surfaceRecordConflict(err, t)) await refresh();
|
|
210
204
|
throw err;
|
|
211
205
|
} finally {
|
|
212
206
|
setPendingId(null);
|
|
@@ -235,10 +229,7 @@ function useInteractions({
|
|
|
235
229
|
);
|
|
236
230
|
await refresh();
|
|
237
231
|
} catch (err) {
|
|
238
|
-
if (surfaceRecordConflict(err, t))
|
|
239
|
-
await refresh();
|
|
240
|
-
return;
|
|
241
|
-
}
|
|
232
|
+
if (surfaceRecordConflict(err, t)) await refresh();
|
|
242
233
|
throw err;
|
|
243
234
|
} finally {
|
|
244
235
|
setPendingId(null);
|
|
@@ -266,7 +257,6 @@ function useInteractions({
|
|
|
266
257
|
} catch (err) {
|
|
267
258
|
if (surfaceRecordConflict(err, t)) {
|
|
268
259
|
await refresh();
|
|
269
|
-
return;
|
|
270
260
|
}
|
|
271
261
|
throw err;
|
|
272
262
|
} finally {
|