import { z } from 'zod'; import { VALIDATION, AUTH_LEVELS, USER_STATUS, TRANSMISSION_TYPES, FUEL_TYPES, USE_TYPES, PAYMENT_STATUS } from './constants'; // Zod schemas for server-side validation export const userSchema = z.object({ name: z.string() .min(1, 'الاسم مطلوب') .max(VALIDATION.MAX_NAME_LENGTH, `الاسم يجب أن يكون أقل من ${VALIDATION.MAX_NAME_LENGTH} حرف`) .trim(), username: z.string() .min(3, 'اسم المستخدم يجب أن يكون على الأقل 3 أحرف') .regex(/^[a-zA-Z0-9_]+$/, 'اسم المستخدم يجب أن يحتوي على أحرف وأرقام فقط') .trim(), email: z.string() .email('البريد الإلكتروني غير صحيح') .trim(), password: z.string() .min(VALIDATION.MIN_PASSWORD_LENGTH, `كلمة المرور يجب أن تكون على الأقل ${VALIDATION.MIN_PASSWORD_LENGTH} أحرف`), authLevel: z.number() .refine(val => Object.values(AUTH_LEVELS).includes(val as any), 'مستوى الصلاحية غير صحيح'), status: z.enum([USER_STATUS.ACTIVE, USER_STATUS.INACTIVE], { errorMap: () => ({ message: 'حالة المستخدم غير صحيحة' }) }), }); export const customerSchema = z.object({ name: z.string() .min(1, 'اسم العميل مطلوب') .max(VALIDATION.MAX_NAME_LENGTH, `الاسم يجب أن يكون أقل من ${VALIDATION.MAX_NAME_LENGTH} حرف`) .trim(), phone: z.string() .regex(/^[\+]?[0-9\s\-\(\)]*$/, 'رقم الهاتف غير صحيح') .optional() .or(z.literal('')), email: z.string() .email('البريد الإلكتروني غير صحيح') .optional() .or(z.literal('')), address: z.string() .optional() .or(z.literal('')), }); export const vehicleSchema = z.object({ plateNumber: z.string() .min(1, 'رقم اللوحة مطلوب') .trim(), bodyType: z.string() .min(1, 'نوع الهيكل مطلوب') .trim(), manufacturer: z.string() .min(1, 'الشركة المصنعة مطلوبة') .trim(), model: z.string() .min(1, 'الموديل مطلوب') .trim(), trim: z.string() .optional() .or(z.literal('')), year: z.number() .min(VALIDATION.MIN_YEAR, `السنة يجب أن تكون بين ${VALIDATION.MIN_YEAR} و ${VALIDATION.MAX_YEAR}`) .max(VALIDATION.MAX_YEAR, `السنة يجب أن تكون بين ${VALIDATION.MIN_YEAR} و ${VALIDATION.MAX_YEAR}`), transmission: z.enum(TRANSMISSION_TYPES.map(t => t.value) as [string, ...string[]], { errorMap: () => ({ message: 'نوع ناقل الحركة غير صحيح' }) }), fuel: z.enum(FUEL_TYPES.map(f => f.value) as [string, ...string[]], { errorMap: () => ({ message: 'نوع الوقود غير صحيح' }) }), cylinders: z.number() .min(1, `عدد الأسطوانات يجب أن يكون بين 1 و ${VALIDATION.MAX_CYLINDERS}`) .max(VALIDATION.MAX_CYLINDERS, `عدد الأسطوانات يجب أن يكون بين 1 و ${VALIDATION.MAX_CYLINDERS}`) .optional() .nullable(), engineDisplacement: z.number() .min(0.1, `سعة المحرك يجب أن تكون بين 0.1 و ${VALIDATION.MAX_ENGINE_DISPLACEMENT}`) .max(VALIDATION.MAX_ENGINE_DISPLACEMENT, `سعة المحرك يجب أن تكون بين 0.1 و ${VALIDATION.MAX_ENGINE_DISPLACEMENT}`) .optional() .nullable(), useType: z.enum(USE_TYPES.map(u => u.value) as [string, ...string[]], { errorMap: () => ({ message: 'نوع الاستخدام غير صحيح' }) }), ownerId: z.number() .min(1, 'مالك المركبة مطلوب'), }); export const maintenanceVisitSchema = z.object({ vehicleId: z.number() .min(1, 'المركبة مطلوبة'), customerId: z.number() .min(1, 'العميل مطلوب'), maintenanceType: z.string() .min(1, 'نوع الصيانة مطلوب') .trim(), description: z.string() .min(1, 'وصف الصيانة مطلوب') .max(VALIDATION.MAX_DESCRIPTION_LENGTH, `الوصف يجب أن يكون أقل من ${VALIDATION.MAX_DESCRIPTION_LENGTH} حرف`) .trim(), cost: z.number() .min(VALIDATION.MIN_COST, `التكلفة يجب أن تكون بين ${VALIDATION.MIN_COST} و ${VALIDATION.MAX_COST}`) .max(VALIDATION.MAX_COST, `التكلفة يجب أن تكون بين ${VALIDATION.MIN_COST} و ${VALIDATION.MAX_COST}`), paymentStatus: z.enum(Object.values(PAYMENT_STATUS) as [string, ...string[]], { errorMap: () => ({ message: 'حالة الدفع غير صحيحة' }) }), kilometers: z.number() .min(0, 'عدد الكيلومترات يجب أن يكون رقم موجب'), nextVisitDelay: z.number() .refine(val => [1, 2, 3, 4].includes(val), 'فترة الزيارة التالية يجب أن تكون 1، 2، 3، أو 4 أشهر'), }); export const expenseSchema = z.object({ description: z.string() .min(1, 'وصف المصروف مطلوب') .max(VALIDATION.MAX_DESCRIPTION_LENGTH, `الوصف يجب أن يكون أقل من ${VALIDATION.MAX_DESCRIPTION_LENGTH} حرف`) .trim(), category: z.string() .min(1, 'فئة المصروف مطلوبة') .trim(), amount: z.number() .min(VALIDATION.MIN_COST + 0.01, `المبلغ يجب أن يكون بين ${VALIDATION.MIN_COST + 0.01} و ${VALIDATION.MAX_COST}`) .max(VALIDATION.MAX_COST, `المبلغ يجب أن يكون بين ${VALIDATION.MIN_COST + 0.01} و ${VALIDATION.MAX_COST}`), }); // Validation result type export interface ValidationResult { success: boolean; errors: Record; data?: any; } // Server-side validation functions export function validateUserData(data: any): ValidationResult { try { const validatedData = userSchema.parse(data); return { success: true, errors: {}, data: validatedData }; } catch (error) { if (error instanceof z.ZodError) { const errors: Record = {}; error.issues.forEach((issue) => { if (issue.path.length > 0) { errors[issue.path[0] as string] = issue.message; } }); return { success: false, errors }; } return { success: false, errors: { general: 'خطأ في التحقق من البيانات' } }; } } export function validateCustomerData(data: any): ValidationResult { try { const validatedData = customerSchema.parse(data); return { success: true, errors: {}, data: validatedData }; } catch (error) { if (error instanceof z.ZodError) { const errors: Record = {}; error.issues.forEach((issue) => { if (issue.path.length > 0) { errors[issue.path[0] as string] = issue.message; } }); return { success: false, errors }; } return { success: false, errors: { general: 'خطأ في التحقق من البيانات' } }; } } export function validateVehicleData(data: any): ValidationResult { try { // Convert string numbers to actual numbers const processedData = { ...data, year: data.year ? parseInt(data.year) : undefined, cylinders: data.cylinders ? parseInt(data.cylinders) : null, engineDisplacement: data.engineDisplacement ? parseFloat(data.engineDisplacement) : null, ownerId: data.ownerId ? parseInt(data.ownerId) : undefined, }; const validatedData = vehicleSchema.parse(processedData); return { success: true, errors: {}, data: validatedData }; } catch (error) { if (error instanceof z.ZodError) { const errors: Record = {}; error.issues.forEach((issue) => { if (issue.path.length > 0) { errors[issue.path[0] as string] = issue.message; } }); return { success: false, errors }; } return { success: false, errors: { general: 'خطأ في التحقق من البيانات' } }; } } export function validateMaintenanceVisitData(data: any): ValidationResult { try { // Convert string numbers to actual numbers const processedData = { ...data, vehicleId: data.vehicleId ? parseInt(data.vehicleId) : undefined, customerId: data.customerId ? parseInt(data.customerId) : undefined, cost: data.cost ? parseFloat(data.cost) : undefined, kilometers: data.kilometers ? parseInt(data.kilometers) : undefined, nextVisitDelay: data.nextVisitDelay ? parseInt(data.nextVisitDelay) : undefined, }; const validatedData = maintenanceVisitSchema.parse(processedData); return { success: true, errors: {}, data: validatedData }; } catch (error) { if (error instanceof z.ZodError) { const errors: Record = {}; error.issues.forEach((issue) => { if (issue.path.length > 0) { errors[issue.path[0] as string] = issue.message; } }); return { success: false, errors }; } return { success: false, errors: { general: 'خطأ في التحقق من البيانات' } }; } } export function validateExpenseData(data: any): ValidationResult { try { // Convert string numbers to actual numbers const processedData = { ...data, amount: data.amount ? parseFloat(data.amount) : undefined, }; const validatedData = expenseSchema.parse(processedData); return { success: true, errors: {}, data: validatedData }; } catch (error) { if (error instanceof z.ZodError) { const errors: Record = {}; error.issues.forEach((issue) => { if (issue.path.length > 0) { errors[issue.path[0] as string] = issue.message; } }); return { success: false, errors }; } return { success: false, errors: { general: 'خطأ في التحقق من البيانات' } }; } } // Client-side validation helpers export function validateField(value: any, schema: z.ZodSchema, fieldName: string): string | null { try { schema.parse({ [fieldName]: value }); return null; } catch (error) { if (error instanceof z.ZodError) { const fieldError = error.errors.find(err => err.path.includes(fieldName)); return fieldError?.message || null; } return null; } } // Real-time validation for forms export function validateFormField(fieldName: string, value: any, schema: z.ZodSchema): string | null { try { const fieldSchema = (schema as any).shape[fieldName]; if (fieldSchema) { fieldSchema.parse(value); } return null; } catch (error) { if (error instanceof z.ZodError) { return error.issues[0]?.message || null; } return null; } }