refactor: clean up imports and improve code readability across multiple components
This commit is contained in:
parent
bcf5842c4a
commit
62341ac50a
@ -5,7 +5,6 @@
|
|||||||
use Illuminate\Database\Eloquent\Attributes\Guarded;
|
use Illuminate\Database\Eloquent\Attributes\Guarded;
|
||||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||||
use Illuminate\Database\Eloquent\Model;
|
use Illuminate\Database\Eloquent\Model;
|
||||||
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
|
||||||
use Illuminate\Database\Eloquent\Relations\MorphTo;
|
use Illuminate\Database\Eloquent\Relations\MorphTo;
|
||||||
|
|
||||||
#[Guarded(['id'])]
|
#[Guarded(['id'])]
|
||||||
|
|||||||
@ -10,7 +10,6 @@
|
|||||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||||
use Illuminate\Database\Eloquent\Relations\HasMany;
|
use Illuminate\Database\Eloquent\Relations\HasMany;
|
||||||
use Illuminate\Database\Eloquent\Relations\HasOne;
|
use Illuminate\Database\Eloquent\Relations\HasOne;
|
||||||
|
|
||||||
use Illuminate\Database\Eloquent\SoftDeletes;
|
use Illuminate\Database\Eloquent\SoftDeletes;
|
||||||
use Illuminate\Foundation\Auth\User as Authenticatable;
|
use Illuminate\Foundation\Auth\User as Authenticatable;
|
||||||
use Illuminate\Notifications\Notifiable;
|
use Illuminate\Notifications\Notifiable;
|
||||||
|
|||||||
@ -1,3 +1,29 @@
|
|||||||
|
import { Link, router } from '@inertiajs/react';
|
||||||
|
import type { LucideIcon } from 'lucide-react';
|
||||||
|
import {
|
||||||
|
Activity,
|
||||||
|
ArrowUpFromLine,
|
||||||
|
BarChart3,
|
||||||
|
Boxes,
|
||||||
|
CalendarCheck,
|
||||||
|
CalendarDays,
|
||||||
|
ClipboardCheck,
|
||||||
|
DollarSign,
|
||||||
|
HandCoins,
|
||||||
|
LayoutGrid,
|
||||||
|
Package,
|
||||||
|
RefreshCw,
|
||||||
|
Scissors,
|
||||||
|
Settings,
|
||||||
|
Shield,
|
||||||
|
ShoppingCart,
|
||||||
|
Tags,
|
||||||
|
Truck,
|
||||||
|
UserCircle,
|
||||||
|
Users,
|
||||||
|
Wallet,
|
||||||
|
} from 'lucide-react';
|
||||||
|
import React from 'react';
|
||||||
import AppLogo from '@/components/app-logo';
|
import AppLogo from '@/components/app-logo';
|
||||||
import {
|
import {
|
||||||
Sidebar,
|
Sidebar,
|
||||||
@ -30,32 +56,6 @@ import { index as productsIndex } from '@/routes/admin/master/products';
|
|||||||
import { index as rawMaterialsIndex } from '@/routes/admin/master/raw-materials';
|
import { index as rawMaterialsIndex } from '@/routes/admin/master/raw-materials';
|
||||||
import { index as suppliersIndex } from '@/routes/admin/master/suppliers';
|
import { index as suppliersIndex } from '@/routes/admin/master/suppliers';
|
||||||
import { index as rolesIndex } from '@/routes/admin/settings/roles';
|
import { index as rolesIndex } from '@/routes/admin/settings/roles';
|
||||||
import { Link, router } from '@inertiajs/react';
|
|
||||||
import type { LucideIcon } from 'lucide-react';
|
|
||||||
import {
|
|
||||||
Activity,
|
|
||||||
ArrowUpFromLine,
|
|
||||||
BarChart3,
|
|
||||||
Boxes,
|
|
||||||
CalendarCheck,
|
|
||||||
CalendarDays,
|
|
||||||
ClipboardCheck,
|
|
||||||
DollarSign,
|
|
||||||
HandCoins,
|
|
||||||
LayoutGrid,
|
|
||||||
Package,
|
|
||||||
RefreshCw,
|
|
||||||
Scissors,
|
|
||||||
Settings,
|
|
||||||
Shield,
|
|
||||||
ShoppingCart,
|
|
||||||
Tags,
|
|
||||||
Truck,
|
|
||||||
UserCircle,
|
|
||||||
Users,
|
|
||||||
Wallet,
|
|
||||||
} from 'lucide-react';
|
|
||||||
import React from 'react';
|
|
||||||
|
|
||||||
type NavMenuItem = { title: string; href: string; icon: LucideIcon; permission?: string | string[] };
|
type NavMenuItem = { title: string; href: string; icon: LucideIcon; permission?: string | string[] };
|
||||||
|
|
||||||
@ -111,12 +111,20 @@ function MenuGroup({ label, items }: { label: string; items: NavMenuItem[] }) {
|
|||||||
const { can, canAny } = useCan();
|
const { can, canAny } = useCan();
|
||||||
|
|
||||||
const filtered = items.filter((item) => {
|
const filtered = items.filter((item) => {
|
||||||
if (!item.permission) return true;
|
if (!item.permission) {
|
||||||
if (Array.isArray(item.permission)) return canAny(...item.permission);
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Array.isArray(item.permission)) {
|
||||||
|
return canAny(...item.permission);
|
||||||
|
}
|
||||||
|
|
||||||
return can(item.permission);
|
return can(item.permission);
|
||||||
});
|
});
|
||||||
|
|
||||||
if (filtered.length === 0) return null;
|
if (filtered.length === 0) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<SidebarGroup>
|
<SidebarGroup>
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
import { Clock, LogIn, LogOut } from 'lucide-react';
|
import { Clock, LogIn, LogOut } from 'lucide-react';
|
||||||
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';
|
|
||||||
import { Button } from '@/components/ui/button';
|
import { Button } from '@/components/ui/button';
|
||||||
|
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';
|
||||||
|
|
||||||
type TodayAttendance = {
|
type TodayAttendance = {
|
||||||
id: number;
|
id: number;
|
||||||
@ -29,8 +29,12 @@ export function AttendanceCard({ todayAttendance, isOnLeave, canCheckIn, onCheck
|
|||||||
const hasCheckedOut = !!todayAttendance?.check_out_at;
|
const hasCheckedOut = !!todayAttendance?.check_out_at;
|
||||||
|
|
||||||
function formatTime(dateStr: string | null): string {
|
function formatTime(dateStr: string | null): string {
|
||||||
if (!dateStr) return '-';
|
if (!dateStr) {
|
||||||
|
return '-';
|
||||||
|
}
|
||||||
|
|
||||||
const d = new Date(dateStr);
|
const d = new Date(dateStr);
|
||||||
|
|
||||||
return d.toLocaleTimeString('id-ID', { hour: '2-digit', minute: '2-digit', hour12: false });
|
return d.toLocaleTimeString('id-ID', { hour: '2-digit', minute: '2-digit', hour12: false });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -6,8 +6,14 @@ export function useCardTableExpand(
|
|||||||
defaultExpanded: boolean | (number | string)[] = false,
|
defaultExpanded: boolean | (number | string)[] = false,
|
||||||
) {
|
) {
|
||||||
const [expandedKeys, setExpandedKeys] = useState<ExpandState>(() => {
|
const [expandedKeys, setExpandedKeys] = useState<ExpandState>(() => {
|
||||||
if (defaultExpanded === true) return 'all';
|
if (defaultExpanded === true) {
|
||||||
if (Array.isArray(defaultExpanded)) return new Set(defaultExpanded);
|
return 'all';
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Array.isArray(defaultExpanded)) {
|
||||||
|
return new Set(defaultExpanded);
|
||||||
|
}
|
||||||
|
|
||||||
return new Set();
|
return new Set();
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -16,12 +22,15 @@ export function useCardTableExpand(
|
|||||||
if (prev === 'all') {
|
if (prev === 'all') {
|
||||||
return new Set([key]);
|
return new Set([key]);
|
||||||
}
|
}
|
||||||
|
|
||||||
const next = new Set(prev);
|
const next = new Set(prev);
|
||||||
|
|
||||||
if (next.has(key)) {
|
if (next.has(key)) {
|
||||||
next.delete(key);
|
next.delete(key);
|
||||||
} else {
|
} else {
|
||||||
next.add(key);
|
next.add(key);
|
||||||
}
|
}
|
||||||
|
|
||||||
return next;
|
return next;
|
||||||
});
|
});
|
||||||
}, []);
|
}, []);
|
||||||
@ -36,7 +45,10 @@ export function useCardTableExpand(
|
|||||||
|
|
||||||
const isExpanded = useCallback(
|
const isExpanded = useCallback(
|
||||||
(key: number | string): boolean => {
|
(key: number | string): boolean => {
|
||||||
if (expandedKeys === 'all') return true;
|
if (expandedKeys === 'all') {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
return expandedKeys.has(key);
|
return expandedKeys.has(key);
|
||||||
},
|
},
|
||||||
[expandedKeys],
|
[expandedKeys],
|
||||||
|
|||||||
@ -17,7 +17,10 @@ type PageProps = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
function extractNames(items?: RoleOrPermission[]): string[] {
|
function extractNames(items?: RoleOrPermission[]): string[] {
|
||||||
if (!items) return [];
|
if (!items) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
return items.map((item) => (typeof item === 'string' ? item : item.name));
|
return items.map((item) => (typeof item === 'string' ? item : item.name));
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -29,24 +32,42 @@ export function useCan() {
|
|||||||
const permissionNames = extractNames(user?.permissions);
|
const permissionNames = extractNames(user?.permissions);
|
||||||
|
|
||||||
function can(permission: string): boolean {
|
function can(permission: string): boolean {
|
||||||
if (!user) return false;
|
if (!user) {
|
||||||
if (roleNames.includes('developer') || roleNames.includes('owner')) return true;
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (roleNames.includes('developer') || roleNames.includes('owner')) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
return permissionNames.includes(permission);
|
return permissionNames.includes(permission);
|
||||||
}
|
}
|
||||||
|
|
||||||
function canAny(...permissions: string[]): boolean {
|
function canAny(...permissions: string[]): boolean {
|
||||||
if (!user) return false;
|
if (!user) {
|
||||||
if (roleNames.includes('developer') || roleNames.includes('owner')) return true;
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (roleNames.includes('developer') || roleNames.includes('owner')) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
return permissions.some((p) => permissionNames.includes(p));
|
return permissions.some((p) => permissionNames.includes(p));
|
||||||
}
|
}
|
||||||
|
|
||||||
function hasRole(role: string): boolean {
|
function hasRole(role: string): boolean {
|
||||||
if (!user) return false;
|
if (!user) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
return roleNames.includes(role);
|
return roleNames.includes(role);
|
||||||
}
|
}
|
||||||
|
|
||||||
function hasAnyRole(roles: string[]): boolean {
|
function hasAnyRole(roles: string[]): boolean {
|
||||||
if (!user) return false;
|
if (!user) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
return roles.some((role) => roleNames.includes(role));
|
return roles.some((role) => roleNames.includes(role));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -6,12 +6,15 @@ export function formatRupiahShort(value: number): string {
|
|||||||
if (value >= 1_000_000_000) {
|
if (value >= 1_000_000_000) {
|
||||||
return (value / 1_000_000_000).toFixed(1).replace('.0', '') + 'jt';
|
return (value / 1_000_000_000).toFixed(1).replace('.0', '') + 'jt';
|
||||||
}
|
}
|
||||||
|
|
||||||
if (value >= 1_000_000) {
|
if (value >= 1_000_000) {
|
||||||
return (value / 1_000_000).toFixed(1).replace('.0', '') + 'jt';
|
return (value / 1_000_000).toFixed(1).replace('.0', '') + 'jt';
|
||||||
}
|
}
|
||||||
|
|
||||||
if (value >= 1_000) {
|
if (value >= 1_000) {
|
||||||
return (value / 1_000).toFixed(0) + 'rb';
|
return (value / 1_000).toFixed(0) + 'rb';
|
||||||
}
|
}
|
||||||
|
|
||||||
return value.toString();
|
return value.toString();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -1,5 +1,8 @@
|
|||||||
'use no memo';
|
'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 { ConfirmDialog } from '@/components/confirm-dialog';
|
||||||
import { FileUpload } from '@/components/file-upload';
|
import { FileUpload } from '@/components/file-upload';
|
||||||
import { ImagePreviewModal } from '@/components/image-preview-modal';
|
import { ImagePreviewModal } from '@/components/image-preview-modal';
|
||||||
@ -19,9 +22,6 @@ import { formatNumber } from '@/lib/format';
|
|||||||
import { getTemporaryUrl } from '@/lib/upload';
|
import { getTemporaryUrl } from '@/lib/upload';
|
||||||
import { formatCurrency } from '@/lib/utils';
|
import { formatCurrency } from '@/lib/utils';
|
||||||
import { index as cuttingIndex, store } from '@/routes/admin/manage/cuttings';
|
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';
|
import type { CuttingCreateData } from './columns';
|
||||||
|
|
||||||
type MaterialState = {
|
type MaterialState = {
|
||||||
@ -63,6 +63,7 @@ export default function CuttingCreate({ data }: Props) {
|
|||||||
photo_url: m.photo_url,
|
photo_url: m.photo_url,
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
|
|
||||||
return [];
|
return [];
|
||||||
});
|
});
|
||||||
const [combinations, setCombinations] = useState<CombinationState[]>(() => {
|
const [combinations, setCombinations] = useState<CombinationState[]>(() => {
|
||||||
@ -71,6 +72,7 @@ export default function CuttingCreate({ data }: Props) {
|
|||||||
material_result: c.material_result ?? 0,
|
material_result: c.material_result ?? 0,
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
|
|
||||||
return [];
|
return [];
|
||||||
});
|
});
|
||||||
const [selectedMaterialName, setSelectedMaterialName] = useState(draft?.selectedMaterialName ?? '');
|
const [selectedMaterialName, setSelectedMaterialName] = useState(draft?.selectedMaterialName ?? '');
|
||||||
@ -149,12 +151,21 @@ export default function CuttingCreate({ data }: Props) {
|
|||||||
|
|
||||||
const addVariant = useCallback(
|
const addVariant = useCallback(
|
||||||
(priceId: number) => {
|
(priceId: number) => {
|
||||||
if (!selectedMaterial) return;
|
if (!selectedMaterial) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
const price = selectedMaterial.raw_material_prices.find((p) => p.id === priceId);
|
const price = selectedMaterial.raw_material_prices.find((p) => p.id === priceId);
|
||||||
if (!price) return;
|
|
||||||
|
if (!price) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
setMaterials((prev) => {
|
setMaterials((prev) => {
|
||||||
if (prev.some((m) => m.raw_material_price_id === priceId && m.combination_id === null)) return prev;
|
if (prev.some((m) => m.raw_material_price_id === priceId && m.combination_id === null)) {
|
||||||
|
return prev;
|
||||||
|
}
|
||||||
|
|
||||||
return [
|
return [
|
||||||
...prev,
|
...prev,
|
||||||
{
|
{
|
||||||
@ -187,21 +198,26 @@ export default function CuttingCreate({ data }: Props) {
|
|||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
const confirmCombo = useCallback(() => {
|
const confirmCombo = useCallback(() => {
|
||||||
if (comboSelectedPriceIds.length < 2) return;
|
if (comboSelectedPriceIds.length < 2) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
const comboIndex = combinations.length;
|
const comboIndex = combinations.length;
|
||||||
|
|
||||||
const newMaterials: MaterialState[] = comboSelectedPriceIds.map((priceId) => {
|
const newMaterials: MaterialState[] = comboSelectedPriceIds.map((priceId) => {
|
||||||
let foundMaterial: typeof rawMaterials[number] | undefined;
|
let foundMaterial: typeof rawMaterials[number] | undefined;
|
||||||
let foundPrice: typeof rawMaterials[number]['raw_material_prices'][number] | undefined;
|
let foundPrice: typeof rawMaterials[number]['raw_material_prices'][number] | undefined;
|
||||||
|
|
||||||
for (const rm of rawMaterials) {
|
for (const rm of rawMaterials) {
|
||||||
const p = rm.raw_material_prices.find((pp) => pp.id === priceId);
|
const p = rm.raw_material_prices.find((pp) => pp.id === priceId);
|
||||||
|
|
||||||
if (p) {
|
if (p) {
|
||||||
foundMaterial = rm;
|
foundMaterial = rm;
|
||||||
foundPrice = p;
|
foundPrice = p;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
raw_material_price_id: priceId,
|
raw_material_price_id: priceId,
|
||||||
material_usage: 0,
|
material_usage: 0,
|
||||||
@ -238,6 +254,7 @@ export default function CuttingCreate({ data }: Props) {
|
|||||||
setMaterials((prev) => {
|
setMaterials((prev) => {
|
||||||
const updated = [...prev];
|
const updated = [...prev];
|
||||||
(updated[index] as Record<string, unknown>)[field] = value;
|
(updated[index] as Record<string, unknown>)[field] = value;
|
||||||
|
|
||||||
return updated;
|
return updated;
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
@ -251,6 +268,7 @@ export default function CuttingCreate({ data }: Props) {
|
|||||||
const totalMaterialCost = useMemo(() => {
|
const totalMaterialCost = useMemo(() => {
|
||||||
return materials.reduce((sum, m) => {
|
return materials.reduce((sum, m) => {
|
||||||
const price = priceMap.get(m.raw_material_price_id);
|
const price = priceMap.get(m.raw_material_price_id);
|
||||||
|
|
||||||
return sum + (price ? price.price * m.material_usage : 0);
|
return sum + (price ? price.price * m.material_usage : 0);
|
||||||
}, 0);
|
}, 0);
|
||||||
}, [materials, priceMap]);
|
}, [materials, priceMap]);
|
||||||
@ -297,7 +315,9 @@ export default function CuttingCreate({ data }: Props) {
|
|||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<Form action={store()} transform={(data) => ({ ...data, ...getPayload() })} onSubmit={() => { submittingRef.current = true; }}>
|
<Form action={store()} transform={(data) => ({ ...data, ...getPayload() })} onSubmit={() => {
|
||||||
|
submittingRef.current = true;
|
||||||
|
}}>
|
||||||
{({ errors, processing }) => (
|
{({ errors, processing }) => (
|
||||||
<div className="grid gap-6 md:grid-cols-3">
|
<div className="grid gap-6 md:grid-cols-3">
|
||||||
<div className="space-y-6 md:col-span-2">
|
<div className="space-y-6 md:col-span-2">
|
||||||
@ -337,6 +357,7 @@ export default function CuttingCreate({ data }: Props) {
|
|||||||
{selectedMaterial.raw_material_prices.map((price) => {
|
{selectedMaterial.raw_material_prices.map((price) => {
|
||||||
const isAdded = materials.some((m) => m.raw_material_price_id === price.id);
|
const isAdded = materials.some((m) => m.raw_material_price_id === price.id);
|
||||||
const addedCount = materials.filter((m) => m.raw_material_price_id === price.id).length;
|
const addedCount = materials.filter((m) => m.raw_material_price_id === price.id).length;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div key={price.id} className={isAdded ? 'flex items-center justify-between gap-3 rounded-lg border border-primary p-3' : 'flex items-center justify-between gap-3 rounded-lg border p-3'}>
|
<div key={price.id} className={isAdded ? 'flex items-center justify-between gap-3 rounded-lg border border-primary p-3' : 'flex items-center justify-between gap-3 rounded-lg border p-3'}>
|
||||||
<div className="flex min-w-0 items-center gap-3">
|
<div className="flex min-w-0 items-center gap-3">
|
||||||
@ -435,7 +456,9 @@ export default function CuttingCreate({ data }: Props) {
|
|||||||
|
|
||||||
<div className="grid gap-2">
|
<div className="grid gap-2">
|
||||||
<Label>Foto</Label>
|
<Label>Foto</Label>
|
||||||
<FileUpload value={photo} onChange={(key) => { setPhoto(key); setPhotoUrl(key ? getTemporaryUrl(key) : null); }} folder="cutting" existingUrl={photoUrl} onUploadingChange={setUploading} />
|
<FileUpload value={photo} onChange={(key) => {
|
||||||
|
setPhoto(key); setPhotoUrl(key ? getTemporaryUrl(key) : null);
|
||||||
|
}} folder="cutting" existingUrl={photoUrl} onUploadingChange={setUploading} />
|
||||||
<InputError message={errors.photo_key} />
|
<InputError message={errors.photo_key} />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@ -474,7 +497,10 @@ export default function CuttingCreate({ data }: Props) {
|
|||||||
|
|
||||||
materials.forEach((m, i) => {
|
materials.forEach((m, i) => {
|
||||||
if (m.combination_id !== null) {
|
if (m.combination_id !== null) {
|
||||||
if (!comboMap.has(m.combination_id)) comboMap.set(m.combination_id, []);
|
if (!comboMap.has(m.combination_id)) {
|
||||||
|
comboMap.set(m.combination_id, []);
|
||||||
|
}
|
||||||
|
|
||||||
comboMap.get(m.combination_id)!.push({ m, index: i });
|
comboMap.get(m.combination_id)!.push({ m, index: i });
|
||||||
} else {
|
} else {
|
||||||
singleItems.push({ m, index: i });
|
singleItems.push({ m, index: i });
|
||||||
@ -509,6 +535,7 @@ export default function CuttingCreate({ data }: Props) {
|
|||||||
{group.items.map(({ m, index }) => {
|
{group.items.map(({ m, index }) => {
|
||||||
const price = priceMap.get(m.raw_material_price_id);
|
const price = priceMap.get(m.raw_material_price_id);
|
||||||
const cartKey = `material-${index}`;
|
const cartKey = `material-${index}`;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div key={cartKey} className="space-y-2">
|
<div key={cartKey} className="space-y-2">
|
||||||
<div className="flex items-start justify-between gap-2">
|
<div className="flex items-start justify-between gap-2">
|
||||||
@ -571,7 +598,11 @@ export default function CuttingCreate({ data }: Props) {
|
|||||||
|
|
||||||
<ImagePreviewModal
|
<ImagePreviewModal
|
||||||
open={previewKey !== null}
|
open={previewKey !== null}
|
||||||
onOpenChange={(open) => { if (!open) setPreviewKey(null); }}
|
onOpenChange={(open) => {
|
||||||
|
if (!open) {
|
||||||
|
setPreviewKey(null);
|
||||||
|
}
|
||||||
|
}}
|
||||||
src={previewKey && previewKey.startsWith('material-') ? (materials[Number(previewKey.replace('material-', ''))]?.photo_url ?? null) : 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}
|
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!)}
|
sources={materials.filter((m) => m.photo_url).map((m) => m.photo_url!)}
|
||||||
@ -619,8 +650,10 @@ export default function CuttingCreate({ data }: Props) {
|
|||||||
let variantName = '';
|
let variantName = '';
|
||||||
let materialName = '';
|
let materialName = '';
|
||||||
let photoUrl: string | null = null;
|
let photoUrl: string | null = null;
|
||||||
|
|
||||||
for (const rm of rawMaterials) {
|
for (const rm of rawMaterials) {
|
||||||
const p = rm.raw_material_prices.find((pp) => pp.id === priceId);
|
const p = rm.raw_material_prices.find((pp) => pp.id === priceId);
|
||||||
|
|
||||||
if (p) {
|
if (p) {
|
||||||
variantName = p.variant;
|
variantName = p.variant;
|
||||||
materialName = rm.name;
|
materialName = rm.name;
|
||||||
@ -628,6 +661,7 @@ export default function CuttingCreate({ data }: Props) {
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div key={priceId} className="flex items-center justify-between gap-2 rounded-md border border-primary bg-primary/5 px-3 py-2">
|
<div key={priceId} className="flex items-center justify-between gap-2 rounded-md border border-primary bg-primary/5 px-3 py-2">
|
||||||
<div className="flex min-w-0 items-center gap-3">
|
<div className="flex min-w-0 items-center gap-3">
|
||||||
@ -657,6 +691,7 @@ export default function CuttingCreate({ data }: Props) {
|
|||||||
<div className="space-y-2">
|
<div className="space-y-2">
|
||||||
{comboMaterial.raw_material_prices.map((price) => {
|
{comboMaterial.raw_material_prices.map((price) => {
|
||||||
const isSelected = comboSelectedPriceIds.includes(price.id);
|
const isSelected = comboSelectedPriceIds.includes(price.id);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div key={price.id} className={`flex items-center justify-between gap-3 rounded-lg border p-3 ${isSelected ? 'border-primary' : ''}`}>
|
<div key={price.id} className={`flex items-center justify-between gap-3 rounded-lg border p-3 ${isSelected ? 'border-primary' : ''}`}>
|
||||||
<div className="flex min-w-0 items-center gap-3">
|
<div className="flex min-w-0 items-center gap-3">
|
||||||
@ -689,11 +724,41 @@ export default function CuttingCreate({ data }: Props) {
|
|||||||
</DialogContent>
|
</DialogContent>
|
||||||
</Dialog>
|
</Dialog>
|
||||||
|
|
||||||
<ConfirmDialog open={deleteConfirmOpen} onOpenChange={(open) => { 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); }} />
|
<ConfirmDialog open={deleteConfirmOpen} onOpenChange={(open) => {
|
||||||
|
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));
|
||||||
|
}
|
||||||
|
|
||||||
<ConfirmDialog open={cartDeleteConfirmOpen} onOpenChange={(open) => { 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); }} />
|
setDeleteConfirmOpen(false); setDeleteMaterialIndex(null);
|
||||||
|
}} />
|
||||||
|
|
||||||
<ConfirmDialog open={comboDeleteConfirmOpen} onOpenChange={(open) => { 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); }} />
|
<ConfirmDialog open={cartDeleteConfirmOpen} onOpenChange={(open) => {
|
||||||
|
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);
|
||||||
|
}} />
|
||||||
|
|
||||||
|
<ConfirmDialog open={comboDeleteConfirmOpen} onOpenChange={(open) => {
|
||||||
|
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);
|
||||||
|
}} />
|
||||||
</div>
|
</div>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
|
|||||||
@ -1,5 +1,8 @@
|
|||||||
'use no memo';
|
'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 { ConfirmDialog } from '@/components/confirm-dialog';
|
||||||
import { FileUpload } from '@/components/file-upload';
|
import { FileUpload } from '@/components/file-upload';
|
||||||
import { ImagePreviewModal } from '@/components/image-preview-modal';
|
import { ImagePreviewModal } from '@/components/image-preview-modal';
|
||||||
@ -17,9 +20,6 @@ import { formatNumber } from '@/lib/format';
|
|||||||
import { getTemporaryUrl } from '@/lib/upload';
|
import { getTemporaryUrl } from '@/lib/upload';
|
||||||
import { formatCurrency } from '@/lib/utils';
|
import { formatCurrency } from '@/lib/utils';
|
||||||
import { index as cuttingIndex, update } from '@/routes/admin/manage/cuttings';
|
import { index as cuttingIndex, update } from '@/routes/admin/manage/cuttings';
|
||||||
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 type { CuttingCreateData, CuttingForEdit } from './columns';
|
import type { CuttingCreateData, CuttingForEdit } from './columns';
|
||||||
|
|
||||||
type MaterialState = {
|
type MaterialState = {
|
||||||
@ -131,12 +131,21 @@ export default function CuttingEdit({ cutting, data }: Props) {
|
|||||||
|
|
||||||
const addVariant = useCallback(
|
const addVariant = useCallback(
|
||||||
(priceId: number) => {
|
(priceId: number) => {
|
||||||
if (!selectedMaterial) return;
|
if (!selectedMaterial) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
const price = selectedMaterial.raw_material_prices.find((p) => p.id === priceId);
|
const price = selectedMaterial.raw_material_prices.find((p) => p.id === priceId);
|
||||||
if (!price) return;
|
|
||||||
|
if (!price) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
setMaterials((prev) => {
|
setMaterials((prev) => {
|
||||||
if (prev.some((m) => m.raw_material_price_id === priceId && m.combination_id === null)) return prev;
|
if (prev.some((m) => m.raw_material_price_id === priceId && m.combination_id === null)) {
|
||||||
|
return prev;
|
||||||
|
}
|
||||||
|
|
||||||
return [
|
return [
|
||||||
...prev,
|
...prev,
|
||||||
{
|
{
|
||||||
@ -169,21 +178,26 @@ export default function CuttingEdit({ cutting, data }: Props) {
|
|||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
const confirmCombo = useCallback(() => {
|
const confirmCombo = useCallback(() => {
|
||||||
if (comboSelectedPriceIds.length < 2) return;
|
if (comboSelectedPriceIds.length < 2) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
const comboIndex = combinations.length;
|
const comboIndex = combinations.length;
|
||||||
|
|
||||||
const newMaterials: MaterialState[] = comboSelectedPriceIds.map((priceId) => {
|
const newMaterials: MaterialState[] = comboSelectedPriceIds.map((priceId) => {
|
||||||
let foundMaterial: (typeof rawMaterials)[number] | undefined;
|
let foundMaterial: (typeof rawMaterials)[number] | undefined;
|
||||||
let foundPrice: (typeof rawMaterials)[number]['raw_material_prices'][number] | undefined;
|
let foundPrice: (typeof rawMaterials)[number]['raw_material_prices'][number] | undefined;
|
||||||
|
|
||||||
for (const rm of rawMaterials) {
|
for (const rm of rawMaterials) {
|
||||||
const p = rm.raw_material_prices.find((pp) => pp.id === priceId);
|
const p = rm.raw_material_prices.find((pp) => pp.id === priceId);
|
||||||
|
|
||||||
if (p) {
|
if (p) {
|
||||||
foundMaterial = rm;
|
foundMaterial = rm;
|
||||||
foundPrice = p;
|
foundPrice = p;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
raw_material_price_id: priceId,
|
raw_material_price_id: priceId,
|
||||||
material_usage: 0,
|
material_usage: 0,
|
||||||
@ -215,6 +229,7 @@ export default function CuttingEdit({ cutting, data }: Props) {
|
|||||||
setMaterials((prev) => {
|
setMaterials((prev) => {
|
||||||
const updated = [...prev];
|
const updated = [...prev];
|
||||||
(updated[index] as Record<string, unknown>)[field] = value;
|
(updated[index] as Record<string, unknown>)[field] = value;
|
||||||
|
|
||||||
return updated;
|
return updated;
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
@ -228,6 +243,7 @@ export default function CuttingEdit({ cutting, data }: Props) {
|
|||||||
const totalMaterialCost = useMemo(() => {
|
const totalMaterialCost = useMemo(() => {
|
||||||
return materials.reduce((sum, m) => {
|
return materials.reduce((sum, m) => {
|
||||||
const price = priceMap.get(m.raw_material_price_id);
|
const price = priceMap.get(m.raw_material_price_id);
|
||||||
|
|
||||||
return sum + (price ? price.price * m.material_usage : 0);
|
return sum + (price ? price.price * m.material_usage : 0);
|
||||||
}, 0);
|
}, 0);
|
||||||
}, [materials, priceMap]);
|
}, [materials, priceMap]);
|
||||||
@ -274,7 +290,9 @@ export default function CuttingEdit({ cutting, data }: Props) {
|
|||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<Form action={update(cutting.id)} transform={(data) => ({ ...data, ...getPayload() })} onSubmit={() => { submittingRef.current = true; }}>
|
<Form action={update(cutting.id)} transform={(data) => ({ ...data, ...getPayload() })} onSubmit={() => {
|
||||||
|
submittingRef.current = true;
|
||||||
|
}}>
|
||||||
{({ errors, processing }) => (
|
{({ errors, processing }) => (
|
||||||
<div className="grid gap-6 md:grid-cols-3">
|
<div className="grid gap-6 md:grid-cols-3">
|
||||||
<div className="space-y-6 md:col-span-2">
|
<div className="space-y-6 md:col-span-2">
|
||||||
@ -314,6 +332,7 @@ export default function CuttingEdit({ cutting, data }: Props) {
|
|||||||
{selectedMaterial.raw_material_prices.map((price) => {
|
{selectedMaterial.raw_material_prices.map((price) => {
|
||||||
const isAdded = materials.some((m) => m.raw_material_price_id === price.id);
|
const isAdded = materials.some((m) => m.raw_material_price_id === price.id);
|
||||||
const addedCount = materials.filter((m) => m.raw_material_price_id === price.id).length;
|
const addedCount = materials.filter((m) => m.raw_material_price_id === price.id).length;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div key={price.id} className={isAdded ? 'flex items-center justify-between gap-3 rounded-lg border border-primary p-3' : 'flex items-center justify-between gap-3 rounded-lg border p-3'}>
|
<div key={price.id} className={isAdded ? 'flex items-center justify-between gap-3 rounded-lg border border-primary p-3' : 'flex items-center justify-between gap-3 rounded-lg border p-3'}>
|
||||||
<div className="flex min-w-0 items-center gap-3">
|
<div className="flex min-w-0 items-center gap-3">
|
||||||
@ -410,7 +429,9 @@ export default function CuttingEdit({ cutting, data }: Props) {
|
|||||||
|
|
||||||
<div className="grid gap-2">
|
<div className="grid gap-2">
|
||||||
<Label>Foto</Label>
|
<Label>Foto</Label>
|
||||||
<FileUpload value={photo} onChange={(key) => { setPhoto(key); setPhotoUrl(key ? getTemporaryUrl(key) : null); }} folder="cutting" existingUrl={photoUrl} onUploadingChange={setUploading} />
|
<FileUpload value={photo} onChange={(key) => {
|
||||||
|
setPhoto(key); setPhotoUrl(key ? getTemporaryUrl(key) : null);
|
||||||
|
}} folder="cutting" existingUrl={photoUrl} onUploadingChange={setUploading} />
|
||||||
<InputError message={errors.photo_key} />
|
<InputError message={errors.photo_key} />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@ -449,7 +470,10 @@ export default function CuttingEdit({ cutting, data }: Props) {
|
|||||||
|
|
||||||
materials.forEach((m, i) => {
|
materials.forEach((m, i) => {
|
||||||
if (m.combination_id !== null) {
|
if (m.combination_id !== null) {
|
||||||
if (!comboMap.has(m.combination_id)) comboMap.set(m.combination_id, []);
|
if (!comboMap.has(m.combination_id)) {
|
||||||
|
comboMap.set(m.combination_id, []);
|
||||||
|
}
|
||||||
|
|
||||||
comboMap.get(m.combination_id)!.push({ m, index: i });
|
comboMap.get(m.combination_id)!.push({ m, index: i });
|
||||||
} else {
|
} else {
|
||||||
singleItems.push({ m, index: i });
|
singleItems.push({ m, index: i });
|
||||||
@ -484,6 +508,7 @@ export default function CuttingEdit({ cutting, data }: Props) {
|
|||||||
{group.items.map(({ m, index }) => {
|
{group.items.map(({ m, index }) => {
|
||||||
const price = priceMap.get(m.raw_material_price_id);
|
const price = priceMap.get(m.raw_material_price_id);
|
||||||
const cartKey = `material-${index}`;
|
const cartKey = `material-${index}`;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div key={cartKey} className="space-y-2">
|
<div key={cartKey} className="space-y-2">
|
||||||
<div className="flex items-start justify-between gap-2">
|
<div className="flex items-start justify-between gap-2">
|
||||||
@ -546,7 +571,11 @@ export default function CuttingEdit({ cutting, data }: Props) {
|
|||||||
|
|
||||||
<ImagePreviewModal
|
<ImagePreviewModal
|
||||||
open={previewKey !== null}
|
open={previewKey !== null}
|
||||||
onOpenChange={(open) => { if (!open) setPreviewKey(null); }}
|
onOpenChange={(open) => {
|
||||||
|
if (!open) {
|
||||||
|
setPreviewKey(null);
|
||||||
|
}
|
||||||
|
}}
|
||||||
src={previewKey && previewKey.startsWith('material-') ? (materials[Number(previewKey.replace('material-', ''))]?.photo_url ?? null) : 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}
|
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!)}
|
sources={materials.filter((m) => m.photo_url).map((m) => m.photo_url!)}
|
||||||
@ -594,8 +623,10 @@ export default function CuttingEdit({ cutting, data }: Props) {
|
|||||||
let variantName = '';
|
let variantName = '';
|
||||||
let materialName = '';
|
let materialName = '';
|
||||||
let photoUrl: string | null = null;
|
let photoUrl: string | null = null;
|
||||||
|
|
||||||
for (const rm of rawMaterials) {
|
for (const rm of rawMaterials) {
|
||||||
const p = rm.raw_material_prices.find((pp) => pp.id === priceId);
|
const p = rm.raw_material_prices.find((pp) => pp.id === priceId);
|
||||||
|
|
||||||
if (p) {
|
if (p) {
|
||||||
variantName = p.variant;
|
variantName = p.variant;
|
||||||
materialName = rm.name;
|
materialName = rm.name;
|
||||||
@ -603,6 +634,7 @@ export default function CuttingEdit({ cutting, data }: Props) {
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div key={priceId} className="flex items-center justify-between gap-2 rounded-md border border-primary bg-primary/5 px-3 py-2">
|
<div key={priceId} className="flex items-center justify-between gap-2 rounded-md border border-primary bg-primary/5 px-3 py-2">
|
||||||
<div className="flex min-w-0 items-center gap-3">
|
<div className="flex min-w-0 items-center gap-3">
|
||||||
@ -632,6 +664,7 @@ export default function CuttingEdit({ cutting, data }: Props) {
|
|||||||
<div className="space-y-2">
|
<div className="space-y-2">
|
||||||
{comboMaterial.raw_material_prices.map((price) => {
|
{comboMaterial.raw_material_prices.map((price) => {
|
||||||
const isSelected = comboSelectedPriceIds.includes(price.id);
|
const isSelected = comboSelectedPriceIds.includes(price.id);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div key={price.id} className={`flex items-center justify-between gap-3 rounded-lg border p-3 ${isSelected ? 'border-primary' : ''}`}>
|
<div key={price.id} className={`flex items-center justify-between gap-3 rounded-lg border p-3 ${isSelected ? 'border-primary' : ''}`}>
|
||||||
<div className="flex min-w-0 items-center gap-3">
|
<div className="flex min-w-0 items-center gap-3">
|
||||||
@ -664,11 +697,41 @@ export default function CuttingEdit({ cutting, data }: Props) {
|
|||||||
</DialogContent>
|
</DialogContent>
|
||||||
</Dialog>
|
</Dialog>
|
||||||
|
|
||||||
<ConfirmDialog open={deleteConfirmOpen} onOpenChange={(open) => { 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); }} />
|
<ConfirmDialog open={deleteConfirmOpen} onOpenChange={(open) => {
|
||||||
|
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));
|
||||||
|
}
|
||||||
|
|
||||||
<ConfirmDialog open={cartDeleteConfirmOpen} onOpenChange={(open) => { 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); }} />
|
setDeleteConfirmOpen(false); setDeleteMaterialIndex(null);
|
||||||
|
}} />
|
||||||
|
|
||||||
<ConfirmDialog open={comboDeleteConfirmOpen} onOpenChange={(open) => { 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); }} />
|
<ConfirmDialog open={cartDeleteConfirmOpen} onOpenChange={(open) => {
|
||||||
|
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);
|
||||||
|
}} />
|
||||||
|
|
||||||
|
<ConfirmDialog open={comboDeleteConfirmOpen} onOpenChange={(open) => {
|
||||||
|
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);
|
||||||
|
}} />
|
||||||
</div>
|
</div>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
|
|||||||
@ -74,7 +74,6 @@ export default function PurchaseIndex({
|
|||||||
pagination,
|
pagination,
|
||||||
filters,
|
filters,
|
||||||
filterWithParams: false,
|
filterWithParams: false,
|
||||||
reloadOnly: ['purchases', 'filters'],
|
|
||||||
});
|
});
|
||||||
|
|
||||||
const selectedSupplier = useMemo(
|
const selectedSupplier = useMemo(
|
||||||
|
|||||||
@ -28,7 +28,6 @@ import {
|
|||||||
SelectTrigger,
|
SelectTrigger,
|
||||||
SelectValue,
|
SelectValue,
|
||||||
} from '@/components/ui/select';
|
} from '@/components/ui/select';
|
||||||
import { Switch } from '@/components/ui/switch';
|
|
||||||
import {
|
import {
|
||||||
Sheet,
|
Sheet,
|
||||||
SheetContent,
|
SheetContent,
|
||||||
@ -36,6 +35,7 @@ import {
|
|||||||
SheetHeader,
|
SheetHeader,
|
||||||
SheetTitle,
|
SheetTitle,
|
||||||
} from '@/components/ui/sheet';
|
} from '@/components/ui/sheet';
|
||||||
|
import { Switch } from '@/components/ui/switch';
|
||||||
import { Textarea } from '@/components/ui/textarea';
|
import { Textarea } from '@/components/ui/textarea';
|
||||||
import { useTransactionDraftSave } from '@/hooks/use-transaction-draft';
|
import { useTransactionDraftSave } from '@/hooks/use-transaction-draft';
|
||||||
import { formatNumber } from '@/lib/format';
|
import { formatNumber } from '@/lib/format';
|
||||||
@ -182,6 +182,7 @@ export default function TransactionCreate({ data }: Props) {
|
|||||||
if (stockType === 'reject') {
|
if (stockType === 'reject') {
|
||||||
return priceTypeOptions.filter((o) => o.value === 'reject');
|
return priceTypeOptions.filter((o) => o.value === 'reject');
|
||||||
}
|
}
|
||||||
|
|
||||||
return priceTypeOptions.filter((o) => SELLING_PRICE_TYPES.includes(o.value));
|
return priceTypeOptions.filter((o) => SELLING_PRICE_TYPES.includes(o.value));
|
||||||
}, [stockType, priceTypeOptions]);
|
}, [stockType, priceTypeOptions]);
|
||||||
|
|
||||||
|
|||||||
@ -28,7 +28,6 @@ import {
|
|||||||
SelectTrigger,
|
SelectTrigger,
|
||||||
SelectValue,
|
SelectValue,
|
||||||
} from '@/components/ui/select';
|
} from '@/components/ui/select';
|
||||||
import { Switch } from '@/components/ui/switch';
|
|
||||||
import {
|
import {
|
||||||
Sheet,
|
Sheet,
|
||||||
SheetContent,
|
SheetContent,
|
||||||
@ -36,6 +35,7 @@ import {
|
|||||||
SheetHeader,
|
SheetHeader,
|
||||||
SheetTitle,
|
SheetTitle,
|
||||||
} from '@/components/ui/sheet';
|
} from '@/components/ui/sheet';
|
||||||
|
import { Switch } from '@/components/ui/switch';
|
||||||
import { Textarea } from '@/components/ui/textarea';
|
import { Textarea } from '@/components/ui/textarea';
|
||||||
import { formatNumber } from '@/lib/format';
|
import { formatNumber } from '@/lib/format';
|
||||||
import { getTemporaryUrl } from '@/lib/upload';
|
import { getTemporaryUrl } from '@/lib/upload';
|
||||||
@ -151,6 +151,7 @@ export default function TransactionEdit({ transaction, data }: Props) {
|
|||||||
if (stockType === 'reject') {
|
if (stockType === 'reject') {
|
||||||
return priceTypeOptions.filter((o) => o.value === 'reject');
|
return priceTypeOptions.filter((o) => o.value === 'reject');
|
||||||
}
|
}
|
||||||
|
|
||||||
return priceTypeOptions.filter((o) => SELLING_PRICE_TYPES.includes(o.value));
|
return priceTypeOptions.filter((o) => SELLING_PRICE_TYPES.includes(o.value));
|
||||||
}, [stockType, priceTypeOptions]);
|
}, [stockType, priceTypeOptions]);
|
||||||
|
|
||||||
|
|||||||
@ -1,3 +1,6 @@
|
|||||||
|
import { Head, Link, router } from '@inertiajs/react';
|
||||||
|
import { Plus } from 'lucide-react';
|
||||||
|
import { useState } from 'react';
|
||||||
import { CardTable } from '@/components/card-table';
|
import { CardTable } from '@/components/card-table';
|
||||||
import { DeleteConfirmDialog } from '@/components/delete-confirm-dialog';
|
import { DeleteConfirmDialog } from '@/components/delete-confirm-dialog';
|
||||||
import { FilterPopover } from '@/components/filter-popover';
|
import { FilterPopover } from '@/components/filter-popover';
|
||||||
@ -11,6 +14,7 @@ import {
|
|||||||
SelectTrigger,
|
SelectTrigger,
|
||||||
SelectValue,
|
SelectValue,
|
||||||
} from '@/components/ui/select';
|
} from '@/components/ui/select';
|
||||||
|
import { useCan } from '@/hooks/use-can';
|
||||||
import { useServerTable } from '@/hooks/use-server-table';
|
import { useServerTable } from '@/hooks/use-server-table';
|
||||||
import {
|
import {
|
||||||
destroy,
|
destroy,
|
||||||
@ -22,13 +26,9 @@ import {
|
|||||||
import {
|
import {
|
||||||
destroy as variantDestroy
|
destroy as variantDestroy
|
||||||
} from '@/routes/admin/master/raw-materials/variants';
|
} from '@/routes/admin/master/raw-materials/variants';
|
||||||
import { Head, Link, router } from '@inertiajs/react';
|
|
||||||
import { Plus } from 'lucide-react';
|
|
||||||
import { useState } from 'react';
|
|
||||||
import type { RawMaterial, RawMaterialVariant } from './columns';
|
import type { RawMaterial, RawMaterialVariant } from './columns';
|
||||||
import { RawMaterialCardRow } from './raw-material-card';
|
import { RawMaterialCardRow } from './raw-material-card';
|
||||||
import { RawMaterialVariantSubRow } from './variant/sub-row';
|
import { RawMaterialVariantSubRow } from './variant/sub-row';
|
||||||
import { useCan } from '@/hooks/use-can';
|
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
rawMaterials: {
|
rawMaterials: {
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user