@open-mercato/core 0.4.5-develop-4849712ccb → 0.4.5-develop-7f44fcf045

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 (25) hide show
  1. package/dist/modules/auth/api/admin/nav.js +10 -7
  2. package/dist/modules/auth/api/admin/nav.js.map +2 -2
  3. package/dist/modules/staff/backend/staff/team-members/[id]/page.js +28 -15
  4. package/dist/modules/staff/backend/staff/team-members/[id]/page.js.map +2 -2
  5. package/dist/modules/staff/translations.js +9 -0
  6. package/dist/modules/staff/translations.js.map +7 -0
  7. package/dist/modules/translations/components/TranslationDrawerAction.js +97 -0
  8. package/dist/modules/translations/components/TranslationDrawerAction.js.map +7 -0
  9. package/dist/modules/translations/lib/extract-record-id.js +31 -2
  10. package/dist/modules/translations/lib/extract-record-id.js.map +2 -2
  11. package/dist/modules/translations/lib/resolve-field-list.js +3 -0
  12. package/dist/modules/translations/lib/resolve-field-list.js.map +2 -2
  13. package/dist/modules/translations/widgets/injection/translation-manager/widget.client.js +105 -36
  14. package/dist/modules/translations/widgets/injection/translation-manager/widget.client.js.map +2 -2
  15. package/dist/modules/translations/widgets/injection-table.js +18 -29
  16. package/dist/modules/translations/widgets/injection-table.js.map +2 -2
  17. package/package.json +2 -2
  18. package/src/modules/auth/api/admin/nav.ts +13 -7
  19. package/src/modules/staff/backend/staff/team-members/[id]/page.tsx +8 -0
  20. package/src/modules/staff/translations.ts +5 -0
  21. package/src/modules/translations/components/TranslationDrawerAction.tsx +107 -0
  22. package/src/modules/translations/lib/extract-record-id.ts +47 -3
  23. package/src/modules/translations/lib/resolve-field-list.ts +4 -0
  24. package/src/modules/translations/widgets/injection/translation-manager/widget.client.tsx +108 -36
  25. package/src/modules/translations/widgets/injection-table.ts +19 -33
@@ -1,15 +1,16 @@
1
1
  "use client"
2
2
 
3
3
  import * as React from 'react'
4
- import Link from 'next/link'
5
4
  import { useParams } from 'next/navigation'
6
- import { ExternalLink, Languages } from 'lucide-react'
5
+ import Link from 'next/link'
6
+ import { ExternalLink, Languages, X } from 'lucide-react'
7
7
  import type { InjectionWidgetComponentProps } from '@open-mercato/shared/modules/widgets/injection'
8
8
  import { useT } from '@open-mercato/shared/lib/i18n/context'
9
+ import { Button } from '@open-mercato/ui/primitives/button'
9
10
  import { TranslationManager } from '../../../components/TranslationManager'
10
11
  import { extractRecordId } from '../../../lib/extract-record-id'
11
12
 
12
- type WidgetContext = { entityId?: string }
13
+ type WidgetContext = { entityId?: string; recordId?: string }
13
14
  type WidgetData = Record<string, unknown> & { id?: string | number }
14
15
 
15
16
  function useTranslationAccess(): boolean {
@@ -32,43 +33,114 @@ export default function TranslationWidget({ context, data }: InjectionWidgetComp
32
33
  const entityType = context?.entityId
33
34
  const params = useParams()
34
35
  const t = useT()
36
+ const [open, setOpen] = React.useState(false)
35
37
  const hasAccess = useTranslationAccess()
36
38
 
37
- const recordId = React.useMemo(() => {
38
- if (data?.id) return String(data.id)
39
- if (params) return extractRecordId(params as Record<string, string | string[]>)
40
- return undefined
41
- }, [data?.id, params])
39
+ const contextRecordId = typeof context?.recordId === 'string' && context.recordId.trim().length > 0
40
+ ? context.recordId.trim()
41
+ : undefined
42
+ const dataRecordId = data?.id === undefined || data.id === null ? undefined : String(data.id)
43
+ const routeRecordId = params ? extractRecordId(params as Record<string, string | string[]>) : undefined
44
+ const recordId = contextRecordId ?? dataRecordId ?? routeRecordId
45
+ const canRender = Boolean(entityType && recordId && hasAccess)
46
+
47
+ React.useEffect(() => {
48
+ if (!open || !canRender) return
49
+ const prev = document.body.style.overflow
50
+ document.body.style.overflow = 'hidden'
51
+ return () => {
52
+ document.body.style.overflow = prev
53
+ }
54
+ }, [canRender, open])
55
+
56
+ React.useEffect(() => {
57
+ if (!open || !canRender) return
58
+ const handleKeyDown = (event: KeyboardEvent) => {
59
+ if (event.key === 'Escape') {
60
+ setOpen(false)
61
+ }
62
+ }
63
+ document.addEventListener('keydown', handleKeyDown)
64
+ return () => document.removeEventListener('keydown', handleKeyDown)
65
+ }, [canRender, open])
42
66
 
43
- if (!entityType || !recordId || !hasAccess) return null
67
+ if (!canRender) return null
44
68
 
45
69
  return (
46
- <div className="space-y-3">
47
- <TranslationManager
48
- mode="embedded"
49
- compact
50
- entityType={entityType}
51
- recordId={recordId}
52
- baseValues={data}
53
- />
54
- <div className="flex flex-wrap gap-x-4 gap-y-1 border-t pt-3">
55
- <Link
56
- href={`/backend/entities/system/${entityType}`}
57
- className="inline-flex items-center gap-1.5 text-xs text-muted-foreground hover:text-foreground transition-colors"
58
- >
59
- <Languages className="h-3 w-3" />
60
- {t('translations.widgets.translationManager.customFieldLabels', 'Custom fields translations')}
61
- <ExternalLink className="h-2.5 w-2.5" />
62
- </Link>
63
- <Link
64
- href="/backend/config/translations"
65
- className="inline-flex items-center gap-1.5 text-xs text-muted-foreground hover:text-foreground transition-colors"
66
- >
67
- <Languages className="h-3 w-3" />
68
- {t('translations.widgets.translationManager.fullManager', 'Translation manager')}
69
- <ExternalLink className="h-2.5 w-2.5" />
70
- </Link>
71
- </div>
72
- </div>
70
+ <>
71
+ <Button
72
+ type="button"
73
+ variant="ghost"
74
+ size="icon"
75
+ onClick={() => setOpen(true)}
76
+ aria-label={t('translations.widgets.translationManager.fullManager', 'Translation manager')}
77
+ title={t('translations.widgets.translationManager.fullManager', 'Translation manager')}
78
+ >
79
+ <Languages className="size-4" />
80
+ </Button>
81
+ {open ? (
82
+ <>
83
+ <div
84
+ className="fixed inset-0 z-40 bg-black/20"
85
+ onClick={() => setOpen(false)}
86
+ aria-hidden="true"
87
+ />
88
+ <div
89
+ className="fixed right-0 top-0 z-50 h-full w-full max-w-4xl border-l bg-background shadow-lg"
90
+ role="dialog"
91
+ aria-modal="true"
92
+ aria-label={t('translations.widgets.translationManager.groupLabel', 'Translations')}
93
+ >
94
+ <div className="flex h-full flex-col">
95
+ <div className="flex items-start justify-between gap-3 border-b px-4 py-3">
96
+ <div className="space-y-1">
97
+ <h2 className="font-semibold">
98
+ {t('translations.widgets.translationManager.groupLabel', 'Translations')}
99
+ </h2>
100
+ <p className="text-sm text-muted-foreground">
101
+ {t('translations.widgets.translationManager.groupDescription', 'Manage translations for this record across supported locales.')}
102
+ </p>
103
+ </div>
104
+ <Button
105
+ variant="ghost"
106
+ size="icon"
107
+ onClick={() => setOpen(false)}
108
+ aria-label={t('ui.dialog.close.ariaLabel', 'Close')}
109
+ >
110
+ <X className="size-4" />
111
+ </Button>
112
+ </div>
113
+ <div className="flex-1 overflow-y-auto px-4 py-4">
114
+ <TranslationManager
115
+ mode="embedded"
116
+ compact
117
+ entityType={entityType}
118
+ recordId={recordId}
119
+ baseValues={data}
120
+ />
121
+ </div>
122
+ <div className="flex flex-wrap gap-x-4 gap-y-1 border-t px-4 py-3">
123
+ <Link
124
+ href={`/backend/entities/system/${encodeURIComponent(entityType!)}`}
125
+ className="inline-flex items-center gap-1.5 text-xs text-muted-foreground hover:text-foreground transition-colors"
126
+ >
127
+ <Languages className="size-3" />
128
+ {t('translations.widgets.translationManager.customFieldLabels', 'Custom fields translations')}
129
+ <ExternalLink className="size-2.5" />
130
+ </Link>
131
+ <Link
132
+ href="/backend/config/translations"
133
+ className="inline-flex items-center gap-1.5 text-xs text-muted-foreground hover:text-foreground transition-colors"
134
+ >
135
+ <Languages className="size-3" />
136
+ {t('translations.widgets.translationManager.fullManager', 'Translation manager')}
137
+ <ExternalLink className="size-2.5" />
138
+ </Link>
139
+ </div>
140
+ </div>
141
+ </div>
142
+ </>
143
+ ) : null}
144
+ </>
73
145
  )
74
146
  }
@@ -1,45 +1,31 @@
1
1
  import type { ModuleInjectionTable } from '@open-mercato/shared/modules/widgets/injection'
2
- import { translatableFields as catalogFields } from '../../catalog/translations'
3
- import { translatableFields as dictionaryFields } from '../../dictionaries/translations'
4
- import { translatableFields as entitiesFields } from '../../entities/translations'
5
- import { translatableFields as resourcesFields } from '../../resources/translations'
2
+ import { getTranslatableFieldsRegistry } from '@open-mercato/shared/lib/localization/translatable-fields'
6
3
 
7
4
  const WIDGET_ID = 'translations.injection.translation-manager'
8
5
 
9
- const allFields: Record<string, string[]> = {
10
- ...catalogFields,
11
- ...dictionaryFields,
12
- ...entitiesFields,
13
- ...resourcesFields,
14
- }
15
-
16
- const ENTRY_TEMPLATE = {
17
- widgetId: WIDGET_ID,
18
- kind: 'group' as const,
19
- column: 2 as const,
20
- groupLabel: 'translations.widgets.translationManager.groupLabel',
21
- groupDescription: 'translations.widgets.translationManager.groupDescription',
22
- priority: 40,
23
- }
24
-
25
- const table: ModuleInjectionTable = {}
26
- for (const entityType of Object.keys(allFields)) {
6
+ function addEntitySpots(table: ModuleInjectionTable, entityType: string): void {
27
7
  const [module, entitySlug] = entityType.split(':')
28
- if (!module || !entitySlug) continue
8
+ if (!module || !entitySlug) return
29
9
 
30
- // Full form: crud-form:catalog.catalog_product (auto-generated by CrudForm from entityId)
31
- const fullSpot = `crud-form:${module}.${entitySlug}`
32
- table[fullSpot] = [{ ...ENTRY_TEMPLATE }]
10
+ const fullSpot = `crud-form:${module}.${entitySlug}:header`
11
+ table[fullSpot] = WIDGET_ID
33
12
 
34
- // Short form: crud-form:catalog.product (hardcoded in some pages)
35
13
  const prefix = `${module}_`
36
- if (entitySlug.startsWith(prefix)) {
37
- const shortSpot = `crud-form:${module}.${entitySlug.slice(prefix.length)}`
38
- if (shortSpot !== fullSpot) {
39
- table[shortSpot] = [{ ...ENTRY_TEMPLATE }]
40
- }
14
+ if (!entitySlug.startsWith(prefix)) return
15
+
16
+ const shortSpot = `crud-form:${module}.${entitySlug.slice(prefix.length)}:header`
17
+ if (shortSpot !== fullSpot) {
18
+ table[shortSpot] = WIDGET_ID
19
+ }
20
+ }
21
+
22
+ export function buildInjectionTable(allFields: Record<string, string[]> = getTranslatableFieldsRegistry()): ModuleInjectionTable {
23
+ const table: ModuleInjectionTable = {}
24
+ for (const entityType of Object.keys(allFields)) {
25
+ addEntitySpots(table, entityType)
41
26
  }
27
+ return table
42
28
  }
43
29
 
44
- export const injectionTable: ModuleInjectionTable = table
30
+ export const injectionTable: ModuleInjectionTable = buildInjectionTable()
45
31
  export default injectionTable