import { Head, Link, router, usePage } from '@inertiajs/react'; import { format } from 'date-fns'; import { Bluetooth, Cable, Plus, Printer, Unplug } from 'lucide-react'; import { useMemo, useState } from 'react'; import { toast } from 'sonner'; import { CardTable } from '@/components/card-table'; import { DatePicker } from '@/components/date-picker'; import { DeleteConfirmDialog } from '@/components/delete-confirm-dialog'; import { FilterPopover } from '@/components/filter-popover'; import { useCardTableExpand } from '@/components/hooks/use-card-table-expand'; import { PageHeader } from '@/components/page-header'; import { Button } from '@/components/ui/button'; import { Combobox, ComboboxContent, ComboboxEmpty, ComboboxInput, ComboboxItem, ComboboxList, } from '@/components/ui/combobox'; import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuSeparator, DropdownMenuTrigger, } from '@/components/ui/dropdown-menu'; import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue, } from '@/components/ui/select'; import { useCan } from '@/hooks/use-can'; import { useServerTable } from '@/hooks/use-server-table'; import { useThermalPrinter, encodeOrderReceipt } from '@/hooks/use-thermal-printer'; import { destroy, create as transactionCreate, index as transactionIndex, edit as transactionEdit, updateStatus as transactionUpdateStatus, } from '@/routes/admin/manage/transactions'; import type { Transaction } from './columns'; import { TransactionCardRow } from './transaction-card'; import { TransactionItemSubRow } from './transaction-sub-row'; import { TransactionSummaryCard } from './transaction-summary-card'; type FilterOption = { id: number; name: string; }; type StatusOption = { value: string; label: string; }; type Props = { transactions: { data: Transaction[]; current_page: number; last_page: number; per_page: number; total: number; }; summary: { total_orders: number; total_subtotal: number; total_discount: number; total_amount: number; net_total: number; }; filters: { status?: string; channel?: string; payment_type?: string; customer_id?: string; marketing_id?: string; created_by_id?: string; date_from?: string; date_to?: string; }; filterOptions: { statusOptions: StatusOption[]; channelOptions: StatusOption[]; paymentTypeOptions: StatusOption[]; customers: FilterOption[]; employees: FilterOption[]; }; }; export default function TransactionIndex({ transactions, summary, filters, filterOptions, }: Props) { const { can } = useCan(); const { name: appName, address: appAddress } = usePage().props as unknown as { name: string; address: string }; const [deleting, setDeleting] = useState(null); const expand = useCardTableExpand(true); const printer = useThermalPrinter(); const pagination = { current_page: transactions.current_page, last_page: transactions.last_page, per_page: transactions.per_page, total: transactions.total, }; const { search, filterOpen, setFilterOpen, handlePageChange, handlePerPageChange, handleSearchChange, applyFilter, clearFilters, } = useServerTable({ route: () => transactionIndex.url(), pagination, filters, }); const selectedCustomer = useMemo( () => filterOptions.customers.find( (c) => String(c.id) === filters.customer_id, ) ?? null, [filterOptions.customers, filters.customer_id], ); const selectedMarketing = useMemo( () => filterOptions.employees.find( (e) => String(e.id) === filters.marketing_id, ) ?? null, [filterOptions.employees, filters.marketing_id], ); const selectedEmployee = useMemo( () => filterOptions.employees.find( (e) => String(e.id) === filters.created_by_id, ) ?? null, [filterOptions.employees, filters.created_by_id], ); function handleDelete() { if (!deleting) { return; } router.delete(destroy.url(deleting.id), { onSuccess: () => setDeleting(null), }); } function handleUpdateStatus(transaction: Transaction, status: string) { router.patch(transactionUpdateStatus.url(transaction.id), { status }); } function handlePrintReceipt(transaction: Transaction, paperWidth: 58 | 80) { if (!printer.connected) { alert('Hubungkan printer terlebih dahulu.'); return; } const rupiah = (n: number) => `Rp ${n.toLocaleString('id-ID')}`; const items = (transaction.order_items ?? []).map((item) => ({ product_name: item.product_variant?.product?.name ?? '-', variant_name: item.product_variant?.name ?? '-', quantity: `${Number(item.quantity)}`, unit_price: rupiah(Number(item.unit_price)), subtotal: rupiah(Number(item.subtotal)), })); const cashierName = transaction.created_by?.user_profile?.full_name ?? '-'; const data = encodeOrderReceipt( { order_number: transaction.order_number, created_at: format(new Date(transaction.created_at), 'dd/MM/yyyy HH:mm'), customer_name: transaction.customer?.name ?? null, cashier_name: cashierName, items, subtotal: rupiah(Number(transaction.subtotal)), discount: rupiah(Number(transaction.discount)), nego_price: transaction.nego_price != null && Number(transaction.nego_price) > 0 ? rupiah(Number(transaction.nego_price)) : null, total_amount: rupiah(Number(transaction.total_amount)), notes: transaction.notes ?? null, }, { storeName: appName, storeAddress: appAddress, paperWidth, }, ); printer.print(data).catch((err: Error) => { alert(`Gagal mencetak: ${err.message}`); }); } const filterToolbar = (
c.name} value={selectedCustomer} onValueChange={(value) => applyFilter( 'customer_id', value ? String(value.id) : '', ) } > Tidak ada pelanggan ditemukan. {(customer) => ( {customer.name} )}
e.name} value={selectedMarketing} onValueChange={(value) => applyFilter( 'marketing_id', value ? String(value.id) : '', ) } > Tidak ada marketing ditemukan. {(employee) => ( {employee.name} )}
e.name} value={selectedEmployee} onValueChange={(value) => applyFilter( 'created_by_id', value ? String(value.id) : '', ) } > Tidak ada pegawai ditemukan. {(employee) => ( {employee.name} )}
applyFilter('date_from', date ? format(date, 'yyyy-MM-dd') : '')} placeholder="Pilih tanggal mulai" />
applyFilter('date_to', date ? format(date, 'yyyy-MM-dd') : '')} placeholder="Pilih tanggal akhir" />
); return ( <>
{printer.connected ? ( { const name = await printer.disconnect(); toast.success('Printer diputuskan.'); }} > Putuskan ) : ( { try { const name = await printer.connect('bluetooth'); toast.success(`Terhubung ke ${name}`); } catch (err) { toast.error(err instanceof Error ? err.message : 'Gagal menghubungkan Bluetooth.'); } }} > Hubungkan Bluetooth { try { const name = await printer.connect('usb'); toast.success(`Terhubung ke ${name}`); } catch (err) { toast.error(err instanceof Error ? err.message : 'Gagal menghubungkan USB.'); } }} > Hubungkan USB )} {can('orders.create') ? ( ) : undefined}
} /> t.id} expandedKeys={expand.expandedKeys} onToggleExpand={expand.toggleExpand} searchValue={search} onSearchChange={handleSearchChange} toolbar={filterToolbar} pagination={pagination} onPageChange={handlePageChange} onPerPageChange={handlePerPageChange} renderCard={({ item, index, isExpanded, onToggleExpand, }) => ( { router.visit(transactionEdit.url(t.id)); }} onDelete={(t) => setDeleting(t)} onUpdateStatus={(t, status) => handleUpdateStatus(t, status)} onPrint={(t, pw) => handlePrintReceipt(t, pw)} /> )} renderSubContent={(transaction) => ( )} /> { if (!open) { setDeleting(null); } }} title="Hapus Transaksi" description="Apakah Anda yakin ingin menghapus transaksi ini? Stok akan dikembalikan. Tindakan ini tidak dapat dibatalkan." onConfirm={handleDelete} /> ); }