uup
This commit is contained in:
199
app/components/forms/EnhancedCustomerForm.tsx
Normal file
199
app/components/forms/EnhancedCustomerForm.tsx
Normal file
@@ -0,0 +1,199 @@
|
||||
import { useEffect } from 'react';
|
||||
import { Form as RemixForm } from "@remix-run/react";
|
||||
import { Input } from "~/components/ui/Input";
|
||||
import { Textarea } from "~/components/ui/Textarea";
|
||||
import { Button } from "~/components/ui/Button";
|
||||
import { FormField } from "~/components/ui/FormField";
|
||||
import { Form, FormActions, FormSection, FormGrid } from "~/components/ui/Form";
|
||||
import { useFormValidation } from "~/hooks/useFormValidation";
|
||||
import { customerSchema } from "~/lib/form-validation";
|
||||
import type { Customer } from "~/types/database";
|
||||
|
||||
interface EnhancedCustomerFormProps {
|
||||
customer?: Customer;
|
||||
onCancel: () => void;
|
||||
errors?: Record<string, string>;
|
||||
isLoading: boolean;
|
||||
onSubmit?: (data: any) => void;
|
||||
}
|
||||
|
||||
export function EnhancedCustomerForm({
|
||||
customer,
|
||||
onCancel,
|
||||
errors = {},
|
||||
isLoading,
|
||||
onSubmit,
|
||||
}: EnhancedCustomerFormProps) {
|
||||
const {
|
||||
values,
|
||||
errors: validationErrors,
|
||||
touched,
|
||||
isValid,
|
||||
setValue,
|
||||
setTouched,
|
||||
reset,
|
||||
validate,
|
||||
getFieldProps,
|
||||
} = useFormValidation({
|
||||
schema: customerSchema,
|
||||
initialValues: {
|
||||
name: customer?.name || "",
|
||||
phone: customer?.phone || "",
|
||||
email: customer?.email || "",
|
||||
address: customer?.address || "",
|
||||
},
|
||||
validateOnChange: true,
|
||||
validateOnBlur: true,
|
||||
});
|
||||
|
||||
// Reset form when customer changes
|
||||
useEffect(() => {
|
||||
if (customer) {
|
||||
reset({
|
||||
name: customer.name || "",
|
||||
phone: customer.phone || "",
|
||||
email: customer.email || "",
|
||||
address: customer.address || "",
|
||||
});
|
||||
} else {
|
||||
reset({
|
||||
name: "",
|
||||
phone: "",
|
||||
email: "",
|
||||
address: "",
|
||||
});
|
||||
}
|
||||
}, [customer, reset]);
|
||||
|
||||
const handleSubmit = (e: React.FormEvent) => {
|
||||
e.preventDefault();
|
||||
|
||||
const { isValid: formIsValid } = validate();
|
||||
|
||||
if (formIsValid && onSubmit) {
|
||||
onSubmit(values);
|
||||
}
|
||||
};
|
||||
|
||||
const isEditing = !!customer;
|
||||
const combinedErrors = { ...validationErrors, ...errors };
|
||||
|
||||
return (
|
||||
<Form
|
||||
title={isEditing ? "تعديل بيانات العميل" : "إضافة عميل جديد"}
|
||||
description={isEditing ? "قم بتعديل بيانات العميل أدناه" : "أدخل بيانات العميل الجديد"}
|
||||
loading={isLoading}
|
||||
onSubmit={handleSubmit}
|
||||
>
|
||||
<input
|
||||
type="hidden"
|
||||
name="_action"
|
||||
value={isEditing ? "update" : "create"}
|
||||
/>
|
||||
{isEditing && (
|
||||
<input type="hidden" name="id" value={customer.id} />
|
||||
)}
|
||||
|
||||
<FormSection
|
||||
title="المعلومات الأساسية"
|
||||
description="البيانات الأساسية للعميل"
|
||||
>
|
||||
<FormGrid columns={2}>
|
||||
{/* Customer Name */}
|
||||
<FormField
|
||||
label="اسم العميل"
|
||||
required
|
||||
error={combinedErrors.name}
|
||||
htmlFor="name"
|
||||
>
|
||||
<Input
|
||||
id="name"
|
||||
name="name"
|
||||
type="text"
|
||||
placeholder="أدخل اسم العميل"
|
||||
disabled={isLoading}
|
||||
{...getFieldProps('name')}
|
||||
/>
|
||||
</FormField>
|
||||
|
||||
{/* Phone Number */}
|
||||
<FormField
|
||||
label="رقم الهاتف"
|
||||
error={combinedErrors.phone}
|
||||
htmlFor="phone"
|
||||
helperText="رقم الهاتف اختياري"
|
||||
>
|
||||
<Input
|
||||
id="phone"
|
||||
name="phone"
|
||||
type="tel"
|
||||
placeholder="أدخل رقم الهاتف"
|
||||
disabled={isLoading}
|
||||
dir="ltr"
|
||||
{...getFieldProps('phone')}
|
||||
/>
|
||||
</FormField>
|
||||
</FormGrid>
|
||||
</FormSection>
|
||||
|
||||
<FormSection
|
||||
title="معلومات الاتصال"
|
||||
description="بيانات الاتصال الإضافية"
|
||||
>
|
||||
{/* Email */}
|
||||
<FormField
|
||||
label="البريد الإلكتروني"
|
||||
error={combinedErrors.email}
|
||||
htmlFor="email"
|
||||
helperText="البريد الإلكتروني اختياري"
|
||||
>
|
||||
<Input
|
||||
id="email"
|
||||
name="email"
|
||||
type="email"
|
||||
placeholder="أدخل البريد الإلكتروني"
|
||||
disabled={isLoading}
|
||||
dir="ltr"
|
||||
{...getFieldProps('email')}
|
||||
/>
|
||||
</FormField>
|
||||
|
||||
{/* Address */}
|
||||
<FormField
|
||||
label="العنوان"
|
||||
error={combinedErrors.address}
|
||||
htmlFor="address"
|
||||
helperText="عنوان العميل اختياري"
|
||||
>
|
||||
<Textarea
|
||||
id="address"
|
||||
name="address"
|
||||
placeholder="أدخل عنوان العميل"
|
||||
rows={3}
|
||||
disabled={isLoading}
|
||||
{...getFieldProps('address')}
|
||||
/>
|
||||
</FormField>
|
||||
</FormSection>
|
||||
|
||||
<FormActions>
|
||||
<Button
|
||||
type="button"
|
||||
variant="outline"
|
||||
onClick={onCancel}
|
||||
disabled={isLoading}
|
||||
>
|
||||
إلغاء
|
||||
</Button>
|
||||
|
||||
<Button
|
||||
type="submit"
|
||||
disabled={isLoading || !isValid || !values.name?.trim()}
|
||||
loading={isLoading}
|
||||
>
|
||||
{isEditing ? "تحديث العميل" : "إنشاء العميل"}
|
||||
</Button>
|
||||
</FormActions>
|
||||
</Form>
|
||||
);
|
||||
}
|
||||
400
app/components/forms/EnhancedVehicleForm.tsx
Normal file
400
app/components/forms/EnhancedVehicleForm.tsx
Normal file
@@ -0,0 +1,400 @@
|
||||
import { useEffect } from 'react';
|
||||
import { Form as RemixForm } from "@remix-run/react";
|
||||
import { Input } from "~/components/ui/Input";
|
||||
import { Select } from "~/components/ui/Select";
|
||||
import { Button } from "~/components/ui/Button";
|
||||
import { FormField } from "~/components/ui/FormField";
|
||||
import { Form, FormActions, FormSection, FormGrid } from "~/components/ui/Form";
|
||||
import { useFormValidation } from "~/hooks/useFormValidation";
|
||||
import { vehicleSchema } from "~/lib/form-validation";
|
||||
import { TRANSMISSION_TYPES, FUEL_TYPES, USE_TYPES, BODY_TYPES, MANUFACTURERS, VALIDATION } from "~/lib/constants";
|
||||
import type { Vehicle } from "~/types/database";
|
||||
|
||||
interface EnhancedVehicleFormProps {
|
||||
vehicle?: Vehicle;
|
||||
customers: { id: number; name: string; phone?: string | null }[];
|
||||
onCancel: () => void;
|
||||
errors?: Record<string, string>;
|
||||
isLoading: boolean;
|
||||
onSubmit?: (data: any) => void;
|
||||
}
|
||||
|
||||
export function EnhancedVehicleForm({
|
||||
vehicle,
|
||||
customers,
|
||||
onCancel,
|
||||
errors = {},
|
||||
isLoading,
|
||||
onSubmit,
|
||||
}: EnhancedVehicleFormProps) {
|
||||
const {
|
||||
values,
|
||||
errors: validationErrors,
|
||||
touched,
|
||||
isValid,
|
||||
setValue,
|
||||
setTouched,
|
||||
reset,
|
||||
validate,
|
||||
getFieldProps,
|
||||
} = useFormValidation({
|
||||
schema: vehicleSchema,
|
||||
initialValues: {
|
||||
plateNumber: vehicle?.plateNumber || "",
|
||||
bodyType: vehicle?.bodyType || "",
|
||||
manufacturer: vehicle?.manufacturer || "",
|
||||
model: vehicle?.model || "",
|
||||
trim: vehicle?.trim || "",
|
||||
year: vehicle?.year || new Date().getFullYear(),
|
||||
transmission: vehicle?.transmission || "",
|
||||
fuel: vehicle?.fuel || "",
|
||||
cylinders: vehicle?.cylinders || null,
|
||||
engineDisplacement: vehicle?.engineDisplacement || null,
|
||||
useType: vehicle?.useType || "",
|
||||
ownerId: vehicle?.ownerId || 0,
|
||||
},
|
||||
validateOnChange: true,
|
||||
validateOnBlur: true,
|
||||
});
|
||||
|
||||
// Reset form when vehicle changes
|
||||
useEffect(() => {
|
||||
if (vehicle) {
|
||||
reset({
|
||||
plateNumber: vehicle.plateNumber || "",
|
||||
bodyType: vehicle.bodyType || "",
|
||||
manufacturer: vehicle.manufacturer || "",
|
||||
model: vehicle.model || "",
|
||||
trim: vehicle.trim || "",
|
||||
year: vehicle.year || new Date().getFullYear(),
|
||||
transmission: vehicle.transmission || "",
|
||||
fuel: vehicle.fuel || "",
|
||||
cylinders: vehicle.cylinders || null,
|
||||
engineDisplacement: vehicle.engineDisplacement || null,
|
||||
useType: vehicle.useType || "",
|
||||
ownerId: vehicle.ownerId || 0,
|
||||
});
|
||||
} else {
|
||||
reset({
|
||||
plateNumber: "",
|
||||
bodyType: "",
|
||||
manufacturer: "",
|
||||
model: "",
|
||||
trim: "",
|
||||
year: new Date().getFullYear(),
|
||||
transmission: "",
|
||||
fuel: "",
|
||||
cylinders: null,
|
||||
engineDisplacement: null,
|
||||
useType: "",
|
||||
ownerId: 0,
|
||||
});
|
||||
}
|
||||
}, [vehicle, reset]);
|
||||
|
||||
const handleSubmit = (e: React.FormEvent) => {
|
||||
e.preventDefault();
|
||||
|
||||
const { isValid: formIsValid } = validate();
|
||||
|
||||
if (formIsValid && onSubmit) {
|
||||
onSubmit(values);
|
||||
}
|
||||
};
|
||||
|
||||
const isEditing = !!vehicle;
|
||||
const combinedErrors = { ...validationErrors, ...errors };
|
||||
const currentYear = new Date().getFullYear();
|
||||
|
||||
return (
|
||||
<Form
|
||||
title={isEditing ? "تعديل بيانات المركبة" : "إضافة مركبة جديدة"}
|
||||
description={isEditing ? "قم بتعديل بيانات المركبة أدناه" : "أدخل بيانات المركبة الجديدة"}
|
||||
loading={isLoading}
|
||||
onSubmit={handleSubmit}
|
||||
>
|
||||
<input
|
||||
type="hidden"
|
||||
name="_action"
|
||||
value={isEditing ? "update" : "create"}
|
||||
/>
|
||||
{isEditing && (
|
||||
<input type="hidden" name="id" value={vehicle.id} />
|
||||
)}
|
||||
|
||||
<FormSection
|
||||
title="المعلومات الأساسية"
|
||||
description="البيانات الأساسية للمركبة"
|
||||
>
|
||||
<FormGrid columns={2}>
|
||||
{/* Plate Number */}
|
||||
<FormField
|
||||
label="رقم اللوحة"
|
||||
required
|
||||
error={combinedErrors.plateNumber}
|
||||
htmlFor="plateNumber"
|
||||
>
|
||||
<Input
|
||||
id="plateNumber"
|
||||
name="plateNumber"
|
||||
type="text"
|
||||
placeholder="أدخل رقم اللوحة"
|
||||
disabled={isLoading}
|
||||
dir="ltr"
|
||||
{...getFieldProps('plateNumber')}
|
||||
/>
|
||||
</FormField>
|
||||
|
||||
{/* Owner */}
|
||||
<FormField
|
||||
label="المالك"
|
||||
required
|
||||
error={combinedErrors.ownerId}
|
||||
htmlFor="ownerId"
|
||||
>
|
||||
<Select
|
||||
id="ownerId"
|
||||
name="ownerId"
|
||||
placeholder="اختر المالك"
|
||||
disabled={isLoading}
|
||||
options={customers.map(customer => ({
|
||||
value: customer.id.toString(),
|
||||
label: `${customer.name}${customer.phone ? ` (${customer.phone})` : ''}`,
|
||||
}))}
|
||||
value={values.ownerId?.toString() || ''}
|
||||
onChange={(e) => setValue('ownerId', parseInt(e.target.value) || 0)}
|
||||
/>
|
||||
</FormField>
|
||||
</FormGrid>
|
||||
</FormSection>
|
||||
|
||||
<FormSection
|
||||
title="مواصفات المركبة"
|
||||
description="التفاصيل التقنية للمركبة"
|
||||
>
|
||||
<FormGrid columns={3}>
|
||||
{/* Body Type */}
|
||||
<FormField
|
||||
label="نوع الهيكل"
|
||||
required
|
||||
error={combinedErrors.bodyType}
|
||||
htmlFor="bodyType"
|
||||
>
|
||||
<Select
|
||||
id="bodyType"
|
||||
name="bodyType"
|
||||
placeholder="اختر نوع الهيكل"
|
||||
aria-readonly={isLoading}
|
||||
options={BODY_TYPES.map(type => ({
|
||||
value: type.value,
|
||||
label: type.label,
|
||||
}))}
|
||||
{...getFieldProps('bodyType')}
|
||||
/>
|
||||
</FormField>
|
||||
|
||||
{/* Manufacturer */}
|
||||
<FormField
|
||||
label="الشركة المصنعة"
|
||||
required
|
||||
error={combinedErrors.manufacturer}
|
||||
htmlFor="manufacturer"
|
||||
>
|
||||
<Select
|
||||
id="manufacturer"
|
||||
name="manufacturer"
|
||||
placeholder="اختر الشركة المصنعة"
|
||||
disabled={isLoading}
|
||||
options={MANUFACTURERS.map(manufacturer => ({
|
||||
value: manufacturer.value,
|
||||
label: manufacturer.label,
|
||||
}))}
|
||||
{...getFieldProps('manufacturer')}
|
||||
/>
|
||||
</FormField>
|
||||
|
||||
{/* Model */}
|
||||
<FormField
|
||||
label="الموديل"
|
||||
required
|
||||
error={combinedErrors.model}
|
||||
htmlFor="model"
|
||||
>
|
||||
<Input
|
||||
id="model"
|
||||
name="model"
|
||||
type="text"
|
||||
placeholder="أدخل الموديل"
|
||||
disabled={isLoading}
|
||||
{...getFieldProps('model')}
|
||||
/>
|
||||
</FormField>
|
||||
|
||||
{/* Trim */}
|
||||
<FormField
|
||||
label="الفئة"
|
||||
error={combinedErrors.trim}
|
||||
htmlFor="trim"
|
||||
helperText="الفئة اختيارية"
|
||||
>
|
||||
<Input
|
||||
id="trim"
|
||||
name="trim"
|
||||
type="text"
|
||||
placeholder="أدخل الفئة (اختياري)"
|
||||
disabled={isLoading}
|
||||
{...getFieldProps('trim')}
|
||||
/>
|
||||
</FormField>
|
||||
|
||||
{/* Year */}
|
||||
<FormField
|
||||
label="سنة الصنع"
|
||||
required
|
||||
error={combinedErrors.year}
|
||||
htmlFor="year"
|
||||
>
|
||||
<Input
|
||||
id="year"
|
||||
name="year"
|
||||
type="number"
|
||||
min={VALIDATION.MIN_YEAR}
|
||||
max={VALIDATION.MAX_YEAR}
|
||||
placeholder={`${VALIDATION.MIN_YEAR} - ${currentYear}`}
|
||||
disabled={isLoading}
|
||||
value={values.year?.toString() || ''}
|
||||
onChange={(e) => setValue('year', parseInt(e.target.value) || currentYear)}
|
||||
/>
|
||||
</FormField>
|
||||
|
||||
{/* Use Type */}
|
||||
<FormField
|
||||
label="نوع الاستخدام"
|
||||
required
|
||||
error={combinedErrors.useType}
|
||||
htmlFor="useType"
|
||||
>
|
||||
<Select
|
||||
id="useType"
|
||||
name="useType"
|
||||
placeholder="اختر نوع الاستخدام"
|
||||
disabled={isLoading}
|
||||
options={USE_TYPES.map(useType => ({
|
||||
value: useType.value,
|
||||
label: useType.label,
|
||||
}))}
|
||||
{...getFieldProps('useType')}
|
||||
/>
|
||||
</FormField>
|
||||
</FormGrid>
|
||||
</FormSection>
|
||||
|
||||
<FormSection
|
||||
title="المحرك والناقل"
|
||||
description="مواصفات المحرك وناقل الحركة"
|
||||
>
|
||||
<FormGrid columns={2}>
|
||||
{/* Transmission */}
|
||||
<FormField
|
||||
label="ناقل الحركة"
|
||||
required
|
||||
error={combinedErrors.transmission}
|
||||
htmlFor="transmission"
|
||||
>
|
||||
<Select
|
||||
id="transmission"
|
||||
name="transmission"
|
||||
placeholder="اختر ناقل الحركة"
|
||||
disabled={isLoading}
|
||||
options={TRANSMISSION_TYPES.map(transmission => ({
|
||||
value: transmission.value,
|
||||
label: transmission.label,
|
||||
}))}
|
||||
{...getFieldProps('transmission')}
|
||||
/>
|
||||
</FormField>
|
||||
|
||||
{/* Fuel */}
|
||||
<FormField
|
||||
label="نوع الوقود"
|
||||
required
|
||||
error={combinedErrors.fuel}
|
||||
htmlFor="fuel"
|
||||
>
|
||||
<Select
|
||||
id="fuel"
|
||||
name="fuel"
|
||||
placeholder="اختر نوع الوقود"
|
||||
disabled={isLoading}
|
||||
options={FUEL_TYPES.map(fuel => ({
|
||||
value: fuel.value,
|
||||
label: fuel.label,
|
||||
}))}
|
||||
{...getFieldProps('fuel')}
|
||||
/>
|
||||
</FormField>
|
||||
|
||||
{/* Cylinders */}
|
||||
<FormField
|
||||
label="عدد الأسطوانات"
|
||||
error={combinedErrors.cylinders}
|
||||
htmlFor="cylinders"
|
||||
helperText="عدد الأسطوانات اختياري"
|
||||
>
|
||||
<Input
|
||||
id="cylinders"
|
||||
name="cylinders"
|
||||
type="number"
|
||||
min="1"
|
||||
max={VALIDATION.MAX_CYLINDERS}
|
||||
placeholder="عدد الأسطوانات (اختياري)"
|
||||
disabled={isLoading}
|
||||
value={values.cylinders?.toString() || ''}
|
||||
onChange={(e) => setValue('cylinders', e.target.value ? parseInt(e.target.value) : null)}
|
||||
/>
|
||||
</FormField>
|
||||
|
||||
{/* Engine Displacement */}
|
||||
<FormField
|
||||
label="سعة المحرك (لتر)"
|
||||
error={combinedErrors.engineDisplacement}
|
||||
htmlFor="engineDisplacement"
|
||||
helperText="سعة المحرك اختيارية"
|
||||
>
|
||||
<Input
|
||||
id="engineDisplacement"
|
||||
name="engineDisplacement"
|
||||
type="number"
|
||||
step="0.1"
|
||||
min="0.1"
|
||||
max={VALIDATION.MAX_ENGINE_DISPLACEMENT}
|
||||
placeholder="سعة المحرك (اختياري)"
|
||||
disabled={isLoading}
|
||||
value={values.engineDisplacement?.toString() || ''}
|
||||
onChange={(e) => setValue('engineDisplacement', e.target.value ? parseFloat(e.target.value) : null)}
|
||||
/>
|
||||
</FormField>
|
||||
</FormGrid>
|
||||
</FormSection>
|
||||
|
||||
<FormActions>
|
||||
<Button
|
||||
type="button"
|
||||
variant="outline"
|
||||
onClick={onCancel}
|
||||
disabled={isLoading}
|
||||
>
|
||||
إلغاء
|
||||
</Button>
|
||||
|
||||
<Button
|
||||
type="submit"
|
||||
disabled={isLoading || !isValid || !values.plateNumber?.trim() || !values.ownerId}
|
||||
loading={isLoading}
|
||||
>
|
||||
{isEditing ? "تحديث المركبة" : "إنشاء المركبة"}
|
||||
</Button>
|
||||
</FormActions>
|
||||
</Form>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user