mirror of
http://88.130.71.182:3000/BlitTech/badoHair_fe.git
synced 2026-06-12 23:23:22 +00:00
150 lines
4.6 KiB
TypeScript
150 lines
4.6 KiB
TypeScript
"use client";
|
|
|
|
import React, { useContext, useState, useEffect, startTransition, ReactNode, createContext } from "react";
|
|
import { useAuth } from "@/contexts/AuthContext";
|
|
import * as productsApi from "@/lib/api/products";
|
|
import * as bookingsApi from "@/lib/api/bookings";
|
|
import { Product } from "@/data/products";
|
|
import { ApiError } from "@/lib/api";
|
|
|
|
// Adapter: maps BookingApi to the Reservation shape used by admin pages
|
|
export interface Reservation {
|
|
id: string;
|
|
clientName: string;
|
|
email: string;
|
|
phone: string;
|
|
service: string;
|
|
date: string;
|
|
time: string;
|
|
status: "pending" | "confirmed" | "cancelled";
|
|
createdAt: string;
|
|
}
|
|
|
|
function toReservation(b: bookingsApi.BookingApi): Reservation {
|
|
return {
|
|
id: b.id,
|
|
clientName: b.client_name ?? "—",
|
|
email: b.client_email ?? "—",
|
|
phone: b.client_phone ?? "—",
|
|
service: b.service_note ?? "—",
|
|
date: b.slot_date,
|
|
time: b.slot_start,
|
|
status: b.status === "completed" || b.status === "no_show" ? "confirmed" : b.status as Reservation["status"],
|
|
createdAt: b.created_at.slice(0, 10),
|
|
};
|
|
}
|
|
|
|
interface AdminContextType {
|
|
isAdmin: boolean;
|
|
products: Product[];
|
|
productsLoading: boolean;
|
|
refreshProducts: () => Promise<void>;
|
|
addProduct: (payload: productsApi.ProductPayload) => Promise<Product>;
|
|
updateProduct: (id: string, payload: Partial<productsApi.ProductPayload>) => Promise<void>;
|
|
deleteProduct: (id: string) => Promise<void>;
|
|
reservations: Reservation[];
|
|
reservationsLoading: boolean;
|
|
refreshReservations: (silent?: boolean) => Promise<void>;
|
|
updateReservationStatus: (id: string, status: "confirmed" | "cancelled") => Promise<void>;
|
|
deleteReservation: (id: string) => Promise<void>;
|
|
}
|
|
|
|
const AdminContext = createContext<AdminContextType | undefined>(undefined);
|
|
|
|
export const AdminProvider = ({ children }: { children: ReactNode }) => {
|
|
const { isAdmin } = useAuth();
|
|
const [products, setProducts] = useState<Product[]>([]);
|
|
const [productsLoading, setProductsLoading] = useState(false);
|
|
const [reservations, setReservations] = useState<Reservation[]>([]);
|
|
const [reservationsLoading, setReservationsLoading] = useState(false);
|
|
|
|
const refreshProducts = async () => {
|
|
if (!isAdmin) return;
|
|
setProductsLoading(true);
|
|
try {
|
|
const res = await productsApi.adminListProducts();
|
|
setProducts(res.data);
|
|
} finally {
|
|
setProductsLoading(false);
|
|
}
|
|
};
|
|
|
|
const refreshReservations = async (silent = false) => {
|
|
if (!isAdmin) return;
|
|
if (!silent) setReservationsLoading(true);
|
|
try {
|
|
const res = await bookingsApi.adminListBookings();
|
|
setReservations(res.data.map(toReservation));
|
|
} finally {
|
|
if (!silent) setReservationsLoading(false);
|
|
}
|
|
};
|
|
|
|
useEffect(() => {
|
|
if (isAdmin) {
|
|
startTransition(() => {
|
|
refreshProducts();
|
|
refreshReservations();
|
|
});
|
|
}
|
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
}, [isAdmin]);
|
|
|
|
const addProduct = async (payload: productsApi.ProductPayload): Promise<Product> => {
|
|
const newProduct = await productsApi.adminCreateProduct(payload);
|
|
setProducts((prev) => [newProduct, ...prev]);
|
|
return newProduct;
|
|
};
|
|
|
|
const updateProduct = async (id: string, payload: Partial<productsApi.ProductPayload>) => {
|
|
const updated = await productsApi.adminUpdateProduct(id, payload);
|
|
setProducts((prev) => prev.map((p) => (p.id === id ? updated : p)));
|
|
};
|
|
|
|
const deleteProduct = async (id: string) => {
|
|
await productsApi.adminDeleteProduct(id);
|
|
setProducts((prev) => prev.filter((p) => p.id !== id));
|
|
};
|
|
|
|
const updateReservationStatus = async (id: string, status: "confirmed" | "cancelled") => {
|
|
await bookingsApi.adminUpdateBookingStatus(id, status);
|
|
setReservations((prev) =>
|
|
prev.map((r) => (r.id === id ? { ...r, status } : r))
|
|
);
|
|
// Sync with server silently — no loading flash
|
|
refreshReservations(true);
|
|
};
|
|
|
|
const deleteReservation = async (id: string) => {
|
|
await bookingsApi.adminDeleteBooking(id);
|
|
setReservations((prev) => prev.filter((r) => r.id !== id));
|
|
};
|
|
|
|
return (
|
|
<AdminContext.Provider
|
|
value={{
|
|
isAdmin,
|
|
products,
|
|
productsLoading,
|
|
refreshProducts,
|
|
addProduct,
|
|
updateProduct,
|
|
deleteProduct,
|
|
reservations,
|
|
reservationsLoading,
|
|
refreshReservations,
|
|
updateReservationStatus,
|
|
deleteReservation,
|
|
}}
|
|
>
|
|
{children}
|
|
</AdminContext.Provider>
|
|
);
|
|
};
|
|
|
|
export const useAdmin = () => {
|
|
const ctx = useContext(AdminContext);
|
|
if (!ctx) throw new Error("useAdmin must be used within AdminProvider");
|
|
return ctx;
|
|
};
|