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');