@open-mercato/core 0.6.6-develop.6201.1.8ceb502c4b → 0.6.6-develop.6204.1.30b1f58642

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 (22) hide show
  1. package/dist/modules/catalog/backend/catalog/products/[id]/page.js +52 -45
  2. package/dist/modules/catalog/backend/catalog/products/[id]/page.js.map +2 -2
  3. package/dist/modules/catalog/backend/catalog/products/[productId]/variants/[variantId]/page.js +9 -13
  4. package/dist/modules/catalog/backend/catalog/products/[productId]/variants/[variantId]/page.js.map +2 -2
  5. package/dist/modules/customers/components/detail/ActivitiesSection.js +17 -12
  6. package/dist/modules/customers/components/detail/ActivitiesSection.js.map +2 -2
  7. package/dist/modules/customers/components/detail/ActivityHistorySection.js +11 -7
  8. package/dist/modules/customers/components/detail/ActivityHistorySection.js.map +2 -2
  9. package/dist/modules/dictionaries/components/DictionariesManager.js +75 -33
  10. package/dist/modules/dictionaries/components/DictionariesManager.js.map +2 -2
  11. package/dist/modules/dictionaries/components/DictionaryEntriesEditor.js +95 -43
  12. package/dist/modules/dictionaries/components/DictionaryEntriesEditor.js.map +2 -2
  13. package/dist/modules/dictionaries/components/DictionarySelectControl.js +40 -19
  14. package/dist/modules/dictionaries/components/DictionarySelectControl.js.map +2 -2
  15. package/package.json +7 -7
  16. package/src/modules/catalog/backend/catalog/products/[id]/page.tsx +58 -48
  17. package/src/modules/catalog/backend/catalog/products/[productId]/variants/[variantId]/page.tsx +14 -13
  18. package/src/modules/customers/components/detail/ActivitiesSection.tsx +22 -13
  19. package/src/modules/customers/components/detail/ActivityHistorySection.tsx +13 -7
  20. package/src/modules/dictionaries/components/DictionariesManager.tsx +82 -34
  21. package/src/modules/dictionaries/components/DictionaryEntriesEditor.tsx +99 -42
  22. package/src/modules/dictionaries/components/DictionarySelectControl.tsx +43 -18
@@ -17,6 +17,7 @@ import { flash } from "@open-mercato/ui/backend/FlashMessages";
17
17
  import { apiCall, withScopedApiRequestHeaders } from "@open-mercato/ui/backend/utils/apiCall";
18
18
  import { buildOptimisticLockHeader } from "@open-mercato/ui/backend/utils/optimisticLock";
19
19
  import { surfaceRecordConflict } from "@open-mercato/ui/backend/conflicts";
20
+ import { useGuardedMutation } from "@open-mercato/ui/backend/injection/useGuardedMutation";
20
21
  import { useQueryClient } from "@tanstack/react-query";
21
22
  import { useOrganizationScopeVersion } from "@open-mercato/shared/lib/frontend/useOrganizationScope";
22
23
  import { useT } from "@open-mercato/shared/lib/i18n/context";
@@ -54,6 +55,14 @@ function DictionaryEntriesEditor({ dictionaryId, dictionaryName, readOnly = fals
54
55
  const [errors, setErrors] = React.useState({});
55
56
  const [translateEntry, setTranslateEntry] = React.useState(null);
56
57
  const appearance = useAppearanceState(formState.icon, formState.color);
58
+ const mutationContextId = React.useMemo(
59
+ () => `dictionaries:dictionary_entry:${dictionaryId}`,
60
+ [dictionaryId]
61
+ );
62
+ const { runMutation, retryLastMutation } = useGuardedMutation({
63
+ contextId: mutationContextId,
64
+ blockedMessage: t("ui.forms.flash.saveBlocked", "Save blocked by validation")
65
+ });
57
66
  const resetForm = React.useCallback(() => {
58
67
  setFormState({ value: "", label: "", color: null, icon: null });
59
68
  appearance.setColor(null);
@@ -115,38 +124,63 @@ function DictionaryEntriesEditor({ dictionaryId, dictionaryName, readOnly = fals
115
124
  icon: appearance.icon
116
125
  };
117
126
  if (formState.id) {
118
- const call = await withScopedApiRequestHeaders(
119
- buildOptimisticLockHeader(formState.updatedAt),
120
- () => apiCall(
121
- `/api/dictionaries/${dictionaryId}/entries/${formState.id}`,
122
- {
123
- method: "PATCH",
124
- headers: { "content-type": "application/json" },
125
- body: JSON.stringify(payload)
127
+ const entryId = formState.id;
128
+ const expectedUpdatedAt = formState.updatedAt;
129
+ await runMutation({
130
+ operation: () => withScopedApiRequestHeaders(
131
+ buildOptimisticLockHeader(expectedUpdatedAt),
132
+ async () => {
133
+ const call = await apiCall(
134
+ `/api/dictionaries/${dictionaryId}/entries/${entryId}`,
135
+ {
136
+ method: "PATCH",
137
+ headers: { "content-type": "application/json" },
138
+ body: JSON.stringify(payload)
139
+ }
140
+ );
141
+ if (!call.ok) {
142
+ throw Object.assign(
143
+ new Error(typeof call.result?.error === "string" ? call.result.error : "Failed to save dictionary entry"),
144
+ { status: call.status, ...call.result && typeof call.result === "object" ? call.result : {} }
145
+ );
146
+ }
147
+ return call;
126
148
  }
127
- )
128
- );
129
- if (!call.ok) {
130
- throw Object.assign(
131
- new Error(typeof call.result?.error === "string" ? call.result.error : "Failed to save dictionary entry"),
132
- { status: call.status, ...call.result && typeof call.result === "object" ? call.result : {} }
133
- );
134
- }
149
+ ),
150
+ context: {
151
+ formId: mutationContextId,
152
+ resourceKind: "dictionaries.dictionary_entry",
153
+ resourceId: entryId,
154
+ retryLastMutation
155
+ },
156
+ mutationPayload: payload
157
+ });
135
158
  flash(t("dictionaries.config.entries.success.update", "Dictionary entry updated."), "success");
136
159
  } else {
137
- const call = await apiCall(
138
- `/api/dictionaries/${dictionaryId}/entries`,
139
- {
140
- method: "POST",
141
- headers: { "content-type": "application/json" },
142
- body: JSON.stringify(payload)
143
- }
144
- );
145
- if (!call.ok) {
146
- throw new Error(
147
- typeof call.result?.error === "string" ? call.result.error : "Failed to create dictionary entry"
148
- );
149
- }
160
+ await runMutation({
161
+ operation: async () => {
162
+ const call = await apiCall(
163
+ `/api/dictionaries/${dictionaryId}/entries`,
164
+ {
165
+ method: "POST",
166
+ headers: { "content-type": "application/json" },
167
+ body: JSON.stringify(payload)
168
+ }
169
+ );
170
+ if (!call.ok) {
171
+ throw new Error(
172
+ typeof call.result?.error === "string" ? call.result.error : "Failed to create dictionary entry"
173
+ );
174
+ }
175
+ return call;
176
+ },
177
+ context: {
178
+ formId: mutationContextId,
179
+ resourceKind: "dictionaries.dictionary_entry",
180
+ retryLastMutation
181
+ },
182
+ mutationPayload: payload
183
+ });
150
184
  flash(t("dictionaries.config.entries.success.create", "Dictionary entry created."), "success");
151
185
  }
152
186
  await invalidateDictionaryEntries(queryClient, dictionaryId);
@@ -164,7 +198,7 @@ function DictionaryEntriesEditor({ dictionaryId, dictionaryName, readOnly = fals
164
198
  } finally {
165
199
  setIsSaving(false);
166
200
  }
167
- }, [appearance, dictionaryId, formState.id, formState.label, formState.updatedAt, formState.value, queryClient, readOnly, readOnlyMessage, t]);
201
+ }, [appearance, dictionaryId, formState.id, formState.label, formState.updatedAt, formState.value, mutationContextId, queryClient, readOnly, readOnlyMessage, runMutation, retryLastMutation, t]);
168
202
  const handleDelete = React.useCallback(
169
203
  async (entry) => {
170
204
  if (readOnly) {
@@ -179,28 +213,46 @@ function DictionaryEntriesEditor({ dictionaryId, dictionaryName, readOnly = fals
179
213
  if (!confirmDelete) return;
180
214
  setIsDeleting(true);
181
215
  try {
182
- const call = await withScopedApiRequestHeaders(
183
- buildOptimisticLockHeader(entry.updatedAt),
184
- () => apiCall(
185
- `/api/dictionaries/${dictionaryId}/entries/${entry.id}`,
186
- { method: "DELETE" }
187
- )
188
- );
189
- if (!call.ok) {
190
- throw new Error(
191
- typeof call.result?.error === "string" ? call.result.error : "Failed to delete dictionary entry"
192
- );
193
- }
216
+ await runMutation({
217
+ operation: () => withScopedApiRequestHeaders(
218
+ buildOptimisticLockHeader(entry.updatedAt),
219
+ async () => {
220
+ const call = await apiCall(
221
+ `/api/dictionaries/${dictionaryId}/entries/${entry.id}`,
222
+ { method: "DELETE" }
223
+ );
224
+ if (!call.ok) {
225
+ throw Object.assign(
226
+ new Error(
227
+ typeof call.result?.error === "string" ? call.result.error : "Failed to delete dictionary entry"
228
+ ),
229
+ { status: call.status, ...call.result && typeof call.result === "object" ? call.result : {} }
230
+ );
231
+ }
232
+ return call;
233
+ }
234
+ ),
235
+ context: {
236
+ formId: mutationContextId,
237
+ resourceKind: "dictionaries.dictionary_entry",
238
+ resourceId: entry.id,
239
+ retryLastMutation
240
+ },
241
+ mutationPayload: { id: entry.id }
242
+ });
194
243
  await invalidateDictionaryEntries(queryClient, dictionaryId);
195
244
  flash(t("dictionaries.config.entries.success.delete", "Dictionary entry deleted."), "success");
196
245
  } catch (err) {
246
+ if (surfaceRecordConflict(err, t)) {
247
+ return;
248
+ }
197
249
  console.error("Failed to delete dictionary entry", err);
198
250
  flash(t("dictionaries.config.entries.error.delete", "Failed to delete dictionary entry."), "error");
199
251
  } finally {
200
252
  setIsDeleting(false);
201
253
  }
202
254
  },
203
- [confirm, dictionaryId, queryClient, readOnly, readOnlyMessage, t]
255
+ [confirm, dictionaryId, mutationContextId, queryClient, readOnly, readOnlyMessage, runMutation, retryLastMutation, t]
204
256
  );
205
257
  const tableContent = React.useMemo(() => {
206
258
  if (isInitialLoading) {
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../../src/modules/dictionaries/components/DictionaryEntriesEditor.tsx"],
4
- "sourcesContent": ["\"use client\"\n\nimport * as React from 'react'\nimport { Plus, Pencil, Trash2, RefreshCw, Languages } from 'lucide-react'\nimport { Button } from '@open-mercato/ui/primitives/button'\nimport { Input } from '@open-mercato/ui/primitives/input'\nimport {\n Dialog,\n DialogContent,\n DialogFooter,\n DialogHeader,\n DialogTitle,\n} from '@open-mercato/ui/primitives/dialog'\nimport { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from '@open-mercato/ui/primitives/table'\nimport { Spinner } from '@open-mercato/ui/primitives/spinner'\nimport { flash } from '@open-mercato/ui/backend/FlashMessages'\nimport { apiCall, withScopedApiRequestHeaders } from '@open-mercato/ui/backend/utils/apiCall'\nimport { buildOptimisticLockHeader } from '@open-mercato/ui/backend/utils/optimisticLock'\nimport { surfaceRecordConflict } from '@open-mercato/ui/backend/conflicts'\nimport { useQueryClient } from '@tanstack/react-query'\nimport { useOrganizationScopeVersion } from '@open-mercato/shared/lib/frontend/useOrganizationScope'\nimport { useT } from '@open-mercato/shared/lib/i18n/context'\nimport { useConfirmDialog } from '@open-mercato/ui/backend/confirm-dialog'\nimport { AppearanceSelector, useAppearanceState } from './AppearanceSelector'\nimport { DictionaryValue } from './dictionaryAppearance'\nimport {\n invalidateDictionaryEntries,\n useDictionaryEntries,\n} from './hooks/useDictionaryEntries'\nimport type { DictionaryEntryRecord } from './hooks/useDictionaryEntries'\nimport {\n DialogDescription,\n} from '@open-mercato/ui/primitives/dialog'\nimport { TranslationManager } from '@open-mercato/core/modules/translations/components/TranslationManager'\n\ntype Entry = DictionaryEntryRecord\n\ntype DictionaryEntriesEditorProps = {\n dictionaryId: string\n dictionaryName: string\n readOnly?: boolean\n}\n\ntype FormState = {\n id?: string | null\n value: string\n label: string\n color: string | null\n icon: string | null\n updatedAt?: string | null\n}\n\nexport function DictionaryEntriesEditor({ dictionaryId, dictionaryName, readOnly = false }: DictionaryEntriesEditorProps) {\n const t = useT()\n const { confirm, ConfirmDialogElement } = useConfirmDialog()\n const readOnlyMessage = t('dictionaries.config.entries.readOnly', 'Inherited dictionaries are managed at the parent organization.')\n const queryClient = useQueryClient()\n const scopeVersion = useOrganizationScopeVersion()\n const dictionaryQuery = useDictionaryEntries(dictionaryId, scopeVersion)\n const entries = React.useMemo<Entry[]>(() => dictionaryQuery.data?.fullEntries ?? [], [dictionaryQuery.data?.fullEntries])\n const dictionaryMap = dictionaryQuery.data?.map ?? {}\n const isInitialLoading = dictionaryQuery.isLoading\n const loadError = dictionaryQuery.isError\n ? t('dictionaries.config.entries.error.load', 'Failed to load dictionary entries.')\n : null\n const [dialogOpen, setDialogOpen] = React.useState(false)\n const [isDeleting, setIsDeleting] = React.useState(false)\n const [isSaving, setIsSaving] = React.useState(false)\n const [formState, setFormState] = React.useState<FormState>(() => ({\n value: '',\n label: '',\n color: null,\n icon: null,\n }))\n const [errors, setErrors] = React.useState<{ value?: string; label?: string }>({})\n const [translateEntry, setTranslateEntry] = React.useState<Entry | null>(null)\n const appearance = useAppearanceState(formState.icon, formState.color)\n\n const resetForm = React.useCallback(() => {\n setFormState({ value: '', label: '', color: null, icon: null })\n appearance.setColor(null)\n appearance.setIcon(null)\n setErrors({})\n }, [appearance])\n\n const openDialog = React.useCallback(\n (entry?: Entry) => {\n if (readOnly) {\n flash(readOnlyMessage, 'info')\n return\n }\n if (entry) {\n setFormState({\n id: entry.id,\n value: entry.value,\n label: entry.label,\n color: entry.color ?? null,\n icon: entry.icon ?? null,\n updatedAt: entry.updatedAt ?? null,\n })\n appearance.setColor(entry.color ?? null)\n appearance.setIcon(entry.icon ?? null)\n } else {\n resetForm()\n }\n setErrors({})\n setDialogOpen(true)\n },\n [appearance, readOnly, readOnlyMessage, resetForm],\n )\n\n const closeDialog = React.useCallback(() => {\n setDialogOpen(false)\n resetForm()\n setErrors({})\n }, [resetForm])\n\n const handleSave = React.useCallback(async () => {\n if (readOnly) {\n flash(readOnlyMessage, 'info')\n return\n }\n const trimmedValue = formState.value.trim()\n const trimmedLabel = formState.label.trim()\n const nextErrors: { value?: string } = {}\n if (!trimmedValue) {\n nextErrors.value = t('dictionaries.config.entries.error.required', 'Value is required.')\n }\n if (nextErrors.value) {\n setErrors(nextErrors)\n return\n }\n setErrors({})\n setIsSaving(true)\n try {\n const payload = {\n value: trimmedValue,\n label: trimmedLabel || trimmedValue,\n color: appearance.color,\n icon: appearance.icon,\n }\n if (formState.id) {\n const call = await withScopedApiRequestHeaders(\n buildOptimisticLockHeader(formState.updatedAt),\n () =>\n apiCall<Record<string, unknown>>(\n `/api/dictionaries/${dictionaryId}/entries/${formState.id}`,\n {\n method: 'PATCH',\n headers: { 'content-type': 'application/json' },\n body: JSON.stringify(payload),\n },\n ),\n )\n if (!call.ok) {\n throw Object.assign(\n new Error(typeof call.result?.error === 'string' ? call.result.error : 'Failed to save dictionary entry'),\n { status: call.status, ...(call.result && typeof call.result === 'object' ? call.result : {}) },\n )\n }\n flash(t('dictionaries.config.entries.success.update', 'Dictionary entry updated.'), 'success')\n } else {\n const call = await apiCall<Record<string, unknown>>(\n `/api/dictionaries/${dictionaryId}/entries`,\n {\n method: 'POST',\n headers: { 'content-type': 'application/json' },\n body: JSON.stringify(payload),\n },\n )\n if (!call.ok) {\n throw new Error(\n typeof call.result?.error === 'string' ? call.result.error : 'Failed to create dictionary entry',\n )\n }\n flash(t('dictionaries.config.entries.success.create', 'Dictionary entry created.'), 'success')\n }\n await invalidateDictionaryEntries(queryClient, dictionaryId)\n setDialogOpen(false)\n setFormState({ value: '', label: '', color: null, icon: null })\n appearance.setColor(null)\n appearance.setIcon(null)\n setErrors({})\n } catch (err) {\n if (surfaceRecordConflict(err, t)) {\n return\n }\n console.error('Failed to save dictionary entry', err)\n flash(t('dictionaries.config.entries.error.save', 'Failed to save dictionary entry.'), 'error')\n } finally {\n setIsSaving(false)\n }\n }, [appearance, dictionaryId, formState.id, formState.label, formState.updatedAt, formState.value, queryClient, readOnly, readOnlyMessage, t])\n\n const handleDelete = React.useCallback(\n async (entry: Entry) => {\n if (readOnly) {\n flash(readOnlyMessage, 'info')\n return\n }\n if (!entry.id) return\n const confirmDelete = await confirm({\n title: t('dictionaries.config.entries.delete.confirm', 'Delete \"{{value}}\"?', { value: entry.label || entry.value }),\n variant: 'destructive',\n })\n if (!confirmDelete) return\n setIsDeleting(true)\n try {\n const call = await withScopedApiRequestHeaders(\n buildOptimisticLockHeader(entry.updatedAt),\n () =>\n apiCall<Record<string, unknown>>(\n `/api/dictionaries/${dictionaryId}/entries/${entry.id}`,\n { method: 'DELETE' },\n ),\n )\n if (!call.ok) {\n throw new Error(\n typeof call.result?.error === 'string' ? call.result.error : 'Failed to delete dictionary entry',\n )\n }\n await invalidateDictionaryEntries(queryClient, dictionaryId)\n flash(t('dictionaries.config.entries.success.delete', 'Dictionary entry deleted.'), 'success')\n } catch (err) {\n console.error('Failed to delete dictionary entry', err)\n flash(t('dictionaries.config.entries.error.delete', 'Failed to delete dictionary entry.'), 'error')\n } finally {\n setIsDeleting(false)\n }\n },\n [confirm, dictionaryId, queryClient, readOnly, readOnlyMessage, t],\n )\n\n const tableContent = React.useMemo(() => {\n if (isInitialLoading) {\n return (\n <TableRow>\n <TableCell colSpan={4} className=\"py-8 text-center text-sm text-muted-foreground\">\n <Spinner className=\"mx-auto mb-2 h-5 w-5\" />\n {t('dictionaries.config.entries.loading', 'Loading entries\u2026')}\n </TableCell>\n </TableRow>\n )\n }\n if (loadError) {\n return (\n <TableRow>\n <TableCell colSpan={4} className=\"py-6 text-center text-sm text-destructive\">\n {loadError}\n </TableCell>\n </TableRow>\n )\n }\n if (!entries.length) {\n return (\n <TableRow>\n <TableCell colSpan={4} className=\"py-6 text-center text-sm text-muted-foreground\">\n {t('dictionaries.config.entries.empty', 'No entries yet.')}\n </TableCell>\n </TableRow>\n )\n }\n return entries.map((entry) => (\n <TableRow key={entry.id}>\n <TableCell className=\"font-medium\">{entry.value}</TableCell>\n <TableCell>{entry.label}</TableCell>\n <TableCell>\n <DictionaryValue\n value={entry.value}\n map={dictionaryMap}\n fallback={<span className=\"text-sm text-muted-foreground\">{t('dictionaries.config.entries.appearance.none', 'None')}</span>}\n />\n </TableCell>\n <TableCell className=\"flex items-center gap-2\">\n {readOnly ? (\n <span className=\"text-xs text-muted-foreground\">\n {t('dictionaries.config.entries.readOnlyActions', 'Managed in parent organization')}\n </span>\n ) : (\n <>\n <Button\n type=\"button\"\n variant=\"ghost\"\n size=\"icon\"\n onClick={() => openDialog(entry)}\n >\n <Pencil className=\"h-4 w-4\" />\n </Button>\n <Button\n type=\"button\"\n variant=\"ghost\"\n size=\"icon\"\n onClick={() => setTranslateEntry(entry)}\n title={t('dictionaries.config.entries.actions.translate', 'Translate')}\n >\n <Languages className=\"h-4 w-4\" />\n </Button>\n <Button\n type=\"button\"\n variant=\"ghost\"\n size=\"icon\"\n onClick={() => handleDelete(entry)}\n disabled={isDeleting}\n >\n <Trash2 className=\"h-4 w-4\" />\n </Button>\n </>\n )}\n </TableCell>\n </TableRow>\n ))\n }, [dictionaryMap, entries, handleDelete, isDeleting, isInitialLoading, loadError, openDialog, readOnly, t])\n\n return (\n <div className=\"space-y-4\">\n <div className=\"flex flex-wrap items-center justify-between gap-3\">\n <div>\n <h2 className=\"text-lg font-semibold\">{dictionaryName}</h2>\n <p className=\"text-sm text-muted-foreground\">\n {readOnly\n ? readOnlyMessage\n : t('dictionaries.config.entries.subtitle', 'Manage reusable values and appearance for this dictionary.')}\n </p>\n </div>\n <div className=\"flex gap-2\">\n <Button\n type=\"button\"\n variant=\"outline\"\n size=\"sm\"\n onClick={() => {\n dictionaryQuery.refetch().catch(() => {})\n }}\n >\n <RefreshCw className=\"mr-2 h-4 w-4\" />\n {t('dictionaries.config.entries.actions.refresh', 'Refresh')}\n </Button>\n <Button\n type=\"button\"\n size=\"sm\"\n onClick={() => openDialog()}\n disabled={readOnly}\n title={readOnly ? readOnlyMessage : undefined}\n >\n <Plus className=\"mr-2 h-4 w-4\" />\n {t('dictionaries.config.entries.actions.add', 'Add entry')}\n </Button>\n </div>\n </div>\n\n <div className=\"overflow-hidden rounded-md border\">\n <Table>\n <TableHeader>\n <TableRow>\n <TableHead className=\"w-48\">{t('dictionaries.config.entries.columns.value', 'Value')}</TableHead>\n <TableHead className=\"w-48\">{t('dictionaries.config.entries.columns.label', 'Label')}</TableHead>\n <TableHead>{t('dictionaries.config.entries.columns.appearance', 'Appearance')}</TableHead>\n <TableHead className=\"w-32 text-right\">{t('dictionaries.config.entries.columns.actions', 'Actions')}</TableHead>\n </TableRow>\n </TableHeader>\n <TableBody>{tableContent}</TableBody>\n </Table>\n </div>\n\n <Dialog open={dialogOpen} onOpenChange={(open) => (!open ? closeDialog() : undefined)}>\n <DialogContent>\n <DialogHeader>\n <DialogTitle>\n {formState.id\n ? t('dictionaries.config.entries.dialog.editTitle', 'Edit dictionary entry')\n : t('dictionaries.config.entries.dialog.addTitle', 'Add dictionary entry')}\n </DialogTitle>\n </DialogHeader>\n <div className=\"space-y-4\">\n <div className=\"space-y-2\">\n <label className=\"text-sm font-medium\">\n {t('dictionaries.config.entries.dialog.valueLabel', 'Value')}\n <span className=\"ml-1 text-destructive\">*</span>\n </label>\n <Input\n type=\"text\"\n value={formState.value}\n onChange={(event) => {\n const nextValue = event.target.value\n setFormState((prev) => ({ ...prev, value: nextValue }))\n if (errors.value) {\n setErrors((prev) => ({ ...prev, value: undefined }))\n }\n }}\n aria-invalid={errors.value ? 'true' : 'false'}\n aria-describedby=\"dictionary-entry-value-error\"\n />\n {errors.value ? (\n <p id=\"dictionary-entry-value-error\" className=\"text-xs text-destructive\">\n {errors.value}\n </p>\n ) : null}\n </div>\n <div className=\"space-y-2\">\n <label className=\"text-sm font-medium\">\n {t('dictionaries.config.entries.dialog.labelLabel', 'Label')}\n </label>\n <Input\n type=\"text\"\n value={formState.label}\n onChange={(event) => setFormState((prev) => ({ ...prev, label: event.target.value }))}\n placeholder={t('dictionaries.config.entries.dialog.labelPlaceholder', 'Display name shown in UI')}\n />\n </div>\n <AppearanceSelector\n icon={appearance.icon}\n color={appearance.color}\n onIconChange={(next) => {\n appearance.setIcon(next)\n setFormState((prev) => ({ ...prev, icon: next }))\n }}\n onColorChange={(next) => {\n appearance.setColor(next)\n setFormState((prev) => ({ ...prev, color: next }))\n }}\n labels={{\n colorLabel: t('dictionaries.config.entries.dialog.colorLabel', 'Color'),\n colorHelp: t('dictionaries.config.entries.dialog.colorHelp', 'Pick a highlight color for this entry.'),\n colorClearLabel: t('dictionaries.config.entries.dialog.colorClear', 'Remove color'),\n iconLabel: t('dictionaries.config.entries.dialog.iconLabel', 'Icon or emoji'),\n iconPlaceholder: t('dictionaries.config.entries.dialog.iconPlaceholder', 'Type an emoji or icon token.'),\n iconPickerTriggerLabel: t('dictionaries.config.entries.dialog.iconBrowse', 'Browse icons and emoji'),\n iconSearchPlaceholder: t('dictionaries.config.entries.dialog.iconSearchPlaceholder', 'Search icons or emojis\u2026'),\n iconSearchEmptyLabel: t('dictionaries.config.entries.dialog.iconSearchEmpty', 'No icons match your search.'),\n iconSuggestionsLabel: t('dictionaries.config.entries.dialog.iconSuggestions', 'Suggestions'),\n iconClearLabel: t('dictionaries.config.entries.dialog.iconClear', 'Remove icon'),\n previewEmptyLabel: t('dictionaries.config.entries.dialog.previewEmpty', 'No appearance selected'),\n }}\n />\n </div>\n <DialogFooter>\n <Button\n type=\"button\"\n variant=\"ghost\"\n onClick={closeDialog}\n disabled={isSaving}\n >\n {t('dictionaries.config.entries.dialog.cancel', 'Cancel')}\n </Button>\n <Button\n type=\"button\"\n onClick={handleSave}\n disabled={isSaving}\n >\n {isSaving ? <Spinner className=\"mr-2 h-4 w-4\" /> : null}\n {t('dictionaries.config.entries.dialog.save', 'Save')}\n </Button>\n </DialogFooter>\n </DialogContent>\n </Dialog>\n <Dialog open={!!translateEntry} onOpenChange={(open) => { if (!open) setTranslateEntry(null) }}>\n <DialogContent className=\"max-w-2xl max-h-[80vh] overflow-y-auto\">\n <DialogHeader>\n <DialogTitle>{t('translations.manager.translateEntry', 'Translate: {{label}}', { label: translateEntry?.label ?? '' })}</DialogTitle>\n <DialogDescription>\n {t('translations.manager.translateEntryDescription', 'Manage translations for this dictionary entry.')}\n </DialogDescription>\n </DialogHeader>\n {translateEntry?.id && (\n <TranslationManager\n mode=\"embedded\"\n entityType=\"dictionaries:dictionary_entry\"\n recordId={translateEntry.id}\n baseValues={{ label: translateEntry.label }}\n translatableFields={['label']}\n />\n )}\n </DialogContent>\n </Dialog>\n {ConfirmDialogElement}\n </div>\n )\n}\n"],
5
- "mappings": "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",
4
+ "sourcesContent": ["\"use client\"\n\nimport * as React from 'react'\nimport { Plus, Pencil, Trash2, RefreshCw, Languages } from 'lucide-react'\nimport { Button } from '@open-mercato/ui/primitives/button'\nimport { Input } from '@open-mercato/ui/primitives/input'\nimport {\n Dialog,\n DialogContent,\n DialogFooter,\n DialogHeader,\n DialogTitle,\n} from '@open-mercato/ui/primitives/dialog'\nimport { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from '@open-mercato/ui/primitives/table'\nimport { Spinner } from '@open-mercato/ui/primitives/spinner'\nimport { flash } from '@open-mercato/ui/backend/FlashMessages'\nimport { apiCall, withScopedApiRequestHeaders } from '@open-mercato/ui/backend/utils/apiCall'\nimport { buildOptimisticLockHeader } from '@open-mercato/ui/backend/utils/optimisticLock'\nimport { surfaceRecordConflict } from '@open-mercato/ui/backend/conflicts'\nimport { useGuardedMutation } from '@open-mercato/ui/backend/injection/useGuardedMutation'\nimport { useQueryClient } from '@tanstack/react-query'\nimport { useOrganizationScopeVersion } from '@open-mercato/shared/lib/frontend/useOrganizationScope'\nimport { useT } from '@open-mercato/shared/lib/i18n/context'\nimport { useConfirmDialog } from '@open-mercato/ui/backend/confirm-dialog'\nimport { AppearanceSelector, useAppearanceState } from './AppearanceSelector'\nimport { DictionaryValue } from './dictionaryAppearance'\nimport {\n invalidateDictionaryEntries,\n useDictionaryEntries,\n} from './hooks/useDictionaryEntries'\nimport type { DictionaryEntryRecord } from './hooks/useDictionaryEntries'\nimport {\n DialogDescription,\n} from '@open-mercato/ui/primitives/dialog'\nimport { TranslationManager } from '@open-mercato/core/modules/translations/components/TranslationManager'\n\ntype Entry = DictionaryEntryRecord\n\ntype DictionaryEntriesEditorProps = {\n dictionaryId: string\n dictionaryName: string\n readOnly?: boolean\n}\n\ntype FormState = {\n id?: string | null\n value: string\n label: string\n color: string | null\n icon: string | null\n updatedAt?: string | null\n}\n\nexport function DictionaryEntriesEditor({ dictionaryId, dictionaryName, readOnly = false }: DictionaryEntriesEditorProps) {\n const t = useT()\n const { confirm, ConfirmDialogElement } = useConfirmDialog()\n const readOnlyMessage = t('dictionaries.config.entries.readOnly', 'Inherited dictionaries are managed at the parent organization.')\n const queryClient = useQueryClient()\n const scopeVersion = useOrganizationScopeVersion()\n const dictionaryQuery = useDictionaryEntries(dictionaryId, scopeVersion)\n const entries = React.useMemo<Entry[]>(() => dictionaryQuery.data?.fullEntries ?? [], [dictionaryQuery.data?.fullEntries])\n const dictionaryMap = dictionaryQuery.data?.map ?? {}\n const isInitialLoading = dictionaryQuery.isLoading\n const loadError = dictionaryQuery.isError\n ? t('dictionaries.config.entries.error.load', 'Failed to load dictionary entries.')\n : null\n const [dialogOpen, setDialogOpen] = React.useState(false)\n const [isDeleting, setIsDeleting] = React.useState(false)\n const [isSaving, setIsSaving] = React.useState(false)\n const [formState, setFormState] = React.useState<FormState>(() => ({\n value: '',\n label: '',\n color: null,\n icon: null,\n }))\n const [errors, setErrors] = React.useState<{ value?: string; label?: string }>({})\n const [translateEntry, setTranslateEntry] = React.useState<Entry | null>(null)\n const appearance = useAppearanceState(formState.icon, formState.color)\n const mutationContextId = React.useMemo(\n () => `dictionaries:dictionary_entry:${dictionaryId}`,\n [dictionaryId],\n )\n const { runMutation, retryLastMutation } = useGuardedMutation<{\n formId: string\n resourceKind: string\n resourceId?: string\n retryLastMutation: () => Promise<boolean>\n }>({\n contextId: mutationContextId,\n blockedMessage: t('ui.forms.flash.saveBlocked', 'Save blocked by validation'),\n })\n\n const resetForm = React.useCallback(() => {\n setFormState({ value: '', label: '', color: null, icon: null })\n appearance.setColor(null)\n appearance.setIcon(null)\n setErrors({})\n }, [appearance])\n\n const openDialog = React.useCallback(\n (entry?: Entry) => {\n if (readOnly) {\n flash(readOnlyMessage, 'info')\n return\n }\n if (entry) {\n setFormState({\n id: entry.id,\n value: entry.value,\n label: entry.label,\n color: entry.color ?? null,\n icon: entry.icon ?? null,\n updatedAt: entry.updatedAt ?? null,\n })\n appearance.setColor(entry.color ?? null)\n appearance.setIcon(entry.icon ?? null)\n } else {\n resetForm()\n }\n setErrors({})\n setDialogOpen(true)\n },\n [appearance, readOnly, readOnlyMessage, resetForm],\n )\n\n const closeDialog = React.useCallback(() => {\n setDialogOpen(false)\n resetForm()\n setErrors({})\n }, [resetForm])\n\n const handleSave = React.useCallback(async () => {\n if (readOnly) {\n flash(readOnlyMessage, 'info')\n return\n }\n const trimmedValue = formState.value.trim()\n const trimmedLabel = formState.label.trim()\n const nextErrors: { value?: string } = {}\n if (!trimmedValue) {\n nextErrors.value = t('dictionaries.config.entries.error.required', 'Value is required.')\n }\n if (nextErrors.value) {\n setErrors(nextErrors)\n return\n }\n setErrors({})\n setIsSaving(true)\n try {\n const payload = {\n value: trimmedValue,\n label: trimmedLabel || trimmedValue,\n color: appearance.color,\n icon: appearance.icon,\n }\n if (formState.id) {\n const entryId = formState.id\n const expectedUpdatedAt = formState.updatedAt\n await runMutation({\n operation: () =>\n withScopedApiRequestHeaders(\n buildOptimisticLockHeader(expectedUpdatedAt),\n async () => {\n const call = await apiCall<Record<string, unknown>>(\n `/api/dictionaries/${dictionaryId}/entries/${entryId}`,\n {\n method: 'PATCH',\n headers: { 'content-type': 'application/json' },\n body: JSON.stringify(payload),\n },\n )\n if (!call.ok) {\n throw Object.assign(\n new Error(typeof call.result?.error === 'string' ? call.result.error : 'Failed to save dictionary entry'),\n { status: call.status, ...(call.result && typeof call.result === 'object' ? call.result : {}) },\n )\n }\n return call\n },\n ),\n context: {\n formId: mutationContextId,\n resourceKind: 'dictionaries.dictionary_entry',\n resourceId: entryId,\n retryLastMutation,\n },\n mutationPayload: payload,\n })\n flash(t('dictionaries.config.entries.success.update', 'Dictionary entry updated.'), 'success')\n } else {\n await runMutation({\n operation: async () => {\n const call = await apiCall<Record<string, unknown>>(\n `/api/dictionaries/${dictionaryId}/entries`,\n {\n method: 'POST',\n headers: { 'content-type': 'application/json' },\n body: JSON.stringify(payload),\n },\n )\n if (!call.ok) {\n throw new Error(\n typeof call.result?.error === 'string' ? call.result.error : 'Failed to create dictionary entry',\n )\n }\n return call\n },\n context: {\n formId: mutationContextId,\n resourceKind: 'dictionaries.dictionary_entry',\n retryLastMutation,\n },\n mutationPayload: payload,\n })\n flash(t('dictionaries.config.entries.success.create', 'Dictionary entry created.'), 'success')\n }\n await invalidateDictionaryEntries(queryClient, dictionaryId)\n setDialogOpen(false)\n setFormState({ value: '', label: '', color: null, icon: null })\n appearance.setColor(null)\n appearance.setIcon(null)\n setErrors({})\n } catch (err) {\n if (surfaceRecordConflict(err, t)) {\n return\n }\n console.error('Failed to save dictionary entry', err)\n flash(t('dictionaries.config.entries.error.save', 'Failed to save dictionary entry.'), 'error')\n } finally {\n setIsSaving(false)\n }\n }, [appearance, dictionaryId, formState.id, formState.label, formState.updatedAt, formState.value, mutationContextId, queryClient, readOnly, readOnlyMessage, runMutation, retryLastMutation, t])\n\n const handleDelete = React.useCallback(\n async (entry: Entry) => {\n if (readOnly) {\n flash(readOnlyMessage, 'info')\n return\n }\n if (!entry.id) return\n const confirmDelete = await confirm({\n title: t('dictionaries.config.entries.delete.confirm', 'Delete \"{{value}}\"?', { value: entry.label || entry.value }),\n variant: 'destructive',\n })\n if (!confirmDelete) return\n setIsDeleting(true)\n try {\n await runMutation({\n operation: () =>\n withScopedApiRequestHeaders(\n buildOptimisticLockHeader(entry.updatedAt),\n async () => {\n const call = await apiCall<Record<string, unknown>>(\n `/api/dictionaries/${dictionaryId}/entries/${entry.id}`,\n { method: 'DELETE' },\n )\n if (!call.ok) {\n throw Object.assign(\n new Error(\n typeof call.result?.error === 'string' ? call.result.error : 'Failed to delete dictionary entry',\n ),\n { status: call.status, ...(call.result && typeof call.result === 'object' ? call.result : {}) },\n )\n }\n return call\n },\n ),\n context: {\n formId: mutationContextId,\n resourceKind: 'dictionaries.dictionary_entry',\n resourceId: entry.id,\n retryLastMutation,\n },\n mutationPayload: { id: entry.id },\n })\n await invalidateDictionaryEntries(queryClient, dictionaryId)\n flash(t('dictionaries.config.entries.success.delete', 'Dictionary entry deleted.'), 'success')\n } catch (err) {\n if (surfaceRecordConflict(err, t)) {\n return\n }\n console.error('Failed to delete dictionary entry', err)\n flash(t('dictionaries.config.entries.error.delete', 'Failed to delete dictionary entry.'), 'error')\n } finally {\n setIsDeleting(false)\n }\n },\n [confirm, dictionaryId, mutationContextId, queryClient, readOnly, readOnlyMessage, runMutation, retryLastMutation, t],\n )\n\n const tableContent = React.useMemo(() => {\n if (isInitialLoading) {\n return (\n <TableRow>\n <TableCell colSpan={4} className=\"py-8 text-center text-sm text-muted-foreground\">\n <Spinner className=\"mx-auto mb-2 h-5 w-5\" />\n {t('dictionaries.config.entries.loading', 'Loading entries\u2026')}\n </TableCell>\n </TableRow>\n )\n }\n if (loadError) {\n return (\n <TableRow>\n <TableCell colSpan={4} className=\"py-6 text-center text-sm text-destructive\">\n {loadError}\n </TableCell>\n </TableRow>\n )\n }\n if (!entries.length) {\n return (\n <TableRow>\n <TableCell colSpan={4} className=\"py-6 text-center text-sm text-muted-foreground\">\n {t('dictionaries.config.entries.empty', 'No entries yet.')}\n </TableCell>\n </TableRow>\n )\n }\n return entries.map((entry) => (\n <TableRow key={entry.id}>\n <TableCell className=\"font-medium\">{entry.value}</TableCell>\n <TableCell>{entry.label}</TableCell>\n <TableCell>\n <DictionaryValue\n value={entry.value}\n map={dictionaryMap}\n fallback={<span className=\"text-sm text-muted-foreground\">{t('dictionaries.config.entries.appearance.none', 'None')}</span>}\n />\n </TableCell>\n <TableCell className=\"flex items-center gap-2\">\n {readOnly ? (\n <span className=\"text-xs text-muted-foreground\">\n {t('dictionaries.config.entries.readOnlyActions', 'Managed in parent organization')}\n </span>\n ) : (\n <>\n <Button\n type=\"button\"\n variant=\"ghost\"\n size=\"icon\"\n onClick={() => openDialog(entry)}\n >\n <Pencil className=\"h-4 w-4\" />\n </Button>\n <Button\n type=\"button\"\n variant=\"ghost\"\n size=\"icon\"\n onClick={() => setTranslateEntry(entry)}\n title={t('dictionaries.config.entries.actions.translate', 'Translate')}\n >\n <Languages className=\"h-4 w-4\" />\n </Button>\n <Button\n type=\"button\"\n variant=\"ghost\"\n size=\"icon\"\n onClick={() => handleDelete(entry)}\n disabled={isDeleting}\n >\n <Trash2 className=\"h-4 w-4\" />\n </Button>\n </>\n )}\n </TableCell>\n </TableRow>\n ))\n }, [dictionaryMap, entries, handleDelete, isDeleting, isInitialLoading, loadError, openDialog, readOnly, t])\n\n return (\n <div className=\"space-y-4\">\n <div className=\"flex flex-wrap items-center justify-between gap-3\">\n <div>\n <h2 className=\"text-lg font-semibold\">{dictionaryName}</h2>\n <p className=\"text-sm text-muted-foreground\">\n {readOnly\n ? readOnlyMessage\n : t('dictionaries.config.entries.subtitle', 'Manage reusable values and appearance for this dictionary.')}\n </p>\n </div>\n <div className=\"flex gap-2\">\n <Button\n type=\"button\"\n variant=\"outline\"\n size=\"sm\"\n onClick={() => {\n dictionaryQuery.refetch().catch(() => {})\n }}\n >\n <RefreshCw className=\"mr-2 h-4 w-4\" />\n {t('dictionaries.config.entries.actions.refresh', 'Refresh')}\n </Button>\n <Button\n type=\"button\"\n size=\"sm\"\n onClick={() => openDialog()}\n disabled={readOnly}\n title={readOnly ? readOnlyMessage : undefined}\n >\n <Plus className=\"mr-2 h-4 w-4\" />\n {t('dictionaries.config.entries.actions.add', 'Add entry')}\n </Button>\n </div>\n </div>\n\n <div className=\"overflow-hidden rounded-md border\">\n <Table>\n <TableHeader>\n <TableRow>\n <TableHead className=\"w-48\">{t('dictionaries.config.entries.columns.value', 'Value')}</TableHead>\n <TableHead className=\"w-48\">{t('dictionaries.config.entries.columns.label', 'Label')}</TableHead>\n <TableHead>{t('dictionaries.config.entries.columns.appearance', 'Appearance')}</TableHead>\n <TableHead className=\"w-32 text-right\">{t('dictionaries.config.entries.columns.actions', 'Actions')}</TableHead>\n </TableRow>\n </TableHeader>\n <TableBody>{tableContent}</TableBody>\n </Table>\n </div>\n\n <Dialog open={dialogOpen} onOpenChange={(open) => (!open ? closeDialog() : undefined)}>\n <DialogContent>\n <DialogHeader>\n <DialogTitle>\n {formState.id\n ? t('dictionaries.config.entries.dialog.editTitle', 'Edit dictionary entry')\n : t('dictionaries.config.entries.dialog.addTitle', 'Add dictionary entry')}\n </DialogTitle>\n </DialogHeader>\n <div className=\"space-y-4\">\n <div className=\"space-y-2\">\n <label className=\"text-sm font-medium\">\n {t('dictionaries.config.entries.dialog.valueLabel', 'Value')}\n <span className=\"ml-1 text-destructive\">*</span>\n </label>\n <Input\n type=\"text\"\n value={formState.value}\n onChange={(event) => {\n const nextValue = event.target.value\n setFormState((prev) => ({ ...prev, value: nextValue }))\n if (errors.value) {\n setErrors((prev) => ({ ...prev, value: undefined }))\n }\n }}\n aria-invalid={errors.value ? 'true' : 'false'}\n aria-describedby=\"dictionary-entry-value-error\"\n />\n {errors.value ? (\n <p id=\"dictionary-entry-value-error\" className=\"text-xs text-destructive\">\n {errors.value}\n </p>\n ) : null}\n </div>\n <div className=\"space-y-2\">\n <label className=\"text-sm font-medium\">\n {t('dictionaries.config.entries.dialog.labelLabel', 'Label')}\n </label>\n <Input\n type=\"text\"\n value={formState.label}\n onChange={(event) => setFormState((prev) => ({ ...prev, label: event.target.value }))}\n placeholder={t('dictionaries.config.entries.dialog.labelPlaceholder', 'Display name shown in UI')}\n />\n </div>\n <AppearanceSelector\n icon={appearance.icon}\n color={appearance.color}\n onIconChange={(next) => {\n appearance.setIcon(next)\n setFormState((prev) => ({ ...prev, icon: next }))\n }}\n onColorChange={(next) => {\n appearance.setColor(next)\n setFormState((prev) => ({ ...prev, color: next }))\n }}\n labels={{\n colorLabel: t('dictionaries.config.entries.dialog.colorLabel', 'Color'),\n colorHelp: t('dictionaries.config.entries.dialog.colorHelp', 'Pick a highlight color for this entry.'),\n colorClearLabel: t('dictionaries.config.entries.dialog.colorClear', 'Remove color'),\n iconLabel: t('dictionaries.config.entries.dialog.iconLabel', 'Icon or emoji'),\n iconPlaceholder: t('dictionaries.config.entries.dialog.iconPlaceholder', 'Type an emoji or icon token.'),\n iconPickerTriggerLabel: t('dictionaries.config.entries.dialog.iconBrowse', 'Browse icons and emoji'),\n iconSearchPlaceholder: t('dictionaries.config.entries.dialog.iconSearchPlaceholder', 'Search icons or emojis\u2026'),\n iconSearchEmptyLabel: t('dictionaries.config.entries.dialog.iconSearchEmpty', 'No icons match your search.'),\n iconSuggestionsLabel: t('dictionaries.config.entries.dialog.iconSuggestions', 'Suggestions'),\n iconClearLabel: t('dictionaries.config.entries.dialog.iconClear', 'Remove icon'),\n previewEmptyLabel: t('dictionaries.config.entries.dialog.previewEmpty', 'No appearance selected'),\n }}\n />\n </div>\n <DialogFooter>\n <Button\n type=\"button\"\n variant=\"ghost\"\n onClick={closeDialog}\n disabled={isSaving}\n >\n {t('dictionaries.config.entries.dialog.cancel', 'Cancel')}\n </Button>\n <Button\n type=\"button\"\n onClick={handleSave}\n disabled={isSaving}\n >\n {isSaving ? <Spinner className=\"mr-2 h-4 w-4\" /> : null}\n {t('dictionaries.config.entries.dialog.save', 'Save')}\n </Button>\n </DialogFooter>\n </DialogContent>\n </Dialog>\n <Dialog open={!!translateEntry} onOpenChange={(open) => { if (!open) setTranslateEntry(null) }}>\n <DialogContent className=\"max-w-2xl max-h-[80vh] overflow-y-auto\">\n <DialogHeader>\n <DialogTitle>{t('translations.manager.translateEntry', 'Translate: {{label}}', { label: translateEntry?.label ?? '' })}</DialogTitle>\n <DialogDescription>\n {t('translations.manager.translateEntryDescription', 'Manage translations for this dictionary entry.')}\n </DialogDescription>\n </DialogHeader>\n {translateEntry?.id && (\n <TranslationManager\n mode=\"embedded\"\n entityType=\"dictionaries:dictionary_entry\"\n recordId={translateEntry.id}\n baseValues={{ label: translateEntry.label }}\n translatableFields={['label']}\n />\n )}\n </DialogContent>\n </Dialog>\n {ConfirmDialogElement}\n </div>\n )\n}\n"],
5
+ "mappings": "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",
6
6
  "names": []
7
7
  }
@@ -2,6 +2,7 @@
2
2
  import { jsx } from "react/jsx-runtime";
3
3
  import * as React from "react";
4
4
  import { apiCall } from "@open-mercato/ui/backend/utils/apiCall";
5
+ import { useGuardedMutation } from "@open-mercato/ui/backend/injection/useGuardedMutation";
5
6
  import { useQueryClient } from "@tanstack/react-query";
6
7
  import { useOrganizationScopeVersion } from "@open-mercato/shared/lib/frontend/useOrganizationScope";
7
8
  import { useT } from "@open-mercato/shared/lib/i18n/context";
@@ -23,6 +24,14 @@ function DictionarySelectControl({
23
24
  const t = useT();
24
25
  const queryClient = useQueryClient();
25
26
  const scopeVersion = useOrganizationScopeVersion();
27
+ const mutationContextId = React.useMemo(
28
+ () => `dictionaries:dictionary_entry:inline:${dictionaryId}`,
29
+ [dictionaryId]
30
+ );
31
+ const { runMutation, retryLastMutation } = useGuardedMutation({
32
+ contextId: mutationContextId,
33
+ blockedMessage: t("ui.forms.flash.saveBlocked", "Save blocked by validation")
34
+ });
26
35
  const [inlineCreateEnabled, setInlineCreateEnabled] = React.useState(allowInlineCreate);
27
36
  React.useEffect(() => {
28
37
  let cancelled = false;
@@ -94,24 +103,36 @@ function DictionarySelectControl({
94
103
  }, [dictionaryId, normalizedPriority, queryClient, scopeVersion]);
95
104
  const createOption = React.useCallback(
96
105
  async (input) => {
97
- const call = await apiCall(
98
- `/api/dictionaries/${dictionaryId}/entries`,
99
- {
100
- method: "POST",
101
- headers: { "content-type": "application/json" },
102
- body: JSON.stringify({
103
- value: input.value,
104
- label: input.label ?? input.value,
105
- color: input.color,
106
- icon: input.icon
107
- })
108
- }
109
- );
110
- if (!call.ok) {
111
- throw new Error(
112
- typeof call.result?.error === "string" ? call.result.error : "Failed to create dictionary entry"
113
- );
114
- }
106
+ const payload = {
107
+ value: input.value,
108
+ label: input.label ?? input.value,
109
+ color: input.color,
110
+ icon: input.icon
111
+ };
112
+ const call = await runMutation({
113
+ operation: async () => {
114
+ const result = await apiCall(
115
+ `/api/dictionaries/${dictionaryId}/entries`,
116
+ {
117
+ method: "POST",
118
+ headers: { "content-type": "application/json" },
119
+ body: JSON.stringify(payload)
120
+ }
121
+ );
122
+ if (!result.ok) {
123
+ throw new Error(
124
+ typeof result.result?.error === "string" ? result.result.error : "Failed to create dictionary entry"
125
+ );
126
+ }
127
+ return result;
128
+ },
129
+ context: {
130
+ formId: mutationContextId,
131
+ resourceKind: "dictionaries.dictionary_entry",
132
+ retryLastMutation
133
+ },
134
+ mutationPayload: payload
135
+ });
115
136
  await invalidateDictionaryEntries(queryClient, dictionaryId);
116
137
  return {
117
138
  value: String(call.result?.value ?? input.value),
@@ -120,7 +141,7 @@ function DictionarySelectControl({
120
141
  icon: typeof call.result?.icon === "string" ? call.result.icon : null
121
142
  };
122
143
  },
123
- [dictionaryId, queryClient]
144
+ [dictionaryId, mutationContextId, queryClient, runMutation, retryLastMutation]
124
145
  );
125
146
  const labels = React.useMemo(
126
147
  () => ({
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../../src/modules/dictionaries/components/DictionarySelectControl.tsx"],
4
- "sourcesContent": ["\"use client\"\n\nimport * as React from 'react'\nimport { apiCall } from '@open-mercato/ui/backend/utils/apiCall'\nimport { useQueryClient } from '@tanstack/react-query'\nimport { useOrganizationScopeVersion } from '@open-mercato/shared/lib/frontend/useOrganizationScope'\nimport { useT } from '@open-mercato/shared/lib/i18n/context'\nimport { DictionaryEntrySelect } from './DictionaryEntrySelect'\nimport {\n ensureDictionaryEntries,\n invalidateDictionaryEntries,\n} from './hooks/useDictionaryEntries'\n\ntype DictionarySelectControlProps = {\n dictionaryId: string\n value?: string | null\n onChange: (value: string | undefined) => void\n allowInlineCreate?: boolean\n selectClassName?: string\n disabled?: boolean\n priorityValues?: string[]\n seedOptions?: Array<{ value: string; label: string; color?: string | null; icon?: string | null }>\n}\n\nexport function DictionarySelectControl({\n dictionaryId,\n value,\n onChange,\n allowInlineCreate = true,\n selectClassName,\n disabled = false,\n priorityValues,\n seedOptions,\n}: DictionarySelectControlProps) {\n const t = useT()\n const queryClient = useQueryClient()\n const scopeVersion = useOrganizationScopeVersion()\n const [inlineCreateEnabled, setInlineCreateEnabled] = React.useState<boolean>(allowInlineCreate)\n\n React.useEffect(() => {\n let cancelled = false\n async function evaluateInlineCreate() {\n if (!allowInlineCreate) {\n setInlineCreateEnabled(false)\n return\n }\n try {\n const call = await apiCall<{ isInherited?: boolean }>(`/api/dictionaries/${dictionaryId}`)\n if (cancelled) return\n if (call.ok && call.result && call.result.isInherited === true) {\n setInlineCreateEnabled(false)\n return\n }\n setInlineCreateEnabled(true)\n } catch (err) {\n console.warn('DictionarySelectControl.inlineCreate check failed', err)\n if (!cancelled) {\n setInlineCreateEnabled(allowInlineCreate)\n }\n }\n }\n evaluateInlineCreate().catch(() => {})\n return () => {\n cancelled = true\n }\n }, [allowInlineCreate, dictionaryId, scopeVersion])\n\n const effectiveAllowInlineCreate = allowInlineCreate && inlineCreateEnabled\n\n const normalizedPriority = React.useMemo(() => {\n if (!Array.isArray(priorityValues) || !priorityValues.length) return []\n const seen = new Set<string>()\n const ordered: string[] = []\n priorityValues.forEach((code) => {\n if (typeof code !== 'string') return\n const normalized = code.trim().toLowerCase()\n if (!normalized.length || seen.has(normalized)) return\n seen.add(normalized)\n ordered.push(normalized)\n })\n return ordered\n }, [priorityValues])\n\n const fetchOptions = React.useCallback(async () => {\n const data = await ensureDictionaryEntries(queryClient, dictionaryId, scopeVersion)\n const options = data.entries.map((entry) => ({\n value: entry.value,\n label: entry.label,\n color: entry.color ?? null,\n icon: entry.icon ?? null,\n }))\n if (!normalizedPriority.length) return options\n const byValue = new Map<string, (typeof options)[number]>()\n options.forEach((option) => {\n const key = option.value.trim().toLowerCase()\n if (!byValue.has(key)) byValue.set(key, option)\n })\n const prioritized: typeof options = []\n normalizedPriority.forEach((key) => {\n const match = byValue.get(key)\n if (match) {\n prioritized.push(match)\n byValue.delete(key)\n }\n })\n const remaining: typeof options = []\n byValue.forEach((option) => remaining.push(option))\n return [...prioritized, ...remaining]\n }, [dictionaryId, normalizedPriority, queryClient, scopeVersion])\n\n const createOption = React.useCallback(\n async (input: { value: string; label?: string; color?: string | null; icon?: string | null }) => {\n const call = await apiCall<Record<string, unknown>>(\n `/api/dictionaries/${dictionaryId}/entries`,\n {\n method: 'POST',\n headers: { 'content-type': 'application/json' },\n body: JSON.stringify({\n value: input.value,\n label: input.label ?? input.value,\n color: input.color,\n icon: input.icon,\n }),\n },\n )\n if (!call.ok) {\n throw new Error(\n typeof call.result?.error === 'string' ? call.result.error : 'Failed to create dictionary entry',\n )\n }\n await invalidateDictionaryEntries(queryClient, dictionaryId)\n return {\n value: String(call.result?.value ?? input.value),\n label:\n typeof call.result?.label === 'string' && call.result.label.length\n ? call.result.label\n : String(call.result?.value ?? input.value),\n color: typeof call.result?.color === 'string' ? call.result.color : null,\n icon: typeof call.result?.icon === 'string' ? call.result.icon : null,\n }\n },\n [dictionaryId, queryClient],\n )\n\n const labels = React.useMemo(\n () => ({\n placeholder: t('dictionaries.customFields.selector.placeholder', 'Select an entry'),\n addLabel: t('dictionaries.customFields.selector.add', 'Add entry'),\n addPrompt: t('dictionaries.customFields.selector.dialogDescription', 'Create a new entry and reuse it across records.'),\n dialogTitle: t('dictionaries.config.entries.dialog.addTitle', 'Add dictionary entry'),\n valueLabel: t('dictionaries.config.entries.dialog.valueLabel', 'Value'),\n valuePlaceholder: t('dictionaries.config.entries.dialog.valueLabel', 'Value'),\n labelLabel: t('dictionaries.config.entries.dialog.labelLabel', 'Label'),\n labelPlaceholder: t('dictionaries.config.entries.dialog.labelPlaceholder', 'Display name shown in UI'),\n emptyError: t('dictionaries.config.entries.error.required', 'Value is required.'),\n cancelLabel: t('dictionaries.config.entries.dialog.cancel', 'Cancel'),\n saveLabel: t('dictionaries.config.entries.dialog.save', 'Save'),\n saveShortcutHint: t('dictionaries.config.entries.dialog.saveShortcut', '\u2318/Ctrl + Enter'),\n successCreateLabel: t('dictionaries.config.entries.success.create', 'Dictionary entry created.'),\n errorLoad: t('dictionaries.config.entries.error.load', 'Failed to load dictionary entries.'),\n errorSave: t('dictionaries.config.entries.error.save', 'Failed to save dictionary entry.'),\n loadingLabel: t('dictionaries.config.entries.loading', 'Loading entries\u2026'),\n manageTitle: t('dictionaries.customFields.manageLink', 'Manage dictionaries'),\n }),\n [t],\n )\n\n const appearanceLabels = React.useMemo(\n () => ({\n colorLabel: t('dictionaries.config.entries.dialog.colorLabel', 'Color'),\n colorHelp: t('dictionaries.config.entries.dialog.colorHelp', 'Pick a highlight color for this entry.'),\n colorClearLabel: t('dictionaries.config.entries.dialog.colorClear', 'Remove color'),\n iconLabel: t('dictionaries.config.entries.dialog.iconLabel', 'Icon or emoji'),\n iconPlaceholder: t('dictionaries.config.entries.dialog.iconPlaceholder', 'Type an emoji or icon token.'),\n iconPickerTriggerLabel: t('dictionaries.config.entries.dialog.iconBrowse', 'Browse icons and emoji'),\n iconSearchPlaceholder: t('dictionaries.config.entries.dialog.iconSearchPlaceholder', 'Search icons or emojis\u2026'),\n iconSearchEmptyLabel: t('dictionaries.config.entries.dialog.iconSearchEmpty', 'No icons match your search.'),\n iconSuggestionsLabel: t('dictionaries.config.entries.dialog.iconSuggestions', 'Suggestions'),\n iconClearLabel: t('dictionaries.config.entries.dialog.iconClear', 'Remove icon'),\n previewEmptyLabel: t('dictionaries.config.entries.dialog.previewEmpty', 'No appearance selected'),\n }),\n [t],\n )\n\n return (\n <DictionaryEntrySelect\n value={typeof value === 'string' ? value : undefined}\n onChange={onChange}\n fetchOptions={fetchOptions}\n createOption={effectiveAllowInlineCreate ? createOption : undefined}\n labels={labels}\n appearanceLabels={appearanceLabels}\n allowAppearance\n allowInlineCreate={effectiveAllowInlineCreate}\n selectClassName={selectClassName}\n seedOptions={seedOptions?.map((option) => ({\n value: option.value,\n label: option.label,\n color: option.color ?? null,\n icon: option.icon ?? null,\n }))}\n sortOptions=\"none\"\n disabled={disabled}\n manageHref=\"/backend/config/dictionaries\"\n />\n )\n}\n"],
5
- "mappings": "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",
4
+ "sourcesContent": ["\"use client\"\n\nimport * as React from 'react'\nimport { apiCall } from '@open-mercato/ui/backend/utils/apiCall'\nimport { useGuardedMutation } from '@open-mercato/ui/backend/injection/useGuardedMutation'\nimport { useQueryClient } from '@tanstack/react-query'\nimport { useOrganizationScopeVersion } from '@open-mercato/shared/lib/frontend/useOrganizationScope'\nimport { useT } from '@open-mercato/shared/lib/i18n/context'\nimport { DictionaryEntrySelect } from './DictionaryEntrySelect'\nimport {\n ensureDictionaryEntries,\n invalidateDictionaryEntries,\n} from './hooks/useDictionaryEntries'\n\ntype DictionarySelectControlProps = {\n dictionaryId: string\n value?: string | null\n onChange: (value: string | undefined) => void\n allowInlineCreate?: boolean\n selectClassName?: string\n disabled?: boolean\n priorityValues?: string[]\n seedOptions?: Array<{ value: string; label: string; color?: string | null; icon?: string | null }>\n}\n\nexport function DictionarySelectControl({\n dictionaryId,\n value,\n onChange,\n allowInlineCreate = true,\n selectClassName,\n disabled = false,\n priorityValues,\n seedOptions,\n}: DictionarySelectControlProps) {\n const t = useT()\n const queryClient = useQueryClient()\n const scopeVersion = useOrganizationScopeVersion()\n const mutationContextId = React.useMemo(\n () => `dictionaries:dictionary_entry:inline:${dictionaryId}`,\n [dictionaryId],\n )\n const { runMutation, retryLastMutation } = useGuardedMutation<{\n formId: string\n resourceKind: string\n retryLastMutation: () => Promise<boolean>\n }>({\n contextId: mutationContextId,\n blockedMessage: t('ui.forms.flash.saveBlocked', 'Save blocked by validation'),\n })\n const [inlineCreateEnabled, setInlineCreateEnabled] = React.useState<boolean>(allowInlineCreate)\n\n React.useEffect(() => {\n let cancelled = false\n async function evaluateInlineCreate() {\n if (!allowInlineCreate) {\n setInlineCreateEnabled(false)\n return\n }\n try {\n const call = await apiCall<{ isInherited?: boolean }>(`/api/dictionaries/${dictionaryId}`)\n if (cancelled) return\n if (call.ok && call.result && call.result.isInherited === true) {\n setInlineCreateEnabled(false)\n return\n }\n setInlineCreateEnabled(true)\n } catch (err) {\n console.warn('DictionarySelectControl.inlineCreate check failed', err)\n if (!cancelled) {\n setInlineCreateEnabled(allowInlineCreate)\n }\n }\n }\n evaluateInlineCreate().catch(() => {})\n return () => {\n cancelled = true\n }\n }, [allowInlineCreate, dictionaryId, scopeVersion])\n\n const effectiveAllowInlineCreate = allowInlineCreate && inlineCreateEnabled\n\n const normalizedPriority = React.useMemo(() => {\n if (!Array.isArray(priorityValues) || !priorityValues.length) return []\n const seen = new Set<string>()\n const ordered: string[] = []\n priorityValues.forEach((code) => {\n if (typeof code !== 'string') return\n const normalized = code.trim().toLowerCase()\n if (!normalized.length || seen.has(normalized)) return\n seen.add(normalized)\n ordered.push(normalized)\n })\n return ordered\n }, [priorityValues])\n\n const fetchOptions = React.useCallback(async () => {\n const data = await ensureDictionaryEntries(queryClient, dictionaryId, scopeVersion)\n const options = data.entries.map((entry) => ({\n value: entry.value,\n label: entry.label,\n color: entry.color ?? null,\n icon: entry.icon ?? null,\n }))\n if (!normalizedPriority.length) return options\n const byValue = new Map<string, (typeof options)[number]>()\n options.forEach((option) => {\n const key = option.value.trim().toLowerCase()\n if (!byValue.has(key)) byValue.set(key, option)\n })\n const prioritized: typeof options = []\n normalizedPriority.forEach((key) => {\n const match = byValue.get(key)\n if (match) {\n prioritized.push(match)\n byValue.delete(key)\n }\n })\n const remaining: typeof options = []\n byValue.forEach((option) => remaining.push(option))\n return [...prioritized, ...remaining]\n }, [dictionaryId, normalizedPriority, queryClient, scopeVersion])\n\n const createOption = React.useCallback(\n async (input: { value: string; label?: string; color?: string | null; icon?: string | null }) => {\n const payload = {\n value: input.value,\n label: input.label ?? input.value,\n color: input.color,\n icon: input.icon,\n }\n const call = await runMutation({\n operation: async () => {\n const result = await apiCall<Record<string, unknown>>(\n `/api/dictionaries/${dictionaryId}/entries`,\n {\n method: 'POST',\n headers: { 'content-type': 'application/json' },\n body: JSON.stringify(payload),\n },\n )\n if (!result.ok) {\n throw new Error(\n typeof result.result?.error === 'string' ? result.result.error : 'Failed to create dictionary entry',\n )\n }\n return result\n },\n context: {\n formId: mutationContextId,\n resourceKind: 'dictionaries.dictionary_entry',\n retryLastMutation,\n },\n mutationPayload: payload,\n })\n await invalidateDictionaryEntries(queryClient, dictionaryId)\n return {\n value: String(call.result?.value ?? input.value),\n label:\n typeof call.result?.label === 'string' && call.result.label.length\n ? call.result.label\n : String(call.result?.value ?? input.value),\n color: typeof call.result?.color === 'string' ? call.result.color : null,\n icon: typeof call.result?.icon === 'string' ? call.result.icon : null,\n }\n },\n [dictionaryId, mutationContextId, queryClient, runMutation, retryLastMutation],\n )\n\n const labels = React.useMemo(\n () => ({\n placeholder: t('dictionaries.customFields.selector.placeholder', 'Select an entry'),\n addLabel: t('dictionaries.customFields.selector.add', 'Add entry'),\n addPrompt: t('dictionaries.customFields.selector.dialogDescription', 'Create a new entry and reuse it across records.'),\n dialogTitle: t('dictionaries.config.entries.dialog.addTitle', 'Add dictionary entry'),\n valueLabel: t('dictionaries.config.entries.dialog.valueLabel', 'Value'),\n valuePlaceholder: t('dictionaries.config.entries.dialog.valueLabel', 'Value'),\n labelLabel: t('dictionaries.config.entries.dialog.labelLabel', 'Label'),\n labelPlaceholder: t('dictionaries.config.entries.dialog.labelPlaceholder', 'Display name shown in UI'),\n emptyError: t('dictionaries.config.entries.error.required', 'Value is required.'),\n cancelLabel: t('dictionaries.config.entries.dialog.cancel', 'Cancel'),\n saveLabel: t('dictionaries.config.entries.dialog.save', 'Save'),\n saveShortcutHint: t('dictionaries.config.entries.dialog.saveShortcut', '\u2318/Ctrl + Enter'),\n successCreateLabel: t('dictionaries.config.entries.success.create', 'Dictionary entry created.'),\n errorLoad: t('dictionaries.config.entries.error.load', 'Failed to load dictionary entries.'),\n errorSave: t('dictionaries.config.entries.error.save', 'Failed to save dictionary entry.'),\n loadingLabel: t('dictionaries.config.entries.loading', 'Loading entries\u2026'),\n manageTitle: t('dictionaries.customFields.manageLink', 'Manage dictionaries'),\n }),\n [t],\n )\n\n const appearanceLabels = React.useMemo(\n () => ({\n colorLabel: t('dictionaries.config.entries.dialog.colorLabel', 'Color'),\n colorHelp: t('dictionaries.config.entries.dialog.colorHelp', 'Pick a highlight color for this entry.'),\n colorClearLabel: t('dictionaries.config.entries.dialog.colorClear', 'Remove color'),\n iconLabel: t('dictionaries.config.entries.dialog.iconLabel', 'Icon or emoji'),\n iconPlaceholder: t('dictionaries.config.entries.dialog.iconPlaceholder', 'Type an emoji or icon token.'),\n iconPickerTriggerLabel: t('dictionaries.config.entries.dialog.iconBrowse', 'Browse icons and emoji'),\n iconSearchPlaceholder: t('dictionaries.config.entries.dialog.iconSearchPlaceholder', 'Search icons or emojis\u2026'),\n iconSearchEmptyLabel: t('dictionaries.config.entries.dialog.iconSearchEmpty', 'No icons match your search.'),\n iconSuggestionsLabel: t('dictionaries.config.entries.dialog.iconSuggestions', 'Suggestions'),\n iconClearLabel: t('dictionaries.config.entries.dialog.iconClear', 'Remove icon'),\n previewEmptyLabel: t('dictionaries.config.entries.dialog.previewEmpty', 'No appearance selected'),\n }),\n [t],\n )\n\n return (\n <DictionaryEntrySelect\n value={typeof value === 'string' ? value : undefined}\n onChange={onChange}\n fetchOptions={fetchOptions}\n createOption={effectiveAllowInlineCreate ? createOption : undefined}\n labels={labels}\n appearanceLabels={appearanceLabels}\n allowAppearance\n allowInlineCreate={effectiveAllowInlineCreate}\n selectClassName={selectClassName}\n seedOptions={seedOptions?.map((option) => ({\n value: option.value,\n label: option.label,\n color: option.color ?? null,\n icon: option.icon ?? null,\n }))}\n sortOptions=\"none\"\n disabled={disabled}\n manageHref=\"/backend/config/dictionaries\"\n />\n )\n}\n"],
5
+ "mappings": "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",
6
6
  "names": []
7
7
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@open-mercato/core",
3
- "version": "0.6.6-develop.6201.1.8ceb502c4b",
3
+ "version": "0.6.6-develop.6204.1.30b1f58642",
4
4
  "license": "MIT",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -246,16 +246,16 @@
246
246
  "zod": "^4.4.3"
247
247
  },
248
248
  "peerDependencies": {
249
- "@open-mercato/ai-assistant": "0.6.6-develop.6201.1.8ceb502c4b",
250
- "@open-mercato/shared": "0.6.6-develop.6201.1.8ceb502c4b",
251
- "@open-mercato/ui": "0.6.6-develop.6201.1.8ceb502c4b",
249
+ "@open-mercato/ai-assistant": "0.6.6-develop.6204.1.30b1f58642",
250
+ "@open-mercato/shared": "0.6.6-develop.6204.1.30b1f58642",
251
+ "@open-mercato/ui": "0.6.6-develop.6204.1.30b1f58642",
252
252
  "react": "^19.0.0",
253
253
  "react-dom": "^19.0.0"
254
254
  },
255
255
  "devDependencies": {
256
- "@open-mercato/ai-assistant": "0.6.6-develop.6201.1.8ceb502c4b",
257
- "@open-mercato/shared": "0.6.6-develop.6201.1.8ceb502c4b",
258
- "@open-mercato/ui": "0.6.6-develop.6201.1.8ceb502c4b",
256
+ "@open-mercato/ai-assistant": "0.6.6-develop.6204.1.30b1f58642",
257
+ "@open-mercato/shared": "0.6.6-develop.6204.1.30b1f58642",
258
+ "@open-mercato/ui": "0.6.6-develop.6204.1.30b1f58642",
259
259
  "@testing-library/dom": "^10.4.1",
260
260
  "@testing-library/jest-dom": "^6.9.1",
261
261
  "@testing-library/react": "^16.3.1",