update(ui) add responsive for tracking

This commit is contained in:
Admin 2026-05-18 09:49:56 +07:00
parent 2675a60ffc
commit ab7ccbe194
15 changed files with 170 additions and 127 deletions

View File

@ -33,24 +33,26 @@ export default function CountDown({
}, [count, running, onCountdowned]); }, [count, running, onCountdowned]);
return ( return (
<div className="absolute inset-0 flex flex-col items-center justify-center bg-transparent"> <div className="absolute inset-0 flex flex-col items-center justify-center bg-transparent px-4">
<div className="flex flex-col items-center gap-8"> <div className="flex flex-col items-center gap-4 sm:gap-6 lg:gap-8">
{/* Instruction text */} {/* Instruction text */}
<div className="text-center space-y-3"> <div className="text-center space-y-1 sm:space-y-3 text-white">
<p className="text-lg font-medium">Chuẩn bị sẵn sàng</p> <p className="text-base sm:text-lg font-medium">Chuẩn bị sẵn sàng</p>
<p className="text-base">Vui lòng nhìn thẳng vào camera</p> <p className="text-sm sm:text-base">Vui lòng nhìn thẳng vào camera</p>
</div> </div>
{/* Timer circle */} {/* Timer circle */}
<div className="relative"> <div className="relative">
<div className="w-40 h-40 bg-white rounded-full flex items-center justify-center shadow-2xl"> <div className="w-24 h-24 sm:w-32 sm:h-32 lg:w-40 lg:h-40 bg-white rounded-full flex items-center justify-center shadow-2xl">
<span className="text-8xl font-bold">{count}</span> <span className="text-5xl sm:text-6xl lg:text-8xl font-bold">
{count}
</span>
</div> </div>
</div> </div>
{/* Countdown text */} {/* Countdown text */}
<div className="text-center"> <div className="text-center text-white">
<p className="text-sm"> <p className="text-xs sm:text-sm">
{count > 0 ? `Còn ${count} giây...` : "Đã hoàn thành!"} {count > 0 ? `Còn ${count} giây...` : "Đã hoàn thành!"}
</p> </p>
</div> </div>
@ -60,7 +62,7 @@ export default function CountDown({
<Button <Button
variant="outline" variant="outline"
onClick={() => setRunning(false)} onClick={() => setRunning(false)}
className="mt-4 px-8 py-2" className="mt-2 sm:mt-4 px-6 sm:px-8 py-2"
> >
Hủy Hủy
</Button> </Button>

View File

@ -1,24 +1,27 @@
import { Button } from "@/components/ui/button";
import { Tabs, TabsList, TabsTrigger } from "@/components/ui/tabs"; import { Tabs, TabsList, TabsTrigger } from "@/components/ui/tabs";
import { cn } from "@/lib/utils"; import { cn } from "@/lib/utils";
import { Users } from "lucide-react"; import { Users, X } from "lucide-react";
import TabUsers from "./tab-users"; import TabUsers from "./tab-users";
export default function LeftSlidebar({ export default function LeftSlidebar({
isSidebarOpen, isSidebarOpen,
onClose,
}: { }: {
isSidebarOpen: boolean; isSidebarOpen: boolean;
onClose?: () => void;
}) { }) {
return ( return (
<div <div
className={cn( className={cn(
"fixed left-0 top-0 h-screen w-96 bg-white border-r border-gray-200 shadow-xl transition-transform duration-300 ease-in-out z-10", "fixed left-0 top-0 h-screen w-[85vw] max-w-sm lg:w-96 lg:max-w-none bg-white border-r border-gray-200 shadow-xl transition-transform duration-300 ease-in-out z-30",
isSidebarOpen ? "translate-x-0" : "-translate-x-full" isSidebarOpen ? "translate-x-0" : "-translate-x-full"
)} )}
> >
<div className="h-full flex flex-col"> <div className="h-full flex flex-col">
<Tabs defaultValue="users" className="flex-1 flex flex-col"> <Tabs defaultValue="users" className="flex-1 flex flex-col min-h-0">
<div className="border-b p-4"> <div className="border-b p-3 sm:p-4 flex items-center gap-2">
<TabsList className="grid w-full grid-cols-1"> <TabsList className="grid flex-1 grid-cols-1">
<TabsTrigger <TabsTrigger
value="users" value="users"
className="flex items-center gap-1 text-xs" className="flex items-center gap-1 text-xs"
@ -27,6 +30,14 @@ export default function LeftSlidebar({
User User
</TabsTrigger> </TabsTrigger>
</TabsList> </TabsList>
<Button
variant="ghost"
size="icon"
className="lg:hidden size-8 shrink-0"
onClick={onClose}
>
<X className="size-4" />
</Button>
</div> </div>
<TabUsers value="users" /> <TabUsers value="users" />

View File

@ -39,7 +39,7 @@ export function CameraNotificationModal({
return ( return (
<Dialog open={open} onOpenChange={setOpen}> <Dialog open={open} onOpenChange={setOpen}>
<DialogTrigger>{children}</DialogTrigger> <DialogTrigger>{children}</DialogTrigger>
<DialogContent className="sm:max-w-md"> <DialogContent className="w-[95vw] max-w-md sm:max-w-md">
<DialogHeader> <DialogHeader>
<div className="flex items-center justify-center mb-4"> <div className="flex items-center justify-center mb-4">
<div className="rounded-full border p-3"> <div className="rounded-full border p-3">

View File

@ -160,7 +160,7 @@ export function UserModal({
> >
<DialogTrigger>{children}</DialogTrigger> <DialogTrigger>{children}</DialogTrigger>
<DialogContent className="sm:max-w-[425px]"> <DialogContent className="w-[95vw] max-w-[425px] sm:max-w-[425px]">
<DialogHeader> <DialogHeader>
<DialogTitle> <DialogTitle>
{isEditMode ? "Cập nhật thông tin" : "Tạo người dùng mới"} {isEditMode ? "Cập nhật thông tin" : "Tạo người dùng mới"}

View File

@ -1,17 +1,20 @@
import { Button } from "@/components/ui/button";
import { Tabs, TabsList, TabsTrigger } from "@/components/ui/tabs"; import { Tabs, TabsList, TabsTrigger } from "@/components/ui/tabs";
import { cn } from "@/lib/utils"; import { cn } from "@/lib/utils";
import { ClipboardList } from "lucide-react"; import { ClipboardList, X } from "lucide-react";
import TabLogs from "./tab-log"; import TabLogs from "./tab-log";
export default function RightSlidebar({ export default function RightSlidebar({
isSidebarOpen, isSidebarOpen,
onClose,
}: { }: {
isSidebarOpen: boolean; isSidebarOpen: boolean;
onClose?: () => void;
}) { }) {
return ( return (
<div <div
className={cn( className={cn(
"fixed right-0 top-0 h-screen w-96 bg-white border-l border-gray-200 shadow-xl transition-transform duration-300 ease-in-out", "fixed right-0 top-0 h-screen w-[85vw] max-w-sm lg:w-96 lg:max-w-none bg-white border-l border-gray-200 shadow-xl transition-transform duration-300 ease-in-out z-30",
isSidebarOpen ? "translate-x-0" : "translate-x-full" isSidebarOpen ? "translate-x-0" : "translate-x-full"
)} )}
> >
@ -19,10 +22,18 @@ export default function RightSlidebar({
<Tabs <Tabs
value={"logs"} value={"logs"}
defaultValue="features" defaultValue="features"
className="flex-1 flex flex-col" className="flex-1 flex flex-col min-h-0"
> >
<div className="border-b p-4"> <div className="border-b p-3 sm:p-4 flex items-center gap-2">
<TabsList className="grid w-full grid-cols-1"> <Button
variant="ghost"
size="icon"
className="lg:hidden size-8 shrink-0"
onClick={onClose}
>
<X className="size-4" />
</Button>
<TabsList className="grid flex-1 grid-cols-1">
<TabsTrigger <TabsTrigger
value="logs" value="logs"
className="flex items-center gap-1 text-xs" className="flex items-center gap-1 text-xs"

View File

@ -140,11 +140,11 @@ export default function TabFeatures() {
}, [captureAndCheck, loading]); }, [captureAndCheck, loading]);
return ( return (
<div className="absolute bottom-10 px-4 right-0 left-0 grid grid-cols-3 gap-4"> <div className="absolute bottom-4 sm:bottom-6 lg:bottom-10 px-3 sm:px-4 right-0 left-0 grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-2 sm:gap-3 lg:gap-4 max-w-3xl mx-auto">
<Button <Button
onClick={captureAndCheck} onClick={captureAndCheck}
disabled={isAutoChecking} disabled={isAutoChecking}
className="w-full bg-blue-600 hover:bg-blue-700 text-white font-semibold" className="w-full bg-blue-600 hover:bg-blue-700 text-white font-semibold text-xs sm:text-sm"
> >
{!loading && ( {!loading && (
<> <>
@ -160,7 +160,7 @@ export default function TabFeatures() {
onClick={toggleAutoCheck} onClick={toggleAutoCheck}
variant={isAutoChecking ? "destructive" : "outline"} variant={isAutoChecking ? "destructive" : "outline"}
className={cn( className={cn(
"w-full font-semibold", "w-full font-semibold text-xs sm:text-sm",
isAutoChecking && "animate-pulse" isAutoChecking && "animate-pulse"
)} )}
> >
@ -183,7 +183,9 @@ export default function TabFeatures() {
<Button <Button
disabled={isAutoChecking || checkPoinLoading} disabled={isAutoChecking || checkPoinLoading}
onClick={createCheckpoint} onClick={createCheckpoint}
className={cn("w-full font-semibold")} className={cn(
"w-full font-semibold text-xs sm:text-sm col-span-1 sm:col-span-2 lg:col-span-1"
)}
> >
{!checkPoinLoading && ( {!checkPoinLoading && (
<> <>

View File

@ -14,7 +14,7 @@ export default function Register() {
<Button <Button
onClick={() => {}} onClick={() => {}}
disabled={isAutoChecking} disabled={isAutoChecking}
className="w-full bg-green-600 hover:bg-green-700 text-white font-semibold" className="w-full bg-green-600 hover:bg-green-700 text-white font-semibold text-xs sm:text-sm col-span-1 sm:col-span-2 lg:col-span-1"
> >
<User2 className="mr-2 size-4" /> <User2 className="mr-2 size-4" />
Tạo User Checking Tạo User Checking

View File

@ -35,8 +35,8 @@ export default function TabLogs({ value }: { value: string }) {
}, [refreshLog]); }, [refreshLog]);
return ( return (
<TabsContent value={value} className=""> <TabsContent value={value} className="flex-1 min-h-0 overflow-hidden">
<div className="flex flex-col gap-2 flex-1 p-4 space-y-2 overflow-y-auto h-[90vh]"> <div className="flex flex-col gap-2 p-3 sm:p-4 space-y-2 overflow-y-auto h-full">
{logs.length === 0 ? ( {logs.length === 0 ? (
<div className="flex flex-col items-center justify-center h-full text-gray-400"> <div className="flex flex-col items-center justify-center h-full text-gray-400">
<ClipboardList className="size-16 mb-3" /> <ClipboardList className="size-16 mb-3" />

View File

@ -70,19 +70,19 @@ export default function TabUsers({ value }: { value: string }) {
}, [refreshUsers]); }, [refreshUsers]);
return ( return (
<TabsContent value={value} className=""> <TabsContent value={value} className="flex-1 min-h-0 overflow-hidden">
<div className="flex flex-col gap-2 flex-1 p-4 space-y-2 overflow-y-auto h-[90vh]"> <div className="flex flex-col gap-2 p-3 sm:p-4 space-y-2 overflow-y-auto h-full">
{users.map((user) => ( {users.map((user) => (
<div <div
key={user.id} key={user.id}
className={cn( className={cn(
"flex items-center gap-3 p-4 rounded-xl border shadow-sm bg-card cursor-pointer transition-all duration-200 hover:shadow-md hover:scale-[1.01] select-none", "flex items-center gap-2 sm:gap-3 p-3 sm:p-4 rounded-xl border shadow-sm bg-card cursor-pointer transition-all duration-200 hover:shadow-md hover:scale-[1.01] select-none",
currentUser?.id === user.id && currentUser?.id === user.id &&
"bg-blue-50 dark:bg-blue-950 border-blue-500 shadow-md" "bg-blue-50 dark:bg-blue-950 border-blue-500 shadow-md"
)} )}
onClick={() => toggle(user)} onClick={() => toggle(user)}
> >
<Avatar className="size-12 shrink-0"> <Avatar className="size-10 sm:size-12 shrink-0">
<AvatarImage <AvatarImage
src={ src={
`https://ms.prology.net/image/storage/${user?.avatar}` || "" `https://ms.prology.net/image/storage/${user?.avatar}` || ""
@ -92,10 +92,10 @@ export default function TabUsers({ value }: { value: string }) {
</Avatar> </Avatar>
<div className="flex-1 min-w-0"> <div className="flex-1 min-w-0">
<h4 className="font-semibold text-gray-900 dark:text-gray-100 truncate"> <h4 className="font-semibold text-sm sm:text-base text-gray-900 dark:text-gray-100 truncate">
{user.name} {user.name}
</h4> </h4>
<p className="text-sm text-gray-600 dark:text-gray-400 truncate"> <p className="text-xs sm:text-sm text-gray-600 dark:text-gray-400 truncate">
{user.email} {user.email}
</p> </p>
</div> </div>

View File

@ -57,17 +57,31 @@ export default function Main() {
return ( return (
<div className="min-h-screen bg-white"> <div className="min-h-screen bg-white">
<div className="flex h-screen"> <div className="flex h-screen relative">
<LeftSlidebar isSidebarOpen={isLeftSidebarOpen} /> <LeftSlidebar
isSidebarOpen={isLeftSidebarOpen}
onClose={() => setIsLeftSidebarOpen(false)}
/>
{/* Mobile/tablet backdrop */}
{(isLeftSidebarOpen || isSidebarOpen) && (
<div
className="fixed inset-0 bg-black/40 z-20 lg:hidden"
onClick={() => {
setIsLeftSidebarOpen(false);
setIsSidebarOpen(false);
}}
/>
)}
<div <div
className={cn( className={cn(
"flex-1 transition-all duration-300 ease-in-out", "flex-1 transition-all duration-300 ease-in-out min-w-0",
isLeftSidebarOpen && "ml-96", isLeftSidebarOpen && "lg:ml-96",
isSidebarOpen && "mr-96", isSidebarOpen && "lg:mr-96"
)} )}
> >
<div className="h-full flex flex-col p-6"> <div className="h-full flex flex-col p-2 sm:p-4 lg:p-6">
{/* Video Feed */} {/* Video Feed */}
<Card className="flex-1 overflow-hidden bg-black relative group"> <Card className="flex-1 overflow-hidden bg-black relative group">
<video <video
@ -122,7 +136,7 @@ export default function Main() {
onClick={() => setIsLeftSidebarOpen(!isLeftSidebarOpen)} onClick={() => setIsLeftSidebarOpen(!isLeftSidebarOpen)}
variant="outline" variant="outline"
size="icon" size="icon"
className="absolute top-1/2 -translate-y-1/2 left-4 bg-white/90 hover:bg-white shadow-lg" className="absolute top-1/2 -translate-y-1/2 left-2 sm:left-4 bg-white/90 hover:bg-white shadow-lg z-30 size-8 sm:size-10"
> >
{isLeftSidebarOpen ? ( {isLeftSidebarOpen ? (
<ChevronLeft className="size-4" /> <ChevronLeft className="size-4" />
@ -135,7 +149,7 @@ export default function Main() {
onClick={() => setIsSidebarOpen(!isSidebarOpen)} onClick={() => setIsSidebarOpen(!isSidebarOpen)}
variant="outline" variant="outline"
size="icon" size="icon"
className="absolute top-1/2 -translate-y-1/2 right-4 bg-white/90 hover:bg-white shadow-lg" className="absolute top-1/2 -translate-y-1/2 right-2 sm:right-4 bg-white/90 hover:bg-white shadow-lg z-30 size-8 sm:size-10"
> >
{isSidebarOpen ? ( {isSidebarOpen ? (
<ChevronRight className="size-4" /> <ChevronRight className="size-4" />
@ -149,7 +163,10 @@ export default function Main() {
</div> </div>
</div> </div>
<RightSlidebar isSidebarOpen={isSidebarOpen} /> <RightSlidebar
isSidebarOpen={isSidebarOpen}
onClose={() => setIsSidebarOpen(false)}
/>
{/* Hidden Canvas for Capture */} {/* Hidden Canvas for Capture */}
<canvas ref={canvasRef} className="hidden" /> <canvas ref={canvasRef} className="hidden" />

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -8,12 +8,12 @@
<script <script
type="module" type="module"
crossorigin crossorigin
src="/au/checkin/static/assets/index-BKsPQIjb.js" src="/au/checkin/static/assets/index-CEkaj5WK.js"
></script> ></script>
<link <link
rel="stylesheet" rel="stylesheet"
crossorigin crossorigin
href="/au/checkin/static/assets/index-BTDrLopT.css" href="/au/checkin/static/assets/index-vMXWhc_A.css"
/> />
</head> </head>
<body> <body>