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

View File

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

View File

@ -2,17 +2,12 @@ import * as React from "react"
import { cn } from "@/lib/utils"
function Card({
className,
size = "default",
...props
}: React.ComponentProps<"div"> & { size?: "default" | "sm" }) {
function Card({ className, ...props }: React.ComponentProps<"div">) {
return (
<div
data-slot="card"
data-size={size}
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
)}
{...props}
@ -25,7 +20,7 @@ function CardHeader({ className, ...props }: React.ComponentProps<"div">) {
<div
data-slot="card-header"
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
)}
{...props}
@ -37,7 +32,7 @@ function CardTitle({ className, ...props }: React.ComponentProps<"div">) {
return (
<div
data-slot="card-title"
className={cn("font-heading text-sm font-medium", className)}
className={cn("leading-none font-semibold", className)}
{...props}
/>
)
@ -47,7 +42,7 @@ function CardDescription({ className, ...props }: React.ComponentProps<"div">) {
return (
<div
data-slot="card-description"
className={cn("text-xs/relaxed text-muted-foreground", className)}
className={cn("text-sm text-muted-foreground", className)}
{...props}
/>
)
@ -70,7 +65,7 @@ function CardContent({ className, ...props }: React.ComponentProps<"div">) {
return (
<div
data-slot="card-content"
className={cn("px-(--card-spacing)", className)}
className={cn("px-6", className)}
{...props}
/>
)
@ -80,10 +75,7 @@ function CardFooter({ className, ...props }: React.ComponentProps<"div">) {
return (
<div
data-slot="card-footer"
className={cn(
"flex items-center rounded-b-lg px-(--card-spacing) [.border-t]:pt-(--card-spacing)",
className
)}
className={cn("flex items-center px-6 [.border-t]:pt-6", className)}
{...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 { addMonths, format, subMonths } from 'date-fns';
import { id } from 'date-fns/locale';
@ -14,23 +30,6 @@ import {
} from 'lucide-react';
import { useMemo, useState } from 'react';
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 = {
id: number;
@ -334,7 +333,7 @@ export default function AttendanceIndex({
<>
<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>
<h2 className="text-2xl font-semibold tracking-tight">
Presensi
@ -415,10 +414,7 @@ export default function AttendanceIndex({
</div>
)}
<Card
className="overflow-hidden"
style={{ '--card-spacing': '0px' } as React.CSSProperties}
>
<Card className="py-0!">
<div className="flex items-center justify-between border-b px-6 py-4">
<div className="flex items-center gap-3">
<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 className="grid grid-cols-7 border-b">
{WEEKDAYS.map((day) => (
<div
key={day}
className="flex items-center justify-center py-3 text-xs font-medium text-muted-foreground"
>
{day}
</div>
))}
</div>
<div className="overflow-x-auto">
<div className="grid min-w-[700px] grid-cols-7 border-b">
{WEEKDAYS.map((day) => (
<div
key={day}
className="flex items-center justify-center py-3 text-xs font-medium text-muted-foreground"
>
{day}
</div>
))}
</div>
<div className="grid grid-cols-7">
{calendarDays.map((cell, idx) => {
<div className="grid min-w-[700px] grid-cols-7">
{calendarDays.map((cell, idx) => {
const dateStr = format(cell.date, 'yyyy-MM-dd');
const dayAttendances = attendanceByDate.get(dateStr) ?? [];
const isSelected = isSameDay(
@ -517,13 +514,11 @@ export default function AttendanceIndex({
setDetailAttendance(dayAttendances[0]);
}
}}
className={`group relative flex min-h-[100px] flex-col p-2 text-left transition-colors hover:bg-muted/50 ${
!cell.isCurrentMonth
? 'bg-muted/30 text-muted-foreground/50'
: ''
} ${isSelected ? 'bg-muted/50' : ''} ${
!isAdmin && dayAttendances.length > 0 ? 'cursor-pointer' : ''
}`}
className={`group relative flex min-h-[100px] flex-col p-2 text-left transition-colors hover:bg-muted/50 ${!cell.isCurrentMonth
? 'bg-muted/30 text-muted-foreground/50'
: ''
} ${isSelected ? 'bg-muted/50' : ''} ${!isAdmin && dayAttendances.length > 0 ? 'cursor-pointer' : ''
}`}
style={{
borderRight: '1px solid var(--border)',
borderBottom: '1px solid var(--border)',
@ -531,13 +526,12 @@ export default function AttendanceIndex({
>
<div className="flex items-start justify-between">
<span
className={`flex h-7 w-7 items-center justify-center rounded-full text-xs font-semibold transition-colors ${
isSelected
? 'bg-primary text-primary-foreground'
: isTodayDate
? 'bg-muted text-foreground'
: 'text-foreground'
}`}
className={`flex h-7 w-7 items-center justify-center rounded-full text-xs font-semibold transition-colors ${isSelected
? 'bg-primary text-primary-foreground'
: isTodayDate
? 'bg-muted text-foreground'
: 'text-foreground'
}`}
>
{cell.day}
</span>
@ -635,7 +629,8 @@ export default function AttendanceIndex({
</div>
</div>
);
})}
})}
</div>
</div>
</Card>
</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 { TodayAttendanceAlert } from '@/components/card/today-attendance-alert';
import { CameraCapture } from '@/components/inputs';
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card';
import {
Dialog,
@ -22,6 +9,19 @@ import {
import { useCan } from '@/hooks/use-can';
import { formatRupiah } from '@/lib/rupiah';
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 = {
id: number;
@ -297,8 +297,8 @@ export default function Dashboard({
<>
<Head title="Dasbor" />
<div className="flex h-full flex-1 flex-col gap-6 overflow-x-auto p-4 md:p-6">
<Card className="relative overflow-hidden">
<div className="flex h-full flex-1 flex-col gap-6 p-4 md:p-6">
<Card className="relative overflow-visible">
<div className="absolute inset-0 bg-linear-to-br from-primary/5 to-background" />
<CardHeader className="relative">
<div className="flex items-center gap-3">
@ -306,7 +306,7 @@ export default function Dashboard({
<GreetingIcon className="size-6 text-primary" />
</div>
<div>
<CardTitle className="text-2xl font-bold">
<CardTitle className="text-2xl font-bold">
{greeting.text}, {auth.user?.username}!
</CardTitle>
<CardDescription className="mt-1">