feat: update TodayAttendanceAlert component and enhance card styling in attendance and dashboard pages
This commit is contained in:
parent
166419134f
commit
e4d956843c
@ -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}
|
||||||
|
|||||||
@ -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>
|
||||||
|
|||||||
@ -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}
|
||||||
/>
|
/>
|
||||||
)
|
)
|
||||||
|
|||||||
@ -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>
|
||||||
|
|||||||
@ -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">
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user