Files
saas_frontend/src/application/modules/admin/components/ModuleForm.tsx
T

180 lines
7.3 KiB
TypeScript

import { useEffect } from 'react';
import { useForm } from 'react-hook-form';
import { X } from 'lucide-react';
import { useModuleApi } from '../hooks/useModuleApi';
import type { Module, ModuleCreate, ModuleUpdate, ModuleStatus } from '../AdminModuleTypes';
import CustomInput from '../../../../components/custom/CustomInput';
import CustomButton from '../../../../components/custom/CustomButton';
interface ModuleFormProps {
module: Module | null;
onClose: (saved: boolean) => void;
}
interface ModuleFormData {
module_id: string;
module_name: string;
description: string;
icon_url: string;
status: ModuleStatus;
display_order: number;
}
const ModuleForm = ({ module, onClose }: ModuleFormProps) => {
const { createModule, updateModule, loading } = useModuleApi();
const { register, handleSubmit, formState: { errors }, reset } = useForm<ModuleFormData>({
defaultValues: {
module_id: '',
module_name: '',
description: '',
icon_url: '',
status: 'active',
display_order: 0,
}
});
useEffect(() => {
if (module) {
reset({
module_id: module.module_id,
module_name: module.module_name,
description: module.description || '',
icon_url: module.icon_url || '',
status: module.status,
display_order: module.display_order,
});
}
}, [module, reset]);
const onSubmit = async (data: ModuleFormData) => {
let success;
if (module) {
const updateData: ModuleUpdate = {
module_name: data.module_name,
description: data.description || undefined,
icon_url: data.icon_url || undefined,
status: data.status,
display_order: data.display_order,
};
success = await updateModule(module.id, updateData);
} else {
const createData: ModuleCreate = {
module_id: data.module_id,
module_name: data.module_name,
description: data.description || undefined,
icon_url: data.icon_url || undefined,
status: data.status,
display_order: data.display_order,
};
success = await createModule(createData);
}
if (success) {
onClose(true);
}
};
return (
<div className="fixed inset-0 bg-black/50 backdrop-blur-sm flex items-center justify-center z-50 p-4">
<div className="bg-(--card-bg) rounded-lg shadow-xl max-w-2xl w-full max-h-[90vh] overflow-y-auto border border-(--card-border)">
<div className="sticky top-0 bg-(--card-bg) border-b border-(--card-border) px-6 py-4 flex items-center justify-between z-10">
<h2 className="text-xl font-semibold text-(--text-primary)">
{module ? 'Edit Module' : 'Create Module'}
</h2>
<button
onClick={() => onClose(false)}
className="p-2 hover:bg-(--background) rounded-lg transition-colors text-(--text-secondary) hover:text-(--text-primary)"
>
<X size={20} />
</button>
</div>
<form onSubmit={handleSubmit(onSubmit)} className="p-6 space-y-4">
<CustomInput
label="Module ID *"
placeholder="e.g., inventory"
{...register('module_id', {
required: 'Module ID is required',
pattern: {
value: /^[a-z0-9-]+$/,
message: 'Only lowercase alphanumeric and hyphens allowed'
}
})}
error={errors.module_id?.message}
disabled={!!module}
/>
<p className="text-xs text-(--text-secondary) -mt-3 ml-1">Unique identifier (lowercase, alphanumeric, hyphens)</p>
<CustomInput
label="Module Name *"
placeholder="e.g., Inventory Management"
{...register('module_name', { required: 'Module Name is required' })}
error={errors.module_name?.message}
/>
<div className="space-y-1.5">
<label className="block text-sm font-medium text-(--text-primary)">
Description
</label>
<textarea
{...register('description')}
className="w-full px-3 py-2 border border-(--card-border) rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500 bg-(--background) text-(--text-primary) placeholder-(--text-secondary)/50"
placeholder="Brief description of the module"
rows={3}
/>
</div>
<CustomInput
label="Icon URL"
placeholder="https://example.com/icon.png"
type="url"
{...register('icon_url')}
/>
<div className="grid grid-cols-2 gap-4">
<div className="space-y-1.5">
<label className="block text-sm font-medium text-(--text-primary)">
Status
</label>
<select
{...register('status')}
className="w-full px-3 py-2 border border-(--card-border) rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500 bg-(--background) text-(--text-primary)"
>
<option value="active">Active</option>
<option value="inactive">Inactive</option>
</select>
</div>
<CustomInput
label="Display Order"
type="number"
{...register('display_order', { valueAsNumber: true })}
/>
</div>
<div className="flex items-center gap-3 pt-4">
<CustomButton
type="submit"
loading={loading}
className="flex-1"
>
{module ? 'Update Module' : 'Create Module'}
</CustomButton>
<CustomButton
type="button"
variant="secondary"
onClick={() => onClose(false)}
className="flex-1"
disabled={loading}
>
Cancel
</CustomButton>
</div>
</form>
</div>
</div>
);
};
export default ModuleForm;