diff --git a/src/components/forms/DynamicForm.tsx b/src/components/forms/DynamicForm.tsx index 0089c7a..b6ebbc3 100644 --- a/src/components/forms/DynamicForm.tsx +++ b/src/components/forms/DynamicForm.tsx @@ -360,6 +360,11 @@ export function DynamicForm({ client, activityId: initialActivityId, instanceId: val = ''; } + // If select options are unselected/empty, explicitly send an empty string to the payload + if ((f.data_type === 'select' || f.data_type === 'multiselect' || f.data_type === 'wf_lookup' || f.data_type === 'radio') && val == null) { + val = ''; + } + if (val == null) continue; if (f.data_type === 'phone' && typeof val === 'string') { @@ -380,6 +385,17 @@ export function DynamicForm({ client, activityId: initialActivityId, instanceId: const fileMeta = await client.uploadFile(val, { activityId: currentActivityId, fieldId: f.id, instanceId: currentInstanceId }); payload[f.id] = [fileMeta]; } else { + if (f.data_type === 'grid' && Array.isArray(val)) { + val = val.map(row => { + const newRow = { ...row }; + f.columns?.forEach(col => { + if ((col.data_type === 'select' || col.data_type === 'multiselect' || col.data_type === 'wf_lookup' || col.data_type === 'radio') && newRow[col.id] == null) { + newRow[col.id] = ''; + } + }); + return newRow; + }); + } payload[f.id] = val; } } diff --git a/src/components/rv/RecordView.tsx b/src/components/rv/RecordView.tsx index a7c016a..ee3fb00 100644 --- a/src/components/rv/RecordView.tsx +++ b/src/components/rv/RecordView.tsx @@ -52,6 +52,36 @@ export interface RecordViewProps { initialFilters?: Record; } +/** + * Helper to remove ugly UUIDs (with or without hyphens) from raw string values. + */ +function formatFilterLabel(raw: string): string { + if (!raw) return raw; + const stripped = raw.replace(/[_\-\s]*\(?(?:[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}|[0-9a-f]{32})\)?/gi, '').trim(); + return stripped || raw; // fallback to raw if the whole string was a UUID +} + +/** + * Resolves a human-readable label for a field key, handling nested sub-columns and stripping UUIDs. + */ +function getFieldLabel(key: string, fields: RecordViewField[] = []): string { + const exactMatch = fields.find((f) => f.field_key === key); + if (exactMatch?.output_label) return exactMatch.output_label; + + if (key.includes('.')) { + const [mainKey, subKey] = key.split('.', 2); + const mainField = fields.find((f) => f.field_key === mainKey); + if (mainField?.output_label) { + const subLabel = subKey.replace(/_/g, ' ').replace(/\b\w/g, c => c.toUpperCase()); + return `${mainField.output_label} (${subLabel})`; + } + } + + // Fallback: Strip UUIDs and format the raw key + const noUuid = formatFilterLabel(key); + return noUuid.replace(/_/g, ' ').replace(/\b\w/g, c => c.toUpperCase()).trim(); +} + /** * Generic Zino record-view table for mobile. Fetches `POST /app/{id}/view/recordview` * (server-side paginated + searchable) and renders cards or table items. @@ -232,15 +262,14 @@ export function RecordView({
Filters: {Object.entries(activeFilters).flatMap(([key, vals]) => { - const fieldDef = resp?.config?.fields?.find((f) => f.field_key === key); - const label = fieldDef?.output_label || key.replace(/_/g, ' ').replace(/\b\w/g, (c) => c.toUpperCase()); + const label = getFieldLabel(key, resp?.config?.fields); return (vals || []).map((val) => ( {label}: - {val} + {formatFilterLabel(val)}