feat: update TodayAttendanceAlert component and enhance card styling in attendance and dashboard pages

This commit is contained in:
Yoga Pangestu 2026-08-07 14:32:45 +07:00
parent 166419134f
commit e4d956843c
5 changed files with 72 additions and 90 deletions

View File

@ -1,6 +1,6 @@
import { CalendarCheck, CheckCircle2, Clock, LogIn, LogOut } from 'lucide-react';
import { Alert, AlertDescription, AlertTitle } from '@/components/ui/alert'; import { Alert, AlertDescription, AlertTitle } from '@/components/ui/alert';
import { Button } from '@/components/ui/button'; import { Button } from '@/components/ui/button';
import { CalendarCheck, CheckCircle2, Clock, LogIn, LogOut } from 'lucide-react';
type TodayAttendance = { type TodayAttendance = {
id: number; id: number;
@ -100,12 +100,7 @@ export function TodayAttendanceAlert({
</div> </div>
{showButtons && canCheckIn && !isOnLeave && ( {showButtons && canCheckIn && !isOnLeave && (
<div className="flex items-center gap-2"> <div className="flex flex-wrap items-center gap-2">
{locationLoading && (
<span className="text-xs text-muted-foreground">
Mendapatkan lokasi...
</span>
)}
<Button <Button
size="sm" size="sm"
onClick={onCheckIn} onClick={onCheckIn}

View File

@ -20,7 +20,7 @@ export function AppShell({ children, variant = 'sidebar' }: Props) {
return ( return (
<SidebarProvider <SidebarProvider
defaultOpen={isOpen} defaultOpen={isOpen}
className="h-screen overflow-hidden" className="h-screen"
> >
{children} {children}
</SidebarProvider> </SidebarProvider>

View File

@ -2,17 +2,12 @@ import * as React from "react"
import { cn } from "@/lib/utils" import { cn } from "@/lib/utils"
function Card({ function Card({ className, ...props }: React.ComponentProps<"div">) {
className,
size = "default",
...props
}: React.ComponentProps<"div"> & { size?: "default" | "sm" }) {
return ( return (
<div <div
data-slot="card" data-slot="card"
data-size={size}
className={cn( className={cn(
"group/card flex flex-col gap-(--card-spacing) overflow-hidden rounded-lg bg-card py-(--card-spacing) text-xs/relaxed text-card-foreground ring-1 ring-foreground/10 [--card-spacing:--spacing(4)] has-[>img:first-child]:pt-0 data-[size=sm]:[--card-spacing:--spacing(3)] *:[img:first-child]:rounded-t-lg *:[img:last-child]:rounded-b-lg", "flex flex-col gap-6 rounded-xl border bg-card py-6 text-card-foreground shadow-sm",
className className
)} )}
{...props} {...props}
@ -25,7 +20,7 @@ function CardHeader({ className, ...props }: React.ComponentProps<"div">) {
<div <div
data-slot="card-header" data-slot="card-header"
className={cn( className={cn(
"group/card-header @container/card-header grid auto-rows-min items-start gap-1 rounded-t-lg px-(--card-spacing) has-data-[slot=card-action]:grid-cols-[1fr_auto] has-data-[slot=card-description]:grid-rows-[auto_auto] [.border-b]:pb-(--card-spacing)", "@container/card-header grid auto-rows-min grid-rows-[auto_auto] items-start gap-2 px-6 has-data-[slot=card-action]:grid-cols-[1fr_auto] [.border-b]:pb-6",
className className
)} )}
{...props} {...props}
@ -37,7 +32,7 @@ function CardTitle({ className, ...props }: React.ComponentProps<"div">) {
return ( return (
<div <div
data-slot="card-title" data-slot="card-title"
className={cn("font-heading text-sm font-medium", className)} className={cn("leading-none font-semibold", className)}
{...props} {...props}
/> />
) )
@ -47,7 +42,7 @@ function CardDescription({ className, ...props }: React.ComponentProps<"div">) {
return ( return (
<div <div
data-slot="card-description" data-slot="card-description"
className={cn("text-xs/relaxed text-muted-foreground", className)} className={cn("text-sm text-muted-foreground", className)}
{...props} {...props}
/> />
) )
@ -70,7 +65,7 @@ function CardContent({ className, ...props }: React.ComponentProps<"div">) {
return ( return (
<div <div
data-slot="card-content" data-slot="card-content"
className={cn("px-(--card-spacing)", className)} className={cn("px-6", className)}
{...props} {...props}
/> />
) )
@ -80,10 +75,7 @@ function CardFooter({ className, ...props }: React.ComponentProps<"div">) {
return ( return (
<div <div
data-slot="card-footer" data-slot="card-footer"
className={cn( className={cn("flex items-center px-6 [.border-t]:pt-6", className)}
"flex items-center rounded-b-lg px-(--card-spacing) [.border-t]:pt-(--card-spacing)",
className
)}
{...props} {...props}
/> />
) )

View File

@ -1,3 +1,19 @@
import { TodayAttendanceAlert } from '@/components/card/today-attendance-alert';
import { CameraCapture, LocationMap } from '@/components/inputs';
import { Badge } from '@/components/ui/badge';
import { Button } from '@/components/ui/button';
import { Card, CardContent } from '@/components/ui/card';
import {
Dialog,
DialogContent,
DialogHeader,
DialogTitle,
} from '@/components/ui/dialog';
import {
index as attendanceIndex,
store,
update,
} from '@/routes/admin/hr/attendances';
import { Head, router } from '@inertiajs/react'; import { Head, router } from '@inertiajs/react';
import { addMonths, format, subMonths } from 'date-fns'; import { addMonths, format, subMonths } from 'date-fns';
import { id } from 'date-fns/locale'; import { id } from 'date-fns/locale';
@ -14,23 +30,6 @@ import {
} from 'lucide-react'; } from 'lucide-react';
import { useMemo, useState } from 'react'; import { useMemo, useState } from 'react';
import { toast } from 'sonner'; import { toast } from 'sonner';
import { CameraCapture } from '@/components/inputs';
import { LocationMap } from '@/components/inputs';
import { TodayAttendanceAlert } from '@/components/card/today-attendance-alert';
import { Badge } from '@/components/ui/badge';
import { Button } from '@/components/ui/button';
import { Card, CardContent } from '@/components/ui/card';
import {
Dialog,
DialogContent,
DialogHeader,
DialogTitle,
} from '@/components/ui/dialog';
import {
index as attendanceIndex,
store,
update,
} from '@/routes/admin/hr/attendances';
type Attendance = { type Attendance = {
id: number; id: number;
@ -334,7 +333,7 @@ export default function AttendanceIndex({
<> <>
<Head title="Presensi" /> <Head title="Presensi" />
<div className="flex min-h-full flex-1 flex-col gap-6 overflow-auto p-4 md:p-6"> <div className="flex h-full flex-1 flex-col gap-6 overflow-x-auto p-4 md:p-6">
<div> <div>
<h2 className="text-2xl font-semibold tracking-tight"> <h2 className="text-2xl font-semibold tracking-tight">
Presensi Presensi
@ -415,10 +414,7 @@ export default function AttendanceIndex({
</div> </div>
)} )}
<Card <Card className="py-0!">
className="overflow-hidden"
style={{ '--card-spacing': '0px' } as React.CSSProperties}
>
<div className="flex items-center justify-between border-b px-6 py-4"> <div className="flex items-center justify-between border-b px-6 py-4">
<div className="flex items-center gap-3"> <div className="flex items-center gap-3">
<div className="flex flex-col items-center overflow-hidden rounded-lg ring-1 ring-border"> <div className="flex flex-col items-center overflow-hidden rounded-lg ring-1 ring-border">
@ -471,19 +467,20 @@ export default function AttendanceIndex({
</div> </div>
</div> </div>
<div className="grid grid-cols-7 border-b"> <div className="overflow-x-auto">
{WEEKDAYS.map((day) => ( <div className="grid min-w-[700px] grid-cols-7 border-b">
<div {WEEKDAYS.map((day) => (
key={day} <div
className="flex items-center justify-center py-3 text-xs font-medium text-muted-foreground" key={day}
> className="flex items-center justify-center py-3 text-xs font-medium text-muted-foreground"
{day} >
</div> {day}
))} </div>
</div> ))}
</div>
<div className="grid grid-cols-7"> <div className="grid min-w-[700px] grid-cols-7">
{calendarDays.map((cell, idx) => { {calendarDays.map((cell, idx) => {
const dateStr = format(cell.date, 'yyyy-MM-dd'); const dateStr = format(cell.date, 'yyyy-MM-dd');
const dayAttendances = attendanceByDate.get(dateStr) ?? []; const dayAttendances = attendanceByDate.get(dateStr) ?? [];
const isSelected = isSameDay( const isSelected = isSameDay(
@ -517,13 +514,11 @@ export default function AttendanceIndex({
setDetailAttendance(dayAttendances[0]); setDetailAttendance(dayAttendances[0]);
} }
}} }}
className={`group relative flex min-h-[100px] flex-col p-2 text-left transition-colors hover:bg-muted/50 ${ className={`group relative flex min-h-[100px] flex-col p-2 text-left transition-colors hover:bg-muted/50 ${!cell.isCurrentMonth
!cell.isCurrentMonth ? 'bg-muted/30 text-muted-foreground/50'
? 'bg-muted/30 text-muted-foreground/50' : ''
: '' } ${isSelected ? 'bg-muted/50' : ''} ${!isAdmin && dayAttendances.length > 0 ? 'cursor-pointer' : ''
} ${isSelected ? 'bg-muted/50' : ''} ${ }`}
!isAdmin && dayAttendances.length > 0 ? 'cursor-pointer' : ''
}`}
style={{ style={{
borderRight: '1px solid var(--border)', borderRight: '1px solid var(--border)',
borderBottom: '1px solid var(--border)', borderBottom: '1px solid var(--border)',
@ -531,13 +526,12 @@ export default function AttendanceIndex({
> >
<div className="flex items-start justify-between"> <div className="flex items-start justify-between">
<span <span
className={`flex h-7 w-7 items-center justify-center rounded-full text-xs font-semibold transition-colors ${ className={`flex h-7 w-7 items-center justify-center rounded-full text-xs font-semibold transition-colors ${isSelected
isSelected ? 'bg-primary text-primary-foreground'
? 'bg-primary text-primary-foreground' : isTodayDate
: isTodayDate ? 'bg-muted text-foreground'
? 'bg-muted text-foreground' : 'text-foreground'
: 'text-foreground' }`}
}`}
> >
{cell.day} {cell.day}
</span> </span>
@ -635,7 +629,8 @@ export default function AttendanceIndex({
</div> </div>
</div> </div>
); );
})} })}
</div>
</div> </div>
</Card> </Card>
</div> </div>

View File

@ -1,19 +1,6 @@
import { Head, router, usePage } from '@inertiajs/react';
import {
Clock,
Moon,
Sunrise,
Sun,
TrendingDown,
TrendingUp,
UserCheck,
} from 'lucide-react';
import { useEffect, useMemo, useState } from 'react';
import { toast } from 'sonner';
import { Pie, PieChart, Cell, ResponsiveContainer, Tooltip } from 'recharts';
import { CameraCapture } from '@/components/inputs';
import { TodayAttendanceAlert } from '@/components/card/today-attendance-alert';
import { StatCard } from '@/components/card/stat-card'; import { StatCard } from '@/components/card/stat-card';
import { TodayAttendanceAlert } from '@/components/card/today-attendance-alert';
import { CameraCapture } from '@/components/inputs';
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card'; import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card';
import { import {
Dialog, Dialog,
@ -22,6 +9,19 @@ import {
import { useCan } from '@/hooks/use-can'; import { useCan } from '@/hooks/use-can';
import { formatRupiah } from '@/lib/rupiah'; import { formatRupiah } from '@/lib/rupiah';
import { store, update } from '@/routes/admin/hr/attendances'; import { store, update } from '@/routes/admin/hr/attendances';
import { Head, router, usePage } from '@inertiajs/react';
import {
Clock,
Moon,
Sun,
Sunrise,
TrendingDown,
TrendingUp,
UserCheck,
} from 'lucide-react';
import { useEffect, useMemo, useState } from 'react';
import { Cell, Pie, PieChart, ResponsiveContainer, Tooltip } from 'recharts';
import { toast } from 'sonner';
type TodayAttendance = { type TodayAttendance = {
id: number; id: number;
@ -297,8 +297,8 @@ export default function Dashboard({
<> <>
<Head title="Dasbor" /> <Head title="Dasbor" />
<div className="flex h-full flex-1 flex-col gap-6 overflow-x-auto p-4 md:p-6"> <div className="flex h-full flex-1 flex-col gap-6 p-4 md:p-6">
<Card className="relative overflow-hidden"> <Card className="relative overflow-visible">
<div className="absolute inset-0 bg-linear-to-br from-primary/5 to-background" /> <div className="absolute inset-0 bg-linear-to-br from-primary/5 to-background" />
<CardHeader className="relative"> <CardHeader className="relative">
<div className="flex items-center gap-3"> <div className="flex items-center gap-3">
@ -306,7 +306,7 @@ export default function Dashboard({
<GreetingIcon className="size-6 text-primary" /> <GreetingIcon className="size-6 text-primary" />
</div> </div>
<div> <div>
<CardTitle className="text-2xl font-bold"> <CardTitle className="text-2xl font-bold">
{greeting.text}, {auth.user?.username}! {greeting.text}, {auth.user?.username}!
</CardTitle> </CardTitle>
<CardDescription className="mt-1"> <CardDescription className="mt-1">