feat: update validation rules in CuttingRequest and enhance error handling in CuttingCreate and CuttingEdit components
This commit is contained in:
parent
b45052dc55
commit
aaafbb92a2
@ -16,16 +16,16 @@ public function rules(): array
|
||||
return [
|
||||
'description' => ['nullable', 'string', 'max:100'],
|
||||
'product_name' => ['required', 'string', 'max:255'],
|
||||
'sample' => ['required', 'integer', 'min:0'],
|
||||
'original_outside_sample' => ['required', 'integer', 'min:0'],
|
||||
'cutting_result' => ['required', 'integer', 'min:0'],
|
||||
'sample' => ['required', 'integer'],
|
||||
'original_outside_sample' => ['required', 'integer'],
|
||||
'cutting_result' => ['required', 'integer', 'min:1'],
|
||||
'materials' => ['required', 'array', 'min:1'],
|
||||
'materials.*.raw_material_price_id' => ['required', 'integer', 'exists:raw_material_prices,id'],
|
||||
'materials.*.material_usage' => ['required', 'integer', 'min:0'],
|
||||
'materials.*.material_result' => ['required', 'integer', 'min:0'],
|
||||
'materials.*.combination_index' => ['nullable', 'integer', 'min:0'],
|
||||
'materials.*.material_usage' => ['required', 'integer', 'min:1'],
|
||||
'materials.*.material_result' => ['required', 'integer', 'min:1'],
|
||||
'materials.*.combination_index' => ['nullable', 'integer', 'min:1'],
|
||||
'combinations' => ['nullable', 'array'],
|
||||
'combinations.*.material_result' => ['nullable', 'integer', 'min:0'],
|
||||
'combinations.*.material_result' => ['nullable', 'integer', 'min:1'],
|
||||
'photo_key' => ['nullable', 'string', 'max:500'],
|
||||
];
|
||||
}
|
||||
|
||||
@ -1,8 +1,5 @@
|
||||
'use no memo';
|
||||
|
||||
import { Form, Head, Link, usePage } from '@inertiajs/react';
|
||||
import { ArrowLeft, Check, Layers, Plus, ShoppingCart, Trash2 } from 'lucide-react';
|
||||
import { useCallback, useMemo, useRef, useState } from 'react';
|
||||
import { ConfirmDialog } from '@/components/confirm-dialog';
|
||||
import { FileUpload } from '@/components/file-upload';
|
||||
import { ImagePreviewModal } from '@/components/image-preview-modal';
|
||||
@ -22,6 +19,9 @@ import { formatNumber } from '@/lib/format';
|
||||
import { getTemporaryUrl } from '@/lib/upload';
|
||||
import { formatCurrency } from '@/lib/utils';
|
||||
import { index as cuttingIndex, store } from '@/routes/admin/manage/cuttings';
|
||||
import { Form, Head, Link, usePage } from '@inertiajs/react';
|
||||
import { ArrowLeft, Check, Layers, Plus, ShoppingCart, Trash2 } from 'lucide-react';
|
||||
import { useCallback, useMemo, useRef, useState } from 'react';
|
||||
import type { CuttingCreateData } from './columns';
|
||||
|
||||
type MaterialState = {
|
||||
@ -45,7 +45,7 @@ type Props = {
|
||||
|
||||
export default function CuttingCreate({ data }: Props) {
|
||||
const { rawMaterials } = data;
|
||||
const { auth } = usePage().props as { auth: { user?: { id?: number } } };
|
||||
const { auth, errors } = usePage().props as { auth: { user?: { id?: number } }; errors: Record<string, string> };
|
||||
const userId = auth.user?.id;
|
||||
|
||||
const draft = loadCuttingDraft('create', userId);
|
||||
@ -284,9 +284,9 @@ return;
|
||||
return {
|
||||
description: notes || null,
|
||||
product_name: productName || null,
|
||||
sample: sample || null,
|
||||
original_outside_sample: originalOutsideSample || null,
|
||||
cutting_result: cuttingResult || null,
|
||||
sample: sample,
|
||||
original_outside_sample: originalOutsideSample,
|
||||
cutting_result: cuttingResult,
|
||||
materials: materialsRef.current.map((m) => ({
|
||||
raw_material_price_id: m.raw_material_price_id,
|
||||
material_usage: m.material_usage,
|
||||
@ -390,8 +390,6 @@ return;
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
|
||||
</CardContent>
|
||||
</Card>
|
||||
</div>
|
||||
@ -462,7 +460,7 @@ return;
|
||||
<InputError message={errors.photo_key} />
|
||||
</div>
|
||||
|
||||
<Button type="submit" className="w-full" disabled={processing || submittingRef.current || uploading || materials.length === 0 || !productName || !sample}>
|
||||
<Button type="submit" className="w-full" disabled={processing}>
|
||||
{processing ? 'Menyimpan...' : 'Simpan'}
|
||||
</Button>
|
||||
</CardContent>
|
||||
@ -513,6 +511,7 @@ comboMap.set(m.combination_id, []);
|
||||
return groups.map((group, gi) => (
|
||||
<div key={gi} className="space-y-2 rounded-lg border p-3">
|
||||
{group.comboIndex !== null && (
|
||||
<>
|
||||
<div className="flex items-center justify-between border-b pb-2">
|
||||
<div className="flex items-center gap-2">
|
||||
<span className="text-xs font-semibold text-muted-foreground">Kombinasi {group.comboIndex + 1}</span>
|
||||
@ -531,6 +530,8 @@ comboMap.set(m.combination_id, []);
|
||||
<Trash2 className="h-4 w-4 text-destructive" />
|
||||
</Button>
|
||||
</div>
|
||||
<InputError message={errors[`combinations.${group.comboIndex}.material_result` as keyof typeof errors]} />
|
||||
</>
|
||||
)}
|
||||
{group.items.map(({ m, index }) => {
|
||||
const price = priceMap.get(m.raw_material_price_id);
|
||||
@ -569,6 +570,7 @@ comboMap.set(m.combination_id, []);
|
||||
onValueChange={(val) => updateMaterial(index, 'material_usage', val)}
|
||||
/>
|
||||
</div>
|
||||
<InputError message={errors[`materials.${index}.material_usage` as keyof typeof errors]} />
|
||||
{group.comboIndex === null && (
|
||||
<div className="flex items-center gap-2">
|
||||
<span className="text-xs text-muted-foreground whitespace-nowrap">Hasil <span className="text-destructive">*</span></span>
|
||||
@ -579,6 +581,7 @@ comboMap.set(m.combination_id, []);
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
{group.comboIndex === null && <InputError message={errors[`materials.${index}.material_result` as keyof typeof errors]} />}
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
|
||||
@ -1,8 +1,5 @@
|
||||
'use no memo';
|
||||
|
||||
import { Form, Head, Link } from '@inertiajs/react';
|
||||
import { ArrowLeft, Check, Layers, Plus, ShoppingCart, Trash2 } from 'lucide-react';
|
||||
import { useCallback, useMemo, useRef, useState } from 'react';
|
||||
import { ConfirmDialog } from '@/components/confirm-dialog';
|
||||
import { FileUpload } from '@/components/file-upload';
|
||||
import { ImagePreviewModal } from '@/components/image-preview-modal';
|
||||
@ -20,6 +17,9 @@ import { formatNumber } from '@/lib/format';
|
||||
import { getTemporaryUrl } from '@/lib/upload';
|
||||
import { formatCurrency } from '@/lib/utils';
|
||||
import { index as cuttingIndex, update } from '@/routes/admin/manage/cuttings';
|
||||
import { Form, Head, Link, usePage } from '@inertiajs/react';
|
||||
import { ArrowLeft, Check, Layers, Plus, ShoppingCart, Trash2 } from 'lucide-react';
|
||||
import { useCallback, useMemo, useRef, useState } from 'react';
|
||||
import type { CuttingCreateData, CuttingForEdit } from './columns';
|
||||
|
||||
type MaterialState = {
|
||||
@ -46,6 +46,7 @@ type Props = {
|
||||
|
||||
export default function CuttingEdit({ cutting, data }: Props) {
|
||||
const { rawMaterials } = data;
|
||||
const { errors } = usePage().props as { errors: Record<string, string> };
|
||||
|
||||
const [materials, setMaterials] = useState<MaterialState[]>(() => {
|
||||
const comboIdToIndex = new Map<number, number>();
|
||||
@ -259,9 +260,9 @@ return;
|
||||
return {
|
||||
description: notes || null,
|
||||
product_name: productName || null,
|
||||
sample: sample || null,
|
||||
original_outside_sample: originalOutsideSample || null,
|
||||
cutting_result: cuttingResult || null,
|
||||
sample: sample,
|
||||
original_outside_sample: originalOutsideSample,
|
||||
cutting_result: cuttingResult,
|
||||
materials: materialsRef.current.map((m) => ({
|
||||
raw_material_price_id: m.raw_material_price_id,
|
||||
material_usage: m.material_usage,
|
||||
@ -435,7 +436,7 @@ return;
|
||||
<InputError message={errors.photo_key} />
|
||||
</div>
|
||||
|
||||
<Button type="submit" className="w-full" disabled={processing || submittingRef.current || uploading || materials.length === 0 || !productName || !sample}>
|
||||
<Button type="submit" className="w-full" disabled={processing}>
|
||||
{processing ? 'Menyimpan...' : 'Simpan'}
|
||||
</Button>
|
||||
</CardContent>
|
||||
@ -486,6 +487,7 @@ comboMap.set(m.combination_id, []);
|
||||
return groups.map((group, gi) => (
|
||||
<div key={gi} className="space-y-2 rounded-lg border p-3">
|
||||
{group.comboIndex !== null && (
|
||||
<>
|
||||
<div className="flex items-center justify-between border-b pb-2">
|
||||
<div className="flex items-center gap-2">
|
||||
<span className="text-xs font-semibold text-muted-foreground">Kombinasi {group.comboIndex + 1}</span>
|
||||
@ -504,6 +506,8 @@ comboMap.set(m.combination_id, []);
|
||||
<Trash2 className="h-4 w-4 text-destructive" />
|
||||
</Button>
|
||||
</div>
|
||||
<InputError message={errors[`combinations.${group.comboIndex}.material_result` as keyof typeof errors]} />
|
||||
</>
|
||||
)}
|
||||
{group.items.map(({ m, index }) => {
|
||||
const price = priceMap.get(m.raw_material_price_id);
|
||||
@ -542,6 +546,7 @@ comboMap.set(m.combination_id, []);
|
||||
onValueChange={(val) => updateMaterial(index, 'material_usage', val)}
|
||||
/>
|
||||
</div>
|
||||
<InputError message={errors[`materials.${index}.material_usage` as keyof typeof errors]} />
|
||||
{group.comboIndex === null && (
|
||||
<div className="flex items-center gap-2">
|
||||
<span className="text-xs text-muted-foreground whitespace-nowrap">Hasil <span className="text-destructive">*</span></span>
|
||||
@ -552,6 +557,7 @@ comboMap.set(m.combination_id, []);
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
{group.comboIndex === null && <InputError message={errors[`materials.${index}.material_result` as keyof typeof errors]} />}
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
|
||||
Loading…
Reference in New Issue
Block a user