@open-mercato/core 0.6.4-develop.4363.1.2f376570ae → 0.6.4-develop.4368.1.2f7e9a7002

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (61) hide show
  1. package/dist/modules/catalog/backend/catalog/categories/[id]/edit/page.js +66 -44
  2. package/dist/modules/catalog/backend/catalog/categories/[id]/edit/page.js.map +2 -2
  3. package/dist/modules/catalog/backend/catalog/products/[productId]/variants/[variantId]/page.js +124 -103
  4. package/dist/modules/catalog/backend/catalog/products/[productId]/variants/[variantId]/page.js.map +2 -2
  5. package/dist/modules/directory/backend/directory/organizations/[id]/edit/page.js +26 -4
  6. package/dist/modules/directory/backend/directory/organizations/[id]/edit/page.js.map +2 -2
  7. package/dist/modules/directory/backend/directory/tenants/[id]/edit/page.js +24 -4
  8. package/dist/modules/directory/backend/directory/tenants/[id]/edit/page.js.map +2 -2
  9. package/dist/modules/planner/backend/planner/availability-rulesets/[id]/page.js +35 -6
  10. package/dist/modules/planner/backend/planner/availability-rulesets/[id]/page.js.map +3 -3
  11. package/dist/modules/sales/backend/sales/channels/[channelId]/edit/page.js +67 -47
  12. package/dist/modules/sales/backend/sales/channels/[channelId]/edit/page.js.map +2 -2
  13. package/dist/modules/staff/backend/staff/leave-requests/[id]/page.js +29 -6
  14. package/dist/modules/staff/backend/staff/leave-requests/[id]/page.js.map +2 -2
  15. package/dist/modules/staff/backend/staff/my-leave-requests/[id]/page.js +29 -6
  16. package/dist/modules/staff/backend/staff/my-leave-requests/[id]/page.js.map +2 -2
  17. package/dist/modules/staff/backend/staff/team-members/[id]/page.js +33 -4
  18. package/dist/modules/staff/backend/staff/team-members/[id]/page.js.map +2 -2
  19. package/dist/modules/staff/backend/staff/team-roles/[id]/edit/page.js +33 -4
  20. package/dist/modules/staff/backend/staff/team-roles/[id]/edit/page.js.map +2 -2
  21. package/dist/modules/staff/backend/staff/teams/[id]/edit/page.js +37 -8
  22. package/dist/modules/staff/backend/staff/teams/[id]/edit/page.js.map +3 -3
  23. package/dist/modules/workflows/backend/definitions/[id]/page.js +24 -6
  24. package/dist/modules/workflows/backend/definitions/[id]/page.js.map +2 -2
  25. package/package.json +7 -7
  26. package/src/modules/catalog/backend/catalog/categories/[id]/edit/page.tsx +39 -8
  27. package/src/modules/catalog/backend/catalog/products/[productId]/variants/[variantId]/page.tsx +38 -6
  28. package/src/modules/catalog/i18n/de.json +2 -0
  29. package/src/modules/catalog/i18n/en.json +2 -0
  30. package/src/modules/catalog/i18n/es.json +2 -0
  31. package/src/modules/catalog/i18n/pl.json +2 -0
  32. package/src/modules/directory/backend/directory/organizations/[id]/edit/page.tsx +30 -4
  33. package/src/modules/directory/backend/directory/tenants/[id]/edit/page.tsx +28 -6
  34. package/src/modules/directory/i18n/de.json +2 -0
  35. package/src/modules/directory/i18n/en.json +2 -0
  36. package/src/modules/directory/i18n/es.json +2 -0
  37. package/src/modules/directory/i18n/pl.json +2 -0
  38. package/src/modules/planner/backend/planner/availability-rulesets/[id]/page.tsx +44 -4
  39. package/src/modules/planner/i18n/de.json +1 -0
  40. package/src/modules/planner/i18n/en.json +1 -0
  41. package/src/modules/planner/i18n/es.json +1 -0
  42. package/src/modules/planner/i18n/pl.json +1 -0
  43. package/src/modules/sales/backend/sales/channels/[channelId]/edit/page.tsx +36 -7
  44. package/src/modules/sales/i18n/de.json +1 -0
  45. package/src/modules/sales/i18n/en.json +1 -0
  46. package/src/modules/sales/i18n/es.json +1 -0
  47. package/src/modules/sales/i18n/pl.json +1 -0
  48. package/src/modules/staff/backend/staff/leave-requests/[id]/page.tsx +33 -6
  49. package/src/modules/staff/backend/staff/my-leave-requests/[id]/page.tsx +33 -6
  50. package/src/modules/staff/backend/staff/team-members/[id]/page.tsx +44 -4
  51. package/src/modules/staff/backend/staff/team-roles/[id]/edit/page.tsx +44 -4
  52. package/src/modules/staff/backend/staff/teams/[id]/edit/page.tsx +44 -4
  53. package/src/modules/staff/i18n/de.json +5 -0
  54. package/src/modules/staff/i18n/en.json +5 -0
  55. package/src/modules/staff/i18n/es.json +5 -0
  56. package/src/modules/staff/i18n/pl.json +5 -0
  57. package/src/modules/workflows/backend/definitions/[id]/page.tsx +26 -3
  58. package/src/modules/workflows/i18n/de.json +1 -0
  59. package/src/modules/workflows/i18n/en.json +1 -0
  60. package/src/modules/workflows/i18n/es.json +1 -0
  61. package/src/modules/workflows/i18n/pl.json +1 -0
@@ -40,6 +40,7 @@
40
40
  "catalog.categories.flash.updated": "Category updated",
41
41
  "catalog.categories.form.action.create": "Create",
42
42
  "catalog.categories.form.action.save": "Save",
43
+ "catalog.categories.form.actions.backToList": "Back to categories",
43
44
  "catalog.categories.form.createTitle": "Create category",
44
45
  "catalog.categories.form.editTitle": "Edit category",
45
46
  "catalog.categories.form.errors.delete": "Failed to delete category",
@@ -559,6 +560,7 @@
559
560
  "catalog.stats.tags": "Tags",
560
561
  "catalog.stats.title": "Catalog overview",
561
562
  "catalog.variants.errors.skuExists": "SKU already in use.",
563
+ "catalog.variants.form.actions.backToProduct": "Back to product variants",
562
564
  "catalog.variants.form.barcodeLabel": "Barcode",
563
565
  "catalog.variants.form.barcodePlaceholder": "EAN, UPC, etc.",
564
566
  "catalog.variants.form.createAction": "Create variant",
@@ -40,6 +40,7 @@
40
40
  "catalog.categories.flash.updated": "Categoría actualizada",
41
41
  "catalog.categories.form.action.create": "Crear",
42
42
  "catalog.categories.form.action.save": "Guardar",
43
+ "catalog.categories.form.actions.backToList": "Volver a categorías",
43
44
  "catalog.categories.form.createTitle": "Crear categoría",
44
45
  "catalog.categories.form.editTitle": "Editar categoría",
45
46
  "catalog.categories.form.errors.delete": "No se pudo eliminar la categoría",
@@ -559,6 +560,7 @@
559
560
  "catalog.stats.tags": "Etiquetas",
560
561
  "catalog.stats.title": "Resumen del catálogo",
561
562
  "catalog.variants.errors.skuExists": "El SKU ya está en uso.",
563
+ "catalog.variants.form.actions.backToProduct": "Volver a variantes del producto",
562
564
  "catalog.variants.form.barcodeLabel": "Código de barras",
563
565
  "catalog.variants.form.barcodePlaceholder": "EAN, UPC, etc.",
564
566
  "catalog.variants.form.createAction": "Crear variante",
@@ -40,6 +40,7 @@
40
40
  "catalog.categories.flash.updated": "Kategoria zaktualizowana",
41
41
  "catalog.categories.form.action.create": "Utwórz",
42
42
  "catalog.categories.form.action.save": "Zapisz",
43
+ "catalog.categories.form.actions.backToList": "Powrót do kategorii",
43
44
  "catalog.categories.form.createTitle": "Utwórz kategorię",
44
45
  "catalog.categories.form.editTitle": "Edytuj kategorię",
45
46
  "catalog.categories.form.errors.delete": "Nie udało się usunąć kategorii",
@@ -559,6 +560,7 @@
559
560
  "catalog.stats.tags": "Tagi",
560
561
  "catalog.stats.title": "Przegląd katalogu",
561
562
  "catalog.variants.errors.skuExists": "SKU jest już używany.",
563
+ "catalog.variants.form.actions.backToProduct": "Powrót do wariantów produktu",
562
564
  "catalog.variants.form.barcodeLabel": "Kod kreskowy",
563
565
  "catalog.variants.form.barcodePlaceholder": "EAN, UPC itp.",
564
566
  "catalog.variants.form.createAction": "Utwórz wariant",
@@ -16,6 +16,7 @@ import { apiCall } from '@open-mercato/ui/backend/utils/apiCall'
16
16
  import { deleteCrud, updateCrud } from '@open-mercato/ui/backend/utils/crud'
17
17
  import { collectCustomFieldValues } from '@open-mercato/ui/backend/utils/customFieldValues'
18
18
  import { createCrudFormError } from '@open-mercato/ui/backend/utils/serverErrors'
19
+ import { ErrorMessage, RecordNotFoundState } from '@open-mercato/ui/backend/detail'
19
20
 
20
21
  type TreeResponse = {
21
22
  items: OrganizationTreeNode[]
@@ -48,6 +49,7 @@ export default function EditOrganizationPage({ params }: { params?: { id?: strin
48
49
  const [tenantId, setTenantId] = React.useState<string | null>(null)
49
50
  const [loading, setLoading] = React.useState(true)
50
51
  const [error, setError] = React.useState<string | null>(null)
52
+ const [isNotFound, setIsNotFound] = React.useState(false)
51
53
  const [parentTree, setParentTree] = React.useState<OrganizationTreeNode[]>([])
52
54
  const [childSummary, setChildSummary] = React.useState<OrganizationTreeOption[]>([])
53
55
  const [originalChildIds, setOriginalChildIds] = React.useState<string[]>([])
@@ -107,13 +109,23 @@ export default function EditOrganizationPage({ params }: { params?: { id?: strin
107
109
  async function load() {
108
110
  setLoading(true)
109
111
  setError(null)
112
+ setIsNotFound(false)
110
113
  try {
111
- const { ok, result } = await apiCall<OrganizationResponse>(
114
+ const { ok, status, result } = await apiCall<OrganizationResponse>(
112
115
  `/api/directory/organizations?view=manage&ids=${currentOrgId}&status=all&includeInactive=true&page=1&pageSize=1`,
113
116
  )
114
- if (!ok) throw new Error(t('directory.organizations.form.errors.load', 'Failed to load organization'))
117
+ if (!ok) {
118
+ if (status === 404) {
119
+ if (!cancelled) setIsNotFound(true)
120
+ return
121
+ }
122
+ throw new Error(t('directory.organizations.form.errors.load', 'Failed to load organization'))
123
+ }
115
124
  const record = Array.isArray(result?.items) ? result.items?.[0] : undefined
116
- if (!record) throw new Error(t('directory.organizations.form.errors.notFound', 'Organization not found'))
125
+ if (!record) {
126
+ if (!cancelled) setIsNotFound(true)
127
+ return
128
+ }
117
129
  const resolvedTenantId = record.tenantId || null
118
130
  setTenantId(resolvedTenantId)
119
131
  const baseTree = await loadParentTree(resolvedTenantId, record.descendantIds ?? [])
@@ -263,11 +275,25 @@ export default function EditOrganizationPage({ params }: { params?: { id?: strin
263
275
  )
264
276
  }
265
277
 
278
+ if (isNotFound) {
279
+ return (
280
+ <Page>
281
+ <PageBody>
282
+ <RecordNotFoundState
283
+ label={t('directory.organizations.form.errors.notFound', 'Organization not found')}
284
+ backHref="/backend/directory/organizations"
285
+ backLabel={t('directory.organizations.form.actions.backToList', 'Back to organizations')}
286
+ />
287
+ </PageBody>
288
+ </Page>
289
+ )
290
+ }
291
+
266
292
  if (error && !loading && !initialValues) {
267
293
  return (
268
294
  <Page>
269
295
  <PageBody>
270
- <div className="rounded border border-destructive/40 bg-destructive/10 px-4 py-3 text-sm text-destructive">{error}</div>
296
+ <ErrorMessage label={error} />
271
297
  </PageBody>
272
298
  </Page>
273
299
  )
@@ -7,6 +7,7 @@ import { collectCustomFieldValues } from '@open-mercato/ui/backend/utils/customF
7
7
  import { updateCrud } from '@open-mercato/ui/backend/utils/crud'
8
8
  import { raiseCrudError } from '@open-mercato/ui/backend/utils/serverErrors'
9
9
  import { readApiResultOrThrow, apiCall } from '@open-mercato/ui/backend/utils/apiCall'
10
+ import { ErrorMessage, RecordNotFoundState } from '@open-mercato/ui/backend/detail'
10
11
  import { useT } from '@open-mercato/shared/lib/i18n/context'
11
12
  import { extractCustomFieldEntries } from '@open-mercato/shared/lib/crud/custom-fields-client'
12
13
 
@@ -21,6 +22,7 @@ export default function EditTenantPage({ params }: { params?: { id?: string } })
21
22
  const [initial, setInitial] = React.useState<TenantFormValues | null>(null)
22
23
  const [loading, setLoading] = React.useState(true)
23
24
  const [error, setError] = React.useState<string | null>(null)
25
+ const [isNotFound, setIsNotFound] = React.useState(false)
24
26
  const t = useT()
25
27
  const fields = React.useMemo<CrudField[]>(() => [
26
28
  { id: 'name', label: t('directory.tenants.form.fields.name', 'Name'), type: 'text', required: true },
@@ -37,6 +39,7 @@ export default function EditTenantPage({ params }: { params?: { id?: string } })
37
39
  if (!tenantId) return
38
40
  setLoading(true)
39
41
  setError(null)
42
+ setIsNotFound(false)
40
43
  try {
41
44
  const data = await readApiResultOrThrow<{ items?: Record<string, unknown>[] }>(
42
45
  `/api/directory/tenants?id=${encodeURIComponent(tenantId)}`,
@@ -45,7 +48,10 @@ export default function EditTenantPage({ params }: { params?: { id?: string } })
45
48
  )
46
49
  const rows = Array.isArray(data?.items) ? data.items : []
47
50
  const row = rows[0]
48
- if (!row) throw new Error(t('directory.tenants.form.errors.notFound', 'Tenant not found'))
51
+ if (!row) {
52
+ if (!cancelled) setIsNotFound(true)
53
+ return
54
+ }
49
55
  const cfValues = extractCustomFieldEntries(row as Record<string, unknown>)
50
56
  const values: TenantFormValues = {
51
57
  id: String(row.id),
@@ -56,8 +62,12 @@ export default function EditTenantPage({ params }: { params?: { id?: string } })
56
62
  if (!cancelled) setInitial(values)
57
63
  } catch (err) {
58
64
  if (!cancelled) {
59
- const message = err instanceof Error ? err.message : t('directory.tenants.form.errors.load', 'Failed to load tenant')
60
- setError(message)
65
+ if ((err as { status?: number }).status === 404) {
66
+ setIsNotFound(true)
67
+ } else {
68
+ const message = err instanceof Error ? err.message : t('directory.tenants.form.errors.load', 'Failed to load tenant')
69
+ setError(message)
70
+ }
61
71
  }
62
72
  } finally {
63
73
  if (!cancelled) setLoading(false)
@@ -69,13 +79,25 @@ export default function EditTenantPage({ params }: { params?: { id?: string } })
69
79
 
70
80
  if (!tenantId) return null
71
81
 
82
+ if (isNotFound) {
83
+ return (
84
+ <Page>
85
+ <PageBody>
86
+ <RecordNotFoundState
87
+ label={t('directory.tenants.form.errors.notFound', 'Tenant not found')}
88
+ backHref="/backend/directory/tenants"
89
+ backLabel={t('directory.tenants.form.actions.backToList', 'Back to tenants')}
90
+ />
91
+ </PageBody>
92
+ </Page>
93
+ )
94
+ }
95
+
72
96
  if (error && !loading && !initial) {
73
97
  return (
74
98
  <Page>
75
99
  <PageBody>
76
- <div className="rounded border border-destructive/40 bg-destructive/10 px-4 py-3 text-sm text-destructive">
77
- {error}
78
- </div>
100
+ <ErrorMessage label={error} />
79
101
  </PageBody>
80
102
  </Page>
81
103
  )
@@ -19,6 +19,7 @@
19
19
  "directory.organizations.flash.updated": "Organisation aktualisiert",
20
20
  "directory.organizations.form.action.create": "Erstellen",
21
21
  "directory.organizations.form.action.save": "Speichern",
22
+ "directory.organizations.form.actions.backToList": "Zurück zu Organisationen",
22
23
  "directory.organizations.form.children.empty": "Keine direkten Unterorganisationen zugewiesen.",
23
24
  "directory.organizations.form.errors.load": "Organisation konnte nicht geladen werden",
24
25
  "directory.organizations.form.errors.missingId": "Organisationskennung fehlt.",
@@ -55,6 +56,7 @@
55
56
  "directory.organizations.list.filters.status": "Status",
56
57
  "directory.organizations.list.searchPlaceholder": "Organisationen durchsuchen",
57
58
  "directory.organizations.list.title": "Organisationen",
59
+ "directory.tenants.form.actions.backToList": "Zurück zu Mandanten",
58
60
  "directory.tenants.form.errors.delete": "Mandant konnte nicht gelöscht werden",
59
61
  "directory.tenants.form.errors.load": "Mandant konnte nicht geladen werden",
60
62
  "directory.tenants.form.errors.notFound": "Mandant nicht gefunden",
@@ -19,6 +19,7 @@
19
19
  "directory.organizations.flash.updated": "Organization updated",
20
20
  "directory.organizations.form.action.create": "Create",
21
21
  "directory.organizations.form.action.save": "Save",
22
+ "directory.organizations.form.actions.backToList": "Back to organizations",
22
23
  "directory.organizations.form.children.empty": "No direct children assigned.",
23
24
  "directory.organizations.form.errors.load": "Failed to load organization",
24
25
  "directory.organizations.form.errors.missingId": "Organization identifier is missing.",
@@ -55,6 +56,7 @@
55
56
  "directory.organizations.list.filters.status": "Status",
56
57
  "directory.organizations.list.searchPlaceholder": "Search organizations",
57
58
  "directory.organizations.list.title": "Organizations",
59
+ "directory.tenants.form.actions.backToList": "Back to tenants",
58
60
  "directory.tenants.form.errors.delete": "Failed to delete tenant",
59
61
  "directory.tenants.form.errors.load": "Failed to load tenant",
60
62
  "directory.tenants.form.errors.notFound": "Tenant not found",
@@ -19,6 +19,7 @@
19
19
  "directory.organizations.flash.updated": "Organización actualizada",
20
20
  "directory.organizations.form.action.create": "Crear",
21
21
  "directory.organizations.form.action.save": "Guardar",
22
+ "directory.organizations.form.actions.backToList": "Volver a organizaciones",
22
23
  "directory.organizations.form.children.empty": "Sin hijos asignados.",
23
24
  "directory.organizations.form.errors.load": "No se pudo cargar la organización",
24
25
  "directory.organizations.form.errors.missingId": "Falta el identificador de la organización.",
@@ -55,6 +56,7 @@
55
56
  "directory.organizations.list.filters.status": "Estado",
56
57
  "directory.organizations.list.searchPlaceholder": "Buscar organizaciones",
57
58
  "directory.organizations.list.title": "Organizaciones",
59
+ "directory.tenants.form.actions.backToList": "Volver a inquilinos",
58
60
  "directory.tenants.form.errors.delete": "No se pudo eliminar el inquilino",
59
61
  "directory.tenants.form.errors.load": "No se pudo cargar el inquilino",
60
62
  "directory.tenants.form.errors.notFound": "Inquilino no encontrado",
@@ -19,6 +19,7 @@
19
19
  "directory.organizations.flash.updated": "Organizacja zaktualizowana",
20
20
  "directory.organizations.form.action.create": "Utwórz",
21
21
  "directory.organizations.form.action.save": "Zapisz",
22
+ "directory.organizations.form.actions.backToList": "Powrót do organizacji",
22
23
  "directory.organizations.form.children.empty": "Brak przypisanych organizacji podrzędnych.",
23
24
  "directory.organizations.form.errors.load": "Nie udało się wczytać organizacji",
24
25
  "directory.organizations.form.errors.missingId": "Brakuje identyfikatora organizacji.",
@@ -55,6 +56,7 @@
55
56
  "directory.organizations.list.filters.status": "Status",
56
57
  "directory.organizations.list.searchPlaceholder": "Szukaj organizacji",
57
58
  "directory.organizations.list.title": "Organizacje",
59
+ "directory.tenants.form.actions.backToList": "Powrót do najemców",
58
60
  "directory.tenants.form.errors.delete": "Nie udało się usunąć najemcy",
59
61
  "directory.tenants.form.errors.load": "Nie udało się wczytać najemcy",
60
62
  "directory.tenants.form.errors.notFound": "Nie znaleziono najemcy",
@@ -4,6 +4,7 @@ import * as React from 'react'
4
4
  import { useRouter } from 'next/navigation'
5
5
  import { Page, PageBody } from '@open-mercato/ui/backend/Page'
6
6
  import { Button } from '@open-mercato/ui/primitives/button'
7
+ import { ErrorMessage, RecordNotFoundState } from '@open-mercato/ui/backend/detail'
7
8
  import { readApiResultOrThrow } from '@open-mercato/ui/backend/utils/apiCall'
8
9
  import { updateCrud, deleteCrud } from '@open-mercato/ui/backend/utils/crud'
9
10
  import { flash } from '@open-mercato/ui/backend/FlashMessages'
@@ -40,6 +41,8 @@ export default function PlannerAvailabilityRuleSetDetailPage({ params }: { param
40
41
  const translate = useT()
41
42
  const router = useRouter()
42
43
  const [initialValues, setInitialValues] = React.useState<AvailabilityRuleSetFormValues | null>(null)
44
+ const [error, setError] = React.useState<string | null>(null)
45
+ const [isNotFound, setIsNotFound] = React.useState(false)
43
46
  const [activeTab, setActiveTab] = React.useState<'details' | 'availability'>('details')
44
47
 
45
48
  React.useEffect(() => {
@@ -47,6 +50,10 @@ export default function PlannerAvailabilityRuleSetDetailPage({ params }: { param
47
50
  const rulesetIdValue = rulesetId
48
51
  let cancelled = false
49
52
  async function loadRuleSet() {
53
+ if (!cancelled) {
54
+ setError(null)
55
+ setIsNotFound(false)
56
+ }
50
57
  try {
51
58
  const params = new URLSearchParams({ page: '1', pageSize: '1', ids: rulesetIdValue })
52
59
  const payload = await readApiResultOrThrow<RuleSetResponse>(
@@ -55,7 +62,10 @@ export default function PlannerAvailabilityRuleSetDetailPage({ params }: { param
55
62
  { errorMessage: translate('planner.availabilityRuleSets.form.errors.load', 'Failed to load schedule.') },
56
63
  )
57
64
  const record = Array.isArray(payload.items) ? payload.items[0] : null
58
- if (!record) throw new Error(translate('planner.availabilityRuleSets.form.errors.notFound', 'Schedule not found.'))
65
+ if (!record) {
66
+ if (!cancelled) setIsNotFound(true)
67
+ return
68
+ }
59
69
  const customFields = extractCustomFieldEntries(record)
60
70
  if (!cancelled) {
61
71
  setInitialValues({
@@ -66,9 +76,15 @@ export default function PlannerAvailabilityRuleSetDetailPage({ params }: { param
66
76
  ...customFields,
67
77
  })
68
78
  }
69
- } catch (error) {
70
- const message = error instanceof Error ? error.message : translate('planner.availabilityRuleSets.form.errors.load', 'Failed to load schedule.')
71
- flash(message, 'error')
79
+ } catch (err) {
80
+ if (!cancelled) {
81
+ if ((err as { status?: number }).status === 404) {
82
+ setIsNotFound(true)
83
+ } else {
84
+ const message = err instanceof Error ? err.message : translate('planner.availabilityRuleSets.form.errors.load', 'Failed to load schedule.')
85
+ setError(message)
86
+ }
87
+ }
72
88
  }
73
89
  }
74
90
  loadRuleSet()
@@ -150,6 +166,30 @@ export default function PlannerAvailabilityRuleSetDetailPage({ params }: { param
150
166
 
151
167
  const resolvedInitialValues = initialValues ?? {}
152
168
 
169
+ if (isNotFound) {
170
+ return (
171
+ <Page>
172
+ <PageBody>
173
+ <RecordNotFoundState
174
+ label={translate('planner.availabilityRuleSets.form.errors.notFound', 'Schedule not found.')}
175
+ backHref="/backend/planner/availability-rulesets"
176
+ backLabel={translate('planner.availabilityRuleSets.actions.backToList', 'Back to schedules')}
177
+ />
178
+ </PageBody>
179
+ </Page>
180
+ )
181
+ }
182
+
183
+ if (error && !initialValues) {
184
+ return (
185
+ <Page>
186
+ <PageBody>
187
+ <ErrorMessage label={error} />
188
+ </PageBody>
189
+ </Page>
190
+ )
191
+ }
192
+
153
193
  return (
154
194
  <Page>
155
195
  <PageBody>
@@ -100,6 +100,7 @@
100
100
  "planner.availability.errors.updateDateSpecific": "Datumsspezifische Verfügbarkeit konnte nicht gespeichert werden.",
101
101
  "planner.availability.errors.updateWeekly": "Wöchentliche Verfügbarkeit konnte nicht gespeichert werden.",
102
102
  "planner.availabilityRuleSets.actions.add": "Neuer Zeitplan",
103
+ "planner.availabilityRuleSets.actions.backToList": "Zurück zu Zeitplänen",
103
104
  "planner.availabilityRuleSets.actions.delete": "Löschen",
104
105
  "planner.availabilityRuleSets.actions.deleteConfirm": "Zeitplan \"{{name}}\" löschen?",
105
106
  "planner.availabilityRuleSets.actions.edit": "Bearbeiten",
@@ -100,6 +100,7 @@
100
100
  "planner.availability.errors.updateDateSpecific": "Failed to save date-specific availability.",
101
101
  "planner.availability.errors.updateWeekly": "Failed to save weekly availability.",
102
102
  "planner.availabilityRuleSets.actions.add": "New schedule",
103
+ "planner.availabilityRuleSets.actions.backToList": "Back to schedules",
103
104
  "planner.availabilityRuleSets.actions.delete": "Delete",
104
105
  "planner.availabilityRuleSets.actions.deleteConfirm": "Delete schedule \"{{name}}\"?",
105
106
  "planner.availabilityRuleSets.actions.edit": "Edit",
@@ -100,6 +100,7 @@
100
100
  "planner.availability.errors.updateDateSpecific": "No se pudo guardar la disponibilidad por fecha.",
101
101
  "planner.availability.errors.updateWeekly": "No se pudo guardar la disponibilidad semanal.",
102
102
  "planner.availabilityRuleSets.actions.add": "Nuevo horario",
103
+ "planner.availabilityRuleSets.actions.backToList": "Volver a horarios",
103
104
  "planner.availabilityRuleSets.actions.delete": "Eliminar",
104
105
  "planner.availabilityRuleSets.actions.deleteConfirm": "¿Eliminar el horario \"{{name}}\"?",
105
106
  "planner.availabilityRuleSets.actions.edit": "Editar",
@@ -100,6 +100,7 @@
100
100
  "planner.availability.errors.updateDateSpecific": "Nie udało się zapisać dostępności dla dat.",
101
101
  "planner.availability.errors.updateWeekly": "Nie udało się zapisać tygodniowej dostępności.",
102
102
  "planner.availabilityRuleSets.actions.add": "Nowy harmonogram",
103
+ "planner.availabilityRuleSets.actions.backToList": "Powrót do harmonogramów",
103
104
  "planner.availabilityRuleSets.actions.delete": "Usuń",
104
105
  "planner.availabilityRuleSets.actions.deleteConfirm": "Usunąć harmonogram \"{{name}}\"?",
105
106
  "planner.availabilityRuleSets.actions.edit": "Edytuj",
@@ -4,6 +4,7 @@ import * as React from 'react'
4
4
  import { useRouter, useSearchParams } from 'next/navigation'
5
5
  import { Page, PageBody } from '@open-mercato/ui/backend/Page'
6
6
  import { CrudForm } from '@open-mercato/ui/backend/CrudForm'
7
+ import { ErrorMessage, RecordNotFoundState } from '@open-mercato/ui/backend/detail'
7
8
  import { collectCustomFieldValues } from '@open-mercato/ui/backend/utils/customFieldValues'
8
9
  import { updateCrud, deleteCrud } from '@open-mercato/ui/backend/utils/crud'
9
10
  import { readApiResultOrThrow } from '@open-mercato/ui/backend/utils/apiCall'
@@ -28,6 +29,7 @@ export default function EditChannelPage({ params }: { params?: { channelId?: str
28
29
  const [initialValues, setInitialValues] = React.useState<ChannelFormValues | null>(null)
29
30
  const [loading, setLoading] = React.useState(true)
30
31
  const [error, setError] = React.useState<string | null>(null)
32
+ const [isNotFound, setIsNotFound] = React.useState(false)
31
33
  const [activeTab, setActiveTab] = React.useState<'settings' | 'offers'>('settings')
32
34
 
33
35
  React.useEffect(() => {
@@ -45,6 +47,7 @@ export default function EditChannelPage({ params }: { params?: { channelId?: str
45
47
  async function loadChannel() {
46
48
  setLoading(true)
47
49
  setError(null)
50
+ setIsNotFound(false)
48
51
  try {
49
52
  const payload = await readApiResultOrThrow<ChannelApiResponse>(
50
53
  `/api/sales/channels?id=${encodeURIComponent(channelId)}&pageSize=1`,
@@ -53,14 +56,21 @@ export default function EditChannelPage({ params }: { params?: { channelId?: str
53
56
  )
54
57
  const item = Array.isArray(payload.items) ? payload.items[0] : null
55
58
  if (!item) {
56
- throw new Error(t('sales.channels.form.errors.notFound', 'Channel not found.'))
59
+ if (!cancelled) setIsNotFound(true)
60
+ return
57
61
  }
58
62
  if (!cancelled) {
59
63
  setInitialValues(mapChannelToFormValues(item))
60
64
  }
61
65
  } catch (err) {
62
66
  console.error('sales.channels.load', err)
63
- if (!cancelled) setError(t('sales.channels.form.errors.load', 'Failed to load channel.'))
67
+ if (!cancelled) {
68
+ if ((err as { status?: number }).status === 404) {
69
+ setIsNotFound(true)
70
+ } else {
71
+ setError(t('sales.channels.form.errors.load', 'Failed to load channel.'))
72
+ }
73
+ }
64
74
  } finally {
65
75
  if (!cancelled) setLoading(false)
66
76
  }
@@ -116,14 +126,33 @@ export default function EditChannelPage({ params }: { params?: { channelId?: str
116
126
  </div>
117
127
  ), [tabButton, t])
118
128
 
129
+ if (isNotFound) {
130
+ return (
131
+ <Page>
132
+ <PageBody>
133
+ <RecordNotFoundState
134
+ label={t('sales.channels.form.errors.notFound', 'Channel not found.')}
135
+ backHref="/backend/sales/channels"
136
+ backLabel={t('sales.channels.actions.backToList', 'Back to channels')}
137
+ />
138
+ </PageBody>
139
+ </Page>
140
+ )
141
+ }
142
+
143
+ if (error && !loading && !initialValues) {
144
+ return (
145
+ <Page>
146
+ <PageBody>
147
+ <ErrorMessage label={error} />
148
+ </PageBody>
149
+ </Page>
150
+ )
151
+ }
152
+
119
153
  return (
120
154
  <Page>
121
155
  <PageBody>
122
- {error ? (
123
- <div className="mb-4 rounded border border-destructive/40 bg-destructive/10 px-4 py-3 text-sm text-destructive">
124
- {error}
125
- </div>
126
- ) : null}
127
156
  {activeTab === 'settings' ? (
128
157
  <CrudForm<ChannelFormValues>
129
158
  title={t('sales.channels.form.editTitle', 'Edit channel')}
@@ -52,6 +52,7 @@
52
52
  "sales.audit.tax-rates.create": "Create tax rate",
53
53
  "sales.audit.tax-rates.delete": "Delete tax rate",
54
54
  "sales.audit.tax-rates.update": "Update tax rate",
55
+ "sales.channels.actions.backToList": "Zurück zu Kanälen",
55
56
  "sales.channels.actions.create": "Kanal hinzufügen",
56
57
  "sales.channels.create": "Vertriebskanal erstellen",
57
58
  "sales.channels.form.address1": "Adresszeile 1",
@@ -52,6 +52,7 @@
52
52
  "sales.audit.tax-rates.create": "Create tax rate",
53
53
  "sales.audit.tax-rates.delete": "Delete tax rate",
54
54
  "sales.audit.tax-rates.update": "Update tax rate",
55
+ "sales.channels.actions.backToList": "Back to channels",
55
56
  "sales.channels.actions.create": "Add channel",
56
57
  "sales.channels.create": "Create sales channel",
57
58
  "sales.channels.form.address1": "Address line 1",
@@ -52,6 +52,7 @@
52
52
  "sales.audit.tax-rates.create": "Create tax rate",
53
53
  "sales.audit.tax-rates.delete": "Delete tax rate",
54
54
  "sales.audit.tax-rates.update": "Update tax rate",
55
+ "sales.channels.actions.backToList": "Volver a canales",
55
56
  "sales.channels.actions.create": "Añadir canal",
56
57
  "sales.channels.create": "Crear canal de ventas",
57
58
  "sales.channels.form.address1": "Línea de dirección 1",
@@ -52,6 +52,7 @@
52
52
  "sales.audit.tax-rates.create": "Create tax rate",
53
53
  "sales.audit.tax-rates.delete": "Delete tax rate",
54
54
  "sales.audit.tax-rates.update": "Update tax rate",
55
+ "sales.channels.actions.backToList": "Powrót do kanałów",
55
56
  "sales.channels.actions.create": "Dodaj kanał",
56
57
  "sales.channels.create": "Utwórz kanał sprzedaży",
57
58
  "sales.channels.form.address1": "Adres linia 1",
@@ -6,7 +6,7 @@ import { Page, PageBody } from '@open-mercato/ui/backend/Page'
6
6
  import { Badge } from '@open-mercato/ui/primitives/badge'
7
7
  import { Button } from '@open-mercato/ui/primitives/button'
8
8
  import { Textarea } from '@open-mercato/ui/primitives/textarea'
9
- import { LoadingMessage, ErrorMessage } from '@open-mercato/ui/backend/detail'
9
+ import { LoadingMessage, ErrorMessage, RecordNotFoundState } from '@open-mercato/ui/backend/detail'
10
10
  import { SendObjectMessageDialog } from '@open-mercato/ui/backend/messages'
11
11
  import { apiCallOrThrow, readApiResultOrThrow } from '@open-mercato/ui/backend/utils/apiCall'
12
12
  import { updateCrud } from '@open-mercato/ui/backend/utils/crud'
@@ -21,12 +21,13 @@ export default function StaffLeaveRequestDetailPage({ params }: { params?: { id?
21
21
  const router = useRouter()
22
22
  const [isLoading, setIsLoading] = React.useState(true)
23
23
  const [error, setError] = React.useState<string | null>(null)
24
+ const [isNotFound, setIsNotFound] = React.useState(false)
24
25
  const [record, setRecord] = React.useState<NormalizedLeaveRequest | null>(null)
25
26
  const [decisionComment, setDecisionComment] = React.useState('')
26
27
 
27
28
  React.useEffect(() => {
28
29
  if (!id) {
29
- setError(t('staff.leaveRequests.errors.notFound', 'Leave request not found.'))
30
+ setIsNotFound(true)
30
31
  setIsLoading(false)
31
32
  return
32
33
  }
@@ -35,6 +36,7 @@ export default function StaffLeaveRequestDetailPage({ params }: { params?: { id?
35
36
  async function load() {
36
37
  setIsLoading(true)
37
38
  setError(null)
39
+ setIsNotFound(false)
38
40
  try {
39
41
  const params = new URLSearchParams({ page: '1', pageSize: '1', ids: requestId })
40
42
  const payload = await readApiResultOrThrow<LeaveRequestsResponse>(
@@ -43,7 +45,13 @@ export default function StaffLeaveRequestDetailPage({ params }: { params?: { id?
43
45
  { errorMessage: t('staff.leaveRequests.errors.load', 'Failed to load leave request.') },
44
46
  )
45
47
  const entry = Array.isArray(payload.items) ? payload.items[0] : null
46
- if (!entry) throw new Error(t('staff.leaveRequests.errors.notFound', 'Leave request not found.'))
48
+ if (!entry) {
49
+ if (!cancelled) {
50
+ setIsNotFound(true)
51
+ setRecord(null)
52
+ }
53
+ return
54
+ }
47
55
  if (!cancelled) {
48
56
  const normalized = normalizeLeaveRequest(entry)
49
57
  setRecord(normalized)
@@ -51,9 +59,14 @@ export default function StaffLeaveRequestDetailPage({ params }: { params?: { id?
51
59
  }
52
60
  } catch (err) {
53
61
  if (!cancelled) {
54
- const message = err instanceof Error ? err.message : t('staff.leaveRequests.errors.load', 'Failed to load leave request.')
55
- setError(message)
56
- setRecord(null)
62
+ if ((err as { status?: number }).status === 404) {
63
+ setIsNotFound(true)
64
+ setRecord(null)
65
+ } else {
66
+ const message = err instanceof Error ? err.message : t('staff.leaveRequests.errors.load', 'Failed to load leave request.')
67
+ setError(message)
68
+ setRecord(null)
69
+ }
57
70
  }
58
71
  } finally {
59
72
  if (!cancelled) setIsLoading(false)
@@ -115,6 +128,20 @@ const handleSubmit = React.useCallback(async (values: LeaveRequestFormValues) =>
115
128
  )
116
129
  }
117
130
 
131
+ if (isNotFound) {
132
+ return (
133
+ <Page>
134
+ <PageBody>
135
+ <RecordNotFoundState
136
+ label={t('staff.leaveRequests.errors.notFound', 'Leave request not found.')}
137
+ backHref="/backend/staff/leave-requests"
138
+ backLabel={t('staff.leaveRequests.actions.backToList', 'Back to leave requests')}
139
+ />
140
+ </PageBody>
141
+ </Page>
142
+ )
143
+ }
144
+
118
145
  if (error || !record) {
119
146
  return (
120
147
  <Page>