285 lines
7.4 KiB
TypeScript
285 lines
7.4 KiB
TypeScript
'use client';
|
|
|
|
import { useState } from 'react';
|
|
import { Plus, Minus, ChevronDown, ChevronRight } from 'lucide-react';
|
|
import { Button } from './ui/button';
|
|
import { Input } from './ui/input';
|
|
import { Card } from './ui/card';
|
|
|
|
export interface SchemaField {
|
|
id: string;
|
|
name: string;
|
|
type: 'field' | 'group';
|
|
description: string;
|
|
fields?: SchemaField[];
|
|
}
|
|
|
|
const defaultSchema: SchemaField[] = [
|
|
{
|
|
id: '1',
|
|
name: 'company',
|
|
type: 'field',
|
|
description: 'name of company'
|
|
},
|
|
{
|
|
id: '2',
|
|
name: 'address',
|
|
type: 'field',
|
|
description: 'address of company'
|
|
},
|
|
{
|
|
id: '3',
|
|
name: 'total_sum',
|
|
type: 'field',
|
|
description: 'total amount we purchased'
|
|
},
|
|
{
|
|
id: '4',
|
|
name: 'items',
|
|
type: 'group',
|
|
description: 'list of items purchased',
|
|
fields: [
|
|
{
|
|
id: '4.1',
|
|
name: 'item',
|
|
type: 'field',
|
|
description: 'name of item'
|
|
},
|
|
{
|
|
id: '4.2',
|
|
name: 'unit_price',
|
|
type: 'field',
|
|
description: 'unit price of item'
|
|
},
|
|
{
|
|
id: '4.3',
|
|
name: 'quantity',
|
|
type: 'field',
|
|
description: 'quantity we purchased'
|
|
},
|
|
{
|
|
id: '4.4',
|
|
name: 'sum',
|
|
type: 'field',
|
|
description: 'total amount we purchased'
|
|
}
|
|
]
|
|
}
|
|
];
|
|
|
|
interface SchemaDefinitionProps {
|
|
onSchemaChange: (schema: SchemaField[]) => void;
|
|
}
|
|
|
|
export function SchemaDefinition({ onSchemaChange }: SchemaDefinitionProps) {
|
|
const [schema, setSchema] = useState<SchemaField[]>(defaultSchema);
|
|
const [expandedGroups, setExpandedGroups] = useState<Set<string>>(new Set(['4']));
|
|
|
|
const toggleGroup = (id: string) => {
|
|
setExpandedGroups(prev => {
|
|
const next = new Set(prev);
|
|
if (next.has(id)) {
|
|
next.delete(id);
|
|
} else {
|
|
next.add(id);
|
|
}
|
|
return next;
|
|
});
|
|
};
|
|
|
|
const addField = (parentId?: string) => {
|
|
const newField: SchemaField = {
|
|
id: Date.now().toString(),
|
|
name: '',
|
|
type: 'field',
|
|
description: ''
|
|
};
|
|
|
|
if (parentId) {
|
|
setSchema(prev => {
|
|
const updateFields = (fields: SchemaField[]): SchemaField[] => {
|
|
return fields.map(field => {
|
|
if (field.id === parentId) {
|
|
return {
|
|
...field,
|
|
fields: [...(field.fields || []), newField]
|
|
};
|
|
}
|
|
if (field.fields) {
|
|
return {
|
|
...field,
|
|
fields: updateFields(field.fields)
|
|
};
|
|
}
|
|
return field;
|
|
});
|
|
};
|
|
return updateFields(prev);
|
|
});
|
|
} else {
|
|
setSchema(prev => [...prev, newField]);
|
|
}
|
|
};
|
|
|
|
const addGroup = (parentId?: string) => {
|
|
const newGroup: SchemaField = {
|
|
id: Date.now().toString(),
|
|
name: '',
|
|
type: 'group',
|
|
description: '',
|
|
fields: []
|
|
};
|
|
|
|
if (parentId) {
|
|
setSchema(prev => {
|
|
const updateFields = (fields: SchemaField[]): SchemaField[] => {
|
|
return fields.map(field => {
|
|
if (field.id === parentId) {
|
|
return {
|
|
...field,
|
|
fields: [...(field.fields || []), newGroup]
|
|
};
|
|
}
|
|
if (field.fields) {
|
|
return {
|
|
...field,
|
|
fields: updateFields(field.fields)
|
|
};
|
|
}
|
|
return field;
|
|
});
|
|
};
|
|
return updateFields(prev);
|
|
});
|
|
} else {
|
|
setSchema(prev => [...prev, newGroup]);
|
|
}
|
|
setExpandedGroups(prev => new Set([...prev, newGroup.id]));
|
|
};
|
|
|
|
const removeField = (id: string) => {
|
|
const removeFieldFromArray = (fields: SchemaField[]): SchemaField[] => {
|
|
return fields.filter(field => {
|
|
if (field.id === id) return false;
|
|
if (field.fields) {
|
|
field.fields = removeFieldFromArray(field.fields);
|
|
}
|
|
return true;
|
|
});
|
|
};
|
|
|
|
setSchema(prev => removeFieldFromArray(prev));
|
|
};
|
|
|
|
const updateField = (id: string, updates: Partial<SchemaField>) => {
|
|
const updateFieldInArray = (fields: SchemaField[]): SchemaField[] => {
|
|
return fields.map(field => {
|
|
if (field.id === id) {
|
|
return { ...field, ...updates };
|
|
}
|
|
if (field.fields) {
|
|
return {
|
|
...field,
|
|
fields: updateFieldInArray(field.fields)
|
|
};
|
|
}
|
|
return field;
|
|
});
|
|
};
|
|
|
|
const updatedSchema = updateFieldInArray(schema);
|
|
setSchema(updatedSchema);
|
|
onSchemaChange(updatedSchema);
|
|
};
|
|
|
|
const renderField = (field: SchemaField, depth = 0) => {
|
|
const isExpanded = expandedGroups.has(field.id);
|
|
|
|
return (
|
|
<div key={field.id} className="space-y-2" style={{ marginLeft: `${depth * 20}px` }}>
|
|
<Card className="p-4">
|
|
<div className="flex items-center space-x-2">
|
|
{field.type === 'group' && (
|
|
<Button
|
|
variant="ghost"
|
|
size="icon"
|
|
onClick={() => toggleGroup(field.id)}
|
|
>
|
|
{isExpanded ? (
|
|
<ChevronDown className="h-4 w-4" />
|
|
) : (
|
|
<ChevronRight className="h-4 w-4" />
|
|
)}
|
|
</Button>
|
|
)}
|
|
<Input
|
|
placeholder="Field name"
|
|
value={field.name}
|
|
onChange={(e) => updateField(field.id, { name: e.target.value })}
|
|
className="flex-1"
|
|
/>
|
|
<Input
|
|
placeholder="Description"
|
|
value={field.description}
|
|
onChange={(e) => updateField(field.id, { description: e.target.value })}
|
|
className="flex-1"
|
|
/>
|
|
<Button
|
|
variant="destructive"
|
|
size="icon"
|
|
onClick={() => removeField(field.id)}
|
|
>
|
|
<Minus className="h-4 w-4" />
|
|
</Button>
|
|
</div>
|
|
</Card>
|
|
|
|
{field.type === 'group' && isExpanded && (
|
|
<div className="space-y-2">
|
|
{field.fields?.map(subField => renderField(subField, depth + 1))}
|
|
<div className="flex space-x-2" style={{ marginLeft: `${(depth + 1) * 20}px` }}>
|
|
<Button
|
|
variant="outline"
|
|
size="sm"
|
|
onClick={() => addField(field.id)}
|
|
>
|
|
<Plus className="h-4 w-4 mr-2" />
|
|
Add Field
|
|
</Button>
|
|
<Button
|
|
variant="outline"
|
|
size="sm"
|
|
onClick={() => addGroup(field.id)}
|
|
>
|
|
<Plus className="h-4 w-4 mr-2" />
|
|
Add Group
|
|
</Button>
|
|
</div>
|
|
</div>
|
|
)}
|
|
</div>
|
|
);
|
|
};
|
|
|
|
return (
|
|
<div className="space-y-4">
|
|
<div className="flex justify-between items-center">
|
|
<h2 className="text-lg font-semibold">Schema Definition</h2>
|
|
<div className="space-x-2">
|
|
<Button variant="outline" onClick={() => addField()}>
|
|
<Plus className="h-4 w-4 mr-2" />
|
|
Add Field
|
|
</Button>
|
|
<Button variant="outline" onClick={() => addGroup()}>
|
|
<Plus className="h-4 w-4 mr-2" />
|
|
Add Group
|
|
</Button>
|
|
</div>
|
|
</div>
|
|
<div className="space-y-2">
|
|
{schema.map(field => renderField(field))}
|
|
</div>
|
|
</div>
|
|
);
|
|
}
|