@open-mercato/core 0.6.6 → 0.6.7-develop.6552.1.5208a19369
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.turbo/turbo-build.log +2 -2
- package/dist/generated/entities/gateway_session_initialization/index.js +23 -0
- package/dist/generated/entities/gateway_session_initialization/index.js.map +7 -0
- package/dist/generated/entities.ids.generated.js +1 -0
- package/dist/generated/entities.ids.generated.js.map +2 -2
- package/dist/generated/entity-fields-registry.js +12 -0
- package/dist/generated/entity-fields-registry.js.map +2 -2
- package/dist/modules/api_keys/api/keys/route.js +30 -13
- package/dist/modules/api_keys/api/keys/route.js.map +2 -2
- package/dist/modules/api_keys/cli.js +2 -1
- package/dist/modules/api_keys/cli.js.map +2 -2
- package/dist/modules/attachments/api/file/[id]/route.js +9 -7
- package/dist/modules/attachments/api/file/[id]/route.js.map +2 -2
- package/dist/modules/attachments/api/image/[id]/[[...slug]]/route.js +9 -7
- package/dist/modules/attachments/api/image/[id]/[[...slug]]/route.js.map +2 -2
- package/dist/modules/attachments/api/route.js +21 -17
- package/dist/modules/attachments/api/route.js.map +2 -2
- package/dist/modules/attachments/components/AttachmentContentPreview.js +6 -35
- package/dist/modules/attachments/components/AttachmentContentPreview.js.map +2 -2
- package/dist/modules/attachments/lib/reconcileOrganization.js +110 -0
- package/dist/modules/attachments/lib/reconcileOrganization.js.map +7 -0
- package/dist/modules/attachments/lib/requestScope.js +10 -0
- package/dist/modules/attachments/lib/requestScope.js.map +7 -0
- package/dist/modules/audit_logs/backend/audit-logs/page.js +14 -24
- package/dist/modules/audit_logs/backend/audit-logs/page.js.map +2 -2
- package/dist/modules/auth/api/autologin.js +85 -0
- package/dist/modules/auth/api/autologin.js.map +7 -0
- package/dist/modules/auth/api/users/route.js +6 -2
- package/dist/modules/auth/api/users/route.js.map +2 -2
- package/dist/modules/auth/cli.js +3 -2
- package/dist/modules/auth/cli.js.map +2 -2
- package/dist/modules/auth/lib/autologin.js +19 -0
- package/dist/modules/auth/lib/autologin.js.map +7 -0
- package/dist/modules/configs/lib/upgrade-actions.js +22 -0
- package/dist/modules/configs/lib/upgrade-actions.js.map +2 -2
- package/dist/modules/customer_accounts/api/admin/roles/[id]/acl.js +1 -0
- package/dist/modules/customer_accounts/api/admin/roles/[id]/acl.js.map +2 -2
- package/dist/modules/customer_accounts/api/admin/roles/[id].js +17 -3
- package/dist/modules/customer_accounts/api/admin/roles/[id].js.map +2 -2
- package/dist/modules/customer_accounts/api/portal/users/[id]/roles.js +6 -1
- package/dist/modules/customer_accounts/api/portal/users/[id]/roles.js.map +2 -2
- package/dist/modules/customer_accounts/api/portal/users-invite.js +6 -1
- package/dist/modules/customer_accounts/api/portal/users-invite.js.map +2 -2
- package/dist/modules/customer_accounts/services/customerInvitationService.js +1 -0
- package/dist/modules/customer_accounts/services/customerInvitationService.js.map +2 -2
- package/dist/modules/customer_accounts/services/customerRbacService.js +6 -1
- package/dist/modules/customer_accounts/services/customerRbacService.js.map +2 -2
- package/dist/modules/customer_accounts/widgets/injection-table.js +0 -2
- package/dist/modules/customer_accounts/widgets/injection-table.js.map +2 -2
- package/dist/modules/customers/backend/customers/companies-v2/[id]/page.js +7 -1
- package/dist/modules/customers/backend/customers/companies-v2/[id]/page.js.map +2 -2
- package/dist/modules/customers/backend/customers/deals/pipeline/components/ViewTabsRow.js +2 -2
- package/dist/modules/customers/backend/customers/deals/pipeline/components/ViewTabsRow.js.map +2 -2
- package/dist/modules/customers/components/detail/CompanyDetailTabs.js +37 -43
- package/dist/modules/customers/components/detail/CompanyDetailTabs.js.map +2 -2
- package/dist/modules/customers/components/detail/DealDetailTabs.js +23 -35
- package/dist/modules/customers/components/detail/DealDetailTabs.js.map +2 -2
- package/dist/modules/customers/components/detail/DetailTabsLayout.js +6 -21
- package/dist/modules/customers/components/detail/DetailTabsLayout.js.map +2 -2
- package/dist/modules/customers/components/detail/MobilePersonDetail.js +26 -32
- package/dist/modules/customers/components/detail/MobilePersonDetail.js.map +2 -2
- package/dist/modules/customers/components/detail/PersonDetailTabs.js +29 -39
- package/dist/modules/customers/components/detail/PersonDetailTabs.js.map +2 -2
- package/dist/modules/customers/components/detail/useDealsAccess.js +16 -0
- package/dist/modules/customers/components/detail/useDealsAccess.js.map +7 -0
- package/dist/modules/dashboards/api/roles/widgets/route.js +22 -3
- package/dist/modules/dashboards/api/roles/widgets/route.js.map +2 -2
- package/dist/modules/dashboards/api/users/widgets/route.js +22 -3
- package/dist/modules/dashboards/api/users/widgets/route.js.map +2 -2
- package/dist/modules/dashboards/cli.js +4 -3
- package/dist/modules/dashboards/cli.js.map +2 -2
- package/dist/modules/dashboards/lib/widgetAssignmentScope.js +9 -1
- package/dist/modules/dashboards/lib/widgetAssignmentScope.js.map +2 -2
- package/dist/modules/dictionaries/commands/entries.js +7 -14
- package/dist/modules/dictionaries/commands/entries.js.map +2 -2
- package/dist/modules/dictionaries/commands/entry-operations.js +7 -16
- package/dist/modules/dictionaries/commands/entry-operations.js.map +2 -2
- package/dist/modules/dictionaries/commands/factory.js +9 -3
- package/dist/modules/dictionaries/commands/factory.js.map +2 -2
- package/dist/modules/dictionaries/fields/dictionary.js +56 -41
- package/dist/modules/dictionaries/fields/dictionary.js.map +2 -2
- package/dist/modules/directory/utils/organizationScope.js +10 -3
- package/dist/modules/directory/utils/organizationScope.js.map +2 -2
- package/dist/modules/entities/api/records.js +5 -4
- package/dist/modules/entities/api/records.js.map +2 -2
- package/dist/modules/entities/backend/entities/system/[entityId]/page.meta.js +1 -0
- package/dist/modules/entities/backend/entities/system/[entityId]/page.meta.js.map +2 -2
- package/dist/modules/entities/backend/entities/user/[entityId]/page.js +49 -49
- package/dist/modules/entities/backend/entities/user/[entityId]/page.js.map +2 -2
- package/dist/modules/entities/backend/entities/user/[entityId]/page.meta.js +1 -0
- package/dist/modules/entities/backend/entities/user/[entityId]/page.meta.js.map +2 -2
- package/dist/modules/entities/cli.js +2 -1
- package/dist/modules/entities/cli.js.map +2 -2
- package/dist/modules/feature_toggles/api/check/boolean/route.js +0 -14
- package/dist/modules/feature_toggles/api/check/boolean/route.js.map +2 -2
- package/dist/modules/feature_toggles/defaults.json +8 -0
- package/dist/modules/integrations/backend/integrations/[id]/page.js +9 -80
- package/dist/modules/integrations/backend/integrations/[id]/page.js.map +2 -2
- package/dist/modules/payment_gateways/data/entities.js +45 -0
- package/dist/modules/payment_gateways/data/entities.js.map +2 -2
- package/dist/modules/payment_gateways/lib/gateway-service.js +173 -38
- package/dist/modules/payment_gateways/lib/gateway-service.js.map +3 -3
- package/dist/modules/payment_gateways/lib/session-idempotency.js +104 -0
- package/dist/modules/payment_gateways/lib/session-idempotency.js.map +7 -0
- package/dist/modules/payment_gateways/migrations/Migration20260709220938_payment_gateways.js +11 -0
- package/dist/modules/payment_gateways/migrations/Migration20260709220938_payment_gateways.js.map +7 -0
- package/dist/modules/planner/backend/planner/availability-rulesets/[id]/page.js +16 -14
- package/dist/modules/planner/backend/planner/availability-rulesets/[id]/page.js.map +2 -2
- package/dist/modules/planner/components/AvailabilityRulesEditor.js +18 -30
- package/dist/modules/planner/components/AvailabilityRulesEditor.js.map +2 -2
- package/dist/modules/query_index/api/status.js +2 -8
- package/dist/modules/query_index/api/status.js.map +2 -2
- package/dist/modules/resources/backend/resources/resources/[id]/page.js +24 -24
- package/dist/modules/resources/backend/resources/resources/[id]/page.js.map +2 -2
- package/dist/modules/sales/backend/sales/channels/create/page.js +6 -0
- package/dist/modules/sales/backend/sales/channels/create/page.js.map +2 -2
- package/dist/modules/sales/backend/sales/channels/create/page.meta.js +2 -0
- package/dist/modules/sales/backend/sales/channels/create/page.meta.js.map +2 -2
- package/dist/modules/sales/backend/sales/channels/offers/page.js +6 -0
- package/dist/modules/sales/backend/sales/channels/offers/page.js.map +2 -2
- package/dist/modules/sales/backend/sales/channels/offers/page.meta.js +2 -0
- package/dist/modules/sales/backend/sales/channels/offers/page.meta.js.map +2 -2
- package/dist/modules/sales/backend/sales/channels/page.js +6 -0
- package/dist/modules/sales/backend/sales/channels/page.js.map +2 -2
- package/dist/modules/sales/backend/sales/channels/page.meta.js +2 -0
- package/dist/modules/sales/backend/sales/channels/page.meta.js.map +2 -2
- package/dist/modules/sales/backend/sales/documents/[id]/page.js +6 -4
- package/dist/modules/sales/backend/sales/documents/[id]/page.js.map +2 -2
- package/dist/modules/sales/commands/documents.js +44 -0
- package/dist/modules/sales/commands/documents.js.map +2 -2
- package/dist/modules/sales/commands/returns.js +2 -27
- package/dist/modules/sales/commands/returns.js.map +2 -2
- package/dist/modules/sales/components/SalesChannelsDisabledNotice.js +15 -0
- package/dist/modules/sales/components/SalesChannelsDisabledNotice.js.map +7 -0
- package/dist/modules/sales/components/documents/AddressesSection.js +6 -2
- package/dist/modules/sales/components/documents/AddressesSection.js.map +2 -2
- package/dist/modules/sales/components/documents/ItemsSection.js +1 -0
- package/dist/modules/sales/components/documents/ItemsSection.js.map +2 -2
- package/dist/modules/sales/components/documents/LineItemDialog.js +9 -0
- package/dist/modules/sales/components/documents/LineItemDialog.js.map +2 -2
- package/dist/modules/sales/components/documents/SalesDocumentForm.js +51 -3
- package/dist/modules/sales/components/documents/SalesDocumentForm.js.map +2 -2
- package/dist/modules/sales/components/documents/SalesDocumentsTable.js +10 -8
- package/dist/modules/sales/components/documents/SalesDocumentsTable.js.map +2 -2
- package/dist/modules/sales/components/documents/ShipmentDialog.js +59 -38
- package/dist/modules/sales/components/documents/ShipmentDialog.js.map +2 -2
- package/dist/modules/sales/components/useSalesChannelsEnabled.js +44 -0
- package/dist/modules/sales/components/useSalesChannelsEnabled.js.map +7 -0
- package/dist/modules/sales/lib/salesChannelsToggle.js +18 -0
- package/dist/modules/sales/lib/salesChannelsToggle.js.map +7 -0
- package/dist/modules/sales/lib/salesChannelsToggleId.js +5 -0
- package/dist/modules/sales/lib/salesChannelsToggleId.js.map +7 -0
- package/dist/modules/sales/lib/shipments/snapshots.js +27 -1
- package/dist/modules/sales/lib/shipments/snapshots.js.map +2 -2
- package/dist/modules/staff/backend/staff/team-members/[id]/page.js +22 -25
- package/dist/modules/staff/backend/staff/team-members/[id]/page.js.map +2 -2
- package/dist/modules/staff/backend/staff/teams/[id]/edit/page.js +12 -14
- package/dist/modules/staff/backend/staff/teams/[id]/edit/page.js.map +2 -2
- package/dist/modules/staff/lib/timesheets-projects-ui/SavedViewTabs.js +10 -29
- package/dist/modules/staff/lib/timesheets-projects-ui/SavedViewTabs.js.map +2 -2
- package/dist/modules/sync_excel/lib/adapters/customers.js +19 -20
- package/dist/modules/sync_excel/lib/adapters/customers.js.map +2 -2
- package/dist/modules/sync_excel/lib/parser.js +185 -0
- package/dist/modules/sync_excel/lib/parser.js.map +2 -2
- package/dist/modules/sync_excel/lib/upload-storage.js +20 -1
- package/dist/modules/sync_excel/lib/upload-storage.js.map +2 -2
- package/generated/entities/gateway_session_initialization/index.ts +10 -0
- package/generated/entities.ids.generated.ts +1 -0
- package/generated/entity-fields-registry.ts +12 -0
- package/package.json +9 -8
- package/src/modules/api_keys/api/keys/route.ts +36 -13
- package/src/modules/api_keys/cli.ts +2 -3
- package/src/modules/attachments/api/file/[id]/route.ts +11 -7
- package/src/modules/attachments/api/image/[id]/[[...slug]]/route.ts +11 -7
- package/src/modules/attachments/api/route.ts +26 -21
- package/src/modules/attachments/components/AttachmentContentPreview.tsx +9 -44
- package/src/modules/attachments/lib/reconcileOrganization.ts +188 -0
- package/src/modules/attachments/lib/requestScope.ts +27 -0
- package/src/modules/audit_logs/backend/audit-logs/page.tsx +12 -22
- package/src/modules/auth/api/autologin.ts +100 -0
- package/src/modules/auth/api/users/route.ts +11 -2
- package/src/modules/auth/cli.ts +3 -2
- package/src/modules/auth/lib/autologin.ts +36 -0
- package/src/modules/configs/i18n/de.json +5 -1
- package/src/modules/configs/i18n/en.json +5 -1
- package/src/modules/configs/i18n/es.json +5 -1
- package/src/modules/configs/i18n/pl.json +5 -1
- package/src/modules/configs/lib/upgrade-actions.ts +24 -0
- package/src/modules/customer_accounts/AGENTS.md +8 -6
- package/src/modules/customer_accounts/api/admin/roles/[id]/acl.ts +1 -0
- package/src/modules/customer_accounts/api/admin/roles/[id].ts +17 -3
- package/src/modules/customer_accounts/api/portal/users/[id]/roles.ts +6 -1
- package/src/modules/customer_accounts/api/portal/users-invite.ts +6 -1
- package/src/modules/customer_accounts/services/customerInvitationService.ts +1 -0
- package/src/modules/customer_accounts/services/customerRbacService.ts +9 -2
- package/src/modules/customer_accounts/widgets/injection-table.ts +0 -2
- package/src/modules/customers/backend/customers/companies-v2/[id]/page.tsx +11 -1
- package/src/modules/customers/backend/customers/deals/pipeline/components/ViewTabsRow.tsx +6 -2
- package/src/modules/customers/components/detail/CompanyDetailTabs.tsx +39 -55
- package/src/modules/customers/components/detail/DealDetailTabs.tsx +24 -49
- package/src/modules/customers/components/detail/DetailTabsLayout.tsx +13 -24
- package/src/modules/customers/components/detail/MobilePersonDetail.tsx +25 -31
- package/src/modules/customers/components/detail/PersonDetailTabs.tsx +29 -50
- package/src/modules/customers/components/detail/useDealsAccess.ts +19 -0
- package/src/modules/dashboards/api/roles/widgets/route.ts +22 -3
- package/src/modules/dashboards/api/users/widgets/route.ts +22 -3
- package/src/modules/dashboards/cli.ts +4 -9
- package/src/modules/dashboards/lib/widgetAssignmentScope.ts +18 -0
- package/src/modules/dictionaries/commands/entries.ts +7 -16
- package/src/modules/dictionaries/commands/entry-operations.ts +8 -18
- package/src/modules/dictionaries/commands/factory.ts +8 -2
- package/src/modules/dictionaries/fields/dictionary.tsx +55 -32
- package/src/modules/dictionaries/i18n/de.json +2 -0
- package/src/modules/dictionaries/i18n/en.json +2 -0
- package/src/modules/dictionaries/i18n/es.json +2 -0
- package/src/modules/dictionaries/i18n/pl.json +2 -0
- package/src/modules/directory/utils/organizationScope.ts +36 -2
- package/src/modules/entities/api/records.ts +5 -7
- package/src/modules/entities/backend/entities/system/[entityId]/page.meta.ts +1 -0
- package/src/modules/entities/backend/entities/user/[entityId]/page.meta.ts +1 -0
- package/src/modules/entities/backend/entities/user/[entityId]/page.tsx +51 -49
- package/src/modules/entities/cli.ts +2 -1
- package/src/modules/entities/i18n/de.json +34 -0
- package/src/modules/entities/i18n/en.json +34 -0
- package/src/modules/entities/i18n/es.json +34 -0
- package/src/modules/entities/i18n/pl.json +34 -0
- package/src/modules/feature_toggles/api/check/boolean/route.ts +0 -15
- package/src/modules/feature_toggles/defaults.json +8 -0
- package/src/modules/integrations/backend/integrations/[id]/page.tsx +16 -59
- package/src/modules/payment_gateways/data/entities.ts +39 -0
- package/src/modules/payment_gateways/lib/gateway-service.ts +211 -40
- package/src/modules/payment_gateways/lib/session-idempotency.ts +142 -0
- package/src/modules/payment_gateways/migrations/.snapshot-open-mercato.json +236 -0
- package/src/modules/payment_gateways/migrations/Migration20260709220938_payment_gateways.ts +10 -0
- package/src/modules/planner/backend/planner/availability-rulesets/[id]/page.tsx +14 -20
- package/src/modules/planner/components/AvailabilityRulesEditor.tsx +16 -34
- package/src/modules/query_index/api/status.ts +2 -8
- package/src/modules/resources/backend/resources/resources/[id]/page.tsx +27 -37
- package/src/modules/sales/backend/sales/channels/create/page.meta.ts +3 -0
- package/src/modules/sales/backend/sales/channels/create/page.tsx +7 -0
- package/src/modules/sales/backend/sales/channels/offers/page.meta.ts +3 -0
- package/src/modules/sales/backend/sales/channels/offers/page.tsx +7 -0
- package/src/modules/sales/backend/sales/channels/page.meta.ts +4 -0
- package/src/modules/sales/backend/sales/channels/page.tsx +7 -0
- package/src/modules/sales/backend/sales/documents/[id]/page.tsx +7 -5
- package/src/modules/sales/commands/documents.ts +65 -0
- package/src/modules/sales/commands/returns.ts +2 -32
- package/src/modules/sales/components/SalesChannelsDisabledNotice.tsx +21 -0
- package/src/modules/sales/components/documents/AddressesSection.tsx +6 -2
- package/src/modules/sales/components/documents/ItemsSection.tsx +5 -0
- package/src/modules/sales/components/documents/LineItemDialog.tsx +10 -0
- package/src/modules/sales/components/documents/SalesDocumentForm.tsx +53 -3
- package/src/modules/sales/components/documents/SalesDocumentsTable.tsx +10 -8
- package/src/modules/sales/components/documents/ShipmentDialog.tsx +65 -40
- package/src/modules/sales/components/useSalesChannelsEnabled.ts +59 -0
- package/src/modules/sales/i18n/de.json +5 -0
- package/src/modules/sales/i18n/en.json +5 -0
- package/src/modules/sales/i18n/es.json +5 -0
- package/src/modules/sales/i18n/pl.json +5 -0
- package/src/modules/sales/lib/salesChannelsToggle.ts +26 -0
- package/src/modules/sales/lib/salesChannelsToggleId.ts +1 -0
- package/src/modules/sales/lib/shipments/snapshots.ts +30 -0
- package/src/modules/staff/backend/staff/team-members/[id]/page.tsx +25 -34
- package/src/modules/staff/backend/staff/teams/[id]/edit/page.tsx +11 -17
- package/src/modules/staff/lib/timesheets-projects-ui/SavedViewTabs.tsx +11 -28
- package/src/modules/sync_excel/lib/adapters/customers.ts +20 -22
- package/src/modules/sync_excel/lib/parser.ts +245 -0
- package/src/modules/sync_excel/lib/upload-storage.ts +23 -1
|
@@ -90,9 +90,12 @@ function DictionaryFieldDefEditor({ def, onChange }: { def: { configJson?: Dicti
|
|
|
90
90
|
const [error, setError] = React.useState<string | null>(null)
|
|
91
91
|
const multiId = React.useId()
|
|
92
92
|
const inlineCreateId = React.useId()
|
|
93
|
+
const inlineCreateHintId = React.useId()
|
|
94
|
+
const defaultValueHintId = React.useId()
|
|
93
95
|
const selectedId = typeof def?.configJson?.dictionaryId === 'string' ? def?.configJson?.dictionaryId : ''
|
|
94
96
|
const inlineCreate = def?.configJson?.dictionaryInlineCreate !== false
|
|
95
97
|
const isMulti = def?.configJson?.multi === true
|
|
98
|
+
const inlineCreateDisabled = !selectedId || isMulti
|
|
96
99
|
const errorLoadLabel = t('dictionaries.customFields.errorLoad', 'Failed to load dictionaries.')
|
|
97
100
|
|
|
98
101
|
React.useEffect(() => {
|
|
@@ -181,45 +184,65 @@ function DictionaryFieldDefEditor({ def, onChange }: { def: { configJson?: Dicti
|
|
|
181
184
|
</a>
|
|
182
185
|
</div>
|
|
183
186
|
) : null}
|
|
184
|
-
<div className="
|
|
185
|
-
<
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
<
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
187
|
+
<div className="space-y-2">
|
|
188
|
+
<div className="flex flex-col gap-2 sm:flex-row sm:flex-wrap sm:items-center sm:gap-x-4">
|
|
189
|
+
<div className="inline-flex items-center gap-2 text-xs">
|
|
190
|
+
<Checkbox
|
|
191
|
+
id={multiId}
|
|
192
|
+
checked={isMulti}
|
|
193
|
+
onCheckedChange={(checked) => {
|
|
194
|
+
const enabled = checked === true
|
|
195
|
+
onChange({ multi: enabled, defaultValue: enabled ? undefined : def?.configJson?.defaultValue })
|
|
196
|
+
}}
|
|
197
|
+
disabled={!selectedId}
|
|
198
|
+
/>
|
|
199
|
+
<label
|
|
200
|
+
htmlFor={multiId}
|
|
201
|
+
className={selectedId ? 'cursor-pointer select-none' : 'cursor-not-allowed opacity-60'}
|
|
202
|
+
>
|
|
203
|
+
{t('dictionaries.customFields.allowMultiple', 'Allow selecting multiple entries')}
|
|
204
|
+
</label>
|
|
205
|
+
</div>
|
|
206
|
+
<div className="inline-flex items-center gap-2 text-xs">
|
|
207
|
+
<Checkbox
|
|
208
|
+
id={inlineCreateId}
|
|
209
|
+
checked={!isMulti && inlineCreate}
|
|
210
|
+
onCheckedChange={(checked) => onChange({ dictionaryInlineCreate: checked === true })}
|
|
211
|
+
disabled={inlineCreateDisabled}
|
|
212
|
+
aria-describedby={isMulti ? inlineCreateHintId : undefined}
|
|
213
|
+
/>
|
|
214
|
+
<label
|
|
215
|
+
htmlFor={inlineCreateId}
|
|
216
|
+
className={!inlineCreateDisabled ? 'cursor-pointer select-none' : 'cursor-not-allowed opacity-60'}
|
|
217
|
+
>
|
|
218
|
+
{t('dictionaries.customFields.allowInlineCreate', 'Allow inline creation inside forms')}
|
|
219
|
+
</label>
|
|
220
|
+
</div>
|
|
215
221
|
</div>
|
|
216
|
-
|
|
222
|
+
{isMulti ? (
|
|
223
|
+
<p id={inlineCreateHintId} className="text-xs text-muted-foreground">
|
|
224
|
+
{t(
|
|
225
|
+
'dictionaries.customFields.inlineCreateSingleOnly',
|
|
226
|
+
'Inline creation is available for single-entry dictionary fields only.',
|
|
227
|
+
)}
|
|
228
|
+
</p>
|
|
229
|
+
) : null}
|
|
230
|
+
</div>
|
|
217
231
|
{selectedId && !isMulti ? (
|
|
218
232
|
<DictionaryDefaultSelector
|
|
219
233
|
dictionaryId={selectedId}
|
|
220
234
|
defaultValue={typeof def?.configJson?.defaultValue === 'string' ? def.configJson.defaultValue : ''}
|
|
221
235
|
onChange={(value) => onChange({ defaultValue: value || undefined })}
|
|
222
236
|
/>
|
|
237
|
+
) : selectedId && isMulti ? (
|
|
238
|
+
<div className="inline-flex items-center gap-2 text-xs">
|
|
239
|
+
<p id={defaultValueHintId} className="text-xs text-muted-foreground">
|
|
240
|
+
{t(
|
|
241
|
+
'dictionaries.customFields.defaultValueMultiUnavailable',
|
|
242
|
+
'Default values are not available for multi-select dictionary fields.',
|
|
243
|
+
)}
|
|
244
|
+
</p>
|
|
245
|
+
</div>
|
|
223
246
|
) : null}
|
|
224
247
|
</div>
|
|
225
248
|
)
|
|
@@ -82,6 +82,7 @@
|
|
|
82
82
|
"dictionaries.customFields.allowInlineCreate": "Inline-Erstellung in Formularen erlauben",
|
|
83
83
|
"dictionaries.customFields.allowMultiple": "Mehrere Einträge auswählen erlauben",
|
|
84
84
|
"dictionaries.customFields.defaultValue": "Standardwert",
|
|
85
|
+
"dictionaries.customFields.defaultValueMultiUnavailable": "Standardwerte sind für Wörterbuchfelder mit Mehrfachauswahl nicht verfügbar.",
|
|
85
86
|
"dictionaries.customFields.defaultValueNone": "Kein Standardwert",
|
|
86
87
|
"dictionaries.customFields.defaultValueStale": "Standardeintrag nicht gefunden — er wurde möglicherweise gelöscht oder umbenannt.",
|
|
87
88
|
"dictionaries.customFields.dictionaryLabel": "Quelle des Wörterbuchs",
|
|
@@ -89,6 +90,7 @@
|
|
|
89
90
|
"dictionaries.customFields.empty": "Noch keine Wörterbücher verfügbar. Erstellen Sie zuerst eines.",
|
|
90
91
|
"dictionaries.customFields.errorLoad": "Wörterbücher konnten nicht geladen werden.",
|
|
91
92
|
"dictionaries.customFields.inactive": "inaktiv",
|
|
93
|
+
"dictionaries.customFields.inlineCreateSingleOnly": "Inline-Erstellung ist nur für Wörterbuchfelder mit Einzelauswahl verfügbar.",
|
|
92
94
|
"dictionaries.customFields.loading": "Wörterbücher werden geladen…",
|
|
93
95
|
"dictionaries.customFields.manageLink": "Wörterbücher verwalten",
|
|
94
96
|
"dictionaries.customFields.selectedHint": "Einträge aus diesem Wörterbuch füllen das Feld.",
|
|
@@ -82,6 +82,7 @@
|
|
|
82
82
|
"dictionaries.customFields.allowInlineCreate": "Allow inline creation inside forms",
|
|
83
83
|
"dictionaries.customFields.allowMultiple": "Allow selecting multiple entries",
|
|
84
84
|
"dictionaries.customFields.defaultValue": "Default value",
|
|
85
|
+
"dictionaries.customFields.defaultValueMultiUnavailable": "Default values are not available for multi-select dictionary fields.",
|
|
85
86
|
"dictionaries.customFields.defaultValueNone": "No default",
|
|
86
87
|
"dictionaries.customFields.defaultValueStale": "Default entry not found — it may have been deleted or renamed.",
|
|
87
88
|
"dictionaries.customFields.dictionaryLabel": "Dictionary source",
|
|
@@ -89,6 +90,7 @@
|
|
|
89
90
|
"dictionaries.customFields.empty": "No dictionaries available yet. Create one first.",
|
|
90
91
|
"dictionaries.customFields.errorLoad": "Failed to load dictionaries.",
|
|
91
92
|
"dictionaries.customFields.inactive": "inactive",
|
|
93
|
+
"dictionaries.customFields.inlineCreateSingleOnly": "Inline creation is available for single-entry dictionary fields only.",
|
|
92
94
|
"dictionaries.customFields.loading": "Loading dictionaries…",
|
|
93
95
|
"dictionaries.customFields.manageLink": "Manage dictionaries",
|
|
94
96
|
"dictionaries.customFields.selectedHint": "Entries from this dictionary populate the field.",
|
|
@@ -82,6 +82,7 @@
|
|
|
82
82
|
"dictionaries.customFields.allowInlineCreate": "Permitir creación en línea dentro de los formularios",
|
|
83
83
|
"dictionaries.customFields.allowMultiple": "Permitir seleccionar varias entradas",
|
|
84
84
|
"dictionaries.customFields.defaultValue": "Valor predeterminado",
|
|
85
|
+
"dictionaries.customFields.defaultValueMultiUnavailable": "Los valores predeterminados no están disponibles para campos de diccionario con selección múltiple.",
|
|
85
86
|
"dictionaries.customFields.defaultValueNone": "Sin valor predeterminado",
|
|
86
87
|
"dictionaries.customFields.defaultValueStale": "No se encontró la entrada predeterminada; puede haber sido eliminada o renombrada.",
|
|
87
88
|
"dictionaries.customFields.dictionaryLabel": "Fuente del diccionario",
|
|
@@ -89,6 +90,7 @@
|
|
|
89
90
|
"dictionaries.customFields.empty": "Aún no hay diccionarios disponibles. Crea uno primero.",
|
|
90
91
|
"dictionaries.customFields.errorLoad": "No se pudieron cargar los diccionarios.",
|
|
91
92
|
"dictionaries.customFields.inactive": "inactivo",
|
|
93
|
+
"dictionaries.customFields.inlineCreateSingleOnly": "La creación en línea solo está disponible para campos de diccionario de una sola entrada.",
|
|
92
94
|
"dictionaries.customFields.loading": "Cargando diccionarios…",
|
|
93
95
|
"dictionaries.customFields.manageLink": "Administrar diccionarios",
|
|
94
96
|
"dictionaries.customFields.selectedHint": "Las entradas de este diccionario rellenarán el campo.",
|
|
@@ -82,6 +82,7 @@
|
|
|
82
82
|
"dictionaries.customFields.allowInlineCreate": "Zezwól na tworzenie w formularzach",
|
|
83
83
|
"dictionaries.customFields.allowMultiple": "Zezwól na wybór wielu wpisów",
|
|
84
84
|
"dictionaries.customFields.defaultValue": "Wartość domyślna",
|
|
85
|
+
"dictionaries.customFields.defaultValueMultiUnavailable": "Wartości domyślne nie są dostępne dla wielokrotnego wyboru wpisów słownika.",
|
|
85
86
|
"dictionaries.customFields.defaultValueNone": "Brak wartości domyślnej",
|
|
86
87
|
"dictionaries.customFields.defaultValueStale": "Nie znaleziono domyślnego wpisu — mógł zostać usunięty albo przemianowany.",
|
|
87
88
|
"dictionaries.customFields.dictionaryLabel": "Źródło słownika",
|
|
@@ -89,6 +90,7 @@
|
|
|
89
90
|
"dictionaries.customFields.empty": "Brak dostępnych słowników. Najpierw utwórz jeden.",
|
|
90
91
|
"dictionaries.customFields.errorLoad": "Nie udało się wczytać słowników.",
|
|
91
92
|
"dictionaries.customFields.inactive": "nieaktywne",
|
|
93
|
+
"dictionaries.customFields.inlineCreateSingleOnly": "Tworzenie wpisów w formularzu jest dostępne tylko dla pól z pojedynczym wyborem.",
|
|
92
94
|
"dictionaries.customFields.loading": "Ładowanie słowników…",
|
|
93
95
|
"dictionaries.customFields.manageLink": "Zarządzaj słownikami",
|
|
94
96
|
"dictionaries.customFields.selectedHint": "Wpisy z tego słownika uzupełnią pole.",
|
|
@@ -18,6 +18,13 @@ export type OrganizationScope = {
|
|
|
18
18
|
filterIds: string[] | null
|
|
19
19
|
allowedIds: string[] | null
|
|
20
20
|
tenantId: string | null
|
|
21
|
+
// True when the caller explicitly selected a concrete organization (cookie /
|
|
22
|
+
// selectedId param) that could not be honored — it does not exist for the
|
|
23
|
+
// tenant or is not accessible. Reads degrade gracefully (filterIds/selectedId
|
|
24
|
+
// fall back to the caller's accessible orgs), but writes MUST fail loudly on
|
|
25
|
+
// this so a record never lands under an org the caller did not intend. Absent
|
|
26
|
+
// (undefined) means the selection — if any — was honored.
|
|
27
|
+
selectionRejected?: boolean
|
|
21
28
|
}
|
|
22
29
|
|
|
23
30
|
// Phase 4 — short-TTL cache for resolveOrganizationScopeForRequest.
|
|
@@ -79,7 +86,8 @@ function isValidCachedScope(value: unknown): value is OrganizationScope {
|
|
|
79
86
|
const record = value as Partial<OrganizationScope>
|
|
80
87
|
const idOk = (v: unknown) => v === null || typeof v === 'string'
|
|
81
88
|
const arrOk = (v: unknown) => v === null || (Array.isArray(v) && v.every((entry) => typeof entry === 'string'))
|
|
82
|
-
|
|
89
|
+
const flagOk = record.selectionRejected === undefined || typeof record.selectionRejected === 'boolean'
|
|
90
|
+
return idOk(record.selectedId) && idOk(record.tenantId) && arrOk(record.filterIds) && arrOk(record.allowedIds) && flagOk
|
|
83
91
|
}
|
|
84
92
|
|
|
85
93
|
function resolveCacheFromContainer(container: AwilixContainer | null | undefined): CacheStrategy | null {
|
|
@@ -320,9 +328,18 @@ export async function resolveOrganizationScope({
|
|
|
320
328
|
const initialSelected =
|
|
321
329
|
normalizedSelectedId
|
|
322
330
|
?? (widenToAllOrgs ? null : accountOrgId ?? null)
|
|
331
|
+
// A selection is only honored when it resolves to a real, non-deleted org for
|
|
332
|
+
// this tenant. `orgDescendants` holds exactly the existing orgs among the
|
|
333
|
+
// candidate ids, so a stale/dead id (e.g. a selected-org cookie or JWT org
|
|
334
|
+
// that no longer resolves after a DB reset) has no entry and is dropped here.
|
|
335
|
+
// Without the existence guard an unrestricted (all-orgs) principal — whose
|
|
336
|
+
// `allowedSet` is null — would accept the dead id as `effectiveSelected`, and
|
|
337
|
+
// writes derived from `selectedId` would land under an org the read scope
|
|
338
|
+
// (`filterIds`) never filters to, silently orphaning the record.
|
|
339
|
+
const selectionResolvesToOrg = (id: string): boolean => orgDescendants.has(id)
|
|
323
340
|
let effectiveSelected: string | null = null
|
|
324
341
|
if (initialSelected) {
|
|
325
|
-
if (allowedSet === null || allowedSet.has(initialSelected)) {
|
|
342
|
+
if ((allowedSet === null || allowedSet.has(initialSelected)) && selectionResolvesToOrg(initialSelected)) {
|
|
326
343
|
effectiveSelected = initialSelected
|
|
327
344
|
}
|
|
328
345
|
}
|
|
@@ -336,6 +353,13 @@ export async function resolveOrganizationScope({
|
|
|
336
353
|
filterSet = null
|
|
337
354
|
} else if (auth.orgId) {
|
|
338
355
|
filterSet = loadFallbackSet()
|
|
356
|
+
// Keep the write target (`selectedId`) aligned with the read scope
|
|
357
|
+
// (`filterIds`): when an unrestricted principal's requested selection was
|
|
358
|
+
// dropped above, fall the selection back to the account org too so a record
|
|
359
|
+
// created here is always readable back by the same caller.
|
|
360
|
+
if (!effectiveSelected && fallbackOrgId && filterSet && filterSet.size > 0) {
|
|
361
|
+
effectiveSelected = fallbackOrgId
|
|
362
|
+
}
|
|
339
363
|
}
|
|
340
364
|
|
|
341
365
|
if ((!filterSet || filterSet.size === 0) && fallbackOrgId && !widenToAllOrgs) {
|
|
@@ -348,11 +372,21 @@ export async function resolveOrganizationScope({
|
|
|
348
372
|
}
|
|
349
373
|
}
|
|
350
374
|
|
|
375
|
+
// A concrete organization was explicitly requested (`normalizedSelectedId` is
|
|
376
|
+
// null for both "no selection" and the "all organizations" token) but the
|
|
377
|
+
// resolver could not honor it — it was dropped as non-existent/inaccessible
|
|
378
|
+
// and the effective selection fell back to something else. Surface this so the
|
|
379
|
+
// write layer can reject the request instead of silently creating the record
|
|
380
|
+
// under the fallback org. Only set the field when true to keep the scope shape
|
|
381
|
+
// unchanged for the common (honored) case.
|
|
382
|
+
const selectionRejected = normalizedSelectedId !== null && effectiveSelected !== normalizedSelectedId
|
|
383
|
+
|
|
351
384
|
return {
|
|
352
385
|
selectedId: effectiveSelected,
|
|
353
386
|
filterIds: filterSet ? Array.from(filterSet) : null,
|
|
354
387
|
allowedIds: allowedSet ? Array.from(allowedSet) : null,
|
|
355
388
|
tenantId,
|
|
389
|
+
...(selectionRejected ? { selectionRejected: true } : {}),
|
|
356
390
|
}
|
|
357
391
|
}
|
|
358
392
|
|
|
@@ -8,6 +8,7 @@ import type { RbacService } from '@open-mercato/core/modules/auth/services/rbacS
|
|
|
8
8
|
import { resolveOrganizationScope, getSelectedOrganizationFromRequest } from '@open-mercato/core/modules/directory/utils/organizationScope'
|
|
9
9
|
import { SYSTEM_ENTITY_RECORDS_BLOCKED_CODE, isOrmBackedSystemEntityId } from '@open-mercato/shared/lib/data/engine'
|
|
10
10
|
import { parseBooleanToken, parseBooleanWithDefault } from '@open-mercato/shared/lib/boolean'
|
|
11
|
+
import { parseCommaSeparatedList } from '@open-mercato/shared/lib/string'
|
|
11
12
|
import { setRecordCustomFields } from '../lib/helpers'
|
|
12
13
|
import { CustomFieldValue } from '../data/entities'
|
|
13
14
|
import type { OpenApiRouteDoc } from '@open-mercato/shared/lib/openapi'
|
|
@@ -151,10 +152,7 @@ export async function GET(req: Request) {
|
|
|
151
152
|
const sortDir = (url.searchParams.get('sortDir') || 'asc').toLowerCase() === 'desc' ? 'desc' : 'asc'
|
|
152
153
|
const withDeleted = parseBooleanWithDefault(url.searchParams.get('withDeleted'), false)
|
|
153
154
|
const searchTerm = (url.searchParams.get('search') || '').trim()
|
|
154
|
-
const searchFields = (url.searchParams.get('searchFields')
|
|
155
|
-
.split(',')
|
|
156
|
-
.map((field) => field.trim())
|
|
157
|
-
.filter(Boolean)
|
|
155
|
+
const searchFields = parseCommaSeparatedList(url.searchParams.get('searchFields'))
|
|
158
156
|
|
|
159
157
|
const qpEntries: Array<[string, string]> = []
|
|
160
158
|
for (const [key, val] of url.searchParams.entries()) {
|
|
@@ -211,11 +209,11 @@ export async function GET(req: Request) {
|
|
|
211
209
|
if (key.startsWith('cf_')) {
|
|
212
210
|
if (key.endsWith('In')) {
|
|
213
211
|
const base = key.slice(0, -2)
|
|
214
|
-
const values = val
|
|
212
|
+
const values = parseCommaSeparatedList(val)
|
|
215
213
|
;(filtersObj as any)[base] = { $in: values }
|
|
216
214
|
} else {
|
|
217
215
|
if (val.includes(',')) {
|
|
218
|
-
const values = val
|
|
216
|
+
const values = parseCommaSeparatedList(val)
|
|
219
217
|
;(filtersObj as any)[key] = { $in: values }
|
|
220
218
|
} else {
|
|
221
219
|
const parsed = parseBooleanToken(val)
|
|
@@ -224,7 +222,7 @@ export async function GET(req: Request) {
|
|
|
224
222
|
}
|
|
225
223
|
} else if (allowAnyKey) {
|
|
226
224
|
if (val.includes(',')) {
|
|
227
|
-
const values = val
|
|
225
|
+
const values = parseCommaSeparatedList(val)
|
|
228
226
|
;(filtersObj as any)[key] = { $in: values }
|
|
229
227
|
} else {
|
|
230
228
|
const parsed = parseBooleanToken(val)
|
|
@@ -167,7 +167,7 @@ export default function EditDefinitionsPage({ params }: { params?: { entityId?:
|
|
|
167
167
|
const entJson = await readApiResultOrThrow<EntitiesListResponse>(
|
|
168
168
|
'/api/entities/entities',
|
|
169
169
|
undefined,
|
|
170
|
-
{ errorMessage: 'Failed to load entity metadata', fallback: { items: [] } },
|
|
170
|
+
{ errorMessage: t('entities.userEntities.edit.errors.loadMetadataFailed', 'Failed to load entity metadata'), fallback: { items: [] } },
|
|
171
171
|
)
|
|
172
172
|
const ent = (entJson.items || []).find((x: any) => x.entityId === entityId)
|
|
173
173
|
if (mounted) {
|
|
@@ -197,7 +197,7 @@ export default function EditDefinitionsPage({ params }: { params?: { entityId?:
|
|
|
197
197
|
const json = await readApiResultOrThrow<DefinitionsManageResponse>(
|
|
198
198
|
`/api/entities/definitions.manage?entityId=${encodeURIComponent(entityId)}`,
|
|
199
199
|
undefined,
|
|
200
|
-
{ errorMessage: 'Failed to load entity definitions', fallback: { items: [], deletedKeys: [] } },
|
|
200
|
+
{ errorMessage: t('entities.userEntities.edit.errors.loadDefinitionsFailed', 'Failed to load entity definitions'), fallback: { items: [], deletedKeys: [] } },
|
|
201
201
|
)
|
|
202
202
|
if (mounted) {
|
|
203
203
|
const loaded: Def[] = (json.items || []).map((d: any) => ({ key: d.key, kind: d.kind, configJson: d.configJson || {}, isActive: d.isActive !== false }))
|
|
@@ -220,14 +220,14 @@ export default function EditDefinitionsPage({ params }: { params?: { entityId?:
|
|
|
220
220
|
setSingleFieldsetPerRecord(json.settings?.singleFieldsetPerRecord !== false)
|
|
221
221
|
}
|
|
222
222
|
} catch (e: any) {
|
|
223
|
-
if (mounted) setError(e.message || 'Failed to load')
|
|
223
|
+
if (mounted) setError(e.message || t('entities.userEntities.edit.errors.loadFailed', 'Failed to load'))
|
|
224
224
|
} finally {
|
|
225
225
|
if (mounted) setLoading(false)
|
|
226
226
|
}
|
|
227
227
|
}
|
|
228
228
|
if (entityId) load()
|
|
229
229
|
return () => { mounted = false }
|
|
230
|
-
}, [entityId])
|
|
230
|
+
}, [entityId, t])
|
|
231
231
|
|
|
232
232
|
function addField() {
|
|
233
233
|
setDefs((arr) => [
|
|
@@ -249,13 +249,13 @@ export default function EditDefinitionsPage({ params }: { params?: { entityId?:
|
|
|
249
249
|
body: JSON.stringify({ entityId, key }),
|
|
250
250
|
})
|
|
251
251
|
if (!call.ok) {
|
|
252
|
-
await raiseCrudError(call.response, 'Failed to restore field')
|
|
252
|
+
await raiseCrudError(call.response, t('entities.userEntities.edit.errors.restoreFieldFailed', 'Failed to restore field'))
|
|
253
253
|
}
|
|
254
254
|
// Reload definitions & deleted keys
|
|
255
255
|
const j2 = await readApiResultOrThrow<DefinitionsManageResponse>(
|
|
256
256
|
`/api/entities/definitions.manage?entityId=${encodeURIComponent(entityId)}`,
|
|
257
257
|
undefined,
|
|
258
|
-
{ errorMessage: 'Failed to reload field definitions', fallback: { items: [], deletedKeys: [] } },
|
|
258
|
+
{ errorMessage: t('entities.userEntities.edit.errors.reloadDefinitionsFailed', 'Failed to reload field definitions'), fallback: { items: [], deletedKeys: [] } },
|
|
259
259
|
)
|
|
260
260
|
const loaded: Def[] = (j2.items || []).map((d: any) => ({ key: d.key, kind: d.kind, configJson: d.configJson || {}, isActive: d.isActive !== false }))
|
|
261
261
|
loaded.sort(
|
|
@@ -264,10 +264,10 @@ export default function EditDefinitionsPage({ params }: { params?: { entityId?:
|
|
|
264
264
|
setDefs(loaded)
|
|
265
265
|
setDefsVersion(readVersionToken(j2.version))
|
|
266
266
|
setDeletedKeys(Array.isArray(j2.deletedKeys) ? j2.deletedKeys : [])
|
|
267
|
-
flash(
|
|
267
|
+
flash(t('entities.userEntities.edit.flash.restoredField', 'Restored {{key}}', { key }), 'success')
|
|
268
268
|
await invalidateCustomFieldDefs(queryClient, entityId)
|
|
269
269
|
} catch (e: any) {
|
|
270
|
-
flash(e?.message || 'Failed to restore field', 'error')
|
|
270
|
+
flash(e?.message || t('entities.userEntities.edit.errors.restoreFieldFailed', 'Failed to restore field'), 'error')
|
|
271
271
|
}
|
|
272
272
|
}
|
|
273
273
|
|
|
@@ -276,8 +276,8 @@ export default function EditDefinitionsPage({ params }: { params?: { entityId?:
|
|
|
276
276
|
setError(null)
|
|
277
277
|
try {
|
|
278
278
|
if (!validateAll()) {
|
|
279
|
-
flash('Please fix validation errors in field definitions', 'error')
|
|
280
|
-
throw new Error('Validation failed')
|
|
279
|
+
flash(t('entities.customFields.errors.validation', 'Please fix validation errors in field definitions.'), 'error')
|
|
280
|
+
throw new Error(t('entities.userEntities.edit.errors.validationFailed', 'Validation failed'))
|
|
281
281
|
}
|
|
282
282
|
const payload = {
|
|
283
283
|
entityId,
|
|
@@ -298,13 +298,13 @@ export default function EditDefinitionsPage({ params }: { params?: { entityId?:
|
|
|
298
298
|
)
|
|
299
299
|
if (!call.ok) {
|
|
300
300
|
if (surfaceRecordConflict({ status: call.status, body: call.result }, t)) return
|
|
301
|
-
await raiseCrudError(call.response, 'Failed to save definitions')
|
|
301
|
+
await raiseCrudError(call.response, t('entities.customFields.errors.saveFailed', 'Failed to save field definitions.'))
|
|
302
302
|
}
|
|
303
303
|
setDefsVersion(readVersionToken(call.result?.version))
|
|
304
304
|
await invalidateCustomFieldDefs(queryClient, entityId)
|
|
305
|
-
router.push(`/backend/entities/user?flash
|
|
305
|
+
router.push(`/backend/entities/user?flash=${encodeURIComponent(t('entities.customFields.flash.saved', 'Definitions saved'))}&type=success`)
|
|
306
306
|
} catch (e: any) {
|
|
307
|
-
setError(e.message || 'Failed to save')
|
|
307
|
+
setError(e.message || t('entities.customFields.errors.saveFailed', 'Failed to save field definitions.'))
|
|
308
308
|
} finally {
|
|
309
309
|
setSaving(false)
|
|
310
310
|
}
|
|
@@ -321,13 +321,13 @@ export default function EditDefinitionsPage({ params }: { params?: { entityId?:
|
|
|
321
321
|
body: JSON.stringify({ entityId, key: def.key }),
|
|
322
322
|
})
|
|
323
323
|
if (!call.ok) {
|
|
324
|
-
await raiseCrudError(call.response, 'Failed to delete field')
|
|
324
|
+
await raiseCrudError(call.response, t('entities.customFields.errors.deleteFailed', 'Failed to delete field.'))
|
|
325
325
|
}
|
|
326
326
|
// Keep the optimistic-lock token in sync after an out-of-band delete so a
|
|
327
327
|
// later Save does not falsely 409 against our own change (issue #3152).
|
|
328
328
|
setDefsVersion(readVersionToken(call.result?.version))
|
|
329
329
|
} catch (error) {
|
|
330
|
-
const message = error instanceof Error ? error.message : 'Failed to delete field'
|
|
330
|
+
const message = error instanceof Error ? error.message : t('entities.customFields.errors.deleteFailed', 'Failed to delete field.')
|
|
331
331
|
flash(message, 'error')
|
|
332
332
|
return
|
|
333
333
|
}
|
|
@@ -372,7 +372,7 @@ export default function EditDefinitionsPage({ params }: { params?: { entityId?:
|
|
|
372
372
|
setOrderSaving(true)
|
|
373
373
|
try {
|
|
374
374
|
// Do not save order when there are invalid keys/kinds
|
|
375
|
-
if (!validateAll()) throw new Error('Validation failed')
|
|
375
|
+
if (!validateAll()) throw new Error(t('entities.userEntities.edit.errors.validationFailed', 'Validation failed'))
|
|
376
376
|
const payload = {
|
|
377
377
|
entityId,
|
|
378
378
|
definitions: defs.filter(d => !!d.key).map((d) => ({
|
|
@@ -396,14 +396,14 @@ export default function EditDefinitionsPage({ params }: { params?: { entityId?:
|
|
|
396
396
|
setOrderDirty(false)
|
|
397
397
|
return
|
|
398
398
|
}
|
|
399
|
-
await raiseCrudError(call.response, 'Failed to save order')
|
|
399
|
+
await raiseCrudError(call.response, t('entities.userEntities.edit.errors.saveOrderFailed', 'Failed to save order'))
|
|
400
400
|
}
|
|
401
401
|
setDefsVersion(readVersionToken(call.result?.version))
|
|
402
402
|
setOrderDirty(false)
|
|
403
|
-
flash('Order saved', 'success')
|
|
403
|
+
flash(t('entities.userEntities.edit.flash.orderSaved', 'Order saved'), 'success')
|
|
404
404
|
await invalidateCustomFieldDefs(queryClient, entityId)
|
|
405
405
|
} catch (e: any) {
|
|
406
|
-
flash(e?.message || 'Failed to save order', 'error')
|
|
406
|
+
flash(e?.message || t('entities.userEntities.edit.errors.saveOrderFailed', 'Failed to save order'), 'error')
|
|
407
407
|
} finally {
|
|
408
408
|
setOrderSaving(false)
|
|
409
409
|
}
|
|
@@ -423,21 +423,21 @@ export default function EditDefinitionsPage({ params }: { params?: { entityId?:
|
|
|
423
423
|
|
|
424
424
|
const metadataFieldsReadOnly = entitySource === 'code'
|
|
425
425
|
const fields: CrudField[] = [
|
|
426
|
-
{ id: 'label', label: 'Label', type: 'text', required: true, readOnly: metadataFieldsReadOnly },
|
|
427
|
-
{ id: 'description', label: 'Description', type: 'textarea', readOnly: metadataFieldsReadOnly },
|
|
426
|
+
{ id: 'label', label: t('entities.userEntities.form.label.label', 'Label'), type: 'text', required: true, readOnly: metadataFieldsReadOnly },
|
|
427
|
+
{ id: 'description', label: t('entities.userEntities.form.description.label', 'Description'), type: 'textarea', readOnly: metadataFieldsReadOnly },
|
|
428
428
|
{
|
|
429
429
|
id: 'defaultEditor',
|
|
430
|
-
label: 'Default Editor (multiline)',
|
|
430
|
+
label: t('entities.userEntities.form.defaultEditor.label', 'Default Editor (multiline)'),
|
|
431
431
|
type: 'select',
|
|
432
432
|
readOnly: metadataFieldsReadOnly,
|
|
433
433
|
options: [
|
|
434
|
-
{ value: '', label: 'Default (Markdown)' },
|
|
435
|
-
{ value: 'markdown', label: 'Markdown (UIW)' },
|
|
436
|
-
{ value: 'simpleMarkdown', label: 'Simple Markdown' },
|
|
437
|
-
{ value: 'htmlRichText', label: 'HTML Rich Text' },
|
|
434
|
+
{ value: '', label: t('entities.userEntities.form.defaultEditor.options.default', 'Default (Markdown)') },
|
|
435
|
+
{ value: 'markdown', label: t('entities.userEntities.form.defaultEditor.options.markdown', 'Markdown (UIW)') },
|
|
436
|
+
{ value: 'simpleMarkdown', label: t('entities.userEntities.form.defaultEditor.options.simpleMarkdown', 'Simple Markdown') },
|
|
437
|
+
{ value: 'htmlRichText', label: t('entities.userEntities.form.defaultEditor.options.htmlRichText', 'HTML Rich Text') },
|
|
438
438
|
],
|
|
439
439
|
} as any,
|
|
440
|
-
...(entitySource === 'custom' ? [{ id: 'showInSidebar', label: 'Show in sidebar', type: 'checkbox' }] : []),
|
|
440
|
+
...(entitySource === 'custom' ? [{ id: 'showInSidebar', label: t('entities.userEntities.form.showInSidebar.label', 'Show in sidebar'), type: 'checkbox' }] : []),
|
|
441
441
|
]
|
|
442
442
|
const renderFieldDefinitions = React.useCallback(() => (
|
|
443
443
|
<FieldDefinitionsEditor
|
|
@@ -474,7 +474,7 @@ export default function EditDefinitionsPage({ params }: { params?: { entityId?:
|
|
|
474
474
|
})
|
|
475
475
|
setOrderDirty(true)
|
|
476
476
|
}}
|
|
477
|
-
orderNotice={orderDirty ? { dirty: true, saving: orderSaving, message: 'Reordered
|
|
477
|
+
orderNotice={orderDirty ? { dirty: true, saving: orderSaving, message: t('entities.userEntities.edit.orderNotice', 'Reordered - will auto-save on blur') } : undefined}
|
|
478
478
|
addButtonLabel={t('entities.customFields.editor.addField', 'Add Field')}
|
|
479
479
|
translate={t}
|
|
480
480
|
listRef={listRef}
|
|
@@ -491,22 +491,22 @@ export default function EditDefinitionsPage({ params }: { params?: { entityId?:
|
|
|
491
491
|
}}
|
|
492
492
|
/>
|
|
493
493
|
),
|
|
494
|
-
[defs, defErrors, deletedKeys, fieldsets, activeFieldset, singleFieldsetPerRecord, orderDirty, orderSaving, addField, removeField, restoreField, saveOrderIfDirty])
|
|
494
|
+
[defs, defErrors, deletedKeys, fieldsets, activeFieldset, singleFieldsetPerRecord, orderDirty, orderSaving, addField, removeField, restoreField, saveOrderIfDirty, t])
|
|
495
495
|
|
|
496
|
-
const definitionsGroup: CrudFormGroup = { id: 'definitions', title: 'Field Definitions', column: 1, component: renderFieldDefinitions }
|
|
496
|
+
const definitionsGroup: CrudFormGroup = { id: 'definitions', title: t('entities.userEntities.edit.groups.definitions', 'Field Definitions'), column: 1, component: renderFieldDefinitions }
|
|
497
497
|
|
|
498
498
|
const groups: CrudFormGroup[] = [
|
|
499
|
-
{ id: 'settings', title: 'Entity Settings', column: 1, fields: entitySource === 'custom' ? ['label','description','defaultEditor','showInSidebar'] : ['label','description','defaultEditor'] },
|
|
499
|
+
{ id: 'settings', title: t('entities.userEntities.edit.groups.settings', 'Entity Settings'), column: 1, fields: entitySource === 'custom' ? ['label','description','defaultEditor','showInSidebar'] : ['label','description','defaultEditor'] },
|
|
500
500
|
definitionsGroup,
|
|
501
501
|
]
|
|
502
502
|
|
|
503
503
|
const handleCrudFormSubmit = React.useCallback(async (vals: Record<string, unknown>) => {
|
|
504
504
|
if (!entityId) {
|
|
505
|
-
throw createCrudFormError('Invalid entity ID')
|
|
505
|
+
throw createCrudFormError(t('entities.userEntities.edit.errors.invalidEntityId', 'Invalid entity ID'))
|
|
506
506
|
}
|
|
507
507
|
if (!validateAll()) {
|
|
508
|
-
flash('Please fix validation errors in field definitions', 'error')
|
|
509
|
-
throw createCrudFormError('Please fix validation errors in field definitions')
|
|
508
|
+
flash(t('entities.customFields.errors.validation', 'Please fix validation errors in field definitions.'), 'error')
|
|
509
|
+
throw createCrudFormError(t('entities.customFields.errors.validation', 'Please fix validation errors in field definitions.'))
|
|
510
510
|
}
|
|
511
511
|
// Code-declared system entities are not registered as custom entities — their
|
|
512
512
|
// metadata is owned by code and `POST /api/entities/entities` is fail-closed for
|
|
@@ -514,14 +514,14 @@ export default function EditDefinitionsPage({ params }: { params?: { entityId?:
|
|
|
514
514
|
// skip the registration call and persist definitions below.
|
|
515
515
|
if (shouldRegisterEntityMetadata(entitySource)) {
|
|
516
516
|
const entityPayload = buildEntityMetadataPayload(entitySource, vals)
|
|
517
|
-
if (!entityPayload) throw createCrudFormError('Validation failed')
|
|
517
|
+
if (!entityPayload) throw createCrudFormError(t('entities.userEntities.edit.errors.validationFailed', 'Validation failed'))
|
|
518
518
|
const callEntity = await apiCall('/api/entities/entities', {
|
|
519
519
|
method: 'POST',
|
|
520
520
|
headers: { 'content-type': 'application/json' },
|
|
521
521
|
body: JSON.stringify({ entityId, ...entityPayload }),
|
|
522
522
|
})
|
|
523
523
|
if (!callEntity.ok) {
|
|
524
|
-
await raiseCrudError(callEntity.response, 'Failed to save entity')
|
|
524
|
+
await raiseCrudError(callEntity.response, t('entities.userEntities.edit.errors.saveEntityFailed', 'Failed to save entity'))
|
|
525
525
|
}
|
|
526
526
|
try { window.dispatchEvent(new Event('om:refresh-sidebar')) } catch {}
|
|
527
527
|
}
|
|
@@ -543,13 +543,13 @@ export default function EditDefinitionsPage({ params }: { params?: { entityId?:
|
|
|
543
543
|
}),
|
|
544
544
|
)
|
|
545
545
|
if (!callDefs.ok) {
|
|
546
|
-
await raiseCrudError(callDefs.response, 'Failed to save definitions')
|
|
546
|
+
await raiseCrudError(callDefs.response, t('entities.customFields.errors.saveFailed', 'Failed to save field definitions.'))
|
|
547
547
|
}
|
|
548
548
|
setDefsVersion(readVersionToken(callDefs.result?.version))
|
|
549
549
|
try { window.dispatchEvent(new Event('om:refresh-sidebar')) } catch {}
|
|
550
550
|
await invalidateCustomFieldDefs(queryClient, entityId)
|
|
551
|
-
flash('Definitions saved', 'success')
|
|
552
|
-
}, [buildFieldsetPayload, defs, defsVersion, entityId, entitySource, queryClient, singleFieldsetPerRecord, validateAll])
|
|
551
|
+
flash(t('entities.customFields.flash.saved', 'Definitions saved'), 'success')
|
|
552
|
+
}, [buildFieldsetPayload, defs, defsVersion, entityId, entitySource, queryClient, singleFieldsetPerRecord, t, validateAll])
|
|
553
553
|
|
|
554
554
|
if (!entityId) {
|
|
555
555
|
return (
|
|
@@ -557,8 +557,8 @@ export default function EditDefinitionsPage({ params }: { params?: { entityId?:
|
|
|
557
557
|
<PageBody>
|
|
558
558
|
<div className="p-6">
|
|
559
559
|
<Alert variant="destructive">
|
|
560
|
-
<AlertTitle>Invalid entity</AlertTitle>
|
|
561
|
-
<AlertDescription>The requested entity ID is missing or invalid
|
|
560
|
+
<AlertTitle>{t('entities.userEntities.edit.errors.invalidEntityTitle', 'Invalid entity')}</AlertTitle>
|
|
561
|
+
<AlertDescription>{t('entities.userEntities.edit.errors.invalidEntityDescription', 'The requested entity ID is missing or invalid.')}</AlertDescription>
|
|
562
562
|
</Alert>
|
|
563
563
|
</div>
|
|
564
564
|
</PageBody>
|
|
@@ -571,12 +571,12 @@ export default function EditDefinitionsPage({ params }: { params?: { entityId?:
|
|
|
571
571
|
<div className="p-4">
|
|
572
572
|
<CrudForm
|
|
573
573
|
schema={entityFormSchema}
|
|
574
|
-
title={
|
|
574
|
+
title={t('entities.userEntities.edit.fieldsetTitle', 'Edit fieldset: {{name}}', { name: requestedFieldset ?? entityId })}
|
|
575
575
|
fields={[]}
|
|
576
576
|
groups={[definitionsGroup]}
|
|
577
577
|
initialValues={entityInitial as any}
|
|
578
578
|
isLoading={entityFormLoading || loading}
|
|
579
|
-
submitLabel=
|
|
579
|
+
submitLabel={t('entities.userEntities.records.form.submitSave', 'Save')}
|
|
580
580
|
deleteVisible={false}
|
|
581
581
|
backHref={undefined}
|
|
582
582
|
cancelHref={undefined}
|
|
@@ -592,30 +592,32 @@ export default function EditDefinitionsPage({ params }: { params?: { entityId?:
|
|
|
592
592
|
<PageBody>
|
|
593
593
|
<CrudForm
|
|
594
594
|
schema={entityFormSchema}
|
|
595
|
-
title={
|
|
595
|
+
title={t('entities.userEntities.edit.title', 'Edit Entity: {{entityId}}', { entityId })}
|
|
596
596
|
backHref={entitySource === 'code' ? "/backend/entities/system" : "/backend/entities/user"}
|
|
597
597
|
fields={fields}
|
|
598
598
|
groups={groups}
|
|
599
599
|
initialValues={entityInitial as any}
|
|
600
600
|
isLoading={entityFormLoading || loading}
|
|
601
|
-
submitLabel=
|
|
601
|
+
submitLabel={t('entities.userEntities.records.form.submitSave', 'Save')}
|
|
602
602
|
deleteVisible={entitySource === 'custom'}
|
|
603
603
|
extraActions={entitySource === 'custom' ? (
|
|
604
604
|
<Button variant="outline" asChild>
|
|
605
605
|
<Link href={`/backend/entities/user/${encodeURIComponent(entityId)}/records`}>
|
|
606
|
-
Show Records
|
|
606
|
+
{t('entities.userEntities.edit.showRecords', 'Show Records')}
|
|
607
607
|
</Link>
|
|
608
608
|
</Button>
|
|
609
609
|
) : null}
|
|
610
610
|
cancelHref={entitySource === 'code' ? "/backend/entities/system" : "/backend/entities/user"}
|
|
611
|
-
successRedirect={entitySource === 'code'
|
|
611
|
+
successRedirect={entitySource === 'code'
|
|
612
|
+
? `/backend/entities/system?flash=${encodeURIComponent(t('entities.customFields.flash.saved', 'Definitions saved'))}&type=success`
|
|
613
|
+
: `/backend/entities/user?flash=${encodeURIComponent(t('entities.customFields.flash.saved', 'Definitions saved'))}&type=success`}
|
|
612
614
|
onSubmit={handleCrudFormSubmit}
|
|
613
615
|
onDelete={entitySource === 'custom' ? async () => {
|
|
614
616
|
const callDelete = await apiCall('/api/entities/entities', { method: 'DELETE', headers: { 'content-type': 'application/json' }, body: JSON.stringify({ entityId }) })
|
|
615
617
|
if (!callDelete.ok) {
|
|
616
|
-
await raiseCrudError(callDelete.response, 'Failed to delete entity')
|
|
618
|
+
await raiseCrudError(callDelete.response, t('entities.userEntities.edit.errors.deleteEntityFailed', 'Failed to delete entity'))
|
|
617
619
|
}
|
|
618
|
-
flash('Entity deleted', 'success')
|
|
620
|
+
flash(t('entities.userEntities.edit.flash.entityDeleted', 'Entity deleted'), 'success')
|
|
619
621
|
try { window.dispatchEvent(new Event('om:refresh-sidebar')) } catch {}
|
|
620
622
|
} : undefined}
|
|
621
623
|
/>
|