ManagementSystem/TrackingToolWebAU/client/src/pages/main/components/tab-features/index.tsx

202 lines
5.3 KiB
TypeScript

/* eslint-disable react-hooks/exhaustive-deps */
/* eslint-disable @typescript-eslint/no-explicit-any */
import { checkingApi } from "@/api/checking-api";
import { Button } from "@/components/ui/button";
import { speak } from "@/lib/speak";
import { capture, formatTime } from "@/lib/utils";
import useAppStore from "@/stores/use-app-store";
import useUserStore from "@/stores/use-user-store";
import type { AxiosError } from "axios";
import { Camera, Image, Loader } from "lucide-react";
import { useCallback, useEffect, useRef, useState } from "react";
import { toast } from "sonner";
import Register from "./register";
export default function TabFeatures({ inline = false }: { inline?: boolean }) {
const timeoutRef = useRef<any>(null);
const { canvasRef, videoRef } = useAppStore();
const { currentUser, setCurrentUser } = useUserStore();
const { setRefreshLog } = useAppStore();
const [loading, setLoading] = useState(false);
const [checkPoinLoading, setCheckPoinLoading] = useState(false);
const createCheckpoint = async () => {
if (!currentUser) {
toast.warning("Vui lòng chọn user để tạo checkpoint");
return;
}
try {
setCheckPoinLoading(true);
const file = await capture(videoRef, canvasRef);
const { data } = await checkingApi.register({ user: currentUser, file });
if (!data) {
toast.error(
(data as any)?.message ||
"Error In Checkpoint: " + JSON.stringify(data),
);
return;
}
toast.success(data?.message || "Tạo checkpoint thành công");
} catch (error) {
const data = error as AxiosError;
toast.error(
(data.response?.data as any)?.message ||
"Error In Checkpoint: " + JSON.stringify(data),
);
} finally {
setCheckPoinLoading(false);
}
};
const captureAndCheck = useCallback(async () => {
try {
setLoading(true);
const file = await capture(videoRef, canvasRef);
const { data } = await checkingApi.checkin({ file });
if (!data || !data?.status) {
toast.error(
(data as any)?.message ||
"Error In Checking: " + JSON.stringify(data),
);
return;
}
const message =
(data as any)?.message ||
`Checking thành công lúc: ${formatTime(new Date().toLocaleString())}`;
toast.success(message);
speak({ type: data?.status_type });
setRefreshLog(true);
} catch (error) {
const data = error as AxiosError;
const message =
(data.response?.data as any)?.message ||
"Error In Checking: " + JSON.stringify(data);
if ((message as string).includes("No face detected")) return;
toast.error(message);
} finally {
setLoading(false);
}
}, [canvasRef, setCurrentUser, videoRef]);
useEffect(() => {
return () => {
if (timeoutRef.current) clearTimeout(timeoutRef.current);
};
}, []);
useEffect(() => {
const down = (e: KeyboardEvent) => {
if (e.code === "Space") {
// ← cách đúng nhất để detect phím cách
e.preventDefault(); // nếu không muốn scroll
if (loading) return;
captureAndCheck();
}
};
window.addEventListener("keydown", down);
return () => {
window.removeEventListener("keydown", down);
};
}, [captureAndCheck, loading]);
if (inline) {
return (
<div className="grid grid-cols-2 gap-2 w-full">
<Button
onClick={captureAndCheck}
className="w-full bg-blue-600 hover:bg-blue-700 text-white font-semibold text-xs"
>
{!loading ? (
<>
<Camera className="mr-1 size-4" />
Điểm Danh Ngay
</>
) : (
<Loader className="size-4 animate-spin" />
)}
</Button>
{currentUser ? (
<Button
disabled={checkPoinLoading}
onClick={createCheckpoint}
className="w-full font-semibold text-xs"
>
{!checkPoinLoading ? (
<>
<Image className="mr-1 size-4" />
Tạo Check Point
</>
) : (
<Loader className="size-4 animate-spin" />
)}
</Button>
) : (
<Register />
)}
</div>
);
}
return (
<div className="absolute bottom-4 sm:bottom-6 lg:bottom-10 px-3 sm:px-4 right-0 left-0 grid grid-cols-2 gap-2 sm:gap-3 lg:gap-4 max-w-3xl mx-auto">
<Button
onClick={captureAndCheck}
className="w-full bg-blue-600 hover:bg-blue-700 text-white font-semibold text-xs sm:text-sm"
>
{!loading && (
<>
<Camera className="mr-2 size-4" />
Điểm Danh Ngay
</>
)}
{loading && <Loader className="size-4 animate-spin" />}
</Button>
{currentUser && (
<Button
disabled={checkPoinLoading}
onClick={createCheckpoint}
className="w-full font-semibold text-xs sm:text-sm"
>
{!checkPoinLoading && (
<>
<Image />
Tạo Check Point
</>
)}
{checkPoinLoading && <Loader className="size-4 animate-spin" />}
</Button>
)}
{!currentUser && <Register />}
</div>
);
}