diff --git a/app/Http/Controllers/Admin/Manage/CuttingController.php b/app/Http/Controllers/Admin/Manage/CuttingController.php index bba7b93..bc11855 100644 --- a/app/Http/Controllers/Admin/Manage/CuttingController.php +++ b/app/Http/Controllers/Admin/Manage/CuttingController.php @@ -95,6 +95,15 @@ public function destroy(Cutting $cutting): RedirectResponse ); } + public function complete(Cutting $cutting): RedirectResponse + { + return $this->handleAction( + fn () => $this->service->complete($cutting), + 'Cutting berhasil diselesaikan.', + 'admin.manage.cuttings.index' + ); + } + public function materials(Cutting $cutting): JsonResponse { return response()->json([ diff --git a/app/Services/Admin/Manage/CuttingService.php b/app/Services/Admin/Manage/CuttingService.php index cfb6f88..95a5d22 100644 --- a/app/Services/Admin/Manage/CuttingService.php +++ b/app/Services/Admin/Manage/CuttingService.php @@ -2,6 +2,7 @@ namespace App\Services\Admin\Manage; +use App\Enums\CuttingStatus; use App\Enums\Role; use App\Models\Cutting; use App\Models\CuttingMaterial; @@ -500,4 +501,18 @@ public function destroy(Cutting $cutting): bool return $result; } + + public function complete(Cutting $cutting): Cutting + { + $cutting->update(['status' => CuttingStatus::COMPLETED]); + + NotificationService::notify( + roles: [Role::OWNER, Role::DEVELOPER, Role::ADMIN_BAHAN_BAKU], + title: 'Cutting Selesai', + body: 'Cutting berhasil diselesaikan oleh '.auth()->user()->full_name.'.', + url: route('admin.manage.cuttings.index'), + ); + + return $cutting; + } } diff --git a/resources/js/pages/admin/manage/cutting/cutting-card.tsx b/resources/js/pages/admin/manage/cutting/cutting-card.tsx index 2c82e6b..db6745a 100644 --- a/resources/js/pages/admin/manage/cutting/cutting-card.tsx +++ b/resources/js/pages/admin/manage/cutting/cutting-card.tsx @@ -1,5 +1,3 @@ -import { Briefcase, ChevronDown, Copy, MessageCircle, Pencil, Share2, Trash2 } from 'lucide-react'; -import { useState } from 'react'; import { RowActions } from '@/components/data-display'; import { ImagePreviewButton } from '@/components/dialogs'; import { Button } from '@/components/ui/button'; @@ -13,6 +11,8 @@ import { Tooltip, TooltipContent, TooltipTrigger } from '@/components/ui/tooltip import { useCan } from '@/hooks/use-can'; import { formatNumber } from '@/lib/format'; import { share as cuttingShare } from '@/routes/admin/manage/cuttings'; +import { ChevronDown, Pencil, ScissorsIcon, Share2, Trash2 } from 'lucide-react'; +import { useState } from 'react'; import type { Cutting } from './columns'; function generateShareLink(cuttingId: number): string { @@ -90,6 +90,7 @@ export type CuttingCardRowParams = { index: number; isExpanded: boolean; onToggleExpand: () => void; + onComplete: (cutting: Cutting) => void; onEdit: (cutting: Cutting) => void; onDelete: (cutting: Cutting) => void; }; @@ -99,12 +100,12 @@ export function CuttingCardRow({ index, isExpanded, onToggleExpand, + onComplete, onEdit, onDelete, }: CuttingCardRowParams) { const { can } = useCan(); const [shareOpen, setShareOpen] = useState(false); - const [copied, setCopied] = useState(false); const materialsCount = cutting.materials_count ?? 0; const totalUsage = cutting.total_usage ?? 0; const productName = cutting.product_name ?? '-'; @@ -254,6 +255,12 @@ export function CuttingCardRow({ {(can('cuttings.update') || can('cuttings.delete')) && ( , + show: can('cuttings.complete') && cutting.status === 'in_progress', + onClick: () => onComplete(cutting), + }, { label: 'Edit', icon: , diff --git a/resources/js/pages/admin/manage/cutting/index.tsx b/resources/js/pages/admin/manage/cutting/index.tsx index 17978a2..4551ba8 100644 --- a/resources/js/pages/admin/manage/cutting/index.tsx +++ b/resources/js/pages/admin/manage/cutting/index.tsx @@ -30,6 +30,7 @@ import { index as cuttingIndex, edit as cuttingEdit, materials as cuttingMaterials, + complete as cuttingComplete, } from '@/routes/admin/manage/cuttings'; import type { Cutting, CuttingMaterialDetail, CuttingCombination } from './columns'; import { CuttingCardRow } from './cutting-card'; @@ -60,6 +61,7 @@ export default function CuttingIndex({ }: Props) { const { can } = useCan(); const [deleting, setDeleting] = useState(null); + const [completing, setCompleting] = useState(null); const [loadedMaterials, setLoadedMaterials] = useState>({}); const [loadedCombinations, setLoadedCombinations] = useState>({}); const [loadingMaterials, setLoadingMaterials] = useState>({}); @@ -100,6 +102,16 @@ export default function CuttingIndex({ }); } + function handleComplete() { + if (!completing) { + return; + } + + router.patch(cuttingComplete.url(completing.id), {}, { + onSuccess: () => setCompleting(null), + }); + } + const selectedProductName = useMemo( () => filterOptions.productNames.find( @@ -261,6 +273,7 @@ export default function CuttingIndex({ } isExpanded={isExpanded} onToggleExpand={onToggleExpand} + onComplete={(c) => setCompleting(c)} onEdit={(c) => { router.visit(cuttingEdit.url(c.id)); }} @@ -290,6 +303,21 @@ export default function CuttingIndex({ } onConfirm={handleDelete} /> + + { + if (!open) { + setCompleting(null); + } + }} + title="Selesaikan Cutting" + description={(cutting) => + `Apakah Anda yakin ingin menyelesaikan cutting "${cutting.cutting_results?.[0]?.product_name ?? '-'}"?` + } + confirmLabel="Selesaikan" + onConfirm={handleComplete} + /> ); diff --git a/routes/web.php b/routes/web.php index e95931f..90c3bf6 100644 --- a/routes/web.php +++ b/routes/web.php @@ -72,6 +72,7 @@ Route::resource('cuttings', CuttingController::class)->middleware('permission:cuttings.view|cuttings.create|cuttings.update|cuttings.delete'); Route::get('cuttings/{cutting}/materials', [CuttingController::class, 'materials'])->name('cuttings.materials')->middleware('permission:cuttings.view'); + Route::patch('cuttings/{cutting}/complete', [CuttingController::class, 'complete'])->name('cuttings.complete')->middleware('permission:cuttings.complete'); Route::resource('transactions', TransactionController::class)->except(['show'])->middleware('permission:orders.view|orders.create|orders.update|orders.delete'); Route::patch('transactions/{transaction}/status', [TransactionController::class, 'updateStatus'])->name('transactions.updateStatus')->middleware('permission:orders.update');