diff --git a/app/Http/Requests/Admin/Manage/CuttingRequest.php b/app/Http/Requests/Admin/Manage/CuttingRequest.php index 5cafaca..e45cc22 100644 --- a/app/Http/Requests/Admin/Manage/CuttingRequest.php +++ b/app/Http/Requests/Admin/Manage/CuttingRequest.php @@ -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'], ]; } diff --git a/resources/js/pages/admin/manage/cutting/create.tsx b/resources/js/pages/admin/manage/cutting/create.tsx index b29619b..d449917 100644 --- a/resources/js/pages/admin/manage/cutting/create.tsx +++ b/resources/js/pages/admin/manage/cutting/create.tsx @@ -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 }; const userId = auth.user?.id; const draft = loadCuttingDraft('create', userId); @@ -152,19 +152,19 @@ export default function CuttingCreate({ data }: Props) { const addVariant = useCallback( (priceId: number) => { if (!selectedMaterial) { -return; -} + return; + } const price = selectedMaterial.raw_material_prices.find((p) => p.id === priceId); if (!price) { -return; -} + return; + } setMaterials((prev) => { if (prev.some((m) => m.raw_material_price_id === priceId && m.combination_id === null)) { -return prev; -} + return prev; + } return [ ...prev, @@ -199,8 +199,8 @@ return prev; const confirmCombo = useCallback(() => { if (comboSelectedPriceIds.length < 2) { -return; -} + return; + } const comboIndex = combinations.length; @@ -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, @@ -316,8 +316,8 @@ return;
({ ...data, ...getPayload() })} onSubmit={() => { - submittingRef.current = true; -}}> + submittingRef.current = true; + }}> {({ errors, processing }) => (
@@ -390,8 +390,6 @@ return;
)} - - @@ -457,12 +455,12 @@ return;
{ - setPhoto(key); setPhotoUrl(key ? getTemporaryUrl(key) : null); -}} folder="cutting" existingUrl={photoUrl} onUploadingChange={setUploading} /> + setPhoto(key); setPhotoUrl(key ? getTemporaryUrl(key) : null); + }} folder="cutting" existingUrl={photoUrl} onUploadingChange={setUploading} />
- @@ -498,8 +496,8 @@ return; materials.forEach((m, i) => { if (m.combination_id !== null) { if (!comboMap.has(m.combination_id)) { -comboMap.set(m.combination_id, []); -} + comboMap.set(m.combination_id, []); + } comboMap.get(m.combination_id)!.push({ m, index: i }); } else { @@ -513,24 +511,27 @@ comboMap.set(m.combination_id, []); return groups.map((group, gi) => (
{group.comboIndex !== null && ( -
-
- Kombinasi {group.comboIndex + 1} - · - - updateCombinationResult(group.comboIndex!, val)} - /> + <> +
+
+ Kombinasi {group.comboIndex + 1} + · + + updateCombinationResult(group.comboIndex!, val)} + /> +
+
- -
+ + )} {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)} />
+ {group.comboIndex === null && (
Hasil * @@ -579,6 +581,7 @@ comboMap.set(m.combination_id, []); />
)} + {group.comboIndex === null && }
); })} @@ -599,10 +602,10 @@ comboMap.set(m.combination_id, []); { - if (!open) { -setPreviewKey(null); -} -}} + if (!open) { + setPreviewKey(null); + } + }} src={previewKey && previewKey.startsWith('material-') ? (materials[Number(previewKey.replace('material-', ''))]?.photo_url ?? null) : null} title={previewKey && previewKey.startsWith('material-') ? `${materials[Number(previewKey.replace('material-', ''))]?.material_name} — ${materials[Number(previewKey.replace('material-', ''))]?.variant}` : undefined} sources={materials.filter((m) => m.photo_url).map((m) => m.photo_url!)} @@ -725,40 +728,40 @@ setPreviewKey(null); { - if (!open) { - setDeleteConfirmOpen(false); setDeleteMaterialIndex(null); -} -}} title="Hapus Bahan Baku" description="Apakah Anda yakin ingin menghapus bahan baku ini?" confirmLabel="Hapus" onConfirm={() => { - if (deleteMaterialIndex !== null) { -setMaterials((prev) => prev.filter((_, i) => i !== deleteMaterialIndex)); -} + if (!open) { + setDeleteConfirmOpen(false); setDeleteMaterialIndex(null); + } + }} title="Hapus Bahan Baku" description="Apakah Anda yakin ingin menghapus bahan baku ini?" confirmLabel="Hapus" onConfirm={() => { + if (deleteMaterialIndex !== null) { + setMaterials((prev) => prev.filter((_, i) => i !== deleteMaterialIndex)); + } - setDeleteConfirmOpen(false); setDeleteMaterialIndex(null); -}} /> + setDeleteConfirmOpen(false); setDeleteMaterialIndex(null); + }} /> { - if (!open) { - setCartDeleteConfirmOpen(false); setCartDeleteIndex(null); -} -}} title="Hapus dari Keranjang" description="Apakah Anda yakin ingin menghapus item ini dari keranjang?" confirmLabel="Hapus" variant="destructive" onConfirm={() => { - if (cartDeleteIndex !== null) { -setMaterials((prev) => prev.filter((_, i) => i !== cartDeleteIndex)); -} + if (!open) { + setCartDeleteConfirmOpen(false); setCartDeleteIndex(null); + } + }} title="Hapus dari Keranjang" description="Apakah Anda yakin ingin menghapus item ini dari keranjang?" confirmLabel="Hapus" variant="destructive" onConfirm={() => { + if (cartDeleteIndex !== null) { + setMaterials((prev) => prev.filter((_, i) => i !== cartDeleteIndex)); + } - setCartDeleteConfirmOpen(false); setCartDeleteIndex(null); -}} /> + setCartDeleteConfirmOpen(false); setCartDeleteIndex(null); + }} /> { - if (!open) { - setComboDeleteConfirmOpen(false); setComboDeleteIndex(null); -} -}} title="Hapus Kombinasi" description="Apakah Anda yakin ingin menghapus kombinasi ini beserta semua bahannya?" confirmLabel="Hapus" variant="destructive" onConfirm={() => { - if (comboDeleteIndex !== null) { - setCombinations((prev) => prev.filter((_, i) => i !== comboDeleteIndex)); setMaterials((prev) => prev.filter((m) => m.combination_id !== comboDeleteIndex)); -} + if (!open) { + setComboDeleteConfirmOpen(false); setComboDeleteIndex(null); + } + }} title="Hapus Kombinasi" description="Apakah Anda yakin ingin menghapus kombinasi ini beserta semua bahannya?" confirmLabel="Hapus" variant="destructive" onConfirm={() => { + if (comboDeleteIndex !== null) { + setCombinations((prev) => prev.filter((_, i) => i !== comboDeleteIndex)); setMaterials((prev) => prev.filter((m) => m.combination_id !== comboDeleteIndex)); + } - setComboDeleteConfirmOpen(false); setComboDeleteIndex(null); -}} /> + setComboDeleteConfirmOpen(false); setComboDeleteIndex(null); + }} /> ); diff --git a/resources/js/pages/admin/manage/cutting/edit.tsx b/resources/js/pages/admin/manage/cutting/edit.tsx index b272a8a..707edc5 100644 --- a/resources/js/pages/admin/manage/cutting/edit.tsx +++ b/resources/js/pages/admin/manage/cutting/edit.tsx @@ -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 }; const [materials, setMaterials] = useState(() => { const comboIdToIndex = new Map(); @@ -132,19 +133,19 @@ export default function CuttingEdit({ cutting, data }: Props) { const addVariant = useCallback( (priceId: number) => { if (!selectedMaterial) { -return; -} + return; + } const price = selectedMaterial.raw_material_prices.find((p) => p.id === priceId); if (!price) { -return; -} + return; + } setMaterials((prev) => { if (prev.some((m) => m.raw_material_price_id === priceId && m.combination_id === null)) { -return prev; -} + return prev; + } return [ ...prev, @@ -179,8 +180,8 @@ return prev; const confirmCombo = useCallback(() => { if (comboSelectedPriceIds.length < 2) { -return; -} + return; + } const comboIndex = combinations.length; @@ -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, @@ -291,8 +292,8 @@ return; ({ ...data, ...getPayload() })} onSubmit={() => { - submittingRef.current = true; -}}> + submittingRef.current = true; + }}> {({ errors, processing }) => (
@@ -430,12 +431,12 @@ return;
{ - setPhoto(key); setPhotoUrl(key ? getTemporaryUrl(key) : null); -}} folder="cutting" existingUrl={photoUrl} onUploadingChange={setUploading} /> + setPhoto(key); setPhotoUrl(key ? getTemporaryUrl(key) : null); + }} folder="cutting" existingUrl={photoUrl} onUploadingChange={setUploading} />
- @@ -471,8 +472,8 @@ return; materials.forEach((m, i) => { if (m.combination_id !== null) { if (!comboMap.has(m.combination_id)) { -comboMap.set(m.combination_id, []); -} + comboMap.set(m.combination_id, []); + } comboMap.get(m.combination_id)!.push({ m, index: i }); } else { @@ -486,24 +487,27 @@ comboMap.set(m.combination_id, []); return groups.map((group, gi) => (
{group.comboIndex !== null && ( -
-
- Kombinasi {group.comboIndex + 1} - · - - updateCombinationResult(group.comboIndex!, val)} - /> + <> +
+
+ Kombinasi {group.comboIndex + 1} + · + + updateCombinationResult(group.comboIndex!, val)} + /> +
+
- -
+ + )} {group.items.map(({ m, index }) => { const price = priceMap.get(m.raw_material_price_id); @@ -535,23 +539,25 @@ comboMap.set(m.combination_id, []); )}
- Pemakaian * - updateMaterial(index, 'material_usage', val)} - /> -
- {group.comboIndex === null && ( -
- Hasil * - updateMaterial(index, 'material_result', val)} - /> -
- )} + Pemakaian * + updateMaterial(index, 'material_usage', val)} + /> +
+ + {group.comboIndex === null && ( +
+ Hasil * + updateMaterial(index, 'material_result', val)} + /> +
+ )} + {group.comboIndex === null && }
); })} @@ -572,10 +578,10 @@ comboMap.set(m.combination_id, []); { - if (!open) { -setPreviewKey(null); -} -}} + if (!open) { + setPreviewKey(null); + } + }} src={previewKey && previewKey.startsWith('material-') ? (materials[Number(previewKey.replace('material-', ''))]?.photo_url ?? null) : null} title={previewKey && previewKey.startsWith('material-') ? `${materials[Number(previewKey.replace('material-', ''))]?.material_name} — ${materials[Number(previewKey.replace('material-', ''))]?.variant}` : undefined} sources={materials.filter((m) => m.photo_url).map((m) => m.photo_url!)} @@ -698,40 +704,40 @@ setPreviewKey(null); { - if (!open) { - setDeleteConfirmOpen(false); setDeleteMaterialIndex(null); -} -}} title="Hapus Bahan Baku" description="Apakah Anda yakin ingin menghapus bahan baku ini?" confirmLabel="Hapus" onConfirm={() => { - if (deleteMaterialIndex !== null) { -setMaterials((prev) => prev.filter((_, i) => i !== deleteMaterialIndex)); -} + if (!open) { + setDeleteConfirmOpen(false); setDeleteMaterialIndex(null); + } + }} title="Hapus Bahan Baku" description="Apakah Anda yakin ingin menghapus bahan baku ini?" confirmLabel="Hapus" onConfirm={() => { + if (deleteMaterialIndex !== null) { + setMaterials((prev) => prev.filter((_, i) => i !== deleteMaterialIndex)); + } - setDeleteConfirmOpen(false); setDeleteMaterialIndex(null); -}} /> + setDeleteConfirmOpen(false); setDeleteMaterialIndex(null); + }} /> { - if (!open) { - setCartDeleteConfirmOpen(false); setCartDeleteIndex(null); -} -}} title="Hapus dari Keranjang" description="Apakah Anda yakin ingin menghapus item ini dari keranjang?" confirmLabel="Hapus" variant="destructive" onConfirm={() => { - if (cartDeleteIndex !== null) { -setMaterials((prev) => prev.filter((_, i) => i !== cartDeleteIndex)); -} + if (!open) { + setCartDeleteConfirmOpen(false); setCartDeleteIndex(null); + } + }} title="Hapus dari Keranjang" description="Apakah Anda yakin ingin menghapus item ini dari keranjang?" confirmLabel="Hapus" variant="destructive" onConfirm={() => { + if (cartDeleteIndex !== null) { + setMaterials((prev) => prev.filter((_, i) => i !== cartDeleteIndex)); + } - setCartDeleteConfirmOpen(false); setCartDeleteIndex(null); -}} /> + setCartDeleteConfirmOpen(false); setCartDeleteIndex(null); + }} /> { - if (!open) { - setComboDeleteConfirmOpen(false); setComboDeleteIndex(null); -} -}} title="Hapus Kombinasi" description="Apakah Anda yakin ingin menghapus kombinasi ini beserta semua bahannya?" confirmLabel="Hapus" variant="destructive" onConfirm={() => { - if (comboDeleteIndex !== null) { - setCombinations((prev) => prev.filter((_, i) => i !== comboDeleteIndex)); setMaterials((prev) => prev.filter((m) => m.combination_id !== comboDeleteIndex)); -} + if (!open) { + setComboDeleteConfirmOpen(false); setComboDeleteIndex(null); + } + }} title="Hapus Kombinasi" description="Apakah Anda yakin ingin menghapus kombinasi ini beserta semua bahannya?" confirmLabel="Hapus" variant="destructive" onConfirm={() => { + if (comboDeleteIndex !== null) { + setCombinations((prev) => prev.filter((_, i) => i !== comboDeleteIndex)); setMaterials((prev) => prev.filter((m) => m.combination_id !== comboDeleteIndex)); + } - setComboDeleteConfirmOpen(false); setComboDeleteIndex(null); -}} /> + setComboDeleteConfirmOpen(false); setComboDeleteIndex(null); + }} />
);