update page to using same tamplet in all project not using inline code
This commit is contained in:
@@ -1,10 +1,10 @@
|
||||
"use client";
|
||||
|
||||
import { useEffect, useRef, useState } from "react";
|
||||
import { useEffect, useState } from "react";
|
||||
import Link from "next/link";
|
||||
import { useForm } from "react-hook-form";
|
||||
import { yupResolver } from "@hookform/resolvers/yup";
|
||||
import { Alert, Spinner } from "../UI";
|
||||
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";
|
||||
@@ -22,34 +22,6 @@ import type {
|
||||
import type { Client } from "@/src/types/client";
|
||||
import type { Trip } from "@/src/types/trip";
|
||||
|
||||
const inputBase: React.CSSProperties = {
|
||||
width: "100%",
|
||||
height: 40,
|
||||
padding: "0 0.75rem",
|
||||
borderRadius: "var(--radius-md)",
|
||||
border: "1px solid var(--color-border)",
|
||||
background: "var(--color-surface)",
|
||||
fontSize: 13,
|
||||
color: "var(--color-text-primary)",
|
||||
outline: "none",
|
||||
fontFamily: "var(--font-sans)",
|
||||
};
|
||||
|
||||
const labelStyle: React.CSSProperties = {
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
gap: 6,
|
||||
fontSize: 12,
|
||||
fontWeight: 600,
|
||||
color: "var(--color-text-secondary)",
|
||||
};
|
||||
|
||||
const errorTextStyle: React.CSSProperties = {
|
||||
fontSize: 11,
|
||||
color: "var(--color-danger)",
|
||||
fontWeight: 500,
|
||||
};
|
||||
|
||||
const sectionHeadingStyle: React.CSSProperties = {
|
||||
fontSize: 11,
|
||||
letterSpacing: "0.25em",
|
||||
@@ -59,19 +31,12 @@ const sectionHeadingStyle: React.CSSProperties = {
|
||||
margin: "0.5rem 0 0",
|
||||
};
|
||||
|
||||
const optionalLabelStyle: React.CSSProperties = {
|
||||
fontSize: 10,
|
||||
fontWeight: 500,
|
||||
color: "var(--color-text-hint)",
|
||||
marginRight: 4,
|
||||
};
|
||||
|
||||
const createLinkStyle: React.CSSProperties = {
|
||||
fontSize: 11,
|
||||
color: "var(--color-brand-600)",
|
||||
textDecoration: "none",
|
||||
fontWeight: 500,
|
||||
marginTop: 2,
|
||||
marginTop: 6,
|
||||
display: "inline-flex",
|
||||
alignItems: "center",
|
||||
gap: 3,
|
||||
@@ -147,9 +112,12 @@ export function OrderFormModal({
|
||||
register,
|
||||
handleSubmit,
|
||||
setError,
|
||||
setFocus,
|
||||
formState: { errors, isSubmitting },
|
||||
} = useForm<OrderFormValues>({
|
||||
resolver: yupResolver(isNew ? createOrderSchema : updateOrderSchema) as never,
|
||||
// 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 ?? "",
|
||||
@@ -200,7 +168,12 @@ export function OrderFormModal({
|
||||
});
|
||||
|
||||
const [apiError, setApiError] = useState("");
|
||||
const firstRef = useRef<HTMLInputElement>(null);
|
||||
|
||||
// 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;
|
||||
@@ -227,9 +200,6 @@ export function OrderFormModal({
|
||||
};
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
firstRef.current?.focus();
|
||||
}, []);
|
||||
useEffect(() => {
|
||||
const h = (e: KeyboardEvent) => {
|
||||
if (e.key === "Escape") onClose();
|
||||
@@ -238,11 +208,6 @@ export function OrderFormModal({
|
||||
return () => window.removeEventListener("keydown", h);
|
||||
}, [onClose]);
|
||||
|
||||
const withError = (hasError: boolean): React.CSSProperties => ({
|
||||
...inputBase,
|
||||
...(hasError ? { borderColor: "var(--color-danger)", background: "#FEF2F2" } : {}),
|
||||
});
|
||||
|
||||
const numberField = (
|
||||
field:
|
||||
| "quantity"
|
||||
@@ -340,438 +305,371 @@ export function OrderFormModal({
|
||||
const pickupErr = errors.pickupAddress;
|
||||
|
||||
return (
|
||||
<div
|
||||
role="dialog"
|
||||
aria-modal="true"
|
||||
aria-labelledby="order-modal-title"
|
||||
onClick={(e) => {
|
||||
if (e.target === e.currentTarget) onClose();
|
||||
}}
|
||||
style={{
|
||||
position: "fixed",
|
||||
inset: 0,
|
||||
zIndex: 50,
|
||||
background: "rgba(15,23,42,0.55)",
|
||||
backdropFilter: "blur(4px)",
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
justifyContent: "center",
|
||||
padding: "1rem",
|
||||
overflowY: "auto",
|
||||
}}
|
||||
<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>
|
||||
</>
|
||||
}
|
||||
>
|
||||
<div
|
||||
onClick={(e) => e.stopPropagation()}
|
||||
style={{
|
||||
width: "100%",
|
||||
maxWidth: 640,
|
||||
background: "var(--color-surface)",
|
||||
borderRadius: "var(--radius-2xl)",
|
||||
border: "1px solid var(--color-border)",
|
||||
boxShadow: "0 24px 64px rgba(0,0,0,.18)",
|
||||
overflow: "hidden",
|
||||
margin: "auto",
|
||||
}}
|
||||
<form
|
||||
id="order-form"
|
||||
onSubmit={handleSubmit(submitHandler)}
|
||||
noValidate
|
||||
style={{ display: "flex", flexDirection: "column", gap: "1rem" }}
|
||||
dir="rtl"
|
||||
>
|
||||
<div
|
||||
style={{
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
justifyContent: "space-between",
|
||||
padding: "1.25rem 1.5rem",
|
||||
borderBottom: "1px solid var(--color-border)",
|
||||
background: "var(--color-surface-muted)",
|
||||
}}
|
||||
>
|
||||
{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>
|
||||
<p style={{ fontSize: 11, letterSpacing: "0.3em", textTransform: "uppercase", color: "#2563EB", fontWeight: 600, margin: 0 }}>
|
||||
{isNew ? "إنشاء طلب" : "تعديل طلب"}
|
||||
</p>
|
||||
<h2
|
||||
id="order-modal-title"
|
||||
style={{ fontSize: 17, fontWeight: 700, color: "var(--color-text-primary)", margin: "4px 0 0", fontFamily: "var(--font-mono)" }}
|
||||
<Select
|
||||
id="order-clientId"
|
||||
label="العميل *"
|
||||
error={errors.clientId?.message}
|
||||
disabled={relLoading}
|
||||
{...register("clientId")}
|
||||
dir="rtl"
|
||||
style={{ cursor: relLoading ? "wait" : "pointer" }}
|
||||
>
|
||||
{isNew ? "طلب جديد" : editOrder?.shipmentNumber}
|
||||
</h2>
|
||||
<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>
|
||||
<button
|
||||
type="button"
|
||||
onClick={onClose}
|
||||
aria-label="إغلاق"
|
||||
style={{
|
||||
width: 34, height: 34, borderRadius: "var(--radius-md)",
|
||||
border: "1px solid var(--color-border)", background: "var(--color-surface)",
|
||||
cursor: "pointer", fontSize: 18, color: "var(--color-text-muted)",
|
||||
display: "flex", alignItems: "center", justifyContent: "center",
|
||||
}}
|
||||
>
|
||||
×
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<form
|
||||
onSubmit={handleSubmit(submitHandler)}
|
||||
noValidate
|
||||
style={{ padding: "1.5rem", display: "flex", flexDirection: "column", gap: "1rem", maxHeight: "75vh", overflowY: "auto" }}
|
||||
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 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>
|
||||
<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>
|
||||
|
||||
<div style={{ display: "grid", gridTemplateColumns: "1fr 1fr", gap: "0.75rem" }}>
|
||||
<label style={labelStyle}>
|
||||
رقم الشحنة *
|
||||
<input
|
||||
ref={firstRef}
|
||||
style={withError(!!errors.shipmentNumber && errors.shipmentNumber.type !== "manual")}
|
||||
{...register("shipmentNumber")}
|
||||
placeholder="SHP-0001"
|
||||
dir="ltr"
|
||||
autoComplete="off"
|
||||
/>
|
||||
{errors.shipmentNumber && errors.shipmentNumber.type !== "manual" && (
|
||||
<span style={errorTextStyle}>{errors.shipmentNumber.message}</span>
|
||||
)}
|
||||
</label>
|
||||
<p style={sectionHeadingStyle}>عنوان الاستلام</p>
|
||||
|
||||
<label style={labelStyle}>
|
||||
العميل *
|
||||
<select
|
||||
style={{ ...withError(!!errors.clientId), cursor: relLoading ? "wait" : "pointer" }}
|
||||
disabled={relLoading}
|
||||
{...register("clientId")}
|
||||
dir="rtl"
|
||||
>
|
||||
<option value="">{relLoading ? "جارٍ التحميل…" : "اختر العميل"}</option>
|
||||
{clients.map((c) => (
|
||||
<option key={c.id} value={c.id}>
|
||||
{c.name}
|
||||
{c.phone ? ` — ${c.phone}` : ""}
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
{errors.clientId && <span style={errorTextStyle}>{errors.clientId.message}</span>}
|
||||
<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>
|
||||
</label>
|
||||
|
||||
<label style={labelStyle}>
|
||||
اسم المستلم *
|
||||
<input style={withError(!!errors.recipientName)} {...register("recipientName")} placeholder="أحمد محمد" dir="rtl" />
|
||||
{errors.recipientName && <span style={errorTextStyle}>{errors.recipientName.message}</span>}
|
||||
</label>
|
||||
|
||||
<label style={labelStyle}>
|
||||
رقم جوال المستلم *
|
||||
<input style={withError(!!errors.recipientPhone)} {...register("recipientPhone")} placeholder="05XXXXXXXX" dir="ltr" />
|
||||
{errors.recipientPhone && <span style={errorTextStyle}>{errors.recipientPhone.message}</span>}
|
||||
</label>
|
||||
|
||||
<label style={{ ...labelStyle, gridColumn: "1 / -1" }}>
|
||||
{isNew ? "الرحلة *" : "الرحلة"}
|
||||
{!isNew && <span style={optionalLabelStyle}>(اختياري)</span>}
|
||||
<select
|
||||
style={{ ...withError(!!errors.tripId), cursor: relLoading ? "wait" : "pointer" }}
|
||||
disabled={relLoading}
|
||||
{...register("tripId")}
|
||||
dir="rtl"
|
||||
>
|
||||
<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>
|
||||
{errors.tripId && <span style={errorTextStyle}>{errors.tripId.message}</span>}
|
||||
<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>
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<p style={sectionHeadingStyle}>تفاصيل الشحنة</p>
|
||||
|
||||
<div style={{ display: "grid", gridTemplateColumns: "1fr 1fr 1fr", gap: "0.75rem" }}>
|
||||
<label style={labelStyle}>
|
||||
نوع الشحنة
|
||||
<span style={optionalLabelStyle}>(اختياري)</span>
|
||||
<input style={inputBase} {...register("type")} placeholder="عادي / مبرد" dir="rtl" />
|
||||
</label>
|
||||
|
||||
<label style={labelStyle}>
|
||||
الكمية *
|
||||
<input type="number" min={1} style={withError(!!errors.quantity)} {...numberField("quantity")} dir="ltr" />
|
||||
{errors.quantity && <span style={errorTextStyle}>{errors.quantity.message}</span>}
|
||||
</label>
|
||||
|
||||
<label style={labelStyle}>
|
||||
الوزن (كجم)
|
||||
<span style={optionalLabelStyle}>(اختياري)</span>
|
||||
<input type="number" min={0} step="0.01" style={inputBase} {...numberField("weight")} dir="ltr" />
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<p style={sectionHeadingStyle}>بيانات الدفع</p>
|
||||
|
||||
<div style={{ display: "grid", gridTemplateColumns: "1fr 1fr 1fr", gap: "0.75rem" }}>
|
||||
<label style={labelStyle}>
|
||||
الإجمالي الفرعي
|
||||
<span style={optionalLabelStyle}>(اختياري)</span>
|
||||
<input type="number" min={0} step="0.01" style={withError(!!errors.subTotal)} {...numberField("subTotal")} dir="ltr" />
|
||||
{errors.subTotal && <span style={errorTextStyle}>{errors.subTotal.message}</span>}
|
||||
</label>
|
||||
|
||||
<label style={labelStyle}>
|
||||
نسبة الضريبة (%)
|
||||
<span style={optionalLabelStyle}>(اختياري)</span>
|
||||
<input type="number" min={0} step="0.01" style={withError(!!errors.vatRate)} {...numberField("vatRate")} dir="ltr" />
|
||||
{errors.vatRate && <span style={errorTextStyle}>{errors.vatRate.message}</span>}
|
||||
</label>
|
||||
|
||||
<label style={labelStyle}>
|
||||
طريقة الدفع
|
||||
<span style={optionalLabelStyle}>(اختياري)</span>
|
||||
<select style={{ ...inputBase, cursor: "pointer" }} {...register("paymentMethod")} dir="rtl">
|
||||
<option value="">اختر الطريقة</option>
|
||||
<option value="Cash">نقداً</option>
|
||||
<option value="Card">بطاقة</option>
|
||||
<option value="Prepaid">مدفوع مسبقاً</option>
|
||||
</select>
|
||||
</label>
|
||||
|
||||
{!isNew && (
|
||||
<label style={labelStyle}>
|
||||
حالة الدفع
|
||||
<select style={{ ...inputBase, cursor: "pointer" }} {...register("paymentStatus")} dir="rtl">
|
||||
<option value="">—</option>
|
||||
<option value="Pending">معلَّق</option>
|
||||
<option value="Paid">مدفوع</option>
|
||||
<option value="Failed">فشل</option>
|
||||
<option value="Refunded">مُسترجع</option>
|
||||
</select>
|
||||
</label>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<p style={sectionHeadingStyle}>عنوان التسليم</p>
|
||||
<div style={{ display: "grid", gridTemplateColumns: "1fr 1fr 1fr", gap: "0.75rem" }}>
|
||||
<label style={labelStyle}>
|
||||
المدينة
|
||||
<span style={optionalLabelStyle}>(اختياري)</span>
|
||||
<input style={inputBase} {...register("deliveryAddress.details.city")} placeholder="الرياض" dir="rtl" />
|
||||
</label>
|
||||
<label style={labelStyle}>
|
||||
الحي
|
||||
<span style={optionalLabelStyle}>(اختياري)</span>
|
||||
<input style={inputBase} {...register("deliveryAddress.details.district")} placeholder="العليا" dir="rtl" />
|
||||
</label>
|
||||
<label style={labelStyle}>
|
||||
الشارع
|
||||
<span style={optionalLabelStyle}>(اختياري)</span>
|
||||
<input style={inputBase} {...register("deliveryAddress.details.street")} placeholder="شارع الأمير محمد" dir="rtl" />
|
||||
</label>
|
||||
<label style={labelStyle}>
|
||||
رقم المبنى
|
||||
<span style={optionalLabelStyle}>(اختياري)</span>
|
||||
<input style={inputBase} {...register("deliveryAddress.details.buildingNo")} placeholder="1234" dir="ltr" />
|
||||
</label>
|
||||
<label style={labelStyle}>
|
||||
رقم الوحدة
|
||||
<span style={optionalLabelStyle}>(اختياري)</span>
|
||||
<input style={inputBase} {...register("deliveryAddress.details.unitNo")} placeholder="56" dir="ltr" />
|
||||
</label>
|
||||
<label style={labelStyle}>
|
||||
الرمز البريدي
|
||||
<span style={optionalLabelStyle}>(اختياري)</span>
|
||||
<input style={inputBase} {...register("deliveryAddress.details.zipCode")} placeholder="12345" dir="ltr" />
|
||||
</label>
|
||||
<label style={labelStyle}>
|
||||
خط الطول (Longitude) *
|
||||
<input
|
||||
type="number"
|
||||
step="any"
|
||||
style={withError(!!deliveryErr?.location?.coordinates)}
|
||||
{...coordField("deliveryAddress.location.coordinates.0")}
|
||||
placeholder="46.6753"
|
||||
dir="ltr"
|
||||
/>
|
||||
</label>
|
||||
<label style={labelStyle}>
|
||||
خط العرض (Latitude) *
|
||||
<input
|
||||
type="number"
|
||||
step="any"
|
||||
style={withError(!!deliveryErr?.location?.coordinates)}
|
||||
{...coordField("deliveryAddress.location.coordinates.1")}
|
||||
placeholder="24.7136"
|
||||
dir="ltr"
|
||||
/>
|
||||
{deliveryErr?.location?.coordinates && (
|
||||
<span style={errorTextStyle}>{deliveryErr.location.coordinates.message}</span>
|
||||
)}
|
||||
</label>
|
||||
</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"
|
||||
onClick={() => setPickupMode(m)}
|
||||
style={{
|
||||
height: 32,
|
||||
padding: "0 0.875rem",
|
||||
borderRadius: "var(--radius-md)",
|
||||
border: `1px solid ${pickupMode === m ? "var(--color-brand-600)" : "var(--color-border)"}`,
|
||||
background: pickupMode === m ? "var(--color-brand-50, #EFF6FF)" : "var(--color-surface)",
|
||||
fontSize: 12,
|
||||
fontWeight: 600,
|
||||
color: pickupMode === m ? "var(--color-brand-600)" : "var(--color-text-secondary)",
|
||||
cursor: "pointer",
|
||||
fontFamily: "var(--font-sans)",
|
||||
}}
|
||||
>
|
||||
{m === "id" ? "عنوان موجود (ID)" : "عنوان جديد"}
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
|
||||
{pickupMode === "id" ? (
|
||||
<label style={labelStyle}>
|
||||
معرّف العنوان (MongoDB ID)
|
||||
<input
|
||||
style={inputBase}
|
||||
{...register("pickupAddressId")}
|
||||
placeholder="67a1b2c3d4e5f6a7b8c9d0e1"
|
||||
dir="ltr"
|
||||
autoComplete="off"
|
||||
/>
|
||||
</label>
|
||||
) : (
|
||||
<div style={{ display: "grid", gridTemplateColumns: "1fr 1fr 1fr", gap: "0.75rem" }}>
|
||||
<label style={labelStyle}>
|
||||
المدينة
|
||||
<span style={optionalLabelStyle}>(اختياري)</span>
|
||||
<input style={inputBase} {...register("pickupAddress.details.city")} placeholder="جدة" dir="rtl" />
|
||||
</label>
|
||||
<label style={labelStyle}>
|
||||
الحي
|
||||
<span style={optionalLabelStyle}>(اختياري)</span>
|
||||
<input style={inputBase} {...register("pickupAddress.details.district")} placeholder="الروضة" dir="rtl" />
|
||||
</label>
|
||||
<label style={labelStyle}>
|
||||
الشارع
|
||||
<span style={optionalLabelStyle}>(اختياري)</span>
|
||||
<input style={inputBase} {...register("pickupAddress.details.street")} placeholder="شارع التحلية" dir="rtl" />
|
||||
</label>
|
||||
<label style={labelStyle}>
|
||||
رقم المبنى
|
||||
<span style={optionalLabelStyle}>(اختياري)</span>
|
||||
<input style={inputBase} {...register("pickupAddress.details.buildingNo")} placeholder="4321" dir="ltr" />
|
||||
</label>
|
||||
<label style={labelStyle}>
|
||||
رقم الوحدة
|
||||
<span style={optionalLabelStyle}>(اختياري)</span>
|
||||
<input style={inputBase} {...register("pickupAddress.details.unitNo")} placeholder="12" dir="ltr" />
|
||||
</label>
|
||||
<label style={labelStyle}>
|
||||
الرمز البريدي
|
||||
<span style={optionalLabelStyle}>(اختياري)</span>
|
||||
<input style={inputBase} {...register("pickupAddress.details.zipCode")} placeholder="23456" dir="ltr" />
|
||||
</label>
|
||||
<label style={labelStyle}>
|
||||
خط الطول (Longitude)
|
||||
<span style={optionalLabelStyle}>(اختياري)</span>
|
||||
<input
|
||||
type="number"
|
||||
step="any"
|
||||
style={withError(!!pickupErr?.location?.coordinates)}
|
||||
{...coordField("pickupAddress.location.coordinates.0")}
|
||||
placeholder="46.6753"
|
||||
dir="ltr"
|
||||
/>
|
||||
</label>
|
||||
<label style={labelStyle}>
|
||||
خط العرض (Latitude)
|
||||
<span style={optionalLabelStyle}>(اختياري)</span>
|
||||
<input
|
||||
type="number"
|
||||
step="any"
|
||||
style={withError(!!pickupErr?.location?.coordinates)}
|
||||
{...coordField("pickupAddress.location.coordinates.1")}
|
||||
placeholder="24.7136"
|
||||
dir="ltr"
|
||||
/>
|
||||
{pickupErr?.location?.coordinates && (
|
||||
<span style={errorTextStyle}>{pickupErr.location.coordinates.message}</span>
|
||||
)}
|
||||
</label>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div style={{ display: "flex", gap: "0.5rem", justifyContent: "flex-end", paddingTop: "0.5rem" }}>
|
||||
<button
|
||||
<div style={{ display: "flex", gap: "0.5rem", marginBottom: "0.25rem" }}>
|
||||
{(["id", "new"] as const).map((m) => (
|
||||
<Button
|
||||
key={m}
|
||||
type="button"
|
||||
onClick={onClose}
|
||||
disabled={isSubmitting}
|
||||
style={{
|
||||
height: 40,
|
||||
padding: "0 1.25rem",
|
||||
borderRadius: "var(--radius-md)",
|
||||
border: "1px solid var(--color-border)",
|
||||
background: "var(--color-surface)",
|
||||
fontSize: 13,
|
||||
fontWeight: 600,
|
||||
color: "var(--color-text-secondary)",
|
||||
cursor: isSubmitting ? "not-allowed" : "pointer",
|
||||
fontFamily: "var(--font-sans)",
|
||||
}}
|
||||
size="sm"
|
||||
variant={pickupMode === m ? "primary" : "secondary"}
|
||||
onClick={() => setPickupMode(m)}
|
||||
>
|
||||
إلغاء
|
||||
</button>
|
||||
<button
|
||||
type="submit"
|
||||
disabled={isSubmitting}
|
||||
style={{
|
||||
height: 40,
|
||||
padding: "0 1.5rem",
|
||||
borderRadius: "var(--radius-md)",
|
||||
border: "none",
|
||||
background: isSubmitting ? "var(--color-brand-400)" : "var(--color-brand-600)",
|
||||
fontSize: 13,
|
||||
fontWeight: 700,
|
||||
color: "#FFF",
|
||||
cursor: isSubmitting ? "not-allowed" : "pointer",
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
gap: 8,
|
||||
fontFamily: "var(--font-sans)",
|
||||
}}
|
||||
>
|
||||
{isSubmitting && <Spinner size="sm" className="text-white" />}
|
||||
{isSubmitting ? "جارٍ الحفظ…" : isNew ? "إنشاء الطلب" : "حفظ التغييرات"}
|
||||
</button>
|
||||
{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>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</form>
|
||||
</Modal>
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user