TIFNJK_E41222887/resources/js/pages/admin/attendances/index.tsx

197 lines
10 KiB
TypeScript

import { Head, router } from '@inertiajs/react';
import React, { useState, useEffect } from 'react';
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';
import { Button } from '@/components/ui/button';
import { Input } from '@/components/ui/input';
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select';
import { Separator } from '@/components/ui/separator';
import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from '@/components/ui/table';
import AppLayout from '@/layouts/app-layout';
import { Search } from 'lucide-react';
import { useDebounce } from 'use-debounce';
import * as XLSX from 'xlsx';
import { AttendanceMapModal } from '@/components/AttendanceMapModal';
interface Attendance {
id: number;
employee_name: string;
employee_nik: string;
date: string;
check_in: string | null;
check_out: string | null;
status: 'present' | 'leave' | 'dispensation';
notes: string | null;
total_hours: string | null;
latitude_in?: string | null;
longitude_in?: string | null;
latitude_out?: string | null;
longitude_out?: string | null;
}
interface PageProps {
attendances: Attendance[];
filters: { search?: string; date?: string; status?: string };
[key: string]: unknown;
}
export default function Index({ attendances, filters }: PageProps) {
const [search, setSearch] = useState(filters.search || '');
const [date, setDate] = useState(filters.date || '');
const [status, setStatus] = useState(filters.status || 'all');
const [debouncedSearch] = useDebounce(search, 500);
useEffect(() => {
if (debouncedSearch !== filters.search || date !== filters.date || (status !== 'all' && status !== filters.status)) {
router.get(
'/admin/attendances',
{ search: debouncedSearch, date, status: status === 'all' ? '' : status },
{ preserveState: true, replace: true }
);
}
}, [debouncedSearch, date, status]);
const exportExcel = () => {
const rows = attendances.map((a, i) => ({
No: i + 1,
Nama: a.employee_name,
NIK: a.employee_nik,
Tanggal: a.date,
'Jam Masuk': a.check_in || '-',
'Jam Keluar': a.check_out || '-',
Status: a.status === 'present' ? 'Hadir' : a.status === 'leave' ? 'Izin/Cuti' : 'Dispensasi',
'Total Jam': a.total_hours ? `${a.total_hours} Jam` : '-',
Catatan: a.notes || '-',
}));
const ws = XLSX.utils.json_to_sheet(rows);
const wb = XLSX.utils.book_new();
XLSX.utils.book_append_sheet(wb, ws, 'Absensi');
XLSX.writeFile(wb, `Data_Absensi_${new Date().toISOString().slice(0, 10)}.xlsx`);
};
const formatStatus = (status: string) => {
switch (status) {
case 'present': return <span className="text-green-600 font-medium">Hadir</span>;
case 'leave': return <span className="text-yellow-600 font-medium">Izin/Cuti</span>;
case 'dispensation': return <span className="text-blue-600 font-medium">Dispensasi</span>;
default: return <span>{status}</span>;
}
};
return (
<AppLayout>
<Head title="Rekap Absensi" />
<div className="p-4 md:p-8 w-full space-y-4">
<div className="space-y-1">
<h2 className="text-2xl font-bold tracking-tight">Rekap Absensi Karyawan</h2>
<p className="text-muted-foreground">Monitoring kehadiran, izin, dan dispensasi karyawan.</p>
</div>
<Card>
<CardHeader className="flex flex-row items-center justify-between pb-4">
<CardTitle>Daftar Absensi</CardTitle>
<Button variant="outline" onClick={exportExcel}>Export Excel</Button>
</CardHeader>
<Separator />
<div className="p-4 flex flex-col md:flex-row gap-4 items-end">
<div className="w-full md:w-1/3 space-y-1">
<label className="text-xs text-muted-foreground font-medium">Cari Karyawan</label>
<div className="relative">
<Search className="absolute left-2.5 top-2.5 h-4 w-4 text-muted-foreground" />
<Input
className="pl-8"
placeholder="Nama / NIK..."
value={search}
onChange={(e) => setSearch(e.target.value)}
/>
</div>
</div>
<div className="w-full md:w-1/4 space-y-1">
<label className="text-xs text-muted-foreground font-medium">Tanggal</label>
<Input
type="date"
value={date}
onChange={(e) => setDate(e.target.value)}
/>
</div>
<div className="w-full md:w-1/4 space-y-1">
<label className="text-xs text-muted-foreground font-medium">Status</label>
<Select value={status} onValueChange={(val) => setStatus(val)}>
<SelectTrigger><SelectValue placeholder="Semua Status" /></SelectTrigger>
<SelectContent>
<SelectItem value="all">Semua Status</SelectItem>
<SelectItem value="present">Hadir</SelectItem>
<SelectItem value="leave">Izin / Cuti</SelectItem>
<SelectItem value="dispensation">Dispensasi</SelectItem>
</SelectContent>
</Select>
</div>
<div className="md:ml-auto">
<Button variant="ghost" onClick={() => { setSearch(''); setDate(''); setStatus('all'); }}>Reset</Button>
</div>
</div>
<CardContent className="pt-0">
<div className="relative w-full overflow-auto">
<Table>
<TableHeader>
<TableRow>
<TableHead>Nama</TableHead>
<TableHead>NIK</TableHead>
<TableHead>Tanggal</TableHead>
<TableHead>Jam Masuk</TableHead>
<TableHead>Jam Keluar</TableHead>
<TableHead>Lokasi Peta</TableHead>
<TableHead>Keterangan</TableHead>
<TableHead className="text-right">Total Jam Kerja</TableHead>
</TableRow>
</TableHeader>
<TableBody>
{attendances.length > 0 ? (
attendances.map((attendance) => (
<TableRow key={attendance.id}>
<TableCell className="font-medium text-gray-900">{attendance.employee_name}</TableCell>
<TableCell className="text-gray-600">{attendance.employee_nik}</TableCell>
<TableCell className="text-gray-600">{attendance.date}</TableCell>
<TableCell>{attendance.check_in || '-'}</TableCell>
<TableCell>{attendance.check_out || '-'}</TableCell>
<TableCell>
<AttendanceMapModal
latitudeIn={attendance.latitude_in}
longitudeIn={attendance.longitude_in}
latitudeOut={attendance.latitude_out}
longitudeOut={attendance.longitude_out}
employeeName={attendance.employee_name}
date={attendance.date}
/>
</TableCell>
<TableCell>
<div className="flex flex-col">
{formatStatus(attendance.status)}
{attendance.notes && (
<span className="text-xs text-gray-500 mt-1">{attendance.notes}</span>
)}
</div>
</TableCell>
<TableCell className="text-right font-medium">
{attendance.total_hours ? `${attendance.total_hours} Jam` : '-'}
</TableCell>
</TableRow>
))
) : (
<TableRow>
<TableCell colSpan={8} className="h-24 text-center text-muted-foreground">
Belum ada data absensi.
</TableCell>
</TableRow>
)}
</TableBody>
</Table>
</div>
</CardContent>
</Card>
</div>
</AppLayout>
);
}