update(ui) add responsive for tracking #165

Merged
zelda merged 1 commits from zelda.checkin-for-au into master 2026-05-18 12:51:16 +10:00
15 changed files with 170 additions and 127 deletions

View File

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

View File

@ -1,24 +1,27 @@
import { Button } from "@/components/ui/button";
import { Tabs, TabsList, TabsTrigger } from "@/components/ui/tabs";
import { cn } from "@/lib/utils";
import { Users } from "lucide-react";
import { Users, X } from "lucide-react";
import TabUsers from "./tab-users";
export default function LeftSlidebar({
isSidebarOpen,
onClose,
}: {
isSidebarOpen: boolean;
onClose?: () => void;
}) {
return (
<div
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"
)}
>
<div className="h-full flex flex-col">
<Tabs defaultValue="users" className="flex-1 flex flex-col">
<div className="border-b p-4">
<TabsList className="grid w-full grid-cols-1">
<Tabs defaultValue="users" className="flex-1 flex flex-col min-h-0">
<div className="border-b p-3 sm:p-4 flex items-center gap-2">
<TabsList className="grid flex-1 grid-cols-1">
<TabsTrigger
value="users"
className="flex items-center gap-1 text-xs"
@ -27,6 +30,14 @@ export default function LeftSlidebar({
User
</TabsTrigger>
</TabsList>
<Button
variant="ghost"
size="icon"
className="lg:hidden size-8 shrink-0"
onClick={onClose}
>
<X className="size-4" />
</Button>
</div>
<TabUsers value="users" />

View File

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

View File

@ -160,7 +160,7 @@ export function UserModal({
>
<DialogTrigger>{children}</DialogTrigger>
<DialogContent className="sm:max-w-[425px]">
<DialogContent className="w-[95vw] max-w-[425px] sm:max-w-[425px]">
<DialogHeader>
<DialogTitle>
{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 { cn } from "@/lib/utils";
import { ClipboardList } from "lucide-react";
import { ClipboardList, X } from "lucide-react";
import TabLogs from "./tab-log";
export default function RightSlidebar({
isSidebarOpen,
onClose,
}: {
isSidebarOpen: boolean;
onClose?: () => void;
}) {
return (
<div
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"
)}
>
@ -19,10 +22,18 @@ export default function RightSlidebar({
<Tabs
value={"logs"}
defaultValue="features"
className="flex-1 flex flex-col"
className="flex-1 flex flex-col min-h-0"
>
<div className="border-b p-4">
<TabsList className="grid w-full grid-cols-1">
<div className="border-b p-3 sm:p-4 flex items-center gap-2">
<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
value="logs"
className="flex items-center gap-1 text-xs"

View File

@ -140,11 +140,11 @@ export default function TabFeatures() {
}, [captureAndCheck, loading]);
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
onClick={captureAndCheck}
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 && (
<>
@ -160,7 +160,7 @@ export default function TabFeatures() {
onClick={toggleAutoCheck}
variant={isAutoChecking ? "destructive" : "outline"}
className={cn(
"w-full font-semibold",
"w-full font-semibold text-xs sm:text-sm",
isAutoChecking && "animate-pulse"
)}
>
@ -183,7 +183,9 @@ export default function TabFeatures() {
<Button
disabled={isAutoChecking || checkPoinLoading}
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 && (
<>

View File

@ -14,7 +14,7 @@ export default function Register() {
<Button
onClick={() => {}}
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" />
Tạo User Checking

View File

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

View File

@ -70,19 +70,19 @@ export default function TabUsers({ value }: { value: string }) {
}, [refreshUsers]);
return (
<TabsContent value={value} className="">
<div className="flex flex-col gap-2 flex-1 p-4 space-y-2 overflow-y-auto h-[90vh]">
<TabsContent value={value} className="flex-1 min-h-0 overflow-hidden">
<div className="flex flex-col gap-2 p-3 sm:p-4 space-y-2 overflow-y-auto h-full">
{users.map((user) => (
<div
key={user.id}
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 &&
"bg-blue-50 dark:bg-blue-950 border-blue-500 shadow-md"
)}
onClick={() => toggle(user)}
>
<Avatar className="size-12 shrink-0">
<Avatar className="size-10 sm:size-12 shrink-0">
<AvatarImage
src={
`https://ms.prology.net/image/storage/${user?.avatar}` || ""
@ -92,10 +92,10 @@ export default function TabUsers({ value }: { value: string }) {
</Avatar>
<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}
</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}
</p>
</div>

View File

@ -57,17 +57,31 @@ export default function Main() {
return (
<div className="min-h-screen bg-white">
<div className="flex h-screen">
<LeftSlidebar isSidebarOpen={isLeftSidebarOpen} />
<div className="flex h-screen relative">
<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
className={cn(
"flex-1 transition-all duration-300 ease-in-out",
isLeftSidebarOpen && "ml-96",
isSidebarOpen && "mr-96",
"flex-1 transition-all duration-300 ease-in-out min-w-0",
isLeftSidebarOpen && "lg:ml-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 */}
<Card className="flex-1 overflow-hidden bg-black relative group">
<video
@ -122,7 +136,7 @@ export default function Main() {
onClick={() => setIsLeftSidebarOpen(!isLeftSidebarOpen)}
variant="outline"
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 ? (
<ChevronLeft className="size-4" />
@ -135,7 +149,7 @@ export default function Main() {
onClick={() => setIsSidebarOpen(!isSidebarOpen)}
variant="outline"
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 ? (
<ChevronRight className="size-4" />
@ -149,7 +163,10 @@ export default function Main() {
</div>
</div>
<RightSlidebar isSidebarOpen={isSidebarOpen} />
<RightSlidebar
isSidebarOpen={isSidebarOpen}
onClose={() => setIsSidebarOpen(false)}
/>
{/* Hidden Canvas for Capture */}
<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
type="module"
crossorigin
src="/au/checkin/static/assets/index-BKsPQIjb.js"
src="/au/checkin/static/assets/index-CEkaj5WK.js"
></script>
<link
rel="stylesheet"
crossorigin
href="/au/checkin/static/assets/index-BTDrLopT.css"
href="/au/checkin/static/assets/index-vMXWhc_A.css"
/>
</head>
<body>