Files
blog/src_generic/components/GenericForm.tsx

203 lines
4.9 KiB
TypeScript

import * as React from 'react';
import {
Box,
TextField,
Button,
FormControl,
InputLabel,
Select,
MenuItem,
FormControlLabel,
Checkbox,
Typography,
Divider,
} from '@mui/material';
import { ResourceConfig, ResourceField } from '../types/config';
import { useUpload } from '../providers/UploadProvider';
import ImageUploadField from './fields/ImageUploadField';
interface GenericFormProps {
config: ResourceConfig;
initialData?: any;
onSave: (data: any) => Promise<void>;
onCancel: () => void;
loading?: boolean;
}
import { ConfigContext } from '../App';
export default function GenericForm({
config,
initialData = {},
onSave,
onCancel,
loading: saving,
}: GenericFormProps) {
const [formData, setFormData] = React.useState(initialData);
const { uploadFile, uploading } = useUpload();
const appConfig = React.useContext(ConfigContext);
const handleChange = (key: string, value: any) => {
setFormData((prev: any) => ({ ...prev, [key]: value }));
};
const handleSubmit = (e: React.FormEvent) => {
e.preventDefault();
onSave(formData);
};
return (
<Box component="form" onSubmit={handleSubmit} sx={{ display: 'flex', flexDirection: 'column', gap: 3 }}>
<Typography variant="h5">
{initialData[config.primaryKey] ? `Edit ${config.label}` : `New ${config.label}`}
</Typography>
<Divider />
{Object.entries(config.fields).map(([key, field]) => (
<FormField
key={key}
name={key}
field={field}
value={formData[key]}
onChange={(val: any) => handleChange(key, val)}
disabled={field.readOnly}
uploadFile={uploadFile}
uploading={uploading}
baseUrl={appConfig?.baseUrl || ""}
/>
))}
<Box sx={{ display: 'flex', justifyContent: 'flex-end', gap: 2, mt: 4 }}>
<Button variant="outlined" onClick={onCancel} disabled={saving}>
Cancel
</Button>
<Button variant="contained" type="submit" loading={saving} disabled={saving || uploading}>
Save {config.label}
</Button>
</Box>
</Box>
);
}
function FormField({ name, field, value, onChange, disabled, uploadFile, uploading, baseUrl }: any) {
const label = field.label;
if (field.type === 'image') {
return (
<ImageUploadField
label={label}
value={value}
onUpload={async (file: any) => {
const url = await uploadFile(file);
if (url) onChange(url);
}}
uploading={uploading}
baseUrl={baseUrl}
/>
);
}
if (field.type === 'boolean') {
return (
<FormControlLabel
control={
<Checkbox
checked={!!value}
onChange={(e) => onChange(e.target.checked)}
disabled={disabled}
/>
}
label={label}
/>
);
}
if (field.type === 'enum' && field.options) {
return (
<FormControl fullWidth>
<InputLabel>{label}</InputLabel>
<Select
value={value || ''}
label={label}
onChange={(e) => onChange(e.target.value)}
disabled={disabled}
>
{field.options.map((opt: string) => (
<MenuItem key={opt} value={opt}>
{opt}
</MenuItem>
))}
</Select>
</FormControl>
);
}
if (field.type === 'datetime') {
return (
<TextField
fullWidth
label={label}
type="datetime-local"
InputLabelProps={{ shrink: true }}
value={value ? new Date(value).toISOString().slice(0, 16) : ''}
onChange={(e) => onChange(e.target.value)}
disabled={disabled}
required={field.required}
/>
);
}
if (field.type === 'date') {
return (
<TextField
fullWidth
label={label}
type="date"
InputLabelProps={{ shrink: true }}
value={value ? new Date(value).toISOString().split('T')[0] : ''}
onChange={(e) => onChange(e.target.value)}
disabled={disabled}
required={field.required}
/>
);
}
if (field.type === 'markdown' || field.type === 'string') {
return (
<TextField
fullWidth
label={label}
value={value || ''}
multiline={field.type === 'markdown'}
rows={field.type === 'markdown' ? 4 : 1}
onChange={(e) => onChange(e.target.value)}
disabled={disabled}
required={field.required}
/>
);
}
if (field.type === 'number') {
return (
<TextField
fullWidth
label={label}
type="number"
value={value || 0}
onChange={(e) => onChange(Number(e.target.value))}
disabled={disabled}
required={field.required}
/>
);
}
return (
<TextField
fullWidth
label={label}
value={JSON.stringify(value)}
disabled
/>
);
}