first action update user pages .
2-update driver pages [fixed image display and notyfi] 3-update car pages [fixed image] 4-build tripe pages crud 5-build some role pages 6-build audit page 7-update ui compounants 8-update saidebar and topbar 9-cheange view to be ar view 10-cheange stractcher to be app,src 11-add validation layer by yup 12-add api image-proxy to broke image corse bloken
This commit is contained in:
236
src/validations/car.validator.ts
Normal file
236
src/validations/car.validator.ts
Normal file
@@ -0,0 +1,236 @@
|
||||
import * as yup from "yup";
|
||||
|
||||
// ── Enums (mirror backend) ────────────────────────────────────────────────────
|
||||
const INSURANCE_STATUSES = ["Valid", "Expired", "NotInsured"] as const;
|
||||
const CAR_STATUSES = ["Active", "InMaintenance", "InTrip", "Inactive"] as const;
|
||||
const IMPOUND_STATUSES = ["None", "Impounded"] as const;
|
||||
const IMAGE_STAGES = ["BEFORE", "AFTER", "GENERAL"] as const;
|
||||
|
||||
// ── Shared date helper ────────────────────────────────────────────────────────
|
||||
// <input type="date"> gives "YYYY-MM-DD"; validate as a real date string
|
||||
const dateString = yup
|
||||
.string()
|
||||
.test(
|
||||
"valid-date",
|
||||
"تاريخ غير صالح",
|
||||
(val) => {
|
||||
if (!val) return true; // optional
|
||||
return !isNaN(Date.parse(val));
|
||||
},
|
||||
)
|
||||
;
|
||||
|
||||
// ── Create schema ─────────────────────────────────────────────────────────────
|
||||
|
||||
export const createCarSchema = yup.object({
|
||||
// Required
|
||||
manufacturer: yup
|
||||
.string()
|
||||
.required("الشركة المصنعة مطلوبة")
|
||||
.min(2, "اسم الشركة يجب أن يكون حرفين على الأقل"),
|
||||
|
||||
model: yup
|
||||
.string()
|
||||
.required("الموديل مطلوب")
|
||||
.min(1, "الموديل مطلوب"),
|
||||
|
||||
year: yup
|
||||
.number()
|
||||
.typeError("سنة الصنع يجب أن تكون رقماً")
|
||||
.required("سنة الصنع مطلوبة")
|
||||
.integer("سنة الصنع يجب أن تكون رقماً صحيحاً")
|
||||
.min(1900, "سنة الصنع غير صالحة")
|
||||
.max(new Date().getFullYear() + 1, "سنة الصنع غير صالحة"),
|
||||
|
||||
plateNumber: yup
|
||||
.string()
|
||||
.required("رقم اللوحة مطلوب")
|
||||
.min(1, "رقم اللوحة مطلوب"),
|
||||
|
||||
plateLetters: yup
|
||||
.string()
|
||||
.required("حروف اللوحة مطلوبة")
|
||||
.min(1, "حروف اللوحة مطلوبة"),
|
||||
|
||||
registrationNumber: yup.string().required("رقم التسجيل مطلوب ").min(4,"رقم التسجيل لا يقل عن 4 ارقام"),
|
||||
|
||||
vinNumber: yup.string().required(" رقم الهيكل مطلوب").min(4,"رقم الهيكل لا يقل عن 4 ارقام"),
|
||||
// Optional
|
||||
color: yup.string().optional(),
|
||||
|
||||
plateType: yup.string().optional(),
|
||||
|
||||
|
||||
|
||||
ownerNationalId: yup.string().optional(),
|
||||
|
||||
branchId: yup
|
||||
.string()
|
||||
.uuid("معرّف الفرع غير صالح")
|
||||
.optional(),
|
||||
|
||||
currentStatus: yup
|
||||
.mixed<typeof CAR_STATUSES[number]>()
|
||||
.oneOf([...CAR_STATUSES], "حالة المركبة غير صالحة")
|
||||
.optional(),
|
||||
|
||||
insuranceStatus: yup
|
||||
.mixed<typeof INSURANCE_STATUSES[number]>()
|
||||
.oneOf([...INSURANCE_STATUSES], "حالة التأمين غير صالحة")
|
||||
.optional(),
|
||||
|
||||
currentImpoundStatus: yup
|
||||
.mixed<typeof IMPOUND_STATUSES[number]>()
|
||||
.oneOf([...IMPOUND_STATUSES], "حالة الحجز غير صالحة")
|
||||
.optional(),
|
||||
|
||||
registrationExpiryDate: dateString,
|
||||
insuranceExpiryDate: dateString,
|
||||
inspectionExpiryDate: dateString,
|
||||
operationCardExpiry: dateString,
|
||||
registrationIssueDate: dateString,
|
||||
ownershipStartDate: dateString,
|
||||
actualUserStartDate: dateString,
|
||||
|
||||
gpsDeviceId: yup.string().optional(),
|
||||
operationCardNumber: yup.string().optional(),
|
||||
|
||||
capacity: yup
|
||||
.number()
|
||||
.typeError("الطاقة الاستيعابية يجب أن تكون رقماً")
|
||||
.integer("الطاقة الاستيعابية يجب أن تكون رقماً صحيحاً")
|
||||
.min(0, "الطاقة الاستيعابية يجب أن تكون 0 أو أكثر")
|
||||
.optional(),
|
||||
|
||||
weight: yup
|
||||
.number()
|
||||
.typeError("الوزن يجب أن يكون رقماً")
|
||||
.integer("الوزن يجب أن يكون رقماً صحيحاً")
|
||||
.min(0, "الوزن يجب أن يكون 0 أو أكثر")
|
||||
.optional(),
|
||||
});
|
||||
|
||||
// ── Update schema (all optional except enums still validated) ─────────────────
|
||||
|
||||
export const updateCarSchema = yup.object({
|
||||
manufacturer: yup
|
||||
.string()
|
||||
.min(2, "اسم الشركة يجب أن يكون حرفين على الأقل")
|
||||
.optional(),
|
||||
|
||||
model: yup.string().optional(),
|
||||
|
||||
year: yup
|
||||
.number()
|
||||
.typeError("سنة الصنع يجب أن تكون رقماً")
|
||||
.integer("سنة الصنع يجب أن تكون رقماً صحيحاً")
|
||||
.min(1900, "سنة الصنع غير صالحة")
|
||||
.max(new Date().getFullYear() + 1, "سنة الصنع غير صالحة")
|
||||
.optional(),
|
||||
|
||||
plateNumber: yup.string().optional(),
|
||||
plateLetters: yup.string().optional(),
|
||||
color: yup.string().optional(),
|
||||
plateType: yup.string().optional(),
|
||||
|
||||
registrationNumber: yup.string().optional(),
|
||||
vinNumber: yup.string().optional(),
|
||||
ownerNationalId: yup.string().optional(),
|
||||
|
||||
branchId: yup
|
||||
.string()
|
||||
.uuid("معرّف الفرع غير صالح")
|
||||
.optional(),
|
||||
|
||||
currentStatus: yup
|
||||
.mixed<typeof CAR_STATUSES[number]>()
|
||||
.oneOf([...CAR_STATUSES], "حالة المركبة غير صالحة")
|
||||
.optional(),
|
||||
|
||||
insuranceStatus: yup
|
||||
.mixed<typeof INSURANCE_STATUSES[number]>()
|
||||
.oneOf([...INSURANCE_STATUSES], "حالة التأمين غير صالحة")
|
||||
.optional(),
|
||||
|
||||
currentImpoundStatus: yup
|
||||
.mixed<typeof IMPOUND_STATUSES[number]>()
|
||||
.oneOf([...IMPOUND_STATUSES], "حالة الحجز غير صالحة")
|
||||
.optional(),
|
||||
|
||||
isActive: yup.boolean().optional(),
|
||||
|
||||
registrationExpiryDate: dateString,
|
||||
insuranceExpiryDate: dateString,
|
||||
inspectionExpiryDate: dateString,
|
||||
operationCardExpiry: dateString,
|
||||
registrationIssueDate: dateString,
|
||||
ownershipStartDate: dateString,
|
||||
actualUserStartDate: dateString,
|
||||
|
||||
gpsDeviceId: yup.string().optional(),
|
||||
operationCardNumber: yup.string().optional(),
|
||||
|
||||
capacity: yup
|
||||
.number()
|
||||
.typeError("الطاقة الاستيعابية يجب أن تكون رقماً")
|
||||
.integer("الطاقة الاستيعابية يجب أن تكون رقماً صحيحاً")
|
||||
.min(0, "الطاقة الاستيعابية يجب أن تكون 0 أو أكثر")
|
||||
.optional(),
|
||||
|
||||
weight: yup
|
||||
.number()
|
||||
.typeError("الوزن يجب أن يكون رقماً")
|
||||
.integer("الوزن يجب أن يكون رقماً صحيحاً")
|
||||
.min(0, "الوزن يجب أن يكون 0 أو أكثر")
|
||||
.optional(),
|
||||
});
|
||||
|
||||
// ── Car image schema (from carImage.validator.js) ─────────────────────────────
|
||||
|
||||
export const addCarImagesSchema = yup.object({
|
||||
stage: yup
|
||||
.mixed<typeof IMAGE_STAGES[number]>()
|
||||
.oneOf([...IMAGE_STAGES], "مرحلة الصورة غير صالحة")
|
||||
.optional(),
|
||||
|
||||
maintenanceId: yup
|
||||
.string()
|
||||
.uuid("معرّف الصيانة غير صالح")
|
||||
.optional(),
|
||||
});
|
||||
|
||||
// ── Infer form error shape ────────────────────────────────────────────────────
|
||||
|
||||
export type CarSchemaErrors = Partial<
|
||||
Record<
|
||||
| "manufacturer"
|
||||
| "model"
|
||||
| "year"
|
||||
| "color"
|
||||
| "plateNumber"
|
||||
| "plateLetters"
|
||||
| "plateType"
|
||||
| "registrationNumber"
|
||||
| "vinNumber"
|
||||
| "ownerNationalId"
|
||||
| "branchId"
|
||||
| "currentStatus"
|
||||
| "insuranceStatus"
|
||||
| "currentImpoundStatus"
|
||||
| "isActive"
|
||||
| "registrationExpiryDate"
|
||||
| "insuranceExpiryDate"
|
||||
| "inspectionExpiryDate"
|
||||
| "operationCardExpiry"
|
||||
| "registrationIssueDate"
|
||||
| "ownershipStartDate"
|
||||
| "actualUserStartDate"
|
||||
| "gpsDeviceId"
|
||||
| "operationCardNumber"
|
||||
| "capacity"
|
||||
| "weight"
|
||||
| "stage"
|
||||
| "maintenanceId",
|
||||
string
|
||||
>
|
||||
>;
|
||||
207
src/validations/driver.validator.ts
Normal file
207
src/validations/driver.validator.ts
Normal file
@@ -0,0 +1,207 @@
|
||||
import * as yup from "yup"
|
||||
import type { NationalIdType, DriverCardType } from "../src/types/driver";
|
||||
|
||||
// ── Create schema ─────────────────────────────────────────────────────────────
|
||||
|
||||
export const createDriverSchema = yup.object({
|
||||
// Required
|
||||
name: yup
|
||||
.string()
|
||||
.required("الاسم مطلوب")
|
||||
.min(2, "الاسم يجب أن يكون حرفين على الأقل"),
|
||||
|
||||
phone: yup
|
||||
.string()
|
||||
.required("رقم الجوال مطلوب")
|
||||
.matches(
|
||||
/^(\+966|966|0)?5[0-9]{8}$/,
|
||||
"رقم الجوال غير صالح — يجب أن يكون رقم سعودي صحيح",
|
||||
),
|
||||
|
||||
// Optional
|
||||
email: yup
|
||||
.string()
|
||||
.email("البريد الإلكتروني غير صالح")
|
||||
.optional(),
|
||||
|
||||
address: yup
|
||||
.string()
|
||||
.min(5, "العنوان يجب أن يكون 5 أحرف على الأقل")
|
||||
.optional(),
|
||||
|
||||
nationality: yup
|
||||
.string()
|
||||
.min(5, "الجنسية يجب أن تكون 5 أحرف على الأقل")
|
||||
.optional(),
|
||||
|
||||
nationalIdType: yup
|
||||
.mixed<NationalIdType>()
|
||||
.oneOf(["NationalID", "Iqama", "Passport"], "نوع الهوية غير صالح")
|
||||
.optional(),
|
||||
|
||||
gosiNumber: yup.string().optional(),
|
||||
|
||||
licenseNumber: yup.string().optional(),
|
||||
|
||||
licenseType: yup.string().optional(),
|
||||
|
||||
licenseExpiry: yup
|
||||
.string()
|
||||
.test(
|
||||
"not-in-past",
|
||||
"تاريخ انتهاء الرخصة يجب أن يكون اليوم أو في المستقبل",
|
||||
(val) => {
|
||||
if (!val) return true; // optional
|
||||
return new Date(val) >= new Date(new Date().toDateString());
|
||||
},
|
||||
)
|
||||
.optional(),
|
||||
|
||||
driverCardNumber: yup.string().optional(),
|
||||
|
||||
driverCardType: yup
|
||||
.mixed<DriverCardType>()
|
||||
.oneOf(
|
||||
["Temporary", "Seasonal", "Annual", "Restricted"],
|
||||
"نوع بطاقة السائق غير صالح",
|
||||
)
|
||||
.optional(),
|
||||
|
||||
driverCardExpiry: yup
|
||||
.string()
|
||||
.test(
|
||||
"not-in-past",
|
||||
"تاريخ انتهاء بطاقة السائق يجب أن يكون اليوم أو في المستقبل",
|
||||
(val) => {
|
||||
if (!val) return true;
|
||||
return new Date(val) >= new Date(new Date().toDateString());
|
||||
},
|
||||
)
|
||||
.optional(),
|
||||
|
||||
driverType: yup.string().optional(),
|
||||
|
||||
branchId: yup
|
||||
.string()
|
||||
.uuid("معرّف الفرع غير صالح")
|
||||
.optional(),
|
||||
});
|
||||
|
||||
// ── Update schema (same rules, all optional except they don't change) ─────────
|
||||
|
||||
export const updateDriverSchema = yup.object({
|
||||
name: yup
|
||||
.string()
|
||||
.min(2, "الاسم يجب أن يكون حرفين على الأقل")
|
||||
.optional(),
|
||||
|
||||
phone: yup
|
||||
.string()
|
||||
.matches(
|
||||
/^(\+966|966|0)?5[0-9]{8}$/,
|
||||
"رقم الجوال غير صالح — يجب أن يكون رقم سعودي صحيح",
|
||||
)
|
||||
.optional(),
|
||||
|
||||
email: yup
|
||||
.string()
|
||||
.email("البريد الإلكتروني غير صالح")
|
||||
.optional(),
|
||||
|
||||
address: yup
|
||||
.string()
|
||||
.min(5, "العنوان يجب أن يكون 5 أحرف على الأقل")
|
||||
.optional(),
|
||||
|
||||
nationality: yup
|
||||
.string()
|
||||
.min(5, "الجنسية يجب أن تكون 5 أحرف على الأقل")
|
||||
.optional(),
|
||||
|
||||
nationalIdType: yup
|
||||
.mixed<NationalIdType>()
|
||||
.oneOf(["NationalID", "Iqama", "Passport"], "نوع الهوية غير صالح")
|
||||
.optional(),
|
||||
|
||||
gosiNumber: yup.string().optional(),
|
||||
|
||||
licenseNumber: yup.string().optional(),
|
||||
|
||||
licenseType: yup.string().optional(),
|
||||
|
||||
licenseExpiry: yup
|
||||
.string()
|
||||
.test(
|
||||
"not-in-past",
|
||||
"تاريخ انتهاء الرخصة يجب أن يكون اليوم أو في المستقبل",
|
||||
(val) => {
|
||||
if (!val) return true;
|
||||
return new Date(val) >= new Date(new Date().toDateString());
|
||||
},
|
||||
)
|
||||
.optional(),
|
||||
|
||||
driverCardNumber: yup.string().optional(),
|
||||
|
||||
driverCardType: yup
|
||||
.mixed<DriverCardType>()
|
||||
.oneOf(
|
||||
["Temporary", "Seasonal", "Annual", "Restricted"],
|
||||
"نوع بطاقة السائق غير صالح",
|
||||
)
|
||||
.optional(),
|
||||
|
||||
driverCardExpiry: yup
|
||||
.string()
|
||||
.test(
|
||||
"not-in-past",
|
||||
"تاريخ انتهاء بطاقة السائق يجب أن يكون اليوم أو في المستقبل",
|
||||
(val) => {
|
||||
if (!val) return true;
|
||||
return new Date(val) >= new Date(new Date().toDateString());
|
||||
},
|
||||
)
|
||||
.optional(),
|
||||
|
||||
driverType: yup.string().optional(),
|
||||
|
||||
branchId: yup
|
||||
.string()
|
||||
.uuid("معرّف الفرع غير صالح")
|
||||
.optional(),
|
||||
|
||||
status: yup
|
||||
.mixed<"Active" | "Inactive" | "InTrip" | "Suspended">()
|
||||
.oneOf(
|
||||
["Active", "Inactive", "InTrip", "Suspended"],
|
||||
"حالة السائق غير صالحة",
|
||||
)
|
||||
.optional(),
|
||||
|
||||
reason: yup.string().optional(),
|
||||
});
|
||||
|
||||
// ── Infer form error shape ────────────────────────────────────────────────────
|
||||
|
||||
export type DriverSchemaErrors = Partial<
|
||||
Record<
|
||||
| "name"
|
||||
| "phone"
|
||||
| "email"
|
||||
| "address"
|
||||
| "nationality"
|
||||
| "nationalIdType"
|
||||
| "gosiNumber"
|
||||
| "licenseNumber"
|
||||
| "licenseType"
|
||||
| "licenseExpiry"
|
||||
| "driverCardNumber"
|
||||
| "driverCardType"
|
||||
| "driverCardExpiry"
|
||||
| "driverType"
|
||||
| "branchId"
|
||||
| "status"
|
||||
| "reason",
|
||||
string
|
||||
>
|
||||
>;
|
||||
194
src/validations/trip.validator.ts
Normal file
194
src/validations/trip.validator.ts
Normal file
@@ -0,0 +1,194 @@
|
||||
import * as yup from "yup";
|
||||
import type { TripStatus } from "@/src/types/trip";
|
||||
|
||||
const TRIP_STATUSES: TripStatus[] = [
|
||||
"Scheduled",
|
||||
"InProgress",
|
||||
"Completed",
|
||||
"Cancelled",
|
||||
];
|
||||
|
||||
// NOTE: Unlike driver_validator.ts, there is no actual backend trip validator
|
||||
// file in this handoff. The rules below come from trip_api_reference.html
|
||||
// (marked "// doc") plus a few sensible defaults that are NOT explicitly
|
||||
// stated in the reference (marked "// inferred"). Verify the "inferred" ones
|
||||
// against the real backend validator before relying on them.
|
||||
|
||||
// ── Create schema ─────────────────────────────────────────────────────────────
|
||||
|
||||
export const createTripSchema = yup.object({
|
||||
// Required
|
||||
title: yup
|
||||
.string()
|
||||
.required("عنوان الرحلة مطلوب")
|
||||
.min(3, "عنوان الرحلة يجب أن يكون 3 أحرف على الأقل"), // doc
|
||||
|
||||
driverId: yup
|
||||
.string()
|
||||
.required("السائق مطلوب")
|
||||
.uuid("معرّف السائق غير صالح"), // doc: UUID, driver must be status=Active (enforced server-side)
|
||||
|
||||
carId: yup
|
||||
.string()
|
||||
.required("السيارة مطلوبة")
|
||||
.uuid("معرّف السيارة غير صالح"), // doc: UUID, car must be status=Active (enforced server-side)
|
||||
|
||||
// Optional
|
||||
startTime: yup.string().optional(), // doc: ISO 8601, default now()
|
||||
branchId: yup.string().required("الفرع مطلوبة").uuid("معرّف الفرع غير صالح"),// doc
|
||||
|
||||
endTime: yup
|
||||
.string()
|
||||
.optional()
|
||||
.test(
|
||||
"after-start",
|
||||
"وقت الانتهاء يجب أن يكون بعد وقت البدء",
|
||||
function (val) {
|
||||
if (!val) return true;
|
||||
const { startTime } = this.parent;
|
||||
if (!startTime) return true;
|
||||
return new Date(val) > new Date(startTime); // inferred
|
||||
},
|
||||
),
|
||||
|
||||
|
||||
status: yup
|
||||
.mixed<TripStatus>()
|
||||
.oneOf(TRIP_STATUSES, "حالة الرحلة غير صالحة")
|
||||
.optional(), // doc: default Scheduled
|
||||
|
||||
collectedCount: yup
|
||||
.number()
|
||||
.typeError("القيمة يجب أن تكون رقم")
|
||||
.integer("القيمة يجب أن تكون رقم صحيح")
|
||||
.min(0, "القيمة لا يمكن أن تكون سالبة") // inferred
|
||||
.optional(),
|
||||
|
||||
deliveredCount: yup
|
||||
.number()
|
||||
.typeError("القيمة يجب أن تكون رقم")
|
||||
.integer("القيمة يجب أن تكون رقم صحيح")
|
||||
.min(0, "القيمة لا يمكن أن تكون سالبة") // inferred
|
||||
.optional(),
|
||||
|
||||
returnedCount: yup
|
||||
.number()
|
||||
.typeError("القيمة يجب أن تكون رقم")
|
||||
.integer("القيمة يجب أن تكون رقم صحيح")
|
||||
.min(0, "القيمة لا يمكن أن تكون سالبة") // inferred
|
||||
.optional(),
|
||||
|
||||
totalCashCollected: yup
|
||||
.number()
|
||||
.typeError("القيمة يجب أن تكون رقم")
|
||||
.min(0, "القيمة لا يمكن أن تكون سالبة") // inferred
|
||||
.optional(), // doc: number | string -> Decimal server-side
|
||||
|
||||
notes: yup
|
||||
.string()
|
||||
.min(2, "الملاحظات يجب أن تكون حرفين على الأقل") // doc
|
||||
.optional(),
|
||||
|
||||
endReason: yup
|
||||
.string()
|
||||
.min(2, "سبب الانتهاء يجب أن يكون حرفين على الأقل") // doc
|
||||
.optional(),
|
||||
});
|
||||
|
||||
// ── Update schema (same rules, all optional + reason) ─────────────────────────
|
||||
|
||||
export const updateTripSchema = yup.object({
|
||||
title: yup
|
||||
.string()
|
||||
.min(3, "عنوان الرحلة يجب أن يكون 3 أحرف على الأقل")
|
||||
.optional(),
|
||||
|
||||
driverId: yup.string().uuid("معرّف السائق غير صالح").optional(), // doc: must be Active
|
||||
|
||||
carId: yup.string().uuid("معرّف السيارة غير صالح").optional(), // doc: must be Active
|
||||
|
||||
status: yup
|
||||
.mixed<TripStatus>()
|
||||
.oneOf(TRIP_STATUSES, "حالة الرحلة غير صالحة")
|
||||
.optional(),
|
||||
|
||||
startTime: yup.string().optional(),
|
||||
|
||||
endTime: yup
|
||||
.string()
|
||||
.optional()
|
||||
.test(
|
||||
"after-start",
|
||||
"وقت الانتهاء يجب أن يكون بعد وقت البدء",
|
||||
function (val) {
|
||||
if (!val) return true;
|
||||
const { startTime } = this.parent;
|
||||
if (!startTime) return true;
|
||||
return new Date(val) > new Date(startTime); // inferred
|
||||
},
|
||||
),
|
||||
|
||||
branchId: yup.string().uuid("معرّف الفرع غير صالح").optional(),
|
||||
|
||||
collectedCount: yup
|
||||
.number()
|
||||
.typeError("القيمة يجب أن تكون رقم")
|
||||
.integer("القيمة يجب أن تكون رقم صحيح")
|
||||
.min(0, "القيمة لا يمكن أن تكون سالبة")
|
||||
.optional(),
|
||||
|
||||
deliveredCount: yup
|
||||
.number()
|
||||
.typeError("القيمة يجب أن تكون رقم")
|
||||
.integer("القيمة يجب أن تكون رقم صحيح")
|
||||
.min(0, "القيمة لا يمكن أن تكون سالبة")
|
||||
.optional(),
|
||||
|
||||
returnedCount: yup
|
||||
.number()
|
||||
.typeError("القيمة يجب أن تكون رقم")
|
||||
.integer("القيمة يجب أن تكون رقم صحيح")
|
||||
.min(0, "القيمة لا يمكن أن تكون سالبة")
|
||||
.optional(),
|
||||
|
||||
totalCashCollected: yup
|
||||
.number()
|
||||
.typeError("القيمة يجب أن تكون رقم")
|
||||
.min(0, "القيمة لا يمكن أن تكون سالبة")
|
||||
.optional(),
|
||||
|
||||
notes: yup
|
||||
.string()
|
||||
.min(2, "الملاحظات يجب أن تكون حرفين على الأقل")
|
||||
.optional(),
|
||||
|
||||
endReason: yup
|
||||
.string()
|
||||
.min(2, "سبب الانتهاء يجب أن يكون حرفين على الأقل")
|
||||
.optional(),
|
||||
|
||||
// Update-only: reason for reassigning driver/car (recorded in history)
|
||||
reason: yup.string().optional(), // doc: no explicit min length stated
|
||||
});
|
||||
|
||||
// ── Infer form error shape ────────────────────────────────────────────────────
|
||||
|
||||
export type TripSchemaErrors = Partial<
|
||||
Record<
|
||||
| "title"
|
||||
| "driverId"
|
||||
| "carId"
|
||||
| "status"
|
||||
| "startTime"
|
||||
| "endTime"
|
||||
| "branchId"
|
||||
| "collectedCount"
|
||||
| "deliveredCount"
|
||||
| "returnedCount"
|
||||
| "totalCashCollected"
|
||||
| "notes"
|
||||
| "endReason"
|
||||
| "reason",
|
||||
string
|
||||
>
|
||||
>;
|
||||
90
src/validations/user.validator.ts
Normal file
90
src/validations/user.validator.ts
Normal file
@@ -0,0 +1,90 @@
|
||||
import * as yup from "yup";
|
||||
|
||||
// ── Phone regex — same as backend ─────────────────────────────────────────────
|
||||
const SAUDI_PHONE_RE = /^(\+966|966|0)?5[0-9]{8}$/;
|
||||
|
||||
// ── Create schema ─────────────────────────────────────────────────────────────
|
||||
|
||||
export const createUserSchema = yup.object({
|
||||
name: yup
|
||||
.string()
|
||||
.required("الاسم الكامل مطلوب")
|
||||
.min(2, "الاسم يجب أن يكون حرفين على الأقل"),
|
||||
|
||||
phone: yup
|
||||
.string()
|
||||
.required("رقم الهاتف مطلوب")
|
||||
.matches(SAUDI_PHONE_RE, "رقم الجوال غير صالح — يجب أن يكون رقم سعودي صحيح"),
|
||||
|
||||
email: yup
|
||||
.string()
|
||||
.email("البريد الإلكتروني غير صالح")
|
||||
.optional(),
|
||||
|
||||
password: yup
|
||||
.string()
|
||||
.required("كلمة المرور مطلوبة")
|
||||
.min(8, "كلمة المرور يجب أن تكون 8 أحرف على الأقل"),
|
||||
|
||||
roleId: yup
|
||||
.string()
|
||||
.required("الدور مطلوب")
|
||||
.uuid("معرّف الدور غير صالح"),
|
||||
|
||||
branchId: yup
|
||||
.string()
|
||||
.uuid("معرّف الفرع غير صالح")
|
||||
.optional(),
|
||||
});
|
||||
|
||||
// ── Update schema ─────────────────────────────────────────────────────────────
|
||||
|
||||
export const updateUserSchema = yup.object({
|
||||
name: yup
|
||||
.string()
|
||||
.min(2, "الاسم يجب أن يكون حرفين على الأقل")
|
||||
.optional(),
|
||||
|
||||
phone: yup
|
||||
.string()
|
||||
.matches(SAUDI_PHONE_RE, "رقم الجوال غير صالح — يجب أن يكون رقم سعودي صحيح")
|
||||
.optional(),
|
||||
|
||||
email: yup
|
||||
.string()
|
||||
.email("البريد الإلكتروني غير صالح")
|
||||
.optional(),
|
||||
|
||||
// password is optional on update — only sent if user wants to change it
|
||||
password: yup
|
||||
.string()
|
||||
.min(8, "كلمة المرور يجب أن تكون 8 أحرف على الأقل")
|
||||
.optional(),
|
||||
|
||||
isActive: yup.boolean().optional(),
|
||||
|
||||
roleId: yup
|
||||
.string()
|
||||
.uuid("معرّف الدور غير صالح")
|
||||
.optional(),
|
||||
|
||||
branchId: yup
|
||||
.string()
|
||||
.uuid("معرّف الفرع غير صالح")
|
||||
.optional(),
|
||||
});
|
||||
|
||||
// ── Infer form error shape ────────────────────────────────────────────────────
|
||||
|
||||
export type UserSchemaErrors = Partial<
|
||||
Record<
|
||||
| "name"
|
||||
| "phone"
|
||||
| "email"
|
||||
| "password"
|
||||
| "roleId"
|
||||
| "branchId"
|
||||
| "isActive",
|
||||
string
|
||||
>
|
||||
>;
|
||||
Reference in New Issue
Block a user