feat: add complete action for cuttings with notification and update route

This commit is contained in:
Yoga Pangestu 2026-08-14 22:13:22 +07:00
parent 7c160cb571
commit 703cdb8b8b
5 changed files with 63 additions and 3 deletions

View File

@ -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([

View File

@ -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;
}
}

View File

@ -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')) && (
<RowActions
actions={[
{
label: 'Selesaikan',
icon: <ScissorsIcon className="h-4 w-4" />,
show: can('cuttings.complete') && cutting.status === 'in_progress',
onClick: () => onComplete(cutting),
},
{
label: 'Edit',
icon: <Pencil className="h-4 w-4" />,

View File

@ -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<Cutting | null>(null);
const [completing, setCompleting] = useState<Cutting | null>(null);
const [loadedMaterials, setLoadedMaterials] = useState<Record<number, CuttingMaterialDetail[]>>({});
const [loadedCombinations, setLoadedCombinations] = useState<Record<number, CuttingCombination[]>>({});
const [loadingMaterials, setLoadingMaterials] = useState<Record<number, boolean>>({});
@ -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}
/>
<DeleteConfirmDialog
target={completing}
onOpenChange={(open) => {
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}
/>
</div>
</>
);

View File

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