import { Head, useForm, router } from '@inertiajs/react'; import type { Expense } from '@/types'; import { ColumnDef } from '@tanstack/react-table'; import { Card, CardContent } from '@/components/ui/card'; import { Button } from '@/components/ui/button'; import { Trash2, Pencil, ImagePlus, X } from 'lucide-react'; import { DataTable } from '@/components/data-table'; import { DataTableColumnHeader } from '@/components/data-table-column-header'; import { Dialog, DialogContent, DialogFooter, DialogHeader, DialogTitle, } from "@/components/ui/dialog" import { Field, FieldGroup } from "@/components/ui/field" import { Input } from "@/components/ui/input" import { Label } from "@/components/ui/label" import { useState, useRef } from 'react'; import { toast } from 'sonner'; import { TooltipContent, TooltipTrigger } from '@radix-ui/react-tooltip'; import { Tooltip } from '@/components/ui/tooltip'; import expenseRoutes from '@/routes/expense'; import { AlertDialog, AlertDialogAction, AlertDialogCancel, AlertDialogContent, AlertDialogDescription, AlertDialogFooter, AlertDialogHeader, AlertDialogMedia, AlertDialogTitle, } from "@/components/ui/alert-dialog" import { NumericFormat } from 'react-number-format'; export default function ExpenseIndex({ expenses }: { expenses: Expense[] }) { const [isOpen, setIsOpen] = useState(false); const [isEditing, setIsEditing] = useState(false); const [selectedExpense, setSelectedExpense] = useState(null); const { data, setData, post, patch, processing, errors, reset, clearErrors } = useForm<{ name: string; amount: string; image: File | null; }>({ name: '', amount: '', image: null, }); const [imagePreview, setImagePreview] = useState(null); const [selectedProof, setSelectedProof] = useState(null); const fileInputRef = useRef(null); const [isDeleteDialogOpen, setIsDeleteDialogOpen] = useState(false); const [isBulkDeleteDialogOpen, setIsBulkDeleteDialogOpen] = useState(false); const [expenseToDelete, setExpenseToDelete] = useState(null); const [rowsToDelete, setRowsToDelete] = useState([]); const [rowSelection, setRowSelection] = useState({}); const onEdit = (expense: Expense) => { setIsEditing(true); setSelectedExpense(expense); setData({ name: expense.name, amount: expense.amount.toString(), image: null, }); setImagePreview(expense.proof_url || null); setIsOpen(true); }; const onDelete = (expense: Expense) => { setExpenseToDelete(expense); setIsDeleteDialogOpen(true); }; const confirmDelete = () => { if (expenseToDelete) { router.delete(expenseRoutes.destroy(expenseToDelete.id).url, { onSuccess: (response: any) => { toast.success(response.props.flash.success); setIsDeleteDialogOpen(false); setExpenseToDelete(null); setRowSelection({}); }, }); } }; const confirmBulkDelete = () => { router.post(expenseRoutes.bulkDestroy().url, { ids: rowsToDelete.map((row: any) => row.id), _method: 'DELETE' }, { onSuccess: (response: any) => { toast.success(response.props.flash.success); setIsBulkDeleteDialogOpen(false); setRowsToDelete([]); setRowSelection({}); }, }); }; const closeModal = () => { setIsOpen(false); setTimeout(() => { setIsEditing(false); setSelectedExpense(null); setImagePreview(null); reset(); clearErrors(); }, 200); }; const onImageChange = (e: React.ChangeEvent) => { const file = e.target.files?.[0]; if (file) { setData('image', file); const reader = new FileReader(); reader.onloadend = () => { setImagePreview(reader.result as string); }; reader.readAsDataURL(file); } }; const removeImage = () => { setData('image', null); setImagePreview(null); if (fileInputRef.current) { fileInputRef.current.value = ''; } }; const onSubmit = (e: React.FormEvent) => { e.preventDefault(); if (isEditing && selectedExpense) { router.post(expenseRoutes.update(selectedExpense.id).url, { ...data, _method: 'PATCH', }, { onSuccess: (response: any) => { toast.success(response.props.flash.success); closeModal(); }, }); } else { post(expenseRoutes.store().url, { onSuccess: (response: any) => { toast.success(response.props.flash.success); closeModal(); }, }); } }; const formatCurrency = (amount: number) => { return new Intl.NumberFormat('id-ID', { style: 'currency', currency: 'IDR', minimumFractionDigits: 0, }).format(amount); }; const columns: ColumnDef[] = [ { accessorKey: "proof_url", header: "Bukti", cell: ({ row }) => { const url = row.original.proof_url; return url ? ( ) : (
); }, meta: { title: "Bukti" }, }, { accessorKey: "user.name", header: ({ column }) => { return ( ) }, cell: ({ row }) => row.original.user?.name, meta: { title: "Pegawai" }, }, { accessorKey: "name", header: ({ column }) => { return ( ) }, meta: { title: "Nama" }, }, { accessorKey: "amount", header: ({ column }) => { return ( ) }, cell: ({ row }) => formatCurrency(row.original.amount), meta: { title: "Nominal" }, }, { accessorKey: "created_at", header: ({ column }) => { return ( ) }, cell: ({ row }) => new Date(row.original.created_at).toLocaleDateString('id-ID', { day: '2-digit', month: 'long', year: 'numeric' }), meta: { title: "Tanggal" }, }, { id: "actions", header: "Aksi", cell: ({ row }) => { const expense = row.original; return (

Ubah

Hapus

); }, meta: { title: "Aksi" }, }, ]; return (

Pengeluaran

!open && closeModal()}> {isEditing ? 'Ubah Pengeluaran' : 'Tambah Pengeluaran'}
setData('name', e.target.value)} autoComplete='off' placeholder='Contoh: Bayar Listrik' maxLength={100} /> {errors.name &&

{errors.name}

}
{ setData('amount', values.value) }} placeholder="Rp 0" autoComplete='off' /> {errors.amount &&

{errors.amount}

}
{imagePreview ? (
Preview
) : (
fileInputRef.current?.click()} className="h-40 w-full flex flex-col items-center justify-center border-2 border-dashed rounded-lg cursor-pointer hover:bg-accent/50 transition-colors" > Klik untuk upload bukti PNG, JPG up to 5MB
)}
{errors.image &&

{errors.image}

}
setSelectedProof(null)}>
Proof
{ setRowsToDelete(rows); setIsBulkDeleteDialogOpen(true); }, icon: Trash2, variant: 'destructive' }, ]} /> Hapus pengeluaran? Tindakan ini tidak dapat dibatalkan. Pengeluaran {expenseToDelete?.name} akan dihapus secara permanen. Batal Hapus Hapus {rowsToDelete.length} pengeluaran? Tindakan ini tidak dapat dibatalkan. {rowsToDelete.length} item yang terpilih akan dihapus secara permanen. Batal Hapus
); } ExpenseIndex.layout = { breadcrumbs: [ { title: 'Keuangan', }, ], };