mirror of
http://88.130.71.182:3000/BlitTech/badoHair_fe.git
synced 2026-06-13 08:58:31 +00:00
Update May 21 by Elvis
This commit is contained in:
@@ -1,6 +1,6 @@
|
|||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import { useState, useEffect, useCallback } from "react";
|
import { useState, useEffect, useCallback, startTransition } from "react";
|
||||||
import { Card } from "@/components/ui/card";
|
import { Card } from "@/components/ui/card";
|
||||||
import { Badge } from "@/components/ui/badge";
|
import { Badge } from "@/components/ui/badge";
|
||||||
import { Button } from "@/components/ui/button";
|
import { Button } from "@/components/ui/button";
|
||||||
@@ -34,7 +34,7 @@ export default function AdminClients() {
|
|||||||
}
|
}
|
||||||
}, [query]);
|
}, [query]);
|
||||||
|
|
||||||
useEffect(() => { load(); }, [load]);
|
useEffect(() => { startTransition(() => load()); }, [load]);
|
||||||
|
|
||||||
const handleSearch = (e: React.FormEvent) => {
|
const handleSearch = (e: React.FormEvent) => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import { useState, useEffect, useCallback } from "react";
|
import { useState, useEffect, useCallback, startTransition } from "react";
|
||||||
import { Card } from "@/components/ui/card";
|
import { Card } from "@/components/ui/card";
|
||||||
import { Badge } from "@/components/ui/badge";
|
import { Badge } from "@/components/ui/badge";
|
||||||
import { Button } from "@/components/ui/button";
|
import { Button } from "@/components/ui/button";
|
||||||
@@ -56,7 +56,7 @@ export default function AdminCommandes() {
|
|||||||
}
|
}
|
||||||
}, [filter]);
|
}, [filter]);
|
||||||
|
|
||||||
useEffect(() => { load(); }, [load]);
|
useEffect(() => { startTransition(() => load()); }, [load]);
|
||||||
|
|
||||||
const handleStatus = async (id: string, status: string) => {
|
const handleStatus = async (id: string, status: string) => {
|
||||||
setUpdating(id);
|
setUpdating(id);
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import React, { useEffect, useState } from "react";
|
import React, { useEffect, useState, startTransition } from "react";
|
||||||
import { Package, CalendarCheck, Clock, TrendingUp, ShoppingBag, Users, AlertTriangle, Euro } from "lucide-react";
|
import { Package, CalendarCheck, Clock, TrendingUp, ShoppingBag, Users, AlertTriangle, Euro } from "lucide-react";
|
||||||
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
|
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
|
||||||
import { Badge } from "@/components/ui/badge";
|
import { Badge } from "@/components/ui/badge";
|
||||||
@@ -8,6 +8,41 @@ import { useAdmin } from "@/contexts/AdminContext";
|
|||||||
import { useLanguage } from "@/contexts/LanguageContext";
|
import { useLanguage } from "@/contexts/LanguageContext";
|
||||||
import { getDashboardStats, DashboardStats } from "@/lib/api/admin";
|
import { getDashboardStats, DashboardStats } from "@/lib/api/admin";
|
||||||
|
|
||||||
|
function Skeleton() {
|
||||||
|
return (
|
||||||
|
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-4 gap-4">
|
||||||
|
{Array.from({ length: 4 }).map((_, i) => (
|
||||||
|
<Card key={i}>
|
||||||
|
<CardContent className="p-5">
|
||||||
|
<div className="h-4 bg-muted animate-pulse rounded w-2/3 mb-3" />
|
||||||
|
<div className="h-7 bg-muted animate-pulse rounded w-1/3" />
|
||||||
|
</CardContent>
|
||||||
|
</Card>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function StatGrid({ cards }: { cards: { label: string; value: string | number; icon: React.ElementType; color: string; bg: string }[] }) {
|
||||||
|
return (
|
||||||
|
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-4 gap-4">
|
||||||
|
{cards.map((s) => (
|
||||||
|
<Card key={s.label}>
|
||||||
|
<CardContent className="p-5 flex items-center justify-between">
|
||||||
|
<div>
|
||||||
|
<p className="text-sm text-muted-foreground">{s.label}</p>
|
||||||
|
<p className="text-2xl font-semibold mt-1">{s.value}</p>
|
||||||
|
</div>
|
||||||
|
<div className={`h-10 w-10 rounded-full ${s.bg} flex items-center justify-center`}>
|
||||||
|
<s.icon className={`h-5 w-5 ${s.color}`} />
|
||||||
|
</div>
|
||||||
|
</CardContent>
|
||||||
|
</Card>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
export default function AdminOverview() {
|
export default function AdminOverview() {
|
||||||
const { reservations } = useAdmin();
|
const { reservations } = useAdmin();
|
||||||
const { t, locale } = useLanguage();
|
const { t, locale } = useLanguage();
|
||||||
@@ -40,37 +75,6 @@ export default function AdminOverview() {
|
|||||||
]
|
]
|
||||||
: [];
|
: [];
|
||||||
|
|
||||||
const Skeleton = () => (
|
|
||||||
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-4 gap-4">
|
|
||||||
{Array.from({ length: 4 }).map((_, i) => (
|
|
||||||
<Card key={i}>
|
|
||||||
<CardContent className="p-5">
|
|
||||||
<div className="h-4 bg-muted animate-pulse rounded w-2/3 mb-3" />
|
|
||||||
<div className="h-7 bg-muted animate-pulse rounded w-1/3" />
|
|
||||||
</CardContent>
|
|
||||||
</Card>
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
|
|
||||||
const StatGrid = ({ cards }: { cards: { label: string; value: string | number; icon: React.ElementType; color: string; bg: string }[] }) => (
|
|
||||||
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-4 gap-4">
|
|
||||||
{cards.map((s) => (
|
|
||||||
<Card key={s.label}>
|
|
||||||
<CardContent className="p-5 flex items-center justify-between">
|
|
||||||
<div>
|
|
||||||
<p className="text-sm text-muted-foreground">{s.label}</p>
|
|
||||||
<p className="text-2xl font-semibold mt-1">{s.value}</p>
|
|
||||||
</div>
|
|
||||||
<div className={`h-10 w-10 rounded-full ${s.bg} flex items-center justify-center`}>
|
|
||||||
<s.icon className={`h-5 w-5 ${s.color}`} />
|
|
||||||
</div>
|
|
||||||
</CardContent>
|
|
||||||
</Card>
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="space-y-8">
|
<div className="space-y-8">
|
||||||
<div>
|
<div>
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import { useState, useEffect, useCallback } from "react";
|
import { useState, useEffect, useCallback, startTransition } from "react";
|
||||||
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
|
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
|
||||||
import { Button } from "@/components/ui/button";
|
import { Button } from "@/components/ui/button";
|
||||||
import { Input } from "@/components/ui/input";
|
import { Input } from "@/components/ui/input";
|
||||||
@@ -263,7 +263,7 @@ function CalendarTab() {
|
|||||||
.finally(() => setLoadingSlots(false));
|
.finally(() => setLoadingSlots(false));
|
||||||
}, [year, month]);
|
}, [year, month]);
|
||||||
|
|
||||||
useEffect(() => { loadSlots(); }, [loadSlots]);
|
useEffect(() => { startTransition(() => loadSlots()); }, [loadSlots]);
|
||||||
|
|
||||||
const prevMonth = () => {
|
const prevMonth = () => {
|
||||||
if (month === 0) { setYear((y) => y - 1); setMonth(11); }
|
if (month === 0) { setYear((y) => y - 1); setMonth(11); }
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import { useState, useEffect } from "react";
|
import { useState, useEffect, startTransition } from "react";
|
||||||
import { Card } from "@/components/ui/card";
|
import { Card } from "@/components/ui/card";
|
||||||
import { Button } from "@/components/ui/button";
|
import { Button } from "@/components/ui/button";
|
||||||
import { Input } from "@/components/ui/input";
|
import { Input } from "@/components/ui/input";
|
||||||
@@ -54,7 +54,7 @@ export default function AdminServices() {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
useEffect(() => { load(); }, []);
|
useEffect(() => { startTransition(() => load()); }, []);
|
||||||
|
|
||||||
const openCreate = () => {
|
const openCreate = () => {
|
||||||
setEditing(null);
|
setEditing(null);
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import { Suspense, useState, useEffect } from "react";
|
import { Suspense, useState, useEffect, startTransition } from "react";
|
||||||
import { useLanguage } from "@/contexts/LanguageContext";
|
import { useLanguage } from "@/contexts/LanguageContext";
|
||||||
import { useSearchParams, useRouter } from "next/navigation";
|
import { useSearchParams, useRouter } from "next/navigation";
|
||||||
import { categories } from "@/data/products";
|
import { categories } from "@/data/products";
|
||||||
@@ -21,7 +21,7 @@ function ShopContent() {
|
|||||||
};
|
};
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
setLoading(true);
|
startTransition(() => setLoading(true));
|
||||||
listProducts({
|
listProducts({
|
||||||
per_page: 100,
|
per_page: 100,
|
||||||
category: selectedCategory === "all" ? undefined : selectedCategory,
|
category: selectedCategory === "all" ? undefined : selectedCategory,
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import { useState, useEffect } from "react";
|
import { useState, useEffect, startTransition } from "react";
|
||||||
import { useRouter } from "next/navigation";
|
import { useRouter } from "next/navigation";
|
||||||
import { useAuth } from "@/contexts/AuthContext";
|
import { useAuth } from "@/contexts/AuthContext";
|
||||||
import { useLanguage } from "@/contexts/LanguageContext";
|
import { useLanguage } from "@/contexts/LanguageContext";
|
||||||
@@ -40,8 +40,10 @@ export default function MonCompte() {
|
|||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (user) {
|
if (user) {
|
||||||
|
startTransition(() => {
|
||||||
setName(user.full_name ?? "");
|
setName(user.full_name ?? "");
|
||||||
setPhone(user.phone ?? "");
|
setPhone(user.phone ?? "");
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}, [user]);
|
}, [user]);
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import { useState, useEffect } from "react";
|
import { useState, useEffect, startTransition } from "react";
|
||||||
import { Star, ChevronLeft, Check } from "lucide-react";
|
import { Star, ChevronLeft, Check } from "lucide-react";
|
||||||
import { Button } from "@/components/ui/button";
|
import { Button } from "@/components/ui/button";
|
||||||
import { useLanguage } from "@/contexts/LanguageContext";
|
import { useLanguage } from "@/contexts/LanguageContext";
|
||||||
@@ -26,7 +26,7 @@ export default function ProductDetail() {
|
|||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!id) return;
|
if (!id) return;
|
||||||
setLoading(true);
|
startTransition(() => setLoading(true));
|
||||||
getProduct(id as string)
|
getProduct(id as string)
|
||||||
.then((p) => {
|
.then((p) => {
|
||||||
setProduct(p);
|
setProduct(p);
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import { useState, useEffect } from "react";
|
import { useState, useEffect, startTransition } from "react";
|
||||||
import { Calendar } from "@/components/ui/calendar";
|
import { Calendar } from "@/components/ui/calendar";
|
||||||
import { Button } from "@/components/ui/button";
|
import { Button } from "@/components/ui/button";
|
||||||
import { Input } from "@/components/ui/input";
|
import { Input } from "@/components/ui/input";
|
||||||
@@ -43,16 +43,20 @@ export default function Booking() {
|
|||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (user) {
|
if (user) {
|
||||||
|
startTransition(() => {
|
||||||
setName(user.full_name ?? "");
|
setName(user.full_name ?? "");
|
||||||
setEmail(user.email ?? "");
|
setEmail(user.email ?? "");
|
||||||
setPhone(user.phone ?? "");
|
setPhone(user.phone ?? "");
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}, [user]);
|
}, [user]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!selectedDate) return;
|
if (!selectedDate) return;
|
||||||
|
startTransition(() => {
|
||||||
setSlotsLoading(true);
|
setSlotsLoading(true);
|
||||||
setSelectedSlot(null);
|
setSelectedSlot(null);
|
||||||
|
});
|
||||||
const dateStr = toDateStr(selectedDate);
|
const dateStr = toDateStr(selectedDate);
|
||||||
getAvailableSlots(dateStr, dateStr)
|
getAvailableSlots(dateStr, dateStr)
|
||||||
.then(setSlots)
|
.then(setSlots)
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import React, { useContext, useState, useEffect, ReactNode, createContext } from "react";
|
import React, { useContext, useState, useEffect, startTransition, ReactNode, createContext } from "react";
|
||||||
import { useAuth } from "@/contexts/AuthContext";
|
import { useAuth } from "@/contexts/AuthContext";
|
||||||
import * as productsApi from "@/lib/api/products";
|
import * as productsApi from "@/lib/api/products";
|
||||||
import * as bookingsApi from "@/lib/api/bookings";
|
import * as bookingsApi from "@/lib/api/bookings";
|
||||||
@@ -82,8 +82,10 @@ export const AdminProvider = ({ children }: { children: ReactNode }) => {
|
|||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (isAdmin) {
|
if (isAdmin) {
|
||||||
|
startTransition(() => {
|
||||||
refreshProducts();
|
refreshProducts();
|
||||||
refreshReservations();
|
refreshReservations();
|
||||||
|
});
|
||||||
}
|
}
|
||||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
}, [isAdmin]);
|
}, [isAdmin]);
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import React, { createContext, useContext, useState, useEffect, ReactNode } from "react";
|
import React, { createContext, useContext, useState, useEffect, startTransition, ReactNode } from "react";
|
||||||
import { getToken } from "@/lib/api";
|
import { getToken } from "@/lib/api";
|
||||||
import * as authApi from "@/lib/api/auth";
|
import * as authApi from "@/lib/api/auth";
|
||||||
|
|
||||||
@@ -24,7 +24,7 @@ export const AuthProvider = ({ children }: { children: ReactNode }) => {
|
|||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const token = getToken();
|
const token = getToken();
|
||||||
if (!token) {
|
if (!token) {
|
||||||
setIsLoading(false);
|
startTransition(() => setIsLoading(false));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
authApi.getMe()
|
authApi.getMe()
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ export function useIsMobile() {
|
|||||||
setIsMobile(window.innerWidth < MOBILE_BREAKPOINT)
|
setIsMobile(window.innerWidth < MOBILE_BREAKPOINT)
|
||||||
}
|
}
|
||||||
mql.addEventListener("change", onChange)
|
mql.addEventListener("change", onChange)
|
||||||
setIsMobile(window.innerWidth < MOBILE_BREAKPOINT)
|
React.startTransition(() => setIsMobile(window.innerWidth < MOBILE_BREAKPOINT))
|
||||||
return () => mql.removeEventListener("change", onChange)
|
return () => mql.removeEventListener("change", onChange)
|
||||||
}, [])
|
}, [])
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user