import { Alert, Badge, Button, Card, Group, Stack, Text, } from "@mantine/core"; import { IconAlertTriangle, IconLogin2, IconLogout2 } from "@tabler/icons-react"; import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query"; import { useEffect, useState } from "react"; import { apiErrorMessage } from "@/auth/http"; import { checkIn, checkOut, getToday } from "../api"; import { AttendanceStatusBadge, formatMinutes, formatTime } from "./AttendanceStatusBadge"; /** * Clock in and out. * * The button offered is derived from today's record rather than from a local * flag: a page reload, a second tab or a punch from the door reader must all * lead to the same state, and a client-side "have I clocked in?" would disagree * with the server the moment any of those happened. */ export function ClockWidget() { const queryClient = useQueryClient(); const [now, setNow] = useState(new Date()); const [error, setError] = useState(null); useEffect(() => { const timer = window.setInterval(() => setNow(new Date()), 30_000); return () => window.clearInterval(timer); }, []); const today = useQuery({ queryKey: ["attendance-today"], queryFn: getToday }); const invalidate = () => { queryClient.invalidateQueries({ queryKey: ["attendance-today"] }); queryClient.invalidateQueries({ queryKey: ["my-attendance"] }); queryClient.invalidateQueries({ queryKey: ["attendance-summary"] }); }; const punchIn = useMutation({ mutationFn: () => checkIn(), onSuccess: () => { invalidate(); setError(null); }, onError: (err) => setError(apiErrorMessage(err)), }); const punchOut = useMutation({ mutationFn: () => checkOut(), onSuccess: () => { invalidate(); setError(null); }, onError: (err) => setError(apiErrorMessage(err)), }); const record = today.data; const clockedIn = Boolean(record?.checkIn && !record?.checkOut); const done = Boolean(record?.checkIn && record?.checkOut); return ( {now.toLocaleDateString(undefined, { weekday: "long", day: "numeric", month: "long", })} {now.toLocaleTimeString([], { hour: "2-digit", minute: "2-digit" })} {record && } {record?.isRegularized && ( corrected )} {!done && ( )} {done && ( Done for today )} {record?.checkIn && ( In {formatTime(record.checkIn)} {record.checkOut && ` · Out ${formatTime(record.checkOut)}`} )} {record?.workedMinutes ? ( {formatMinutes(record.workedMinutes)} worked {record.lateMinutes > 0 && ` · ${formatMinutes(record.lateMinutes)} late`} ) : null} {error && ( }> {error} )} ); }