Fix array label properties display
This commit is contained in:
@@ -2,6 +2,15 @@
|
|||||||
|
|
||||||
All notable changes to this project will be documented in this file. This project adheres to [Semantic Versioning](http://semver.org/).
|
All notable changes to this project will be documented in this file. This project adheres to [Semantic Versioning](http://semver.org/).
|
||||||
|
|
||||||
|
## [0.1.1] - 2026-04-XX
|
||||||
|
|
||||||
|
### Added
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
- Fix array label properties display.
|
||||||
|
|
||||||
## [0.1.0] - 2026-04-20
|
## [0.1.0] - 2026-04-20
|
||||||
|
|
||||||
### Added
|
### Added
|
||||||
|
|||||||
@@ -48,8 +48,14 @@ export function ObjectPicker({ schema, objectName, value, onChange, onClear, pla
|
|||||||
return (
|
return (
|
||||||
<div className="flex items-center gap-2 flex-wrap">
|
<div className="flex items-center gap-2 flex-wrap">
|
||||||
{value && (
|
{value && (
|
||||||
<Badge variant="secondary" className="gap-1 pr-1.5 text-sm">
|
<Badge variant="secondary" className="gap-1 pr-1.5 text-sm max-w-xs">
|
||||||
{labelLoading ? <Loader2 className="h-3 w-3 animate-spin" /> : (display ?? value)}
|
{labelLoading ? (
|
||||||
|
<Loader2 className="h-3 w-3 animate-spin" />
|
||||||
|
) : (
|
||||||
|
<span className="truncate" title={display ?? value}>
|
||||||
|
{display ?? value}
|
||||||
|
</span>
|
||||||
|
)}
|
||||||
{onClear && (
|
{onClear && (
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
@@ -121,7 +127,9 @@ export function ObjectPicker({ schema, objectName, value, onChange, onClear, pla
|
|||||||
setOpen(false);
|
setOpen(false);
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{opt.label}
|
<span className="truncate" title={opt.label}>
|
||||||
|
{opt.label}
|
||||||
|
</span>
|
||||||
</CommandItem>
|
</CommandItem>
|
||||||
))}
|
))}
|
||||||
</CommandGroup>
|
</CommandGroup>
|
||||||
|
|||||||
@@ -47,6 +47,7 @@ import {
|
|||||||
import { jmapGet, jmapSet, jmapRequest, getAccountId } from '@/services/jmap/client';
|
import { jmapGet, jmapSet, jmapRequest, getAccountId } from '@/services/jmap/client';
|
||||||
import { calculateJmapPatch } from '@/lib/jmapPatch';
|
import { calculateJmapPatch } from '@/lib/jmapPatch';
|
||||||
import { friendlySetError } from '@/lib/jmapErrors';
|
import { friendlySetError } from '@/lib/jmapErrors';
|
||||||
|
import { coerceLabel } from '@/lib/objectOptions';
|
||||||
import { toast } from '@/hooks/use-toast';
|
import { toast } from '@/hooks/use-toast';
|
||||||
import { logFormChange } from '@/lib/debug';
|
import { logFormChange } from '@/lib/debug';
|
||||||
import { FieldWidget } from '@/components/forms/FieldWidget';
|
import { FieldWidget } from '@/components/forms/FieldWidget';
|
||||||
@@ -695,9 +696,9 @@ export function DynamicForm({ viewName, objectId }: DynamicFormProps) {
|
|||||||
|
|
||||||
const labelProp = list?.labelProperty;
|
const labelProp = list?.labelProperty;
|
||||||
if (labelProp) {
|
if (labelProp) {
|
||||||
const raw = formData[labelProp];
|
const value = coerceLabel(formData[labelProp], '');
|
||||||
if (typeof raw === 'string' && raw.length > 0) {
|
if (value.length > 0) {
|
||||||
return t('form.editTitleWithValue', 'Edit {{name}}: {{value}}', { name, value: raw });
|
return t('form.editTitleWithValue', 'Edit {{name}}: {{value}}', { name, value });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return t('form.editTitle', 'Edit {{name}}', { name });
|
return t('form.editTitle', 'Edit {{name}}', { name });
|
||||||
|
|||||||
@@ -48,6 +48,7 @@ import { Collapsible, CollapsibleTrigger, CollapsibleContent } from '@/component
|
|||||||
import { ObjectPicker } from '@/components/common/ObjectPicker';
|
import { ObjectPicker } from '@/components/common/ObjectPicker';
|
||||||
import { toast } from '@/hooks/use-toast';
|
import { toast } from '@/hooks/use-toast';
|
||||||
import { friendlySetError } from '@/lib/jmapErrors';
|
import { friendlySetError } from '@/lib/jmapErrors';
|
||||||
|
import { coerceLabel } from '@/lib/objectOptions';
|
||||||
|
|
||||||
import { useSchemaStore } from '@/stores/schemaStore';
|
import { useSchemaStore } from '@/stores/schemaStore';
|
||||||
import { useAuthStore } from '@/stores/authStore';
|
import { useAuthStore } from '@/stores/authStore';
|
||||||
@@ -515,7 +516,7 @@ export function DynamicList({ viewName }: DynamicListProps) {
|
|||||||
for (const obj of list) {
|
for (const obj of list) {
|
||||||
const id = obj.id as string;
|
const id = obj.id as string;
|
||||||
if (!id) continue;
|
if (!id) continue;
|
||||||
entries[id] = (obj[displayProp] as string) ?? id;
|
entries[id] = coerceLabel(obj[displayProp], id);
|
||||||
}
|
}
|
||||||
if (Object.keys(entries).length > 0) {
|
if (Object.keys(entries).length > 0) {
|
||||||
setDisplayNames(refType, entries);
|
setDisplayNames(refType, entries);
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ import { ArrowLeft, Loader2 } from 'lucide-react';
|
|||||||
import { Button } from '@/components/ui/button';
|
import { Button } from '@/components/ui/button';
|
||||||
import { useSchemaStore } from '@/stores/schemaStore';
|
import { useSchemaStore } from '@/stores/schemaStore';
|
||||||
import { resolveObject, resolveList } from '@/lib/schemaResolver';
|
import { resolveObject, resolveList } from '@/lib/schemaResolver';
|
||||||
|
import { coerceLabel } from '@/lib/objectOptions';
|
||||||
import { jmapGet, getAccountId } from '@/services/jmap/client';
|
import { jmapGet, getAccountId } from '@/services/jmap/client';
|
||||||
import { DynamicView } from './DynamicView';
|
import { DynamicView } from './DynamicView';
|
||||||
|
|
||||||
@@ -89,7 +90,8 @@ export function DynamicViewPage({ viewName, objectId }: DynamicViewPageProps) {
|
|||||||
|
|
||||||
const list = resolveList(schema, viewName, resolved.objectName);
|
const list = resolveList(schema, viewName, resolved.objectName);
|
||||||
const labelProp = list?.labelProperty ?? list?.columns?.[0]?.name;
|
const labelProp = list?.labelProperty ?? list?.columns?.[0]?.name;
|
||||||
const displayName = labelProp && typeof data[labelProp] === 'string' ? (data[labelProp] as string) : undefined;
|
const rawLabel = labelProp ? coerceLabel(data[labelProp], '') : '';
|
||||||
|
const displayName = rawLabel.length > 0 ? rawLabel : undefined;
|
||||||
const singularName = list?.singularName ?? resolved.objectName.replace(/^x:/, '');
|
const singularName = list?.singularName ?? resolved.objectName.replace(/^x:/, '');
|
||||||
const title = displayName
|
const title = displayName
|
||||||
? `${singularName.charAt(0).toUpperCase() + singularName.slice(1)}: ${displayName}`
|
? `${singularName.charAt(0).toUpperCase() + singularName.slice(1)}: ${displayName}`
|
||||||
|
|||||||
@@ -21,6 +21,18 @@ interface PendingBatch {
|
|||||||
|
|
||||||
const pendingByType = new Map<string, PendingBatch>();
|
const pendingByType = new Map<string, PendingBatch>();
|
||||||
|
|
||||||
|
export function coerceLabel(value: unknown, fallback: string): string {
|
||||||
|
if (value == null) return fallback;
|
||||||
|
if (typeof value === 'string') return value;
|
||||||
|
if (typeof value === 'number' || typeof value === 'boolean') return String(value);
|
||||||
|
if (Array.isArray(value)) return value.length > 0 ? value.map((v) => coerceLabel(v, '')).filter(Boolean).join(', ') : fallback;
|
||||||
|
if (typeof value === 'object') {
|
||||||
|
const keys = Object.keys(value as Record<string, unknown>);
|
||||||
|
return keys.length > 0 ? keys.join(', ') : fallback;
|
||||||
|
}
|
||||||
|
return fallback;
|
||||||
|
}
|
||||||
|
|
||||||
function requestObjectLabel(parentObjectName: string, viewOrObjectName: string, id: string, schema: Schema): void {
|
function requestObjectLabel(parentObjectName: string, viewOrObjectName: string, id: string, schema: Schema): void {
|
||||||
const existing = pendingByType.get(parentObjectName);
|
const existing = pendingByType.get(parentObjectName);
|
||||||
if (existing) {
|
if (existing) {
|
||||||
@@ -54,7 +66,7 @@ async function executeBatch(parentObjectName: string, batch: PendingBatch): Prom
|
|||||||
for (const item of list) {
|
for (const item of list) {
|
||||||
const itemId = item.id as string;
|
const itemId = item.id as string;
|
||||||
if (!itemId) continue;
|
if (!itemId) continue;
|
||||||
entries[itemId] = (item[batch.displayProp] as string) ?? itemId;
|
entries[itemId] = coerceLabel(item[batch.displayProp], itemId);
|
||||||
}
|
}
|
||||||
for (const id of ids) {
|
for (const id of ids) {
|
||||||
if (!(id in entries)) entries[id] = id;
|
if (!(id in entries)) entries[id] = id;
|
||||||
@@ -95,7 +107,7 @@ async function fetchObjectList(viewOrObjectName: string, schema: Schema): Promis
|
|||||||
|
|
||||||
return items.map((item) => ({
|
return items.map((item) => ({
|
||||||
id: item.id as string,
|
id: item.id as string,
|
||||||
label: (item[displayProp] as string) ?? (item.id as string),
|
label: coerceLabel(item[displayProp], item.id as string),
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user