add archive endpoint to user , driver , car ,order ,trip and update trip fails also update saidecar

This commit is contained in:
m7amedez5511
2026-07-02 19:04:51 +03:00
parent c33778012a
commit d225291b70
29 changed files with 2096 additions and 432 deletions

View File

@@ -0,0 +1,159 @@
"use client";
import { useCallback, useEffect, useState } from "react";
import { getStoredToken } from "@/src/lib/auth";
import { carMaintenanceService } from "@/src/services/carMaintanance.service";
import type {
CarMaintenance,
CreateMaintenancePayload,
MaintenanceToastMsg,
UpdateMaintenancePayload,
} from "../types/carMaintanance";
// ── useCarMaintenanceList ─────────────────────────────────────────────────────
// Loads every maintenance record for one car — used in the detail panel.
export function useCarMaintenanceList(carId: string | null) {
const [records, setRecords] = useState<CarMaintenance[]>([]);
const [loading, setLoading] = useState(true);
const [error, setError] = useState<string | null>(null);
const loadRecords = useCallback(() => {
if (!carId) return;
const token = getStoredToken();
setLoading(true);
setError(null);
carMaintenanceService
.getAll(carId, token)
.then((res) => {
const payload = (res as unknown as { data: { data: CarMaintenance[] } }).data ?? res;
setRecords((payload as { data: CarMaintenance[] }).data ?? []);
})
.catch((err: Error) => setError(err.message))
.finally(() => setLoading(false));
}, [carId]);
useEffect(() => { loadRecords(); }, [loadRecords]);
// Remove a record from the list right away, instead of waiting on a refetch.
const removeRecord = useCallback((id: string) => {
setRecords((prev) => prev.filter((r) => r.id !== id));
}, []);
return { records, loading, error, loadRecords, removeRecord, setError };
}
// ── useCarMaintenanceDetail ───────────────────────────────────────────────────
// Fetches a single maintenance record by id.
export function useCarMaintenanceDetail(carId: string | null, maintenanceId: string | null) {
const [record, setRecord] = useState<CarMaintenance | null>(null);
const [loading, setLoading] = useState(true);
const [error, setError] = useState<string | null>(null);
useEffect(() => {
if (!carId || !maintenanceId) return;
const token = getStoredToken();
setLoading(true);
setError(null);
carMaintenanceService
.getById(carId, maintenanceId, token)
.then((res) => setRecord((res as unknown as { data: CarMaintenance }).data))
.catch((err: Error) => setError(err.message))
.finally(() => setLoading(false));
}, [carId, maintenanceId]);
return { record, loading, error };
}
// ── useCarMaintenanceMutations ────────────────────────────────────────────────
// Create, update, and soft-delete operations for maintenance records.
interface UseCarMaintenanceMutationsOptions {
carId: string;
onSuccess: (msg: string) => void;
onError: (msg: string) => void;
onDeleted: (id: string) => void;
getEditTarget: () => CarMaintenance | null;
}
export function useCarMaintenanceMutations({
carId,
onSuccess,
onError,
onDeleted,
getEditTarget,
}: UseCarMaintenanceMutationsOptions) {
const [saving, setSaving] = useState(false);
const [deleting, setDeleting] = useState(false);
const handleFormSubmit = useCallback(
async (
payload: CreateMaintenancePayload | UpdateMaintenancePayload,
isNew: boolean,
): Promise<boolean> => {
const token = getStoredToken();
setSaving(true);
try {
if (isNew) {
await carMaintenanceService.create(carId, payload as CreateMaintenancePayload, token);
onSuccess("تم إضافة سجل الصيانة بنجاح.");
} else {
const editTarget = getEditTarget();
if (!editTarget) return false;
await carMaintenanceService.update(
carId,
editTarget.id,
payload as UpdateMaintenancePayload,
token,
);
onSuccess("تم تحديث سجل الصيانة.");
}
return true;
} catch (err: unknown) {
// Show the backend's own message when it has one, otherwise a generic fallback.
onError(err instanceof Error ? err.message : "فشلت العملية.");
return false;
} finally {
setSaving(false);
}
},
[carId, onSuccess, onError, getEditTarget],
);
const handleDeleteConfirm = useCallback(
async (target: CarMaintenance) => {
setDeleting(true);
const token = getStoredToken();
try {
await carMaintenanceService.delete(carId, target.id, token);
onDeleted(target.id);
onSuccess("تم حذف سجل الصيانة بنجاح.");
} catch (err: unknown) {
onError(err instanceof Error ? err.message : "فشل الحذف.");
} finally {
setDeleting(false);
}
},
[carId, onSuccess, onError, onDeleted],
);
return { saving, deleting, handleFormSubmit, handleDeleteConfirm };
}
// ── useMaintenanceToast ────────────────────────────────────────────────────────
// Simple toast notification state, same shape as useCars' useToast.
export function useMaintenanceToast(duration = 3500) {
const [toast, setToast] = useState<MaintenanceToastMsg | null>(null);
const notify = useCallback(
(t: MaintenanceToastMsg) => {
setToast(t);
setTimeout(() => setToast(null), duration);
},
[duration],
);
return { toast, notify };
}

View File

View File

View File

View File

@@ -1,58 +1,112 @@
import { useCallback, useEffect, useMemo, useState } from "react";
"use client";
import { useCallback, useEffect, useReducer, useState } from "react";
import { getStoredToken } from "@/src/lib/auth";
import { archivedOrderService } from "@/src/services/archive/archivedOrder.service";
import type { ArchivedOrder } from "@/src/types/order";
const PAGE_SIZE = 10;
// ── Table state / reducer ──────────────────────────────────────────────────
// نفس شكل TableState/TableAction اللي في useOrder.ts عشان الهوكين يفضلوا متسقين
interface TableState {
orders: ArchivedOrder[];
loading: boolean;
error: string | null;
}
type TableAction =
| { type: "LOAD_START" }
| { type: "LOAD_OK"; orders: ArchivedOrder[] }
| { type: "LOAD_ERR"; error: string }
| { type: "CLEAR_ERR" };
function reducer(s: TableState, a: TableAction): TableState {
switch (a.type) {
case "LOAD_START":
return { ...s, loading: true, error: null };
case "LOAD_OK":
return { ...s, loading: false, orders: a.orders };
case "LOAD_ERR":
return { ...s, loading: false, error: a.error };
case "CLEAR_ERR":
return { ...s, error: null };
default:
return s;
}
}
const initialState: TableState = {
orders: [],
loading: true,
error: null,
};
// ── Main hook ──────────────────────────────────────────────────────────────
export function useArchivedOrders() {
const [orders, setOrders] = useState<ArchivedOrder[]>([]);
const [loading, setLoading] = useState(true);
const [state, dispatch] = useReducer(reducer, initialState);
const [page, setPage] = useState(1);
const [search, setSearch] = useState("");
const [error, setError] = useState<string | null>(null);
// ── Fetch archived orders ─────────────────────────────────────────────
const load = useCallback(async () => {
setLoading(true);
dispatch({ type: "LOAD_START" });
try {
const token = getStoredToken();
const res = await archivedOrderService.getAll(token);
setOrders(res.data);
setError(null);
// نفس الـ pattern بتاع useOrder.ts:
// get() بترجع axios response كامل، فـ res.data هو الـ JSON body
// {success, message, responseAt, data}. الـ "?? res" احتياطي لو
// الـ interceptor بتاع axios بيرجّع الـ body مباشرة من غير .data
const payload =
(res as unknown as { data: { data: ArchivedOrder[] } }).data ?? res;
dispatch({ type: "LOAD_OK", orders: payload.data ?? [] });
} catch {
setError("تعذّر تحميل الطلبات المؤرشفة. يرجى المحاولة لاحقاً.");
} finally {
setLoading(false);
dispatch({
type: "LOAD_ERR",
error: "تعذّر تحميل الطلبات المؤرشفة. يرجى المحاولة لاحقاً.",
});
}
}, []);
useEffect(() => { load(); }, [load]);
useEffect(() => {
load();
}, [load]);
// Client-side search — endpoint has no `?search=` param in the sample payload
const filtered = useMemo(() => {
if (!search.trim()) return orders;
const q = search.trim().toLowerCase();
return orders.filter(
o =>
o.shipmentNumber.toLowerCase().includes(q) ||
o.recipientName.toLowerCase().includes(q) ||
o.recipientPhone.includes(q),
);
}, [orders, search]);
// ── Client-side search — الـ endpoint معندوش ?search= param ────────────
const filtered = !search.trim()
? state.orders
: state.orders.filter((o) => {
const q = search.trim().toLowerCase();
return (
o.shipmentNumber.toLowerCase().includes(q) ||
o.recipientName.toLowerCase().includes(q) ||
o.recipientPhone.includes(q)
);
});
const pages = Math.max(1, Math.ceil(filtered.length / PAGE_SIZE));
const paginated = filtered.slice((page - 1) * PAGE_SIZE, page * PAGE_SIZE);
const handleSearch = (value: string) => {
// ── Search / page helpers ──────────────────────────────────────────────
const handleSearch = useCallback((value: string) => {
setSearch(value);
setPage(1);
};
}, []);
return {
orders: paginated,
total: filtered.length,
loading, pages, page, search, error,
setPage, handleSearch, clearError: () => setError(null),
loading: state.loading,
pages,
page,
search,
error: state.error,
setPage,
handleSearch,
clearError: () => dispatch({ type: "CLEAR_ERR" }),
refresh: load,
};
}