|
| 1 | +/** |
| 2 | + * ViewDesignerPage |
| 3 | + * |
| 4 | + * Console page that wraps the ViewDesigner component for creating |
| 5 | + * and editing list views for a given object. |
| 6 | + * |
| 7 | + * Routes: |
| 8 | + * - /:objectName/views/new → Create new view |
| 9 | + * - /:objectName/views/:viewId → Edit existing view |
| 10 | + */ |
| 11 | + |
| 12 | +import { useMemo, useCallback } from 'react'; |
| 13 | +import { useParams, useNavigate } from 'react-router-dom'; |
| 14 | +import { ViewDesigner } from '@object-ui/plugin-designer'; |
| 15 | +import type { ViewDesignerConfig } from '@object-ui/plugin-designer'; |
| 16 | +import { toast } from 'sonner'; |
| 17 | + |
| 18 | +export function ViewDesignerPage({ objects }: { objects: any[] }) { |
| 19 | + const navigate = useNavigate(); |
| 20 | + const { objectName, viewId } = useParams(); |
| 21 | + |
| 22 | + const objectDef = objects.find((o: any) => o.name === objectName); |
| 23 | + |
| 24 | + // Build available fields from object definition |
| 25 | + const availableFields = useMemo(() => { |
| 26 | + if (!objectDef?.fields) return []; |
| 27 | + const fields = objectDef.fields; |
| 28 | + if (Array.isArray(fields)) { |
| 29 | + return fields.map((f: any) => |
| 30 | + typeof f === 'string' |
| 31 | + ? { name: f, label: f, type: 'text' } |
| 32 | + : { name: f.name, label: f.label || f.name, type: f.type || 'text' }, |
| 33 | + ); |
| 34 | + } |
| 35 | + return Object.entries(fields).map(([name, def]: [string, any]) => ({ |
| 36 | + name, |
| 37 | + label: def.label || name, |
| 38 | + type: def.type || 'text', |
| 39 | + })); |
| 40 | + }, [objectDef]); |
| 41 | + |
| 42 | + // Resolve existing view for editing |
| 43 | + const existingView = useMemo(() => { |
| 44 | + if (!viewId || viewId === 'new' || !objectDef?.list_views) return null; |
| 45 | + return objectDef.list_views[viewId] || null; |
| 46 | + }, [viewId, objectDef]); |
| 47 | + |
| 48 | + const handleSave = useCallback( |
| 49 | + (config: ViewDesignerConfig) => { |
| 50 | + // In a real implementation this would persist the view config. |
| 51 | + // For now, log and show toast. |
| 52 | + console.log('[ViewDesigner] Save view config:', config); |
| 53 | + toast.success( |
| 54 | + existingView |
| 55 | + ? `View "${config.viewLabel}" updated` |
| 56 | + : `View "${config.viewLabel}" created`, |
| 57 | + ); |
| 58 | + navigate(-1); |
| 59 | + }, |
| 60 | + [existingView, navigate], |
| 61 | + ); |
| 62 | + |
| 63 | + const handleCancel = useCallback(() => { |
| 64 | + navigate(-1); |
| 65 | + }, [navigate]); |
| 66 | + |
| 67 | + if (!objectDef) { |
| 68 | + return ( |
| 69 | + <div className="h-full flex items-center justify-center text-muted-foreground"> |
| 70 | + Object "{objectName}" not found |
| 71 | + </div> |
| 72 | + ); |
| 73 | + } |
| 74 | + |
| 75 | + return ( |
| 76 | + <div className="h-full flex flex-col"> |
| 77 | + <ViewDesigner |
| 78 | + objectName={objectDef.name} |
| 79 | + viewId={viewId === 'new' ? undefined : viewId} |
| 80 | + viewLabel={existingView?.label ?? ''} |
| 81 | + viewType={existingView?.type ?? 'grid'} |
| 82 | + columns={ |
| 83 | + existingView?.columns |
| 84 | + ? existingView.columns.map((c: string, i: number) => ({ |
| 85 | + field: c, |
| 86 | + label: availableFields.find((f: any) => f.name === c)?.label ?? c, |
| 87 | + visible: true, |
| 88 | + order: i, |
| 89 | + })) |
| 90 | + : [] |
| 91 | + } |
| 92 | + filters={existingView?.filter ?? []} |
| 93 | + sort={ |
| 94 | + existingView?.sort?.map((s: any) => ({ |
| 95 | + field: s.field, |
| 96 | + direction: s.order || s.direction || 'asc', |
| 97 | + })) ?? [] |
| 98 | + } |
| 99 | + availableFields={availableFields} |
| 100 | + options={existingView?.options ?? {}} |
| 101 | + onSave={handleSave} |
| 102 | + onCancel={handleCancel} |
| 103 | + className="flex-1" |
| 104 | + /> |
| 105 | + </div> |
| 106 | + ); |
| 107 | +} |
0 commit comments