@open-mercato/core 0.6.6-develop.6330.1.a261878aa8 → 0.6.6-develop.6331.1.a33b8e99b7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/modules/attachments/components/AttachmentPartitionSettings.js +3 -0
- package/dist/modules/attachments/components/AttachmentPartitionSettings.js.map +2 -2
- package/dist/modules/auth/api/roles/acl/route.js +2 -2
- package/dist/modules/auth/api/roles/acl/route.js.map +2 -2
- package/dist/modules/auth/api/users/acl/route.js +2 -2
- package/dist/modules/auth/api/users/acl/route.js.map +2 -2
- package/dist/modules/auth/backend/roles/[id]/edit/page.js +12 -0
- package/dist/modules/auth/backend/roles/[id]/edit/page.js.map +2 -2
- package/dist/modules/auth/backend/users/[id]/edit/page.js +12 -0
- package/dist/modules/auth/backend/users/[id]/edit/page.js.map +2 -2
- package/dist/modules/auth/data/entities.js +2 -2
- package/dist/modules/auth/data/entities.js.map +2 -2
- package/dist/modules/business_rules/api/rules/route.js +3 -3
- package/dist/modules/business_rules/api/rules/route.js.map +2 -2
- package/dist/modules/business_rules/api/sets/route.js +3 -3
- package/dist/modules/business_rules/api/sets/route.js.map +2 -2
- package/dist/modules/business_rules/backend/rules/[id]/page.js +12 -1
- package/dist/modules/business_rules/backend/rules/[id]/page.js.map +2 -2
- package/dist/modules/business_rules/backend/sets/[id]/page.js +12 -1
- package/dist/modules/business_rules/backend/sets/[id]/page.js.map +2 -2
- package/dist/modules/catalog/api/product-media/route.js.map +2 -2
- package/dist/modules/catalog/backend/catalog/categories/[id]/edit/page.js +12 -0
- package/dist/modules/catalog/backend/catalog/categories/[id]/edit/page.js.map +2 -2
- package/dist/modules/catalog/backend/catalog/products/[id]/page.js +15 -0
- package/dist/modules/catalog/backend/catalog/products/[id]/page.js.map +2 -2
- package/dist/modules/catalog/backend/catalog/products/[productId]/variants/[variantId]/page.js +12 -1
- package/dist/modules/catalog/backend/catalog/products/[productId]/variants/[variantId]/page.js.map +2 -2
- package/dist/modules/catalog/components/PriceKindSettings.js +13 -3
- package/dist/modules/catalog/components/PriceKindSettings.js.map +2 -2
- package/dist/modules/catalog/lib/bulkDelete.js.map +2 -2
- package/dist/modules/currencies/backend/currencies/[id]/page.js +15 -3
- package/dist/modules/currencies/backend/currencies/[id]/page.js.map +2 -2
- package/dist/modules/currencies/backend/currencies/page.js +2 -1
- package/dist/modules/currencies/backend/currencies/page.js.map +2 -2
- package/dist/modules/currencies/backend/exchange-rates/[id]/page.js +12 -1
- package/dist/modules/currencies/backend/exchange-rates/[id]/page.js.map +2 -2
- package/dist/modules/currencies/backend/exchange-rates/page.js +3 -2
- package/dist/modules/currencies/backend/exchange-rates/page.js.map +2 -2
- package/dist/modules/customer_accounts/api/admin/roles/[id].js +3 -3
- package/dist/modules/customer_accounts/api/admin/roles/[id].js.map +2 -2
- package/dist/modules/customer_accounts/api/admin/users/[id].js +3 -3
- package/dist/modules/customer_accounts/api/admin/users/[id].js.map +2 -2
- package/dist/modules/customers/api/addresses/route.js +4 -2
- package/dist/modules/customers/api/addresses/route.js.map +2 -2
- package/dist/modules/customers/api/tags/route.js +8 -3
- package/dist/modules/customers/api/tags/route.js.map +2 -2
- package/dist/modules/customers/backend/customers/companies-v2/[id]/page.js +13 -2
- package/dist/modules/customers/backend/customers/companies-v2/[id]/page.js.map +2 -2
- package/dist/modules/customers/backend/customers/deals/[id]/hooks/useDealAssociations.js +24 -9
- package/dist/modules/customers/backend/customers/deals/[id]/hooks/useDealAssociations.js.map +2 -2
- package/dist/modules/customers/backend/customers/deals/[id]/hooks/useDealClosure.js +53 -23
- package/dist/modules/customers/backend/customers/deals/[id]/hooks/useDealClosure.js.map +2 -2
- package/dist/modules/customers/backend/customers/deals/[id]/hooks/useDealPipeline.js +13 -3
- package/dist/modules/customers/backend/customers/deals/[id]/hooks/useDealPipeline.js.map +2 -2
- package/dist/modules/customers/backend/customers/deals/[id]/page.js +19 -2
- package/dist/modules/customers/backend/customers/deals/[id]/page.js.map +2 -2
- package/dist/modules/customers/backend/customers/people-v2/[id]/page.js +13 -2
- package/dist/modules/customers/backend/customers/people-v2/[id]/page.js.map +2 -2
- package/dist/modules/customers/commands/interactions.js +5 -5
- package/dist/modules/customers/commands/interactions.js.map +2 -2
- package/dist/modules/customers/commands/pipeline-stages.js +24 -2
- package/dist/modules/customers/commands/pipeline-stages.js.map +2 -2
- package/dist/modules/customers/commands/pipelines.js +24 -2
- package/dist/modules/customers/commands/pipelines.js.map +2 -2
- package/dist/modules/customers/components/detail/AddressesSection.js +46 -29
- package/dist/modules/customers/components/detail/AddressesSection.js.map +2 -2
- package/dist/modules/customers/components/detail/DealForm.js +5 -1
- package/dist/modules/customers/components/detail/DealForm.js.map +2 -2
- package/dist/modules/customers/components/detail/notesAdapter.js +25 -18
- package/dist/modules/customers/components/detail/notesAdapter.js.map +2 -2
- package/dist/modules/data_sync/api/schedules/[id]/route.js +38 -19
- package/dist/modules/data_sync/api/schedules/[id]/route.js.map +2 -2
- package/dist/modules/data_sync/api/schedules/route.js +1 -1
- package/dist/modules/data_sync/api/schedules/route.js.map +2 -2
- package/dist/modules/data_sync/backend/data-sync/page.js +32 -15
- package/dist/modules/data_sync/backend/data-sync/page.js.map +2 -2
- package/dist/modules/data_sync/lib/sync-schedule-service.js +19 -5
- package/dist/modules/data_sync/lib/sync-schedule-service.js.map +2 -2
- package/dist/modules/dictionaries/api/[dictionaryId]/entries/[entryId]/route.js +3 -3
- package/dist/modules/dictionaries/api/[dictionaryId]/entries/[entryId]/route.js.map +2 -2
- package/dist/modules/dictionaries/api/[dictionaryId]/route.js +3 -3
- package/dist/modules/dictionaries/api/[dictionaryId]/route.js.map +2 -2
- package/dist/modules/dictionaries/components/DictionariesManager.js +12 -1
- package/dist/modules/dictionaries/components/DictionariesManager.js.map +2 -2
- package/dist/modules/dictionaries/components/DictionaryEntriesEditor.js.map +2 -2
- package/dist/modules/directory/backend/directory/organizations/[id]/edit/page.js +12 -0
- package/dist/modules/directory/backend/directory/organizations/[id]/edit/page.js.map +2 -2
- package/dist/modules/directory/backend/directory/tenants/[id]/edit/page.js +12 -0
- package/dist/modules/directory/backend/directory/tenants/[id]/edit/page.js.map +2 -2
- package/dist/modules/entities/api/entities.js +2 -2
- package/dist/modules/entities/api/entities.js.map +2 -2
- package/dist/modules/entities/api/records.js +7 -5
- package/dist/modules/entities/api/records.js.map +2 -2
- package/dist/modules/feature_toggles/api/overrides/route.js +2 -2
- package/dist/modules/feature_toggles/api/overrides/route.js.map +2 -2
- package/dist/modules/feature_toggles/backend/feature-toggles/global/[id]/edit/page.js.map +2 -2
- package/dist/modules/feature_toggles/data/entities.js +1 -1
- package/dist/modules/feature_toggles/data/entities.js.map +2 -2
- package/dist/modules/feature_toggles/data/validators.js +2 -1
- package/dist/modules/feature_toggles/data/validators.js.map +2 -2
- package/dist/modules/feature_toggles/lib/queries.js +2 -1
- package/dist/modules/feature_toggles/lib/queries.js.map +2 -2
- package/dist/modules/inbox_ops/api/settings/route.js +2 -2
- package/dist/modules/inbox_ops/api/settings/route.js.map +2 -2
- package/dist/modules/resources/backend/resources/resource-types/[id]/edit/page.js +12 -1
- package/dist/modules/resources/backend/resources/resource-types/[id]/edit/page.js.map +2 -2
- package/dist/modules/resources/backend/resources/resources/[id]/page.js +12 -1
- package/dist/modules/resources/backend/resources/resources/[id]/page.js.map +2 -2
- package/dist/modules/resources/components/detail/notesAdapter.js +25 -18
- package/dist/modules/resources/components/detail/notesAdapter.js.map +2 -2
- package/dist/modules/sales/backend/sales/documents/[id]/page.js +14 -1
- package/dist/modules/sales/backend/sales/documents/[id]/page.js.map +2 -2
- package/dist/modules/sales/commands/documents.js +11 -10
- package/dist/modules/sales/commands/documents.js.map +2 -2
- package/dist/modules/sales/commands/payments.js +6 -1
- package/dist/modules/sales/commands/payments.js.map +2 -2
- package/dist/modules/sales/commands/returns.js +3 -3
- package/dist/modules/sales/commands/returns.js.map +2 -2
- package/dist/modules/sales/commands/shared.js +3 -3
- package/dist/modules/sales/commands/shared.js.map +2 -2
- package/dist/modules/sales/commands/shipments.js +6 -1
- package/dist/modules/sales/commands/shipments.js.map +2 -2
- package/dist/modules/sales/components/documents/PaymentDialog.js +6 -3
- package/dist/modules/sales/components/documents/PaymentDialog.js.map +2 -2
- package/dist/modules/sales/components/documents/PaymentsSection.js +7 -3
- package/dist/modules/sales/components/documents/PaymentsSection.js.map +2 -2
- package/dist/modules/sales/components/documents/ShipmentDialog.js +6 -3
- package/dist/modules/sales/components/documents/ShipmentDialog.js.map +2 -2
- package/dist/modules/sales/components/documents/ShipmentsSection.js +7 -3
- package/dist/modules/sales/components/documents/ShipmentsSection.js.map +2 -2
- package/dist/modules/sales/di.js +39 -3
- package/dist/modules/sales/di.js.map +2 -2
- package/dist/modules/sales/setup.js +2 -0
- package/dist/modules/sales/setup.js.map +2 -2
- package/dist/modules/staff/backend/staff/leave-requests/[id]/page.js +12 -1
- package/dist/modules/staff/backend/staff/leave-requests/[id]/page.js.map +2 -2
- package/dist/modules/staff/backend/staff/team-members/[id]/page.js +12 -1
- package/dist/modules/staff/backend/staff/team-members/[id]/page.js.map +2 -2
- package/dist/modules/staff/backend/staff/team-roles/[id]/edit/page.js +12 -1
- package/dist/modules/staff/backend/staff/team-roles/[id]/edit/page.js.map +2 -2
- package/dist/modules/staff/backend/staff/teams/[id]/edit/page.js +12 -1
- package/dist/modules/staff/backend/staff/teams/[id]/edit/page.js.map +2 -2
- package/dist/modules/staff/commands/job-histories.js +3 -3
- package/dist/modules/staff/commands/job-histories.js.map +2 -2
- package/dist/modules/staff/components/detail/addressesAdapter.js +33 -24
- package/dist/modules/staff/components/detail/addressesAdapter.js.map +2 -2
- package/dist/modules/staff/components/detail/notesAdapter.js +25 -18
- package/dist/modules/staff/components/detail/notesAdapter.js.map +2 -2
- package/dist/modules/translations/api/[entityType]/[entityId]/route.js +37 -0
- package/dist/modules/translations/api/[entityType]/[entityId]/route.js.map +2 -2
- package/dist/modules/translations/components/TranslationManager.js +7 -1
- package/dist/modules/translations/components/TranslationManager.js.map +2 -2
- package/dist/modules/workflows/api/definitions/[id]/route.js +3 -3
- package/dist/modules/workflows/api/definitions/[id]/route.js.map +2 -2
- package/dist/modules/workflows/backend/definitions/[id]/page.js +12 -1
- package/dist/modules/workflows/backend/definitions/[id]/page.js.map +2 -2
- package/dist/modules/workflows/backend/definitions/visual-editor/page.js +11 -1
- package/dist/modules/workflows/backend/definitions/visual-editor/page.js.map +2 -2
- package/package.json +7 -7
- package/src/modules/attachments/components/AttachmentPartitionSettings.tsx +3 -0
- package/src/modules/auth/api/roles/acl/route.ts +2 -2
- package/src/modules/auth/api/users/acl/route.ts +2 -2
- package/src/modules/auth/backend/roles/[id]/edit/page.tsx +17 -0
- package/src/modules/auth/backend/users/[id]/edit/page.tsx +17 -0
- package/src/modules/auth/data/entities.ts +2 -2
- package/src/modules/business_rules/api/rules/route.ts +3 -3
- package/src/modules/business_rules/api/sets/route.ts +3 -3
- package/src/modules/business_rules/backend/rules/[id]/page.tsx +17 -1
- package/src/modules/business_rules/backend/sets/[id]/page.tsx +18 -1
- package/src/modules/catalog/api/product-media/route.ts +4 -0
- package/src/modules/catalog/backend/catalog/categories/[id]/edit/page.tsx +17 -0
- package/src/modules/catalog/backend/catalog/products/[id]/page.tsx +21 -0
- package/src/modules/catalog/backend/catalog/products/[productId]/variants/[variantId]/page.tsx +16 -1
- package/src/modules/catalog/components/PriceKindSettings.tsx +15 -7
- package/src/modules/catalog/lib/bulkDelete.ts +6 -0
- package/src/modules/currencies/backend/currencies/[id]/page.tsx +20 -3
- package/src/modules/currencies/backend/currencies/page.tsx +2 -1
- package/src/modules/currencies/backend/exchange-rates/[id]/page.tsx +17 -1
- package/src/modules/currencies/backend/exchange-rates/page.tsx +3 -2
- package/src/modules/customer_accounts/api/admin/roles/[id].ts +3 -3
- package/src/modules/customer_accounts/api/admin/users/[id].ts +3 -3
- package/src/modules/customers/api/addresses/route.ts +2 -0
- package/src/modules/customers/api/tags/route.ts +6 -1
- package/src/modules/customers/backend/customers/companies-v2/[id]/page.tsx +19 -2
- package/src/modules/customers/backend/customers/deals/[id]/hooks/useDealAssociations.ts +29 -8
- package/src/modules/customers/backend/customers/deals/[id]/hooks/useDealClosure.ts +47 -20
- package/src/modules/customers/backend/customers/deals/[id]/hooks/useDealPipeline.ts +13 -3
- package/src/modules/customers/backend/customers/deals/[id]/page.tsx +21 -1
- package/src/modules/customers/backend/customers/people-v2/[id]/page.tsx +18 -2
- package/src/modules/customers/commands/interactions.ts +5 -5
- package/src/modules/customers/commands/pipeline-stages.ts +26 -2
- package/src/modules/customers/commands/pipelines.ts +26 -2
- package/src/modules/customers/components/detail/AddressesSection.tsx +50 -28
- package/src/modules/customers/components/detail/DealForm.tsx +12 -0
- package/src/modules/customers/components/detail/notesAdapter.ts +29 -18
- package/src/modules/customers/i18n/de.json +2 -0
- package/src/modules/customers/i18n/en.json +2 -0
- package/src/modules/customers/i18n/es.json +2 -0
- package/src/modules/customers/i18n/pl.json +2 -0
- package/src/modules/data_sync/api/schedules/[id]/route.ts +38 -20
- package/src/modules/data_sync/api/schedules/route.ts +1 -1
- package/src/modules/data_sync/backend/data-sync/page.tsx +32 -15
- package/src/modules/data_sync/lib/sync-schedule-service.ts +30 -5
- package/src/modules/dictionaries/api/[dictionaryId]/entries/[entryId]/route.ts +3 -3
- package/src/modules/dictionaries/api/[dictionaryId]/route.ts +3 -3
- package/src/modules/dictionaries/components/DictionariesManager.tsx +21 -1
- package/src/modules/dictionaries/components/DictionaryEntriesEditor.tsx +2 -0
- package/src/modules/directory/backend/directory/organizations/[id]/edit/page.tsx +17 -0
- package/src/modules/directory/backend/directory/tenants/[id]/edit/page.tsx +17 -0
- package/src/modules/entities/api/entities.ts +2 -2
- package/src/modules/entities/api/records.ts +7 -5
- package/src/modules/feature_toggles/api/overrides/route.ts +2 -2
- package/src/modules/feature_toggles/backend/feature-toggles/global/[id]/edit/page.tsx +12 -0
- package/src/modules/feature_toggles/data/entities.ts +1 -1
- package/src/modules/feature_toggles/data/validators.ts +1 -0
- package/src/modules/feature_toggles/lib/queries.ts +1 -0
- package/src/modules/inbox_ops/api/settings/route.ts +2 -2
- package/src/modules/resources/backend/resources/resource-types/[id]/edit/page.tsx +17 -1
- package/src/modules/resources/backend/resources/resources/[id]/page.tsx +17 -1
- package/src/modules/resources/components/detail/notesAdapter.ts +29 -18
- package/src/modules/sales/backend/sales/documents/[id]/page.tsx +17 -1
- package/src/modules/sales/commands/documents.ts +11 -10
- package/src/modules/sales/commands/payments.ts +11 -0
- package/src/modules/sales/commands/returns.ts +3 -3
- package/src/modules/sales/commands/shared.ts +10 -4
- package/src/modules/sales/commands/shipments.ts +14 -0
- package/src/modules/sales/components/documents/PaymentDialog.tsx +7 -3
- package/src/modules/sales/components/documents/PaymentsSection.tsx +8 -3
- package/src/modules/sales/components/documents/ShipmentDialog.tsx +7 -3
- package/src/modules/sales/components/documents/ShipmentsSection.tsx +8 -3
- package/src/modules/sales/di.ts +68 -9
- package/src/modules/sales/setup.ts +2 -0
- package/src/modules/staff/backend/staff/leave-requests/[id]/page.tsx +17 -1
- package/src/modules/staff/backend/staff/team-members/[id]/page.tsx +17 -1
- package/src/modules/staff/backend/staff/team-roles/[id]/edit/page.tsx +17 -1
- package/src/modules/staff/backend/staff/teams/[id]/edit/page.tsx +17 -1
- package/src/modules/staff/commands/job-histories.ts +3 -3
- package/src/modules/staff/components/detail/addressesAdapter.ts +40 -23
- package/src/modules/staff/components/detail/notesAdapter.ts +28 -18
- package/src/modules/translations/api/[entityType]/[entityId]/route.ts +74 -0
- package/src/modules/translations/components/TranslationManager.tsx +14 -1
- package/src/modules/workflows/api/definitions/[id]/route.ts +3 -3
- package/src/modules/workflows/backend/definitions/[id]/page.tsx +18 -1
- package/src/modules/workflows/backend/definitions/visual-editor/page.tsx +19 -1
|
@@ -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 { 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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",
|
|
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 // Route a concurrent-edit 409 through the single conflict surface (matching\n // the entry save path); fall back to a flash for any other delete failure.\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
|
}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
import { jsx, jsxs } from "react/jsx-runtime";
|
|
3
3
|
import * as React from "react";
|
|
4
|
+
import { usePathname } from "next/navigation";
|
|
4
5
|
import { useT } from "@open-mercato/shared/lib/i18n/context";
|
|
5
6
|
import { extractCustomFieldEntries } from "@open-mercato/shared/lib/crud/custom-fields-client";
|
|
6
7
|
import { E } from "../../../../../../../generated/entities.ids.generated.js";
|
|
@@ -16,11 +17,13 @@ import { deleteCrud, updateCrud } from "@open-mercato/ui/backend/utils/crud";
|
|
|
16
17
|
import { collectCustomFieldValues } from "@open-mercato/ui/backend/utils/customFieldValues";
|
|
17
18
|
import { createCrudFormError } from "@open-mercato/ui/backend/utils/serverErrors";
|
|
18
19
|
import { ErrorMessage, RecordNotFoundState } from "@open-mercato/ui/backend/detail";
|
|
20
|
+
import { buildRecordInjectionContext, useSetCurrentRecordInjectionContext } from "@open-mercato/ui/backend/injection/recordContext";
|
|
19
21
|
const TREE_STEP = 16;
|
|
20
22
|
const TREE_PADDING = 12;
|
|
21
23
|
function EditOrganizationPage({ params }) {
|
|
22
24
|
const orgId = params?.id;
|
|
23
25
|
const t = useT();
|
|
26
|
+
const pathname = usePathname();
|
|
24
27
|
const [initialValues, setInitialValues] = React.useState(null);
|
|
25
28
|
const [pathLabel, setPathLabel] = React.useState("");
|
|
26
29
|
const [tenantId, setTenantId] = React.useState(null);
|
|
@@ -218,6 +221,15 @@ function EditOrganizationPage({ params }) {
|
|
|
218
221
|
{ id: "details", title: t("directory.organizations.form.group.details", "Details"), column: 1, fields: detailFields },
|
|
219
222
|
{ id: "custom", title: t("directory.organizations.form.group.customFields", "Custom Data"), column: 2, kind: "customFields" }
|
|
220
223
|
], [detailFields, t]);
|
|
224
|
+
useSetCurrentRecordInjectionContext(
|
|
225
|
+
buildRecordInjectionContext({
|
|
226
|
+
resourceKind: "directory.organization",
|
|
227
|
+
resourceId: orgId || null,
|
|
228
|
+
updatedAt: typeof initialValues?.updatedAt === "string" ? initialValues.updatedAt : null,
|
|
229
|
+
data: initialValues,
|
|
230
|
+
path: pathname
|
|
231
|
+
})
|
|
232
|
+
);
|
|
221
233
|
if (!orgId) {
|
|
222
234
|
return /* @__PURE__ */ jsx(Page, { children: /* @__PURE__ */ jsx(PageBody, { children: /* @__PURE__ */ jsx("div", { className: "rounded border border-destructive/40 bg-destructive/10 px-4 py-3 text-sm text-destructive", children: t("directory.organizations.form.errors.missingId", "Organization identifier is missing.") }) }) });
|
|
223
235
|
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../../../../../../../src/modules/directory/backend/directory/organizations/%5Bid%5D/edit/page.tsx"],
|
|
4
|
-
"sourcesContent": ["\"use client\"\nimport * as React from 'react'\nimport { useT } from '@open-mercato/shared/lib/i18n/context'\nimport { extractCustomFieldEntries } from '@open-mercato/shared/lib/crud/custom-fields-client'\nimport { E } from '#generated/entities.ids.generated'\nimport { OrganizationSelect } from '@open-mercato/core/modules/directory/components/OrganizationSelect'\nimport { TenantSelect } from '@open-mercato/core/modules/directory/components/TenantSelect'\nimport {\n buildOrganizationTreeOptions,\n type OrganizationTreeNode,\n type OrganizationTreeOption,\n} from '@open-mercato/core/modules/directory/lib/tree'\nimport { Page, PageBody } from '@open-mercato/ui/backend/Page'\nimport { CrudForm, type CrudField, type CrudFormGroup } from '@open-mercato/ui/backend/CrudForm'\nimport { apiCall } from '@open-mercato/ui/backend/utils/apiCall'\nimport { deleteCrud, updateCrud } from '@open-mercato/ui/backend/utils/crud'\nimport { collectCustomFieldValues } from '@open-mercato/ui/backend/utils/customFieldValues'\nimport { createCrudFormError } from '@open-mercato/ui/backend/utils/serverErrors'\nimport { ErrorMessage, RecordNotFoundState } from '@open-mercato/ui/backend/detail'\n\ntype TreeResponse = {\n items: OrganizationTreeNode[]\n}\n\ntype OrganizationResponse = {\n items: Array<{\n id: string\n name: string\n slug?: string | null\n tenantId: string\n tenantName?: string | null\n parentId: string | null\n childIds: string[]\n ancestorIds: string[]\n descendantIds: string[]\n isActive: boolean\n pathLabel: string\n updatedAt?: string | null\n updated_at?: string | null\n } & Record<string, unknown>>\n}\n\nconst TREE_STEP = 16\nconst TREE_PADDING = 12\n\nexport default function EditOrganizationPage({ params }: { params?: { id?: string } }) {\n const orgId = params?.id\n const t = useT()\n const [initialValues, setInitialValues] = React.useState<Record<string, unknown> | null>(null)\n const [pathLabel, setPathLabel] = React.useState<string>('')\n const [tenantId, setTenantId] = React.useState<string | null>(null)\n const [loading, setLoading] = React.useState(true)\n const [error, setError] = React.useState<string | null>(null)\n const [isNotFound, setIsNotFound] = React.useState(false)\n const [parentTree, setParentTree] = React.useState<OrganizationTreeNode[]>([])\n const [childSummary, setChildSummary] = React.useState<OrganizationTreeOption[]>([])\n const [originalChildIds, setOriginalChildIds] = React.useState<string[]>([])\n const [actorIsSuperAdmin, setActorIsSuperAdmin] = React.useState(false)\n const skipTenantEffectRef = React.useRef(true)\n\n React.useEffect(() => {\n let cancelled = false\n async function loadActor() {\n try {\n const { ok, result } = await apiCall<{ isSuperAdmin?: boolean }>('/api/auth/roles?page=1&pageSize=1')\n if (!cancelled && ok) setActorIsSuperAdmin(Boolean(result?.isSuperAdmin))\n } catch {\n if (!cancelled) setActorIsSuperAdmin(false)\n }\n }\n loadActor()\n return () => { cancelled = true }\n }, [])\n\n const markSelectable = React.useCallback((nodes: OrganizationTreeNode[], excluded: Set<string>): OrganizationTreeNode[] => (\n nodes.map((node) => ({\n ...node,\n selectable: !excluded.has(node.id),\n children: Array.isArray(node.children) ? markSelectable(node.children, excluded) : [],\n }))\n ), [])\n\n const loadParentTree = React.useCallback(async (\n targetTenantId: string | null,\n excludedIds: Iterable<string>,\n ): Promise<OrganizationTreeNode[]> => {\n const treeParams = new URLSearchParams({ view: 'tree', includeInactive: 'true' })\n if (targetTenantId) treeParams.set('tenantId', targetTenantId)\n if (orgId) treeParams.set('ids', orgId)\n try {\n const { ok, result } = await apiCall<TreeResponse>(`/api/directory/organizations?${treeParams.toString()}`)\n if (!ok) {\n setParentTree([])\n return []\n }\n const baseTree = Array.isArray(result?.items) ? result.items ?? [] : []\n const excludedSet = new Set<string>(excludedIds)\n if (orgId) excludedSet.add(orgId)\n setParentTree(markSelectable(baseTree, excludedSet))\n return baseTree\n } catch {\n setParentTree([])\n return []\n }\n }, [markSelectable, orgId])\n\n React.useEffect(() => {\n if (!orgId) return\n const currentOrgId = orgId\n let cancelled = false\n async function load() {\n setLoading(true)\n setError(null)\n setIsNotFound(false)\n try {\n const { ok, status, result } = await apiCall<OrganizationResponse>(\n `/api/directory/organizations?view=manage&ids=${currentOrgId}&status=all&includeInactive=true&page=1&pageSize=1`,\n )\n if (!ok) {\n if (status === 404) {\n if (!cancelled) setIsNotFound(true)\n return\n }\n throw new Error(t('directory.organizations.form.errors.load', 'Failed to load organization'))\n }\n const record = Array.isArray(result?.items) ? result.items?.[0] : undefined\n if (!record) {\n if (!cancelled) setIsNotFound(true)\n return\n }\n const resolvedTenantId = record.tenantId || null\n setTenantId(resolvedTenantId)\n const baseTree = await loadParentTree(resolvedTenantId, record.descendantIds ?? [])\n const fullTree = buildOrganizationTreeOptions(baseTree)\n const nodeMap = new Map(fullTree.map((opt) => [opt.value, opt]))\n const childrenDetails = record.childIds\n .map((id) => nodeMap.get(id))\n .filter((node): node is OrganizationTreeOption => !!node)\n setChildSummary(childrenDetails)\n setOriginalChildIds(Array.isArray(record.childIds) ? record.childIds : [])\n\n const customValues = extractCustomFieldEntries(record as Record<string, unknown>)\n setInitialValues({\n id: record.id,\n name: record.name,\n slug: record.slug ?? '',\n parentId: record.parentId || '',\n isActive: record.isActive,\n tenantId: resolvedTenantId,\n updatedAt: typeof record.updatedAt === 'string'\n ? record.updatedAt\n : typeof record.updated_at === 'string'\n ? record.updated_at\n : null,\n ...customValues,\n })\n setPathLabel(record.pathLabel)\n } catch (err: unknown) {\n if (!cancelled) {\n const fallback = t('directory.organizations.form.errors.load', 'Failed to load organization')\n const message = err instanceof Error && err.message ? err.message : fallback\n setError(message)\n }\n } finally {\n if (!cancelled) {\n skipTenantEffectRef.current = false\n setLoading(false)\n }\n }\n }\n load()\n return () => { cancelled = true }\n }, [loadParentTree, orgId, t])\n\n React.useEffect(() => {\n if (!actorIsSuperAdmin) return\n if (skipTenantEffectRef.current) {\n skipTenantEffectRef.current = false\n return\n }\n if (!orgId) return\n if (!tenantId) {\n setParentTree([])\n setChildSummary([])\n setOriginalChildIds([])\n return\n }\n void loadParentTree(tenantId, [])\n setChildSummary([])\n setOriginalChildIds([])\n }, [actorIsSuperAdmin, loadParentTree, orgId, tenantId])\n\n const fields = React.useMemo<CrudField[]>(() => [\n ...(actorIsSuperAdmin ? [\n {\n id: 'tenantId',\n label: t('directory.organizations.form.field.tenant', 'Tenant'),\n type: 'custom',\n component: ({ value, setValue }) => (\n <TenantSelect\n id=\"tenantId\"\n value={typeof value === 'string' ? value : tenantId}\n onChange={(next) => {\n const normalized = next ?? null\n setTenantId(normalized)\n setValue(normalized)\n }}\n includeEmptyOption={false}\n className=\"w-full h-9 rounded border px-2 text-sm\"\n />\n ),\n } as CrudField,\n ] : []),\n { id: 'name', label: t('directory.organizations.form.field.name', 'Name'), type: 'text', required: true },\n {\n id: 'slug',\n label: t('directory.organizations.form.field.slug', 'Slug'),\n type: 'text',\n description: t('directory.organizations.form.field.slug.description', 'URL-safe identifier used for the customer portal (lowercase letters, numbers, hyphens, underscores).'),\n },\n {\n id: 'parentId',\n label: t('directory.organizations.form.field.parent', 'Parent'),\n type: 'custom',\n component: ({ id, value, setValue }) => (\n <OrganizationSelect\n id={id}\n value={value ? String(value) : null}\n onChange={(next) => setValue(next ?? '')}\n nodes={parentTree}\n includeEmptyOption\n emptyOptionLabel={t('directory.organizations.form.rootOption', '\u2014 Root level \u2014')}\n className=\"w-full h-9 rounded border px-2 text-sm\"\n />\n ),\n },\n {\n id: 'childrenInfo',\n label: t('directory.organizations.form.field.children', 'Children'),\n type: 'custom',\n component: () => {\n if (!childSummary.length) {\n return <p className=\"text-xs text-muted-foreground\">{t('directory.organizations.form.children.empty', 'No direct children assigned.')}</p>\n }\n return (\n <ul className=\"space-y-1 text-sm\">\n {childSummary.map((child) => (\n <li key={child.value} className=\"leading-none\">\n <span style={{ paddingLeft: child.depth > 0 ? TREE_PADDING + (child.depth - 1) * TREE_STEP : 0 }}>\n {child.depth > 0 ? <span className=\"text-muted-foreground\">\u21B3 </span> : null}\n {child.name}\n </span>\n </li>\n ))}\n </ul>\n )\n },\n },\n { id: 'isActive', label: t('directory.organizations.form.field.isActive', 'Active'), type: 'checkbox' },\n ], [actorIsSuperAdmin, childSummary, parentTree, t, tenantId])\n\n const detailFields = React.useMemo(() => (\n actorIsSuperAdmin\n ? ['tenantId', 'name', 'slug', 'parentId', 'childrenInfo', 'isActive']\n : ['name', 'slug', 'parentId', 'childrenInfo', 'isActive']\n ), [actorIsSuperAdmin])\n\n const groups: CrudFormGroup[] = React.useMemo(() => ([\n { id: 'details', title: t('directory.organizations.form.group.details', 'Details'), column: 1, fields: detailFields },\n { id: 'custom', title: t('directory.organizations.form.group.customFields', 'Custom Data'), column: 2, kind: 'customFields' },\n ]), [detailFields, t])\n\n if (!orgId) {\n return (\n <Page>\n <PageBody>\n <div className=\"rounded border border-destructive/40 bg-destructive/10 px-4 py-3 text-sm text-destructive\">\n {t('directory.organizations.form.errors.missingId', 'Organization identifier is missing.')}\n </div>\n </PageBody>\n </Page>\n )\n }\n\n if (isNotFound) {\n return (\n <Page>\n <PageBody>\n <RecordNotFoundState\n label={t('directory.organizations.form.errors.notFound', 'Organization not found')}\n backHref=\"/backend/directory/organizations\"\n backLabel={t('directory.organizations.form.actions.backToList', 'Back to organizations')}\n />\n </PageBody>\n </Page>\n )\n }\n\n if (error && !loading && !initialValues) {\n return (\n <Page>\n <PageBody>\n <ErrorMessage label={error} />\n </PageBody>\n </Page>\n )\n }\n\n return (\n <Page>\n <PageBody>\n <CrudForm\n title={t('directory.organizations.form.title.edit', 'Edit Organization')}\n backHref=\"/backend/directory/organizations\"\n versionHistory={{ resourceKind: 'directory.organization', resourceId: orgId ? String(orgId) : '' }}\n fields={fields}\n groups={groups}\n entityId={E.directory.organization}\n initialValues={initialValues ?? { id: orgId, tenantId: tenantId ?? null, name: '', slug: '', parentId: '', isActive: true, childIds: [] }}\n optimisticLockUpdatedAt={typeof initialValues?.updatedAt === 'string' ? initialValues.updatedAt : null}\n isLoading={loading}\n loadingMessage={t('directory.organizations.form.loading', 'Loading organization...')}\n submitLabel={t('directory.organizations.form.action.save', 'Save')}\n cancelHref=\"/backend/directory/organizations\"\n successRedirect={`/backend/directory/organizations?flash=${encodeURIComponent(t('directory.organizations.flash.updated', 'Organization updated'))}&type=success`}\n extraActions={pathLabel ? (\n <span className=\"text-xs text-muted-foreground\">\n {t('directory.organizations.form.pathLabel', { path: pathLabel })}\n </span>\n ) : null}\n onSubmit={async (values) => {\n await submitUpdateOrganization({\n values: values as Record<string, unknown>,\n orgId: orgId ?? '',\n tenantId,\n originalChildIds,\n messages: {\n orgIdRequired: t('directory.organizations.errors.orgIdRequired', 'Organization identifier is required'),\n },\n })\n }}\n onDelete={async () => {\n await deleteCrud('directory/organizations', orgId ?? '', {\n errorMessage: t('directory.organizations.errors.deleteFailed', 'Failed to delete organization'),\n })\n }}\n deleteRedirect={`/backend/directory/organizations?flash=${encodeURIComponent(t('directory.organizations.flash.deleted', 'Organization deleted'))}&type=success`}\n />\n </PageBody>\n </Page>\n )\n}\n\ntype UpdateOrganizationPayload = {\n id: string\n name: string\n slug?: string | null\n isActive: boolean\n parentId: string | null\n childIds: string[]\n tenantId?: string | null\n customFields?: Record<string, unknown>\n}\n\ntype UpdateOrganizationRequest = (payload: UpdateOrganizationPayload) => Promise<void>\n\nconst defaultUpdateOrganizationRequest: UpdateOrganizationRequest = async (payload) => {\n await updateCrud('directory/organizations', payload)\n}\n\nexport async function submitUpdateOrganization(options: {\n values: Record<string, unknown>\n orgId: string\n tenantId: string | null\n originalChildIds: string[]\n updateOrganization?: UpdateOrganizationRequest\n messages?: {\n orgIdRequired?: string\n }\n}): Promise<void> {\n const {\n values,\n orgId,\n tenantId,\n originalChildIds,\n updateOrganization = defaultUpdateOrganizationRequest,\n messages,\n } = options\n\n const payloadId = typeof values.id === 'string' && values.id.length ? values.id : orgId\n if (!payloadId) {\n const message = messages?.orgIdRequired ?? 'Organization identifier is required'\n throw createCrudFormError(message, { id: message })\n }\n\n const customFields = collectCustomFieldValues(values)\n\n const submittedTenantId =\n typeof values.tenantId === 'string' && values.tenantId.trim().length\n ? values.tenantId.trim()\n : tenantId\n\n const payload: UpdateOrganizationPayload = {\n id: payloadId,\n name: typeof values.name === 'string' ? values.name : '',\n isActive: values.isActive !== false,\n parentId:\n typeof values.parentId === 'string' && values.parentId.length\n ? values.parentId\n : null,\n childIds: originalChildIds,\n }\n\n if (typeof values.slug === 'string') {\n const trimmedSlug = values.slug.trim()\n payload.slug = trimmedSlug.length ? trimmedSlug : null\n }\n\n if (submittedTenantId !== undefined && submittedTenantId !== null) {\n payload.tenantId = submittedTenantId\n }\n if (Object.keys(customFields).length > 0) {\n payload.customFields = customFields\n }\n\n await updateOrganization(payload)\n}\n"],
|
|
5
|
-
"mappings": ";
|
|
4
|
+
"sourcesContent": ["\"use client\"\nimport * as React from 'react'\nimport { usePathname } from 'next/navigation'\nimport { useT } from '@open-mercato/shared/lib/i18n/context'\nimport { extractCustomFieldEntries } from '@open-mercato/shared/lib/crud/custom-fields-client'\nimport { E } from '#generated/entities.ids.generated'\nimport { OrganizationSelect } from '@open-mercato/core/modules/directory/components/OrganizationSelect'\nimport { TenantSelect } from '@open-mercato/core/modules/directory/components/TenantSelect'\nimport {\n buildOrganizationTreeOptions,\n type OrganizationTreeNode,\n type OrganizationTreeOption,\n} from '@open-mercato/core/modules/directory/lib/tree'\nimport { Page, PageBody } from '@open-mercato/ui/backend/Page'\nimport { CrudForm, type CrudField, type CrudFormGroup } from '@open-mercato/ui/backend/CrudForm'\nimport { apiCall } from '@open-mercato/ui/backend/utils/apiCall'\nimport { deleteCrud, updateCrud } from '@open-mercato/ui/backend/utils/crud'\nimport { collectCustomFieldValues } from '@open-mercato/ui/backend/utils/customFieldValues'\nimport { createCrudFormError } from '@open-mercato/ui/backend/utils/serverErrors'\nimport { ErrorMessage, RecordNotFoundState } from '@open-mercato/ui/backend/detail'\nimport { buildRecordInjectionContext, useSetCurrentRecordInjectionContext } from '@open-mercato/ui/backend/injection/recordContext'\n\ntype TreeResponse = {\n items: OrganizationTreeNode[]\n}\n\ntype OrganizationResponse = {\n items: Array<{\n id: string\n name: string\n slug?: string | null\n tenantId: string\n tenantName?: string | null\n parentId: string | null\n childIds: string[]\n ancestorIds: string[]\n descendantIds: string[]\n isActive: boolean\n pathLabel: string\n updatedAt?: string | null\n updated_at?: string | null\n } & Record<string, unknown>>\n}\n\nconst TREE_STEP = 16\nconst TREE_PADDING = 12\n\nexport default function EditOrganizationPage({ params }: { params?: { id?: string } }) {\n const orgId = params?.id\n const t = useT()\n const pathname = usePathname()\n const [initialValues, setInitialValues] = React.useState<Record<string, unknown> | null>(null)\n const [pathLabel, setPathLabel] = React.useState<string>('')\n const [tenantId, setTenantId] = React.useState<string | null>(null)\n const [loading, setLoading] = React.useState(true)\n const [error, setError] = React.useState<string | null>(null)\n const [isNotFound, setIsNotFound] = React.useState(false)\n const [parentTree, setParentTree] = React.useState<OrganizationTreeNode[]>([])\n const [childSummary, setChildSummary] = React.useState<OrganizationTreeOption[]>([])\n const [originalChildIds, setOriginalChildIds] = React.useState<string[]>([])\n const [actorIsSuperAdmin, setActorIsSuperAdmin] = React.useState(false)\n const skipTenantEffectRef = React.useRef(true)\n\n React.useEffect(() => {\n let cancelled = false\n async function loadActor() {\n try {\n const { ok, result } = await apiCall<{ isSuperAdmin?: boolean }>('/api/auth/roles?page=1&pageSize=1')\n if (!cancelled && ok) setActorIsSuperAdmin(Boolean(result?.isSuperAdmin))\n } catch {\n if (!cancelled) setActorIsSuperAdmin(false)\n }\n }\n loadActor()\n return () => { cancelled = true }\n }, [])\n\n const markSelectable = React.useCallback((nodes: OrganizationTreeNode[], excluded: Set<string>): OrganizationTreeNode[] => (\n nodes.map((node) => ({\n ...node,\n selectable: !excluded.has(node.id),\n children: Array.isArray(node.children) ? markSelectable(node.children, excluded) : [],\n }))\n ), [])\n\n const loadParentTree = React.useCallback(async (\n targetTenantId: string | null,\n excludedIds: Iterable<string>,\n ): Promise<OrganizationTreeNode[]> => {\n const treeParams = new URLSearchParams({ view: 'tree', includeInactive: 'true' })\n if (targetTenantId) treeParams.set('tenantId', targetTenantId)\n if (orgId) treeParams.set('ids', orgId)\n try {\n const { ok, result } = await apiCall<TreeResponse>(`/api/directory/organizations?${treeParams.toString()}`)\n if (!ok) {\n setParentTree([])\n return []\n }\n const baseTree = Array.isArray(result?.items) ? result.items ?? [] : []\n const excludedSet = new Set<string>(excludedIds)\n if (orgId) excludedSet.add(orgId)\n setParentTree(markSelectable(baseTree, excludedSet))\n return baseTree\n } catch {\n setParentTree([])\n return []\n }\n }, [markSelectable, orgId])\n\n React.useEffect(() => {\n if (!orgId) return\n const currentOrgId = orgId\n let cancelled = false\n async function load() {\n setLoading(true)\n setError(null)\n setIsNotFound(false)\n try {\n const { ok, status, result } = await apiCall<OrganizationResponse>(\n `/api/directory/organizations?view=manage&ids=${currentOrgId}&status=all&includeInactive=true&page=1&pageSize=1`,\n )\n if (!ok) {\n if (status === 404) {\n if (!cancelled) setIsNotFound(true)\n return\n }\n throw new Error(t('directory.organizations.form.errors.load', 'Failed to load organization'))\n }\n const record = Array.isArray(result?.items) ? result.items?.[0] : undefined\n if (!record) {\n if (!cancelled) setIsNotFound(true)\n return\n }\n const resolvedTenantId = record.tenantId || null\n setTenantId(resolvedTenantId)\n const baseTree = await loadParentTree(resolvedTenantId, record.descendantIds ?? [])\n const fullTree = buildOrganizationTreeOptions(baseTree)\n const nodeMap = new Map(fullTree.map((opt) => [opt.value, opt]))\n const childrenDetails = record.childIds\n .map((id) => nodeMap.get(id))\n .filter((node): node is OrganizationTreeOption => !!node)\n setChildSummary(childrenDetails)\n setOriginalChildIds(Array.isArray(record.childIds) ? record.childIds : [])\n\n const customValues = extractCustomFieldEntries(record as Record<string, unknown>)\n setInitialValues({\n id: record.id,\n name: record.name,\n slug: record.slug ?? '',\n parentId: record.parentId || '',\n isActive: record.isActive,\n tenantId: resolvedTenantId,\n updatedAt: typeof record.updatedAt === 'string'\n ? record.updatedAt\n : typeof record.updated_at === 'string'\n ? record.updated_at\n : null,\n ...customValues,\n })\n setPathLabel(record.pathLabel)\n } catch (err: unknown) {\n if (!cancelled) {\n const fallback = t('directory.organizations.form.errors.load', 'Failed to load organization')\n const message = err instanceof Error && err.message ? err.message : fallback\n setError(message)\n }\n } finally {\n if (!cancelled) {\n skipTenantEffectRef.current = false\n setLoading(false)\n }\n }\n }\n load()\n return () => { cancelled = true }\n }, [loadParentTree, orgId, t])\n\n React.useEffect(() => {\n if (!actorIsSuperAdmin) return\n if (skipTenantEffectRef.current) {\n skipTenantEffectRef.current = false\n return\n }\n if (!orgId) return\n if (!tenantId) {\n setParentTree([])\n setChildSummary([])\n setOriginalChildIds([])\n return\n }\n void loadParentTree(tenantId, [])\n setChildSummary([])\n setOriginalChildIds([])\n }, [actorIsSuperAdmin, loadParentTree, orgId, tenantId])\n\n const fields = React.useMemo<CrudField[]>(() => [\n ...(actorIsSuperAdmin ? [\n {\n id: 'tenantId',\n label: t('directory.organizations.form.field.tenant', 'Tenant'),\n type: 'custom',\n component: ({ value, setValue }) => (\n <TenantSelect\n id=\"tenantId\"\n value={typeof value === 'string' ? value : tenantId}\n onChange={(next) => {\n const normalized = next ?? null\n setTenantId(normalized)\n setValue(normalized)\n }}\n includeEmptyOption={false}\n className=\"w-full h-9 rounded border px-2 text-sm\"\n />\n ),\n } as CrudField,\n ] : []),\n { id: 'name', label: t('directory.organizations.form.field.name', 'Name'), type: 'text', required: true },\n {\n id: 'slug',\n label: t('directory.organizations.form.field.slug', 'Slug'),\n type: 'text',\n description: t('directory.organizations.form.field.slug.description', 'URL-safe identifier used for the customer portal (lowercase letters, numbers, hyphens, underscores).'),\n },\n {\n id: 'parentId',\n label: t('directory.organizations.form.field.parent', 'Parent'),\n type: 'custom',\n component: ({ id, value, setValue }) => (\n <OrganizationSelect\n id={id}\n value={value ? String(value) : null}\n onChange={(next) => setValue(next ?? '')}\n nodes={parentTree}\n includeEmptyOption\n emptyOptionLabel={t('directory.organizations.form.rootOption', '\u2014 Root level \u2014')}\n className=\"w-full h-9 rounded border px-2 text-sm\"\n />\n ),\n },\n {\n id: 'childrenInfo',\n label: t('directory.organizations.form.field.children', 'Children'),\n type: 'custom',\n component: () => {\n if (!childSummary.length) {\n return <p className=\"text-xs text-muted-foreground\">{t('directory.organizations.form.children.empty', 'No direct children assigned.')}</p>\n }\n return (\n <ul className=\"space-y-1 text-sm\">\n {childSummary.map((child) => (\n <li key={child.value} className=\"leading-none\">\n <span style={{ paddingLeft: child.depth > 0 ? TREE_PADDING + (child.depth - 1) * TREE_STEP : 0 }}>\n {child.depth > 0 ? <span className=\"text-muted-foreground\">\u21B3 </span> : null}\n {child.name}\n </span>\n </li>\n ))}\n </ul>\n )\n },\n },\n { id: 'isActive', label: t('directory.organizations.form.field.isActive', 'Active'), type: 'checkbox' },\n ], [actorIsSuperAdmin, childSummary, parentTree, t, tenantId])\n\n const detailFields = React.useMemo(() => (\n actorIsSuperAdmin\n ? ['tenantId', 'name', 'slug', 'parentId', 'childrenInfo', 'isActive']\n : ['name', 'slug', 'parentId', 'childrenInfo', 'isActive']\n ), [actorIsSuperAdmin])\n\n const groups: CrudFormGroup[] = React.useMemo(() => ([\n { id: 'details', title: t('directory.organizations.form.group.details', 'Details'), column: 1, fields: detailFields },\n { id: 'custom', title: t('directory.organizations.form.group.customFields', 'Custom Data'), column: 2, kind: 'customFields' },\n ]), [detailFields, t])\n\n // Publish page-load record context to the AppShell-owned `backend:record:current`\n // mount so the enterprise record_locks widget resolves `directory.organization` + id\n // explicitly. The resourceKind mirrors the CrudForm `versionHistory` so the held\n // lock matches the save-time conflict surface for the same organization.\n useSetCurrentRecordInjectionContext(\n buildRecordInjectionContext({\n resourceKind: 'directory.organization',\n resourceId: orgId || null,\n updatedAt: typeof initialValues?.updatedAt === 'string' ? initialValues.updatedAt : null,\n data: initialValues,\n path: pathname,\n }),\n )\n\n if (!orgId) {\n return (\n <Page>\n <PageBody>\n <div className=\"rounded border border-destructive/40 bg-destructive/10 px-4 py-3 text-sm text-destructive\">\n {t('directory.organizations.form.errors.missingId', 'Organization identifier is missing.')}\n </div>\n </PageBody>\n </Page>\n )\n }\n\n if (isNotFound) {\n return (\n <Page>\n <PageBody>\n <RecordNotFoundState\n label={t('directory.organizations.form.errors.notFound', 'Organization not found')}\n backHref=\"/backend/directory/organizations\"\n backLabel={t('directory.organizations.form.actions.backToList', 'Back to organizations')}\n />\n </PageBody>\n </Page>\n )\n }\n\n if (error && !loading && !initialValues) {\n return (\n <Page>\n <PageBody>\n <ErrorMessage label={error} />\n </PageBody>\n </Page>\n )\n }\n\n return (\n <Page>\n <PageBody>\n <CrudForm\n title={t('directory.organizations.form.title.edit', 'Edit Organization')}\n backHref=\"/backend/directory/organizations\"\n versionHistory={{ resourceKind: 'directory.organization', resourceId: orgId ? String(orgId) : '' }}\n fields={fields}\n groups={groups}\n entityId={E.directory.organization}\n initialValues={initialValues ?? { id: orgId, tenantId: tenantId ?? null, name: '', slug: '', parentId: '', isActive: true, childIds: [] }}\n optimisticLockUpdatedAt={typeof initialValues?.updatedAt === 'string' ? initialValues.updatedAt : null}\n isLoading={loading}\n loadingMessage={t('directory.organizations.form.loading', 'Loading organization...')}\n submitLabel={t('directory.organizations.form.action.save', 'Save')}\n cancelHref=\"/backend/directory/organizations\"\n successRedirect={`/backend/directory/organizations?flash=${encodeURIComponent(t('directory.organizations.flash.updated', 'Organization updated'))}&type=success`}\n extraActions={pathLabel ? (\n <span className=\"text-xs text-muted-foreground\">\n {t('directory.organizations.form.pathLabel', { path: pathLabel })}\n </span>\n ) : null}\n onSubmit={async (values) => {\n await submitUpdateOrganization({\n values: values as Record<string, unknown>,\n orgId: orgId ?? '',\n tenantId,\n originalChildIds,\n messages: {\n orgIdRequired: t('directory.organizations.errors.orgIdRequired', 'Organization identifier is required'),\n },\n })\n }}\n onDelete={async () => {\n await deleteCrud('directory/organizations', orgId ?? '', {\n errorMessage: t('directory.organizations.errors.deleteFailed', 'Failed to delete organization'),\n })\n }}\n deleteRedirect={`/backend/directory/organizations?flash=${encodeURIComponent(t('directory.organizations.flash.deleted', 'Organization deleted'))}&type=success`}\n />\n </PageBody>\n </Page>\n )\n}\n\ntype UpdateOrganizationPayload = {\n id: string\n name: string\n slug?: string | null\n isActive: boolean\n parentId: string | null\n childIds: string[]\n tenantId?: string | null\n customFields?: Record<string, unknown>\n}\n\ntype UpdateOrganizationRequest = (payload: UpdateOrganizationPayload) => Promise<void>\n\nconst defaultUpdateOrganizationRequest: UpdateOrganizationRequest = async (payload) => {\n await updateCrud('directory/organizations', payload)\n}\n\nexport async function submitUpdateOrganization(options: {\n values: Record<string, unknown>\n orgId: string\n tenantId: string | null\n originalChildIds: string[]\n updateOrganization?: UpdateOrganizationRequest\n messages?: {\n orgIdRequired?: string\n }\n}): Promise<void> {\n const {\n values,\n orgId,\n tenantId,\n originalChildIds,\n updateOrganization = defaultUpdateOrganizationRequest,\n messages,\n } = options\n\n const payloadId = typeof values.id === 'string' && values.id.length ? values.id : orgId\n if (!payloadId) {\n const message = messages?.orgIdRequired ?? 'Organization identifier is required'\n throw createCrudFormError(message, { id: message })\n }\n\n const customFields = collectCustomFieldValues(values)\n\n const submittedTenantId =\n typeof values.tenantId === 'string' && values.tenantId.trim().length\n ? values.tenantId.trim()\n : tenantId\n\n const payload: UpdateOrganizationPayload = {\n id: payloadId,\n name: typeof values.name === 'string' ? values.name : '',\n isActive: values.isActive !== false,\n parentId:\n typeof values.parentId === 'string' && values.parentId.length\n ? values.parentId\n : null,\n childIds: originalChildIds,\n }\n\n if (typeof values.slug === 'string') {\n const trimmedSlug = values.slug.trim()\n payload.slug = trimmedSlug.length ? trimmedSlug : null\n }\n\n if (submittedTenantId !== undefined && submittedTenantId !== null) {\n payload.tenantId = submittedTenantId\n }\n if (Object.keys(customFields).length > 0) {\n payload.customFields = customFields\n }\n\n await updateOrganization(payload)\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
import { jsx } from "react/jsx-runtime";
|
|
3
3
|
import * as React from "react";
|
|
4
|
+
import { usePathname } from "next/navigation";
|
|
4
5
|
import { E } from "../../../../../../../generated/entities.ids.generated.js";
|
|
5
6
|
import { Page, PageBody } from "@open-mercato/ui/backend/Page";
|
|
6
7
|
import { CrudForm } from "@open-mercato/ui/backend/CrudForm";
|
|
@@ -10,10 +11,12 @@ import { raiseCrudError } from "@open-mercato/ui/backend/utils/serverErrors";
|
|
|
10
11
|
import { readApiResultOrThrow, apiCall, withScopedApiRequestHeaders } from "@open-mercato/ui/backend/utils/apiCall";
|
|
11
12
|
import { buildOptimisticLockHeader } from "@open-mercato/ui/backend/utils/optimisticLock";
|
|
12
13
|
import { ErrorMessage, RecordNotFoundState } from "@open-mercato/ui/backend/detail";
|
|
14
|
+
import { buildRecordInjectionContext, useSetCurrentRecordInjectionContext } from "@open-mercato/ui/backend/injection/recordContext";
|
|
13
15
|
import { useT } from "@open-mercato/shared/lib/i18n/context";
|
|
14
16
|
import { extractCustomFieldEntries } from "@open-mercato/shared/lib/crud/custom-fields-client";
|
|
15
17
|
function EditTenantPage({ params }) {
|
|
16
18
|
const tenantId = params?.id;
|
|
19
|
+
const pathname = usePathname();
|
|
17
20
|
const [initial, setInitial] = React.useState(null);
|
|
18
21
|
const [loading, setLoading] = React.useState(true);
|
|
19
22
|
const [error, setError] = React.useState(null);
|
|
@@ -73,6 +76,15 @@ function EditTenantPage({ params }) {
|
|
|
73
76
|
cancelled = true;
|
|
74
77
|
};
|
|
75
78
|
}, [tenantId, t]);
|
|
79
|
+
useSetCurrentRecordInjectionContext(
|
|
80
|
+
buildRecordInjectionContext({
|
|
81
|
+
resourceKind: "directory.tenant",
|
|
82
|
+
resourceId: tenantId || null,
|
|
83
|
+
updatedAt: initial?.updatedAt ?? null,
|
|
84
|
+
data: initial,
|
|
85
|
+
path: pathname
|
|
86
|
+
})
|
|
87
|
+
);
|
|
76
88
|
if (!tenantId) return null;
|
|
77
89
|
if (isNotFound) {
|
|
78
90
|
return /* @__PURE__ */ jsx(Page, { children: /* @__PURE__ */ jsx(PageBody, { children: /* @__PURE__ */ jsx(
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../../../../../../../src/modules/directory/backend/directory/tenants/%5Bid%5D/edit/page.tsx"],
|
|
4
|
-
"sourcesContent": ["\"use client\"\nimport * as React from 'react'\nimport { E } from '#generated/entities.ids.generated'\nimport { Page, PageBody } from '@open-mercato/ui/backend/Page'\nimport { CrudForm, type CrudField, type CrudFormGroup } from '@open-mercato/ui/backend/CrudForm'\nimport { collectCustomFieldValues } from '@open-mercato/ui/backend/utils/customFieldValues'\nimport { updateCrud } from '@open-mercato/ui/backend/utils/crud'\nimport { raiseCrudError } from '@open-mercato/ui/backend/utils/serverErrors'\nimport { readApiResultOrThrow, apiCall, withScopedApiRequestHeaders } from '@open-mercato/ui/backend/utils/apiCall'\nimport { buildOptimisticLockHeader } from '@open-mercato/ui/backend/utils/optimisticLock'\nimport { ErrorMessage, RecordNotFoundState } from '@open-mercato/ui/backend/detail'\nimport { useT } from '@open-mercato/shared/lib/i18n/context'\nimport { extractCustomFieldEntries } from '@open-mercato/shared/lib/crud/custom-fields-client'\n\ntype TenantFormValues = {\n id: string\n name: string\n isActive: boolean\n updatedAt?: string | null\n} & Record<string, unknown>\n\nexport default function EditTenantPage({ params }: { params?: { id?: string } }) {\n const tenantId = params?.id\n const [initial, setInitial] = React.useState<TenantFormValues | null>(null)\n const [loading, setLoading] = React.useState(true)\n const [error, setError] = React.useState<string | null>(null)\n const [isNotFound, setIsNotFound] = React.useState(false)\n const t = useT()\n const fields = React.useMemo<CrudField[]>(() => [\n { id: 'name', label: t('directory.tenants.form.fields.name', 'Name'), type: 'text', required: true },\n { id: 'isActive', label: t('directory.tenants.form.fields.active', 'Active'), type: 'checkbox' },\n ], [t])\n const groups = React.useMemo<CrudFormGroup[]>(() => [\n { id: 'details', title: t('directory.tenants.form.groups.details', 'Details'), column: 1, fields: ['name', 'isActive'] },\n { id: 'custom', title: t('directory.tenants.form.groups.custom', 'Custom Data'), column: 2, kind: 'customFields' },\n ], [t])\n\n React.useEffect(() => {\n let cancelled = false\n async function load() {\n if (!tenantId) return\n setLoading(true)\n setError(null)\n setIsNotFound(false)\n try {\n const data = await readApiResultOrThrow<{ items?: Record<string, unknown>[] }>(\n `/api/directory/tenants?id=${encodeURIComponent(tenantId)}`,\n undefined,\n { errorMessage: t('directory.tenants.form.errors.load', 'Failed to load tenant'), fallback: { items: [] } },\n )\n const rows = Array.isArray(data?.items) ? data.items : []\n const row = rows[0]\n if (!row) {\n if (!cancelled) setIsNotFound(true)\n return\n }\n const cfValues = extractCustomFieldEntries(row as Record<string, unknown>)\n const values: TenantFormValues = {\n id: String(row.id),\n name: String(row.name),\n isActive: !!row.isActive,\n updatedAt: typeof row.updatedAt === 'string'\n ? row.updatedAt\n : typeof row.updated_at === 'string'\n ? row.updated_at\n : null,\n ...cfValues,\n }\n if (!cancelled) setInitial(values)\n } catch (err) {\n if (!cancelled) {\n if ((err as { status?: number }).status === 404) {\n setIsNotFound(true)\n } else {\n const message = err instanceof Error ? err.message : t('directory.tenants.form.errors.load', 'Failed to load tenant')\n setError(message)\n }\n }\n } finally {\n if (!cancelled) setLoading(false)\n }\n }\n load()\n return () => { cancelled = true }\n }, [tenantId, t])\n\n if (!tenantId) return null\n\n if (isNotFound) {\n return (\n <Page>\n <PageBody>\n <RecordNotFoundState\n label={t('directory.tenants.form.errors.notFound', 'Tenant not found')}\n backHref=\"/backend/directory/tenants\"\n backLabel={t('directory.tenants.form.actions.backToList', 'Back to tenants')}\n />\n </PageBody>\n </Page>\n )\n }\n\n if (error && !loading && !initial) {\n return (\n <Page>\n <PageBody>\n <ErrorMessage label={error} />\n </PageBody>\n </Page>\n )\n }\n\n return (\n <Page>\n <PageBody>\n <CrudForm<TenantFormValues>\n title={t('directory.tenants.form.title.edit', 'Edit Tenant')}\n backHref=\"/backend/directory/tenants\"\n versionHistory={{ resourceKind: 'directory.tenant', resourceId: tenantId ? String(tenantId) : '' }}\n fields={fields}\n groups={groups}\n entityId={E.directory.tenant}\n initialValues={(initial || { id: tenantId, name: '', isActive: true }) as Partial<TenantFormValues>}\n optimisticLockUpdatedAt={initial?.updatedAt}\n isLoading={loading}\n loadingMessage={t('directory.tenants.form.loading', 'Loading tenant\u2026')}\n submitLabel={t('common.save', 'Save')}\n cancelHref=\"/backend/directory/tenants\"\n successRedirect=\"/backend/directory/tenants?flash=Tenant%20updated&type=success\"\n onSubmit={async (values) => {\n const customFields = collectCustomFieldValues(values)\n const payload: {\n id: string\n name: string\n isActive: boolean\n customFields?: Record<string, unknown>\n } = {\n id: values.id || tenantId,\n name: values.name,\n isActive: values.isActive !== false,\n }\n if (Object.keys(customFields).length > 0) {\n payload.customFields = customFields\n }\n await updateCrud('directory/tenants', payload)\n }}\n onDelete={async () => {\n const headers = buildOptimisticLockHeader(initial?.updatedAt)\n const call = await withScopedApiRequestHeaders(headers, () => (\n apiCall(\n `/api/directory/tenants?id=${encodeURIComponent(tenantId)}`,\n { method: 'DELETE' },\n )\n ))\n if (!call.ok) {\n await raiseCrudError(call.response, t('directory.tenants.form.errors.delete', 'Failed to delete tenant'))\n }\n }}\n deleteRedirect=\"/backend/directory/tenants?flash=Tenant%20deleted&type=success\"\n />\n </PageBody>\n </Page>\n )\n}\n"],
|
|
5
|
-
"mappings": ";
|
|
4
|
+
"sourcesContent": ["\"use client\"\nimport * as React from 'react'\nimport { usePathname } from 'next/navigation'\nimport { E } from '#generated/entities.ids.generated'\nimport { Page, PageBody } from '@open-mercato/ui/backend/Page'\nimport { CrudForm, type CrudField, type CrudFormGroup } from '@open-mercato/ui/backend/CrudForm'\nimport { collectCustomFieldValues } from '@open-mercato/ui/backend/utils/customFieldValues'\nimport { updateCrud } from '@open-mercato/ui/backend/utils/crud'\nimport { raiseCrudError } from '@open-mercato/ui/backend/utils/serverErrors'\nimport { readApiResultOrThrow, apiCall, withScopedApiRequestHeaders } from '@open-mercato/ui/backend/utils/apiCall'\nimport { buildOptimisticLockHeader } from '@open-mercato/ui/backend/utils/optimisticLock'\nimport { ErrorMessage, RecordNotFoundState } from '@open-mercato/ui/backend/detail'\nimport { buildRecordInjectionContext, useSetCurrentRecordInjectionContext } from '@open-mercato/ui/backend/injection/recordContext'\nimport { useT } from '@open-mercato/shared/lib/i18n/context'\nimport { extractCustomFieldEntries } from '@open-mercato/shared/lib/crud/custom-fields-client'\n\ntype TenantFormValues = {\n id: string\n name: string\n isActive: boolean\n updatedAt?: string | null\n} & Record<string, unknown>\n\nexport default function EditTenantPage({ params }: { params?: { id?: string } }) {\n const tenantId = params?.id\n const pathname = usePathname()\n const [initial, setInitial] = React.useState<TenantFormValues | null>(null)\n const [loading, setLoading] = React.useState(true)\n const [error, setError] = React.useState<string | null>(null)\n const [isNotFound, setIsNotFound] = React.useState(false)\n const t = useT()\n const fields = React.useMemo<CrudField[]>(() => [\n { id: 'name', label: t('directory.tenants.form.fields.name', 'Name'), type: 'text', required: true },\n { id: 'isActive', label: t('directory.tenants.form.fields.active', 'Active'), type: 'checkbox' },\n ], [t])\n const groups = React.useMemo<CrudFormGroup[]>(() => [\n { id: 'details', title: t('directory.tenants.form.groups.details', 'Details'), column: 1, fields: ['name', 'isActive'] },\n { id: 'custom', title: t('directory.tenants.form.groups.custom', 'Custom Data'), column: 2, kind: 'customFields' },\n ], [t])\n\n React.useEffect(() => {\n let cancelled = false\n async function load() {\n if (!tenantId) return\n setLoading(true)\n setError(null)\n setIsNotFound(false)\n try {\n const data = await readApiResultOrThrow<{ items?: Record<string, unknown>[] }>(\n `/api/directory/tenants?id=${encodeURIComponent(tenantId)}`,\n undefined,\n { errorMessage: t('directory.tenants.form.errors.load', 'Failed to load tenant'), fallback: { items: [] } },\n )\n const rows = Array.isArray(data?.items) ? data.items : []\n const row = rows[0]\n if (!row) {\n if (!cancelled) setIsNotFound(true)\n return\n }\n const cfValues = extractCustomFieldEntries(row as Record<string, unknown>)\n const values: TenantFormValues = {\n id: String(row.id),\n name: String(row.name),\n isActive: !!row.isActive,\n updatedAt: typeof row.updatedAt === 'string'\n ? row.updatedAt\n : typeof row.updated_at === 'string'\n ? row.updated_at\n : null,\n ...cfValues,\n }\n if (!cancelled) setInitial(values)\n } catch (err) {\n if (!cancelled) {\n if ((err as { status?: number }).status === 404) {\n setIsNotFound(true)\n } else {\n const message = err instanceof Error ? err.message : t('directory.tenants.form.errors.load', 'Failed to load tenant')\n setError(message)\n }\n }\n } finally {\n if (!cancelled) setLoading(false)\n }\n }\n load()\n return () => { cancelled = true }\n }, [tenantId, t])\n\n // Publish page-load record context to the AppShell-owned `backend:record:current`\n // mount so the enterprise record_locks widget resolves `directory.tenant` + id\n // explicitly. The resourceKind mirrors the CrudForm `versionHistory` so the held\n // lock matches the save-time conflict surface for the same tenant.\n useSetCurrentRecordInjectionContext(\n buildRecordInjectionContext({\n resourceKind: 'directory.tenant',\n resourceId: tenantId || null,\n updatedAt: initial?.updatedAt ?? null,\n data: initial as Record<string, unknown> | null,\n path: pathname,\n }),\n )\n\n if (!tenantId) return null\n\n if (isNotFound) {\n return (\n <Page>\n <PageBody>\n <RecordNotFoundState\n label={t('directory.tenants.form.errors.notFound', 'Tenant not found')}\n backHref=\"/backend/directory/tenants\"\n backLabel={t('directory.tenants.form.actions.backToList', 'Back to tenants')}\n />\n </PageBody>\n </Page>\n )\n }\n\n if (error && !loading && !initial) {\n return (\n <Page>\n <PageBody>\n <ErrorMessage label={error} />\n </PageBody>\n </Page>\n )\n }\n\n return (\n <Page>\n <PageBody>\n <CrudForm<TenantFormValues>\n title={t('directory.tenants.form.title.edit', 'Edit Tenant')}\n backHref=\"/backend/directory/tenants\"\n versionHistory={{ resourceKind: 'directory.tenant', resourceId: tenantId ? String(tenantId) : '' }}\n fields={fields}\n groups={groups}\n entityId={E.directory.tenant}\n initialValues={(initial || { id: tenantId, name: '', isActive: true }) as Partial<TenantFormValues>}\n optimisticLockUpdatedAt={initial?.updatedAt}\n isLoading={loading}\n loadingMessage={t('directory.tenants.form.loading', 'Loading tenant\u2026')}\n submitLabel={t('common.save', 'Save')}\n cancelHref=\"/backend/directory/tenants\"\n successRedirect=\"/backend/directory/tenants?flash=Tenant%20updated&type=success\"\n onSubmit={async (values) => {\n const customFields = collectCustomFieldValues(values)\n const payload: {\n id: string\n name: string\n isActive: boolean\n customFields?: Record<string, unknown>\n } = {\n id: values.id || tenantId,\n name: values.name,\n isActive: values.isActive !== false,\n }\n if (Object.keys(customFields).length > 0) {\n payload.customFields = customFields\n }\n await updateCrud('directory/tenants', payload)\n }}\n onDelete={async () => {\n const headers = buildOptimisticLockHeader(initial?.updatedAt)\n const call = await withScopedApiRequestHeaders(headers, () => (\n apiCall(\n `/api/directory/tenants?id=${encodeURIComponent(tenantId)}`,\n { method: 'DELETE' },\n )\n ))\n if (!call.ok) {\n await raiseCrudError(call.response, t('directory.tenants.form.errors.delete', 'Failed to delete tenant'))\n }\n }}\n deleteRedirect=\"/backend/directory/tenants?flash=Tenant%20deleted&type=success\"\n />\n </PageBody>\n </Page>\n )\n}\n"],
|
|
5
|
+
"mappings": ";AA6GU;AA5GV,YAAY,WAAW;AACvB,SAAS,mBAAmB;AAC5B,SAAS,SAAS;AAClB,SAAS,MAAM,gBAAgB;AAC/B,SAAS,gBAAoD;AAC7D,SAAS,gCAAgC;AACzC,SAAS,kBAAkB;AAC3B,SAAS,sBAAsB;AAC/B,SAAS,sBAAsB,SAAS,mCAAmC;AAC3E,SAAS,iCAAiC;AAC1C,SAAS,cAAc,2BAA2B;AAClD,SAAS,6BAA6B,2CAA2C;AACjF,SAAS,YAAY;AACrB,SAAS,iCAAiC;AAS3B,SAAR,eAAgC,EAAE,OAAO,GAAiC;AAC/E,QAAM,WAAW,QAAQ;AACzB,QAAM,WAAW,YAAY;AAC7B,QAAM,CAAC,SAAS,UAAU,IAAI,MAAM,SAAkC,IAAI;AAC1E,QAAM,CAAC,SAAS,UAAU,IAAI,MAAM,SAAS,IAAI;AACjD,QAAM,CAAC,OAAO,QAAQ,IAAI,MAAM,SAAwB,IAAI;AAC5D,QAAM,CAAC,YAAY,aAAa,IAAI,MAAM,SAAS,KAAK;AACxD,QAAM,IAAI,KAAK;AACf,QAAM,SAAS,MAAM,QAAqB,MAAM;AAAA,IAC9C,EAAE,IAAI,QAAQ,OAAO,EAAE,sCAAsC,MAAM,GAAG,MAAM,QAAQ,UAAU,KAAK;AAAA,IACnG,EAAE,IAAI,YAAY,OAAO,EAAE,wCAAwC,QAAQ,GAAG,MAAM,WAAW;AAAA,EACjG,GAAG,CAAC,CAAC,CAAC;AACN,QAAM,SAAS,MAAM,QAAyB,MAAM;AAAA,IAClD,EAAE,IAAI,WAAW,OAAO,EAAE,yCAAyC,SAAS,GAAG,QAAQ,GAAG,QAAQ,CAAC,QAAQ,UAAU,EAAE;AAAA,IACvH,EAAE,IAAI,UAAU,OAAO,EAAE,wCAAwC,aAAa,GAAG,QAAQ,GAAG,MAAM,eAAe;AAAA,EACnH,GAAG,CAAC,CAAC,CAAC;AAEN,QAAM,UAAU,MAAM;AACpB,QAAI,YAAY;AAChB,mBAAe,OAAO;AACpB,UAAI,CAAC,SAAU;AACf,iBAAW,IAAI;AACf,eAAS,IAAI;AACb,oBAAc,KAAK;AACnB,UAAI;AACF,cAAM,OAAO,MAAM;AAAA,UACjB,6BAA6B,mBAAmB,QAAQ,CAAC;AAAA,UACzD;AAAA,UACA,EAAE,cAAc,EAAE,sCAAsC,uBAAuB,GAAG,UAAU,EAAE,OAAO,CAAC,EAAE,EAAE;AAAA,QAC5G;AACA,cAAM,OAAO,MAAM,QAAQ,MAAM,KAAK,IAAI,KAAK,QAAQ,CAAC;AACxD,cAAM,MAAM,KAAK,CAAC;AAClB,YAAI,CAAC,KAAK;AACR,cAAI,CAAC,UAAW,eAAc,IAAI;AAClC;AAAA,QACF;AACA,cAAM,WAAW,0BAA0B,GAA8B;AACzE,cAAM,SAA2B;AAAA,UAC/B,IAAI,OAAO,IAAI,EAAE;AAAA,UACjB,MAAM,OAAO,IAAI,IAAI;AAAA,UACrB,UAAU,CAAC,CAAC,IAAI;AAAA,UAChB,WAAW,OAAO,IAAI,cAAc,WAChC,IAAI,YACJ,OAAO,IAAI,eAAe,WACxB,IAAI,aACJ;AAAA,UACN,GAAG;AAAA,QACL;AACA,YAAI,CAAC,UAAW,YAAW,MAAM;AAAA,MACnC,SAAS,KAAK;AACZ,YAAI,CAAC,WAAW;AACd,cAAK,IAA4B,WAAW,KAAK;AAC/C,0BAAc,IAAI;AAAA,UACpB,OAAO;AACL,kBAAM,UAAU,eAAe,QAAQ,IAAI,UAAU,EAAE,sCAAsC,uBAAuB;AACpH,qBAAS,OAAO;AAAA,UAClB;AAAA,QACF;AAAA,MACF,UAAE;AACA,YAAI,CAAC,UAAW,YAAW,KAAK;AAAA,MAClC;AAAA,IACF;AACA,SAAK;AACL,WAAO,MAAM;AAAE,kBAAY;AAAA,IAAK;AAAA,EAClC,GAAG,CAAC,UAAU,CAAC,CAAC;AAMhB;AAAA,IACE,4BAA4B;AAAA,MAC1B,cAAc;AAAA,MACd,YAAY,YAAY;AAAA,MACxB,WAAW,SAAS,aAAa;AAAA,MACjC,MAAM;AAAA,MACN,MAAM;AAAA,IACR,CAAC;AAAA,EACH;AAEA,MAAI,CAAC,SAAU,QAAO;AAEtB,MAAI,YAAY;AACd,WACE,oBAAC,QACC,8BAAC,YACC;AAAA,MAAC;AAAA;AAAA,QACC,OAAO,EAAE,0CAA0C,kBAAkB;AAAA,QACrE,UAAS;AAAA,QACT,WAAW,EAAE,6CAA6C,iBAAiB;AAAA;AAAA,IAC7E,GACF,GACF;AAAA,EAEJ;AAEA,MAAI,SAAS,CAAC,WAAW,CAAC,SAAS;AACjC,WACE,oBAAC,QACC,8BAAC,YACC,8BAAC,gBAAa,OAAO,OAAO,GAC9B,GACF;AAAA,EAEJ;AAEA,SACE,oBAAC,QACC,8BAAC,YACC;AAAA,IAAC;AAAA;AAAA,MACC,OAAO,EAAE,qCAAqC,aAAa;AAAA,MAC3D,UAAS;AAAA,MACT,gBAAgB,EAAE,cAAc,oBAAoB,YAAY,WAAW,OAAO,QAAQ,IAAI,GAAG;AAAA,MACjG;AAAA,MACA;AAAA,MACA,UAAU,EAAE,UAAU;AAAA,MACtB,eAAgB,WAAW,EAAE,IAAI,UAAU,MAAM,IAAI,UAAU,KAAK;AAAA,MACpE,yBAAyB,SAAS;AAAA,MAClC,WAAW;AAAA,MACX,gBAAgB,EAAE,kCAAkC,sBAAiB;AAAA,MACrE,aAAa,EAAE,eAAe,MAAM;AAAA,MACpC,YAAW;AAAA,MACX,iBAAgB;AAAA,MAChB,UAAU,OAAO,WAAW;AAC1B,cAAM,eAAe,yBAAyB,MAAM;AACpD,cAAM,UAKF;AAAA,UACF,IAAI,OAAO,MAAM;AAAA,UACjB,MAAM,OAAO;AAAA,UACb,UAAU,OAAO,aAAa;AAAA,QAChC;AACA,YAAI,OAAO,KAAK,YAAY,EAAE,SAAS,GAAG;AACxC,kBAAQ,eAAe;AAAA,QACzB;AACA,cAAM,WAAW,qBAAqB,OAAO;AAAA,MAC/C;AAAA,MACA,UAAU,YAAY;AACpB,cAAM,UAAU,0BAA0B,SAAS,SAAS;AAC5D,cAAM,OAAO,MAAM,4BAA4B,SAAS,MACtD;AAAA,UACE,6BAA6B,mBAAmB,QAAQ,CAAC;AAAA,UACzD,EAAE,QAAQ,SAAS;AAAA,QACrB,CACD;AACD,YAAI,CAAC,KAAK,IAAI;AACZ,gBAAM,eAAe,KAAK,UAAU,EAAE,wCAAwC,yBAAyB,CAAC;AAAA,QAC1G;AAAA,MACF;AAAA,MACA,gBAAe;AAAA;AAAA,EACjB,GACF,GACF;AAEJ;",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
|
@@ -7,7 +7,7 @@ import { getEntityIds } from "@open-mercato/shared/lib/encryption/entityIds";
|
|
|
7
7
|
import { upsertCustomEntitySchema } from "@open-mercato/core/modules/entities/data/validators";
|
|
8
8
|
import { isSystemEntitySelectable } from "@open-mercato/shared/lib/entities/system-entities";
|
|
9
9
|
import { SYSTEM_ENTITY_RECORDS_BLOCKED_CODE, isOrmBackedSystemEntityId } from "@open-mercato/shared/lib/data/engine";
|
|
10
|
-
import {
|
|
10
|
+
import { enforceCommandOptimisticLockWithGuards } from "@open-mercato/shared/lib/crud/optimistic-lock-command";
|
|
11
11
|
import { isCrudHttpError } from "@open-mercato/shared/lib/crud/errors";
|
|
12
12
|
import {
|
|
13
13
|
beginEntitiesMutationGuard,
|
|
@@ -110,7 +110,7 @@ async function POST(req) {
|
|
|
110
110
|
let ent = await em.findOne(CustomEntity, where);
|
|
111
111
|
if (ent) {
|
|
112
112
|
try {
|
|
113
|
-
|
|
113
|
+
await enforceCommandOptimisticLockWithGuards(container, {
|
|
114
114
|
resourceKind: CUSTOM_ENTITY_DEFINITION_RESOURCE_KIND,
|
|
115
115
|
resourceId: ent.id,
|
|
116
116
|
current: ent.updatedAt ?? null,
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../../../src/modules/entities/api/entities.ts"],
|
|
4
|
-
"sourcesContent": ["import { NextResponse } from 'next/server'\nimport { z } from 'zod'\nimport { createRequestContainer } from '@open-mercato/shared/lib/di/container'\nimport { getAuthFromRequest } from '@open-mercato/shared/lib/auth/server'\nimport { CustomEntity, CustomFieldDef } from '@open-mercato/core/modules/entities/data/entities'\nimport { getEntityIds } from '@open-mercato/shared/lib/encryption/entityIds'\nimport { upsertCustomEntitySchema } from '@open-mercato/core/modules/entities/data/validators'\nimport type { OpenApiRouteDoc } from '@open-mercato/shared/lib/openapi'\nimport { isSystemEntitySelectable } from '@open-mercato/shared/lib/entities/system-entities'\nimport { SYSTEM_ENTITY_RECORDS_BLOCKED_CODE, isOrmBackedSystemEntityId } from '@open-mercato/shared/lib/data/engine'\nimport { enforceCommandOptimisticLock } from '@open-mercato/shared/lib/crud/optimistic-lock-command'\nimport { isCrudHttpError } from '@open-mercato/shared/lib/crud/errors'\nimport {\n beginEntitiesMutationGuard,\n ENTITY_DEFINITION_RESOURCE_KIND,\n} from './definitions.mutation-guard'\n\nconst CUSTOM_ENTITY_DEFINITION_RESOURCE_KIND = ENTITY_DEFINITION_RESOURCE_KIND\n\nexport const metadata = {\n GET: { requireAuth: true },\n POST: { requireAuth: true, requireFeatures: ['entities.definitions.manage'] },\n DELETE: { requireAuth: true, requireFeatures: ['entities.definitions.manage'] },\n}\n\nexport async function GET(req: Request) {\n const auth = await getAuthFromRequest(req)\n if (!auth || !auth.tenantId || (!auth.orgId && !auth.isSuperAdmin)) return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })\n\n const { resolve } = await createRequestContainer()\n const em = resolve('em') as any\n\n // Generated entities from code\n const AllEntities = getEntityIds()\n const generated: { entityId: string; source: 'code'; label: string }[] = []\n for (const modId of Object.keys(AllEntities)) {\n const entities = (AllEntities as any)[modId] as Record<string, string>\n for (const k of Object.keys(entities)) {\n const id = entities[k]\n if (!isSystemEntitySelectable(id)) continue\n generated.push({ entityId: id, source: 'code', label: id })\n }\n }\n\n // Custom user-defined entities (global/org/tenant scoped)\n const where: any = { isActive: true }\n where.$and = [\n { $or: [ { organizationId: auth.orgId ?? undefined as any }, { organizationId: null } ] },\n { $or: [ { tenantId: auth.tenantId ?? undefined as any }, { tenantId: null } ] },\n ]\n const customs = await em.find(CustomEntity as any, where as any, { orderBy: { entityId: 'asc' } as any })\n // Resolve overlay precedence: prefer organization/tenant-specific over global\n const customByEntityId = new Map<string, any>()\n for (const c of customs as any[]) {\n const specificity = (c.organizationId ? 2 : 0) + (c.tenantId ? 1 : 0)\n const prev = customByEntityId.get(c.entityId)\n const prevSpec = prev ? ((prev.organizationId ? 2 : 0) + (prev.tenantId ? 1 : 0)) : -1\n if (!prev || specificity > prevSpec) customByEntityId.set(c.entityId, c)\n }\n\n const custom = Array.from(customByEntityId.values())\n .filter((c) => isSystemEntitySelectable(c.entityId))\n .map((c) => ({\n entityId: c.entityId,\n source: 'custom' as const,\n label: c.label,\n description: c.description ?? undefined,\n labelField: (c as any).labelField ?? undefined,\n defaultEditor: (c as any).defaultEditor ?? undefined,\n showInSidebar: (c as any).showInSidebar ?? false,\n updatedAt: c.updatedAt instanceof Date ? c.updatedAt.toISOString() : (c.updatedAt ?? undefined),\n }))\n\n const byId = new Map<string, any>()\n for (const g of generated) byId.set(g.entityId, g)\n for (const cu of custom) {\n const existing = byId.get(cu.entityId)\n byId.set(cu.entityId, { ...existing, ...cu, source: existing?.source ?? cu.source })\n }\n\n // Count field definitions scoped to current tenant/org (same scoping as custom entities)\n const defsWhere: any = { isActive: true }\n defsWhere.$and = [\n //{ $or: [ { organizationId: auth.orgId ?? undefined as any }, { organizationId: null } ] }, // the entities and custom fields are defined per tenant\n { tenantId: auth.tenantId ?? undefined as any },\n ]\n const defs = await em.find(CustomFieldDef as any, defsWhere as any)\n // Count distinct field names (keys) per entityId\n const keySets = new Map<string, Set<string>>()\n for (const d of defs as any[]) {\n const eid = String(d.entityId)\n const k = String(d.key)\n if (!isSystemEntitySelectable(eid)) continue\n const set = keySets.get(eid) || new Set<string>()\n set.add(k)\n keySets.set(eid, set)\n }\n const counts: Record<string, number> = {}\n for (const [eid, set] of keySets.entries()) counts[eid] = set.size\n\n const items = Array.from(byId.values()).map((it: any) => ({ ...it, count: counts[it.entityId] || 0 }))\n return NextResponse.json({ items })\n}\n\nexport async function POST(req: Request) {\n const auth = await getAuthFromRequest(req)\n if (!auth || !auth.orgId) return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })\n\n let body: any\n try { body = await req.json() } catch { return NextResponse.json({ error: 'Invalid JSON' }, { status: 400 }) }\n const parsed = upsertCustomEntitySchema.safeParse(body)\n if (!parsed.success) {\n return NextResponse.json({ error: 'Validation failed', details: parsed.error.flatten() }, { status: 400 })\n }\n const input = parsed.data\n\n const container = await createRequestContainer()\n const { resolve } = container\n const em = resolve('em') as any\n\n // A registration for a module-declared, table-backed system entity would flip\n // query-engine classification to doc storage for the whole entity type (#2939's\n // failure mode via another door) \u2014 refuse to create one.\n if (isOrmBackedSystemEntityId(em, input.entityId)) {\n return NextResponse.json(\n { error: 'System entities cannot be registered as custom entities', code: SYSTEM_ENTITY_RECORDS_BLOCKED_CODE, entityId: input.entityId },\n { status: 400 },\n )\n }\n\n const where: any = { entityId: input.entityId, organizationId: auth.orgId ?? null, tenantId: auth.tenantId ?? null }\n let ent = await em.findOne(CustomEntity, where)\n if (ent) {\n try {\n enforceCommandOptimisticLock({\n resourceKind: CUSTOM_ENTITY_DEFINITION_RESOURCE_KIND,\n resourceId: ent.id,\n current: ent.updatedAt ?? null,\n request: req,\n })\n } catch (lockError) {\n if (isCrudHttpError(lockError)) {\n return NextResponse.json(lockError.body, { status: lockError.status })\n }\n throw lockError\n }\n }\n\n const guard = await beginEntitiesMutationGuard({\n container,\n auth,\n req,\n resourceKind: ENTITY_DEFINITION_RESOURCE_KIND,\n resourceId: ent ? ent.id : input.entityId,\n operation: ent ? 'update' : 'create',\n mutationPayload: input as unknown as Record<string, unknown>,\n })\n if (guard.blockedResponse) return guard.blockedResponse\n\n if (!ent) ent = em.create(CustomEntity, { ...where, createdAt: new Date() })\n ent.label = input.label\n ent.description = input.description ?? null\n ent.isActive = input.isActive ?? true\n ent.labelField = input.labelField ?? ent.labelField ?? null\n ent.defaultEditor = input.defaultEditor ?? ent.defaultEditor ?? null\n ent.showInSidebar = input.showInSidebar ?? ent.showInSidebar ?? false\n ent.updatedAt = new Date()\n em.persist(ent)\n await em.flush()\n await guard.runAfterSuccess()\n // Invalidate sidebar/nav cache for tenant scope (also when tenantId is null)\n try {\n const cache = (await createRequestContainer()).resolve('cache') as any\n if (cache) {\n await cache.deleteByTags([`nav:entities:${auth.tenantId || 'null'}`])\n }\n } catch {}\n return NextResponse.json({ ok: true, item: { id: ent.id, entityId: ent.entityId, label: ent.label, description: ent.description ?? undefined } })\n}\n\nexport async function DELETE(req: Request) {\n const auth = await getAuthFromRequest(req)\n if (!auth || !auth.orgId) return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })\n let body: any\n try { body = await req.json() } catch { return NextResponse.json({ error: 'Invalid JSON' }, { status: 400 }) }\n const entityId = body?.entityId\n if (!entityId) return NextResponse.json({ error: 'entityId is required' }, { status: 400 })\n\n const container = await createRequestContainer()\n const { resolve } = container\n const em = resolve('em') as any\n\n const where: any = { entityId, organizationId: auth.orgId ?? null, tenantId: auth.tenantId ?? null }\n const ent = await em.findOne(CustomEntity, where)\n if (!ent) return NextResponse.json({ error: 'Not found' }, { status: 404 })\n\n const guard = await beginEntitiesMutationGuard({\n container,\n auth,\n req,\n resourceKind: ENTITY_DEFINITION_RESOURCE_KIND,\n resourceId: ent.id,\n operation: 'delete',\n mutationPayload: { entityId },\n })\n if (guard.blockedResponse) return guard.blockedResponse\n\n ent.isActive = false\n ent.updatedAt = new Date()\n ent.deletedAt = ent.deletedAt ?? new Date()\n em.persist(ent)\n await em.flush()\n await guard.runAfterSuccess()\n // Invalidate sidebar/nav cache for tenant scope (also when tenantId is null)\n try {\n const cache = (await createRequestContainer()).resolve('cache') as any\n if (cache) {\n await cache.deleteByTags([`nav:entities:${auth.tenantId || 'null'}`])\n }\n } catch {}\n return NextResponse.json({ ok: true })\n}\n\nconst entitySummarySchema = z.object({\n entityId: z.string(),\n source: z.enum(['code', 'custom']),\n label: z.string(),\n description: z.string().optional(),\n labelField: z.string().optional(),\n defaultEditor: z.string().optional(),\n showInSidebar: z.boolean().optional(),\n updatedAt: z.string().optional(),\n count: z.number(),\n})\n\nconst entityListResponseSchema = z.object({\n items: z.array(entitySummarySchema),\n})\n\nconst deleteEntityRequestSchema = z.object({\n entityId: z.string(),\n})\n\nconst upsertCustomEntityResponseSchema = z.object({\n ok: z.literal(true),\n item: z.object({\n id: z.string().uuid(),\n entityId: z.string(),\n label: z.string(),\n description: z.string().optional(),\n }),\n})\n\nexport const openApi: OpenApiRouteDoc = {\n tag: 'Entities',\n summary: 'Manage custom entities',\n methods: {\n GET: {\n summary: 'List available entities',\n description: 'Returns generated and custom entities scoped to the caller with field counts per entity.',\n responses: [\n {\n status: 200,\n description: 'List of entities',\n schema: entityListResponseSchema,\n },\n {\n status: 401,\n description: 'Missing authentication',\n schema: z.object({ error: z.string() }),\n },\n ],\n },\n POST: {\n summary: 'Upsert custom entity',\n description: 'Creates or updates a tenant/org scoped custom entity definition.',\n requestBody: {\n contentType: 'application/json',\n schema: upsertCustomEntitySchema,\n },\n responses: [\n {\n status: 200,\n description: 'Entity saved',\n schema: upsertCustomEntityResponseSchema,\n },\n {\n status: 400,\n description: 'Validation error',\n schema: z.object({\n error: z.string(),\n details: z.any().optional(),\n }),\n },\n {\n status: 401,\n description: 'Missing authentication',\n schema: z.object({ error: z.string() }),\n },\n ],\n },\n DELETE: {\n summary: 'Soft delete custom entity',\n description: 'Marks the specified custom entity inactive within the current scope.',\n requestBody: {\n contentType: 'application/json',\n schema: deleteEntityRequestSchema,\n },\n responses: [\n {\n status: 200,\n description: 'Entity deleted',\n schema: z.object({ ok: z.boolean() }),\n },\n {\n status: 400,\n description: 'Missing entity id',\n schema: z.object({ error: z.string() }),\n },\n {\n status: 401,\n description: 'Missing authentication',\n schema: z.object({ error: z.string() }),\n },\n {\n status: 404,\n description: 'Entity not found in scope',\n schema: z.object({ error: z.string() }),\n },\n ],\n },\n },\n}\n"],
|
|
5
|
-
"mappings": "AAAA,SAAS,oBAAoB;AAC7B,SAAS,SAAS;AAClB,SAAS,8BAA8B;AACvC,SAAS,0BAA0B;AACnC,SAAS,cAAc,sBAAsB;AAC7C,SAAS,oBAAoB;AAC7B,SAAS,gCAAgC;AAEzC,SAAS,gCAAgC;AACzC,SAAS,oCAAoC,iCAAiC;AAC9E,SAAS,
|
|
4
|
+
"sourcesContent": ["import { NextResponse } from 'next/server'\nimport { z } from 'zod'\nimport { createRequestContainer } from '@open-mercato/shared/lib/di/container'\nimport { getAuthFromRequest } from '@open-mercato/shared/lib/auth/server'\nimport { CustomEntity, CustomFieldDef } from '@open-mercato/core/modules/entities/data/entities'\nimport { getEntityIds } from '@open-mercato/shared/lib/encryption/entityIds'\nimport { upsertCustomEntitySchema } from '@open-mercato/core/modules/entities/data/validators'\nimport type { OpenApiRouteDoc } from '@open-mercato/shared/lib/openapi'\nimport { isSystemEntitySelectable } from '@open-mercato/shared/lib/entities/system-entities'\nimport { SYSTEM_ENTITY_RECORDS_BLOCKED_CODE, isOrmBackedSystemEntityId } from '@open-mercato/shared/lib/data/engine'\nimport { enforceCommandOptimisticLockWithGuards } from '@open-mercato/shared/lib/crud/optimistic-lock-command'\nimport { isCrudHttpError } from '@open-mercato/shared/lib/crud/errors'\nimport {\n beginEntitiesMutationGuard,\n ENTITY_DEFINITION_RESOURCE_KIND,\n} from './definitions.mutation-guard'\n\nconst CUSTOM_ENTITY_DEFINITION_RESOURCE_KIND = ENTITY_DEFINITION_RESOURCE_KIND\n\nexport const metadata = {\n GET: { requireAuth: true },\n POST: { requireAuth: true, requireFeatures: ['entities.definitions.manage'] },\n DELETE: { requireAuth: true, requireFeatures: ['entities.definitions.manage'] },\n}\n\nexport async function GET(req: Request) {\n const auth = await getAuthFromRequest(req)\n if (!auth || !auth.tenantId || (!auth.orgId && !auth.isSuperAdmin)) return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })\n\n const { resolve } = await createRequestContainer()\n const em = resolve('em') as any\n\n // Generated entities from code\n const AllEntities = getEntityIds()\n const generated: { entityId: string; source: 'code'; label: string }[] = []\n for (const modId of Object.keys(AllEntities)) {\n const entities = (AllEntities as any)[modId] as Record<string, string>\n for (const k of Object.keys(entities)) {\n const id = entities[k]\n if (!isSystemEntitySelectable(id)) continue\n generated.push({ entityId: id, source: 'code', label: id })\n }\n }\n\n // Custom user-defined entities (global/org/tenant scoped)\n const where: any = { isActive: true }\n where.$and = [\n { $or: [ { organizationId: auth.orgId ?? undefined as any }, { organizationId: null } ] },\n { $or: [ { tenantId: auth.tenantId ?? undefined as any }, { tenantId: null } ] },\n ]\n const customs = await em.find(CustomEntity as any, where as any, { orderBy: { entityId: 'asc' } as any })\n // Resolve overlay precedence: prefer organization/tenant-specific over global\n const customByEntityId = new Map<string, any>()\n for (const c of customs as any[]) {\n const specificity = (c.organizationId ? 2 : 0) + (c.tenantId ? 1 : 0)\n const prev = customByEntityId.get(c.entityId)\n const prevSpec = prev ? ((prev.organizationId ? 2 : 0) + (prev.tenantId ? 1 : 0)) : -1\n if (!prev || specificity > prevSpec) customByEntityId.set(c.entityId, c)\n }\n\n const custom = Array.from(customByEntityId.values())\n .filter((c) => isSystemEntitySelectable(c.entityId))\n .map((c) => ({\n entityId: c.entityId,\n source: 'custom' as const,\n label: c.label,\n description: c.description ?? undefined,\n labelField: (c as any).labelField ?? undefined,\n defaultEditor: (c as any).defaultEditor ?? undefined,\n showInSidebar: (c as any).showInSidebar ?? false,\n updatedAt: c.updatedAt instanceof Date ? c.updatedAt.toISOString() : (c.updatedAt ?? undefined),\n }))\n\n const byId = new Map<string, any>()\n for (const g of generated) byId.set(g.entityId, g)\n for (const cu of custom) {\n const existing = byId.get(cu.entityId)\n byId.set(cu.entityId, { ...existing, ...cu, source: existing?.source ?? cu.source })\n }\n\n // Count field definitions scoped to current tenant/org (same scoping as custom entities)\n const defsWhere: any = { isActive: true }\n defsWhere.$and = [\n //{ $or: [ { organizationId: auth.orgId ?? undefined as any }, { organizationId: null } ] }, // the entities and custom fields are defined per tenant\n { tenantId: auth.tenantId ?? undefined as any },\n ]\n const defs = await em.find(CustomFieldDef as any, defsWhere as any)\n // Count distinct field names (keys) per entityId\n const keySets = new Map<string, Set<string>>()\n for (const d of defs as any[]) {\n const eid = String(d.entityId)\n const k = String(d.key)\n if (!isSystemEntitySelectable(eid)) continue\n const set = keySets.get(eid) || new Set<string>()\n set.add(k)\n keySets.set(eid, set)\n }\n const counts: Record<string, number> = {}\n for (const [eid, set] of keySets.entries()) counts[eid] = set.size\n\n const items = Array.from(byId.values()).map((it: any) => ({ ...it, count: counts[it.entityId] || 0 }))\n return NextResponse.json({ items })\n}\n\nexport async function POST(req: Request) {\n const auth = await getAuthFromRequest(req)\n if (!auth || !auth.orgId) return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })\n\n let body: any\n try { body = await req.json() } catch { return NextResponse.json({ error: 'Invalid JSON' }, { status: 400 }) }\n const parsed = upsertCustomEntitySchema.safeParse(body)\n if (!parsed.success) {\n return NextResponse.json({ error: 'Validation failed', details: parsed.error.flatten() }, { status: 400 })\n }\n const input = parsed.data\n\n const container = await createRequestContainer()\n const { resolve } = container\n const em = resolve('em') as any\n\n // A registration for a module-declared, table-backed system entity would flip\n // query-engine classification to doc storage for the whole entity type (#2939's\n // failure mode via another door) \u2014 refuse to create one.\n if (isOrmBackedSystemEntityId(em, input.entityId)) {\n return NextResponse.json(\n { error: 'System entities cannot be registered as custom entities', code: SYSTEM_ENTITY_RECORDS_BLOCKED_CODE, entityId: input.entityId },\n { status: 400 },\n )\n }\n\n const where: any = { entityId: input.entityId, organizationId: auth.orgId ?? null, tenantId: auth.tenantId ?? null }\n let ent = await em.findOne(CustomEntity, where)\n if (ent) {\n try {\n await enforceCommandOptimisticLockWithGuards(container, {\n resourceKind: CUSTOM_ENTITY_DEFINITION_RESOURCE_KIND,\n resourceId: ent.id,\n current: ent.updatedAt ?? null,\n request: req,\n })\n } catch (lockError) {\n if (isCrudHttpError(lockError)) {\n return NextResponse.json(lockError.body, { status: lockError.status })\n }\n throw lockError\n }\n }\n\n const guard = await beginEntitiesMutationGuard({\n container,\n auth,\n req,\n resourceKind: ENTITY_DEFINITION_RESOURCE_KIND,\n resourceId: ent ? ent.id : input.entityId,\n operation: ent ? 'update' : 'create',\n mutationPayload: input as unknown as Record<string, unknown>,\n })\n if (guard.blockedResponse) return guard.blockedResponse\n\n if (!ent) ent = em.create(CustomEntity, { ...where, createdAt: new Date() })\n ent.label = input.label\n ent.description = input.description ?? null\n ent.isActive = input.isActive ?? true\n ent.labelField = input.labelField ?? ent.labelField ?? null\n ent.defaultEditor = input.defaultEditor ?? ent.defaultEditor ?? null\n ent.showInSidebar = input.showInSidebar ?? ent.showInSidebar ?? false\n ent.updatedAt = new Date()\n em.persist(ent)\n await em.flush()\n await guard.runAfterSuccess()\n // Invalidate sidebar/nav cache for tenant scope (also when tenantId is null)\n try {\n const cache = (await createRequestContainer()).resolve('cache') as any\n if (cache) {\n await cache.deleteByTags([`nav:entities:${auth.tenantId || 'null'}`])\n }\n } catch {}\n return NextResponse.json({ ok: true, item: { id: ent.id, entityId: ent.entityId, label: ent.label, description: ent.description ?? undefined } })\n}\n\nexport async function DELETE(req: Request) {\n const auth = await getAuthFromRequest(req)\n if (!auth || !auth.orgId) return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })\n let body: any\n try { body = await req.json() } catch { return NextResponse.json({ error: 'Invalid JSON' }, { status: 400 }) }\n const entityId = body?.entityId\n if (!entityId) return NextResponse.json({ error: 'entityId is required' }, { status: 400 })\n\n const container = await createRequestContainer()\n const { resolve } = container\n const em = resolve('em') as any\n\n const where: any = { entityId, organizationId: auth.orgId ?? null, tenantId: auth.tenantId ?? null }\n const ent = await em.findOne(CustomEntity, where)\n if (!ent) return NextResponse.json({ error: 'Not found' }, { status: 404 })\n\n const guard = await beginEntitiesMutationGuard({\n container,\n auth,\n req,\n resourceKind: ENTITY_DEFINITION_RESOURCE_KIND,\n resourceId: ent.id,\n operation: 'delete',\n mutationPayload: { entityId },\n })\n if (guard.blockedResponse) return guard.blockedResponse\n\n ent.isActive = false\n ent.updatedAt = new Date()\n ent.deletedAt = ent.deletedAt ?? new Date()\n em.persist(ent)\n await em.flush()\n await guard.runAfterSuccess()\n // Invalidate sidebar/nav cache for tenant scope (also when tenantId is null)\n try {\n const cache = (await createRequestContainer()).resolve('cache') as any\n if (cache) {\n await cache.deleteByTags([`nav:entities:${auth.tenantId || 'null'}`])\n }\n } catch {}\n return NextResponse.json({ ok: true })\n}\n\nconst entitySummarySchema = z.object({\n entityId: z.string(),\n source: z.enum(['code', 'custom']),\n label: z.string(),\n description: z.string().optional(),\n labelField: z.string().optional(),\n defaultEditor: z.string().optional(),\n showInSidebar: z.boolean().optional(),\n updatedAt: z.string().optional(),\n count: z.number(),\n})\n\nconst entityListResponseSchema = z.object({\n items: z.array(entitySummarySchema),\n})\n\nconst deleteEntityRequestSchema = z.object({\n entityId: z.string(),\n})\n\nconst upsertCustomEntityResponseSchema = z.object({\n ok: z.literal(true),\n item: z.object({\n id: z.string().uuid(),\n entityId: z.string(),\n label: z.string(),\n description: z.string().optional(),\n }),\n})\n\nexport const openApi: OpenApiRouteDoc = {\n tag: 'Entities',\n summary: 'Manage custom entities',\n methods: {\n GET: {\n summary: 'List available entities',\n description: 'Returns generated and custom entities scoped to the caller with field counts per entity.',\n responses: [\n {\n status: 200,\n description: 'List of entities',\n schema: entityListResponseSchema,\n },\n {\n status: 401,\n description: 'Missing authentication',\n schema: z.object({ error: z.string() }),\n },\n ],\n },\n POST: {\n summary: 'Upsert custom entity',\n description: 'Creates or updates a tenant/org scoped custom entity definition.',\n requestBody: {\n contentType: 'application/json',\n schema: upsertCustomEntitySchema,\n },\n responses: [\n {\n status: 200,\n description: 'Entity saved',\n schema: upsertCustomEntityResponseSchema,\n },\n {\n status: 400,\n description: 'Validation error',\n schema: z.object({\n error: z.string(),\n details: z.any().optional(),\n }),\n },\n {\n status: 401,\n description: 'Missing authentication',\n schema: z.object({ error: z.string() }),\n },\n ],\n },\n DELETE: {\n summary: 'Soft delete custom entity',\n description: 'Marks the specified custom entity inactive within the current scope.',\n requestBody: {\n contentType: 'application/json',\n schema: deleteEntityRequestSchema,\n },\n responses: [\n {\n status: 200,\n description: 'Entity deleted',\n schema: z.object({ ok: z.boolean() }),\n },\n {\n status: 400,\n description: 'Missing entity id',\n schema: z.object({ error: z.string() }),\n },\n {\n status: 401,\n description: 'Missing authentication',\n schema: z.object({ error: z.string() }),\n },\n {\n status: 404,\n description: 'Entity not found in scope',\n schema: z.object({ error: z.string() }),\n },\n ],\n },\n },\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|