@open-mercato/core 0.6.6-develop.6515.1.01911d00e2 → 0.6.6-develop.6519.1.bdc91fb755

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.
Files changed (86) hide show
  1. package/.turbo/turbo-build.log +1 -1
  2. package/dist/modules/configs/lib/upgrade-actions.js +31 -1
  3. package/dist/modules/configs/lib/upgrade-actions.js.map +2 -2
  4. package/dist/modules/customers/ai-tools/activities-tasks-pack.js +11 -2
  5. package/dist/modules/customers/ai-tools/activities-tasks-pack.js.map +2 -2
  6. package/dist/modules/customers/api/companies/[id]/route.js +3 -2
  7. package/dist/modules/customers/api/companies/[id]/route.js.map +2 -2
  8. package/dist/modules/customers/api/dictionaries/context.js +4 -2
  9. package/dist/modules/customers/api/dictionaries/context.js.map +2 -2
  10. package/dist/modules/customers/api/interactions/conflicts/route.js +2 -1
  11. package/dist/modules/customers/api/interactions/conflicts/route.js.map +2 -2
  12. package/dist/modules/customers/api/interactions/counts/route.js +7 -2
  13. package/dist/modules/customers/api/interactions/counts/route.js.map +2 -2
  14. package/dist/modules/customers/api/people/[id]/route.js +3 -2
  15. package/dist/modules/customers/api/people/[id]/route.js.map +2 -2
  16. package/dist/modules/customers/cli.js +19 -0
  17. package/dist/modules/customers/cli.js.map +2 -2
  18. package/dist/modules/customers/commands/interactions.js +8 -3
  19. package/dist/modules/customers/commands/interactions.js.map +2 -2
  20. package/dist/modules/customers/commands/shared.js +2 -1
  21. package/dist/modules/customers/commands/shared.js.map +2 -2
  22. package/dist/modules/customers/components/DictionarySettings.js +5 -0
  23. package/dist/modules/customers/components/DictionarySettings.js.map +2 -2
  24. package/dist/modules/customers/components/DictionarySortSettings.js +5 -0
  25. package/dist/modules/customers/components/DictionarySortSettings.js.map +2 -2
  26. package/dist/modules/customers/components/detail/ActivitiesCard.js +3 -2
  27. package/dist/modules/customers/components/detail/ActivitiesCard.js.map +2 -2
  28. package/dist/modules/customers/components/detail/ActivityCard.js +2 -1
  29. package/dist/modules/customers/components/detail/ActivityCard.js.map +2 -2
  30. package/dist/modules/customers/components/detail/ActivityTimeline.js +3 -2
  31. package/dist/modules/customers/components/detail/ActivityTimeline.js.map +2 -2
  32. package/dist/modules/customers/components/detail/TaskDialog.js +3 -2
  33. package/dist/modules/customers/components/detail/TaskDialog.js.map +2 -2
  34. package/dist/modules/customers/components/detail/TaskForm.js +170 -69
  35. package/dist/modules/customers/components/detail/TaskForm.js.map +2 -2
  36. package/dist/modules/customers/components/detail/TasksSection.js +27 -4
  37. package/dist/modules/customers/components/detail/TasksSection.js.map +2 -2
  38. package/dist/modules/customers/components/detail/hooks/usePersonTasks.js.map +2 -2
  39. package/dist/modules/customers/data/enrichers.js +2 -2
  40. package/dist/modules/customers/data/enrichers.js.map +2 -2
  41. package/dist/modules/customers/data/validators.js +6 -2
  42. package/dist/modules/customers/data/validators.js.map +2 -2
  43. package/dist/modules/customers/lib/dictionaries.js +2 -1
  44. package/dist/modules/customers/lib/dictionaries.js.map +2 -2
  45. package/dist/modules/customers/lib/interactionProjection.js +2 -1
  46. package/dist/modules/customers/lib/interactionProjection.js.map +2 -2
  47. package/dist/modules/customers/lib/interactionStatus.js +24 -0
  48. package/dist/modules/customers/lib/interactionStatus.js.map +7 -0
  49. package/dist/modules/customers/lib/link-channel-message-handler.js +4 -5
  50. package/dist/modules/customers/lib/link-channel-message-handler.js.map +2 -2
  51. package/dist/modules/dictionaries/components/AppearanceSelector.js +7 -3
  52. package/dist/modules/dictionaries/components/AppearanceSelector.js.map +2 -2
  53. package/dist/modules/dictionaries/components/DictionaryEntriesEditor.js +1 -0
  54. package/dist/modules/dictionaries/components/DictionaryEntriesEditor.js.map +2 -2
  55. package/package.json +7 -7
  56. package/src/modules/configs/lib/upgrade-actions.ts +31 -1
  57. package/src/modules/customers/ai-tools/activities-tasks-pack.ts +12 -3
  58. package/src/modules/customers/api/companies/[id]/route.ts +3 -2
  59. package/src/modules/customers/api/dictionaries/context.ts +2 -0
  60. package/src/modules/customers/api/interactions/conflicts/route.ts +2 -1
  61. package/src/modules/customers/api/interactions/counts/route.ts +7 -2
  62. package/src/modules/customers/api/people/[id]/route.ts +3 -2
  63. package/src/modules/customers/cli.ts +19 -0
  64. package/src/modules/customers/commands/interactions.ts +8 -3
  65. package/src/modules/customers/commands/shared.ts +1 -0
  66. package/src/modules/customers/components/DictionarySettings.tsx +5 -0
  67. package/src/modules/customers/components/DictionarySortSettings.tsx +5 -0
  68. package/src/modules/customers/components/detail/ActivitiesCard.tsx +3 -2
  69. package/src/modules/customers/components/detail/ActivityCard.tsx +2 -1
  70. package/src/modules/customers/components/detail/ActivityTimeline.tsx +3 -2
  71. package/src/modules/customers/components/detail/TaskDialog.tsx +4 -1
  72. package/src/modules/customers/components/detail/TaskForm.tsx +218 -71
  73. package/src/modules/customers/components/detail/TasksSection.tsx +28 -2
  74. package/src/modules/customers/components/detail/hooks/usePersonTasks.ts +1 -0
  75. package/src/modules/customers/data/enrichers.ts +2 -7
  76. package/src/modules/customers/data/validators.ts +16 -2
  77. package/src/modules/customers/i18n/de.json +10 -0
  78. package/src/modules/customers/i18n/en.json +10 -0
  79. package/src/modules/customers/i18n/es.json +10 -0
  80. package/src/modules/customers/i18n/pl.json +10 -0
  81. package/src/modules/customers/lib/dictionaries.ts +1 -0
  82. package/src/modules/customers/lib/interactionProjection.ts +6 -2
  83. package/src/modules/customers/lib/interactionStatus.ts +30 -0
  84. package/src/modules/customers/lib/link-channel-message-handler.ts +4 -5
  85. package/src/modules/dictionaries/components/AppearanceSelector.tsx +10 -1
  86. package/src/modules/dictionaries/components/DictionaryEntriesEditor.tsx +1 -0
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../../../src/modules/customers/components/detail/ActivityTimeline.tsx"],
4
- "sourcesContent": ["'use client'\nimport * as React from 'react'\nimport { Check, ListTodo, Phone, Mail, Users, StickyNote, User } from 'lucide-react'\nimport { useT } from '@open-mercato/shared/lib/i18n/context'\nimport type { TranslateFn } from '@open-mercato/shared/lib/i18n/context'\nimport { Button } from '@open-mercato/ui/primitives/button'\nimport { AiActionChips } from './AiActionChips'\nimport type { InteractionSummary } from './types'\n\nconst TYPE_ICONS: Record<string, React.ComponentType<{ className?: string }>> = {\n call: Phone,\n email: Mail,\n meeting: Users,\n note: StickyNote,\n task: ListTodo,\n}\n\ninterface ActivityTimelineProps {\n activities: InteractionSummary[]\n onEdit?: (activity: InteractionSummary) => void\n onMarkDone?: (activityId: string, updatedAt?: string | null) => void | Promise<void>\n}\n\nexport function ActivityTimeline({ activities, onEdit, onMarkDone }: ActivityTimelineProps) {\n const t = useT()\n\n if (activities.length === 0) {\n return (\n <div className=\"py-6 text-center text-sm text-muted-foreground\">\n {t('customers.timeline.empty', 'No activities match the current filters.')}\n </div>\n )\n }\n\n return (\n <div>\n {activities.map((activity, index) => {\n const dateStr = activity.scheduledAt ?? activity.occurredAt ?? activity.createdAt\n const activityYear = dateStr ? new Date(dateStr).getFullYear() : null\n const prevDateStr = index > 0 ? (activities[index - 1].scheduledAt ?? activities[index - 1].occurredAt ?? activities[index - 1].createdAt) : null\n const prevYear = prevDateStr ? new Date(prevDateStr).getFullYear() : null\n const showYearSeparator = activityYear !== null && prevYear !== null && activityYear !== prevYear\n\n return (\n <React.Fragment key={activity.id}>\n {showYearSeparator && (\n <div className=\"flex items-center gap-3 py-3 px-5\">\n <div className=\"h-px flex-1 bg-border\" />\n <span className=\"text-xs font-semibold text-muted-foreground\">\n {t('customers.activities.yearSeparator', '{year}', { year: activityYear })}\n </span>\n <div className=\"h-px flex-1 bg-border\" />\n </div>\n )}\n <TimelineEntry\n activity={activity}\n t={t}\n withBorder={index < activities.length - 1}\n onEdit={onEdit}\n onMarkDone={onMarkDone}\n />\n </React.Fragment>\n )\n })}\n </div>\n )\n}\n\nfunction TimelineEntry({\n activity,\n t,\n withBorder,\n onEdit,\n onMarkDone,\n}: {\n activity: InteractionSummary\n t: TranslateFn\n withBorder: boolean\n onEdit?: (activity: InteractionSummary) => void\n onMarkDone?: (activityId: string, updatedAt?: string | null) => void | Promise<void>\n}) {\n const dateStr = activity.scheduledAt ?? activity.occurredAt ?? activity.createdAt\n const TypeIcon = TYPE_ICONS[activity.interactionType]\n const title = activity.title ?? activity.body ?? activity.interactionType\n const duration = activity.duration ? ` (${activity.duration} min)` : ''\n const isPlanned = activity.status === 'planned'\n const [markingDone, setMarkingDone] = React.useState(false)\n\n const handleMarkDone = React.useCallback(async (event: React.MouseEvent | React.KeyboardEvent) => {\n event.stopPropagation()\n if (!onMarkDone || markingDone) return\n setMarkingDone(true)\n try {\n await onMarkDone(activity.id, activity.updatedAt)\n } finally {\n setMarkingDone(false)\n }\n }, [activity.id, markingDone, onMarkDone])\n\n return (\n <div\n className={`py-2.5 ${withBorder ? 'border-b border-border/60' : ''} ${onEdit ? 'cursor-pointer hover:bg-accent/40 transition-colors' : ''}`}\n onClick={() => onEdit?.(activity)}\n role={onEdit ? 'button' : undefined}\n tabIndex={onEdit ? 0 : undefined}\n onKeyDown={onEdit ? (e) => { if (e.key === 'Enter') onEdit(activity) } : undefined}\n >\n <div className=\"grid items-start gap-3\" style={{ gridTemplateColumns: '75px 32px 1fr' }}>\n {/* Column 1: Date */}\n <div className=\"shrink-0\">\n <span className=\"block text-[11px] font-semibold leading-tight text-foreground\">\n {formatRelativeDate(dateStr, t)}\n </span>\n <span className=\"block text-[10px] leading-tight text-muted-foreground\">\n {formatTime(dateStr)}\n </span>\n </div>\n\n {/* Column 2: Type icon */}\n <div className=\"flex size-8 items-center justify-center rounded-md bg-muted shrink-0\">\n {TypeIcon ? <TypeIcon className=\"size-3.5 text-muted-foreground\" /> : null}\n </div>\n\n {/* Column 3: Content */}\n <div className=\"min-w-0 space-y-1.5\">\n <div className=\"flex items-start justify-between gap-2\">\n <span className=\"block text-[12px] font-semibold leading-tight text-foreground\">\n {title}{duration}\n </span>\n {isPlanned && onMarkDone ? (\n <Button\n type=\"button\"\n variant=\"default\"\n size=\"sm\"\n disabled={markingDone}\n onClick={handleMarkDone}\n className=\"shrink-0\"\n >\n <Check className=\"size-3.5\" />\n {t('customers.activities.actions.markDone', 'Mark done')}\n </Button>\n ) : null}\n </div>\n\n {activity.body && activity.title && (\n <p className=\"text-[11px] leading-snug text-muted-foreground\">\n {activity.body}\n </p>\n )}\n\n {activity.authorName && (\n <div className=\"flex items-center gap-1 text-[10px] text-muted-foreground\">\n <User className=\"size-2.5 shrink-0\" />\n <span>{t('customers.timeline.author', 'by {{name}}', { name: activity.authorName })}</span>\n </div>\n )}\n\n <AiActionChips activityType={activity.interactionType} />\n </div>\n </div>\n </div>\n )\n}\n\nfunction formatRelativeDate(isoString: string, t: TranslateFn): string {\n try {\n const date = new Date(isoString)\n const now = new Date()\n // Compare calendar dates (not time-based diff) to correctly handle same-day future times\n const dateDay = new Date(date.getFullYear(), date.getMonth(), date.getDate())\n const nowDay = new Date(now.getFullYear(), now.getMonth(), now.getDate())\n const diffDays = Math.round((nowDay.getTime() - dateDay.getTime()) / (1000 * 60 * 60 * 24))\n\n if (diffDays === 0) return t('customers.timeline.date.today', 'today')\n if (diffDays === 1) return t('customers.timeline.date.yesterday', 'yesterday')\n return date.toLocaleDateString(undefined, { day: 'numeric', month: 'short' })\n } catch {\n return ''\n }\n}\n\nfunction formatTime(isoString: string): string {\n try {\n return new Date(isoString).toLocaleTimeString(undefined, { hour: '2-digit', minute: '2-digit' })\n } catch {\n return ''\n }\n}\n"],
5
- "mappings": "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",
4
+ "sourcesContent": ["'use client'\nimport * as React from 'react'\nimport { Check, ListTodo, Phone, Mail, Users, StickyNote, User } from 'lucide-react'\nimport { useT } from '@open-mercato/shared/lib/i18n/context'\nimport type { TranslateFn } from '@open-mercato/shared/lib/i18n/context'\nimport { Button } from '@open-mercato/ui/primitives/button'\nimport { AiActionChips } from './AiActionChips'\nimport type { InteractionSummary } from './types'\nimport { isOpenInteractionStatus } from '../../lib/interactionStatus'\n\nconst TYPE_ICONS: Record<string, React.ComponentType<{ className?: string }>> = {\n call: Phone,\n email: Mail,\n meeting: Users,\n note: StickyNote,\n task: ListTodo,\n}\n\ninterface ActivityTimelineProps {\n activities: InteractionSummary[]\n onEdit?: (activity: InteractionSummary) => void\n onMarkDone?: (activityId: string, updatedAt?: string | null) => void | Promise<void>\n}\n\nexport function ActivityTimeline({ activities, onEdit, onMarkDone }: ActivityTimelineProps) {\n const t = useT()\n\n if (activities.length === 0) {\n return (\n <div className=\"py-6 text-center text-sm text-muted-foreground\">\n {t('customers.timeline.empty', 'No activities match the current filters.')}\n </div>\n )\n }\n\n return (\n <div>\n {activities.map((activity, index) => {\n const dateStr = activity.scheduledAt ?? activity.occurredAt ?? activity.createdAt\n const activityYear = dateStr ? new Date(dateStr).getFullYear() : null\n const prevDateStr = index > 0 ? (activities[index - 1].scheduledAt ?? activities[index - 1].occurredAt ?? activities[index - 1].createdAt) : null\n const prevYear = prevDateStr ? new Date(prevDateStr).getFullYear() : null\n const showYearSeparator = activityYear !== null && prevYear !== null && activityYear !== prevYear\n\n return (\n <React.Fragment key={activity.id}>\n {showYearSeparator && (\n <div className=\"flex items-center gap-3 py-3 px-5\">\n <div className=\"h-px flex-1 bg-border\" />\n <span className=\"text-xs font-semibold text-muted-foreground\">\n {t('customers.activities.yearSeparator', '{year}', { year: activityYear })}\n </span>\n <div className=\"h-px flex-1 bg-border\" />\n </div>\n )}\n <TimelineEntry\n activity={activity}\n t={t}\n withBorder={index < activities.length - 1}\n onEdit={onEdit}\n onMarkDone={onMarkDone}\n />\n </React.Fragment>\n )\n })}\n </div>\n )\n}\n\nfunction TimelineEntry({\n activity,\n t,\n withBorder,\n onEdit,\n onMarkDone,\n}: {\n activity: InteractionSummary\n t: TranslateFn\n withBorder: boolean\n onEdit?: (activity: InteractionSummary) => void\n onMarkDone?: (activityId: string, updatedAt?: string | null) => void | Promise<void>\n}) {\n const dateStr = activity.scheduledAt ?? activity.occurredAt ?? activity.createdAt\n const TypeIcon = TYPE_ICONS[activity.interactionType]\n const title = activity.title ?? activity.body ?? activity.interactionType\n const duration = activity.duration ? ` (${activity.duration} min)` : ''\n const isOpen = isOpenInteractionStatus(activity.status)\n const [markingDone, setMarkingDone] = React.useState(false)\n\n const handleMarkDone = React.useCallback(async (event: React.MouseEvent | React.KeyboardEvent) => {\n event.stopPropagation()\n if (!onMarkDone || markingDone) return\n setMarkingDone(true)\n try {\n await onMarkDone(activity.id, activity.updatedAt)\n } finally {\n setMarkingDone(false)\n }\n }, [activity.id, markingDone, onMarkDone])\n\n return (\n <div\n className={`py-2.5 ${withBorder ? 'border-b border-border/60' : ''} ${onEdit ? 'cursor-pointer hover:bg-accent/40 transition-colors' : ''}`}\n onClick={() => onEdit?.(activity)}\n role={onEdit ? 'button' : undefined}\n tabIndex={onEdit ? 0 : undefined}\n onKeyDown={onEdit ? (e) => { if (e.key === 'Enter') onEdit(activity) } : undefined}\n >\n <div className=\"grid items-start gap-3\" style={{ gridTemplateColumns: '75px 32px 1fr' }}>\n {/* Column 1: Date */}\n <div className=\"shrink-0\">\n <span className=\"block text-[11px] font-semibold leading-tight text-foreground\">\n {formatRelativeDate(dateStr, t)}\n </span>\n <span className=\"block text-[10px] leading-tight text-muted-foreground\">\n {formatTime(dateStr)}\n </span>\n </div>\n\n {/* Column 2: Type icon */}\n <div className=\"flex size-8 items-center justify-center rounded-md bg-muted shrink-0\">\n {TypeIcon ? <TypeIcon className=\"size-3.5 text-muted-foreground\" /> : null}\n </div>\n\n {/* Column 3: Content */}\n <div className=\"min-w-0 space-y-1.5\">\n <div className=\"flex items-start justify-between gap-2\">\n <span className=\"block text-[12px] font-semibold leading-tight text-foreground\">\n {title}{duration}\n </span>\n {isOpen && onMarkDone ? (\n <Button\n type=\"button\"\n variant=\"default\"\n size=\"sm\"\n disabled={markingDone}\n onClick={handleMarkDone}\n className=\"shrink-0\"\n >\n <Check className=\"size-3.5\" />\n {t('customers.activities.actions.markDone', 'Mark done')}\n </Button>\n ) : null}\n </div>\n\n {activity.body && activity.title && (\n <p className=\"text-[11px] leading-snug text-muted-foreground\">\n {activity.body}\n </p>\n )}\n\n {activity.authorName && (\n <div className=\"flex items-center gap-1 text-[10px] text-muted-foreground\">\n <User className=\"size-2.5 shrink-0\" />\n <span>{t('customers.timeline.author', 'by {{name}}', { name: activity.authorName })}</span>\n </div>\n )}\n\n <AiActionChips activityType={activity.interactionType} />\n </div>\n </div>\n </div>\n )\n}\n\nfunction formatRelativeDate(isoString: string, t: TranslateFn): string {\n try {\n const date = new Date(isoString)\n const now = new Date()\n // Compare calendar dates (not time-based diff) to correctly handle same-day future times\n const dateDay = new Date(date.getFullYear(), date.getMonth(), date.getDate())\n const nowDay = new Date(now.getFullYear(), now.getMonth(), now.getDate())\n const diffDays = Math.round((nowDay.getTime() - dateDay.getTime()) / (1000 * 60 * 60 * 24))\n\n if (diffDays === 0) return t('customers.timeline.date.today', 'today')\n if (diffDays === 1) return t('customers.timeline.date.yesterday', 'yesterday')\n return date.toLocaleDateString(undefined, { day: 'numeric', month: 'short' })\n } catch {\n return ''\n }\n}\n\nfunction formatTime(isoString: string): string {\n try {\n return new Date(isoString).toLocaleTimeString(undefined, { hour: '2-digit', minute: '2-digit' })\n } catch {\n return ''\n }\n}\n"],
5
+ "mappings": "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",
6
6
  "names": []
7
7
  }
@@ -4,7 +4,7 @@ import * as React from "react";
4
4
  import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogDescription } from "@open-mercato/ui/primitives/dialog";
5
5
  import { useT } from "@open-mercato/shared/lib/i18n/context";
6
6
  import { TaskForm } from "./TaskForm.js";
7
- function TaskDialog({ open, mode, onOpenChange, initialValues, onSubmit, isSubmitting, contextMessage }) {
7
+ function TaskDialog({ open, mode, onOpenChange, initialValues, onSubmit, isSubmitting, contextMessage, useCanonicalInteractions = false }) {
8
8
  const t = useT();
9
9
  const dialogTitle = mode === "edit" ? t("customers.people.detail.tasks.dialog.editTitle", "Edit task") : t("customers.people.detail.tasks.dialog.createTitle", "Add task");
10
10
  const handleCancel = React.useCallback(() => {
@@ -29,7 +29,8 @@ function TaskDialog({ open, mode, onOpenChange, initialValues, onSubmit, isSubmi
29
29
  initialValues,
30
30
  onSubmit: handleSubmit,
31
31
  onCancel: handleCancel,
32
- isSubmitting
32
+ isSubmitting,
33
+ useCanonicalInteractions
33
34
  }
34
35
  )
35
36
  ] }) });
@@ -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}\n\nexport function TaskDialog({ open, mode, onOpenChange, initialValues, onSubmit, isSubmitting, contextMessage }: 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 />\n </DialogContent>\n </Dialog>\n )\n}\n"],
5
- "mappings": ";AAyCQ,SACE,KADF;AAvCR,YAAY,WAAW;AACvB,SAAS,QAAQ,eAAe,cAAc,aAAa,yBAAyB;AACpF,SAAS,YAAY;AAErB,SAAS,gBAAgB;AAYlB,SAAS,WAAW,EAAE,MAAM,MAAM,cAAc,eAAe,UAAU,cAAc,eAAe,GAAoB;AAC/H,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,SACE,oBAAC,UAAO,MAAY,cAClB,+BAAC,iBAAc,WAAU,gBACvB;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;AAAA,IACF;AAAA,KACF,GACF;AAEJ;",
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": ";AA2CQ,SACE,KADF;AAzCR,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,SACE,oBAAC,UAAO,MAAY,cAClB,+BAAC,iBAAc,WAAU,gBACvB;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
  }
@@ -1,13 +1,30 @@
1
1
  "use client";
2
- import { jsx } from "react/jsx-runtime";
2
+ import { jsx, jsxs } from "react/jsx-runtime";
3
3
  import * as React from "react";
4
- import { CrudForm } from "@open-mercato/ui/backend/CrudForm";
4
+ import {
5
+ CrudForm
6
+ } from "@open-mercato/ui/backend/CrudForm";
5
7
  import { Button } from "@open-mercato/ui/primitives/button";
8
+ import {
9
+ Select,
10
+ SelectContent,
11
+ SelectItem,
12
+ SelectItemLeading,
13
+ SelectTrigger,
14
+ SelectTriggerLeading,
15
+ SelectValue
16
+ } from "@open-mercato/ui/primitives/select";
17
+ import {
18
+ renderDictionaryColor,
19
+ renderDictionaryIcon
20
+ } from "@open-mercato/core/modules/dictionaries/components/dictionaryAppearance";
6
21
  import { collectCustomFieldValues } from "@open-mercato/ui/backend/utils/customFieldValues";
7
22
  import { createCrudFormError } from "@open-mercato/ui/backend/utils/serverErrors";
8
23
  import { useT } from "@open-mercato/shared/lib/i18n/context";
24
+ import { useOrganizationScopeVersion } from "@open-mercato/shared/lib/frontend/useOrganizationScope";
9
25
  import { CUSTOMER_INTERACTION_ENTITY_ID } from "../../lib/interactionCompatibility.js";
10
26
  import { normalizeCustomFieldSubmitValue } from "./customFieldUtils.js";
27
+ import { useCustomerDictionary } from "./hooks/useCustomerDictionary.js";
11
28
  function TaskForm({
12
29
  mode,
13
30
  initialValues,
@@ -16,76 +33,39 @@ function TaskForm({
16
33
  submitLabel,
17
34
  cancelLabel,
18
35
  isSubmitting = false,
19
- formEntityId = CUSTOMER_INTERACTION_ENTITY_ID
36
+ formEntityId = CUSTOMER_INTERACTION_ENTITY_ID,
37
+ useCanonicalInteractions = false
20
38
  }) {
21
39
  const t = useT();
22
40
  const containerRef = React.useRef(null);
23
- const fields = React.useMemo(() => {
24
- return [
25
- {
26
- id: "title",
27
- label: t("customers.people.detail.tasks.fields.title", "Title"),
28
- type: "text",
29
- required: true,
30
- placeholder: t("customers.people.detail.tasks.fields.titlePlaceholder", "Task title")
31
- },
32
- {
33
- id: "scheduledAt",
34
- label: t("customers.people.detail.tasks.fields.scheduledAt", "Scheduled for"),
35
- type: "datetime",
36
- placeholder: t("customers.people.detail.tasks.fields.scheduledAtPlaceholder", "Choose a date and time")
37
- },
38
- {
39
- id: "priority",
40
- label: t("customers.people.detail.tasks.fields.priority", "Priority"),
41
- type: "number",
42
- placeholder: t("customers.people.detail.tasks.fields.priorityPlaceholder", "0-100")
43
- },
44
- {
45
- id: "description",
46
- label: t("customers.people.detail.tasks.fields.description", "Description"),
47
- type: "textarea",
48
- placeholder: t("customers.people.detail.tasks.fields.descriptionPlaceholder", "Add context, outcome, or follow-up notes"),
49
- layout: "full"
50
- },
51
- {
52
- id: "is_done",
53
- label: t("customers.people.detail.tasks.fields.done", "Mark as done"),
54
- type: "checkbox"
55
- }
56
- ];
57
- }, [t]);
58
- const groups = React.useMemo(() => {
41
+ const scopeVersion = useOrganizationScopeVersion();
42
+ const statusDictionary = useCustomerDictionary("interaction-statuses", scopeVersion);
43
+ const statusOptions = React.useMemo(() => {
44
+ const entries = statusDictionary.data?.entries ?? [];
45
+ if (entries.length > 0) {
46
+ return entries.map((entry) => ({
47
+ value: entry.value,
48
+ label: entry.label,
49
+ color: entry.color ?? null,
50
+ icon: entry.icon ?? null
51
+ }));
52
+ }
59
53
  return [
60
- {
61
- id: "details",
62
- title: t("customers.people.detail.tasks.form.details", "Task details"),
63
- column: 1,
64
- fields: ["title", "scheduledAt", "description"]
65
- },
66
- {
67
- id: "status",
68
- title: t("customers.people.detail.tasks.form.status", "Status"),
69
- column: 2,
70
- fields: ["priority", "is_done"]
71
- },
72
- {
73
- id: "attributes",
74
- title: t("customers.people.detail.tasks.form.customFields", "Task attributes"),
75
- column: 1,
76
- kind: "customFields"
77
- },
78
- {
79
- id: "tips",
80
- title: t("customers.people.detail.tasks.form.tips", "Tips"),
81
- column: 2,
82
- component: () => /* @__PURE__ */ jsx("div", { className: "text-sm text-muted-foreground", children: t(
83
- "customers.people.detail.tasks.form.tipsBody",
84
- 'Tasks save independently from the main customer form. Use clear titles like "Follow up call" or "Send pricing deck".'
85
- ) })
86
- }
54
+ { value: "planned", label: t("customers.interactions.status.planned", "Planned") },
55
+ { value: "in_progress", label: t("customers.interactions.status.in_progress", "In progress") },
56
+ { value: "waiting", label: t("customers.interactions.status.waiting", "Waiting / blocked") },
57
+ { value: "done", label: t("customers.interactions.status.done", "Done") },
58
+ { value: "canceled", label: t("customers.interactions.status.canceled", "Canceled") }
87
59
  ];
88
- }, [t]);
60
+ }, [statusDictionary.data, t]);
61
+ const fields = React.useMemo(
62
+ () => buildTaskFormFields({ useCanonicalInteractions, statusOptions, t }),
63
+ [statusOptions, t, useCanonicalInteractions]
64
+ );
65
+ const groups = React.useMemo(
66
+ () => buildTaskFormGroups({ useCanonicalInteractions, t }),
67
+ [t, useCanonicalInteractions]
68
+ );
89
69
  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)"));
90
70
  const resolvedCancelLabel = cancelLabel ?? t("customers.people.detail.tasks.cancel", "Cancel");
91
71
  const handleKeyDown = React.useCallback(
@@ -109,6 +89,11 @@ function TaskForm({
109
89
  },
110
90
  [onSubmit, t]
111
91
  );
92
+ const resolvedInitialValues = React.useMemo(() => {
93
+ if (mode !== "create") return initialValues;
94
+ if (useCanonicalInteractions) return { status: "planned", ...initialValues ?? {} };
95
+ return initialValues;
96
+ }, [initialValues, mode, useCanonicalInteractions]);
112
97
  return /* @__PURE__ */ jsx("div", { ref: containerRef, onKeyDown: handleKeyDown, children: /* @__PURE__ */ jsx(
113
98
  CrudForm,
114
99
  {
@@ -116,13 +101,122 @@ function TaskForm({
116
101
  entityId: formEntityId,
117
102
  fields,
118
103
  groups,
119
- initialValues,
104
+ initialValues: resolvedInitialValues,
120
105
  submitLabel: resolvedSubmitLabel,
121
106
  onSubmit: handleSubmit,
122
107
  extraActions: /* @__PURE__ */ jsx(Button, { type: "button", variant: "outline", onClick: onCancel, disabled: isSubmitting, children: resolvedCancelLabel })
123
108
  }
124
109
  ) });
125
110
  }
111
+ function TaskStatusSelect({ id, value, setValue, disabled, options, placeholder }) {
112
+ const stringValue = typeof value === "string" ? value : "";
113
+ const activeOption = options.find((option) => option.value === stringValue) ?? null;
114
+ return /* @__PURE__ */ jsxs(
115
+ Select,
116
+ {
117
+ value: stringValue,
118
+ onValueChange: (next) => setValue(next || void 0),
119
+ disabled,
120
+ children: [
121
+ /* @__PURE__ */ jsxs(SelectTrigger, { id, "data-crud-focus-target": "", children: [
122
+ activeOption && (activeOption.icon || activeOption.color) ? /* @__PURE__ */ jsxs(SelectTriggerLeading, { children: [
123
+ renderDictionaryIcon(activeOption.icon, "h-4 w-4"),
124
+ renderDictionaryColor(activeOption.color, "h-3.5 w-3.5 rounded-full")
125
+ ] }) : null,
126
+ /* @__PURE__ */ jsx(SelectValue, { placeholder, children: activeOption?.label })
127
+ ] }),
128
+ /* @__PURE__ */ jsx(SelectContent, { children: options.map((option) => /* @__PURE__ */ jsxs(SelectItem, { value: option.value, children: [
129
+ option.icon || option.color ? /* @__PURE__ */ jsxs(SelectItemLeading, { children: [
130
+ renderDictionaryIcon(option.icon, "h-4 w-4"),
131
+ renderDictionaryColor(option.color, "h-3.5 w-3.5 rounded-full")
132
+ ] }) : null,
133
+ option.label
134
+ ] }, option.value)) })
135
+ ]
136
+ }
137
+ );
138
+ }
139
+ function buildTaskFormFields(options) {
140
+ const { useCanonicalInteractions, statusOptions, t } = options;
141
+ const statusField = useCanonicalInteractions ? {
142
+ id: "status",
143
+ label: t("customers.people.detail.tasks.fields.status", "Status"),
144
+ type: "custom",
145
+ component: (props) => /* @__PURE__ */ jsx(
146
+ TaskStatusSelect,
147
+ {
148
+ ...props,
149
+ options: statusOptions,
150
+ placeholder: t("customers.people.detail.tasks.fields.statusPlaceholder", "Select a status")
151
+ }
152
+ )
153
+ } : {
154
+ id: "is_done",
155
+ label: t("customers.people.detail.tasks.fields.done", "Mark as done"),
156
+ type: "checkbox"
157
+ };
158
+ return [
159
+ {
160
+ id: "title",
161
+ label: t("customers.people.detail.tasks.fields.title", "Title"),
162
+ type: "text",
163
+ required: true,
164
+ placeholder: t("customers.people.detail.tasks.fields.titlePlaceholder", "Task title")
165
+ },
166
+ {
167
+ id: "scheduledAt",
168
+ label: t("customers.people.detail.tasks.fields.scheduledAt", "Scheduled for"),
169
+ type: "datetime",
170
+ placeholder: t("customers.people.detail.tasks.fields.scheduledAtPlaceholder", "Choose a date and time")
171
+ },
172
+ {
173
+ id: "priority",
174
+ label: t("customers.people.detail.tasks.fields.priority", "Priority"),
175
+ type: "number",
176
+ placeholder: t("customers.people.detail.tasks.fields.priorityPlaceholder", "0-100")
177
+ },
178
+ {
179
+ id: "description",
180
+ label: t("customers.people.detail.tasks.fields.description", "Description"),
181
+ type: "textarea",
182
+ placeholder: t("customers.people.detail.tasks.fields.descriptionPlaceholder", "Add context, outcome, or follow-up notes"),
183
+ layout: "full"
184
+ },
185
+ statusField
186
+ ];
187
+ }
188
+ function buildTaskFormGroups(options) {
189
+ const { useCanonicalInteractions, t } = options;
190
+ return [
191
+ {
192
+ id: "details",
193
+ title: t("customers.people.detail.tasks.form.details", "Task details"),
194
+ column: 1,
195
+ fields: ["title", "scheduledAt", "description"]
196
+ },
197
+ {
198
+ id: "status",
199
+ title: t("customers.people.detail.tasks.form.status", "Status"),
200
+ column: 2,
201
+ fields: ["priority", useCanonicalInteractions ? "status" : "is_done"]
202
+ },
203
+ {
204
+ id: "attributes",
205
+ title: t("customers.people.detail.tasks.form.customFields", "Task attributes"),
206
+ column: 1,
207
+ kind: "customFields"
208
+ },
209
+ {
210
+ id: "tips",
211
+ title: t("customers.people.detail.tasks.form.tips", "Tips"),
212
+ column: 2,
213
+ component: () => /* @__PURE__ */ jsx("div", { className: "text-sm text-muted-foreground", children: t(
214
+ "customers.people.detail.tasks.form.tipsBody",
215
+ 'Tasks save independently from the main customer form. Use clear titles like "Follow up call" or "Send pricing deck".'
216
+ ) })
217
+ }
218
+ ];
219
+ }
126
220
  function buildTaskSubmitPayload(values, t) {
127
221
  const rawTitle = typeof values.title === "string" ? values.title.trim() : "";
128
222
  if (!rawTitle.length) {
@@ -141,8 +235,13 @@ function buildTaskSubmitPayload(values, t) {
141
235
  }
142
236
  const rawDescription = typeof values.description === "string" ? values.description.trim() : "";
143
237
  const rawScheduledAt = typeof values.scheduledAt === "string" ? values.scheduledAt.trim() : "";
238
+ const rawStatus = typeof values.status === "string" ? values.status.trim() : "";
239
+ const status = rawStatus.length ? rawStatus : void 0;
144
240
  const base = { title: rawTitle };
145
- if (typeof values.is_done === "boolean") {
241
+ if (status !== void 0) {
242
+ base.status = status;
243
+ base.is_done = status === "done";
244
+ } else if (typeof values.is_done === "boolean") {
146
245
  base.is_done = values.is_done;
147
246
  }
148
247
  base.description = rawDescription || null;
@@ -155,6 +254,8 @@ function buildTaskSubmitPayload(values, t) {
155
254
  }
156
255
  export {
157
256
  TaskForm,
257
+ buildTaskFormFields,
258
+ buildTaskFormGroups,
158
259
  buildTaskSubmitPayload
159
260
  };
160
261
  //# sourceMappingURL=TaskForm.js.map
@@ -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 { CrudForm, type CrudField, type CrudFormGroup } from '@open-mercato/ui/backend/CrudForm'\nimport { Button } from '@open-mercato/ui/primitives/button'\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 { CUSTOMER_INTERACTION_ENTITY_ID } from '../../lib/interactionCompatibility'\nimport type { TaskFormPayload } from './hooks/usePersonTasks'\nimport { normalizeCustomFieldSubmitValue } from './customFieldUtils'\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\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}: TaskFormProps) {\n const t = useT()\n const containerRef = React.useRef<HTMLDivElement | null>(null)\n\n const fields = React.useMemo<CrudField[]>(() => {\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 {\n id: 'is_done',\n label: t('customers.people.detail.tasks.fields.done', 'Mark as done'),\n type: 'checkbox',\n },\n ]\n }, [t])\n\n const groups = React.useMemo<CrudFormGroup[]>(() => {\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', '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 }, [t])\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 return (\n <div ref={containerRef} onKeyDown={handleKeyDown}>\n <CrudForm\n embedded\n entityId={formEntityId}\n fields={fields}\n groups={groups}\n initialValues={initialValues}\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\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 base: TaskFormPayload['base'] = { title: rawTitle }\n 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": "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",
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": "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",
6
6
  "names": []
7
7
  }
@@ -5,6 +5,9 @@ import Link from "next/link";
5
5
  import { Loader2, Pencil, Trash2 } from "lucide-react";
6
6
  import { Button } from "@open-mercato/ui/primitives/button";
7
7
  import { IconButton } from "@open-mercato/ui/primitives/icon-button";
8
+ import { StatusBadge } from "@open-mercato/ui/primitives/status-badge";
9
+ import { mapDictionaryColorToTone } from "@open-mercato/shared/lib/query/advanced-filter";
10
+ import { useOrganizationScopeVersion } from "@open-mercato/shared/lib/frontend/useOrganizationScope";
8
11
  import { flash } from "@open-mercato/ui/backend/FlashMessages";
9
12
  import { LoadingMessage, TabEmptyState } from "@open-mercato/ui/backend/detail";
10
13
  import { cn } from "@open-mercato/shared/lib/utils";
@@ -15,6 +18,7 @@ import { formatDateTime } from "@open-mercato/shared/lib/time";
15
18
  import { TimelineItemHeader } from "./TimelineItemHeader.js";
16
19
  import { TaskDialog } from "./TaskDialog.js";
17
20
  import { usePersonTasks } from "./hooks/usePersonTasks.js";
21
+ import { useCustomerDictionary } from "./hooks/useCustomerDictionary.js";
18
22
  import { useInteractions } from "./hooks/useInteractions.js";
19
23
  import { mapInteractionRecordToTodoSummary } from "../../lib/interactionCompatibility.js";
20
24
  const RESERVED_TASK_CUSTOM_KEYS = /* @__PURE__ */ new Set(["priority", "description", "due_at", "dueAt"]);
@@ -38,11 +42,16 @@ function sortTaskSummaries(tasks) {
38
42
  return left.id.localeCompare(right.id);
39
43
  });
40
44
  }
45
+ const STATUS_BADGE_VARIANTS = /* @__PURE__ */ new Set(["success", "warning", "error", "info", "neutral"]);
46
+ function coerceStatusBadgeVariant(tone) {
47
+ return tone && STATUS_BADGE_VARIANTS.has(tone) ? tone : "neutral";
48
+ }
41
49
  function buildInitialFormValues(task) {
42
50
  if (!task) return void 0;
43
51
  const values = {
44
52
  title: task.title ?? "",
45
53
  is_done: task.isDone ?? false,
54
+ status: task.status ?? "planned",
46
55
  description: task.description ?? "",
47
56
  priority: task.priority ?? "",
48
57
  scheduledAt: task.dueAt ?? ""
@@ -84,6 +93,9 @@ function TasksSection({
84
93
  },
85
94
  [runGuardedMutation]
86
95
  );
96
+ const scopeVersion = useOrganizationScopeVersion();
97
+ const statusDictionary = useCustomerDictionary("interaction-statuses", scopeVersion);
98
+ const statusMap = statusDictionary.data?.map ?? null;
87
99
  const legacyResult = usePersonTasks({ entityId, initialTasks });
88
100
  const canonicalResult = useInteractions({
89
101
  entityId: useCanonicalInteractions ? entityId : null,
@@ -100,7 +112,7 @@ function TasksSection({
100
112
  entityId,
101
113
  interactionType: "task",
102
114
  title: payload.base.title,
103
- status: payload.base.is_done ? "done" : "planned",
115
+ status: payload.base.status ?? (payload.base.is_done ? "done" : "planned"),
104
116
  priority: payload.base.priority ?? null,
105
117
  body: payload.base.description ?? null,
106
118
  scheduledAt: payload.base.scheduledAt ?? null,
@@ -114,7 +126,7 @@ function TasksSection({
114
126
  async (task, payload) => {
115
127
  await canonicalResult.updateInteraction(task.todoId, {
116
128
  title: payload.base.title,
117
- status: payload.base.is_done ? "done" : "planned",
129
+ status: payload.base.status ?? (payload.base.is_done ? "done" : "planned"),
118
130
  priority: payload.base.priority ?? null,
119
131
  body: payload.base.description ?? null,
120
132
  scheduledAt: payload.base.scheduledAt ?? null,
@@ -403,7 +415,17 @@ function TasksSection({
403
415
  ),
404
416
  children: title
405
417
  }
406
- )
418
+ ),
419
+ task.status ? /* @__PURE__ */ jsx(
420
+ StatusBadge,
421
+ {
422
+ variant: coerceStatusBadgeVariant(
423
+ mapDictionaryColorToTone(statusMap?.[task.status]?.color ?? null)
424
+ ),
425
+ dot: true,
426
+ children: statusMap?.[task.status]?.label ?? task.status
427
+ }
428
+ ) : null
407
429
  ] }),
408
430
  timestamp: task.createdAt,
409
431
  fallbackTimestampLabel: createdLabel
@@ -480,7 +502,8 @@ function TasksSection({
480
502
  initialValues: buildInitialFormValues(editingTask),
481
503
  onSubmit: handleDialogSubmit,
482
504
  isSubmitting: isMutating,
483
- contextMessage: dialogContextMessage
505
+ contextMessage: dialogContextMessage,
506
+ useCanonicalInteractions
484
507
  }
485
508
  )
486
509
  ] });