247 lines
12 KiB
TypeScript
247 lines
12 KiB
TypeScript
import { Button } from '@/components/ui/button';
|
|
import { Input } from '@/components/ui/input';
|
|
import { Label } from '@/components/ui/label';
|
|
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select';
|
|
import { Textarea } from '@/components/ui/textarea';
|
|
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';
|
|
|
|
interface ProcedureFormProps {
|
|
mode: 'create' | 'edit';
|
|
procedure?: {
|
|
id?: string;
|
|
code: string;
|
|
name: string;
|
|
category: string;
|
|
description: string;
|
|
base_price: number;
|
|
is_taxable: boolean;
|
|
tax_percentage: number;
|
|
is_discountable: boolean;
|
|
requires_approval: boolean;
|
|
is_active: boolean;
|
|
};
|
|
categories: string[];
|
|
defaults?: {
|
|
is_taxable: boolean;
|
|
tax_percentage: number;
|
|
is_discountable: boolean;
|
|
requires_approval: boolean;
|
|
is_active: boolean;
|
|
};
|
|
}
|
|
|
|
const breadcrumbs: BreadcrumbItem[] = [
|
|
{ title: 'Dashboard', href: '/dashboard' },
|
|
{ title: 'Manajemen Prosedur', href: '/procedures' },
|
|
{ title: 'Form Prosedur', href: '#' },
|
|
];
|
|
|
|
export default function ProcedureForm({ mode, procedure, categories = [], defaults }: ProcedureFormProps) {
|
|
// First ensure categories is an array
|
|
const availableCategories = Array.isArray(categories) ? categories : [];
|
|
|
|
const { data, setData, post, put, processing, errors, reset } = useForm({
|
|
code: procedure?.code || '',
|
|
name: procedure?.name || '',
|
|
category: procedure?.category || 'Tindakan',
|
|
description: procedure?.description || '',
|
|
base_price: procedure?.base_price || 0,
|
|
is_taxable: procedure?.is_taxable ?? defaults?.is_taxable ?? true,
|
|
tax_percentage: procedure?.tax_percentage ?? defaults?.tax_percentage ?? 10.0,
|
|
is_discountable: procedure?.is_discountable ?? defaults?.is_discountable ?? true,
|
|
requires_approval: procedure?.requires_approval ?? defaults?.requires_approval ?? false,
|
|
is_active: procedure?.is_active ?? defaults?.is_active ?? true,
|
|
});
|
|
|
|
const onSubmit = (e: React.FormEvent) => {
|
|
e.preventDefault();
|
|
if (mode === 'create') {
|
|
post(route('procedures.store'), {
|
|
onSuccess: () => reset(),
|
|
});
|
|
} else {
|
|
put(route('procedures.update', procedure?.id), {
|
|
preserveScroll: true,
|
|
});
|
|
}
|
|
};
|
|
|
|
const formatCurrency = (value: number) => {
|
|
return value.toLocaleString('id-ID');
|
|
};
|
|
|
|
const handlePriceChange = (e: React.ChangeEvent<HTMLInputElement>) => {
|
|
const value = e.target.value;
|
|
const num = parseFloat(value.replace(/\D/g, '')) || 0;
|
|
setData('base_price', isNaN(num) ? 0 : num);
|
|
};
|
|
|
|
return (
|
|
<AppLayout breadcrumbs={breadcrumbs}>
|
|
<Head title={`${mode === 'create' ? 'Tambah' : 'Edit'} Prosedur`} />
|
|
|
|
<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 Prosedur Baru' : 'Edit Data Prosedur'}
|
|
</h1>
|
|
</div>
|
|
|
|
<div>
|
|
<form onSubmit={onSubmit} className="space-y-6">
|
|
<div className="grid grid-cols-1 gap-6 md:grid-cols-2">
|
|
{/* Basic Information */}
|
|
<div className="space-y-2">
|
|
<Label htmlFor="code">Kode Prosedur*</Label>
|
|
<Input
|
|
id="code"
|
|
value={data.code}
|
|
onChange={(e) => setData('code', e.target.value)}
|
|
placeholder="PR-001, LAB-002"
|
|
/>
|
|
<InputError message={errors.code} />
|
|
</div>
|
|
|
|
<div className="space-y-2">
|
|
<Label htmlFor="name">Nama Prosedur*</Label>
|
|
<Input
|
|
id="name"
|
|
value={data.name}
|
|
onChange={(e) => setData('name', e.target.value)}
|
|
placeholder="Pemeriksaan Darah, Operasi Katarak"
|
|
/>
|
|
<InputError message={errors.name} />
|
|
</div>
|
|
|
|
<div className="space-y-2">
|
|
<Label htmlFor="category">Kategori*</Label>
|
|
<Select
|
|
value={data.category}
|
|
onValueChange={(value) => setData('category', value)}
|
|
>
|
|
<SelectTrigger>
|
|
<SelectValue placeholder="Pilih kategori prosedur" />
|
|
</SelectTrigger>
|
|
<SelectContent>
|
|
{categories.map((category) => (
|
|
<SelectItem key={category} value={category}>
|
|
{category}
|
|
</SelectItem>
|
|
))}
|
|
</SelectContent>
|
|
</Select>
|
|
<InputError message={errors.category} />
|
|
</div>
|
|
|
|
<div className="space-y-2">
|
|
<Label htmlFor="base_price">Harga Dasar (Rp)*</Label>
|
|
<Input
|
|
id="base_price"
|
|
value={formatCurrency(data.base_price)}
|
|
onChange={handlePriceChange}
|
|
placeholder="500.000"
|
|
/>
|
|
<InputError message={errors.base_price} />
|
|
</div>
|
|
|
|
{/* Description */}
|
|
<div className="space-y-2 col-span-full">
|
|
<Label htmlFor="description">Deskripsi</Label>
|
|
<Textarea
|
|
id="description"
|
|
value={data.description}
|
|
onChange={(e) => setData('description', e.target.value)}
|
|
placeholder="Deskripsi lengkap prosedur..."
|
|
rows={3}
|
|
/>
|
|
<InputError message={errors.description} />
|
|
</div>
|
|
|
|
{/* Tax Settings */}
|
|
<div className="flex items-center space-x-2 col-span-full">
|
|
<input
|
|
type="checkbox"
|
|
id="is_taxable"
|
|
checked={data.is_taxable}
|
|
onChange={(e) => setData('is_taxable', e.target.checked)}
|
|
className="h-4 w-4 rounded border-gray-300 text-indigo-600 focus:ring-indigo-500"
|
|
/>
|
|
<Label htmlFor="is_taxable">Kena Pajak</Label>
|
|
<InputError message={errors.is_taxable} />
|
|
</div>
|
|
|
|
{data.is_taxable && (
|
|
<div className="space-y-2">
|
|
<Label htmlFor="tax_percentage">Persentase Pajak (%)*</Label>
|
|
<Input
|
|
id="tax_percentage"
|
|
type="number"
|
|
min="0"
|
|
max="100"
|
|
step="0.01"
|
|
value={data.tax_percentage}
|
|
onChange={(e) => setData('tax_percentage', parseFloat(e.target.value) || 0)}
|
|
/>
|
|
<InputError message={errors.tax_percentage} />
|
|
</div>
|
|
)}
|
|
|
|
{/* Other Settings */}
|
|
<div className="flex items-center space-x-2">
|
|
<input
|
|
type="checkbox"
|
|
id="is_discountable"
|
|
checked={data.is_discountable}
|
|
onChange={(e) => setData('is_discountable', e.target.checked)}
|
|
className="h-4 w-4 rounded border-gray-300 text-indigo-600 focus:ring-indigo-500"
|
|
/>
|
|
<Label htmlFor="is_discountable">Dapat Diskon</Label>
|
|
<InputError message={errors.is_discountable} />
|
|
</div>
|
|
|
|
<div className="flex items-center space-x-2">
|
|
<input
|
|
type="checkbox"
|
|
id="requires_approval"
|
|
checked={data.requires_approval}
|
|
onChange={(e) => setData('requires_approval', e.target.checked)}
|
|
className="h-4 w-4 rounded border-gray-300 text-indigo-600 focus:ring-indigo-500"
|
|
/>
|
|
<Label htmlFor="requires_approval">Perlu Persetujuan</Label>
|
|
<InputError message={errors.requires_approval} />
|
|
</div>
|
|
|
|
{mode === 'edit' && (
|
|
<div className="flex items-center space-x-2">
|
|
<input
|
|
type="checkbox"
|
|
id="is_active"
|
|
checked={data.is_active}
|
|
onChange={(e) => setData('is_active', e.target.checked)}
|
|
className="h-4 w-4 rounded border-gray-300 text-indigo-600 focus:ring-indigo-500"
|
|
/>
|
|
<Label htmlFor="is_active">Aktif</Label>
|
|
<InputError message={errors.is_active} />
|
|
</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'} Prosedur
|
|
</Button>
|
|
</div>
|
|
</form>
|
|
</div>
|
|
</div>
|
|
</AppLayout>
|
|
);
|
|
}
|