@asteby/metacore-runtime-react 25.1.2 → 25.1.4

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/src/index.ts CHANGED
@@ -236,6 +236,7 @@ export {
236
236
  } from './collection-cell'
237
237
  export { NIL_UUID, isNilUuid, normalizeNilUuid } from './nil-uuid'
238
238
  export { DynamicRecordDialog, ViewValue } from './dialogs/dynamic-record'
239
+ export { normalizeRefFieldsForSubmit } from './dialogs/normalize-submit'
239
240
  export type { DynamicRecordDialogProps, FieldDef, FieldOption, GetImageUrl } from './dialogs/dynamic-record'
240
241
  export { CreateRecordDialog } from './dialogs/create-record-dialog'
241
242
  export { ViewRecordDialog } from './dialogs/view-record-dialog'
@@ -71,8 +71,93 @@ export function extractServerError(err: unknown, fallbackTitle: string): Extract
71
71
  return { title: fallbackTitle }
72
72
  }
73
73
 
74
- /** i18n translator, tolerant of non-keys (returns `defaultValue`/the input). */
75
- export type Translate = (key: string, opts?: { defaultValue?: string }) => string
74
+ /** i18n translator, tolerant of non-keys (returns `defaultValue`/the input).
75
+ * Accepts arbitrary interpolation values (e.g. `label`, plus a code's `params`)
76
+ * so `localizeFieldIssue` can pass `{{label}}` and friends through i18next. */
77
+ export type Translate = (key: string, opts?: { defaultValue?: string; [k: string]: unknown }) => string
78
+
79
+ // ── Per-field validation errors ────────────────────────────────────────────
80
+ // The kernel answers a failed create/edit with HTTP 422 and a per-field map:
81
+ // { success:false, message:"validation failed",
82
+ // errors: { "<fieldKey>": [ { code, params } ] } }
83
+ // Codes are locale-agnostic; the SDK localizes them to Spanish using the field
84
+ // label. Some endpoints (7leguas-style) instead send pre-localized STRINGS —
85
+ // so a value entry may be an object `{code,params}` OR a plain string.
86
+
87
+ /** A single normalized validation issue for one field: either a machine `code`
88
+ * (+ optional `params`) to localize, or a ready-to-show `message` string. */
89
+ export interface FieldIssue {
90
+ code?: string
91
+ params?: Record<string, any>
92
+ message?: string
93
+ }
94
+
95
+ /** Spanish defaults per known validation code. `{{label}}` (and code params like
96
+ * `allowed`/`ref`/`expected`) are interpolated by i18next, so a host can override
97
+ * any of these via its addon i18n bundle under `validation.<code>`. */
98
+ const VALIDATION_DEFAULTS: Record<string, string> = {
99
+ required: 'El campo {{label}} es obligatorio',
100
+ invalid_option: 'El valor de {{label}} no es válido',
101
+ not_found: 'El {{label}} seleccionado no existe',
102
+ duplicate: 'Ya existe un registro con ese {{label}}',
103
+ invalid_type: 'El campo {{label}} tiene un formato inválido',
104
+ }
105
+ const VALIDATION_FALLBACK = '{{label}}: valor inválido'
106
+
107
+ /** Normalize one raw `errors` value entry into a `FieldIssue`.
108
+ * A string → `{message}` (pre-localized, shown verbatim); an object → `{code,params}`. */
109
+ function toFieldIssue(entry: unknown): FieldIssue | undefined {
110
+ if (typeof entry === 'string') {
111
+ const s = entry.trim()
112
+ return s ? { message: s } : undefined
113
+ }
114
+ if (entry && typeof entry === 'object') {
115
+ const e = entry as { code?: unknown; params?: unknown; message?: unknown }
116
+ if (typeof e.message === 'string' && e.message.trim()) return { message: e.message.trim() }
117
+ if (typeof e.code === 'string' && e.code) {
118
+ return {
119
+ code: e.code,
120
+ params: e.params && typeof e.params === 'object' ? (e.params as Record<string, any>) : undefined,
121
+ }
122
+ }
123
+ }
124
+ return undefined
125
+ }
126
+
127
+ /**
128
+ * Pull the per-field `errors` map out of an axios error (`err.response.data.errors`)
129
+ * or a bare response body (`{ errors }`), normalizing each field's value to a
130
+ * `FieldIssue[]`. A value may be a single entry or an array; string entries become
131
+ * `{message}`, object entries `{code,params}`. Returns `undefined` when there is no
132
+ * usable map (no `errors`, or nothing normalized). Pure — no i18n, no toast.
133
+ */
134
+ export function extractFieldErrors(err: unknown): Record<string, FieldIssue[]> | undefined {
135
+ const maybeAxios = (err as { response?: { data?: unknown } } | undefined)?.response?.data
136
+ const data = maybeAxios ?? err
137
+ const errors = (data as { errors?: unknown } | undefined)?.errors
138
+ if (!errors || typeof errors !== 'object' || Array.isArray(errors)) return undefined
139
+
140
+ const out: Record<string, FieldIssue[]> = {}
141
+ for (const [key, raw] of Object.entries(errors as Record<string, unknown>)) {
142
+ const entries = Array.isArray(raw) ? raw : [raw]
143
+ const issues = entries.map(toFieldIssue).filter((i): i is FieldIssue => !!i)
144
+ if (issues.length) out[key] = issues
145
+ }
146
+ return Object.keys(out).length ? out : undefined
147
+ }
148
+
149
+ /**
150
+ * Localize a single `FieldIssue` to a human, Spanish-by-default string using the
151
+ * field `label`. A pre-localized `message` passes through verbatim. Otherwise the
152
+ * `code` is translated via `t('validation.'+code, { defaultValue, label, ...params })`
153
+ * so hosts can override the copy and `{{label}}`/param interpolation still works.
154
+ */
155
+ export function localizeFieldIssue(issue: FieldIssue, label: string, t: Translate): string {
156
+ if (issue.message) return issue.message
157
+ const code = issue.code ?? ''
158
+ const defaultValue = VALIDATION_DEFAULTS[code] ?? VALIDATION_FALLBACK
159
+ return t(`validation.${code}`, { defaultValue, label, ...(issue.params ?? {}) })
160
+ }
76
161
 
77
162
  /** A dotted, space-free token (e.g. "pos.rate.created") — the shape of an i18n
78
163
  * key, as opposed to human prose ("Record created successfully"). Used to