@open-mercato/core 0.6.8-develop.7101.1.bd9356828a → 0.7.1-develop.7102.1.b41f7e3e51
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 +1 -1
- package/dist/modules/auth/api/login.js +19 -0
- package/dist/modules/auth/api/login.js.map +2 -2
- package/dist/modules/auth/api/routeError.js +11 -0
- package/dist/modules/auth/api/routeError.js.map +7 -0
- package/dist/modules/auth/api/users/acl/route.js +23 -7
- package/dist/modules/auth/api/users/acl/route.js.map +2 -2
- package/dist/modules/business_rules/backend/rules/[id]/page.meta.js +6 -1
- package/dist/modules/business_rules/backend/rules/[id]/page.meta.js.map +2 -2
- package/dist/modules/business_rules/backend/rules/create/page.meta.js +5 -1
- package/dist/modules/business_rules/backend/rules/create/page.meta.js.map +2 -2
- package/dist/modules/business_rules/backend/sets/[id]/page.meta.js +6 -1
- package/dist/modules/business_rules/backend/sets/[id]/page.meta.js.map +2 -2
- package/dist/modules/business_rules/backend/sets/create/page.meta.js +5 -1
- package/dist/modules/business_rules/backend/sets/create/page.meta.js.map +2 -2
- package/dist/modules/customer_accounts/backend/customer_accounts/settings/CustomerAccountsSettingsPageClient.js +84 -0
- package/dist/modules/customer_accounts/backend/customer_accounts/settings/CustomerAccountsSettingsPageClient.js.map +7 -0
- package/dist/modules/customer_accounts/backend/customer_accounts/settings/page.js +7 -81
- package/dist/modules/customer_accounts/backend/customer_accounts/settings/page.js.map +2 -2
- package/dist/modules/customer_accounts/backend/customer_accounts/users/PortalUsersPageClient.js +525 -0
- package/dist/modules/customer_accounts/backend/customer_accounts/users/PortalUsersPageClient.js.map +7 -0
- package/dist/modules/customer_accounts/backend/customer_accounts/users/[id]/PortalUserDetailPageClient.js +731 -0
- package/dist/modules/customer_accounts/backend/customer_accounts/users/[id]/PortalUserDetailPageClient.js.map +7 -0
- package/dist/modules/customer_accounts/backend/customer_accounts/users/[id]/page.js +7 -727
- package/dist/modules/customer_accounts/backend/customer_accounts/users/[id]/page.js.map +2 -2
- package/dist/modules/customer_accounts/backend/customer_accounts/users/page.js +7 -521
- package/dist/modules/customer_accounts/backend/customer_accounts/users/page.js.map +2 -2
- package/dist/modules/customer_accounts/extension-points.js +1 -1
- package/dist/modules/customer_accounts/extension-points.js.map +2 -2
- package/dist/modules/customer_accounts/lib/portalUrl.js +69 -0
- package/dist/modules/customer_accounts/lib/portalUrl.js.map +7 -0
- package/dist/modules/customers/backend/customers/companies/page.js +2 -2
- package/dist/modules/customers/backend/customers/companies/page.js.map +2 -2
- package/dist/modules/customers/backend/customers/deals/page.js +2 -2
- package/dist/modules/customers/backend/customers/deals/page.js.map +2 -2
- package/dist/modules/customers/backend/customers/people/page.js +2 -2
- package/dist/modules/customers/backend/customers/people/page.js.map +2 -2
- package/dist/modules/customers/components/detail/AssignRoleDialog.js +33 -28
- package/dist/modules/customers/components/detail/AssignRoleDialog.js.map +2 -2
- package/dist/modules/eudr/backend/eudr/evidence-submissions/[id]/page.meta.js +3 -1
- package/dist/modules/eudr/backend/eudr/evidence-submissions/[id]/page.meta.js.map +2 -2
- package/dist/modules/eudr/backend/eudr/evidence-submissions/create/page.meta.js +1 -0
- package/dist/modules/eudr/backend/eudr/evidence-submissions/create/page.meta.js.map +2 -2
- package/dist/modules/eudr/backend/eudr/evidence-submissions/page.meta.js +4 -1
- package/dist/modules/eudr/backend/eudr/evidence-submissions/page.meta.js.map +2 -2
- package/dist/modules/eudr/backend/eudr/page.meta.js +2 -2
- package/dist/modules/eudr/backend/eudr/page.meta.js.map +2 -2
- package/dist/modules/eudr/backend/eudr/plots/[id]/page.js +8 -2
- package/dist/modules/eudr/backend/eudr/plots/[id]/page.js.map +2 -2
- package/dist/modules/eudr/backend/eudr/plots/[id]/page.meta.js +3 -1
- package/dist/modules/eudr/backend/eudr/plots/[id]/page.meta.js.map +2 -2
- package/dist/modules/eudr/backend/eudr/plots/create/page.js +8 -2
- package/dist/modules/eudr/backend/eudr/plots/create/page.js.map +2 -2
- package/dist/modules/eudr/backend/eudr/plots/create/page.meta.js +1 -0
- package/dist/modules/eudr/backend/eudr/plots/create/page.meta.js.map +2 -2
- package/dist/modules/eudr/backend/eudr/plots/page.meta.js +4 -1
- package/dist/modules/eudr/backend/eudr/plots/page.meta.js.map +2 -2
- package/dist/modules/eudr/backend/eudr/product-mappings/[id]/page.meta.js +3 -1
- package/dist/modules/eudr/backend/eudr/product-mappings/[id]/page.meta.js.map +2 -2
- package/dist/modules/eudr/backend/eudr/product-mappings/create/page.meta.js +1 -0
- package/dist/modules/eudr/backend/eudr/product-mappings/create/page.meta.js.map +2 -2
- package/dist/modules/eudr/backend/eudr/product-mappings/page.meta.js +4 -1
- package/dist/modules/eudr/backend/eudr/product-mappings/page.meta.js.map +2 -2
- package/dist/modules/eudr/backend/eudr/risk-assessments/[id]/page.meta.js +3 -1
- package/dist/modules/eudr/backend/eudr/risk-assessments/[id]/page.meta.js.map +2 -2
- package/dist/modules/eudr/backend/eudr/risk-assessments/create/page.meta.js +1 -0
- package/dist/modules/eudr/backend/eudr/risk-assessments/create/page.meta.js.map +2 -2
- package/dist/modules/eudr/backend/eudr/risk-assessments/page.meta.js +4 -1
- package/dist/modules/eudr/backend/eudr/risk-assessments/page.meta.js.map +2 -2
- package/dist/modules/eudr/backend/eudr/statements/[id]/page.js +2 -0
- package/dist/modules/eudr/backend/eudr/statements/[id]/page.js.map +2 -2
- package/dist/modules/eudr/backend/eudr/statements/[id]/page.meta.js +3 -1
- package/dist/modules/eudr/backend/eudr/statements/[id]/page.meta.js.map +2 -2
- package/dist/modules/eudr/backend/eudr/statements/create/page.meta.js +1 -0
- package/dist/modules/eudr/backend/eudr/statements/create/page.meta.js.map +2 -2
- package/dist/modules/eudr/backend/eudr/statements/page.js +30 -25
- package/dist/modules/eudr/backend/eudr/statements/page.js.map +2 -2
- package/dist/modules/eudr/backend/eudr/statements/page.meta.js +4 -1
- package/dist/modules/eudr/backend/eudr/statements/page.meta.js.map +2 -2
- package/dist/modules/eudr/commands/statements.js +5 -3
- package/dist/modules/eudr/commands/statements.js.map +2 -2
- package/dist/modules/eudr/components/crudErrorI18n.js +21 -1
- package/dist/modules/eudr/components/crudErrorI18n.js.map +2 -2
- package/dist/modules/eudr/components/formConfig.js +6 -2
- package/dist/modules/eudr/components/formConfig.js.map +2 -2
- package/dist/modules/eudr/lib/statement-lifecycle.js +9 -1
- package/dist/modules/eudr/lib/statement-lifecycle.js.map +2 -2
- package/dist/modules/sales/components/documents/LineItemDialog.js +1 -1
- package/dist/modules/sales/components/documents/LineItemDialog.js.map +1 -1
- package/dist/modules/sales/widgets/injection/document-history/widget.client.js +44 -2
- package/dist/modules/sales/widgets/injection/document-history/widget.client.js.map +2 -2
- package/dist/modules/wms/components/backend/WmsLocationDetailPage.js +20 -16
- package/dist/modules/wms/components/backend/WmsLocationDetailPage.js.map +2 -2
- package/dist/modules/wms/components/backend/WmsLotDetailPage.js +10 -6
- package/dist/modules/wms/components/backend/WmsLotDetailPage.js.map +2 -2
- package/dist/modules/wms/components/backend/WmsOperationalDashboardPage.js +11 -7
- package/dist/modules/wms/components/backend/WmsOperationalDashboardPage.js.map +2 -2
- package/dist/modules/wms/components/backend/WmsSkuDetailPage.js +10 -6
- package/dist/modules/wms/components/backend/WmsSkuDetailPage.js.map +2 -2
- package/package.json +8 -8
- package/src/modules/auth/api/login.ts +23 -0
- package/src/modules/auth/api/routeError.ts +30 -0
- package/src/modules/auth/api/users/acl/route.ts +49 -9
- package/src/modules/business_rules/backend/rules/[id]/page.meta.ts +5 -0
- package/src/modules/business_rules/backend/rules/create/page.meta.ts +5 -1
- package/src/modules/business_rules/backend/sets/[id]/page.meta.ts +5 -0
- package/src/modules/business_rules/backend/sets/create/page.meta.ts +5 -1
- package/src/modules/customer_accounts/backend/customer_accounts/settings/CustomerAccountsSettingsPageClient.tsx +138 -0
- package/src/modules/customer_accounts/backend/customer_accounts/settings/page.tsx +8 -132
- package/src/modules/customer_accounts/backend/customer_accounts/users/PortalUsersPageClient.tsx +601 -0
- package/src/modules/customer_accounts/backend/customer_accounts/users/[id]/PortalUserDetailPageClient.tsx +838 -0
- package/src/modules/customer_accounts/backend/customer_accounts/users/[id]/page.tsx +6 -838
- package/src/modules/customer_accounts/backend/customer_accounts/users/page.tsx +6 -590
- package/src/modules/customer_accounts/extension-points.ts +1 -1
- package/src/modules/customer_accounts/lib/portalUrl.ts +112 -0
- package/src/modules/customers/backend/customers/companies/page.tsx +2 -2
- package/src/modules/customers/backend/customers/deals/page.tsx +2 -2
- package/src/modules/customers/backend/customers/people/page.tsx +2 -2
- package/src/modules/customers/components/detail/AssignRoleDialog.tsx +12 -7
- package/src/modules/eudr/backend/eudr/evidence-submissions/[id]/page.meta.ts +2 -0
- package/src/modules/eudr/backend/eudr/evidence-submissions/create/page.meta.ts +1 -0
- package/src/modules/eudr/backend/eudr/evidence-submissions/page.meta.ts +4 -1
- package/src/modules/eudr/backend/eudr/page.meta.ts +2 -2
- package/src/modules/eudr/backend/eudr/plots/[id]/page.meta.ts +2 -0
- package/src/modules/eudr/backend/eudr/plots/[id]/page.tsx +8 -2
- package/src/modules/eudr/backend/eudr/plots/create/page.meta.ts +1 -0
- package/src/modules/eudr/backend/eudr/plots/create/page.tsx +8 -2
- package/src/modules/eudr/backend/eudr/plots/page.meta.ts +4 -1
- package/src/modules/eudr/backend/eudr/product-mappings/[id]/page.meta.ts +2 -0
- package/src/modules/eudr/backend/eudr/product-mappings/create/page.meta.ts +1 -0
- package/src/modules/eudr/backend/eudr/product-mappings/page.meta.ts +4 -1
- package/src/modules/eudr/backend/eudr/risk-assessments/[id]/page.meta.ts +2 -0
- package/src/modules/eudr/backend/eudr/risk-assessments/create/page.meta.ts +1 -0
- package/src/modules/eudr/backend/eudr/risk-assessments/page.meta.ts +4 -1
- package/src/modules/eudr/backend/eudr/statements/[id]/page.meta.ts +2 -0
- package/src/modules/eudr/backend/eudr/statements/[id]/page.tsx +2 -0
- package/src/modules/eudr/backend/eudr/statements/create/page.meta.ts +1 -0
- package/src/modules/eudr/backend/eudr/statements/page.meta.ts +4 -1
- package/src/modules/eudr/backend/eudr/statements/page.tsx +32 -26
- package/src/modules/eudr/commands/statements.ts +4 -2
- package/src/modules/eudr/components/crudErrorI18n.ts +31 -0
- package/src/modules/eudr/components/formConfig.tsx +9 -1
- package/src/modules/eudr/i18n/de.json +3 -2
- package/src/modules/eudr/i18n/en.json +3 -2
- package/src/modules/eudr/i18n/es.json +3 -2
- package/src/modules/eudr/i18n/ko.json +3 -2
- package/src/modules/eudr/i18n/pl.json +3 -2
- package/src/modules/eudr/lib/statement-lifecycle.ts +8 -0
- package/src/modules/sales/components/documents/LineItemDialog.tsx +1 -1
- package/src/modules/sales/i18n/de.json +37 -0
- package/src/modules/sales/i18n/en.json +37 -0
- package/src/modules/sales/i18n/es.json +37 -0
- package/src/modules/sales/i18n/ko.json +37 -0
- package/src/modules/sales/i18n/pl.json +37 -0
- package/src/modules/sales/widgets/injection/document-history/widget.client.tsx +42 -2
- package/src/modules/wms/components/backend/WmsLocationDetailPage.tsx +30 -26
- package/src/modules/wms/components/backend/WmsLotDetailPage.tsx +21 -17
- package/src/modules/wms/components/backend/WmsOperationalDashboardPage.tsx +17 -13
- package/src/modules/wms/components/backend/WmsSkuDetailPage.tsx +16 -12
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
// Resolve the browser-visible origin on the server so the admin portal-address
|
|
2
|
+
// hints are already correct in the first server-rendered paint. Deriving the
|
|
3
|
+
// origin from `window.location.origin` during render made the server HTML and
|
|
4
|
+
// the hydrated client output disagree, which React reported as a hydration
|
|
5
|
+
// error and repaired with a visible flicker of the portal address (#5457).
|
|
6
|
+
//
|
|
7
|
+
// `resolvePortalRequestOrigin` is deliberately named apart from the shared
|
|
8
|
+
// `resolveRequestOrigin` in `@open-mercato/shared/lib/url`: that one takes a
|
|
9
|
+
// `Request` and reads the protocol off `req.url`, which a server component does
|
|
10
|
+
// not have — it only has `headers()`. This one also validates the untrusted host
|
|
11
|
+
// header before the value reaches rendered text or a link href, and infers the
|
|
12
|
+
// protocol when the proxy stays quiet, so the two are not interchangeable.
|
|
13
|
+
|
|
14
|
+
export type RequestHeaderReader = {
|
|
15
|
+
get(name: string): string | null | undefined
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
const FORWARDED_HOST_HEADER = 'x-forwarded-host'
|
|
19
|
+
const HOST_HEADER = 'host'
|
|
20
|
+
const FORWARDED_PROTO_HEADER = 'x-forwarded-proto'
|
|
21
|
+
|
|
22
|
+
// Host headers are client-controlled, so only a plain `host[:port]` or bracketed
|
|
23
|
+
// IPv6 literal is accepted before it reaches rendered text and link hrefs.
|
|
24
|
+
const HOST_PATTERN = /^(?:[a-z0-9.-]+|\[[0-9a-f:.]+\])(?::\d{1,5})?$/i
|
|
25
|
+
const PROTOCOL_PATTERN = /^https?$/i
|
|
26
|
+
// Bracketed, because HOST_PATTERN only accepts a colon-bearing host in that form.
|
|
27
|
+
const LOOPBACK_HOSTS = new Set(['localhost', '127.0.0.1', '[::1]'])
|
|
28
|
+
|
|
29
|
+
export const PORTAL_ORG_SLUG_PLACEHOLDER = '[org-slug]'
|
|
30
|
+
|
|
31
|
+
function firstHeaderValue(raw: string | null | undefined): string | null {
|
|
32
|
+
if (typeof raw !== 'string') return null
|
|
33
|
+
// Proxy chains append values, so the client-facing hop is the first entry.
|
|
34
|
+
const value = raw.split(',')[0]?.trim() ?? ''
|
|
35
|
+
return value.length > 0 ? value : null
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function readHost(headers: RequestHeaderReader): string | null {
|
|
39
|
+
const forwarded = firstHeaderValue(headers.get(FORWARDED_HOST_HEADER))
|
|
40
|
+
const host = forwarded ?? firstHeaderValue(headers.get(HOST_HEADER))
|
|
41
|
+
if (!host) return null
|
|
42
|
+
return HOST_PATTERN.test(host) ? host.toLowerCase() : null
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
function readProtocol(headers: RequestHeaderReader, host: string): string {
|
|
46
|
+
const forwarded = firstHeaderValue(headers.get(FORWARDED_PROTO_HEADER))
|
|
47
|
+
if (forwarded && PROTOCOL_PATTERN.test(forwarded)) return forwarded.toLowerCase()
|
|
48
|
+
// An operator who configured APP_URL has named the canonical origin explicitly;
|
|
49
|
+
// when it points at this very host, trust it over any inference below. Without
|
|
50
|
+
// this, a plain-HTTP deployment behind a proxy that omits x-forwarded-proto
|
|
51
|
+
// would render https:// links that do not work.
|
|
52
|
+
const configured = configuredOrigin()
|
|
53
|
+
if (configured) {
|
|
54
|
+
try {
|
|
55
|
+
const parsed = new URL(configured)
|
|
56
|
+
if (parsed.host.toLowerCase() === host) return parsed.protocol.replace(':', '')
|
|
57
|
+
} catch {
|
|
58
|
+
// configuredOrigin() already parsed this successfully; ignore defensively.
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
// Otherwise a terminating proxy that omits the header is serving a public
|
|
62
|
+
// hostname over TLS far more often than not; only loopback hosts default to http.
|
|
63
|
+
const hostname = host.replace(/:\d{1,5}$/, '')
|
|
64
|
+
return LOOPBACK_HOSTS.has(hostname) ? 'http' : 'https'
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
function configuredOrigin(): string {
|
|
68
|
+
const candidates = [process.env.APP_URL, process.env.NEXT_PUBLIC_APP_URL]
|
|
69
|
+
for (const candidate of candidates) {
|
|
70
|
+
const value = candidate?.trim()
|
|
71
|
+
if (!value) continue
|
|
72
|
+
try {
|
|
73
|
+
const parsed = new URL(value)
|
|
74
|
+
if (PROTOCOL_PATTERN.test(parsed.protocol.replace(':', ''))) return parsed.origin
|
|
75
|
+
} catch {
|
|
76
|
+
// A malformed APP_URL must not break the page — fall through to the next candidate.
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
return ''
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* Resolve the origin the current request was made to, mirroring the value
|
|
84
|
+
* `window.location.origin` reports in the browser. Falls back to the configured
|
|
85
|
+
* app URL, then to an empty string so callers render a root-relative path that
|
|
86
|
+
* is identical on the server and the client.
|
|
87
|
+
*
|
|
88
|
+
* Named apart from `resolveRequestOrigin` in `@open-mercato/shared/lib/url` —
|
|
89
|
+
* see the note at the top of this file for why the two cannot be merged.
|
|
90
|
+
*/
|
|
91
|
+
export function resolvePortalRequestOrigin(headers: RequestHeaderReader | null | undefined): string {
|
|
92
|
+
if (headers && typeof headers.get === 'function') {
|
|
93
|
+
const host = readHost(headers)
|
|
94
|
+
if (host) return `${readProtocol(headers, host)}://${host}`
|
|
95
|
+
}
|
|
96
|
+
return configuredOrigin()
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
function normalizeOrigin(origin: string | null | undefined): string {
|
|
100
|
+
if (typeof origin !== 'string') return ''
|
|
101
|
+
return origin.trim().replace(/\/+$/, '')
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
/** Display pattern for a tenant portal address, e.g. `https://app.example.com/[org-slug]/portal`. */
|
|
105
|
+
export function buildPortalUrlPattern(origin: string | null | undefined): string {
|
|
106
|
+
return `${normalizeOrigin(origin)}/${PORTAL_ORG_SLUG_PLACEHOLDER}/portal`
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
/** Portal entry point used by the "Open Portal" action on admin pages. */
|
|
110
|
+
export function buildPortalRootUrl(origin: string | null | undefined): string {
|
|
111
|
+
return `${normalizeOrigin(origin)}/portal`
|
|
112
|
+
}
|
|
@@ -829,8 +829,8 @@ export default function CustomersCompaniesPage() {
|
|
|
829
829
|
header: def.label || def.key,
|
|
830
830
|
enableSorting: true,
|
|
831
831
|
meta: {
|
|
832
|
-
columnChooserGroup: def.group?.title ?? 'Custom Fields',
|
|
833
|
-
filterGroup: def.group?.title ?? 'Custom Fields',
|
|
832
|
+
columnChooserGroup: def.group?.title ?? t('ui.columnChooser.customFieldsGroup', 'Custom Fields'),
|
|
833
|
+
filterGroup: def.group?.title ?? t('ui.columnChooser.customFieldsGroup', 'Custom Fields'),
|
|
834
834
|
filterType: mapCustomFieldKindToFilterType(def.kind),
|
|
835
835
|
filterOptions: normalizeCustomFieldFilterOptions(def.options),
|
|
836
836
|
hidden: def.listVisible === false,
|
|
@@ -687,8 +687,8 @@ export default function CustomersDealsPage() {
|
|
|
687
687
|
accessorKey: `cf_${def.key}`,
|
|
688
688
|
header: def.label || def.key,
|
|
689
689
|
meta: {
|
|
690
|
-
columnChooserGroup: def.group?.title ?? 'Custom Fields',
|
|
691
|
-
filterGroup: def.group?.title ?? 'Custom Fields',
|
|
690
|
+
columnChooserGroup: def.group?.title ?? t('ui.columnChooser.customFieldsGroup', 'Custom Fields'),
|
|
691
|
+
filterGroup: def.group?.title ?? t('ui.columnChooser.customFieldsGroup', 'Custom Fields'),
|
|
692
692
|
filterType: mapCustomFieldKindToFilterType(def.kind),
|
|
693
693
|
filterOptions: normalizeCustomFieldFilterOptions(def.options),
|
|
694
694
|
hidden: def.listVisible === false,
|
|
@@ -856,8 +856,8 @@ export default function CustomersPeoplePage() {
|
|
|
856
856
|
header: def.label || def.key,
|
|
857
857
|
enableSorting: true,
|
|
858
858
|
meta: {
|
|
859
|
-
columnChooserGroup: def.group?.title ?? 'Custom Fields',
|
|
860
|
-
filterGroup: def.group?.title ?? 'Custom Fields',
|
|
859
|
+
columnChooserGroup: def.group?.title ?? t('ui.columnChooser.customFieldsGroup', 'Custom Fields'),
|
|
860
|
+
filterGroup: def.group?.title ?? t('ui.columnChooser.customFieldsGroup', 'Custom Fields'),
|
|
861
861
|
filterType: mapCustomFieldKindToFilterType(def.kind),
|
|
862
862
|
filterOptions: normalizeCustomFieldFilterOptions(def.options),
|
|
863
863
|
hidden: def.listVisible === false,
|
|
@@ -640,13 +640,18 @@ export function AssignRoleDialog({
|
|
|
640
640
|
</div>
|
|
641
641
|
</div>
|
|
642
642
|
|
|
643
|
-
<DialogFooter
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
643
|
+
<DialogFooter
|
|
644
|
+
bordered={false}
|
|
645
|
+
className="shrink-0 border-t border-border/70 px-6 py-4"
|
|
646
|
+
leading={
|
|
647
|
+
<p className="text-xs text-muted-foreground sm:max-w-2xs">
|
|
648
|
+
{t(
|
|
649
|
+
'customers.roles.dialog.footerNote',
|
|
650
|
+
'One person per role · can be changed at any time',
|
|
651
|
+
)}
|
|
652
|
+
</p>
|
|
653
|
+
}
|
|
654
|
+
>
|
|
650
655
|
<div className="flex items-center gap-2">
|
|
651
656
|
<Button type="button" variant="outline" onClick={onClose}>
|
|
652
657
|
{t('customers.roles.cancelAdd', 'Cancel')}
|
|
@@ -7,6 +7,8 @@ export const metadata = {
|
|
|
7
7
|
pageGroupKey: 'eudr.nav.group',
|
|
8
8
|
navHidden: true,
|
|
9
9
|
breadcrumb: [
|
|
10
|
+
{ label: 'EUDR', labelKey: 'eudr.nav.module', href: '/backend/eudr' },
|
|
10
11
|
{ label: 'Evidence submissions', labelKey: 'eudr.nav.submissions', href: '/backend/eudr/evidence-submissions' },
|
|
12
|
+
{ label: 'Edit evidence submission', labelKey: 'eudr.evidenceSubmissions.edit.title' },
|
|
11
13
|
],
|
|
12
14
|
}
|
|
@@ -7,6 +7,7 @@ export const metadata = {
|
|
|
7
7
|
pageGroupKey: 'eudr.nav.group',
|
|
8
8
|
navHidden: true,
|
|
9
9
|
breadcrumb: [
|
|
10
|
+
{ label: 'EUDR', labelKey: 'eudr.nav.module', href: '/backend/eudr' },
|
|
10
11
|
{ label: 'Evidence submissions', labelKey: 'eudr.nav.submissions', href: '/backend/eudr/evidence-submissions' },
|
|
11
12
|
{ label: 'Create', labelKey: 'eudr.evidenceSubmissions.create.title' },
|
|
12
13
|
],
|
|
@@ -8,5 +8,8 @@ export const metadata = {
|
|
|
8
8
|
pagePriority: 10,
|
|
9
9
|
pageOrder: 20,
|
|
10
10
|
icon: 'files',
|
|
11
|
-
breadcrumb: [
|
|
11
|
+
breadcrumb: [
|
|
12
|
+
{ label: 'EUDR', labelKey: 'eudr.nav.module', href: '/backend/eudr' },
|
|
13
|
+
{ label: 'Evidence submissions', labelKey: 'eudr.nav.submissions' },
|
|
14
|
+
],
|
|
12
15
|
}
|
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
export const metadata = {
|
|
2
2
|
requireAuth: true,
|
|
3
3
|
requireFeatures: ['eudr.statements.view'],
|
|
4
|
-
pageTitle: 'Overview',
|
|
4
|
+
pageTitle: 'EUDR Overview',
|
|
5
5
|
pageTitleKey: 'eudr.nav.overview',
|
|
6
6
|
pageGroup: 'Compliance',
|
|
7
7
|
pageGroupKey: 'eudr.nav.group',
|
|
8
8
|
pagePriority: 10,
|
|
9
9
|
pageOrder: 5,
|
|
10
10
|
icon: 'gauge',
|
|
11
|
-
breadcrumb: [{ label: 'Overview', labelKey: 'eudr.nav.overview' }],
|
|
11
|
+
breadcrumb: [{ label: 'EUDR Overview', labelKey: 'eudr.nav.overview' }],
|
|
12
12
|
}
|
|
@@ -7,6 +7,8 @@ export const metadata = {
|
|
|
7
7
|
pageGroupKey: 'eudr.nav.group',
|
|
8
8
|
navHidden: true,
|
|
9
9
|
breadcrumb: [
|
|
10
|
+
{ label: 'EUDR', labelKey: 'eudr.nav.module', href: '/backend/eudr' },
|
|
10
11
|
{ label: 'Plots', labelKey: 'eudr.nav.plots', href: '/backend/eudr/plots' },
|
|
12
|
+
{ label: 'Edit plot', labelKey: 'eudr.plots.edit.title' },
|
|
11
13
|
],
|
|
12
14
|
}
|
|
@@ -93,6 +93,8 @@ export default function EditEudrPlotPage({ params }: { params?: { id?: string }
|
|
|
93
93
|
const [loading, setLoading] = React.useState(true)
|
|
94
94
|
const [error, setError] = React.useState<string | null>(null)
|
|
95
95
|
const [notFound, setNotFound] = React.useState(false)
|
|
96
|
+
const [geometryDraft, setGeometryDraft] = React.useState<string | null>(null)
|
|
97
|
+
const areaRequired = !isPolygonGeometry(geometryDraft ?? formatGeometry(record?.geometry))
|
|
96
98
|
|
|
97
99
|
React.useEffect(() => {
|
|
98
100
|
let cancelled = false
|
|
@@ -197,7 +199,10 @@ export default function EditEudrPlotPage({ params }: { params?: { id?: string }
|
|
|
197
199
|
<GeometryInput
|
|
198
200
|
id={id}
|
|
199
201
|
value={typeof value === 'string' ? value : ''}
|
|
200
|
-
onChange={(nextValue) =>
|
|
202
|
+
onChange={(nextValue) => {
|
|
203
|
+
setValue(nextValue)
|
|
204
|
+
setGeometryDraft(nextValue)
|
|
205
|
+
}}
|
|
201
206
|
disabled={disabled}
|
|
202
207
|
areaHa={typeof values?.areaHa === 'string' || typeof values?.areaHa === 'number' ? String(values.areaHa) : ''}
|
|
203
208
|
/>
|
|
@@ -208,6 +213,7 @@ export default function EditEudrPlotPage({ params }: { params?: { id?: string }
|
|
|
208
213
|
layout: 'half',
|
|
209
214
|
label: translate('eudr.plots.form.areaHa'),
|
|
210
215
|
type: 'custom',
|
|
216
|
+
required: areaRequired,
|
|
211
217
|
description: translate('eudr.plots.form.areaHaHelp'),
|
|
212
218
|
component: ({ id, value, setValue, values, disabled }) => {
|
|
213
219
|
const polygonGeometry = isPolygonGeometry(values?.geometry)
|
|
@@ -241,7 +247,7 @@ export default function EditEudrPlotPage({ params }: { params?: { id?: string }
|
|
|
241
247
|
/>
|
|
242
248
|
),
|
|
243
249
|
},
|
|
244
|
-
], [translate])
|
|
250
|
+
], [translate, areaRequired])
|
|
245
251
|
|
|
246
252
|
const groups = React.useMemo<CrudFormGroup[]>(() => [
|
|
247
253
|
{
|
|
@@ -7,6 +7,7 @@ export const metadata = {
|
|
|
7
7
|
pageGroupKey: 'eudr.nav.group',
|
|
8
8
|
navHidden: true,
|
|
9
9
|
breadcrumb: [
|
|
10
|
+
{ label: 'EUDR', labelKey: 'eudr.nav.module', href: '/backend/eudr' },
|
|
10
11
|
{ label: 'Plots', labelKey: 'eudr.nav.plots', href: '/backend/eudr/plots' },
|
|
11
12
|
{ label: 'Create', labelKey: 'eudr.plots.create.title' },
|
|
12
13
|
],
|
|
@@ -50,6 +50,8 @@ function isCompanySnapshot(value: unknown): value is CompanySnapshot {
|
|
|
50
50
|
export default function CreateEudrPlotPage() {
|
|
51
51
|
const translate = useT()
|
|
52
52
|
const router = useRouter()
|
|
53
|
+
const [geometryDraft, setGeometryDraft] = React.useState('')
|
|
54
|
+
const areaRequired = !isPolygonGeometry(geometryDraft)
|
|
53
55
|
|
|
54
56
|
const fields = React.useMemo<CrudField[]>(() => [
|
|
55
57
|
{
|
|
@@ -113,7 +115,10 @@ export default function CreateEudrPlotPage() {
|
|
|
113
115
|
<GeometryInput
|
|
114
116
|
id={id}
|
|
115
117
|
value={typeof value === 'string' ? value : ''}
|
|
116
|
-
onChange={(nextValue) =>
|
|
118
|
+
onChange={(nextValue) => {
|
|
119
|
+
setValue(nextValue)
|
|
120
|
+
setGeometryDraft(nextValue)
|
|
121
|
+
}}
|
|
117
122
|
disabled={disabled}
|
|
118
123
|
areaHa={typeof values?.areaHa === 'string' || typeof values?.areaHa === 'number' ? String(values.areaHa) : ''}
|
|
119
124
|
/>
|
|
@@ -124,6 +129,7 @@ export default function CreateEudrPlotPage() {
|
|
|
124
129
|
layout: 'half',
|
|
125
130
|
label: translate('eudr.plots.form.areaHa'),
|
|
126
131
|
type: 'custom',
|
|
132
|
+
required: areaRequired,
|
|
127
133
|
description: translate('eudr.plots.form.areaHaHelp'),
|
|
128
134
|
component: ({ id, value, setValue, values, disabled }) => {
|
|
129
135
|
const polygonGeometry = isPolygonGeometry(values?.geometry)
|
|
@@ -158,7 +164,7 @@ export default function CreateEudrPlotPage() {
|
|
|
158
164
|
/>
|
|
159
165
|
),
|
|
160
166
|
},
|
|
161
|
-
], [translate])
|
|
167
|
+
], [translate, areaRequired])
|
|
162
168
|
|
|
163
169
|
const groups = React.useMemo<CrudFormGroup[]>(() => [
|
|
164
170
|
{
|
|
@@ -8,5 +8,8 @@ export const metadata = {
|
|
|
8
8
|
pagePriority: 10,
|
|
9
9
|
pageOrder: 20,
|
|
10
10
|
icon: 'map',
|
|
11
|
-
breadcrumb: [
|
|
11
|
+
breadcrumb: [
|
|
12
|
+
{ label: 'EUDR', labelKey: 'eudr.nav.module', href: '/backend/eudr' },
|
|
13
|
+
{ label: 'Plots', labelKey: 'eudr.nav.plots' },
|
|
14
|
+
],
|
|
12
15
|
}
|
|
@@ -7,6 +7,8 @@ export const metadata = {
|
|
|
7
7
|
pageGroupKey: 'eudr.nav.group',
|
|
8
8
|
navHidden: true,
|
|
9
9
|
breadcrumb: [
|
|
10
|
+
{ label: 'EUDR', labelKey: 'eudr.nav.module', href: '/backend/eudr' },
|
|
10
11
|
{ label: 'Product mappings', labelKey: 'eudr.nav.mappings', href: '/backend/eudr/product-mappings' },
|
|
12
|
+
{ label: 'Edit product mapping', labelKey: 'eudr.productMappings.edit.title' },
|
|
11
13
|
],
|
|
12
14
|
}
|
|
@@ -7,6 +7,7 @@ export const metadata = {
|
|
|
7
7
|
pageGroupKey: 'eudr.nav.group',
|
|
8
8
|
navHidden: true,
|
|
9
9
|
breadcrumb: [
|
|
10
|
+
{ label: 'EUDR', labelKey: 'eudr.nav.module', href: '/backend/eudr' },
|
|
10
11
|
{ label: 'Product mappings', labelKey: 'eudr.nav.mappings', href: '/backend/eudr/product-mappings' },
|
|
11
12
|
{ label: 'Create', labelKey: 'eudr.productMappings.create.title' },
|
|
12
13
|
],
|
|
@@ -8,5 +8,8 @@ export const metadata = {
|
|
|
8
8
|
pagePriority: 10,
|
|
9
9
|
pageOrder: 10,
|
|
10
10
|
icon: 'package',
|
|
11
|
-
breadcrumb: [
|
|
11
|
+
breadcrumb: [
|
|
12
|
+
{ label: 'EUDR', labelKey: 'eudr.nav.module', href: '/backend/eudr' },
|
|
13
|
+
{ label: 'Product mappings', labelKey: 'eudr.nav.mappings' },
|
|
14
|
+
],
|
|
12
15
|
}
|
|
@@ -7,6 +7,8 @@ export const metadata = {
|
|
|
7
7
|
pageGroupKey: 'eudr.nav.group',
|
|
8
8
|
navHidden: true,
|
|
9
9
|
breadcrumb: [
|
|
10
|
+
{ label: 'EUDR', labelKey: 'eudr.nav.module', href: '/backend/eudr' },
|
|
10
11
|
{ label: 'Risk assessments', labelKey: 'eudr.nav.riskAssessments', href: '/backend/eudr/risk-assessments' },
|
|
12
|
+
{ label: 'Edit risk assessment', labelKey: 'eudr.riskAssessments.edit.title' },
|
|
11
13
|
],
|
|
12
14
|
}
|
|
@@ -7,6 +7,7 @@ export const metadata = {
|
|
|
7
7
|
pageGroupKey: 'eudr.nav.group',
|
|
8
8
|
navHidden: true,
|
|
9
9
|
breadcrumb: [
|
|
10
|
+
{ label: 'EUDR', labelKey: 'eudr.nav.module', href: '/backend/eudr' },
|
|
10
11
|
{ label: 'Risk assessments', labelKey: 'eudr.nav.riskAssessments', href: '/backend/eudr/risk-assessments' },
|
|
11
12
|
{ label: 'Create', labelKey: 'eudr.riskAssessments.create.title' },
|
|
12
13
|
],
|
|
@@ -8,5 +8,8 @@ export const metadata = {
|
|
|
8
8
|
pagePriority: 10,
|
|
9
9
|
pageOrder: 40,
|
|
10
10
|
icon: 'shield-alert',
|
|
11
|
-
breadcrumb: [
|
|
11
|
+
breadcrumb: [
|
|
12
|
+
{ label: 'EUDR', labelKey: 'eudr.nav.module', href: '/backend/eudr' },
|
|
13
|
+
{ label: 'Risk assessments', labelKey: 'eudr.nav.riskAssessments' },
|
|
14
|
+
],
|
|
12
15
|
}
|
|
@@ -7,6 +7,8 @@ export const metadata = {
|
|
|
7
7
|
pageGroupKey: 'eudr.nav.group',
|
|
8
8
|
navHidden: true,
|
|
9
9
|
breadcrumb: [
|
|
10
|
+
{ label: 'EUDR', labelKey: 'eudr.nav.module', href: '/backend/eudr' },
|
|
10
11
|
{ label: 'Statements', labelKey: 'eudr.nav.statements', href: '/backend/eudr/statements' },
|
|
12
|
+
{ label: 'Edit statement', labelKey: 'eudr.statements.edit.title' },
|
|
11
13
|
],
|
|
12
14
|
}
|
|
@@ -29,6 +29,7 @@ import {
|
|
|
29
29
|
type ReferencedStatementValue,
|
|
30
30
|
translateEudrCrudError,
|
|
31
31
|
} from '../../../../components/formConfig'
|
|
32
|
+
import { canDeleteStatement } from '../../../../lib/statement-lifecycle'
|
|
32
33
|
import { StatementLifecycleBar } from '../../../../components/StatementLifecycleBar'
|
|
33
34
|
import { StatementReadinessChecklist } from '../../../../components/StatementReadinessChecklist'
|
|
34
35
|
import { StatementRiskSection, type StatementLatestRisk } from '../../../../components/StatementRiskSection'
|
|
@@ -644,6 +645,7 @@ export default function EditEudrStatementPage({ params }: { params?: { id?: stri
|
|
|
644
645
|
backHref="/backend/eudr/statements"
|
|
645
646
|
cancelHref="/backend/eudr/statements"
|
|
646
647
|
deleteRedirect="/backend/eudr/statements"
|
|
648
|
+
deleteVisible={canDeleteStatement(record.status)}
|
|
647
649
|
submitLabel={translate('eudr.statements.form.submitUpdate')}
|
|
648
650
|
fields={fields}
|
|
649
651
|
groups={groups}
|
|
@@ -7,6 +7,7 @@ export const metadata = {
|
|
|
7
7
|
pageGroupKey: 'eudr.nav.group',
|
|
8
8
|
navHidden: true,
|
|
9
9
|
breadcrumb: [
|
|
10
|
+
{ label: 'EUDR', labelKey: 'eudr.nav.module', href: '/backend/eudr' },
|
|
10
11
|
{ label: 'Statements', labelKey: 'eudr.nav.statements', href: '/backend/eudr/statements' },
|
|
11
12
|
{ label: 'Create', labelKey: 'eudr.statements.create.title' },
|
|
12
13
|
],
|
|
@@ -8,5 +8,8 @@ export const metadata = {
|
|
|
8
8
|
pagePriority: 10,
|
|
9
9
|
pageOrder: 30,
|
|
10
10
|
icon: 'file-text',
|
|
11
|
-
breadcrumb: [
|
|
11
|
+
breadcrumb: [
|
|
12
|
+
{ label: 'EUDR', labelKey: 'eudr.nav.module', href: '/backend/eudr' },
|
|
13
|
+
{ label: 'Statements', labelKey: 'eudr.nav.statements' },
|
|
14
|
+
],
|
|
12
15
|
}
|
|
@@ -6,7 +6,7 @@ import { useRouter } from 'next/navigation'
|
|
|
6
6
|
import { Page, PageBody } from '@open-mercato/ui/backend/Page'
|
|
7
7
|
import { DataTable } from '@open-mercato/ui/backend/DataTable'
|
|
8
8
|
import { ListEmptyState } from '@open-mercato/ui/backend/filters/ListEmptyState'
|
|
9
|
-
import { RowActions } from '@open-mercato/ui/backend/RowActions'
|
|
9
|
+
import { RowActions, type RowActionItem } from '@open-mercato/ui/backend/RowActions'
|
|
10
10
|
import type { FilterDef, FilterValues } from '@open-mercato/ui/backend/FilterBar'
|
|
11
11
|
import { apiCall, withScopedApiRequestHeaders } from '@open-mercato/ui/backend/utils/apiCall'
|
|
12
12
|
import { buildOptimisticLockHeader } from '@open-mercato/ui/backend/utils/optimisticLock'
|
|
@@ -22,7 +22,8 @@ import type { LegacyColumnDef as ColumnDef } from '@tanstack/react-table/legacy'
|
|
|
22
22
|
import { useOrganizationScopeVersion } from '@open-mercato/shared/lib/frontend/useOrganizationScope'
|
|
23
23
|
import { useLocale, useT } from '@open-mercato/shared/lib/i18n/context'
|
|
24
24
|
import type { EudrActivityType, EudrCommodity, EudrRiskConclusion, EudrRiskTier, EudrStatementStatus } from '../../../data/validators'
|
|
25
|
-
import { commodityOptions, statementStatusOptions, statusBadgeVariant } from '../../../components/formConfig'
|
|
25
|
+
import { commodityOptions, statementStatusOptions, statusBadgeVariant, translateEudrErrorMessage } from '../../../components/formConfig'
|
|
26
|
+
import { canDeleteStatement } from '../../../lib/statement-lifecycle'
|
|
26
27
|
import {
|
|
27
28
|
riskConclusionBadgeVariant,
|
|
28
29
|
riskTierBadgeVariant,
|
|
@@ -189,7 +190,11 @@ export default function EudrStatementsPage() {
|
|
|
189
190
|
refreshRows()
|
|
190
191
|
} catch (error) {
|
|
191
192
|
if (surfaceRecordConflict(error, translate, { onRefresh: refreshRows })) return
|
|
192
|
-
flash(
|
|
193
|
+
flash(
|
|
194
|
+
translateEudrErrorMessage(error, translate, translate('eudr.statements.list.deleteError')),
|
|
195
|
+
'error',
|
|
196
|
+
)
|
|
197
|
+
refreshRows()
|
|
193
198
|
}
|
|
194
199
|
}, [confirm, mutationContextId, refreshRows, retryLastMutation, runMutation, translate])
|
|
195
200
|
|
|
@@ -312,30 +317,31 @@ export default function EudrStatementsPage() {
|
|
|
312
317
|
</Link>
|
|
313
318
|
</Button>
|
|
314
319
|
)}
|
|
315
|
-
rowActions={(row) =>
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
320
|
+
rowActions={(row) => {
|
|
321
|
+
const items: RowActionItem[] = [
|
|
322
|
+
{
|
|
323
|
+
id: 'edit',
|
|
324
|
+
label: translate('eudr.statements.list.actions.edit'),
|
|
325
|
+
href: `/backend/eudr/statements/${row.id}`,
|
|
326
|
+
},
|
|
327
|
+
{
|
|
328
|
+
id: 'duplicate',
|
|
329
|
+
label: translate('eudr.statements.duplicateAction'),
|
|
330
|
+
href: `/backend/eudr/statements/create?duplicateFrom=${encodeURIComponent(row.id)}`,
|
|
331
|
+
},
|
|
332
|
+
]
|
|
333
|
+
if (canDeleteStatement(row.status)) {
|
|
334
|
+
items.push({
|
|
335
|
+
id: 'delete',
|
|
336
|
+
label: translate('eudr.statements.list.actions.delete'),
|
|
337
|
+
destructive: true,
|
|
338
|
+
onSelect: () => {
|
|
339
|
+
void handleDelete(row)
|
|
335
340
|
},
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
341
|
+
})
|
|
342
|
+
}
|
|
343
|
+
return <RowActions items={items} />
|
|
344
|
+
}}
|
|
339
345
|
onRowClick={(row) => router.push(`/backend/eudr/statements/${row.id}`)}
|
|
340
346
|
rowClickActionIds={['edit']}
|
|
341
347
|
emptyState={(
|
|
@@ -43,9 +43,11 @@ import {
|
|
|
43
43
|
} from '../data/validators'
|
|
44
44
|
import {
|
|
45
45
|
EUDR_AMEND_GUARDED_FIELDS,
|
|
46
|
+
canDeleteStatement,
|
|
46
47
|
canTransition,
|
|
47
48
|
evaluateSubmissionGate,
|
|
48
49
|
isAmendWindowOpen,
|
|
50
|
+
isStatementReadOnly,
|
|
49
51
|
type GateAssessmentView,
|
|
50
52
|
type GateSubmissionView,
|
|
51
53
|
} from '../lib/statement-lifecycle'
|
|
@@ -495,7 +497,7 @@ async function validateStatementUpdate(
|
|
|
495
497
|
}
|
|
496
498
|
|
|
497
499
|
const hasBaseChanges = hasStatementFieldChanges(record, parsed)
|
|
498
|
-
if (record.status
|
|
500
|
+
if (isStatementReadOnly(record.status) && (hasBaseChanges || Object.keys(custom).length > 0)) {
|
|
499
501
|
throw new CrudHttpError(400, { error: 'eudr.errors.archivedReadOnly' })
|
|
500
502
|
}
|
|
501
503
|
|
|
@@ -841,7 +843,7 @@ const deleteStatementCommand: CommandHandler<{ body?: Record<string, unknown>; q
|
|
|
841
843
|
if (!record) throw new CrudHttpError(404, { error: 'EUDR due diligence statement not found' })
|
|
842
844
|
ensureTenantScope(ctx, record.tenantId)
|
|
843
845
|
ensureOrganizationScope(ctx, record.organizationId)
|
|
844
|
-
if (record.status
|
|
846
|
+
if (isStatementStatus(record.status) && !canDeleteStatement(record.status)) {
|
|
845
847
|
throw new CrudHttpError(400, { error: 'eudr.errors.archivedReadOnly' })
|
|
846
848
|
}
|
|
847
849
|
if (record.status === 'available') {
|
|
@@ -15,6 +15,37 @@ function translateEudrErrorToken(token: string, translate: Translator): string {
|
|
|
15
15
|
return translated === trimmed ? token : translated
|
|
16
16
|
}
|
|
17
17
|
|
|
18
|
+
function readEudrErrorToken(err: unknown): string | null {
|
|
19
|
+
if (err == null || typeof err !== 'object') return null
|
|
20
|
+
const candidates = [
|
|
21
|
+
(err as { error?: unknown }).error,
|
|
22
|
+
(err as { message?: unknown }).message,
|
|
23
|
+
]
|
|
24
|
+
for (const candidate of candidates) {
|
|
25
|
+
if (typeof candidate !== 'string') continue
|
|
26
|
+
const trimmed = candidate.trim()
|
|
27
|
+
if (EUDR_ERROR_KEY_PATTERN.test(trimmed)) return trimmed
|
|
28
|
+
}
|
|
29
|
+
return null
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Resolves the user-facing message for a rejected EUDR mutation, preferring the
|
|
34
|
+
* server's `eudr.errors.*` token over the caller's generic copy so the reason
|
|
35
|
+
* survives to the user. Falls back to `fallbackMessage` when the rejection
|
|
36
|
+
* carries no known token (network errors, 5xx, unmapped tokens).
|
|
37
|
+
*/
|
|
38
|
+
export function translateEudrErrorMessage(
|
|
39
|
+
err: unknown,
|
|
40
|
+
translate: Translator,
|
|
41
|
+
fallbackMessage: string,
|
|
42
|
+
): string {
|
|
43
|
+
const token = readEudrErrorToken(err)
|
|
44
|
+
if (!token) return fallbackMessage
|
|
45
|
+
const translated = translate(token)
|
|
46
|
+
return translated === token ? fallbackMessage : translated
|
|
47
|
+
}
|
|
48
|
+
|
|
18
49
|
export function translateEudrCrudError(err: unknown, translate: Translator): unknown {
|
|
19
50
|
if (!(err instanceof Error)) return err
|
|
20
51
|
const rawMessage = typeof err.message === 'string' ? err.message : ''
|
|
@@ -247,8 +247,16 @@ export function LookupSelectField<Snapshot extends Record<string, unknown>>({
|
|
|
247
247
|
}
|
|
248
248
|
}, [loadSelectedOption, selectedOption, selectedValue, translate])
|
|
249
249
|
|
|
250
|
+
// A snapshot is a denormalization captured when the user picks a record, so resolving the
|
|
251
|
+
// value an edit form was hydrated with must not re-emit it. Doing so overwrites the stored
|
|
252
|
+
// snapshot with a freshly fetched one, which makes an untouched CrudForm look dirty and
|
|
253
|
+
// raises the "You have unsaved changes" prompt on the way out.
|
|
254
|
+
const hydratedValueRef = React.useRef<string | null>(selectedValue)
|
|
255
|
+
|
|
250
256
|
React.useEffect(() => {
|
|
251
257
|
if (!onSnapshot || !selectedOption || selectedOption.unavailable) return
|
|
258
|
+
if (hydratedValueRef.current === selectedOption.value) return
|
|
259
|
+
hydratedValueRef.current = null
|
|
252
260
|
onSnapshot(selectedOption.snapshot ?? null)
|
|
253
261
|
}, [onSnapshot, selectedOption])
|
|
254
262
|
|
|
@@ -592,7 +600,7 @@ export function parseGeolocationInput(raw: string, translate: Translator): Recor
|
|
|
592
600
|
return parsed
|
|
593
601
|
}
|
|
594
602
|
|
|
595
|
-
export { translateEudrCrudError } from './crudErrorI18n'
|
|
603
|
+
export { translateEudrCrudError, translateEudrErrorMessage } from './crudErrorI18n'
|
|
596
604
|
|
|
597
605
|
let referencedStatementRowKeySeq = 0
|
|
598
606
|
|
|
@@ -239,7 +239,7 @@
|
|
|
239
239
|
"eudr.lifecycle.amendWindowClosed": "Änderungsfenster geschlossen",
|
|
240
240
|
"eudr.lifecycle.amendWindowRemaining": "Verbleibendes Änderungsfenster: {hours} Std. {minutes} Min.",
|
|
241
241
|
"eudr.lifecycle.confirmArchive": "Erklärung archivieren?",
|
|
242
|
-
"eudr.lifecycle.confirmArchiveBody": "Die Erklärung wird archiviert.",
|
|
242
|
+
"eudr.lifecycle.confirmArchiveBody": "Die Erklärung wird archiviert. Archivierte Erklärungen sind schreibgeschützt und können nicht bearbeitet, wiederhergestellt oder gelöscht werden.",
|
|
243
243
|
"eudr.lifecycle.confirmMarkAvailable": "Erklärung als verfügbar markieren?",
|
|
244
244
|
"eudr.lifecycle.confirmMarkAvailableBody": "Referenzdetails sind erforderlich, bevor die Erklärung verfügbar wird.",
|
|
245
245
|
"eudr.lifecycle.confirmReturnToDraft": "Erklärung in Entwurf zurücksetzen?",
|
|
@@ -313,7 +313,8 @@
|
|
|
313
313
|
"eudr.mitigation_actions.update": "EUDR-Minderungsmaßnahme aktualisieren",
|
|
314
314
|
"eudr.nav.group": "Compliance",
|
|
315
315
|
"eudr.nav.mappings": "Produktzuordnungen",
|
|
316
|
-
"eudr.nav.
|
|
316
|
+
"eudr.nav.module": "EUDR",
|
|
317
|
+
"eudr.nav.overview": "EUDR-Übersicht",
|
|
317
318
|
"eudr.nav.plots": "Parzellen",
|
|
318
319
|
"eudr.nav.riskAssessments": "Risikobewertungen",
|
|
319
320
|
"eudr.nav.statements": "Erklärungen",
|