495 lines
25 KiB
TypeScript

import { Button } from '@/components/ui/button';
import { Calendar } from '@/components/ui/calendar';
import { Input } from '@/components/ui/input';
import { Label } from '@/components/ui/label';
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select';
import { useForm } from '@inertiajs/react';
import AppLayout from '@/layouts/app-layout';
import { Head } from '@inertiajs/react';
import { type BreadcrumbItem } from '@/types';
import InputError from '@/components/input-error';
import { format } from 'date-fns';
import { CalendarIcon } from 'lucide-react';
import { Popover, PopoverContent, PopoverTrigger } from '@/components/ui/popover';
import { cn } from '@/lib/utils';
import { Textarea } from '@/components/ui/textarea';
import { useState, useEffect } from 'react';
interface TransactionFormProps {
mode: 'create' | 'edit';
patients: Array<{ id: string; name: string }>;
registrations: Array<{ id: string; registration_number: string; patient_name: string }>;
insurances: Array<{ id: string; name: string }>;
procedures: Array<{ id: string; code: string; name: string }>;
employees: Array<{ id: string; name: string }>;
paymentMethods: Record<string, string>;
statusOptions: Record<string, string>;
transaction?: {
id?: string;
transaction_number: string;
registration_id?: string;
patient_id?: string;
insurance_id?: string;
procedure_id?: string;
employee_id?: string;
service_name: string;
description?: string;
transaction_datetime: string;
subtotal: number;
tax_amount: number;
discount_amount: number;
grand_total: number;
payment_method: string;
status: string;
paid_amount: number;
insurance_covered_amount: number;
details?: string;
notes?: string;
};
}
const breadcrumbs: BreadcrumbItem[] = [
{ title: 'Dashboard', href: '/dashboard' },
{ title: 'Transaksi', href: '/transactions' },
{ title: 'Form', href: '#' },
];
const toLocalISOString = (date: Date) => {
const offset = date.getTimezoneOffset();
const localDate = new Date(date.getTime() - (offset * 60 * 1000));
return localDate.toISOString().split('T')[0];
};
export default function TransactionForm({
mode,
patients,
registrations,
insurances,
procedures,
employees,
paymentMethods,
statusOptions,
transaction
}: TransactionFormProps) {
const { data, setData, post, put, processing, errors, reset } = useForm({
transaction_number: transaction?.transaction_number || '',
registration_id: transaction?.registration_id || '',
patient_id: transaction?.patient_id || '',
insurance_id: transaction?.insurance_id || '',
procedure_id: transaction?.procedure_id || '',
employee_id: transaction?.employee_id || '',
service_name: transaction?.service_name || '',
transaction_datetime: transaction?.transaction_datetime || toLocalISOString(new Date()),
subtotal: transaction?.subtotal || 0,
tax_amount: transaction?.tax_amount || 0,
discount_amount: transaction?.discount_amount || 0,
grand_total: transaction?.grand_total || 0,
payment_method: transaction?.payment_method || 'cash',
status: transaction?.status || 'pending',
paid_amount: transaction?.paid_amount || 0,
insurance_covered_amount: transaction?.insurance_covered_amount || 0,
notes: transaction?.notes || '',
details: transaction?.details || '',
});
const [registrationSelected, setRegistrationSelected] = useState<boolean>(false);
useEffect(() => {
const calculatedGrandTotal = data.subtotal + data.tax_amount - data.discount_amount;
setData('grand_total', calculatedGrandTotal);
if (data.payment_method === 'insurance') {
const insuranceCovered = calculatedGrandTotal * 0.8; // Example: insurance covers 80%
setData('insurance_covered_amount', insuranceCovered);
setData('paid_amount', calculatedGrandTotal - insuranceCovered);
} else {
setData('insurance_covered_amount', 0);
setData('paid_amount', calculatedGrandTotal);
}
}, [data.subtotal, data.tax_amount, data.discount_amount, data.payment_method]);
useEffect(() => {
if (data.registration_id) {
const selectedRegistration = registrations.find(r => r.id === data.registration_id);
if (selectedRegistration) {
setRegistrationSelected(true);
const patient = patients.find(p => p.name === selectedRegistration.patient_name);
if (patient) {
setData('patient_id', patient.id);
}
}
} else {
setRegistrationSelected(false);
}
}, [data.registration_id, registrations, patients]);
useEffect(() => {
if (data.procedure_id) {
const selectedProcedure = procedures.find(p => p.id === data.procedure_id);
if (selectedProcedure) {
setData('service_name', selectedProcedure.name);
}
}
}, [data.procedure_id, procedures]);
const onSubmit = (e: React.FormEvent) => {
e.preventDefault();
if (mode === 'create') {
post(route('transactions.store'), {
onSuccess: () => reset(),
});
} else {
put(route('transactions.update', transaction?.id), {
preserveScroll: true,
});
}
};
const formatCurrency = (value: number) => {
return new Intl.NumberFormat('id-ID', {
style: 'currency',
currency: 'IDR',
minimumFractionDigits: 0,
}).format(value);
};
return (
<AppLayout breadcrumbs={breadcrumbs}>
<Head title={`${mode === 'create' ? 'Tambah' : 'Edit'} Transaksi`} />
<div className="flex h-full flex-1 flex-col gap-4 rounded-xl p-4">
<div className="flex items-center justify-between">
<h1 className="text-2xl font-bold">
{mode === 'create' ? 'Tambah Transaksi Baru' : 'Edit Data Transaksi'}
</h1>
{mode === 'edit' && (
<span className="text-xl">
No. Transaksi {data.transaction_number}
</span>
)}
</div>
<div>
<form onSubmit={onSubmit} className="space-y-6">
<div className="grid grid-cols-1 gap-6 md:grid-cols-2">
<div className="space-y-2">
<Label htmlFor="registration_id">No. Registrasi</Label>
<Select
value={data.registration_id}
onValueChange={(value) => setData('registration_id', value)}
>
<SelectTrigger>
<SelectValue placeholder="Pilih nomor registrasi" />
</SelectTrigger>
<SelectContent>
<SelectItem value="none">Tanpa registrasi</SelectItem>
{registrations.map((registration) => (
<SelectItem key={registration.id} value={registration.id}>
{registration.registration_number} - {registration.patient_name}
</SelectItem>
))}
</SelectContent>
</Select>
<InputError message={errors.registration_id} />
</div>
<div className="space-y-2">
<Label htmlFor="patient_id">Pasien *</Label>
<Select
value={data.patient_id}
onValueChange={(value) => setData('patient_id', value)}
disabled={registrationSelected}
>
<SelectTrigger>
<SelectValue placeholder="Pilih pasien" />
</SelectTrigger>
<SelectContent>
{patients.map((patient) => (
<SelectItem key={patient.id} value={patient.id}>
{patient.name}
</SelectItem>
))}
</SelectContent>
</Select>
<InputError message={errors.patient_id} />
</div>
<div className="space-y-2">
<Label htmlFor="procedure_id">Prosedur</Label>
<Select
value={data.procedure_id}
onValueChange={(value) => setData('procedure_id', value)}
>
<SelectTrigger>
<SelectValue placeholder="Pilih prosedur" />
</SelectTrigger>
<SelectContent>
<SelectItem value="manual">Manual Input</SelectItem>
{procedures.map((procedure) => (
<SelectItem key={procedure.id} value={procedure.id}>
{procedure.code} - {procedure.name}
</SelectItem>
))}
</SelectContent>
</Select>
<InputError message={errors.procedure_id} />
</div>
<div className="space-y-2">
<Label htmlFor="service_name">Nama Layanan *</Label>
<Input
id="service_name"
value={data.service_name}
onChange={(e) => setData('service_name', e.target.value)}
placeholder="Nama layanan"
/>
<InputError message={errors.service_name} />
</div>
<div className="space-y-2">
<Label htmlFor="employee_id">Pelaksana Layanan</Label>
<Select
value={data.employee_id}
onValueChange={(value) => setData('employee_id', value)}
>
<SelectTrigger>
<SelectValue placeholder="Pilih pelaksana" />
</SelectTrigger>
<SelectContent>
{employees.map((employee) => (
<SelectItem key={employee.id} value={employee.id}>
{employee.name}
</SelectItem>
))}
</SelectContent>
</Select>
<InputError message={errors.employee_id} />
</div>
<div className="space-y-2">
<Label htmlFor="transaction_datetime">Tanggal/Waktu Transaksi *</Label>
<Popover>
<PopoverTrigger asChild>
<Button
variant={"outline"}
className={cn(
"w-full justify-start text-left font-normal",
!data.transaction_datetime && "text-muted-foreground"
)}
>
<CalendarIcon className="mr-2 h-4 w-4" />
{data.transaction_datetime ? (
format(new Date(data.transaction_datetime), "dd/MM/yyyy HH:mm")
) : (
<span>Pilih tanggal/waktu</span>
)}
</Button>
</PopoverTrigger>
<PopoverContent className="w-auto p-0">
<Calendar
mode="single"
selected={data.transaction_datetime ? new Date(data.transaction_datetime) : undefined}
onSelect={(date) => date && setData('transaction_datetime', toLocalISOString(date))}
initialFocus
/>
</PopoverContent>
</Popover>
<InputError message={errors.transaction_datetime} />
</div>
{/* Financial Section */}
<div className="space-y-2">
<Label htmlFor="subtotal">Subtotal *</Label>
<Input
id="subtotal"
type="number"
value={data.subtotal}
onChange={(e) => setData('subtotal', Math.max(0, parseFloat(e.target.value) || 0))}
placeholder="Subtotal"
/>
<InputError message={errors.subtotal} />
</div>
<div className="space-y-2">
<Label htmlFor="tax_amount">Pajak *</Label>
<Input
id="tax_amount"
type="number"
value={data.tax_amount}
onChange={(e) => setData('tax_amount', Math.max(0, parseFloat(e.target.value) || 0))}
placeholder="Jumlah pajak"
/>
<InputError message={errors.tax_amount} />
</div>
<div className="space-y-2">
<Label htmlFor="discount_amount">Diskon *</Label>
<Input
id="discount_amount"
type="number"
value={data.discount_amount}
onChange={(e) => setData('discount_amount', Math.max(0, parseFloat(e.target.value) || 0))}
placeholder="Jumlah diskon"
/>
<InputError message={errors.discount_amount} />
</div>
<div className="space-y-2">
<Label>Grand Total</Label>
<div className="p-2 font-semibold text-xl">
{formatCurrency(data.grand_total)}
</div>
<InputError message={errors.grand_total} />
</div>
<div className="space-y-2">
<Label htmlFor="payment_method">Metode Pembayaran *</Label>
<Select
value={data.payment_method}
onValueChange ={(value) => {
setData('payment_method', value);
if (value === 'insurance' && data.insurance_id) {
const insuranceCovered = data.grand_total * 0.8;
setData('insurance_covered_amount', insuranceCovered);
setData('paid_amount', data.grand_total - insuranceCovered);
} else {
setData('insurance_covered_amount', 0);
setData('paid_amount', data.grand_total);
}
}}
>
<SelectTrigger>
<SelectValue placeholder="Pilih metode pembayaran" />
</SelectTrigger>
<SelectContent>
{Object.entries(paymentMethods).map(([key, value]) => (
<SelectItem key={key} value={key}>
{value}
</SelectItem>
))}
</SelectContent>
</Select>
<InputError message={errors.payment_method} />
</div>
<div className="space-y-2">
<Label htmlFor="status">Status Pembayaran *</Label>
<Select
value={data.status}
onValueChange={(value) => setData('status', value)}
>
<SelectTrigger>
<SelectValue placeholder="Pilih status pembayaran" />
</SelectTrigger>
<SelectContent>
{Object.entries(statusOptions).map(([key, value]) => (
<SelectItem key={key} value={key}>
{value}
</SelectItem>
))}
</SelectContent>
</Select>
<InputError message={errors.status} />
</div>
{data.payment_method === 'insurance' && (
<>
<div className="space-y-2">
<Label htmlFor="insurance_id">Asuransi *</Label>
<Select
value={data.insurance_id}
onValueChange={(value) => {
setData('insurance_id', value);
const insuranceCovered = data.grand_total * 0.8;
setData('insurance_covered_amount', insuranceCovered);
setData('paid_amount', data.grand_total - insuranceCovered);
}}
>
<SelectTrigger>
<SelectValue placeholder="Pilih asuransi" />
</SelectTrigger>
<SelectContent>
{insurances.map((insurance) => (
<SelectItem key={insurance.id} value={insurance.id}>
{insurance.name}
</SelectItem>
))}
</SelectContent>
</Select>
<InputError message={errors.insurance_id} />
</div>
<div className="space-y-2">
<Label htmlFor="insurance_covered_amount">Dibayar Asuransi *</Label>
<Input
id="insurance_covered_amount"
type="number"
value={data.insurance_covered_amount}
onChange={(e) => {
const insuranceCovered = Math.max(0, parseFloat(e.target.value) || 0);
setData('insurance_covered_amount', insuranceCovered);
setData('paid_amount', data.grand_total - insuranceCovered);
}}
placeholder="Jumlah dibayar asuransi"
/>
<InputError message={errors.insurance_covered_amount} />
</div>
</>
)}
<div className="space-y-2">
<Label htmlFor="paid_amount">Jumlah Pembayaran *</Label>
<Input
id="paid_amount"
type="number"
value={data.paid_amount}
onChange={(e) => {
const paidAmount = Math.max(0, parseFloat(e.target.value) || 0);
setData('paid_amount', paidAmount);
if (data.payment_method === 'insurance') {
setData('insurance_covered_amount', data.grand_total - paidAmount);
}
}}
placeholder="Jumlah pembayaran"
/>
<InputError message={errors.paid_amount} />
</div>
<div className="space-y-2 col-span-full">
<Label htmlFor="details">Detail Layanan *</Label>
<Textarea
id="details"
value={data.details}
onChange={(e) => setData('details', e.target.value)}
placeholder="Detail layanan yang diberikan"
/>
<InputError message={errors.details} />
</div>
<div className="space-y-2 col-span-full">
<Label htmlFor="notes">Catatan</Label>
<Textarea
id="notes"
value={data.notes}
onChange={(e) => setData('notes', e.target.value)}
placeholder="Catatan tambahan"
/>
<InputError message={errors.notes} />
</div>
</div>
<div className="flex justify-end gap-2">
<Button variant="outline" type="button" onClick ={() => window.history.back()}>
Batal
</Button>
<Button type="submit" disabled={processing}>
{mode === 'create' ? 'Simpan' : 'Perbarui'} Transaksi
</Button>
</div>
</form>
</div>
</div>
</AppLayout>
);
}