Files
LogisicsApp_Client/hooks/useCars.ts

217 lines
7.5 KiB
TypeScript

"use client";
// hooks/useCars.ts
// All custom hooks for the Car module.
import { useCallback, useEffect, useState } from "react";
import { getStoredToken } from "../lib/auth";
import { carService } from "../services/car.service";
import { get } from "../services/api";
import type {
Car,
CarImage,
CarListResponse,
CreateCarPayload,
ImageStage,
ToastMsg,
UpdateCarPayload,
} from "../types/car";
// ── useCars ───────────────────────────────────────────────────────────────────
// Manages the paginated car list for the main page.
export function useCars(page: number, search: string) {
const [cars, setCars] = useState<Car[]>([]);
const [loading, setLoading] = useState(true);
const [error, setError] = useState<string | null>(null);
const [total, setTotal] = useState(0);
const [pages, setPages] = useState(1);
const loadCars = useCallback(() => {
const token = getStoredToken();
setLoading(true);
setError(null);
const query = `?page=${page}&limit=12${search ? `&search=${encodeURIComponent(search)}` : ""}`;
get<CarListResponse>(`cars${query}`, token)
.then((res) => {
const payload = (res as unknown as { data: CarListResponse["data"] }).data ?? res;
setCars((payload as CarListResponse["data"]).data ?? []);
setTotal((payload as CarListResponse["data"]).meta?.total ?? 0);
setPages((payload as CarListResponse["data"]).meta?.pages ?? 1);
})
.catch((err: Error) => setError(err.message))
.finally(() => setLoading(false));
}, [page, search]);
useEffect(() => { loadCars(); }, [loadCars]);
// Optimistic removal after delete
const removeCar = useCallback((id: string) => {
setCars(prev => prev.filter(c => c.id !== id));
setTotal(prev => Math.max(0, prev - 1));
}, []);
return { cars, loading, error, total, pages, loadCars, removeCar, setError };
}
// ── useCarDetail ──────────────────────────────────────────────────────────────
// Fetches a single car by ID — used in CarDetailPanel.
export function useCarDetail(carId: string) {
const [car, setCar] = useState<Car | null>(null);
const [loading, setLoading] = useState(true);
const [error, setError] = useState<string | null>(null);
useEffect(() => {
const token = getStoredToken();
setLoading(true);
setError(null);
carService
.getById(carId, token)
.then(res => setCar((res as unknown as { data: Car }).data))
.catch((err: Error) => setError(err.message))
.finally(() => setLoading(false));
}, [carId]);
return { car, loading, error };
}
// ── useCarMutations ───────────────────────────────────────────────────────────
// Create, update, and delete operations — used in the main page.
interface UseCarMutationsOptions {
onSuccess: (msg: string) => void;
onError: (msg: string) => void;
onDeleted: (id: string) => void;
getEditTarget: () => Car | null;
}
export function useCarMutations({
onSuccess,
onError,
onDeleted,
getEditTarget,
}: UseCarMutationsOptions) {
const [deleting, setDeleting] = useState(false);
const handleFormSubmit = useCallback(async (
payload: CreateCarPayload | UpdateCarPayload,
isNew: boolean,
): Promise<boolean> => {
const token = getStoredToken();
try {
if (isNew) {
await carService.create(payload as CreateCarPayload, token);
onSuccess("تم إضافة المركبة بنجاح.");
} else {
const editTarget = getEditTarget();
if (!editTarget) return false;
await carService.update(editTarget.id, payload as UpdateCarPayload, token);
onSuccess("تم تحديث بيانات المركبة.");
}
return true;
} catch (err: unknown) {
onError(err instanceof Error ? err.message : "فشلت العملية.");
return false;
}
}, [onSuccess, onError, getEditTarget]);
const handleDeleteConfirm = useCallback(async (target: Car) => {
setDeleting(true);
const token = getStoredToken();
try {
await carService.delete(target.id, token);
onDeleted(target.id);
onSuccess(`تم حذف ${target.manufacturer} ${target.model} بنجاح.`);
} catch (err: unknown) {
onError(err instanceof Error ? err.message : "فشل الحذف.");
} finally {
setDeleting(false);
}
}, [onSuccess, onError, onDeleted]);
return { deleting, handleFormSubmit, handleDeleteConfirm };
}
// ── useToast ──────────────────────────────────────────────────────────────────
// Simple toast notification state.
export function useToast(duration = 3500) {
const [toast, setToast] = useState<ToastMsg | null>(null);
const notify = useCallback((t: ToastMsg) => {
setToast(t);
setTimeout(() => setToast(null), duration);
}, [duration]);
return { toast, notify };
}
// ── useCarImages ──────────────────────────────────────────────────────────────
// Manages gallery images — used in CarImageGallery.
export function useCarImages(carId: string, sortBy: "asc" | "desc") {
const [images, setImages] = useState<CarImage[]>([]);
const [loading, setLoading] = useState(true);
const [uploading, setUploading] = useState(false);
const [deleting, setDeleting] = useState<string | null>(null);
const [error, setError] = useState<string | null>(null);
const fetchImages = useCallback(async () => {
setLoading(true);
setError(null);
try {
const token = getStoredToken();
const res = await carService.getImages(carId, token, { sortBy });
const raw = (res as unknown as { data: CarImage[] }).data ?? [];
setImages(raw);
} catch (err: unknown) {
setError(err instanceof Error ? err.message : "تعذّر تحميل الصور");
} finally {
setLoading(false);
}
}, [carId, sortBy]);
useEffect(() => { fetchImages(); }, [fetchImages]);
const uploadImages = useCallback(async (files: File[], stage: ImageStage) => {
setUploading(true);
setError(null);
try {
const token = getStoredToken();
await carService.uploadImages(carId, files, stage, token);
await fetchImages();
} catch (err: unknown) {
setError(err instanceof Error ? err.message : "فشل رفع الصور");
} finally {
setUploading(false);
}
}, [carId, fetchImages]);
const deleteImage = useCallback(async (imageId: string) => {
setDeleting(imageId);
setError(null);
try {
const token = getStoredToken();
await carService.deleteImage(imageId, token);
setImages(prev => prev.filter(img => img.id !== imageId));
return true;
} catch (err: unknown) {
setError(err instanceof Error ? err.message : "فشل حذف الصورة");
return false;
} finally {
setDeleting(null);
}
}, []);
return {
images,
loading,
uploading,
deleting,
error,
setError,
fetchImages,
uploadImages,
deleteImage,
};
}