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, DialogContent, } from '@/components/ui/dialog'; 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; attendance_date: string; check_in_at: string | null; check_out_at: string | null; check_in_photo: string | null; check_out_photo: string | null; check_in_latitude: number; check_in_longitude: number; check_out_latitude: number | null; check_out_longitude: number | null; work_duration_minutes: number | null; } | null; type DashboardProps = { attendance: { total_employees: number; percentage: number; present: number; absent: number; on_leave: number; }; revenueSummary: { total_revenue: number; total_discount: number; total_marketplace_fees: number; total_deduction: number; total_orders: number; avg_order: number; }; expenseSummary: { total: number; purchase_total: number; expense_total: number; advance_total: number; }; orderStats: { by_channel: Array<{ channel: string; label: string; count: number; total: number; }>; by_payment_type: Array<{ payment_type: string; label: string; count: number; total: number; }>; by_marketing: Array<{ name: string; count: number; total: number; }>; by_status: Array<{ status: string; label: string; count: number; }>; }; todayAttendance: TodayAttendance; isOnLeave: boolean; canCheckIn: boolean; }; const CHART_COLORS = ['#60a5fa', '#f97316', '#22c55e', '#a855f7', '#ef4444']; type CustomTooltipProps = { active?: boolean; payload?: Array<{ name: string; value: number; payload: { label?: string; name?: string; count: number; }; }>; }; function ChartTooltip({ active, payload }: CustomTooltipProps) { if (!active || !payload?.length) { return null; } const data = payload[0].payload; return (

{data.label ?? data.name ?? ''} {data.count.toLocaleString('id-ID')}

); } export default function Dashboard({ attendance: attendanceStats, revenueSummary, expenseSummary, orderStats, todayAttendance, isOnLeave, canCheckIn, }: DashboardProps) { const { can } = useCan(); const { auth } = usePage().props as { auth: { user?: { username?: string } } }; const [currentTime, setCurrentTime] = useState(new Date()); const [showCamera, setShowCamera] = useState(false); const [actionType, setActionType] = useState<'check-in' | 'check-out'>('check-in'); const [locationLoading, setLocationLoading] = useState(false); useEffect(() => { const timer = setInterval(() => setCurrentTime(new Date()), 1000); return () => clearInterval(timer); }, []); const greeting = useMemo(() => { const hour = currentTime.getHours(); if (hour >= 4 && hour < 11) { return { text: 'Selamat Pagi', icon: Sunrise }; } if (hour >= 11 && hour < 15) { return { text: 'Selamat Siang', icon: Sun }; } if (hour >= 15 && hour < 18) { return { text: 'Selamat Sore', icon: Sun }; } return { text: 'Selamat Malam', icon: Moon }; }, [currentTime]); const timeStr = currentTime.toLocaleTimeString('id-ID', { hour: '2-digit', minute: '2-digit', second: '2-digit', hour12: false, }); const dateStr = currentTime.toLocaleDateString('id-ID', { weekday: 'long', year: 'numeric', month: 'long', day: 'numeric', }); const visibleStatsCount = useMemo(() => { let count = 0; if (can('dashboard.attendance')) { count++; } if (can('dashboard.revenue')) { count++; } if (can('dashboard.expense')) { count++; } return count; }, [can]); const statsGridClass = useMemo(() => { if (visibleStatsCount === 1) { return 'grid gap-4 grid-cols-1 md:max-w-md'; } if (visibleStatsCount === 2) { return 'grid gap-4 grid-cols-1 md:grid-cols-2'; } if (visibleStatsCount === 3) { return 'grid gap-4 grid-cols-1 md:grid-cols-3'; } return 'grid gap-4 grid-cols-1 md:grid-cols-2 lg:grid-cols-4'; }, [visibleStatsCount]); const visibleChartsCount = useMemo(() => { let count = 0; if (can('dashboard.orders_channel')) { count++; } if (can('dashboard.orders_payment')) { count++; } if (can('dashboard.orders_marketing')) { count++; } if (can('dashboard.orders_status')) { count++; } return count; }, [can]); const chartsGridClass = useMemo(() => { if (visibleChartsCount === 1) { return 'grid gap-4 grid-cols-1 md:max-w-xl'; } if (visibleChartsCount === 2) { return 'grid gap-4 grid-cols-1 md:grid-cols-2'; } if (visibleChartsCount === 3) { return 'grid gap-4 grid-cols-1 md:grid-cols-3'; } return 'grid gap-4 grid-cols-1 md:grid-cols-2 lg:grid-cols-4'; }, [visibleChartsCount]); const GreetingIcon = greeting.icon; const handleCheckIn = () => { setActionType('check-in'); setShowCamera(true); }; const handleCheckOut = () => { setActionType('check-out'); setShowCamera(true); }; const handleCameraCapture = (dataUrl: string) => { setShowCamera(false); setLocationLoading(true); if (!navigator.geolocation) { setLocationLoading(false); toast.error('Geolocation tidak didukung di browser ini.'); return; } navigator.geolocation.getCurrentPosition( (position) => { setLocationLoading(false); const formData = new FormData(); formData.append('photo', dataUrl); formData.append('latitude', position.coords.latitude.toString()); formData.append('longitude', position.coords.longitude.toString()); if (actionType === 'check-in') { router.post(store(), formData, { preserveScroll: true }); } else if (actionType === 'check-out' && todayAttendance) { router.put(update(todayAttendance.id), formData, { preserveScroll: true, }); } }, () => { setLocationLoading(false); toast.error('Gagal mendapatkan lokasi. Pastikan izin lokasi diberikan.'); }, { enableHighAccuracy: true, timeout: 10000 }, ); }; return ( <>
{greeting.text}, {auth.user?.username}! Selamat datang di dasbor aplikasi.
{dateStr} - {timeStr}
setShowCamera(false)} /> {visibleStatsCount > 0 && (
{can('dashboard.attendance') && ( )} {can('dashboard.revenue') && ( )} {can('dashboard.expense') && ( )}
)} {visibleChartsCount > 0 && (
{can('dashboard.orders_channel') && ( ({ ...item, color: CHART_COLORS[index % CHART_COLORS.length], }))} dataKey="count" nameKey="label" /> )} {can('dashboard.orders_payment') && ( ({ ...item, color: CHART_COLORS[index % CHART_COLORS.length], }))} dataKey="count" nameKey="label" /> )} {can('dashboard.orders_marketing') && ( ({ ...item, label: item.name, color: CHART_COLORS[index % CHART_COLORS.length], }))} dataKey="count" nameKey="label" /> )} {can('dashboard.orders_status') && ( ({ ...item, color: CHART_COLORS[index % CHART_COLORS.length], }))} dataKey="count" nameKey="label" /> )}
)}
); } type DonutChartItem = { label: string; count: number; color: string; }; type DashboardDonutChartProps = { title: string; data: DonutChartItem[]; dataKey: string; nameKey: string; }; function DashboardDonutChart({ title, data }: DashboardDonutChartProps) { const hasData = data.length > 0 && data.some((d) => d.count > 0); return ( {title} {hasData ? ( <>
{data.map((entry, index) => ( ))} } />
{data.map((item, index) => (
{item.label} {item.count}
))}
) : (
Belum ada data
)}
); }