This commit is contained in:
2026-01-23 20:35:40 +03:00
parent cf3b0e48ec
commit 66c151653e
137 changed files with 41495 additions and 0 deletions

View 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>
);
}

View 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>
);
}