149 lines
4.0 KiB
TypeScript
149 lines
4.0 KiB
TypeScript
"use client";
|
|
|
|
import React, { useState, useEffect } from "react";
|
|
import { Stack, Typography, Button, Alert, TextField } from "@mui/material";
|
|
import Modal from "@/app/components/Modal/Modal";
|
|
import Spinner from "@/app/components/Spinner/Spinner";
|
|
import { IAddModalProps } from "./types";
|
|
|
|
const AddModal: React.FC<IAddModalProps> = ({
|
|
open,
|
|
onClose,
|
|
onConfirm,
|
|
resourceType = "item",
|
|
fields,
|
|
isLoading = false,
|
|
error = null,
|
|
}) => {
|
|
const [formData, setFormData] = useState<Record<string, unknown>>({});
|
|
const [validationErrors, setValidationErrors] = useState<
|
|
Record<string, string>
|
|
>({});
|
|
|
|
useEffect(() => {
|
|
if (open) {
|
|
const initialData: Record<string, unknown> = {};
|
|
fields.forEach(field => {
|
|
initialData[field.name] = field.defaultValue ?? "";
|
|
});
|
|
setFormData(initialData);
|
|
setValidationErrors({});
|
|
}
|
|
}, [open, fields]);
|
|
|
|
const handleChange = (
|
|
e: React.ChangeEvent<HTMLInputElement | HTMLTextAreaElement>
|
|
) => {
|
|
const { name, value } = e.target;
|
|
setFormData(prev => ({ ...prev, [name]: value }));
|
|
|
|
if (validationErrors[name]) {
|
|
setValidationErrors(prev => {
|
|
const next = { ...prev };
|
|
delete next[name];
|
|
return next;
|
|
});
|
|
}
|
|
};
|
|
|
|
const validateForm = (): boolean => {
|
|
const errors: Record<string, string> = {};
|
|
|
|
fields.forEach(field => {
|
|
const value = formData[field.name];
|
|
const isEmpty =
|
|
value === undefined || value === null || String(value).trim() === "";
|
|
|
|
if (field.required && isEmpty) {
|
|
errors[field.name] = `${field.label} is required`;
|
|
}
|
|
|
|
if (field.type === "email" && value && !isEmpty) {
|
|
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
|
|
if (!emailRegex.test(String(value))) {
|
|
errors[field.name] = "Please enter a valid email address";
|
|
}
|
|
}
|
|
});
|
|
|
|
setValidationErrors(errors);
|
|
return Object.keys(errors).length === 0;
|
|
};
|
|
|
|
const handleSubmit = async (e: React.FormEvent) => {
|
|
e.preventDefault();
|
|
|
|
if (!validateForm()) {
|
|
return;
|
|
}
|
|
|
|
await Promise.resolve(onConfirm(formData));
|
|
};
|
|
|
|
const handleClose = () => {
|
|
if (!isLoading) {
|
|
setFormData({});
|
|
setValidationErrors({});
|
|
onClose();
|
|
}
|
|
};
|
|
|
|
const title = `Add ${resourceType.charAt(0).toUpperCase() + resourceType.slice(1)}`;
|
|
|
|
return (
|
|
<Modal open={open} onClose={handleClose} title={title}>
|
|
<form onSubmit={handleSubmit}>
|
|
<Stack spacing={3}>
|
|
{fields.map(field => (
|
|
<TextField
|
|
key={field.name}
|
|
name={field.name}
|
|
label={field.label}
|
|
type={field.type === "number" ? "number" : field.type || "text"}
|
|
value={formData[field.name] ?? ""}
|
|
onChange={handleChange}
|
|
required={field.required}
|
|
placeholder={field.placeholder}
|
|
error={!!validationErrors[field.name]}
|
|
helperText={validationErrors[field.name]}
|
|
disabled={isLoading}
|
|
multiline={field.multiline || field.type === "textarea"}
|
|
rows={field.rows || (field.multiline ? 4 : undefined)}
|
|
fullWidth
|
|
/>
|
|
))}
|
|
|
|
{error && (
|
|
<Alert severity="error" sx={{ mt: 1 }}>
|
|
{error}
|
|
</Alert>
|
|
)}
|
|
|
|
<Stack direction="row" spacing={2} justifyContent="flex-end">
|
|
<Button
|
|
variant="outlined"
|
|
onClick={handleClose}
|
|
disabled={isLoading}
|
|
>
|
|
Cancel
|
|
</Button>
|
|
<Button
|
|
type="submit"
|
|
color="primary"
|
|
variant="contained"
|
|
disabled={isLoading}
|
|
startIcon={
|
|
isLoading ? <Spinner size="small" color="#fff" /> : null
|
|
}
|
|
>
|
|
{isLoading ? "Adding..." : `Add ${resourceType}`}
|
|
</Button>
|
|
</Stack>
|
|
</Stack>
|
|
</form>
|
|
</Modal>
|
|
);
|
|
};
|
|
|
|
export default AddModal;
|