@open-mercato/ui 0.6.2-develop.3467.1.2a1818709d → 0.6.2

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 (71) hide show
  1. package/dist/ai/conversation-store.js +2 -0
  2. package/dist/ai/conversation-store.js.map +2 -2
  3. package/dist/ai/parts/MutationPreviewCard.js +4 -3
  4. package/dist/ai/parts/MutationPreviewCard.js.map +2 -2
  5. package/dist/ai/parts/MutationResultCard.js +9 -16
  6. package/dist/ai/parts/MutationResultCard.js.map +2 -2
  7. package/dist/backend/CrudForm.js +2 -1
  8. package/dist/backend/CrudForm.js.map +2 -2
  9. package/dist/backend/FilterOverlay.js +1 -1
  10. package/dist/backend/FilterOverlay.js.map +1 -1
  11. package/dist/backend/dashboard/widgetRegistry.js +2 -1
  12. package/dist/backend/dashboard/widgetRegistry.js.map +2 -2
  13. package/dist/backend/date-range/dateRanges.js +16 -18
  14. package/dist/backend/date-range/dateRanges.js.map +2 -2
  15. package/dist/backend/detail/AttachmentMetadataDialog.js +3 -1
  16. package/dist/backend/detail/AttachmentMetadataDialog.js.map +2 -2
  17. package/dist/backend/detail/InlineEditors.js +1 -1
  18. package/dist/backend/detail/InlineEditors.js.map +1 -1
  19. package/dist/backend/injection/useAppEvent.js +2 -7
  20. package/dist/backend/injection/useAppEvent.js.map +2 -2
  21. package/dist/backend/injection/widgetRegistry.js +2 -1
  22. package/dist/backend/injection/widgetRegistry.js.map +2 -2
  23. package/dist/backend/messages/useMessageCompose.js +62 -5
  24. package/dist/backend/messages/useMessageCompose.js.map +2 -2
  25. package/dist/backend/messages/useMessageComposeOperations.js +64 -5
  26. package/dist/backend/messages/useMessageComposeOperations.js.map +2 -2
  27. package/dist/backend/schedule/ScheduleCalendar.js +11 -1
  28. package/dist/backend/schedule/ScheduleCalendar.js.map +2 -2
  29. package/dist/backend/schedule/ScheduleToolbar.js +11 -1
  30. package/dist/backend/schedule/ScheduleToolbar.js.map +2 -2
  31. package/dist/primitives/calendar.js +1 -1
  32. package/dist/primitives/calendar.js.map +1 -1
  33. package/dist/primitives/date-format.js +1 -1
  34. package/dist/primitives/date-format.js.map +1 -1
  35. package/dist/primitives/date-picker-helpers.js +7 -9
  36. package/dist/primitives/date-picker-helpers.js.map +2 -2
  37. package/dist/primitives/date-picker.js +1 -1
  38. package/dist/primitives/date-picker.js.map +2 -2
  39. package/dist/primitives/date-range-picker.js +1 -1
  40. package/dist/primitives/date-range-picker.js.map +2 -2
  41. package/jest.config.cjs +4 -1
  42. package/package.json +6 -7
  43. package/src/ai/__tests__/AiAssistantLauncher.test.tsx +1 -1
  44. package/src/ai/__tests__/AiChat.test.tsx +17 -2
  45. package/src/ai/__tests__/conversation-store.test.ts +29 -0
  46. package/src/ai/conversation-store.ts +2 -0
  47. package/src/ai/parts/MutationPreviewCard.tsx +25 -20
  48. package/src/ai/parts/MutationResultCard.tsx +15 -27
  49. package/src/ai/parts/__tests__/MutationPreviewCard.test.tsx +26 -1
  50. package/src/ai/parts/__tests__/MutationResultCard.test.tsx +16 -1
  51. package/src/backend/CrudForm.tsx +3 -2
  52. package/src/backend/FilterOverlay.tsx +1 -1
  53. package/src/backend/__tests__/AppShell.test.tsx +1 -1
  54. package/src/backend/__tests__/SidebarCustomizationEditor.test.tsx +8 -1
  55. package/src/backend/dashboard/widgetRegistry.ts +2 -1
  56. package/src/backend/date-range/dateRanges.ts +16 -18
  57. package/src/backend/detail/AttachmentMetadataDialog.tsx +1 -1
  58. package/src/backend/detail/InlineEditors.tsx +1 -1
  59. package/src/backend/injection/__tests__/useAppEvent.test.ts +16 -0
  60. package/src/backend/injection/useAppEvent.ts +2 -9
  61. package/src/backend/injection/widgetRegistry.ts +2 -1
  62. package/src/backend/messages/__tests__/MessageComposer.test.tsx +77 -0
  63. package/src/backend/messages/useMessageCompose.ts +64 -5
  64. package/src/backend/messages/useMessageComposeOperations.ts +70 -4
  65. package/src/backend/schedule/ScheduleCalendar.tsx +11 -1
  66. package/src/backend/schedule/ScheduleToolbar.tsx +11 -1
  67. package/src/primitives/calendar.tsx +2 -2
  68. package/src/primitives/date-format.ts +2 -2
  69. package/src/primitives/date-picker-helpers.ts +7 -9
  70. package/src/primitives/date-picker.tsx +2 -2
  71. package/src/primitives/date-range-picker.tsx +2 -2
@@ -1,7 +1,7 @@
1
1
  "use client";
2
2
  import { Fragment, jsx, jsxs } from "react/jsx-runtime";
3
3
  import * as React from "react";
4
- import { format } from "date-fns";
4
+ import { format } from "date-fns/format";
5
5
  import { Button } from "../primitives/button.js";
6
6
  import { Checkbox } from "../primitives/checkbox.js";
7
7
  import { DateRangePicker } from "../primitives/date-range-picker.js";
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../src/backend/FilterOverlay.tsx"],
4
- "sourcesContent": ["\"use client\"\nimport * as React from 'react'\nimport { format } from 'date-fns'\nimport { Button } from '../primitives/button'\nimport { Checkbox } from '../primitives/checkbox'\nimport { DateRangePicker } from '../primitives/date-range-picker'\nimport type { DateRange } from './date-range/dateRanges'\nimport {\n Select,\n SelectContent,\n SelectItem,\n SelectTrigger,\n SelectValue,\n} from '../primitives/select'\nimport { ComboboxInput } from './inputs/ComboboxInput'\nimport { TagsInput, type TagsInputOption } from './inputs/TagsInput'\nimport { useT } from '@open-mercato/shared/lib/i18n/context'\n\nexport type FilterOption = { value: string; label: string; description?: string | null }\n\nexport type FilterDef = {\n id: string\n label: string\n type: 'text' | 'select' | 'checkbox' | 'dateRange' | 'tags' | 'combobox'\n options?: FilterOption[]\n // Optional async loader for options (used by select/tags/combobox)\n loadOptions?: (query?: string) => Promise<FilterOption[]>\n multiple?: boolean\n placeholder?: string\n group?: string\n formatValue?: (value: string) => string\n formatDescription?: (value: string) => string | null | undefined\n}\n\nexport type FilterValues = Record<string, any>\n\nexport type FilterOverlayProps = {\n title?: string\n filters: FilterDef[]\n initialValues: FilterValues\n open: boolean\n onOpenChange: (open: boolean) => void\n onApply: (values: FilterValues) => void\n onClear?: () => void\n extraContent?: React.ReactNode\n}\n\nconst EMPTY_FILTER_VALUES: FilterValues = {}\n\nfunction isPlainObject(value: unknown): value is Record<string, unknown> {\n return value != null && typeof value === 'object' && !Array.isArray(value)\n}\n\nfunction normalizeKeys(source: FilterValues | null | undefined): string[] {\n if (!source) return []\n return Object.keys(source).filter((key) => source[key] !== undefined)\n}\n\nfunction areFieldValuesEqual(a: any, b: any): boolean {\n if (a === b) return true\n if (Array.isArray(a) && Array.isArray(b)) {\n if (a.length !== b.length) return false\n for (let i = 0; i < a.length; i += 1) {\n if (!areFieldValuesEqual(a[i], b[i])) return false\n }\n return true\n }\n if (isPlainObject(a) && isPlainObject(b)) {\n const keysA = normalizeKeys(a as FilterValues)\n const keysB = normalizeKeys(b as FilterValues)\n if (keysA.length !== keysB.length) return false\n for (const key of keysA) {\n if (!keysB.includes(key)) return false\n if (!areFieldValuesEqual((a as FilterValues)[key], (b as FilterValues)[key])) return false\n }\n return true\n }\n return false\n}\n\nfunction areFilterValuesEqual(a?: FilterValues | null, b?: FilterValues | null): boolean {\n if (a === b) return true\n const keysA = normalizeKeys(a || EMPTY_FILTER_VALUES)\n const keysB = normalizeKeys(b || EMPTY_FILTER_VALUES)\n if (keysA.length !== keysB.length) return false\n for (const key of keysA) {\n if (!keysB.includes(key)) return false\n if (!areFieldValuesEqual(a?.[key], b?.[key])) return false\n }\n return true\n}\n\nexport function FilterOverlay({\n title,\n filters,\n initialValues,\n open,\n onOpenChange,\n onApply,\n onClear,\n extraContent,\n}: FilterOverlayProps) {\n const t = useT()\n const defaultTitle = title ?? t('ui.filters.title', 'Filters')\n const [values, setValues] = React.useState<FilterValues>(initialValues)\n React.useEffect(() => {\n setValues((prev) => (areFilterValuesEqual(prev, initialValues) ? prev : initialValues))\n }, [initialValues])\n const filtersSignature = React.useMemo(\n () => filters.map((f) => `${f.id}:${f.type}:${Boolean((f as any).loadOptions)}:${(f.options || []).length}`).join('|'),\n [filters]\n )\n const lastLoadedSignatureRef = React.useRef<string | null>(null)\n // eslint-disable-next-line react-hooks/exhaustive-deps\n const stableFilters = React.useMemo(() => filters, [filtersSignature])\n\n // Load dynamic options for filters that request it\n const [dynamicOptions, setDynamicOptions] = React.useState<Record<string, FilterOption[]>>({})\n React.useEffect(() => {\n if (!open) return\n if (lastLoadedSignatureRef.current === filtersSignature) return\n lastLoadedSignatureRef.current = filtersSignature\n setDynamicOptions({})\n let cancelled = false\n const loadAll = async () => {\n const loaders = filters\n .filter((f): f is FilterDef & { loadOptions: (query?: string) => Promise<FilterOption[]> } => (f as any).loadOptions != null)\n .map(async (f) => {\n try {\n const opts = await (f as any).loadOptions()\n if (!cancelled) setDynamicOptions((prev) => ({ ...prev, [f.id]: opts }))\n } catch {\n // ignore\n }\n })\n await Promise.all(loaders)\n }\n loadAll()\n return () => {\n cancelled = true\n }\n }, [filters, filtersSignature, open])\n React.useEffect(() => {\n if (!open) {\n lastLoadedSignatureRef.current = null\n }\n }, [open])\n\n const setValue = (id: string, v: any) => setValues((prev) => ({ ...prev, [id]: v }))\n\n const handleApply = () => {\n onApply(values)\n onOpenChange(false)\n }\n\n const handleClear = () => {\n setValues({})\n onClear?.()\n }\n\n const tagLoaders = React.useMemo(() => {\n const map = new Map<string, (q?: string) => Promise<Array<string | TagsInputOption>>>()\n for (const f of stableFilters) {\n if (f.type === 'tags' && typeof f.loadOptions === 'function') {\n const fieldId = f.id\n const load = f.loadOptions as (query?: string) => Promise<FilterOption[]>\n map.set(fieldId, async (q?: string) => {\n const query = (q ?? '').trim()\n if (!query.length) return []\n try {\n const opts = await load(query)\n setDynamicOptions((prev) => ({ ...prev, [fieldId]: opts }))\n return opts.map((o) => ({ value: o.value, label: o.label, description: o.description ?? null }))\n } catch {\n return []\n }\n })\n }\n }\n return map\n }, [stableFilters])\n\n const comboboxLoaders = React.useMemo(() => {\n const map = new Map<string, (q?: string) => Promise<FilterOption[]>>()\n for (const f of stableFilters) {\n if (f.type === 'combobox' && typeof f.loadOptions === 'function') {\n map.set(f.id, async (query?: string) => {\n try {\n const opts = await f.loadOptions?.(query)\n setDynamicOptions((prev) => ({ ...prev, [f.id]: opts ?? [] }))\n return opts ?? []\n } catch {\n return []\n }\n })\n }\n }\n return map\n }, [stableFilters])\n\n return (\n <>\n {open && (\n <div className=\"fixed inset-0 z-modal\">\n <div className=\"absolute inset-0 bg-black/20\" onClick={() => onOpenChange(false)} role=\"presentation\" />\n <div className=\"absolute left-0 top-0 h-full w-full sm:w-[380px] bg-background shadow-xl border-r flex flex-col\">\n <div className=\"flex items-center justify-between p-4 border-b\">\n <h2 className=\"text-base font-semibold\">{defaultTitle}</h2>\n <Button variant=\"muted\" size=\"sm\" onClick={() => onOpenChange(false)}>{t('common.close')}</Button>\n </div>\n {/* Top actions: duplicate Clear/Apply */}\n <div className=\"px-4 py-2 border-b flex items-center justify-between gap-2\">\n <Button variant=\"outline\" size=\"sm\" onClick={handleClear}>{t('ui.filters.actions.clear', 'Clear')}</Button>\n <Button size=\"sm\" onClick={handleApply}>\n <svg width=\"14\" height=\"14\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" strokeWidth=\"2\" aria-hidden=\"true\" className=\"opacity-80\"><path d=\"M3 4h18\"/><path d=\"M6 8h12l-3 8H9L6 8z\"/></svg>\n {t('ui.filters.actions.apply', 'Apply')}\n </Button>\n </div>\n <div className=\"flex-1 overflow-auto p-4 space-y-4\">\n {extraContent ? <div className=\"space-y-2 rounded-md border bg-muted/30 p-3\">{extraContent}</div> : null}\n {filters.map((f) => (\n <div key={f.id} className=\"space-y-2\">\n <div className=\"text-sm font-medium\">{f.label}</div>\n {f.type === 'text' && (\n <input\n type=\"text\"\n className=\"w-full h-11 rounded border px-2 text-sm\"\n placeholder={f.placeholder}\n value={values[f.id] ?? ''}\n onChange={(e) => setValue(f.id, e.target.value || undefined)}\n />\n )}\n {f.type === 'dateRange' && (() => {\n const fromStr = values[f.id]?.from\n const toStr = values[f.id]?.to\n const parseISODate = (input: unknown): Date | null => {\n if (typeof input !== 'string' || !input.length) return null\n const candidate = new Date(input)\n return Number.isNaN(candidate.getTime()) ? null : candidate\n }\n const fromDate = parseISODate(fromStr)\n const toDate = parseISODate(toStr)\n const rangeValue: DateRange | null =\n fromDate && toDate\n ? { start: fromDate, end: toDate }\n : fromDate\n ? { start: fromDate, end: fromDate }\n : toDate\n ? { start: toDate, end: toDate }\n : null\n return (\n <DateRangePicker\n value={rangeValue}\n onChange={(next) => {\n if (!next) {\n setValue(f.id, { from: undefined, to: undefined })\n return\n }\n setValue(f.id, {\n from: format(next.start, 'yyyy-MM-dd'),\n to: format(next.end, 'yyyy-MM-dd'),\n })\n }}\n placeholder={t('ui.filters.dateRange.placeholder', 'Pick a date range')}\n />\n )\n })()}\n {f.type === 'select' && (\n <div className=\"space-y-1\">\n {f.multiple ? (\n <div className=\"flex flex-col gap-1\">\n {(f.options || dynamicOptions[f.id] || []).map((opt) => {\n const arr: string[] = Array.isArray(values[f.id]) ? values[f.id] : []\n const checked = arr.includes(opt.value)\n return (\n <label key={opt.value} className=\"inline-flex items-center gap-2 cursor-pointer\">\n <Checkbox\n checked={checked}\n onCheckedChange={(next) => {\n const set = new Set(arr)\n if (next === true) set.add(opt.value)\n else set.delete(opt.value)\n setValue(f.id, Array.from(set))\n }}\n />\n <span className=\"text-sm\">{opt.label}</span>\n </label>\n )\n })}\n </div>\n ) : (\n <Select\n value={values[f.id] || undefined}\n onValueChange={(next) => setValue(f.id, next || undefined)}\n >\n <SelectTrigger size=\"lg\">\n <SelectValue placeholder={t('ui.forms.select.emptyOption', '\u2014')} />\n </SelectTrigger>\n <SelectContent>\n {(f.options || dynamicOptions[f.id] || [])\n .filter((opt) => opt.value !== '')\n .map((opt) => (\n <SelectItem key={opt.value} value={opt.value}>{opt.label}</SelectItem>\n ))}\n </SelectContent>\n </Select>\n )}\n </div>\n )}\n {f.type === 'combobox' && (() => {\n const staticOptions = f.options || []\n const dynamic = dynamicOptions[f.id] || []\n const optionMap = new Map<string, FilterOption>()\n staticOptions.forEach((opt) => optionMap.set(opt.value, opt))\n dynamic.forEach((opt) => optionMap.set(opt.value, opt))\n const currentValue = typeof values[f.id] === 'string' ? values[f.id] : ''\n const suggestions = Array.from(optionMap.values()).map((opt) => ({\n value: opt.value,\n label: opt.label,\n description: opt.description ?? null,\n }))\n const loadSuggestions = comboboxLoaders.get(f.id)\n return (\n <div className=\"flex items-start gap-2\">\n <div className=\"min-w-0 flex-1\">\n <ComboboxInput\n value={currentValue}\n onChange={(next) => setValue(f.id, next.trim().length ? next : undefined)}\n suggestions={suggestions}\n loadSuggestions={loadSuggestions}\n resolveLabel={(value) => f.formatValue?.(value) ?? optionMap.get(value)?.label ?? value}\n resolveDescription={(value) => optionMap.get(value)?.description ?? null}\n placeholder={f.placeholder}\n allowCustomValues={false}\n />\n </div>\n {currentValue ? (\n <Button\n type=\"button\"\n variant=\"outline\"\n size=\"sm\"\n className=\"shrink-0\"\n onClick={() => setValue(f.id, undefined)}\n >\n {t('ui.filters.actions.clear', 'Clear')}\n </Button>\n ) : null}\n </div>\n )\n })()}\n {f.type === 'tags' && (() => {\n const arr: string[] = Array.isArray(values[f.id]) ? values[f.id] : []\n const staticOptions = f.options || []\n const dynamic = dynamicOptions[f.id] || []\n const optionMap = new Map<string, FilterOption>()\n staticOptions.forEach((opt) => optionMap.set(opt.value, opt))\n dynamic.forEach((opt) => optionMap.set(opt.value, opt))\n const loadSuggestions = tagLoaders.get(f.id)\n const resolveTagLabel = f.formatValue\n ? (val: string) => f.formatValue!(val)\n : (val: string) => optionMap.get(val)?.label ?? val\n const resolveTagDescription = f.formatDescription\n ? (val: string) => f.formatDescription!(val) ?? null\n : (val: string) => optionMap.get(val)?.description ?? null\n const suggestionList: TagsInputOption[] = Array.from(optionMap.values()).map((opt) => ({\n value: opt.value,\n label: opt.label,\n description: opt.description ?? null,\n }))\n return (\n <TagsInput\n value={arr}\n suggestions={suggestionList}\n loadSuggestions={loadSuggestions}\n allowCustomValues={false}\n resolveLabel={resolveTagLabel}\n resolveDescription={resolveTagDescription}\n placeholder={f.placeholder}\n onChange={(next) => setValue(f.id, next.length ? next : undefined)}\n />\n )\n })()}\n {f.type === 'checkbox' && (\n <div>\n <Select\n value={values[f.id] === true ? 'true' : values[f.id] === false ? 'false' : undefined}\n onValueChange={(next) => {\n if (!next) setValue(f.id, undefined)\n else if (next === 'true') setValue(f.id, true)\n else if (next === 'false') setValue(f.id, false)\n }}\n >\n <SelectTrigger size=\"lg\">\n <SelectValue placeholder={t('ui.forms.select.emptyOption', '\u2014')} />\n </SelectTrigger>\n <SelectContent>\n <SelectItem value=\"true\">{t('common.yes', 'Yes')}</SelectItem>\n <SelectItem value=\"false\">{t('common.no', 'No')}</SelectItem>\n </SelectContent>\n </Select>\n </div>\n )}\n </div>\n ))}\n </div>\n <div className=\"p-4 border-t flex items-center justify-between gap-2\">\n <Button variant=\"outline\" onClick={handleClear}>{t('ui.filters.actions.clear', 'Clear')}</Button>\n <Button onClick={handleApply}>\n <svg width=\"14\" height=\"14\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" strokeWidth=\"2\" aria-hidden=\"true\" className=\"opacity-80\"><path d=\"M3 4h18\"/><path d=\"M6 8h12l-3 8H9L6 8z\"/></svg>\n {t('ui.filters.actions.apply', 'Apply')}\n </Button>\n </div>\n </div>\n </div>\n )}\n </>\n )\n}\n"],
4
+ "sourcesContent": ["\"use client\"\nimport * as React from 'react'\nimport { format } from 'date-fns/format'\nimport { Button } from '../primitives/button'\nimport { Checkbox } from '../primitives/checkbox'\nimport { DateRangePicker } from '../primitives/date-range-picker'\nimport type { DateRange } from './date-range/dateRanges'\nimport {\n Select,\n SelectContent,\n SelectItem,\n SelectTrigger,\n SelectValue,\n} from '../primitives/select'\nimport { ComboboxInput } from './inputs/ComboboxInput'\nimport { TagsInput, type TagsInputOption } from './inputs/TagsInput'\nimport { useT } from '@open-mercato/shared/lib/i18n/context'\n\nexport type FilterOption = { value: string; label: string; description?: string | null }\n\nexport type FilterDef = {\n id: string\n label: string\n type: 'text' | 'select' | 'checkbox' | 'dateRange' | 'tags' | 'combobox'\n options?: FilterOption[]\n // Optional async loader for options (used by select/tags/combobox)\n loadOptions?: (query?: string) => Promise<FilterOption[]>\n multiple?: boolean\n placeholder?: string\n group?: string\n formatValue?: (value: string) => string\n formatDescription?: (value: string) => string | null | undefined\n}\n\nexport type FilterValues = Record<string, any>\n\nexport type FilterOverlayProps = {\n title?: string\n filters: FilterDef[]\n initialValues: FilterValues\n open: boolean\n onOpenChange: (open: boolean) => void\n onApply: (values: FilterValues) => void\n onClear?: () => void\n extraContent?: React.ReactNode\n}\n\nconst EMPTY_FILTER_VALUES: FilterValues = {}\n\nfunction isPlainObject(value: unknown): value is Record<string, unknown> {\n return value != null && typeof value === 'object' && !Array.isArray(value)\n}\n\nfunction normalizeKeys(source: FilterValues | null | undefined): string[] {\n if (!source) return []\n return Object.keys(source).filter((key) => source[key] !== undefined)\n}\n\nfunction areFieldValuesEqual(a: any, b: any): boolean {\n if (a === b) return true\n if (Array.isArray(a) && Array.isArray(b)) {\n if (a.length !== b.length) return false\n for (let i = 0; i < a.length; i += 1) {\n if (!areFieldValuesEqual(a[i], b[i])) return false\n }\n return true\n }\n if (isPlainObject(a) && isPlainObject(b)) {\n const keysA = normalizeKeys(a as FilterValues)\n const keysB = normalizeKeys(b as FilterValues)\n if (keysA.length !== keysB.length) return false\n for (const key of keysA) {\n if (!keysB.includes(key)) return false\n if (!areFieldValuesEqual((a as FilterValues)[key], (b as FilterValues)[key])) return false\n }\n return true\n }\n return false\n}\n\nfunction areFilterValuesEqual(a?: FilterValues | null, b?: FilterValues | null): boolean {\n if (a === b) return true\n const keysA = normalizeKeys(a || EMPTY_FILTER_VALUES)\n const keysB = normalizeKeys(b || EMPTY_FILTER_VALUES)\n if (keysA.length !== keysB.length) return false\n for (const key of keysA) {\n if (!keysB.includes(key)) return false\n if (!areFieldValuesEqual(a?.[key], b?.[key])) return false\n }\n return true\n}\n\nexport function FilterOverlay({\n title,\n filters,\n initialValues,\n open,\n onOpenChange,\n onApply,\n onClear,\n extraContent,\n}: FilterOverlayProps) {\n const t = useT()\n const defaultTitle = title ?? t('ui.filters.title', 'Filters')\n const [values, setValues] = React.useState<FilterValues>(initialValues)\n React.useEffect(() => {\n setValues((prev) => (areFilterValuesEqual(prev, initialValues) ? prev : initialValues))\n }, [initialValues])\n const filtersSignature = React.useMemo(\n () => filters.map((f) => `${f.id}:${f.type}:${Boolean((f as any).loadOptions)}:${(f.options || []).length}`).join('|'),\n [filters]\n )\n const lastLoadedSignatureRef = React.useRef<string | null>(null)\n // eslint-disable-next-line react-hooks/exhaustive-deps\n const stableFilters = React.useMemo(() => filters, [filtersSignature])\n\n // Load dynamic options for filters that request it\n const [dynamicOptions, setDynamicOptions] = React.useState<Record<string, FilterOption[]>>({})\n React.useEffect(() => {\n if (!open) return\n if (lastLoadedSignatureRef.current === filtersSignature) return\n lastLoadedSignatureRef.current = filtersSignature\n setDynamicOptions({})\n let cancelled = false\n const loadAll = async () => {\n const loaders = filters\n .filter((f): f is FilterDef & { loadOptions: (query?: string) => Promise<FilterOption[]> } => (f as any).loadOptions != null)\n .map(async (f) => {\n try {\n const opts = await (f as any).loadOptions()\n if (!cancelled) setDynamicOptions((prev) => ({ ...prev, [f.id]: opts }))\n } catch {\n // ignore\n }\n })\n await Promise.all(loaders)\n }\n loadAll()\n return () => {\n cancelled = true\n }\n }, [filters, filtersSignature, open])\n React.useEffect(() => {\n if (!open) {\n lastLoadedSignatureRef.current = null\n }\n }, [open])\n\n const setValue = (id: string, v: any) => setValues((prev) => ({ ...prev, [id]: v }))\n\n const handleApply = () => {\n onApply(values)\n onOpenChange(false)\n }\n\n const handleClear = () => {\n setValues({})\n onClear?.()\n }\n\n const tagLoaders = React.useMemo(() => {\n const map = new Map<string, (q?: string) => Promise<Array<string | TagsInputOption>>>()\n for (const f of stableFilters) {\n if (f.type === 'tags' && typeof f.loadOptions === 'function') {\n const fieldId = f.id\n const load = f.loadOptions as (query?: string) => Promise<FilterOption[]>\n map.set(fieldId, async (q?: string) => {\n const query = (q ?? '').trim()\n if (!query.length) return []\n try {\n const opts = await load(query)\n setDynamicOptions((prev) => ({ ...prev, [fieldId]: opts }))\n return opts.map((o) => ({ value: o.value, label: o.label, description: o.description ?? null }))\n } catch {\n return []\n }\n })\n }\n }\n return map\n }, [stableFilters])\n\n const comboboxLoaders = React.useMemo(() => {\n const map = new Map<string, (q?: string) => Promise<FilterOption[]>>()\n for (const f of stableFilters) {\n if (f.type === 'combobox' && typeof f.loadOptions === 'function') {\n map.set(f.id, async (query?: string) => {\n try {\n const opts = await f.loadOptions?.(query)\n setDynamicOptions((prev) => ({ ...prev, [f.id]: opts ?? [] }))\n return opts ?? []\n } catch {\n return []\n }\n })\n }\n }\n return map\n }, [stableFilters])\n\n return (\n <>\n {open && (\n <div className=\"fixed inset-0 z-modal\">\n <div className=\"absolute inset-0 bg-black/20\" onClick={() => onOpenChange(false)} role=\"presentation\" />\n <div className=\"absolute left-0 top-0 h-full w-full sm:w-[380px] bg-background shadow-xl border-r flex flex-col\">\n <div className=\"flex items-center justify-between p-4 border-b\">\n <h2 className=\"text-base font-semibold\">{defaultTitle}</h2>\n <Button variant=\"muted\" size=\"sm\" onClick={() => onOpenChange(false)}>{t('common.close')}</Button>\n </div>\n {/* Top actions: duplicate Clear/Apply */}\n <div className=\"px-4 py-2 border-b flex items-center justify-between gap-2\">\n <Button variant=\"outline\" size=\"sm\" onClick={handleClear}>{t('ui.filters.actions.clear', 'Clear')}</Button>\n <Button size=\"sm\" onClick={handleApply}>\n <svg width=\"14\" height=\"14\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" strokeWidth=\"2\" aria-hidden=\"true\" className=\"opacity-80\"><path d=\"M3 4h18\"/><path d=\"M6 8h12l-3 8H9L6 8z\"/></svg>\n {t('ui.filters.actions.apply', 'Apply')}\n </Button>\n </div>\n <div className=\"flex-1 overflow-auto p-4 space-y-4\">\n {extraContent ? <div className=\"space-y-2 rounded-md border bg-muted/30 p-3\">{extraContent}</div> : null}\n {filters.map((f) => (\n <div key={f.id} className=\"space-y-2\">\n <div className=\"text-sm font-medium\">{f.label}</div>\n {f.type === 'text' && (\n <input\n type=\"text\"\n className=\"w-full h-11 rounded border px-2 text-sm\"\n placeholder={f.placeholder}\n value={values[f.id] ?? ''}\n onChange={(e) => setValue(f.id, e.target.value || undefined)}\n />\n )}\n {f.type === 'dateRange' && (() => {\n const fromStr = values[f.id]?.from\n const toStr = values[f.id]?.to\n const parseISODate = (input: unknown): Date | null => {\n if (typeof input !== 'string' || !input.length) return null\n const candidate = new Date(input)\n return Number.isNaN(candidate.getTime()) ? null : candidate\n }\n const fromDate = parseISODate(fromStr)\n const toDate = parseISODate(toStr)\n const rangeValue: DateRange | null =\n fromDate && toDate\n ? { start: fromDate, end: toDate }\n : fromDate\n ? { start: fromDate, end: fromDate }\n : toDate\n ? { start: toDate, end: toDate }\n : null\n return (\n <DateRangePicker\n value={rangeValue}\n onChange={(next) => {\n if (!next) {\n setValue(f.id, { from: undefined, to: undefined })\n return\n }\n setValue(f.id, {\n from: format(next.start, 'yyyy-MM-dd'),\n to: format(next.end, 'yyyy-MM-dd'),\n })\n }}\n placeholder={t('ui.filters.dateRange.placeholder', 'Pick a date range')}\n />\n )\n })()}\n {f.type === 'select' && (\n <div className=\"space-y-1\">\n {f.multiple ? (\n <div className=\"flex flex-col gap-1\">\n {(f.options || dynamicOptions[f.id] || []).map((opt) => {\n const arr: string[] = Array.isArray(values[f.id]) ? values[f.id] : []\n const checked = arr.includes(opt.value)\n return (\n <label key={opt.value} className=\"inline-flex items-center gap-2 cursor-pointer\">\n <Checkbox\n checked={checked}\n onCheckedChange={(next) => {\n const set = new Set(arr)\n if (next === true) set.add(opt.value)\n else set.delete(opt.value)\n setValue(f.id, Array.from(set))\n }}\n />\n <span className=\"text-sm\">{opt.label}</span>\n </label>\n )\n })}\n </div>\n ) : (\n <Select\n value={values[f.id] || undefined}\n onValueChange={(next) => setValue(f.id, next || undefined)}\n >\n <SelectTrigger size=\"lg\">\n <SelectValue placeholder={t('ui.forms.select.emptyOption', '\u2014')} />\n </SelectTrigger>\n <SelectContent>\n {(f.options || dynamicOptions[f.id] || [])\n .filter((opt) => opt.value !== '')\n .map((opt) => (\n <SelectItem key={opt.value} value={opt.value}>{opt.label}</SelectItem>\n ))}\n </SelectContent>\n </Select>\n )}\n </div>\n )}\n {f.type === 'combobox' && (() => {\n const staticOptions = f.options || []\n const dynamic = dynamicOptions[f.id] || []\n const optionMap = new Map<string, FilterOption>()\n staticOptions.forEach((opt) => optionMap.set(opt.value, opt))\n dynamic.forEach((opt) => optionMap.set(opt.value, opt))\n const currentValue = typeof values[f.id] === 'string' ? values[f.id] : ''\n const suggestions = Array.from(optionMap.values()).map((opt) => ({\n value: opt.value,\n label: opt.label,\n description: opt.description ?? null,\n }))\n const loadSuggestions = comboboxLoaders.get(f.id)\n return (\n <div className=\"flex items-start gap-2\">\n <div className=\"min-w-0 flex-1\">\n <ComboboxInput\n value={currentValue}\n onChange={(next) => setValue(f.id, next.trim().length ? next : undefined)}\n suggestions={suggestions}\n loadSuggestions={loadSuggestions}\n resolveLabel={(value) => f.formatValue?.(value) ?? optionMap.get(value)?.label ?? value}\n resolveDescription={(value) => optionMap.get(value)?.description ?? null}\n placeholder={f.placeholder}\n allowCustomValues={false}\n />\n </div>\n {currentValue ? (\n <Button\n type=\"button\"\n variant=\"outline\"\n size=\"sm\"\n className=\"shrink-0\"\n onClick={() => setValue(f.id, undefined)}\n >\n {t('ui.filters.actions.clear', 'Clear')}\n </Button>\n ) : null}\n </div>\n )\n })()}\n {f.type === 'tags' && (() => {\n const arr: string[] = Array.isArray(values[f.id]) ? values[f.id] : []\n const staticOptions = f.options || []\n const dynamic = dynamicOptions[f.id] || []\n const optionMap = new Map<string, FilterOption>()\n staticOptions.forEach((opt) => optionMap.set(opt.value, opt))\n dynamic.forEach((opt) => optionMap.set(opt.value, opt))\n const loadSuggestions = tagLoaders.get(f.id)\n const resolveTagLabel = f.formatValue\n ? (val: string) => f.formatValue!(val)\n : (val: string) => optionMap.get(val)?.label ?? val\n const resolveTagDescription = f.formatDescription\n ? (val: string) => f.formatDescription!(val) ?? null\n : (val: string) => optionMap.get(val)?.description ?? null\n const suggestionList: TagsInputOption[] = Array.from(optionMap.values()).map((opt) => ({\n value: opt.value,\n label: opt.label,\n description: opt.description ?? null,\n }))\n return (\n <TagsInput\n value={arr}\n suggestions={suggestionList}\n loadSuggestions={loadSuggestions}\n allowCustomValues={false}\n resolveLabel={resolveTagLabel}\n resolveDescription={resolveTagDescription}\n placeholder={f.placeholder}\n onChange={(next) => setValue(f.id, next.length ? next : undefined)}\n />\n )\n })()}\n {f.type === 'checkbox' && (\n <div>\n <Select\n value={values[f.id] === true ? 'true' : values[f.id] === false ? 'false' : undefined}\n onValueChange={(next) => {\n if (!next) setValue(f.id, undefined)\n else if (next === 'true') setValue(f.id, true)\n else if (next === 'false') setValue(f.id, false)\n }}\n >\n <SelectTrigger size=\"lg\">\n <SelectValue placeholder={t('ui.forms.select.emptyOption', '\u2014')} />\n </SelectTrigger>\n <SelectContent>\n <SelectItem value=\"true\">{t('common.yes', 'Yes')}</SelectItem>\n <SelectItem value=\"false\">{t('common.no', 'No')}</SelectItem>\n </SelectContent>\n </Select>\n </div>\n )}\n </div>\n ))}\n </div>\n <div className=\"p-4 border-t flex items-center justify-between gap-2\">\n <Button variant=\"outline\" onClick={handleClear}>{t('ui.filters.actions.clear', 'Clear')}</Button>\n <Button onClick={handleApply}>\n <svg width=\"14\" height=\"14\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" strokeWidth=\"2\" aria-hidden=\"true\" className=\"opacity-80\"><path d=\"M3 4h18\"/><path d=\"M6 8h12l-3 8H9L6 8z\"/></svg>\n {t('ui.filters.actions.apply', 'Apply')}\n </Button>\n </div>\n </div>\n </div>\n )}\n </>\n )\n}\n"],
5
5
  "mappings": "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",
6
6
  "names": []
7
7
  }
@@ -1,9 +1,10 @@
1
+ import { applyDashboardWidgetOverridesToEntries } from "@open-mercato/shared/modules/overrides";
1
2
  let _dashboardWidgetEntries = null;
2
3
  function registerDashboardWidgets(entries) {
3
4
  if (_dashboardWidgetEntries !== null && process.env.NODE_ENV === "development") {
4
5
  console.debug("[Bootstrap] Dashboard widgets re-registered (this may occur during HMR)");
5
6
  }
6
- _dashboardWidgetEntries = entries;
7
+ _dashboardWidgetEntries = applyDashboardWidgetOverridesToEntries(entries);
7
8
  }
8
9
  function getDashboardWidgets() {
9
10
  if (!_dashboardWidgetEntries) {
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../src/backend/dashboard/widgetRegistry.ts"],
4
- "sourcesContent": ["import type { ModuleDashboardWidgetEntry } from '@open-mercato/shared/modules/registry'\nimport type { DashboardWidgetModule } from '@open-mercato/shared/modules/dashboard/widgets'\n\ntype Entry = ModuleDashboardWidgetEntry\n\n// Registration pattern for publishable packages\nlet _dashboardWidgetEntries: Entry[] | null = null\n\nexport function registerDashboardWidgets(entries: Entry[]) {\n if (_dashboardWidgetEntries !== null && process.env.NODE_ENV === 'development') {\n console.debug('[Bootstrap] Dashboard widgets re-registered (this may occur during HMR)')\n }\n _dashboardWidgetEntries = entries\n}\n\nexport function getDashboardWidgets(): Entry[] {\n if (!_dashboardWidgetEntries) {\n // On client-side, bootstrap doesn't run - return empty array gracefully\n if (typeof window !== 'undefined') {\n return []\n }\n throw new Error('[Bootstrap] Dashboard widgets not registered. Call registerDashboardWidgets() at bootstrap.')\n }\n return _dashboardWidgetEntries\n}\n\nlet entriesPromise: Promise<Entry[]> | null = null\n\nasync function getEntries(): Promise<Entry[]> {\n if (!entriesPromise) {\n const promise = Promise.resolve().then(() => getDashboardWidgets())\n entriesPromise = promise.catch((err) => {\n // Clear cache on error so next call can retry after registration\n if (entriesPromise === promise) {\n entriesPromise = null\n }\n throw err\n })\n }\n return entriesPromise\n}\n\ntype LoadedWidgetModule = DashboardWidgetModule<any>\n\nconst cache = new Map<string, Promise<LoadedWidgetModule>>()\n\nasync function findEntry(loaderKey: string): Promise<Entry | undefined> {\n const entries = await getEntries()\n return entries.find((entry) => entry.key === loaderKey)\n}\n\nexport async function loadDashboardWidgetModule(loaderKey: string): Promise<LoadedWidgetModule | null> {\n const entry = await findEntry(loaderKey)\n if (!entry) return null\n if (!cache.has(loaderKey)) {\n cache.set(\n loaderKey,\n entry\n .loader()\n .then((mod) => {\n const candidate = mod as LoadedWidgetModule\n const maybeDefault = (mod as { default?: LoadedWidgetModule }).default\n return maybeDefault ?? candidate\n })\n )\n }\n return cache.get(loaderKey) ?? null\n}\n"],
5
- "mappings": "AAMA,IAAI,0BAA0C;AAEvC,SAAS,yBAAyB,SAAkB;AACzD,MAAI,4BAA4B,QAAQ,QAAQ,IAAI,aAAa,eAAe;AAC9E,YAAQ,MAAM,yEAAyE;AAAA,EACzF;AACA,4BAA0B;AAC5B;AAEO,SAAS,sBAA+B;AAC7C,MAAI,CAAC,yBAAyB;AAE5B,QAAI,OAAO,WAAW,aAAa;AACjC,aAAO,CAAC;AAAA,IACV;AACA,UAAM,IAAI,MAAM,6FAA6F;AAAA,EAC/G;AACA,SAAO;AACT;AAEA,IAAI,iBAA0C;AAE9C,eAAe,aAA+B;AAC5C,MAAI,CAAC,gBAAgB;AACnB,UAAM,UAAU,QAAQ,QAAQ,EAAE,KAAK,MAAM,oBAAoB,CAAC;AAClE,qBAAiB,QAAQ,MAAM,CAAC,QAAQ;AAEtC,UAAI,mBAAmB,SAAS;AAC9B,yBAAiB;AAAA,MACnB;AACA,YAAM;AAAA,IACR,CAAC;AAAA,EACH;AACA,SAAO;AACT;AAIA,MAAM,QAAQ,oBAAI,IAAyC;AAE3D,eAAe,UAAU,WAA+C;AACtE,QAAM,UAAU,MAAM,WAAW;AACjC,SAAO,QAAQ,KAAK,CAAC,UAAU,MAAM,QAAQ,SAAS;AACxD;AAEA,eAAsB,0BAA0B,WAAuD;AACrG,QAAM,QAAQ,MAAM,UAAU,SAAS;AACvC,MAAI,CAAC,MAAO,QAAO;AACnB,MAAI,CAAC,MAAM,IAAI,SAAS,GAAG;AACzB,UAAM;AAAA,MACJ;AAAA,MACA,MACG,OAAO,EACP,KAAK,CAAC,QAAQ;AACb,cAAM,YAAY;AAClB,cAAM,eAAgB,IAAyC;AAC/D,eAAO,gBAAgB;AAAA,MACzB,CAAC;AAAA,IACL;AAAA,EACF;AACA,SAAO,MAAM,IAAI,SAAS,KAAK;AACjC;",
4
+ "sourcesContent": ["import type { ModuleDashboardWidgetEntry } from '@open-mercato/shared/modules/registry'\nimport type { DashboardWidgetModule } from '@open-mercato/shared/modules/dashboard/widgets'\nimport { applyDashboardWidgetOverridesToEntries } from '@open-mercato/shared/modules/overrides'\n\ntype Entry = ModuleDashboardWidgetEntry\n\n// Registration pattern for publishable packages\nlet _dashboardWidgetEntries: Entry[] | null = null\n\nexport function registerDashboardWidgets(entries: Entry[]) {\n if (_dashboardWidgetEntries !== null && process.env.NODE_ENV === 'development') {\n console.debug('[Bootstrap] Dashboard widgets re-registered (this may occur during HMR)')\n }\n _dashboardWidgetEntries = applyDashboardWidgetOverridesToEntries(entries)\n}\n\nexport function getDashboardWidgets(): Entry[] {\n if (!_dashboardWidgetEntries) {\n // On client-side, bootstrap doesn't run - return empty array gracefully\n if (typeof window !== 'undefined') {\n return []\n }\n throw new Error('[Bootstrap] Dashboard widgets not registered. Call registerDashboardWidgets() at bootstrap.')\n }\n return _dashboardWidgetEntries\n}\n\nlet entriesPromise: Promise<Entry[]> | null = null\n\nasync function getEntries(): Promise<Entry[]> {\n if (!entriesPromise) {\n const promise = Promise.resolve().then(() => getDashboardWidgets())\n entriesPromise = promise.catch((err) => {\n // Clear cache on error so next call can retry after registration\n if (entriesPromise === promise) {\n entriesPromise = null\n }\n throw err\n })\n }\n return entriesPromise\n}\n\ntype LoadedWidgetModule = DashboardWidgetModule<any>\n\nconst cache = new Map<string, Promise<LoadedWidgetModule>>()\n\nasync function findEntry(loaderKey: string): Promise<Entry | undefined> {\n const entries = await getEntries()\n return entries.find((entry) => entry.key === loaderKey)\n}\n\nexport async function loadDashboardWidgetModule(loaderKey: string): Promise<LoadedWidgetModule | null> {\n const entry = await findEntry(loaderKey)\n if (!entry) return null\n if (!cache.has(loaderKey)) {\n cache.set(\n loaderKey,\n entry\n .loader()\n .then((mod) => {\n const candidate = mod as LoadedWidgetModule\n const maybeDefault = (mod as { default?: LoadedWidgetModule }).default\n return maybeDefault ?? candidate\n })\n )\n }\n return cache.get(loaderKey) ?? null\n}\n"],
5
+ "mappings": "AAEA,SAAS,8CAA8C;AAKvD,IAAI,0BAA0C;AAEvC,SAAS,yBAAyB,SAAkB;AACzD,MAAI,4BAA4B,QAAQ,QAAQ,IAAI,aAAa,eAAe;AAC9E,YAAQ,MAAM,yEAAyE;AAAA,EACzF;AACA,4BAA0B,uCAAuC,OAAO;AAC1E;AAEO,SAAS,sBAA+B;AAC7C,MAAI,CAAC,yBAAyB;AAE5B,QAAI,OAAO,WAAW,aAAa;AACjC,aAAO,CAAC;AAAA,IACV;AACA,UAAM,IAAI,MAAM,6FAA6F;AAAA,EAC/G;AACA,SAAO;AACT;AAEA,IAAI,iBAA0C;AAE9C,eAAe,aAA+B;AAC5C,MAAI,CAAC,gBAAgB;AACnB,UAAM,UAAU,QAAQ,QAAQ,EAAE,KAAK,MAAM,oBAAoB,CAAC;AAClE,qBAAiB,QAAQ,MAAM,CAAC,QAAQ;AAEtC,UAAI,mBAAmB,SAAS;AAC9B,yBAAiB;AAAA,MACnB;AACA,YAAM;AAAA,IACR,CAAC;AAAA,EACH;AACA,SAAO;AACT;AAIA,MAAM,QAAQ,oBAAI,IAAyC;AAE3D,eAAe,UAAU,WAA+C;AACtE,QAAM,UAAU,MAAM,WAAW;AACjC,SAAO,QAAQ,KAAK,CAAC,UAAU,MAAM,QAAQ,SAAS;AACxD;AAEA,eAAsB,0BAA0B,WAAuD;AACrG,QAAM,QAAQ,MAAM,UAAU,SAAS;AACvC,MAAI,CAAC,MAAO,QAAO;AACnB,MAAI,CAAC,MAAM,IAAI,SAAS,GAAG;AACzB,UAAM;AAAA,MACJ;AAAA,MACA,MACG,OAAO,EACP,KAAK,CAAC,QAAQ;AACb,cAAM,YAAY;AAClB,cAAM,eAAgB,IAAyC;AAC/D,eAAO,gBAAgB;AAAA,MACzB,CAAC;AAAA,IACL;AAAA,EACF;AACA,SAAO,MAAM,IAAI,SAAS,KAAK;AACjC;",
6
6
  "names": []
7
7
  }
@@ -1,21 +1,19 @@
1
- import {
2
- startOfDay,
3
- endOfDay,
4
- startOfWeek,
5
- endOfWeek,
6
- startOfMonth,
7
- endOfMonth,
8
- startOfQuarter,
9
- endOfQuarter,
10
- startOfYear,
11
- endOfYear,
12
- subDays,
13
- subWeeks,
14
- subMonths,
15
- subQuarters,
16
- subYears,
17
- differenceInDays
18
- } from "date-fns";
1
+ import { differenceInDays } from "date-fns/differenceInDays";
2
+ import { endOfDay } from "date-fns/endOfDay";
3
+ import { endOfMonth } from "date-fns/endOfMonth";
4
+ import { endOfQuarter } from "date-fns/endOfQuarter";
5
+ import { endOfWeek } from "date-fns/endOfWeek";
6
+ import { endOfYear } from "date-fns/endOfYear";
7
+ import { startOfDay } from "date-fns/startOfDay";
8
+ import { startOfMonth } from "date-fns/startOfMonth";
9
+ import { startOfQuarter } from "date-fns/startOfQuarter";
10
+ import { startOfWeek } from "date-fns/startOfWeek";
11
+ import { startOfYear } from "date-fns/startOfYear";
12
+ import { subDays } from "date-fns/subDays";
13
+ import { subMonths } from "date-fns/subMonths";
14
+ import { subQuarters } from "date-fns/subQuarters";
15
+ import { subWeeks } from "date-fns/subWeeks";
16
+ import { subYears } from "date-fns/subYears";
19
17
  const DATE_RANGE_OPTIONS = [
20
18
  { value: "today", labelKey: "dashboards.analytics.dateRange.today" },
21
19
  { value: "yesterday", labelKey: "dashboards.analytics.dateRange.yesterday" },
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../src/backend/date-range/dateRanges.ts"],
4
- "sourcesContent": ["import {\n startOfDay,\n endOfDay,\n startOfWeek,\n endOfWeek,\n startOfMonth,\n endOfMonth,\n startOfQuarter,\n endOfQuarter,\n startOfYear,\n endOfYear,\n subDays,\n subWeeks,\n subMonths,\n subQuarters,\n subYears,\n differenceInDays,\n} from 'date-fns'\n\nexport type DateRangePreset =\n | 'today'\n | 'yesterday'\n | 'this_week'\n | 'last_week'\n | 'this_month'\n | 'last_month'\n | 'this_quarter'\n | 'last_quarter'\n | 'this_year'\n | 'last_year'\n | 'last_7_days'\n | 'last_30_days'\n | 'last_90_days'\n\nexport type DateRange = {\n start: Date\n end: Date\n}\n\nexport type DateRangeOption = {\n value: DateRangePreset\n labelKey: string\n}\n\nexport const DATE_RANGE_OPTIONS: DateRangeOption[] = [\n { value: 'today', labelKey: 'dashboards.analytics.dateRange.today' },\n { value: 'yesterday', labelKey: 'dashboards.analytics.dateRange.yesterday' },\n { value: 'this_week', labelKey: 'dashboards.analytics.dateRange.thisWeek' },\n { value: 'last_week', labelKey: 'dashboards.analytics.dateRange.lastWeek' },\n { value: 'this_month', labelKey: 'dashboards.analytics.dateRange.thisMonth' },\n { value: 'last_month', labelKey: 'dashboards.analytics.dateRange.lastMonth' },\n { value: 'this_quarter', labelKey: 'dashboards.analytics.dateRange.thisQuarter' },\n { value: 'last_quarter', labelKey: 'dashboards.analytics.dateRange.lastQuarter' },\n { value: 'this_year', labelKey: 'dashboards.analytics.dateRange.thisYear' },\n { value: 'last_year', labelKey: 'dashboards.analytics.dateRange.lastYear' },\n { value: 'last_7_days', labelKey: 'dashboards.analytics.dateRange.last7Days' },\n { value: 'last_30_days', labelKey: 'dashboards.analytics.dateRange.last30Days' },\n { value: 'last_90_days', labelKey: 'dashboards.analytics.dateRange.last90Days' },\n]\n\nexport function resolveDateRange(preset: DateRangePreset, referenceDate: Date = new Date()): DateRange {\n const today = referenceDate\n\n switch (preset) {\n case 'today':\n return { start: startOfDay(today), end: endOfDay(today) }\n\n case 'yesterday': {\n const yesterday = subDays(today, 1)\n return { start: startOfDay(yesterday), end: endOfDay(yesterday) }\n }\n\n case 'this_week':\n return { start: startOfWeek(today, { weekStartsOn: 1 }), end: endOfWeek(today, { weekStartsOn: 1 }) }\n\n case 'last_week': {\n const lastWeek = subWeeks(today, 1)\n return { start: startOfWeek(lastWeek, { weekStartsOn: 1 }), end: endOfWeek(lastWeek, { weekStartsOn: 1 }) }\n }\n\n case 'this_month':\n return { start: startOfMonth(today), end: endOfMonth(today) }\n\n case 'last_month': {\n const lastMonth = subMonths(today, 1)\n return { start: startOfMonth(lastMonth), end: endOfMonth(lastMonth) }\n }\n\n case 'this_quarter':\n return { start: startOfQuarter(today), end: endOfQuarter(today) }\n\n case 'last_quarter': {\n const lastQuarter = subQuarters(today, 1)\n return { start: startOfQuarter(lastQuarter), end: endOfQuarter(lastQuarter) }\n }\n\n case 'this_year':\n return { start: startOfYear(today), end: endOfYear(today) }\n\n case 'last_year': {\n const lastYear = subYears(today, 1)\n return { start: startOfYear(lastYear), end: endOfYear(lastYear) }\n }\n\n case 'last_7_days':\n return { start: startOfDay(subDays(today, 6)), end: endOfDay(today) }\n\n case 'last_30_days':\n return { start: startOfDay(subDays(today, 29)), end: endOfDay(today) }\n\n case 'last_90_days':\n return { start: startOfDay(subDays(today, 89)), end: endOfDay(today) }\n\n default:\n return { start: startOfMonth(today), end: endOfMonth(today) }\n }\n}\n\nexport function getPreviousPeriod(range: DateRange, preset: DateRangePreset): DateRange {\n const daysDiff = differenceInDays(range.end, range.start) + 1\n\n switch (preset) {\n case 'today':\n case 'yesterday':\n return {\n start: subDays(range.start, 1),\n end: subDays(range.end, 1),\n }\n\n case 'this_week':\n case 'last_week':\n return {\n start: subWeeks(range.start, 1),\n end: subWeeks(range.end, 1),\n }\n\n case 'this_month':\n case 'last_month':\n return {\n start: subMonths(range.start, 1),\n end: subMonths(range.end, 1),\n }\n\n case 'this_quarter':\n case 'last_quarter':\n return {\n start: subQuarters(range.start, 1),\n end: subQuarters(range.end, 1),\n }\n\n case 'this_year':\n case 'last_year':\n return {\n start: subYears(range.start, 1),\n end: subYears(range.end, 1),\n }\n\n case 'last_7_days':\n case 'last_30_days':\n case 'last_90_days':\n default:\n return {\n start: subDays(range.start, daysDiff),\n end: subDays(range.end, daysDiff),\n }\n }\n}\n\nexport function isValidDateRangePreset(value: unknown): value is DateRangePreset {\n if (typeof value !== 'string') return false\n return DATE_RANGE_OPTIONS.some((option) => option.value === value)\n}\n\nexport function calculatePercentageChange(current: number, previous: number): number {\n if (previous === 0) {\n return current === 0 ? 0 : 100\n }\n return ((current - previous) / Math.abs(previous)) * 100\n}\n\nexport function determineChangeDirection(current: number, previous: number): 'up' | 'down' | 'unchanged' {\n if (current > previous) return 'up'\n if (current < previous) return 'down'\n return 'unchanged'\n}\n\nexport function getComparisonLabelKey(preset: DateRangePreset): { key: string; fallback: string } {\n switch (preset) {\n case 'today':\n return { key: 'dashboards.analytics.comparison.vsYesterday', fallback: 'vs yesterday' }\n case 'yesterday':\n return { key: 'dashboards.analytics.comparison.vsDayBefore', fallback: 'vs day before' }\n case 'this_week':\n return { key: 'dashboards.analytics.comparison.vsLastWeek', fallback: 'vs last week' }\n case 'last_week':\n return { key: 'dashboards.analytics.comparison.vsWeekBefore', fallback: 'vs week before' }\n case 'this_month':\n return { key: 'dashboards.analytics.comparison.vsLastMonth', fallback: 'vs last month' }\n case 'last_month':\n return { key: 'dashboards.analytics.comparison.vsMonthBefore', fallback: 'vs month before' }\n case 'this_quarter':\n return { key: 'dashboards.analytics.comparison.vsLastQuarter', fallback: 'vs last quarter' }\n case 'last_quarter':\n return { key: 'dashboards.analytics.comparison.vsQuarterBefore', fallback: 'vs quarter before' }\n case 'this_year':\n return { key: 'dashboards.analytics.comparison.vsLastYear', fallback: 'vs last year' }\n case 'last_year':\n return { key: 'dashboards.analytics.comparison.vsYearBefore', fallback: 'vs year before' }\n case 'last_7_days':\n return { key: 'dashboards.analytics.comparison.vsPrevious7Days', fallback: 'vs previous 7 days' }\n case 'last_30_days':\n return { key: 'dashboards.analytics.comparison.vsPrevious30Days', fallback: 'vs previous 30 days' }\n case 'last_90_days':\n return { key: 'dashboards.analytics.comparison.vsPrevious90Days', fallback: 'vs previous 90 days' }\n default:\n return { key: 'dashboards.analytics.comparison.vsPreviousPeriod', fallback: 'vs previous period' }\n }\n}\n"],
5
- "mappings": "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",
4
+ "sourcesContent": ["import { differenceInDays } from 'date-fns/differenceInDays'\nimport { endOfDay } from 'date-fns/endOfDay'\nimport { endOfMonth } from 'date-fns/endOfMonth'\nimport { endOfQuarter } from 'date-fns/endOfQuarter'\nimport { endOfWeek } from 'date-fns/endOfWeek'\nimport { endOfYear } from 'date-fns/endOfYear'\nimport { startOfDay } from 'date-fns/startOfDay'\nimport { startOfMonth } from 'date-fns/startOfMonth'\nimport { startOfQuarter } from 'date-fns/startOfQuarter'\nimport { startOfWeek } from 'date-fns/startOfWeek'\nimport { startOfYear } from 'date-fns/startOfYear'\nimport { subDays } from 'date-fns/subDays'\nimport { subMonths } from 'date-fns/subMonths'\nimport { subQuarters } from 'date-fns/subQuarters'\nimport { subWeeks } from 'date-fns/subWeeks'\nimport { subYears } from 'date-fns/subYears'\n\nexport type DateRangePreset =\n | 'today'\n | 'yesterday'\n | 'this_week'\n | 'last_week'\n | 'this_month'\n | 'last_month'\n | 'this_quarter'\n | 'last_quarter'\n | 'this_year'\n | 'last_year'\n | 'last_7_days'\n | 'last_30_days'\n | 'last_90_days'\n\nexport type DateRange = {\n start: Date\n end: Date\n}\n\nexport type DateRangeOption = {\n value: DateRangePreset\n labelKey: string\n}\n\nexport const DATE_RANGE_OPTIONS: DateRangeOption[] = [\n { value: 'today', labelKey: 'dashboards.analytics.dateRange.today' },\n { value: 'yesterday', labelKey: 'dashboards.analytics.dateRange.yesterday' },\n { value: 'this_week', labelKey: 'dashboards.analytics.dateRange.thisWeek' },\n { value: 'last_week', labelKey: 'dashboards.analytics.dateRange.lastWeek' },\n { value: 'this_month', labelKey: 'dashboards.analytics.dateRange.thisMonth' },\n { value: 'last_month', labelKey: 'dashboards.analytics.dateRange.lastMonth' },\n { value: 'this_quarter', labelKey: 'dashboards.analytics.dateRange.thisQuarter' },\n { value: 'last_quarter', labelKey: 'dashboards.analytics.dateRange.lastQuarter' },\n { value: 'this_year', labelKey: 'dashboards.analytics.dateRange.thisYear' },\n { value: 'last_year', labelKey: 'dashboards.analytics.dateRange.lastYear' },\n { value: 'last_7_days', labelKey: 'dashboards.analytics.dateRange.last7Days' },\n { value: 'last_30_days', labelKey: 'dashboards.analytics.dateRange.last30Days' },\n { value: 'last_90_days', labelKey: 'dashboards.analytics.dateRange.last90Days' },\n]\n\nexport function resolveDateRange(preset: DateRangePreset, referenceDate: Date = new Date()): DateRange {\n const today = referenceDate\n\n switch (preset) {\n case 'today':\n return { start: startOfDay(today), end: endOfDay(today) }\n\n case 'yesterday': {\n const yesterday = subDays(today, 1)\n return { start: startOfDay(yesterday), end: endOfDay(yesterday) }\n }\n\n case 'this_week':\n return { start: startOfWeek(today, { weekStartsOn: 1 }), end: endOfWeek(today, { weekStartsOn: 1 }) }\n\n case 'last_week': {\n const lastWeek = subWeeks(today, 1)\n return { start: startOfWeek(lastWeek, { weekStartsOn: 1 }), end: endOfWeek(lastWeek, { weekStartsOn: 1 }) }\n }\n\n case 'this_month':\n return { start: startOfMonth(today), end: endOfMonth(today) }\n\n case 'last_month': {\n const lastMonth = subMonths(today, 1)\n return { start: startOfMonth(lastMonth), end: endOfMonth(lastMonth) }\n }\n\n case 'this_quarter':\n return { start: startOfQuarter(today), end: endOfQuarter(today) }\n\n case 'last_quarter': {\n const lastQuarter = subQuarters(today, 1)\n return { start: startOfQuarter(lastQuarter), end: endOfQuarter(lastQuarter) }\n }\n\n case 'this_year':\n return { start: startOfYear(today), end: endOfYear(today) }\n\n case 'last_year': {\n const lastYear = subYears(today, 1)\n return { start: startOfYear(lastYear), end: endOfYear(lastYear) }\n }\n\n case 'last_7_days':\n return { start: startOfDay(subDays(today, 6)), end: endOfDay(today) }\n\n case 'last_30_days':\n return { start: startOfDay(subDays(today, 29)), end: endOfDay(today) }\n\n case 'last_90_days':\n return { start: startOfDay(subDays(today, 89)), end: endOfDay(today) }\n\n default:\n return { start: startOfMonth(today), end: endOfMonth(today) }\n }\n}\n\nexport function getPreviousPeriod(range: DateRange, preset: DateRangePreset): DateRange {\n const daysDiff = differenceInDays(range.end, range.start) + 1\n\n switch (preset) {\n case 'today':\n case 'yesterday':\n return {\n start: subDays(range.start, 1),\n end: subDays(range.end, 1),\n }\n\n case 'this_week':\n case 'last_week':\n return {\n start: subWeeks(range.start, 1),\n end: subWeeks(range.end, 1),\n }\n\n case 'this_month':\n case 'last_month':\n return {\n start: subMonths(range.start, 1),\n end: subMonths(range.end, 1),\n }\n\n case 'this_quarter':\n case 'last_quarter':\n return {\n start: subQuarters(range.start, 1),\n end: subQuarters(range.end, 1),\n }\n\n case 'this_year':\n case 'last_year':\n return {\n start: subYears(range.start, 1),\n end: subYears(range.end, 1),\n }\n\n case 'last_7_days':\n case 'last_30_days':\n case 'last_90_days':\n default:\n return {\n start: subDays(range.start, daysDiff),\n end: subDays(range.end, daysDiff),\n }\n }\n}\n\nexport function isValidDateRangePreset(value: unknown): value is DateRangePreset {\n if (typeof value !== 'string') return false\n return DATE_RANGE_OPTIONS.some((option) => option.value === value)\n}\n\nexport function calculatePercentageChange(current: number, previous: number): number {\n if (previous === 0) {\n return current === 0 ? 0 : 100\n }\n return ((current - previous) / Math.abs(previous)) * 100\n}\n\nexport function determineChangeDirection(current: number, previous: number): 'up' | 'down' | 'unchanged' {\n if (current > previous) return 'up'\n if (current < previous) return 'down'\n return 'unchanged'\n}\n\nexport function getComparisonLabelKey(preset: DateRangePreset): { key: string; fallback: string } {\n switch (preset) {\n case 'today':\n return { key: 'dashboards.analytics.comparison.vsYesterday', fallback: 'vs yesterday' }\n case 'yesterday':\n return { key: 'dashboards.analytics.comparison.vsDayBefore', fallback: 'vs day before' }\n case 'this_week':\n return { key: 'dashboards.analytics.comparison.vsLastWeek', fallback: 'vs last week' }\n case 'last_week':\n return { key: 'dashboards.analytics.comparison.vsWeekBefore', fallback: 'vs week before' }\n case 'this_month':\n return { key: 'dashboards.analytics.comparison.vsLastMonth', fallback: 'vs last month' }\n case 'last_month':\n return { key: 'dashboards.analytics.comparison.vsMonthBefore', fallback: 'vs month before' }\n case 'this_quarter':\n return { key: 'dashboards.analytics.comparison.vsLastQuarter', fallback: 'vs last quarter' }\n case 'last_quarter':\n return { key: 'dashboards.analytics.comparison.vsQuarterBefore', fallback: 'vs quarter before' }\n case 'this_year':\n return { key: 'dashboards.analytics.comparison.vsLastYear', fallback: 'vs last year' }\n case 'last_year':\n return { key: 'dashboards.analytics.comparison.vsYearBefore', fallback: 'vs year before' }\n case 'last_7_days':\n return { key: 'dashboards.analytics.comparison.vsPrevious7Days', fallback: 'vs previous 7 days' }\n case 'last_30_days':\n return { key: 'dashboards.analytics.comparison.vsPrevious30Days', fallback: 'vs previous 30 days' }\n case 'last_90_days':\n return { key: 'dashboards.analytics.comparison.vsPrevious90Days', fallback: 'vs previous 90 days' }\n default:\n return { key: 'dashboards.analytics.comparison.vsPreviousPeriod', fallback: 'vs previous period' }\n }\n}\n"],
5
+ "mappings": "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",
6
6
  "names": []
7
7
  }
@@ -399,7 +399,9 @@ function AttachmentMetadataDialog({ open, onOpenChange, item, availableTags, onS
399
399
  /* @__PURE__ */ jsx("div", { className: "truncate text-sm font-medium", title: item.fileName, children: item.fileName }),
400
400
  /* @__PURE__ */ jsxs("div", { className: "text-xs text-muted-foreground", children: [
401
401
  formatFileSize(item.fileSize),
402
- " \\u2022 ",
402
+ " ",
403
+ "\u2022",
404
+ " ",
403
405
  item.partitionTitle ?? item.partitionCode
404
406
  ] })
405
407
  ] }),
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../src/backend/detail/AttachmentMetadataDialog.tsx"],
4
- "sourcesContent": ["\"use client\"\n\nimport * as React from 'react'\nimport { z } from 'zod'\nimport { Button } from '../../primitives/button'\nimport { Dialog, DialogContent, DialogHeader, DialogTitle } from '@open-mercato/ui/primitives/dialog'\nimport { Input } from '@open-mercato/ui/primitives/input'\nimport { TagsInput } from '@open-mercato/ui/backend/inputs/TagsInput'\nimport { CrudForm, type CrudField, type CrudFormGroup } from '@open-mercato/ui/backend/CrudForm'\nimport { collectCustomFieldValues } from '@open-mercato/ui/backend/utils/customFieldValues'\nimport { apiCall } from '@open-mercato/ui/backend/utils/apiCall'\nimport { flash } from '@open-mercato/ui/backend/FlashMessages'\nimport { useT } from '@open-mercato/shared/lib/i18n/context'\nimport { cn } from '@open-mercato/shared/lib/utils'\nimport { Copy, Download, Trash2 } from 'lucide-react'\nimport { AttachmentContentPreview } from '@open-mercato/core/modules/attachments/components/AttachmentContentPreview'\nimport { buildAttachmentFileUrl, buildAttachmentImageUrl, slugifyAttachmentFileName } from '@open-mercato/core/modules/attachments/lib/imageUrls'\nimport { E } from '@open-mercato/core/generated-shims/entities.ids.generated'\n\nexport type AttachmentAssignment = {\n type: string\n id: string\n href?: string | null\n label?: string | null\n}\n\nexport type AttachmentItem = {\n id: string\n fileName: string\n fileSize: number\n mimeType?: string | null\n partitionCode?: string | null\n partitionTitle?: string | null\n url?: string | null\n createdAt?: string | null\n tags?: string[]\n assignments?: AttachmentAssignment[]\n thumbnailUrl?: string\n content?: string | null\n}\n\nexport type AssignmentDraft = {\n type: string\n id: string\n href?: string\n label?: string\n}\n\nexport type AttachmentMetadataSavePayload = {\n tags: string[]\n assignments: AssignmentDraft[]\n customFields?: Record<string, unknown>\n}\n\ntype AttachmentMetadataResponse = {\n item: {\n id: string\n fileName?: string\n fileSize?: number\n mimeType?: string | null\n partitionCode?: string\n partitionTitle?: string | null\n tags?: string[]\n content?: string | null\n assignments?: AttachmentAssignment[]\n customFields?: Record<string, unknown>\n }\n error?: string\n}\n\ntype AttachmentMetadataFormValues = {\n id: string\n tags?: string[]\n assignments?: AssignmentDraft[]\n} & Record<string, unknown>\n\ntype AssignmentEditorLabels = {\n title: string\n description: string\n type: string\n id: string\n href: string\n label: string\n add: string\n remove: string\n}\n\ntype AttachmentMetadataDialogProps = {\n open: boolean\n onOpenChange: (next: boolean) => void\n item: AttachmentItem | null\n availableTags: string[]\n onSave: (id: string, payload: AttachmentMetadataSavePayload) => Promise<void>\n}\n\nfunction formatFileSize(value: number): string {\n if (!Number.isFinite(value)) return '\\u2014'\n if (value <= 0) return '0 B'\n const units = ['B', 'KB', 'MB', 'GB', 'TB']\n let idx = 0\n let current = value\n while (current >= 1024 && idx < units.length - 1) {\n current /= 1024\n idx += 1\n }\n return `${current.toFixed(idx === 0 ? 0 : 1)} ${units[idx]}`\n}\n\nconst ENV_APP_URL = (process.env.NEXT_PUBLIC_APP_URL || '').replace(/\\/$/, '')\n\nfunction resolveAbsoluteUrl(path: string): string {\n if (!path) return path\n if (/^https?:\\/\\//i.test(path)) return path\n const base =\n ENV_APP_URL ||\n (typeof window !== 'undefined' && window.location?.origin ? window.location.origin : '')\n if (!base) return path\n const normalizedBase = base.replace(/\\/$/, '')\n return `${normalizedBase}${path.startsWith('/') ? path : `/${path}`}`\n}\n\nfunction normalizeCustomFieldSubmitValue(value: unknown): unknown {\n if (Array.isArray(value)) {\n return value.filter((entry) => entry !== undefined)\n }\n if (value === undefined) return null\n return value\n}\n\nfunction prepareAssignmentsForForm(assignments?: AttachmentAssignment[] | null): AssignmentDraft[] {\n return (assignments ?? []).map((assignment) => ({\n type: assignment.type,\n id: assignment.id,\n href: assignment.href ?? '',\n label: assignment.label ?? '',\n }))\n}\n\nfunction prefixCustomFieldValues(values?: Record<string, unknown> | null): Record<string, unknown> {\n if (!values) return {}\n const prefixed: Record<string, unknown> = {}\n Object.entries(values).forEach(([key, value]) => {\n if (!key) return\n if (key.startsWith('cf_')) {\n prefixed[key] = value\n } else if (key.startsWith('cf:')) {\n const normalized = key.slice(3)\n if (normalized) prefixed[`cf_${normalized}`] = value\n } else {\n prefixed[`cf_${key}`] = value\n }\n })\n return prefixed\n}\n\nfunction AssignmentInputRow({\n value,\n onChange,\n labels,\n disabled,\n onRemove,\n}: {\n value: AssignmentDraft\n onChange: (next: AssignmentDraft) => void\n labels: AssignmentEditorLabels\n disabled?: boolean\n onRemove: () => void\n}) {\n return (\n <div className=\"grid grid-cols-1 gap-2 rounded-md border border-border/70 bg-background p-3 sm:grid-cols-2 lg:grid-cols-[1.2fr_1.2fr_1.6fr_1fr_auto]\">\n <div className=\"space-y-1\">\n <label className=\"text-xs font-medium\">{labels.type}</label>\n <Input\n value={value.type}\n onChange={(event) => onChange({ ...value, type: event.target.value })}\n placeholder=\"catalog.product\"\n disabled={disabled}\n />\n </div>\n <div className=\"space-y-1\">\n <label className=\"text-xs font-medium\">{labels.id}</label>\n <Input\n value={value.id}\n onChange={(event) => onChange({ ...value, id: event.target.value })}\n placeholder=\"Record ID\"\n disabled={disabled}\n />\n </div>\n <div className=\"space-y-1\">\n <label className=\"text-xs font-medium\">{labels.href}</label>\n <Input\n value={value.href ?? ''}\n onChange={(event) => onChange({ ...value, href: event.target.value })}\n placeholder=\"https://\"\n disabled={disabled}\n />\n </div>\n <div className=\"space-y-1\">\n <label className=\"text-xs font-medium\">{labels.label}</label>\n <Input\n value={value.label ?? ''}\n onChange={(event) => onChange({ ...value, label: event.target.value })}\n placeholder=\"Optional label\"\n disabled={disabled}\n />\n </div>\n <div className=\"flex items-end\">\n <Button type=\"button\" variant=\"ghost\" size=\"icon\" onClick={onRemove} disabled={disabled}>\n <Trash2 className=\"h-4 w-4\" />\n </Button>\n </div>\n </div>\n )\n}\n\nfunction AttachmentAssignmentsEditor({\n value,\n onChange,\n labels,\n disabled,\n}: {\n value: AssignmentDraft[]\n onChange: (next: AssignmentDraft[]) => void\n labels: AssignmentEditorLabels\n disabled?: boolean\n}) {\n const handleAdd = React.useCallback(() => {\n onChange([...value, { type: '', id: '', href: '', label: '' }])\n }, [onChange, value])\n\n const handleChange = React.useCallback(\n (index: number, next: AssignmentDraft) => {\n const draft = [...value]\n draft[index] = next\n onChange(draft)\n },\n [onChange, value],\n )\n\n const handleRemove = React.useCallback(\n (index: number) => {\n const next = value.filter((_, idx) => idx !== index)\n onChange(next)\n },\n [onChange, value],\n )\n\n return (\n <div className=\"space-y-2\">\n <div className=\"space-y-1\">\n <div className=\"text-sm font-medium\">{labels.title}</div>\n <p className=\"text-xs text-muted-foreground\">{labels.description}</p>\n </div>\n <div className=\"space-y-2\">\n {value.length ? value.map((entry, idx) => (\n <AssignmentInputRow\n key={`${entry.type}-${entry.id}-${idx}`}\n value={entry}\n labels={labels}\n disabled={disabled}\n onChange={(next) => handleChange(idx, next)}\n onRemove={() => handleRemove(idx)}\n />\n )) : (\n <div className=\"rounded-md border border-dashed border-border/70 px-3 py-4 text-xs text-muted-foreground\">\n {labels.description}\n </div>\n )}\n </div>\n <Button type=\"button\" variant=\"outline\" size=\"sm\" onClick={handleAdd} disabled={disabled}>\n {labels.add}\n </Button>\n </div>\n )\n}\n\nexport function AttachmentMetadataDialog({ open, onOpenChange, item, availableTags, onSave }: AttachmentMetadataDialogProps) {\n const t = useT()\n const [sizeWidth, setSizeWidth] = React.useState<string>('')\n const [sizeHeight, setSizeHeight] = React.useState<string>('')\n const [imageTab, setImageTab] = React.useState<'preview' | 'resize'>('preview')\n const [initialValues, setInitialValues] = React.useState<Partial<AttachmentMetadataFormValues> | null>(null)\n const [loading, setLoading] = React.useState(false)\n const [loadError, setLoadError] = React.useState<string | null>(null)\n const [extractedContent, setExtractedContent] = React.useState<string | null>(null)\n\n React.useEffect(() => {\n if (!open || !item) {\n setInitialValues(null)\n setLoadError(null)\n setLoading(false)\n setImageTab('preview')\n setExtractedContent(null)\n return\n }\n let cancelled = false\n setLoading(true)\n setLoadError(null)\n setSizeWidth('')\n setSizeHeight('')\n setImageTab('preview')\n setInitialValues({\n id: item.id,\n tags: item.tags ?? [],\n assignments: prepareAssignmentsForForm(item.assignments),\n })\n setExtractedContent(item.content && item.content.trim() ? item.content : null)\n const loadDetails = async () => {\n try {\n const call = await apiCall<AttachmentMetadataResponse>(`/api/attachments/library/${encodeURIComponent(item.id)}`)\n if (!call.ok || !call.result?.item) {\n const message = call.result?.error || t('attachments.library.metadata.error', 'Failed to update metadata.')\n throw new Error(message)\n }\n const payload = call.result.item\n const prefixedCustom = prefixCustomFieldValues(payload.customFields)\n if (!cancelled) {\n setInitialValues({\n id: payload.id,\n tags: Array.isArray(payload.tags) ? payload.tags : [],\n assignments: prepareAssignmentsForForm(payload.assignments ?? item.assignments),\n ...prefixedCustom,\n })\n const nextContent = typeof payload.content === 'string' && payload.content.trim() ? payload.content : null\n setExtractedContent(nextContent)\n }\n } catch (err: any) {\n if (!cancelled) {\n const message =\n err?.message || t('attachments.library.metadata.loadError', 'Failed to load attachment metadata.')\n setLoadError(message)\n }\n } finally {\n if (!cancelled) setLoading(false)\n }\n }\n void loadDetails()\n return () => {\n cancelled = true\n }\n }, [item, open, t])\n\n const isImage = React.useMemo(() => Boolean(item?.mimeType?.toLowerCase().startsWith('image/')), [item])\n const previewUrl = React.useMemo(() => {\n if (!item) return null\n return (\n item.thumbnailUrl ??\n buildAttachmentImageUrl(item.id, {\n width: 320,\n height: 320,\n slug: slugifyAttachmentFileName(item.fileName),\n })\n )\n }, [item])\n const downloadUrl = React.useMemo(() => {\n if (!item) return null\n const original = buildAttachmentFileUrl(item.id, { download: true })\n return resolveAbsoluteUrl(original)\n }, [item])\n\n const assignmentLabels = React.useMemo(\n () => ({\n title: t('attachments.library.metadata.assignments.title', 'Assignments'),\n description: t(\n 'attachments.library.metadata.assignments.description',\n 'Add the records this attachment belongs to with optional links.',\n ),\n type: t('attachments.library.metadata.assignments.type', 'Type'),\n id: t('attachments.library.metadata.assignments.id', 'Record ID'),\n href: t('attachments.library.metadata.assignments.href', 'Link'),\n label: t('attachments.library.metadata.assignments.label', 'Label'),\n add: t('attachments.library.metadata.assignments.add', 'Add assignment'),\n remove: t('attachments.library.metadata.assignments.remove', 'Remove'),\n }),\n [t],\n )\n\n const metadataFields = React.useMemo<CrudField[]>(() => {\n return [\n {\n id: 'tags',\n label: t('attachments.library.table.tags', 'Tags'),\n type: 'custom',\n component: ({ value, setValue, disabled }) => (\n <TagsInput\n value={Array.isArray(value) ? (value as string[]) : []}\n onChange={(next) => setValue(next)}\n suggestions={availableTags}\n placeholder={t('attachments.library.metadata.tagsPlaceholder', 'Add tags')}\n disabled={Boolean(disabled) || loading}\n />\n ),\n },\n {\n id: 'assignments',\n label: '',\n type: 'custom',\n component: ({ value, setValue, disabled }) => (\n <AttachmentAssignmentsEditor\n value={Array.isArray(value) ? (value as AssignmentDraft[]) : []}\n onChange={(next) => setValue(next)}\n labels={assignmentLabels}\n disabled={Boolean(disabled) || loading}\n />\n ),\n },\n ]\n }, [assignmentLabels, availableTags, loading, t])\n\n const metadataGroups = React.useMemo<CrudFormGroup[]>(() => {\n return [\n {\n id: 'details',\n title: t('attachments.library.metadata.details', 'Details'),\n column: 1,\n fields: ['tags', 'assignments'],\n },\n {\n id: 'customFields',\n title: t('entities.customFields.title', 'Custom attributes'),\n column: 2,\n kind: 'customFields',\n },\n ]\n }, [t])\n\n const metadataSchema = React.useMemo(\n () =>\n z\n .object({\n id: z.string().min(1),\n tags: z.array(z.string()).optional(),\n assignments: z\n .array(\n z.object({\n type: z.string().min(1),\n id: z.string().min(1),\n href: z.string().optional(),\n label: z.string().optional(),\n }),\n )\n .optional(),\n })\n .passthrough(),\n [],\n )\n\n const handleSubmit = React.useCallback(\n async (values: AttachmentMetadataFormValues) => {\n if (!item) return\n const tags = Array.isArray(values.tags)\n ? values.tags.map((tag) => (typeof tag === 'string' ? tag.trim() : '')).filter((tag) => tag.length > 0)\n : []\n const assignments = Array.isArray(values.assignments)\n ? values.assignments\n .map((assignment) => ({\n type: assignment.type?.trim() ?? '',\n id: assignment.id?.trim() ?? '',\n href: assignment.href?.trim() || undefined,\n label: assignment.label?.trim() || undefined,\n }))\n .filter((assignment) => assignment.type && assignment.id)\n : []\n const customFields = collectCustomFieldValues(values, {\n transform: (value) => normalizeCustomFieldSubmitValue(value),\n })\n const payload: AttachmentMetadataSavePayload = {\n tags,\n assignments,\n }\n if (Object.keys(customFields).length) {\n payload.customFields = customFields\n }\n await onSave(item.id, payload)\n },\n [item, onSave],\n )\n\n const handleKeyDown = React.useCallback(\n (event: React.KeyboardEvent) => {\n if (event.key === 'Escape') {\n event.preventDefault()\n onOpenChange(false)\n }\n },\n [onOpenChange],\n )\n\n const handleCopyResizedUrl = React.useCallback(async () => {\n if (!item) return\n const width = sizeWidth ? Number(sizeWidth) : undefined\n const height = sizeHeight ? Number(sizeHeight) : undefined\n if (!width && !height) {\n flash(\n t('attachments.library.metadata.resizeTool.missing', 'Enter width or height to generate the URL.'),\n 'error',\n )\n return\n }\n const url = buildAttachmentImageUrl(item.id, {\n width: width && width > 0 ? width : undefined,\n height: height && height > 0 ? height : undefined,\n slug: slugifyAttachmentFileName(item.fileName),\n })\n const absolute = resolveAbsoluteUrl(url)\n try {\n await navigator.clipboard.writeText(absolute)\n flash(\n t('attachments.library.metadata.resizeTool.copied', 'Image URL copied.'),\n 'success',\n )\n } catch {\n flash(\n t('attachments.library.metadata.resizeTool.copyError', 'Unable to copy URL.'),\n 'error',\n )\n }\n }, [item, sizeHeight, sizeWidth, t])\n\n const loadMessage = t('attachments.library.metadata.loading', 'Loading attachment details\\u2026')\n\n return (\n <Dialog open={open} onOpenChange={onOpenChange}>\n <DialogContent className=\"sm:max-w-2xl\" onKeyDown={handleKeyDown}>\n <DialogHeader>\n <DialogTitle>{t('attachments.library.metadata.title', 'Edit attachment metadata')}</DialogTitle>\n </DialogHeader>\n {item ? (\n <div className=\"space-y-4\">\n <div className=\"flex items-start justify-between gap-3\">\n <div className=\"space-y-1 min-w-0\">\n <div className=\"truncate text-sm font-medium\" title={item.fileName}>\n {item.fileName}\n </div>\n <div className=\"text-xs text-muted-foreground\">\n {formatFileSize(item.fileSize)} \\u2022 {item.partitionTitle ?? item.partitionCode}\n </div>\n </div>\n {downloadUrl ? (\n <Button variant=\"outline\" size=\"sm\" asChild className=\"shrink-0\">\n <a href={downloadUrl} download>\n <Download className=\"mr-2 h-4 w-4\" />\n {t('attachments.library.metadata.download', 'Download')}\n </a>\n </Button>\n ) : null}\n </div>\n {isImage ? (\n <div className=\"rounded border\">\n <div className=\"flex flex-wrap gap-4 border-b px-3 py-2 text-sm font-medium\" role=\"tablist\">\n {(['preview', 'resize'] as const).map((tab) => (\n <Button\n key={tab}\n type=\"button\"\n variant=\"ghost\"\n size=\"sm\"\n role=\"tab\"\n aria-selected={imageTab === tab}\n onClick={() => setImageTab(tab)}\n className={cn(\n 'h-auto -mb-px rounded-none border-b-2 border-transparent px-0 py-1',\n imageTab === tab\n ? 'border-primary text-foreground'\n : 'text-muted-foreground hover:text-foreground',\n )}\n >\n {tab === 'preview'\n ? t('attachments.library.metadata.preview', 'Preview')\n : t('attachments.library.metadata.resizeTool.title', 'Generate resized URL')}\n </Button>\n ))}\n </div>\n <div className=\"space-y-3 p-3\">\n {imageTab === 'preview' ? (\n previewUrl ? (\n <img\n src={previewUrl}\n alt={item.fileName}\n className=\"h-48 w-full rounded-md bg-muted object-contain\"\n />\n ) : (\n <div className=\"text-sm text-muted-foreground\">\n {t('attachments.library.metadata.previewUnavailable', 'Preview unavailable.')}\n </div>\n )\n ) : (\n <div className=\"space-y-2\">\n <div className=\"grid gap-2 sm:grid-cols-2\">\n <div className=\"space-y-1\">\n <label className=\"text-xs font-medium\" htmlFor=\"resize-width\">\n {t('attachments.library.metadata.resizeTool.width', 'Width (px)')}\n </label>\n <Input\n id=\"resize-width\"\n type=\"number\"\n min={0}\n value={sizeWidth}\n onChange={(event) => setSizeWidth(event.target.value)}\n disabled={loading}\n />\n </div>\n <div className=\"space-y-1\">\n <label className=\"text-xs font-medium\" htmlFor=\"resize-height\">\n {t('attachments.library.metadata.resizeTool.height', 'Height (px)')}\n </label>\n <Input\n id=\"resize-height\"\n type=\"number\"\n min={0}\n value={sizeHeight}\n onChange={(event) => setSizeHeight(event.target.value)}\n disabled={loading}\n />\n </div>\n </div>\n <Button\n type=\"button\"\n variant=\"outline\"\n size=\"sm\"\n className=\"inline-flex items-center gap-2\"\n onClick={() => void handleCopyResizedUrl()}\n disabled={loading}\n >\n <Copy className=\"h-4 w-4\" />\n {t('attachments.library.metadata.resizeTool.copy', 'Copy URL')}\n </Button>\n </div>\n )}\n </div>\n </div>\n ) : null}\n {loadError ? (\n <div className=\"rounded border border-destructive/40 bg-destructive/10 px-3 py-2 text-xs text-destructive\">\n {loadError}\n </div>\n ) : null}\n <div className=\"rounded border border-border/70 bg-muted/30 px-3 py-2\">\n <div className=\"text-xs font-semibold text-muted-foreground\">\n {t('attachments.library.metadata.extractedTitle', 'Extracted text')}\n </div>\n <AttachmentContentPreview\n content={extractedContent}\n emptyLabel={t('attachments.library.metadata.noContent', 'No text extracted')}\n showMoreLabel={t('attachments.library.metadata.showMore', 'Show more')}\n showLessLabel={t('attachments.library.metadata.showLess', 'Show less')}\n />\n </div>\n <CrudForm<AttachmentMetadataFormValues>\n embedded\n schema={metadataSchema}\n entityId={E.attachments.attachment}\n fields={metadataFields}\n groups={metadataGroups}\n initialValues={initialValues ?? undefined}\n isLoading={!initialValues || loading}\n loadingMessage={loadMessage}\n submitLabel={t('attachments.library.metadata.save', 'Save')}\n extraActions={\n <Button type=\"button\" variant=\"outline\" onClick={() => onOpenChange(false)}>\n {t('attachments.library.metadata.cancel', 'Cancel')}\n </Button>\n }\n onSubmit={handleSubmit}\n />\n </div>\n ) : (\n <div className=\"py-8 text-center text-sm text-muted-foreground\">\n {t('attachments.library.metadata.noSelection', 'Select an attachment to edit.')}\n </div>\n )}\n </DialogContent>\n </Dialog>\n )\n}\n"],
5
- "mappings": ";AA0KM,SACE,KADF;AAxKN,YAAY,WAAW;AACvB,SAAS,SAAS;AAClB,SAAS,cAAc;AACvB,SAAS,QAAQ,eAAe,cAAc,mBAAmB;AACjE,SAAS,aAAa;AACtB,SAAS,iBAAiB;AAC1B,SAAS,gBAAoD;AAC7D,SAAS,gCAAgC;AACzC,SAAS,eAAe;AACxB,SAAS,aAAa;AACtB,SAAS,YAAY;AACrB,SAAS,UAAU;AACnB,SAAS,MAAM,UAAU,cAAc;AACvC,SAAS,gCAAgC;AACzC,SAAS,wBAAwB,yBAAyB,iCAAiC;AAC3F,SAAS,SAAS;AA8ElB,SAAS,eAAe,OAAuB;AAC7C,MAAI,CAAC,OAAO,SAAS,KAAK,EAAG,QAAO;AACpC,MAAI,SAAS,EAAG,QAAO;AACvB,QAAM,QAAQ,CAAC,KAAK,MAAM,MAAM,MAAM,IAAI;AAC1C,MAAI,MAAM;AACV,MAAI,UAAU;AACd,SAAO,WAAW,QAAQ,MAAM,MAAM,SAAS,GAAG;AAChD,eAAW;AACX,WAAO;AAAA,EACT;AACA,SAAO,GAAG,QAAQ,QAAQ,QAAQ,IAAI,IAAI,CAAC,CAAC,IAAI,MAAM,GAAG,CAAC;AAC5D;AAEA,MAAM,eAAe,QAAQ,IAAI,uBAAuB,IAAI,QAAQ,OAAO,EAAE;AAE7E,SAAS,mBAAmB,MAAsB;AAChD,MAAI,CAAC,KAAM,QAAO;AAClB,MAAI,gBAAgB,KAAK,IAAI,EAAG,QAAO;AACvC,QAAM,OACJ,gBACC,OAAO,WAAW,eAAe,OAAO,UAAU,SAAS,OAAO,SAAS,SAAS;AACvF,MAAI,CAAC,KAAM,QAAO;AAClB,QAAM,iBAAiB,KAAK,QAAQ,OAAO,EAAE;AAC7C,SAAO,GAAG,cAAc,GAAG,KAAK,WAAW,GAAG,IAAI,OAAO,IAAI,IAAI,EAAE;AACrE;AAEA,SAAS,gCAAgC,OAAyB;AAChE,MAAI,MAAM,QAAQ,KAAK,GAAG;AACxB,WAAO,MAAM,OAAO,CAAC,UAAU,UAAU,MAAS;AAAA,EACpD;AACA,MAAI,UAAU,OAAW,QAAO;AAChC,SAAO;AACT;AAEA,SAAS,0BAA0B,aAAgE;AACjG,UAAQ,eAAe,CAAC,GAAG,IAAI,CAAC,gBAAgB;AAAA,IAC9C,MAAM,WAAW;AAAA,IACjB,IAAI,WAAW;AAAA,IACf,MAAM,WAAW,QAAQ;AAAA,IACzB,OAAO,WAAW,SAAS;AAAA,EAC7B,EAAE;AACJ;AAEA,SAAS,wBAAwB,QAAkE;AACjG,MAAI,CAAC,OAAQ,QAAO,CAAC;AACrB,QAAM,WAAoC,CAAC;AAC3C,SAAO,QAAQ,MAAM,EAAE,QAAQ,CAAC,CAAC,KAAK,KAAK,MAAM;AAC/C,QAAI,CAAC,IAAK;AACV,QAAI,IAAI,WAAW,KAAK,GAAG;AACzB,eAAS,GAAG,IAAI;AAAA,IAClB,WAAW,IAAI,WAAW,KAAK,GAAG;AAChC,YAAM,aAAa,IAAI,MAAM,CAAC;AAC9B,UAAI,WAAY,UAAS,MAAM,UAAU,EAAE,IAAI;AAAA,IACjD,OAAO;AACL,eAAS,MAAM,GAAG,EAAE,IAAI;AAAA,IAC1B;AAAA,EACF,CAAC;AACD,SAAO;AACT;AAEA,SAAS,mBAAmB;AAAA,EAC1B;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF,GAMG;AACD,SACE,qBAAC,SAAI,WAAU,wIACb;AAAA,yBAAC,SAAI,WAAU,aACb;AAAA,0BAAC,WAAM,WAAU,uBAAuB,iBAAO,MAAK;AAAA,MACpD;AAAA,QAAC;AAAA;AAAA,UACC,OAAO,MAAM;AAAA,UACb,UAAU,CAAC,UAAU,SAAS,EAAE,GAAG,OAAO,MAAM,MAAM,OAAO,MAAM,CAAC;AAAA,UACpE,aAAY;AAAA,UACZ;AAAA;AAAA,MACF;AAAA,OACF;AAAA,IACA,qBAAC,SAAI,WAAU,aACb;AAAA,0BAAC,WAAM,WAAU,uBAAuB,iBAAO,IAAG;AAAA,MAClD;AAAA,QAAC;AAAA;AAAA,UACC,OAAO,MAAM;AAAA,UACb,UAAU,CAAC,UAAU,SAAS,EAAE,GAAG,OAAO,IAAI,MAAM,OAAO,MAAM,CAAC;AAAA,UAClE,aAAY;AAAA,UACZ;AAAA;AAAA,MACF;AAAA,OACF;AAAA,IACA,qBAAC,SAAI,WAAU,aACb;AAAA,0BAAC,WAAM,WAAU,uBAAuB,iBAAO,MAAK;AAAA,MACpD;AAAA,QAAC;AAAA;AAAA,UACC,OAAO,MAAM,QAAQ;AAAA,UACrB,UAAU,CAAC,UAAU,SAAS,EAAE,GAAG,OAAO,MAAM,MAAM,OAAO,MAAM,CAAC;AAAA,UACpE,aAAY;AAAA,UACZ;AAAA;AAAA,MACF;AAAA,OACF;AAAA,IACA,qBAAC,SAAI,WAAU,aACb;AAAA,0BAAC,WAAM,WAAU,uBAAuB,iBAAO,OAAM;AAAA,MACrD;AAAA,QAAC;AAAA;AAAA,UACC,OAAO,MAAM,SAAS;AAAA,UACtB,UAAU,CAAC,UAAU,SAAS,EAAE,GAAG,OAAO,OAAO,MAAM,OAAO,MAAM,CAAC;AAAA,UACrE,aAAY;AAAA,UACZ;AAAA;AAAA,MACF;AAAA,OACF;AAAA,IACA,oBAAC,SAAI,WAAU,kBACb,8BAAC,UAAO,MAAK,UAAS,SAAQ,SAAQ,MAAK,QAAO,SAAS,UAAU,UACnE,8BAAC,UAAO,WAAU,WAAU,GAC9B,GACF;AAAA,KACF;AAEJ;AAEA,SAAS,4BAA4B;AAAA,EACnC;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF,GAKG;AACD,QAAM,YAAY,MAAM,YAAY,MAAM;AACxC,aAAS,CAAC,GAAG,OAAO,EAAE,MAAM,IAAI,IAAI,IAAI,MAAM,IAAI,OAAO,GAAG,CAAC,CAAC;AAAA,EAChE,GAAG,CAAC,UAAU,KAAK,CAAC;AAEpB,QAAM,eAAe,MAAM;AAAA,IACzB,CAAC,OAAe,SAA0B;AACxC,YAAM,QAAQ,CAAC,GAAG,KAAK;AACvB,YAAM,KAAK,IAAI;AACf,eAAS,KAAK;AAAA,IAChB;AAAA,IACA,CAAC,UAAU,KAAK;AAAA,EAClB;AAEA,QAAM,eAAe,MAAM;AAAA,IACzB,CAAC,UAAkB;AACjB,YAAM,OAAO,MAAM,OAAO,CAAC,GAAG,QAAQ,QAAQ,KAAK;AACnD,eAAS,IAAI;AAAA,IACf;AAAA,IACA,CAAC,UAAU,KAAK;AAAA,EAClB;AAEA,SACE,qBAAC,SAAI,WAAU,aACb;AAAA,yBAAC,SAAI,WAAU,aACb;AAAA,0BAAC,SAAI,WAAU,uBAAuB,iBAAO,OAAM;AAAA,MACnD,oBAAC,OAAE,WAAU,iCAAiC,iBAAO,aAAY;AAAA,OACnE;AAAA,IACA,oBAAC,SAAI,WAAU,aACZ,gBAAM,SAAS,MAAM,IAAI,CAAC,OAAO,QAChC;AAAA,MAAC;AAAA;AAAA,QAEC,OAAO;AAAA,QACP;AAAA,QACA;AAAA,QACA,UAAU,CAAC,SAAS,aAAa,KAAK,IAAI;AAAA,QAC1C,UAAU,MAAM,aAAa,GAAG;AAAA;AAAA,MAL3B,GAAG,MAAM,IAAI,IAAI,MAAM,EAAE,IAAI,GAAG;AAAA,IAMvC,CACD,IACC,oBAAC,SAAI,WAAU,4FACZ,iBAAO,aACV,GAEJ;AAAA,IACA,oBAAC,UAAO,MAAK,UAAS,SAAQ,WAAU,MAAK,MAAK,SAAS,WAAW,UACnE,iBAAO,KACV;AAAA,KACF;AAEJ;AAEO,SAAS,yBAAyB,EAAE,MAAM,cAAc,MAAM,eAAe,OAAO,GAAkC;AAC3H,QAAM,IAAI,KAAK;AACf,QAAM,CAAC,WAAW,YAAY,IAAI,MAAM,SAAiB,EAAE;AAC3D,QAAM,CAAC,YAAY,aAAa,IAAI,MAAM,SAAiB,EAAE;AAC7D,QAAM,CAAC,UAAU,WAAW,IAAI,MAAM,SAA+B,SAAS;AAC9E,QAAM,CAAC,eAAe,gBAAgB,IAAI,MAAM,SAAuD,IAAI;AAC3G,QAAM,CAAC,SAAS,UAAU,IAAI,MAAM,SAAS,KAAK;AAClD,QAAM,CAAC,WAAW,YAAY,IAAI,MAAM,SAAwB,IAAI;AACpE,QAAM,CAAC,kBAAkB,mBAAmB,IAAI,MAAM,SAAwB,IAAI;AAElF,QAAM,UAAU,MAAM;AACpB,QAAI,CAAC,QAAQ,CAAC,MAAM;AAClB,uBAAiB,IAAI;AACrB,mBAAa,IAAI;AACjB,iBAAW,KAAK;AAChB,kBAAY,SAAS;AACrB,0BAAoB,IAAI;AACxB;AAAA,IACF;AACA,QAAI,YAAY;AAChB,eAAW,IAAI;AACf,iBAAa,IAAI;AACjB,iBAAa,EAAE;AACf,kBAAc,EAAE;AAChB,gBAAY,SAAS;AACrB,qBAAiB;AAAA,MACf,IAAI,KAAK;AAAA,MACT,MAAM,KAAK,QAAQ,CAAC;AAAA,MACpB,aAAa,0BAA0B,KAAK,WAAW;AAAA,IACzD,CAAC;AACD,wBAAoB,KAAK,WAAW,KAAK,QAAQ,KAAK,IAAI,KAAK,UAAU,IAAI;AAC7E,UAAM,cAAc,YAAY;AAC9B,UAAI;AACF,cAAM,OAAO,MAAM,QAAoC,4BAA4B,mBAAmB,KAAK,EAAE,CAAC,EAAE;AAChH,YAAI,CAAC,KAAK,MAAM,CAAC,KAAK,QAAQ,MAAM;AAClC,gBAAM,UAAU,KAAK,QAAQ,SAAS,EAAE,sCAAsC,4BAA4B;AAC1G,gBAAM,IAAI,MAAM,OAAO;AAAA,QACzB;AACA,cAAM,UAAU,KAAK,OAAO;AAC5B,cAAM,iBAAiB,wBAAwB,QAAQ,YAAY;AACnE,YAAI,CAAC,WAAW;AACd,2BAAiB;AAAA,YACf,IAAI,QAAQ;AAAA,YACZ,MAAM,MAAM,QAAQ,QAAQ,IAAI,IAAI,QAAQ,OAAO,CAAC;AAAA,YACpD,aAAa,0BAA0B,QAAQ,eAAe,KAAK,WAAW;AAAA,YAC9E,GAAG;AAAA,UACL,CAAC;AACD,gBAAM,cAAc,OAAO,QAAQ,YAAY,YAAY,QAAQ,QAAQ,KAAK,IAAI,QAAQ,UAAU;AACtG,8BAAoB,WAAW;AAAA,QACjC;AAAA,MACF,SAAS,KAAU;AACjB,YAAI,CAAC,WAAW;AACd,gBAAM,UACJ,KAAK,WAAW,EAAE,0CAA0C,qCAAqC;AACnG,uBAAa,OAAO;AAAA,QACtB;AAAA,MACF,UAAE;AACA,YAAI,CAAC,UAAW,YAAW,KAAK;AAAA,MAClC;AAAA,IACF;AACA,SAAK,YAAY;AACjB,WAAO,MAAM;AACX,kBAAY;AAAA,IACd;AAAA,EACF,GAAG,CAAC,MAAM,MAAM,CAAC,CAAC;AAElB,QAAM,UAAU,MAAM,QAAQ,MAAM,QAAQ,MAAM,UAAU,YAAY,EAAE,WAAW,QAAQ,CAAC,GAAG,CAAC,IAAI,CAAC;AACvG,QAAM,aAAa,MAAM,QAAQ,MAAM;AACrC,QAAI,CAAC,KAAM,QAAO;AAClB,WACE,KAAK,gBACL,wBAAwB,KAAK,IAAI;AAAA,MAC/B,OAAO;AAAA,MACP,QAAQ;AAAA,MACR,MAAM,0BAA0B,KAAK,QAAQ;AAAA,IAC/C,CAAC;AAAA,EAEL,GAAG,CAAC,IAAI,CAAC;AACT,QAAM,cAAc,MAAM,QAAQ,MAAM;AACtC,QAAI,CAAC,KAAM,QAAO;AAClB,UAAM,WAAW,uBAAuB,KAAK,IAAI,EAAE,UAAU,KAAK,CAAC;AACnE,WAAO,mBAAmB,QAAQ;AAAA,EACpC,GAAG,CAAC,IAAI,CAAC;AAET,QAAM,mBAAmB,MAAM;AAAA,IAC7B,OAAO;AAAA,MACL,OAAO,EAAE,kDAAkD,aAAa;AAAA,MACxE,aAAa;AAAA,QACX;AAAA,QACA;AAAA,MACF;AAAA,MACA,MAAM,EAAE,iDAAiD,MAAM;AAAA,MAC/D,IAAI,EAAE,+CAA+C,WAAW;AAAA,MAChE,MAAM,EAAE,iDAAiD,MAAM;AAAA,MAC/D,OAAO,EAAE,kDAAkD,OAAO;AAAA,MAClE,KAAK,EAAE,gDAAgD,gBAAgB;AAAA,MACvE,QAAQ,EAAE,mDAAmD,QAAQ;AAAA,IACvE;AAAA,IACA,CAAC,CAAC;AAAA,EACJ;AAEA,QAAM,iBAAiB,MAAM,QAAqB,MAAM;AACtD,WAAO;AAAA,MACL;AAAA,QACE,IAAI;AAAA,QACJ,OAAO,EAAE,kCAAkC,MAAM;AAAA,QACjD,MAAM;AAAA,QACN,WAAW,CAAC,EAAE,OAAO,UAAU,SAAS,MACtC;AAAA,UAAC;AAAA;AAAA,YACC,OAAO,MAAM,QAAQ,KAAK,IAAK,QAAqB,CAAC;AAAA,YACrD,UAAU,CAAC,SAAS,SAAS,IAAI;AAAA,YACjC,aAAa;AAAA,YACb,aAAa,EAAE,gDAAgD,UAAU;AAAA,YACzE,UAAU,QAAQ,QAAQ,KAAK;AAAA;AAAA,QACjC;AAAA,MAEJ;AAAA,MACA;AAAA,QACE,IAAI;AAAA,QACJ,OAAO;AAAA,QACP,MAAM;AAAA,QACN,WAAW,CAAC,EAAE,OAAO,UAAU,SAAS,MACtC;AAAA,UAAC;AAAA;AAAA,YACC,OAAO,MAAM,QAAQ,KAAK,IAAK,QAA8B,CAAC;AAAA,YAC9D,UAAU,CAAC,SAAS,SAAS,IAAI;AAAA,YACjC,QAAQ;AAAA,YACR,UAAU,QAAQ,QAAQ,KAAK;AAAA;AAAA,QACjC;AAAA,MAEJ;AAAA,IACF;AAAA,EACF,GAAG,CAAC,kBAAkB,eAAe,SAAS,CAAC,CAAC;AAEhD,QAAM,iBAAiB,MAAM,QAAyB,MAAM;AAC1D,WAAO;AAAA,MACL;AAAA,QACE,IAAI;AAAA,QACJ,OAAO,EAAE,wCAAwC,SAAS;AAAA,QAC1D,QAAQ;AAAA,QACR,QAAQ,CAAC,QAAQ,aAAa;AAAA,MAChC;AAAA,MACA;AAAA,QACE,IAAI;AAAA,QACJ,OAAO,EAAE,+BAA+B,mBAAmB;AAAA,QAC3D,QAAQ;AAAA,QACR,MAAM;AAAA,MACR;AAAA,IACF;AAAA,EACF,GAAG,CAAC,CAAC,CAAC;AAEN,QAAM,iBAAiB,MAAM;AAAA,IAC3B,MACE,EACG,OAAO;AAAA,MACN,IAAI,EAAE,OAAO,EAAE,IAAI,CAAC;AAAA,MACpB,MAAM,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,SAAS;AAAA,MACnC,aAAa,EACV;AAAA,QACC,EAAE,OAAO;AAAA,UACP,MAAM,EAAE,OAAO,EAAE,IAAI,CAAC;AAAA,UACtB,IAAI,EAAE,OAAO,EAAE,IAAI,CAAC;AAAA,UACpB,MAAM,EAAE,OAAO,EAAE,SAAS;AAAA,UAC1B,OAAO,EAAE,OAAO,EAAE,SAAS;AAAA,QAC7B,CAAC;AAAA,MACH,EACC,SAAS;AAAA,IACd,CAAC,EACA,YAAY;AAAA,IACjB,CAAC;AAAA,EACH;AAEA,QAAM,eAAe,MAAM;AAAA,IACzB,OAAO,WAAyC;AAC9C,UAAI,CAAC,KAAM;AACX,YAAM,OAAO,MAAM,QAAQ,OAAO,IAAI,IAClC,OAAO,KAAK,IAAI,CAAC,QAAS,OAAO,QAAQ,WAAW,IAAI,KAAK,IAAI,EAAG,EAAE,OAAO,CAAC,QAAQ,IAAI,SAAS,CAAC,IACpG,CAAC;AACL,YAAM,cAAc,MAAM,QAAQ,OAAO,WAAW,IAChD,OAAO,YACJ,IAAI,CAAC,gBAAgB;AAAA,QACpB,MAAM,WAAW,MAAM,KAAK,KAAK;AAAA,QACjC,IAAI,WAAW,IAAI,KAAK,KAAK;AAAA,QAC7B,MAAM,WAAW,MAAM,KAAK,KAAK;AAAA,QACjC,OAAO,WAAW,OAAO,KAAK,KAAK;AAAA,MACrC,EAAE,EACD,OAAO,CAAC,eAAe,WAAW,QAAQ,WAAW,EAAE,IAC1D,CAAC;AACL,YAAM,eAAe,yBAAyB,QAAQ;AAAA,QACpD,WAAW,CAAC,UAAU,gCAAgC,KAAK;AAAA,MAC7D,CAAC;AACD,YAAM,UAAyC;AAAA,QAC7C;AAAA,QACA;AAAA,MACF;AACA,UAAI,OAAO,KAAK,YAAY,EAAE,QAAQ;AACpC,gBAAQ,eAAe;AAAA,MACzB;AACA,YAAM,OAAO,KAAK,IAAI,OAAO;AAAA,IAC/B;AAAA,IACA,CAAC,MAAM,MAAM;AAAA,EACf;AAEA,QAAM,gBAAgB,MAAM;AAAA,IAC1B,CAAC,UAA+B;AAC9B,UAAI,MAAM,QAAQ,UAAU;AAC1B,cAAM,eAAe;AACrB,qBAAa,KAAK;AAAA,MACpB;AAAA,IACF;AAAA,IACA,CAAC,YAAY;AAAA,EACf;AAEA,QAAM,uBAAuB,MAAM,YAAY,YAAY;AACzD,QAAI,CAAC,KAAM;AACX,UAAM,QAAQ,YAAY,OAAO,SAAS,IAAI;AAC9C,UAAM,SAAS,aAAa,OAAO,UAAU,IAAI;AACjD,QAAI,CAAC,SAAS,CAAC,QAAQ;AACrB;AAAA,QACE,EAAE,mDAAmD,4CAA4C;AAAA,QACjG;AAAA,MACF;AACA;AAAA,IACF;AACA,UAAM,MAAM,wBAAwB,KAAK,IAAI;AAAA,MAC3C,OAAO,SAAS,QAAQ,IAAI,QAAQ;AAAA,MACpC,QAAQ,UAAU,SAAS,IAAI,SAAS;AAAA,MACxC,MAAM,0BAA0B,KAAK,QAAQ;AAAA,IAC/C,CAAC;AACD,UAAM,WAAW,mBAAmB,GAAG;AACvC,QAAI;AACF,YAAM,UAAU,UAAU,UAAU,QAAQ;AAC5C;AAAA,QACE,EAAE,kDAAkD,mBAAmB;AAAA,QACvE;AAAA,MACF;AAAA,IACF,QAAQ;AACN;AAAA,QACE,EAAE,qDAAqD,qBAAqB;AAAA,QAC5E;AAAA,MACF;AAAA,IACF;AAAA,EACF,GAAG,CAAC,MAAM,YAAY,WAAW,CAAC,CAAC;AAEnC,QAAM,cAAc,EAAE,wCAAwC,kCAAkC;AAEhG,SACE,oBAAC,UAAO,MAAY,cAClB,+BAAC,iBAAc,WAAU,gBAAe,WAAW,eACjD;AAAA,wBAAC,gBACC,8BAAC,eAAa,YAAE,sCAAsC,0BAA0B,GAAE,GACpF;AAAA,IACC,OACC,qBAAC,SAAI,WAAU,aACb;AAAA,2BAAC,SAAI,WAAU,0CACb;AAAA,6BAAC,SAAI,WAAU,qBACb;AAAA,8BAAC,SAAI,WAAU,gCAA+B,OAAO,KAAK,UACvD,eAAK,UACR;AAAA,UACA,qBAAC,SAAI,WAAU,iCACZ;AAAA,2BAAe,KAAK,QAAQ;AAAA,YAAE;AAAA,YAAS,KAAK,kBAAkB,KAAK;AAAA,aACtE;AAAA,WACF;AAAA,QACC,cACC,oBAAC,UAAO,SAAQ,WAAU,MAAK,MAAK,SAAO,MAAC,WAAU,YACpD,+BAAC,OAAE,MAAM,aAAa,UAAQ,MAC5B;AAAA,8BAAC,YAAS,WAAU,gBAAe;AAAA,UAClC,EAAE,yCAAyC,UAAU;AAAA,WACxD,GACF,IACE;AAAA,SACN;AAAA,MACC,UACC,qBAAC,SAAI,WAAU,kBACb;AAAA,4BAAC,SAAI,WAAU,+DAA8D,MAAK,WAC9E,WAAC,WAAW,QAAQ,EAAY,IAAI,CAAC,QACrC;AAAA,UAAC;AAAA;AAAA,YAEC,MAAK;AAAA,YACL,SAAQ;AAAA,YACR,MAAK;AAAA,YACL,MAAK;AAAA,YACL,iBAAe,aAAa;AAAA,YAC5B,SAAS,MAAM,YAAY,GAAG;AAAA,YAC9B,WAAW;AAAA,cACT;AAAA,cACA,aAAa,MACT,mCACA;AAAA,YACN;AAAA,YAEC,kBAAQ,YACL,EAAE,wCAAwC,SAAS,IACnD,EAAE,iDAAiD,sBAAsB;AAAA;AAAA,UAhBxE;AAAA,QAiBP,CACD,GACH;AAAA,QACA,oBAAC,SAAI,WAAU,iBACZ,uBAAa,YACZ,aACE;AAAA,UAAC;AAAA;AAAA,YACC,KAAK;AAAA,YACL,KAAK,KAAK;AAAA,YACV,WAAU;AAAA;AAAA,QACZ,IAEA,oBAAC,SAAI,WAAU,iCACZ,YAAE,mDAAmD,sBAAsB,GAC9E,IAGF,qBAAC,SAAI,WAAU,aACb;AAAA,+BAAC,SAAI,WAAU,6BACb;AAAA,iCAAC,SAAI,WAAU,aACb;AAAA,kCAAC,WAAM,WAAU,uBAAsB,SAAQ,gBAC5C,YAAE,iDAAiD,YAAY,GAClE;AAAA,cACA;AAAA,gBAAC;AAAA;AAAA,kBACC,IAAG;AAAA,kBACH,MAAK;AAAA,kBACL,KAAK;AAAA,kBACL,OAAO;AAAA,kBACP,UAAU,CAAC,UAAU,aAAa,MAAM,OAAO,KAAK;AAAA,kBACpD,UAAU;AAAA;AAAA,cACZ;AAAA,eACF;AAAA,YACA,qBAAC,SAAI,WAAU,aACb;AAAA,kCAAC,WAAM,WAAU,uBAAsB,SAAQ,iBAC5C,YAAE,kDAAkD,aAAa,GACpE;AAAA,cACA;AAAA,gBAAC;AAAA;AAAA,kBACC,IAAG;AAAA,kBACH,MAAK;AAAA,kBACL,KAAK;AAAA,kBACL,OAAO;AAAA,kBACP,UAAU,CAAC,UAAU,cAAc,MAAM,OAAO,KAAK;AAAA,kBACrD,UAAU;AAAA;AAAA,cACZ;AAAA,eACF;AAAA,aACF;AAAA,UACA;AAAA,YAAC;AAAA;AAAA,cACC,MAAK;AAAA,cACL,SAAQ;AAAA,cACR,MAAK;AAAA,cACL,WAAU;AAAA,cACV,SAAS,MAAM,KAAK,qBAAqB;AAAA,cACzC,UAAU;AAAA,cAEV;AAAA,oCAAC,QAAK,WAAU,WAAU;AAAA,gBACzB,EAAE,gDAAgD,UAAU;AAAA;AAAA;AAAA,UAC/D;AAAA,WACF,GAEJ;AAAA,SACF,IACE;AAAA,MACH,YACC,oBAAC,SAAI,WAAU,6FACZ,qBACH,IACE;AAAA,MACJ,qBAAC,SAAI,WAAU,yDACb;AAAA,4BAAC,SAAI,WAAU,+CACZ,YAAE,+CAA+C,gBAAgB,GACpE;AAAA,QACA;AAAA,UAAC;AAAA;AAAA,YACC,SAAS;AAAA,YACT,YAAY,EAAE,0CAA0C,mBAAmB;AAAA,YAC3E,eAAe,EAAE,yCAAyC,WAAW;AAAA,YACrE,eAAe,EAAE,yCAAyC,WAAW;AAAA;AAAA,QACvE;AAAA,SACF;AAAA,MACA;AAAA,QAAC;AAAA;AAAA,UACC,UAAQ;AAAA,UACR,QAAQ;AAAA,UACR,UAAU,EAAE,YAAY;AAAA,UACxB,QAAQ;AAAA,UACR,QAAQ;AAAA,UACR,eAAe,iBAAiB;AAAA,UAChC,WAAW,CAAC,iBAAiB;AAAA,UAC7B,gBAAgB;AAAA,UAChB,aAAa,EAAE,qCAAqC,MAAM;AAAA,UAC1D,cACE,oBAAC,UAAO,MAAK,UAAS,SAAQ,WAAU,SAAS,MAAM,aAAa,KAAK,GACtE,YAAE,uCAAuC,QAAQ,GACpD;AAAA,UAEF,UAAU;AAAA;AAAA,MACZ;AAAA,OACF,IAEA,oBAAC,SAAI,WAAU,kDACZ,YAAE,4CAA4C,+BAA+B,GAChF;AAAA,KAEJ,GACF;AAEJ;",
4
+ "sourcesContent": ["\"use client\"\n\nimport * as React from 'react'\nimport { z } from 'zod'\nimport { Button } from '../../primitives/button'\nimport { Dialog, DialogContent, DialogHeader, DialogTitle } from '@open-mercato/ui/primitives/dialog'\nimport { Input } from '@open-mercato/ui/primitives/input'\nimport { TagsInput } from '@open-mercato/ui/backend/inputs/TagsInput'\nimport { CrudForm, type CrudField, type CrudFormGroup } from '@open-mercato/ui/backend/CrudForm'\nimport { collectCustomFieldValues } from '@open-mercato/ui/backend/utils/customFieldValues'\nimport { apiCall } from '@open-mercato/ui/backend/utils/apiCall'\nimport { flash } from '@open-mercato/ui/backend/FlashMessages'\nimport { useT } from '@open-mercato/shared/lib/i18n/context'\nimport { cn } from '@open-mercato/shared/lib/utils'\nimport { Copy, Download, Trash2 } from 'lucide-react'\nimport { AttachmentContentPreview } from '@open-mercato/core/modules/attachments/components/AttachmentContentPreview'\nimport { buildAttachmentFileUrl, buildAttachmentImageUrl, slugifyAttachmentFileName } from '@open-mercato/core/modules/attachments/lib/imageUrls'\nimport { E } from '@open-mercato/core/generated-shims/entities.ids.generated'\n\nexport type AttachmentAssignment = {\n type: string\n id: string\n href?: string | null\n label?: string | null\n}\n\nexport type AttachmentItem = {\n id: string\n fileName: string\n fileSize: number\n mimeType?: string | null\n partitionCode?: string | null\n partitionTitle?: string | null\n url?: string | null\n createdAt?: string | null\n tags?: string[]\n assignments?: AttachmentAssignment[]\n thumbnailUrl?: string\n content?: string | null\n}\n\nexport type AssignmentDraft = {\n type: string\n id: string\n href?: string\n label?: string\n}\n\nexport type AttachmentMetadataSavePayload = {\n tags: string[]\n assignments: AssignmentDraft[]\n customFields?: Record<string, unknown>\n}\n\ntype AttachmentMetadataResponse = {\n item: {\n id: string\n fileName?: string\n fileSize?: number\n mimeType?: string | null\n partitionCode?: string\n partitionTitle?: string | null\n tags?: string[]\n content?: string | null\n assignments?: AttachmentAssignment[]\n customFields?: Record<string, unknown>\n }\n error?: string\n}\n\ntype AttachmentMetadataFormValues = {\n id: string\n tags?: string[]\n assignments?: AssignmentDraft[]\n} & Record<string, unknown>\n\ntype AssignmentEditorLabels = {\n title: string\n description: string\n type: string\n id: string\n href: string\n label: string\n add: string\n remove: string\n}\n\ntype AttachmentMetadataDialogProps = {\n open: boolean\n onOpenChange: (next: boolean) => void\n item: AttachmentItem | null\n availableTags: string[]\n onSave: (id: string, payload: AttachmentMetadataSavePayload) => Promise<void>\n}\n\nfunction formatFileSize(value: number): string {\n if (!Number.isFinite(value)) return '\\u2014'\n if (value <= 0) return '0 B'\n const units = ['B', 'KB', 'MB', 'GB', 'TB']\n let idx = 0\n let current = value\n while (current >= 1024 && idx < units.length - 1) {\n current /= 1024\n idx += 1\n }\n return `${current.toFixed(idx === 0 ? 0 : 1)} ${units[idx]}`\n}\n\nconst ENV_APP_URL = (process.env.NEXT_PUBLIC_APP_URL || '').replace(/\\/$/, '')\n\nfunction resolveAbsoluteUrl(path: string): string {\n if (!path) return path\n if (/^https?:\\/\\//i.test(path)) return path\n const base =\n ENV_APP_URL ||\n (typeof window !== 'undefined' && window.location?.origin ? window.location.origin : '')\n if (!base) return path\n const normalizedBase = base.replace(/\\/$/, '')\n return `${normalizedBase}${path.startsWith('/') ? path : `/${path}`}`\n}\n\nfunction normalizeCustomFieldSubmitValue(value: unknown): unknown {\n if (Array.isArray(value)) {\n return value.filter((entry) => entry !== undefined)\n }\n if (value === undefined) return null\n return value\n}\n\nfunction prepareAssignmentsForForm(assignments?: AttachmentAssignment[] | null): AssignmentDraft[] {\n return (assignments ?? []).map((assignment) => ({\n type: assignment.type,\n id: assignment.id,\n href: assignment.href ?? '',\n label: assignment.label ?? '',\n }))\n}\n\nfunction prefixCustomFieldValues(values?: Record<string, unknown> | null): Record<string, unknown> {\n if (!values) return {}\n const prefixed: Record<string, unknown> = {}\n Object.entries(values).forEach(([key, value]) => {\n if (!key) return\n if (key.startsWith('cf_')) {\n prefixed[key] = value\n } else if (key.startsWith('cf:')) {\n const normalized = key.slice(3)\n if (normalized) prefixed[`cf_${normalized}`] = value\n } else {\n prefixed[`cf_${key}`] = value\n }\n })\n return prefixed\n}\n\nfunction AssignmentInputRow({\n value,\n onChange,\n labels,\n disabled,\n onRemove,\n}: {\n value: AssignmentDraft\n onChange: (next: AssignmentDraft) => void\n labels: AssignmentEditorLabels\n disabled?: boolean\n onRemove: () => void\n}) {\n return (\n <div className=\"grid grid-cols-1 gap-2 rounded-md border border-border/70 bg-background p-3 sm:grid-cols-2 lg:grid-cols-[1.2fr_1.2fr_1.6fr_1fr_auto]\">\n <div className=\"space-y-1\">\n <label className=\"text-xs font-medium\">{labels.type}</label>\n <Input\n value={value.type}\n onChange={(event) => onChange({ ...value, type: event.target.value })}\n placeholder=\"catalog.product\"\n disabled={disabled}\n />\n </div>\n <div className=\"space-y-1\">\n <label className=\"text-xs font-medium\">{labels.id}</label>\n <Input\n value={value.id}\n onChange={(event) => onChange({ ...value, id: event.target.value })}\n placeholder=\"Record ID\"\n disabled={disabled}\n />\n </div>\n <div className=\"space-y-1\">\n <label className=\"text-xs font-medium\">{labels.href}</label>\n <Input\n value={value.href ?? ''}\n onChange={(event) => onChange({ ...value, href: event.target.value })}\n placeholder=\"https://\"\n disabled={disabled}\n />\n </div>\n <div className=\"space-y-1\">\n <label className=\"text-xs font-medium\">{labels.label}</label>\n <Input\n value={value.label ?? ''}\n onChange={(event) => onChange({ ...value, label: event.target.value })}\n placeholder=\"Optional label\"\n disabled={disabled}\n />\n </div>\n <div className=\"flex items-end\">\n <Button type=\"button\" variant=\"ghost\" size=\"icon\" onClick={onRemove} disabled={disabled}>\n <Trash2 className=\"h-4 w-4\" />\n </Button>\n </div>\n </div>\n )\n}\n\nfunction AttachmentAssignmentsEditor({\n value,\n onChange,\n labels,\n disabled,\n}: {\n value: AssignmentDraft[]\n onChange: (next: AssignmentDraft[]) => void\n labels: AssignmentEditorLabels\n disabled?: boolean\n}) {\n const handleAdd = React.useCallback(() => {\n onChange([...value, { type: '', id: '', href: '', label: '' }])\n }, [onChange, value])\n\n const handleChange = React.useCallback(\n (index: number, next: AssignmentDraft) => {\n const draft = [...value]\n draft[index] = next\n onChange(draft)\n },\n [onChange, value],\n )\n\n const handleRemove = React.useCallback(\n (index: number) => {\n const next = value.filter((_, idx) => idx !== index)\n onChange(next)\n },\n [onChange, value],\n )\n\n return (\n <div className=\"space-y-2\">\n <div className=\"space-y-1\">\n <div className=\"text-sm font-medium\">{labels.title}</div>\n <p className=\"text-xs text-muted-foreground\">{labels.description}</p>\n </div>\n <div className=\"space-y-2\">\n {value.length ? value.map((entry, idx) => (\n <AssignmentInputRow\n key={`${entry.type}-${entry.id}-${idx}`}\n value={entry}\n labels={labels}\n disabled={disabled}\n onChange={(next) => handleChange(idx, next)}\n onRemove={() => handleRemove(idx)}\n />\n )) : (\n <div className=\"rounded-md border border-dashed border-border/70 px-3 py-4 text-xs text-muted-foreground\">\n {labels.description}\n </div>\n )}\n </div>\n <Button type=\"button\" variant=\"outline\" size=\"sm\" onClick={handleAdd} disabled={disabled}>\n {labels.add}\n </Button>\n </div>\n )\n}\n\nexport function AttachmentMetadataDialog({ open, onOpenChange, item, availableTags, onSave }: AttachmentMetadataDialogProps) {\n const t = useT()\n const [sizeWidth, setSizeWidth] = React.useState<string>('')\n const [sizeHeight, setSizeHeight] = React.useState<string>('')\n const [imageTab, setImageTab] = React.useState<'preview' | 'resize'>('preview')\n const [initialValues, setInitialValues] = React.useState<Partial<AttachmentMetadataFormValues> | null>(null)\n const [loading, setLoading] = React.useState(false)\n const [loadError, setLoadError] = React.useState<string | null>(null)\n const [extractedContent, setExtractedContent] = React.useState<string | null>(null)\n\n React.useEffect(() => {\n if (!open || !item) {\n setInitialValues(null)\n setLoadError(null)\n setLoading(false)\n setImageTab('preview')\n setExtractedContent(null)\n return\n }\n let cancelled = false\n setLoading(true)\n setLoadError(null)\n setSizeWidth('')\n setSizeHeight('')\n setImageTab('preview')\n setInitialValues({\n id: item.id,\n tags: item.tags ?? [],\n assignments: prepareAssignmentsForForm(item.assignments),\n })\n setExtractedContent(item.content && item.content.trim() ? item.content : null)\n const loadDetails = async () => {\n try {\n const call = await apiCall<AttachmentMetadataResponse>(`/api/attachments/library/${encodeURIComponent(item.id)}`)\n if (!call.ok || !call.result?.item) {\n const message = call.result?.error || t('attachments.library.metadata.error', 'Failed to update metadata.')\n throw new Error(message)\n }\n const payload = call.result.item\n const prefixedCustom = prefixCustomFieldValues(payload.customFields)\n if (!cancelled) {\n setInitialValues({\n id: payload.id,\n tags: Array.isArray(payload.tags) ? payload.tags : [],\n assignments: prepareAssignmentsForForm(payload.assignments ?? item.assignments),\n ...prefixedCustom,\n })\n const nextContent = typeof payload.content === 'string' && payload.content.trim() ? payload.content : null\n setExtractedContent(nextContent)\n }\n } catch (err: any) {\n if (!cancelled) {\n const message =\n err?.message || t('attachments.library.metadata.loadError', 'Failed to load attachment metadata.')\n setLoadError(message)\n }\n } finally {\n if (!cancelled) setLoading(false)\n }\n }\n void loadDetails()\n return () => {\n cancelled = true\n }\n }, [item, open, t])\n\n const isImage = React.useMemo(() => Boolean(item?.mimeType?.toLowerCase().startsWith('image/')), [item])\n const previewUrl = React.useMemo(() => {\n if (!item) return null\n return (\n item.thumbnailUrl ??\n buildAttachmentImageUrl(item.id, {\n width: 320,\n height: 320,\n slug: slugifyAttachmentFileName(item.fileName),\n })\n )\n }, [item])\n const downloadUrl = React.useMemo(() => {\n if (!item) return null\n const original = buildAttachmentFileUrl(item.id, { download: true })\n return resolveAbsoluteUrl(original)\n }, [item])\n\n const assignmentLabels = React.useMemo(\n () => ({\n title: t('attachments.library.metadata.assignments.title', 'Assignments'),\n description: t(\n 'attachments.library.metadata.assignments.description',\n 'Add the records this attachment belongs to with optional links.',\n ),\n type: t('attachments.library.metadata.assignments.type', 'Type'),\n id: t('attachments.library.metadata.assignments.id', 'Record ID'),\n href: t('attachments.library.metadata.assignments.href', 'Link'),\n label: t('attachments.library.metadata.assignments.label', 'Label'),\n add: t('attachments.library.metadata.assignments.add', 'Add assignment'),\n remove: t('attachments.library.metadata.assignments.remove', 'Remove'),\n }),\n [t],\n )\n\n const metadataFields = React.useMemo<CrudField[]>(() => {\n return [\n {\n id: 'tags',\n label: t('attachments.library.table.tags', 'Tags'),\n type: 'custom',\n component: ({ value, setValue, disabled }) => (\n <TagsInput\n value={Array.isArray(value) ? (value as string[]) : []}\n onChange={(next) => setValue(next)}\n suggestions={availableTags}\n placeholder={t('attachments.library.metadata.tagsPlaceholder', 'Add tags')}\n disabled={Boolean(disabled) || loading}\n />\n ),\n },\n {\n id: 'assignments',\n label: '',\n type: 'custom',\n component: ({ value, setValue, disabled }) => (\n <AttachmentAssignmentsEditor\n value={Array.isArray(value) ? (value as AssignmentDraft[]) : []}\n onChange={(next) => setValue(next)}\n labels={assignmentLabels}\n disabled={Boolean(disabled) || loading}\n />\n ),\n },\n ]\n }, [assignmentLabels, availableTags, loading, t])\n\n const metadataGroups = React.useMemo<CrudFormGroup[]>(() => {\n return [\n {\n id: 'details',\n title: t('attachments.library.metadata.details', 'Details'),\n column: 1,\n fields: ['tags', 'assignments'],\n },\n {\n id: 'customFields',\n title: t('entities.customFields.title', 'Custom attributes'),\n column: 2,\n kind: 'customFields',\n },\n ]\n }, [t])\n\n const metadataSchema = React.useMemo(\n () =>\n z\n .object({\n id: z.string().min(1),\n tags: z.array(z.string()).optional(),\n assignments: z\n .array(\n z.object({\n type: z.string().min(1),\n id: z.string().min(1),\n href: z.string().optional(),\n label: z.string().optional(),\n }),\n )\n .optional(),\n })\n .passthrough(),\n [],\n )\n\n const handleSubmit = React.useCallback(\n async (values: AttachmentMetadataFormValues) => {\n if (!item) return\n const tags = Array.isArray(values.tags)\n ? values.tags.map((tag) => (typeof tag === 'string' ? tag.trim() : '')).filter((tag) => tag.length > 0)\n : []\n const assignments = Array.isArray(values.assignments)\n ? values.assignments\n .map((assignment) => ({\n type: assignment.type?.trim() ?? '',\n id: assignment.id?.trim() ?? '',\n href: assignment.href?.trim() || undefined,\n label: assignment.label?.trim() || undefined,\n }))\n .filter((assignment) => assignment.type && assignment.id)\n : []\n const customFields = collectCustomFieldValues(values, {\n transform: (value) => normalizeCustomFieldSubmitValue(value),\n })\n const payload: AttachmentMetadataSavePayload = {\n tags,\n assignments,\n }\n if (Object.keys(customFields).length) {\n payload.customFields = customFields\n }\n await onSave(item.id, payload)\n },\n [item, onSave],\n )\n\n const handleKeyDown = React.useCallback(\n (event: React.KeyboardEvent) => {\n if (event.key === 'Escape') {\n event.preventDefault()\n onOpenChange(false)\n }\n },\n [onOpenChange],\n )\n\n const handleCopyResizedUrl = React.useCallback(async () => {\n if (!item) return\n const width = sizeWidth ? Number(sizeWidth) : undefined\n const height = sizeHeight ? Number(sizeHeight) : undefined\n if (!width && !height) {\n flash(\n t('attachments.library.metadata.resizeTool.missing', 'Enter width or height to generate the URL.'),\n 'error',\n )\n return\n }\n const url = buildAttachmentImageUrl(item.id, {\n width: width && width > 0 ? width : undefined,\n height: height && height > 0 ? height : undefined,\n slug: slugifyAttachmentFileName(item.fileName),\n })\n const absolute = resolveAbsoluteUrl(url)\n try {\n await navigator.clipboard.writeText(absolute)\n flash(\n t('attachments.library.metadata.resizeTool.copied', 'Image URL copied.'),\n 'success',\n )\n } catch {\n flash(\n t('attachments.library.metadata.resizeTool.copyError', 'Unable to copy URL.'),\n 'error',\n )\n }\n }, [item, sizeHeight, sizeWidth, t])\n\n const loadMessage = t('attachments.library.metadata.loading', 'Loading attachment details\\u2026')\n\n return (\n <Dialog open={open} onOpenChange={onOpenChange}>\n <DialogContent className=\"sm:max-w-2xl\" onKeyDown={handleKeyDown}>\n <DialogHeader>\n <DialogTitle>{t('attachments.library.metadata.title', 'Edit attachment metadata')}</DialogTitle>\n </DialogHeader>\n {item ? (\n <div className=\"space-y-4\">\n <div className=\"flex items-start justify-between gap-3\">\n <div className=\"space-y-1 min-w-0\">\n <div className=\"truncate text-sm font-medium\" title={item.fileName}>\n {item.fileName}\n </div>\n <div className=\"text-xs text-muted-foreground\">\n {formatFileSize(item.fileSize)} {'\\u2022'} {item.partitionTitle ?? item.partitionCode}\n </div>\n </div>\n {downloadUrl ? (\n <Button variant=\"outline\" size=\"sm\" asChild className=\"shrink-0\">\n <a href={downloadUrl} download>\n <Download className=\"mr-2 h-4 w-4\" />\n {t('attachments.library.metadata.download', 'Download')}\n </a>\n </Button>\n ) : null}\n </div>\n {isImage ? (\n <div className=\"rounded border\">\n <div className=\"flex flex-wrap gap-4 border-b px-3 py-2 text-sm font-medium\" role=\"tablist\">\n {(['preview', 'resize'] as const).map((tab) => (\n <Button\n key={tab}\n type=\"button\"\n variant=\"ghost\"\n size=\"sm\"\n role=\"tab\"\n aria-selected={imageTab === tab}\n onClick={() => setImageTab(tab)}\n className={cn(\n 'h-auto -mb-px rounded-none border-b-2 border-transparent px-0 py-1',\n imageTab === tab\n ? 'border-primary text-foreground'\n : 'text-muted-foreground hover:text-foreground',\n )}\n >\n {tab === 'preview'\n ? t('attachments.library.metadata.preview', 'Preview')\n : t('attachments.library.metadata.resizeTool.title', 'Generate resized URL')}\n </Button>\n ))}\n </div>\n <div className=\"space-y-3 p-3\">\n {imageTab === 'preview' ? (\n previewUrl ? (\n <img\n src={previewUrl}\n alt={item.fileName}\n className=\"h-48 w-full rounded-md bg-muted object-contain\"\n />\n ) : (\n <div className=\"text-sm text-muted-foreground\">\n {t('attachments.library.metadata.previewUnavailable', 'Preview unavailable.')}\n </div>\n )\n ) : (\n <div className=\"space-y-2\">\n <div className=\"grid gap-2 sm:grid-cols-2\">\n <div className=\"space-y-1\">\n <label className=\"text-xs font-medium\" htmlFor=\"resize-width\">\n {t('attachments.library.metadata.resizeTool.width', 'Width (px)')}\n </label>\n <Input\n id=\"resize-width\"\n type=\"number\"\n min={0}\n value={sizeWidth}\n onChange={(event) => setSizeWidth(event.target.value)}\n disabled={loading}\n />\n </div>\n <div className=\"space-y-1\">\n <label className=\"text-xs font-medium\" htmlFor=\"resize-height\">\n {t('attachments.library.metadata.resizeTool.height', 'Height (px)')}\n </label>\n <Input\n id=\"resize-height\"\n type=\"number\"\n min={0}\n value={sizeHeight}\n onChange={(event) => setSizeHeight(event.target.value)}\n disabled={loading}\n />\n </div>\n </div>\n <Button\n type=\"button\"\n variant=\"outline\"\n size=\"sm\"\n className=\"inline-flex items-center gap-2\"\n onClick={() => void handleCopyResizedUrl()}\n disabled={loading}\n >\n <Copy className=\"h-4 w-4\" />\n {t('attachments.library.metadata.resizeTool.copy', 'Copy URL')}\n </Button>\n </div>\n )}\n </div>\n </div>\n ) : null}\n {loadError ? (\n <div className=\"rounded border border-destructive/40 bg-destructive/10 px-3 py-2 text-xs text-destructive\">\n {loadError}\n </div>\n ) : null}\n <div className=\"rounded border border-border/70 bg-muted/30 px-3 py-2\">\n <div className=\"text-xs font-semibold text-muted-foreground\">\n {t('attachments.library.metadata.extractedTitle', 'Extracted text')}\n </div>\n <AttachmentContentPreview\n content={extractedContent}\n emptyLabel={t('attachments.library.metadata.noContent', 'No text extracted')}\n showMoreLabel={t('attachments.library.metadata.showMore', 'Show more')}\n showLessLabel={t('attachments.library.metadata.showLess', 'Show less')}\n />\n </div>\n <CrudForm<AttachmentMetadataFormValues>\n embedded\n schema={metadataSchema}\n entityId={E.attachments.attachment}\n fields={metadataFields}\n groups={metadataGroups}\n initialValues={initialValues ?? undefined}\n isLoading={!initialValues || loading}\n loadingMessage={loadMessage}\n submitLabel={t('attachments.library.metadata.save', 'Save')}\n extraActions={\n <Button type=\"button\" variant=\"outline\" onClick={() => onOpenChange(false)}>\n {t('attachments.library.metadata.cancel', 'Cancel')}\n </Button>\n }\n onSubmit={handleSubmit}\n />\n </div>\n ) : (\n <div className=\"py-8 text-center text-sm text-muted-foreground\">\n {t('attachments.library.metadata.noSelection', 'Select an attachment to edit.')}\n </div>\n )}\n </DialogContent>\n </Dialog>\n )\n}\n"],
5
+ "mappings": "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",
6
6
  "names": []
7
7
  }
@@ -16,7 +16,7 @@ import { Textarea } from "@open-mercato/ui/primitives/textarea";
16
16
  import { DatePicker } from "@open-mercato/ui/primitives/date-picker";
17
17
  import { useT } from "@open-mercato/shared/lib/i18n/context";
18
18
  import { cn } from "@open-mercato/shared/lib/utils";
19
- import { format } from "date-fns";
19
+ import { format } from "date-fns/format";
20
20
  import { LoadingMessage } from "./LoadingMessage.js";
21
21
  import { mapCrudServerErrorToFormErrors } from "../utils/serverErrors.js";
22
22
  import { MarkdownPreview } from "../markdown/index.js";