202 lines
5.3 KiB
TypeScript
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>
|
|
);
|
|
}
|