669 lines
23 KiB
TypeScript
669 lines
23 KiB
TypeScript
"use client";
|
||
|
||
import { useEffect, useState } from "react";
|
||
import Link from "next/link";
|
||
import { useForm } from "react-hook-form";
|
||
import { yupResolver } from "@hookform/resolvers/yup";
|
||
import { Alert, Button, Input, Modal, Select } from "../UI";
|
||
import { getStoredToken } from "@/src/lib/auth";
|
||
import { clientService } from "@/src/services/client.service";
|
||
import { tripService } from "@/src/services/trip.service";
|
||
import {
|
||
createOrderSchema,
|
||
updateOrderSchema,
|
||
} from "@/src/validations/order.validation";
|
||
import type {
|
||
Order,
|
||
CreateOrderPayload,
|
||
UpdateOrderPayload,
|
||
PaymentMethod,
|
||
PaymentStatus,
|
||
} from "@/src/types/order";
|
||
import type { Client } from "@/src/types/client";
|
||
import type { Trip } from "@/src/types/trip";
|
||
|
||
const sectionHeadingStyle: React.CSSProperties = {
|
||
fontSize: 11,
|
||
letterSpacing: "0.25em",
|
||
textTransform: "uppercase",
|
||
color: "var(--color-text-hint)",
|
||
fontWeight: 700,
|
||
margin: "0.5rem 0 0",
|
||
};
|
||
|
||
const createLinkStyle: React.CSSProperties = {
|
||
fontSize: 11,
|
||
color: "var(--color-brand-600)",
|
||
textDecoration: "none",
|
||
fontWeight: 500,
|
||
marginTop: 6,
|
||
display: "inline-flex",
|
||
alignItems: "center",
|
||
gap: 3,
|
||
};
|
||
|
||
// ── Form values shape ────────────────────────────────────────────────────────
|
||
// Nested address paths mirror the yup schema exactly (deliveryAddress.details.*,
|
||
// deliveryAddress.location.coordinates.0/1, etc.) so register("a.b.c") dot-paths
|
||
// validate against the matching nested schema node without any extra mapping.
|
||
|
||
interface AddressFormValues {
|
||
details: {
|
||
city: string;
|
||
district: string;
|
||
street: string;
|
||
buildingNo: string;
|
||
unitNo: string;
|
||
zipCode: string;
|
||
};
|
||
location: {
|
||
coordinates: [number | undefined, number | undefined];
|
||
};
|
||
}
|
||
|
||
interface OrderFormValues {
|
||
shipmentNumber: string;
|
||
recipientName: string;
|
||
recipientPhone: string;
|
||
clientId: string;
|
||
tripId: string;
|
||
type: string;
|
||
quantity: number | undefined;
|
||
weight: number | undefined;
|
||
subTotal: number | undefined;
|
||
vatRate: number | undefined;
|
||
paymentMethod: PaymentMethod | "";
|
||
paymentStatus: PaymentStatus | "";
|
||
deliveryAddress: AddressFormValues;
|
||
pickupAddressId: string;
|
||
pickupAddress: AddressFormValues;
|
||
}
|
||
|
||
const emptyAddress = (): AddressFormValues => ({
|
||
details: { city: "", district: "", street: "", buildingNo: "", unitNo: "", zipCode: "" },
|
||
location: { coordinates: [undefined, undefined] },
|
||
});
|
||
|
||
interface OrderFormModalProps {
|
||
editOrder: Order | null;
|
||
onClose: () => void;
|
||
onSubmit: (
|
||
payload: CreateOrderPayload | UpdateOrderPayload,
|
||
isNew: boolean,
|
||
) => Promise<boolean>;
|
||
}
|
||
|
||
export function OrderFormModal({
|
||
editOrder,
|
||
onClose,
|
||
onSubmit,
|
||
}: OrderFormModalProps) {
|
||
const isNew = editOrder === null;
|
||
|
||
const [clients, setClients] = useState<Client[]>([]);
|
||
const [trips, setTrips] = useState<Trip[]>([]);
|
||
const [relLoading, setRelLoading] = useState(true);
|
||
|
||
const [pickupMode, setPickupMode] = useState<"id" | "new">(
|
||
editOrder?.pickupAddressId ? "id" : "new",
|
||
);
|
||
|
||
const {
|
||
register,
|
||
handleSubmit,
|
||
setError,
|
||
setFocus,
|
||
formState: { errors, isSubmitting },
|
||
} = useForm<OrderFormValues>({
|
||
// Cast the schema itself — create/update schemas differ structurally in
|
||
// which fields are required, so yup can't unify them into one type.
|
||
resolver: yupResolver<OrderFormValues>((isNew ? createOrderSchema : updateOrderSchema) as any),
|
||
defaultValues: {
|
||
shipmentNumber: editOrder?.shipmentNumber ?? "",
|
||
recipientName: editOrder?.recipientName ?? "",
|
||
recipientPhone: editOrder?.recipientPhone ?? "",
|
||
clientId: editOrder?.clientId ?? "",
|
||
tripId: editOrder?.tripId ?? "",
|
||
type: editOrder?.type ?? "",
|
||
quantity: editOrder?.quantity ?? 1,
|
||
weight: editOrder?.weight ?? undefined,
|
||
subTotal: editOrder?.subTotal ?? undefined,
|
||
vatRate: editOrder?.vatRate ?? undefined,
|
||
paymentMethod: editOrder?.paymentMethod ?? "",
|
||
paymentStatus: editOrder?.paymentStatus ?? "",
|
||
deliveryAddress: {
|
||
details: {
|
||
city: editOrder?.deliveryAddress?.details?.city ?? "",
|
||
district: editOrder?.deliveryAddress?.details?.district ?? "",
|
||
street: editOrder?.deliveryAddress?.details?.street ?? "",
|
||
buildingNo: editOrder?.deliveryAddress?.details?.buildingNo ?? "",
|
||
unitNo: editOrder?.deliveryAddress?.details?.unitNo ?? "",
|
||
zipCode: editOrder?.deliveryAddress?.details?.zipCode ?? "",
|
||
},
|
||
location: {
|
||
coordinates: [
|
||
editOrder?.deliveryAddress?.location?.coordinates?.[0],
|
||
editOrder?.deliveryAddress?.location?.coordinates?.[1],
|
||
],
|
||
},
|
||
},
|
||
pickupAddressId: editOrder?.pickupAddressId ?? "",
|
||
pickupAddress: {
|
||
details: {
|
||
city: editOrder?.pickupAddress?.details?.city ?? "",
|
||
district: editOrder?.pickupAddress?.details?.district ?? "",
|
||
street: editOrder?.pickupAddress?.details?.street ?? "",
|
||
buildingNo: editOrder?.pickupAddress?.details?.buildingNo ?? "",
|
||
unitNo: editOrder?.pickupAddress?.details?.unitNo ?? "",
|
||
zipCode: editOrder?.pickupAddress?.details?.zipCode ?? "",
|
||
},
|
||
location: {
|
||
coordinates: [
|
||
editOrder?.pickupAddress?.location?.coordinates?.[0],
|
||
editOrder?.pickupAddress?.location?.coordinates?.[1],
|
||
],
|
||
},
|
||
},
|
||
},
|
||
});
|
||
|
||
const [apiError, setApiError] = useState("");
|
||
|
||
// register("shipmentNumber") already attaches its own ref — focusing via
|
||
// setFocus avoids the ref collision a separate `ref={firstRef}` would cause.
|
||
useEffect(() => {
|
||
setFocus("shipmentNumber");
|
||
}, [setFocus]);
|
||
|
||
useEffect(() => {
|
||
let cancelled = false;
|
||
(async () => {
|
||
try {
|
||
const token = getStoredToken();
|
||
const { items: clientList } = await clientService.getAll(1, "", token);
|
||
const { items: tripList } = await tripService.getAll({ page: 1, limit: 100 }, token);
|
||
if (!cancelled) {
|
||
setClients(clientList);
|
||
setTrips(tripList);
|
||
}
|
||
} catch (err) {
|
||
console.error("OrderFormModal: failed to load clients/trips", err);
|
||
if (!cancelled) {
|
||
setApiError("تعذّر تحميل قوائم العملاء والرحلات. تحقق من اتصالك وحاول إعادة فتح النافذة.");
|
||
}
|
||
} finally {
|
||
if (!cancelled) setRelLoading(false);
|
||
}
|
||
})();
|
||
return () => {
|
||
cancelled = true;
|
||
};
|
||
}, []);
|
||
|
||
const numberField = (
|
||
field:
|
||
| "quantity"
|
||
| "weight"
|
||
| "subTotal"
|
||
| "vatRate",
|
||
) =>
|
||
register(field, {
|
||
setValueAs: (v) => (v === "" || v === null ? undefined : Number(v)),
|
||
});
|
||
|
||
// Coordinate fields use dot-path registration (matches the pattern already
|
||
// used in Client_Adress/Addressformmodal.tsx for location.coordinates.0/1).
|
||
const coordField = (
|
||
path:
|
||
| "deliveryAddress.location.coordinates.0"
|
||
| "deliveryAddress.location.coordinates.1"
|
||
| "pickupAddress.location.coordinates.0"
|
||
| "pickupAddress.location.coordinates.1",
|
||
) =>
|
||
register(path as never, {
|
||
setValueAs: (v: string) => (v === "" || v === null ? undefined : Number(v)),
|
||
});
|
||
|
||
// ── Submit ────────────────────────────────────────────────────────────────
|
||
// Address objects stay entirely optional at the schema level (see the
|
||
// inline note in order.validation.ts), so — exactly like before the
|
||
// refactor — we only attach deliveryAddress/pickupAddress to the payload
|
||
// when the person actually filled in at least one field for them.
|
||
|
||
const submitHandler = async (data: OrderFormValues) => {
|
||
const payload: Record<string, unknown> = {
|
||
shipmentNumber: data.shipmentNumber,
|
||
recipientName: data.recipientName,
|
||
recipientPhone: data.recipientPhone,
|
||
clientId: data.clientId,
|
||
quantity: data.quantity || 1,
|
||
};
|
||
|
||
if (data.tripId) payload.tripId = data.tripId;
|
||
if (data.type) payload.type = data.type;
|
||
if (data.weight !== undefined) payload.weight = data.weight;
|
||
if (data.subTotal !== undefined) payload.subTotal = data.subTotal;
|
||
if (data.vatRate !== undefined) payload.vatRate = data.vatRate;
|
||
if (data.paymentMethod) payload.paymentMethod = data.paymentMethod;
|
||
if (data.paymentStatus) payload.paymentStatus = data.paymentStatus;
|
||
|
||
const delDetails = Object.fromEntries(
|
||
Object.entries(data.deliveryAddress.details).filter(([, v]) => !!v),
|
||
);
|
||
const [delLng, delLat] = data.deliveryAddress.location.coordinates;
|
||
const delCoordinates = delLng !== undefined && delLat !== undefined ? [delLng, delLat] : undefined;
|
||
|
||
if (Object.keys(delDetails).length > 0 || delCoordinates) {
|
||
payload.deliveryAddress = {
|
||
...(Object.keys(delDetails).length > 0 && { details: delDetails }),
|
||
...(delCoordinates && { location: { coordinates: delCoordinates } }),
|
||
};
|
||
}
|
||
|
||
if (pickupMode === "id" && data.pickupAddressId.trim()) {
|
||
payload.pickupAddressId = data.pickupAddressId.trim();
|
||
} else {
|
||
const pkpDetails = Object.fromEntries(
|
||
Object.entries(data.pickupAddress.details).filter(([, v]) => !!v),
|
||
);
|
||
const [pkpLng, pkpLat] = data.pickupAddress.location.coordinates;
|
||
const pkpCoordinates = pkpLng !== undefined && pkpLat !== undefined ? [pkpLng, pkpLat] : undefined;
|
||
|
||
if (Object.keys(pkpDetails).length > 0 || pkpCoordinates) {
|
||
payload.pickupAddress = {
|
||
...(Object.keys(pkpDetails).length > 0 && { details: pkpDetails }),
|
||
...(pkpCoordinates && { location: { coordinates: pkpCoordinates } }),
|
||
};
|
||
}
|
||
}
|
||
|
||
setApiError("");
|
||
try {
|
||
const ok = await onSubmit(payload as unknown as CreateOrderPayload, isNew);
|
||
if (ok) {
|
||
onClose();
|
||
} else {
|
||
setError("shipmentNumber", { message: "حدث خطأ غير متوقع. يرجى المحاولة لاحقاً." });
|
||
setApiError("حدث خطأ غير متوقع. يرجى المحاولة لاحقاً.");
|
||
}
|
||
} catch (err) {
|
||
const message = err instanceof Error ? err.message : "حدث خطأ غير متوقع. يرجى المحاولة لاحقاً.";
|
||
setError("shipmentNumber", { message });
|
||
setApiError(message);
|
||
}
|
||
};
|
||
|
||
const deliveryErr = errors.deliveryAddress;
|
||
const pickupErr = errors.pickupAddress;
|
||
|
||
return (
|
||
<Modal
|
||
open
|
||
title={isNew ? "طلب جديد" : editOrder?.shipmentNumber ?? ""}
|
||
subtitle={isNew ? "إنشاء طلب" : "تعديل طلب"}
|
||
onClose={onClose}
|
||
size="lg"
|
||
footer={
|
||
<>
|
||
<Button type="button" variant="secondary" onClick={onClose} disabled={isSubmitting}>
|
||
إلغاء
|
||
</Button>
|
||
<Button type="submit" form="order-form" loading={isSubmitting}>
|
||
{isNew ? "إنشاء الطلب" : "حفظ التغييرات"}
|
||
</Button>
|
||
</>
|
||
}
|
||
>
|
||
<form
|
||
id="order-form"
|
||
onSubmit={handleSubmit(submitHandler)}
|
||
noValidate
|
||
style={{ display: "flex", flexDirection: "column", gap: "1rem" }}
|
||
dir="rtl"
|
||
>
|
||
{errors.shipmentNumber?.type === "manual" && (
|
||
<Alert type="error" message={errors.shipmentNumber.message ?? ""} onClose={() => setApiError("")} />
|
||
)}
|
||
|
||
<p style={sectionHeadingStyle}>بيانات الشحنة والمستلم</p>
|
||
|
||
<div style={{ display: "grid", gridTemplateColumns: "1fr 1fr", gap: "0.75rem" }}>
|
||
<Input
|
||
id="order-shipmentNumber"
|
||
label="رقم الشحنة *"
|
||
error={
|
||
errors.shipmentNumber && errors.shipmentNumber.type !== "manual"
|
||
? errors.shipmentNumber.message
|
||
: undefined
|
||
}
|
||
{...register("shipmentNumber")}
|
||
placeholder="SHP-0001"
|
||
dir="ltr"
|
||
autoComplete="off"
|
||
/>
|
||
|
||
<div>
|
||
<Select
|
||
id="order-clientId"
|
||
label="العميل *"
|
||
error={errors.clientId?.message}
|
||
disabled={relLoading}
|
||
{...register("clientId")}
|
||
dir="rtl"
|
||
style={{ cursor: relLoading ? "wait" : "pointer" }}
|
||
>
|
||
<option value="">{relLoading ? "جارٍ التحميل…" : "اختر العميل"}</option>
|
||
{clients.map((c) => (
|
||
<option key={c.id} value={c.id}>
|
||
{c.name}
|
||
{c.phone ? ` — ${c.phone}` : ""}
|
||
</option>
|
||
))}
|
||
</Select>
|
||
<Link href="/dashboard/clients" style={createLinkStyle} tabIndex={-1}>
|
||
<svg width="11" height="11" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.5">
|
||
<line x1="12" y1="5" x2="12" y2="19" />
|
||
<line x1="5" y1="12" x2="19" y2="12" />
|
||
</svg>
|
||
إنشاء عميل جديد
|
||
</Link>
|
||
</div>
|
||
|
||
<Input
|
||
id="order-recipientName"
|
||
label="اسم المستلم *"
|
||
error={errors.recipientName?.message}
|
||
{...register("recipientName")}
|
||
placeholder="أحمد محمد"
|
||
dir="rtl"
|
||
/>
|
||
|
||
<Input
|
||
id="order-recipientPhone"
|
||
label="رقم جوال المستلم *"
|
||
error={errors.recipientPhone?.message}
|
||
{...register("recipientPhone")}
|
||
placeholder="05XXXXXXXX"
|
||
dir="ltr"
|
||
/>
|
||
|
||
<div style={{ gridColumn: "1 / -1" }}>
|
||
<Select
|
||
id="order-tripId"
|
||
label={isNew ? "الرحلة *" : "الرحلة (اختياري)"}
|
||
error={errors.tripId?.message}
|
||
disabled={relLoading}
|
||
{...register("tripId")}
|
||
dir="rtl"
|
||
style={{ cursor: relLoading ? "wait" : "pointer" }}
|
||
>
|
||
<option value="">{relLoading ? "جارٍ التحميل…" : "اختر الرحلة"}</option>
|
||
{trips.map((t) => (
|
||
<option key={t.id} value={t.id}>
|
||
{t.tripNumber}
|
||
{t.title ? ` — ${t.title}` : ""}
|
||
{t.driver?.name ? ` (${t.driver.name})` : ""}
|
||
</option>
|
||
))}
|
||
</Select>
|
||
<Link href="/dashboard/trips" style={createLinkStyle} tabIndex={-1}>
|
||
<svg width="11" height="11" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.5">
|
||
<line x1="12" y1="5" x2="12" y2="19" />
|
||
<line x1="5" y1="12" x2="19" y2="12" />
|
||
</svg>
|
||
إنشاء رحلة جديدة
|
||
</Link>
|
||
</div>
|
||
</div>
|
||
|
||
<p style={sectionHeadingStyle}>تفاصيل الشحنة</p>
|
||
|
||
<div style={{ display: "grid", gridTemplateColumns: "1fr 1fr 1fr", gap: "0.75rem" }}>
|
||
<Input
|
||
id="order-type"
|
||
label="نوع الشحنة (اختياري)"
|
||
{...register("type")}
|
||
placeholder="عادي / مبرد"
|
||
dir="rtl"
|
||
/>
|
||
|
||
<Input
|
||
id="order-quantity"
|
||
label="الكمية *"
|
||
type="number"
|
||
min={1}
|
||
error={errors.quantity?.message}
|
||
{...numberField("quantity")}
|
||
dir="ltr"
|
||
/>
|
||
|
||
<Input
|
||
id="order-weight"
|
||
label="الوزن (كجم) (اختياري)"
|
||
type="number"
|
||
min={0}
|
||
step="0.01"
|
||
{...numberField("weight")}
|
||
dir="ltr"
|
||
/>
|
||
</div>
|
||
|
||
<p style={sectionHeadingStyle}>بيانات الدفع</p>
|
||
|
||
<div style={{ display: "grid", gridTemplateColumns: "1fr 1fr 1fr", gap: "0.75rem" }}>
|
||
<Input
|
||
id="order-subTotal"
|
||
label="الإجمالي الفرعي (اختياري)"
|
||
type="number"
|
||
min={0}
|
||
step="0.01"
|
||
error={errors.subTotal?.message}
|
||
{...numberField("subTotal")}
|
||
dir="ltr"
|
||
/>
|
||
|
||
<Input
|
||
id="order-vatRate"
|
||
label="نسبة الضريبة (%) (اختياري)"
|
||
type="number"
|
||
min={0}
|
||
step="0.01"
|
||
error={errors.vatRate?.message}
|
||
{...numberField("vatRate")}
|
||
dir="ltr"
|
||
/>
|
||
|
||
<Select
|
||
id="order-paymentMethod"
|
||
label="طريقة الدفع (اختياري)"
|
||
{...register("paymentMethod")}
|
||
dir="rtl"
|
||
style={{ cursor: "pointer" }}
|
||
>
|
||
<option value="">اختر الطريقة</option>
|
||
<option value="Cash">نقداً</option>
|
||
<option value="Card">بطاقة</option>
|
||
<option value="Prepaid">مدفوع مسبقاً</option>
|
||
</Select>
|
||
|
||
{!isNew && (
|
||
<Select
|
||
id="order-paymentStatus"
|
||
label="حالة الدفع"
|
||
{...register("paymentStatus")}
|
||
dir="rtl"
|
||
style={{ cursor: "pointer" }}
|
||
>
|
||
<option value="">—</option>
|
||
<option value="Pending">معلَّق</option>
|
||
<option value="Paid">مدفوع</option>
|
||
<option value="Failed">فشل</option>
|
||
<option value="Refunded">مُسترجع</option>
|
||
</Select>
|
||
)}
|
||
</div>
|
||
|
||
<p style={sectionHeadingStyle}>عنوان التسليم</p>
|
||
<div style={{ display: "grid", gridTemplateColumns: "1fr 1fr 1fr", gap: "0.75rem" }}>
|
||
<Input
|
||
id="delivery-city"
|
||
label="المدينة (اختياري)"
|
||
{...register("deliveryAddress.details.city")}
|
||
placeholder="الرياض"
|
||
dir="rtl"
|
||
/>
|
||
<Input
|
||
id="delivery-district"
|
||
label="الحي (اختياري)"
|
||
{...register("deliveryAddress.details.district")}
|
||
placeholder="العليا"
|
||
dir="rtl"
|
||
/>
|
||
<Input
|
||
id="delivery-street"
|
||
label="الشارع (اختياري)"
|
||
{...register("deliveryAddress.details.street")}
|
||
placeholder="شارع الأمير محمد"
|
||
dir="rtl"
|
||
/>
|
||
<Input
|
||
id="delivery-buildingNo"
|
||
label="رقم المبنى (اختياري)"
|
||
{...register("deliveryAddress.details.buildingNo")}
|
||
placeholder="1234"
|
||
dir="ltr"
|
||
/>
|
||
<Input
|
||
id="delivery-unitNo"
|
||
label="رقم الوحدة (اختياري)"
|
||
{...register("deliveryAddress.details.unitNo")}
|
||
placeholder="56"
|
||
dir="ltr"
|
||
/>
|
||
<Input
|
||
id="delivery-zipCode"
|
||
label="الرمز البريدي (اختياري)"
|
||
{...register("deliveryAddress.details.zipCode")}
|
||
placeholder="12345"
|
||
dir="ltr"
|
||
/>
|
||
<Input
|
||
id="delivery-lng"
|
||
label="خط الطول (Longitude) *"
|
||
type="number"
|
||
step="any"
|
||
error={deliveryErr?.location?.coordinates ? " " : undefined}
|
||
{...coordField("deliveryAddress.location.coordinates.0")}
|
||
placeholder="46.6753"
|
||
dir="ltr"
|
||
/>
|
||
<Input
|
||
id="delivery-lat"
|
||
label="خط العرض (Latitude) *"
|
||
type="number"
|
||
step="any"
|
||
error={deliveryErr?.location?.coordinates?.message}
|
||
{...coordField("deliveryAddress.location.coordinates.1")}
|
||
placeholder="24.7136"
|
||
dir="ltr"
|
||
/>
|
||
</div>
|
||
|
||
<p style={sectionHeadingStyle}>عنوان الاستلام</p>
|
||
|
||
<div style={{ display: "flex", gap: "0.5rem", marginBottom: "0.25rem" }}>
|
||
{(["id", "new"] as const).map((m) => (
|
||
<Button
|
||
key={m}
|
||
type="button"
|
||
size="sm"
|
||
variant={pickupMode === m ? "primary" : "secondary"}
|
||
onClick={() => setPickupMode(m)}
|
||
>
|
||
{m === "id" ? "عنوان موجود (ID)" : "عنوان جديد"}
|
||
</Button>
|
||
))}
|
||
</div>
|
||
|
||
{pickupMode === "id" ? (
|
||
<Input
|
||
id="pickup-addressId"
|
||
label="معرّف العنوان (MongoDB ID)"
|
||
{...register("pickupAddressId")}
|
||
placeholder="67a1b2c3d4e5f6a7b8c9d0e1"
|
||
dir="ltr"
|
||
autoComplete="off"
|
||
/>
|
||
) : (
|
||
<div style={{ display: "grid", gridTemplateColumns: "1fr 1fr 1fr", gap: "0.75rem" }}>
|
||
<Input
|
||
id="pickup-city"
|
||
label="المدينة (اختياري)"
|
||
{...register("pickupAddress.details.city")}
|
||
placeholder="جدة"
|
||
dir="rtl"
|
||
/>
|
||
<Input
|
||
id="pickup-district"
|
||
label="الحي (اختياري)"
|
||
{...register("pickupAddress.details.district")}
|
||
placeholder="الروضة"
|
||
dir="rtl"
|
||
/>
|
||
<Input
|
||
id="pickup-street"
|
||
label="الشارع (اختياري)"
|
||
{...register("pickupAddress.details.street")}
|
||
placeholder="شارع التحلية"
|
||
dir="rtl"
|
||
/>
|
||
<Input
|
||
id="pickup-buildingNo"
|
||
label="رقم المبنى (اختياري)"
|
||
{...register("pickupAddress.details.buildingNo")}
|
||
placeholder="4321"
|
||
dir="ltr"
|
||
/>
|
||
<Input
|
||
id="pickup-unitNo"
|
||
label="رقم الوحدة (اختياري)"
|
||
{...register("pickupAddress.details.unitNo")}
|
||
placeholder="12"
|
||
dir="ltr"
|
||
/>
|
||
<Input
|
||
id="pickup-zipCode"
|
||
label="الرمز البريدي (اختياري)"
|
||
{...register("pickupAddress.details.zipCode")}
|
||
placeholder="23456"
|
||
dir="ltr"
|
||
/>
|
||
<Input
|
||
id="pickup-lng"
|
||
label="خط الطول (Longitude) (اختياري)"
|
||
type="number"
|
||
step="any"
|
||
error={pickupErr?.location?.coordinates ? " " : undefined}
|
||
{...coordField("pickupAddress.location.coordinates.0")}
|
||
placeholder="46.6753"
|
||
dir="ltr"
|
||
/>
|
||
<Input
|
||
id="pickup-lat"
|
||
label="خط العرض (Latitude) (اختياري)"
|
||
type="number"
|
||
step="any"
|
||
error={pickupErr?.location?.coordinates?.message}
|
||
{...coordField("pickupAddress.location.coordinates.1")}
|
||
placeholder="24.7136"
|
||
dir="ltr"
|
||
/>
|
||
</div>
|
||
)}
|
||
</form>
|
||
</Modal>
|
||
);
|
||
}
|
||
|
||
// Re-exported for callers that only need the empty-address shape (e.g. tests).
|
||
export { emptyAddress as emptyOrderAddress }; |