@asteby/metacore-runtime-react 16.0.1 → 17.0.0
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/CHANGELOG.md +19 -0
- package/dist/dialogs/dynamic-record.d.ts.map +1 -1
- package/dist/dialogs/dynamic-record.js +8 -2
- package/dist/dynamic-columns.d.ts.map +1 -1
- package/dist/dynamic-columns.js +9 -2
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -0
- package/dist/nil-uuid.d.ts +15 -0
- package/dist/nil-uuid.d.ts.map +1 -0
- package/dist/nil-uuid.js +20 -0
- package/package.json +4 -4
- package/src/__tests__/nil-uuid.test.ts +42 -0
- package/src/__tests__/relation-option-cells.test.ts +13 -0
- package/src/dialogs/dynamic-record.tsx +8 -2
- package/src/dynamic-columns.tsx +8 -2
- package/src/index.ts +1 -0
- package/src/nil-uuid.ts +25 -0
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,24 @@
|
|
|
1
1
|
# @asteby/metacore-runtime-react
|
|
2
2
|
|
|
3
|
+
## 17.0.0
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- c834e67: Render the nil UUID (`00000000-0000-0000-0000-000000000000`) as empty in
|
|
8
|
+
dynamic tables and the detail view.
|
|
9
|
+
|
|
10
|
+
A nullable FK that a backend serializes as the all-zeros UUID instead of `null`
|
|
11
|
+
used to leak into cells and read-only fields as a long string of zeros. The
|
|
12
|
+
table cell renderer (`defaultGetDynamicColumns`) and the record detail view
|
|
13
|
+
(`DynamicRecordDialog`/`ViewRecordDialog`) now treat the nil UUID as "no value",
|
|
14
|
+
falling through to their existing empty markers (`-` / `—`). This covers
|
|
15
|
+
relation/ref chips, `creator`/`url`/`status`/`dynamic_select` and any generic
|
|
16
|
+
UUID-bearing column. A new shared guard (`NIL_UUID`, `isNilUuid`,
|
|
17
|
+
`normalizeNilUuid`) is exported for hosts that render values themselves.
|
|
18
|
+
|
|
19
|
+
- Updated dependencies [8a4a315]
|
|
20
|
+
- @asteby/metacore-sdk@3.2.0
|
|
21
|
+
|
|
3
22
|
## 16.0.1
|
|
4
23
|
|
|
5
24
|
### Patch Changes
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"dynamic-record.d.ts","sourceRoot":"","sources":["../../src/dialogs/dynamic-record.tsx"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,SAAS,CAAA;
|
|
1
|
+
{"version":3,"file":"dynamic-record.d.ts","sourceRoot":"","sources":["../../src/dialogs/dynamic-record.tsx"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,SAAS,CAAA;AAyF1C,MAAM,WAAW,wBAAwB;IACrC,IAAI,EAAE,OAAO,CAAA;IACb,YAAY,EAAE,CAAC,IAAI,EAAE,OAAO,KAAK,IAAI,CAAA;IACrC,IAAI,EAAE,MAAM,GAAG,MAAM,GAAG,QAAQ,CAAA;IAChC,KAAK,EAAE,MAAM,CAAA;IACb,QAAQ,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;IACxB,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB,OAAO,CAAC,EAAE,MAAM,IAAI,CAAA;IACpB;;;;;OAKG;IACH,QAAQ,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,KAAK,OAAO,CAAC;QAAE,EAAE,CAAC,EAAE,MAAM,GAAG,MAAM,CAAA;KAAE,GAAG,IAAI,CAAC,CAAA;IAClF;;;OAGG;IACH,QAAQ,CAAC,EAAE,CAAC,QAAQ,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,KAAK,OAAO,CAAC;QAAE,EAAE,CAAC,EAAE,MAAM,GAAG,MAAM,CAAA;KAAE,GAAG,IAAI,CAAC,CAAA;IACpG;;;OAGG;IACH,QAAQ,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAAA;IAC9B;;;OAGG;IACH,MAAM,CAAC,EAAE,WAAW,CAAA;IACpB;;;OAGG;IACH,QAAQ,CAAC,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAA;IAC9B;;;OAGG;IACH,MAAM,CAAC,EAAE,MAAM,IAAI,CAAA;CACtB;AAwDD,wBAAgB,mBAAmB,CAAC,EAChC,IAAI,EACJ,YAAY,EACZ,IAAI,EACJ,KAAK,EACL,QAAQ,EACR,QAAQ,EACR,OAAO,EACP,QAAQ,EACR,QAAQ,EACR,QAAQ,EACR,MAAM,EACN,QAAQ,EACR,MAAM,GACT,EAAE,wBAAwB,2CAsP1B"}
|
|
@@ -14,10 +14,13 @@ import { ExternalLink, Loader2, CalendarIcon, ChevronDown, Check, Upload, X as X
|
|
|
14
14
|
import { useApi } from '../api-context';
|
|
15
15
|
import { DynamicSelectField } from '../dynamic-select-field';
|
|
16
16
|
import { getFieldRef } from '../dynamic-form-schema';
|
|
17
|
+
import { normalizeNilUuid } from '../nil-uuid';
|
|
17
18
|
function resolvePath(obj, path) {
|
|
18
19
|
return path.split('.').reduce((acc, part) => acc?.[part], obj);
|
|
19
20
|
}
|
|
20
|
-
function formatDisplayValue(
|
|
21
|
+
function formatDisplayValue(rawValue, field) {
|
|
22
|
+
// Unset nullable FK serialized as the nil UUID renders as empty, not zeros.
|
|
23
|
+
const value = normalizeNilUuid(rawValue);
|
|
21
24
|
if (value === null || value === undefined || value === '')
|
|
22
25
|
return '—';
|
|
23
26
|
if (field.type === 'boolean' || typeof value === 'boolean')
|
|
@@ -227,7 +230,10 @@ function FieldRow({ field, record, value, mode, onChange }) {
|
|
|
227
230
|
const isReadonly = field.readonly || mode === 'view';
|
|
228
231
|
return (_jsxs("div", { className: "flex flex-col gap-1.5", children: [_jsxs(Label, { className: "text-xs font-medium text-muted-foreground uppercase tracking-wide", children: [field.label, field.required && mode !== 'view' && (_jsx("span", { className: "text-destructive ml-0.5", children: "*" }))] }), isReadonly ? (_jsx(ViewValue, { field: field, value: value, record: record })) : (_jsx(EditField, { field: field, value: value, onChange: onChange }))] }));
|
|
229
232
|
}
|
|
230
|
-
function ViewValue({ field, value }) {
|
|
233
|
+
function ViewValue({ field, value: rawValue }) {
|
|
234
|
+
// Normalize the nil UUID to undefined up front so the search/url/color/
|
|
235
|
+
// image/select branches all fall through to their empty states.
|
|
236
|
+
const value = normalizeNilUuid(rawValue);
|
|
231
237
|
if (field.type === 'search' && value) {
|
|
232
238
|
return _jsx(SearchViewValue, { field: field, value: value });
|
|
233
239
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"dynamic-columns.d.ts","sourceRoot":"","sources":["../src/dynamic-columns.tsx"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"dynamic-columns.d.ts","sourceRoot":"","sources":["../src/dynamic-columns.tsx"],"names":[],"mappings":"AA+CA,OAAO,KAAK,EAAiB,gBAAgB,EAAE,MAAM,SAAS,CAAA;AAE9D,OAAO,KAAK,EAER,iBAAiB,EACpB,MAAM,wBAAwB,CAAA;AAE/B,qEAAqE;AACrE,MAAM,WAAW,qBAAqB;IAClC;;;;OAIG;IACH,WAAW,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,MAAM,CAAA;IACtC;;;;OAIG;IACH,UAAU,CAAC,EAAE,MAAM,CAAA;CACtB;AAgGD;;;;;;;;;;;GAWG;AACH,eAAO,MAAM,0BAA0B,GAAI,QAAQ,GAAG,EAAE,KAAK,GAAG,KAAG,OAMlE,CAAA;AAwHD;;;;;;;GAOG;AACH,eAAO,MAAM,cAAc,GAAI,KAAK,IAAI,CAAC,gBAAgB,EAAE,KAAK,CAAC,KAAG,MAGnE,CAAA;AAED;;;;;;;GAOG;AACH,eAAO,MAAM,oBAAoB,GAAI,KAAK,gBAAgB,EAAE,KAAK,GAAG,KAAG,MAWtE,CAAA;AAiED;;;;GAIG;AACH,wBAAgB,4BAA4B,CACxC,OAAO,GAAE,qBAA0B,GACpC,iBAAiB,CAmmBnB;AAED;;;;GAIG;AACH,eAAO,MAAM,wBAAwB,EAAE,iBACL,CAAA"}
|
package/dist/dynamic-columns.js
CHANGED
|
@@ -23,6 +23,7 @@ import { generateBadgeStyles, getInitials, optionColor, relationChipStyles, } fr
|
|
|
23
23
|
import { Progress } from './dialogs/_primitives';
|
|
24
24
|
import { OptionsContext } from './options-context';
|
|
25
25
|
import { DynamicIcon } from './dynamic-icon';
|
|
26
|
+
import { isNilUuid, normalizeNilUuid } from './nil-uuid';
|
|
26
27
|
import { isColumnVisibleInTable } from './column-visibility';
|
|
27
28
|
const defaultGetImageUrl = (path) => path;
|
|
28
29
|
const getNestedValue = (obj, path) => path.split('.').reduce((acc, part) => acc && acc[part], obj);
|
|
@@ -213,7 +214,10 @@ export const resolveRelationLabel = (col, row) => {
|
|
|
213
214
|
if (label !== undefined && label !== null && label !== '')
|
|
214
215
|
return String(label);
|
|
215
216
|
const raw = getNestedValue(row, col.key);
|
|
216
|
-
|
|
217
|
+
// An unresolved FK that arrived as the nil UUID reads as empty, not zeros.
|
|
218
|
+
if (raw === undefined || raw === null || isNilUuid(raw))
|
|
219
|
+
return '';
|
|
220
|
+
return String(raw);
|
|
217
221
|
};
|
|
218
222
|
/**
|
|
219
223
|
* Renders a resolved FK relation as a clean, truncated chip. Reads the
|
|
@@ -290,7 +294,10 @@ export function makeDefaultGetDynamicColumns(helpers = {}) {
|
|
|
290
294
|
meta: columnMeta,
|
|
291
295
|
header: ({ column }) => filterConfig ? (_jsx(FilterableColumnHeader, { column: column, title: translatedLabel })) : (_jsx(DataTableColumnHeader, { column: column, title: translatedLabel })),
|
|
292
296
|
cell: ({ row }) => {
|
|
293
|
-
|
|
297
|
+
// Treat the nil UUID (unset nullable FK serialized as
|
|
298
|
+
// all-zeros) as no value, so every type below hits its
|
|
299
|
+
// existing empty branch instead of printing the zeros.
|
|
300
|
+
const value = normalizeNilUuid(getNestedValue(row.original, col.key));
|
|
294
301
|
// Kernel emits the renderer flag as `type`; older hosts used
|
|
295
302
|
// `cellStyle`. Accept both so a single backend works across
|
|
296
303
|
// SDK versions.
|
package/dist/index.d.ts
CHANGED
|
@@ -17,6 +17,7 @@ export { useHotSwapReload, applyHotSwapReload, withVersionParam, clearFederation
|
|
|
17
17
|
export * from './dynamic-icon';
|
|
18
18
|
export type { ColumnFilterConfig, FilterOption as DynamicColumnFilterOption, GetDynamicColumns, DynamicIconComponent, } from './dynamic-columns-shim';
|
|
19
19
|
export { defaultGetDynamicColumns, makeDefaultGetDynamicColumns, relationKeyFor, resolveRelationLabel, type DynamicColumnsHelpers, } from './dynamic-columns';
|
|
20
|
+
export { NIL_UUID, isNilUuid, normalizeNilUuid } from './nil-uuid';
|
|
20
21
|
export { DynamicRecordDialog } from './dialogs/dynamic-record';
|
|
21
22
|
export { CreateRecordDialog } from './dialogs/create-record-dialog';
|
|
22
23
|
export { ViewRecordDialog } from './dialogs/view-record-dialog';
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
|
package/dist/index.js
CHANGED
|
@@ -21,6 +21,7 @@ export { ADDON_MANIFEST_CHANGED_TYPE, wireHotSwapInvalidation, useManifestHotSwa
|
|
|
21
21
|
export { useHotSwapReload, applyHotSwapReload, withVersionParam, clearFederationContainer, shortenHash, } from './hotswap-reload-policy';
|
|
22
22
|
export * from './dynamic-icon';
|
|
23
23
|
export { defaultGetDynamicColumns, makeDefaultGetDynamicColumns, relationKeyFor, resolveRelationLabel, } from './dynamic-columns';
|
|
24
|
+
export { NIL_UUID, isNilUuid, normalizeNilUuid } from './nil-uuid';
|
|
24
25
|
export { DynamicRecordDialog } from './dialogs/dynamic-record';
|
|
25
26
|
export { CreateRecordDialog } from './dialogs/create-record-dialog';
|
|
26
27
|
export { ViewRecordDialog } from './dialogs/view-record-dialog';
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/** The canonical nil/zero UUID sentinel. */
|
|
2
|
+
export declare const NIL_UUID = "00000000-0000-0000-0000-000000000000";
|
|
3
|
+
/**
|
|
4
|
+
* True when `value` is the nil UUID string. Tolerant of surrounding whitespace
|
|
5
|
+
* and letter-case (UUIDs are conventionally lowercase, but be defensive). Only
|
|
6
|
+
* matches strings — numeric/object values are never the nil UUID.
|
|
7
|
+
*/
|
|
8
|
+
export declare const isNilUuid: (value: unknown) => boolean;
|
|
9
|
+
/**
|
|
10
|
+
* Normalizes a raw cell value: returns `undefined` when it is the nil UUID so
|
|
11
|
+
* downstream renderers hit their existing nullish/empty branches; otherwise the
|
|
12
|
+
* value is passed through unchanged.
|
|
13
|
+
*/
|
|
14
|
+
export declare const normalizeNilUuid: <T>(value: T) => T | undefined;
|
|
15
|
+
//# sourceMappingURL=nil-uuid.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"nil-uuid.d.ts","sourceRoot":"","sources":["../src/nil-uuid.ts"],"names":[],"mappings":"AAOA,4CAA4C;AAC5C,eAAO,MAAM,QAAQ,yCAAyC,CAAA;AAE9D;;;;GAIG;AACH,eAAO,MAAM,SAAS,GAAI,OAAO,OAAO,KAAG,OAC6B,CAAA;AAExE;;;;GAIG;AACH,eAAO,MAAM,gBAAgB,GAAI,CAAC,EAAE,OAAO,CAAC,KAAG,CAAC,GAAG,SACX,CAAA"}
|
package/dist/nil-uuid.js
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
// Shared guard for the "nil UUID" — the all-zeros UUID Postgres/Go emit for a
|
|
2
|
+
// nullable FK that was never set (`00000000-0000-0000-0000-000000000000`).
|
|
3
|
+
// Backends sometimes serialize an unset `uuid` column as this sentinel instead
|
|
4
|
+
// of `null`, which then leaks into the UI as a long string of zeros. Treat it
|
|
5
|
+
// as "no value" so cell/detail renderers fall through to their existing empty
|
|
6
|
+
// markers ("-" / "—"). Cosmetic defense-in-depth — the backend should emit null.
|
|
7
|
+
/** The canonical nil/zero UUID sentinel. */
|
|
8
|
+
export const NIL_UUID = '00000000-0000-0000-0000-000000000000';
|
|
9
|
+
/**
|
|
10
|
+
* True when `value` is the nil UUID string. Tolerant of surrounding whitespace
|
|
11
|
+
* and letter-case (UUIDs are conventionally lowercase, but be defensive). Only
|
|
12
|
+
* matches strings — numeric/object values are never the nil UUID.
|
|
13
|
+
*/
|
|
14
|
+
export const isNilUuid = (value) => typeof value === 'string' && value.trim().toLowerCase() === NIL_UUID;
|
|
15
|
+
/**
|
|
16
|
+
* Normalizes a raw cell value: returns `undefined` when it is the nil UUID so
|
|
17
|
+
* downstream renderers hit their existing nullish/empty branches; otherwise the
|
|
18
|
+
* value is passed through unchanged.
|
|
19
|
+
*/
|
|
20
|
+
export const normalizeNilUuid = (value) => isNilUuid(value) ? undefined : value;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@asteby/metacore-runtime-react",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "17.0.0",
|
|
4
4
|
"description": "React runtime for metacore hosts — renders addon contributions dynamically",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
@@ -33,7 +33,7 @@
|
|
|
33
33
|
"lucide-react": ">=0.460",
|
|
34
34
|
"date-fns": ">=3",
|
|
35
35
|
"react-day-picker": ">=8",
|
|
36
|
-
"@asteby/metacore-sdk": "^3.
|
|
36
|
+
"@asteby/metacore-sdk": "^3.2.0",
|
|
37
37
|
"@asteby/metacore-ui": "^2.4.0"
|
|
38
38
|
},
|
|
39
39
|
"peerDependenciesMeta": {
|
|
@@ -61,8 +61,8 @@
|
|
|
61
61
|
"typescript": "^6.0.0",
|
|
62
62
|
"vitest": "^4.0.0",
|
|
63
63
|
"zustand": "^5.0.0",
|
|
64
|
-
"@asteby/metacore-
|
|
65
|
-
"@asteby/metacore-
|
|
64
|
+
"@asteby/metacore-sdk": "3.2.0",
|
|
65
|
+
"@asteby/metacore-ui": "2.4.0"
|
|
66
66
|
},
|
|
67
67
|
"scripts": {
|
|
68
68
|
"build": "tsc -p tsconfig.json",
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
// Locks the nil-UUID guard shared by the table cell renderer and the detail
|
|
2
|
+
// view. The all-zeros UUID is the sentinel a backend emits for an unset
|
|
3
|
+
// nullable FK; the UI must read it as "no value", never as a string of zeros.
|
|
4
|
+
import { describe, it, expect } from 'vitest'
|
|
5
|
+
import { NIL_UUID, isNilUuid, normalizeNilUuid } from '../nil-uuid'
|
|
6
|
+
|
|
7
|
+
describe('isNilUuid', () => {
|
|
8
|
+
it('matches the canonical nil UUID', () => {
|
|
9
|
+
expect(isNilUuid(NIL_UUID)).toBe(true)
|
|
10
|
+
expect(isNilUuid('00000000-0000-0000-0000-000000000000')).toBe(true)
|
|
11
|
+
})
|
|
12
|
+
|
|
13
|
+
it('is tolerant of whitespace and case', () => {
|
|
14
|
+
expect(isNilUuid(' 00000000-0000-0000-0000-000000000000 ')).toBe(true)
|
|
15
|
+
expect(isNilUuid('00000000-0000-0000-0000-000000000000'.toUpperCase())).toBe(true)
|
|
16
|
+
})
|
|
17
|
+
|
|
18
|
+
it('does not match a real UUID', () => {
|
|
19
|
+
expect(isNilUuid('3f2504e0-4f89-11d3-9a0c-0305e82c3301')).toBe(false)
|
|
20
|
+
})
|
|
21
|
+
|
|
22
|
+
it('does not match non-string values', () => {
|
|
23
|
+
expect(isNilUuid(null)).toBe(false)
|
|
24
|
+
expect(isNilUuid(undefined)).toBe(false)
|
|
25
|
+
expect(isNilUuid(0)).toBe(false)
|
|
26
|
+
expect(isNilUuid('')).toBe(false)
|
|
27
|
+
expect(isNilUuid({ value: NIL_UUID })).toBe(false)
|
|
28
|
+
})
|
|
29
|
+
})
|
|
30
|
+
|
|
31
|
+
describe('normalizeNilUuid', () => {
|
|
32
|
+
it('maps the nil UUID to undefined', () => {
|
|
33
|
+
expect(normalizeNilUuid(NIL_UUID)).toBeUndefined()
|
|
34
|
+
})
|
|
35
|
+
|
|
36
|
+
it('passes through real values unchanged', () => {
|
|
37
|
+
expect(normalizeNilUuid('real-id')).toBe('real-id')
|
|
38
|
+
expect(normalizeNilUuid(42)).toBe(42)
|
|
39
|
+
expect(normalizeNilUuid(null)).toBeNull()
|
|
40
|
+
expect(normalizeNilUuid('')).toBe('')
|
|
41
|
+
})
|
|
42
|
+
})
|
|
@@ -50,4 +50,17 @@ describe('resolveRelationLabel', () => {
|
|
|
50
50
|
expect(resolveRelationLabel(col({ ref: 'categories' }), {})).toBe('')
|
|
51
51
|
expect(resolveRelationLabel(col({ ref: 'categories' }), { category_id: null })).toBe('')
|
|
52
52
|
})
|
|
53
|
+
|
|
54
|
+
it('treats an unresolved nil UUID FK as empty, not a string of zeros', () => {
|
|
55
|
+
const row = { category_id: '00000000-0000-0000-0000-000000000000' }
|
|
56
|
+
expect(resolveRelationLabel(col({ ref: 'categories' }), row)).toBe('')
|
|
57
|
+
})
|
|
58
|
+
|
|
59
|
+
it('still prefers a resolved sibling label even if the FK id is the nil UUID', () => {
|
|
60
|
+
const row = {
|
|
61
|
+
category_id: '00000000-0000-0000-0000-000000000000',
|
|
62
|
+
category: { value: '00000000-0000-0000-0000-000000000000', label: 'Sin categoría' },
|
|
63
|
+
}
|
|
64
|
+
expect(resolveRelationLabel(col({ ref: 'categories' }), row)).toBe('Sin categoría')
|
|
65
|
+
})
|
|
53
66
|
})
|
|
@@ -42,6 +42,7 @@ import { ExternalLink, Loader2, CalendarIcon, ChevronDown, Check, Upload, X as X
|
|
|
42
42
|
import { useApi } from '../api-context'
|
|
43
43
|
import { DynamicSelectField } from '../dynamic-select-field'
|
|
44
44
|
import { getFieldRef } from '../dynamic-form-schema'
|
|
45
|
+
import { normalizeNilUuid } from '../nil-uuid'
|
|
45
46
|
import type { ActionFieldDef } from '../types'
|
|
46
47
|
|
|
47
48
|
interface FieldOption {
|
|
@@ -137,7 +138,9 @@ function resolvePath(obj: any, path: string): any {
|
|
|
137
138
|
return path.split('.').reduce((acc, part) => acc?.[part], obj)
|
|
138
139
|
}
|
|
139
140
|
|
|
140
|
-
function formatDisplayValue(
|
|
141
|
+
function formatDisplayValue(rawValue: any, field: FieldDef): string {
|
|
142
|
+
// Unset nullable FK serialized as the nil UUID renders as empty, not zeros.
|
|
143
|
+
const value = normalizeNilUuid(rawValue)
|
|
141
144
|
if (value === null || value === undefined || value === '') return '—'
|
|
142
145
|
if (field.type === 'boolean' || typeof value === 'boolean') return value ? 'Sí' : 'No'
|
|
143
146
|
|
|
@@ -489,7 +492,10 @@ function FieldRow({ field, record, value, mode, onChange }: FieldRowProps) {
|
|
|
489
492
|
)
|
|
490
493
|
}
|
|
491
494
|
|
|
492
|
-
function ViewValue({ field, value }: { field: FieldDef; value: any; record: any }) {
|
|
495
|
+
function ViewValue({ field, value: rawValue }: { field: FieldDef; value: any; record: any }) {
|
|
496
|
+
// Normalize the nil UUID to undefined up front so the search/url/color/
|
|
497
|
+
// image/select branches all fall through to their empty states.
|
|
498
|
+
const value = normalizeNilUuid(rawValue)
|
|
493
499
|
if (field.type === 'search' && value) {
|
|
494
500
|
return <SearchViewValue field={field} value={value} />
|
|
495
501
|
}
|
package/src/dynamic-columns.tsx
CHANGED
|
@@ -44,6 +44,7 @@ import {
|
|
|
44
44
|
import { Progress } from './dialogs/_primitives'
|
|
45
45
|
import { OptionsContext } from './options-context'
|
|
46
46
|
import { DynamicIcon } from './dynamic-icon'
|
|
47
|
+
import { isNilUuid, normalizeNilUuid } from './nil-uuid'
|
|
47
48
|
import type { TableMetadata, ColumnDefinition } from './types'
|
|
48
49
|
import { isColumnVisibleInTable } from './column-visibility'
|
|
49
50
|
import type {
|
|
@@ -328,7 +329,9 @@ export const resolveRelationLabel = (col: ColumnDefinition, row: any): string =>
|
|
|
328
329
|
: undefined
|
|
329
330
|
if (label !== undefined && label !== null && label !== '') return String(label)
|
|
330
331
|
const raw = getNestedValue(row, col.key)
|
|
331
|
-
|
|
332
|
+
// An unresolved FK that arrived as the nil UUID reads as empty, not zeros.
|
|
333
|
+
if (raw === undefined || raw === null || isNilUuid(raw)) return ''
|
|
334
|
+
return String(raw)
|
|
332
335
|
}
|
|
333
336
|
|
|
334
337
|
/**
|
|
@@ -476,7 +479,10 @@ export function makeDefaultGetDynamicColumns(
|
|
|
476
479
|
<DataTableColumnHeader column={column} title={translatedLabel} />
|
|
477
480
|
),
|
|
478
481
|
cell: ({ row }) => {
|
|
479
|
-
|
|
482
|
+
// Treat the nil UUID (unset nullable FK serialized as
|
|
483
|
+
// all-zeros) as no value, so every type below hits its
|
|
484
|
+
// existing empty branch instead of printing the zeros.
|
|
485
|
+
const value = normalizeNilUuid(getNestedValue(row.original, col.key))
|
|
480
486
|
// Kernel emits the renderer flag as `type`; older hosts used
|
|
481
487
|
// `cellStyle`. Accept both so a single backend works across
|
|
482
488
|
// SDK versions.
|
package/src/index.ts
CHANGED
|
@@ -66,6 +66,7 @@ export {
|
|
|
66
66
|
resolveRelationLabel,
|
|
67
67
|
type DynamicColumnsHelpers,
|
|
68
68
|
} from './dynamic-columns'
|
|
69
|
+
export { NIL_UUID, isNilUuid, normalizeNilUuid } from './nil-uuid'
|
|
69
70
|
export { DynamicRecordDialog } from './dialogs/dynamic-record'
|
|
70
71
|
export { CreateRecordDialog } from './dialogs/create-record-dialog'
|
|
71
72
|
export { ViewRecordDialog } from './dialogs/view-record-dialog'
|
package/src/nil-uuid.ts
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
// Shared guard for the "nil UUID" — the all-zeros UUID Postgres/Go emit for a
|
|
2
|
+
// nullable FK that was never set (`00000000-0000-0000-0000-000000000000`).
|
|
3
|
+
// Backends sometimes serialize an unset `uuid` column as this sentinel instead
|
|
4
|
+
// of `null`, which then leaks into the UI as a long string of zeros. Treat it
|
|
5
|
+
// as "no value" so cell/detail renderers fall through to their existing empty
|
|
6
|
+
// markers ("-" / "—"). Cosmetic defense-in-depth — the backend should emit null.
|
|
7
|
+
|
|
8
|
+
/** The canonical nil/zero UUID sentinel. */
|
|
9
|
+
export const NIL_UUID = '00000000-0000-0000-0000-000000000000'
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* True when `value` is the nil UUID string. Tolerant of surrounding whitespace
|
|
13
|
+
* and letter-case (UUIDs are conventionally lowercase, but be defensive). Only
|
|
14
|
+
* matches strings — numeric/object values are never the nil UUID.
|
|
15
|
+
*/
|
|
16
|
+
export const isNilUuid = (value: unknown): boolean =>
|
|
17
|
+
typeof value === 'string' && value.trim().toLowerCase() === NIL_UUID
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Normalizes a raw cell value: returns `undefined` when it is the nil UUID so
|
|
21
|
+
* downstream renderers hit their existing nullish/empty branches; otherwise the
|
|
22
|
+
* value is passed through unchanged.
|
|
23
|
+
*/
|
|
24
|
+
export const normalizeNilUuid = <T>(value: T): T | undefined =>
|
|
25
|
+
isNilUuid(value) ? undefined : value
|