update(ui) add responsive for tracking
This commit is contained in:
parent
2675a60ffc
commit
ab7ccbe194
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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" />
|
||||
|
|
|
|||
|
|
@ -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">
|
||||
|
|
|
|||
|
|
@ -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"}
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
|
|
|
|||
|
|
@ -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 && (
|
||||
<>
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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" />
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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
|
|
@ -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>
|
||||
|
|
|
|||
Loading…
Reference in New Issue