feat: implement grouped table views for purchase items and cutting materials/results with updated type definitions
This commit is contained in:
parent
d8079fb3ff
commit
e5f2f526c3
@ -73,6 +73,63 @@ function statusVariant(status: string): 'default' | 'secondary' | 'destructive'
|
|||||||
|
|
||||||
return 'outline';
|
return 'outline';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
interface GroupedCuttingMaterials {
|
||||||
|
rawMaterialId: number;
|
||||||
|
rawMaterialName: string;
|
||||||
|
unitLabel?: string;
|
||||||
|
items: any[];
|
||||||
|
}
|
||||||
|
|
||||||
|
interface GroupedCuttingResults {
|
||||||
|
productId: number;
|
||||||
|
productName: string;
|
||||||
|
items: any[];
|
||||||
|
}
|
||||||
|
|
||||||
|
function getGroupedMaterials(materials: any[]): GroupedCuttingMaterials[] {
|
||||||
|
const groups: Record<number, GroupedCuttingMaterials> = {};
|
||||||
|
|
||||||
|
materials.forEach((item) => {
|
||||||
|
const rawMaterial = item.raw_material_price?.raw_material;
|
||||||
|
const rawMaterialId = rawMaterial?.id ?? 0;
|
||||||
|
const rawMaterialName = rawMaterial?.name ?? 'Bahan Baku Tidak Diketahui';
|
||||||
|
const unitLabel = rawMaterial?.unit_label;
|
||||||
|
|
||||||
|
if (!groups[rawMaterialId]) {
|
||||||
|
groups[rawMaterialId] = {
|
||||||
|
rawMaterialId,
|
||||||
|
rawMaterialName,
|
||||||
|
unitLabel,
|
||||||
|
items: [],
|
||||||
|
};
|
||||||
|
}
|
||||||
|
groups[rawMaterialId].items.push(item);
|
||||||
|
});
|
||||||
|
|
||||||
|
return Object.values(groups);
|
||||||
|
}
|
||||||
|
|
||||||
|
function getGroupedResults(results: any[]): GroupedCuttingResults[] {
|
||||||
|
const groups: Record<number, GroupedCuttingResults> = {};
|
||||||
|
|
||||||
|
results.forEach((item) => {
|
||||||
|
const product = item.product_variant?.product;
|
||||||
|
const productId = product?.id ?? 0;
|
||||||
|
const productName = product?.name ?? 'Produk Tidak Diketahui';
|
||||||
|
|
||||||
|
if (!groups[productId]) {
|
||||||
|
groups[productId] = {
|
||||||
|
productId,
|
||||||
|
productName,
|
||||||
|
items: [],
|
||||||
|
};
|
||||||
|
}
|
||||||
|
groups[productId].items.push(item);
|
||||||
|
});
|
||||||
|
|
||||||
|
return Object.values(groups);
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
@ -114,16 +171,13 @@ function statusVariant(status: string): 'default' | 'secondary' | 'destructive'
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="grid gap-0 lg:grid-cols-2">
|
<div class="space-y-6 p-4">
|
||||||
|
<div class="space-y-2">
|
||||||
|
<h4 class="text-sm font-semibold tracking-tight text-foreground">Bahan Baku</h4>
|
||||||
|
<div class="rounded-md border">
|
||||||
<Table>
|
<Table>
|
||||||
<TableHeader>
|
<TableHeader>
|
||||||
<TableRow>
|
<TableRow>
|
||||||
<TableHead colspan="4" class="bg-muted/20">
|
|
||||||
Bahan Baku
|
|
||||||
</TableHead>
|
|
||||||
</TableRow>
|
|
||||||
<TableRow>
|
|
||||||
<TableHead>Bahan</TableHead>
|
|
||||||
<TableHead>Varian</TableHead>
|
<TableHead>Varian</TableHead>
|
||||||
<TableHead>Pemakaian</TableHead>
|
<TableHead>Pemakaian</TableHead>
|
||||||
<TableHead>Sisa</TableHead>
|
<TableHead>Sisa</TableHead>
|
||||||
@ -131,15 +185,21 @@ function statusVariant(status: string): 'default' | 'secondary' | 'destructive'
|
|||||||
</TableHeader>
|
</TableHeader>
|
||||||
<TableBody>
|
<TableBody>
|
||||||
<TableRow v-if="!cutting.materials.length" :key="`${cutting.id}-material-empty`">
|
<TableRow v-if="!cutting.materials.length" :key="`${cutting.id}-material-empty`">
|
||||||
<TableCell colspan="4" class="text-muted-foreground">
|
<TableCell colspan="3" class="text-muted-foreground">
|
||||||
Belum ada bahan baku
|
Belum ada bahan baku
|
||||||
</TableCell>
|
</TableCell>
|
||||||
</TableRow>
|
</TableRow>
|
||||||
<TableRow v-for="material in cutting.materials" :key="material.id">
|
<template v-else v-for="group in getGroupedMaterials(cutting.materials)" :key="group.rawMaterialId">
|
||||||
<TableCell class="font-medium">
|
<TableRow class="bg-muted/20 hover:bg-muted/20">
|
||||||
{{ material.raw_material_price?.raw_material?.name }}
|
<TableCell colspan="3" class="font-semibold text-foreground">
|
||||||
|
{{ group.rawMaterialName }}
|
||||||
|
<Badge v-if="group.unitLabel" variant="secondary" class="ml-2 font-normal">
|
||||||
|
{{ group.unitLabel }}
|
||||||
|
</Badge>
|
||||||
</TableCell>
|
</TableCell>
|
||||||
<TableCell>
|
</TableRow>
|
||||||
|
<TableRow v-for="material in group.items" :key="material.id">
|
||||||
|
<TableCell class="pl-6 font-medium">
|
||||||
{{ material.raw_material_price?.variant }}
|
{{ material.raw_material_price?.variant }}
|
||||||
</TableCell>
|
</TableCell>
|
||||||
<TableCell class="tabular-nums">
|
<TableCell class="tabular-nums">
|
||||||
@ -149,18 +209,18 @@ function statusVariant(status: string): 'default' | 'secondary' | 'destructive'
|
|||||||
{{ material.remaining_material_formatted }}
|
{{ material.remaining_material_formatted }}
|
||||||
</TableCell>
|
</TableCell>
|
||||||
</TableRow>
|
</TableRow>
|
||||||
|
</template>
|
||||||
</TableBody>
|
</TableBody>
|
||||||
</Table>
|
</Table>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="space-y-2">
|
||||||
|
<h4 class="text-sm font-semibold tracking-tight text-foreground">Hasil Produk</h4>
|
||||||
|
<div class="rounded-md border">
|
||||||
<Table>
|
<Table>
|
||||||
<TableHeader>
|
<TableHeader>
|
||||||
<TableRow>
|
<TableRow>
|
||||||
<TableHead colspan="5" class="bg-muted/20">
|
|
||||||
Hasil Produk
|
|
||||||
</TableHead>
|
|
||||||
</TableRow>
|
|
||||||
<TableRow>
|
|
||||||
<TableHead>Produk</TableHead>
|
|
||||||
<TableHead>Varian</TableHead>
|
<TableHead>Varian</TableHead>
|
||||||
<TableHead>Hasil</TableHead>
|
<TableHead>Hasil</TableHead>
|
||||||
<TableHead>Gudang</TableHead>
|
<TableHead>Gudang</TableHead>
|
||||||
@ -169,15 +229,18 @@ function statusVariant(status: string): 'default' | 'secondary' | 'destructive'
|
|||||||
</TableHeader>
|
</TableHeader>
|
||||||
<TableBody>
|
<TableBody>
|
||||||
<TableRow v-if="!cutting.results.length" :key="`${cutting.id}-result-empty`">
|
<TableRow v-if="!cutting.results.length" :key="`${cutting.id}-result-empty`">
|
||||||
<TableCell colspan="5" class="text-muted-foreground">
|
<TableCell colspan="4" class="text-muted-foreground">
|
||||||
Belum ada hasil produk
|
Belum ada hasil produk
|
||||||
</TableCell>
|
</TableCell>
|
||||||
</TableRow>
|
</TableRow>
|
||||||
<TableRow v-for="result in cutting.results" :key="result.id">
|
<template v-else v-for="group in getGroupedResults(cutting.results)" :key="group.productId">
|
||||||
<TableCell class="font-medium">
|
<TableRow class="bg-muted/20 hover:bg-muted/20">
|
||||||
{{ result.product_variant?.product?.name }}
|
<TableCell colspan="4" class="font-semibold text-foreground">
|
||||||
|
{{ group.productName }}
|
||||||
</TableCell>
|
</TableCell>
|
||||||
<TableCell>
|
</TableRow>
|
||||||
|
<TableRow v-for="result in group.items" :key="result.id">
|
||||||
|
<TableCell class="pl-6 font-medium">
|
||||||
{{ result.product_variant?.name }}
|
{{ result.product_variant?.name }}
|
||||||
</TableCell>
|
</TableCell>
|
||||||
<TableCell class="tabular-nums">
|
<TableCell class="tabular-nums">
|
||||||
@ -190,11 +253,14 @@ function statusVariant(status: string): 'default' | 'secondary' | 'destructive'
|
|||||||
{{ result.cutting_reject }} pcs
|
{{ result.cutting_reject }} pcs
|
||||||
</TableCell>
|
</TableCell>
|
||||||
</TableRow>
|
</TableRow>
|
||||||
|
</template>
|
||||||
</TableBody>
|
</TableBody>
|
||||||
</Table>
|
</Table>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div v-else class="rounded-md border px-6 py-10">
|
<div v-else class="rounded-md border px-6 py-10">
|
||||||
<Empty>
|
<Empty>
|
||||||
|
|||||||
@ -4,6 +4,7 @@ import { computed } from 'vue';
|
|||||||
import DataTableActions from '@/components/admin/manage/purchases/data-table-actions.vue';
|
import DataTableActions from '@/components/admin/manage/purchases/data-table-actions.vue';
|
||||||
import DataTableToolbar from '@/components/data-table/DataTableToolbar.vue';
|
import DataTableToolbar from '@/components/data-table/DataTableToolbar.vue';
|
||||||
import MediaThumbnailCell from '@/components/media/MediaThumbnailCell.vue';
|
import MediaThumbnailCell from '@/components/media/MediaThumbnailCell.vue';
|
||||||
|
import { Badge } from '@/components/ui/badge';
|
||||||
import { Button } from '@/components/ui/button';
|
import { Button } from '@/components/ui/button';
|
||||||
import {
|
import {
|
||||||
Empty,
|
Empty,
|
||||||
@ -57,6 +58,36 @@ const paginationSummary = computed(() => {
|
|||||||
function rowNumber(index: number): number {
|
function rowNumber(index: number): number {
|
||||||
return (props.firstItem ?? 1) + index;
|
return (props.firstItem ?? 1) + index;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
interface GroupedPurchaseItems {
|
||||||
|
rawMaterialId: number;
|
||||||
|
rawMaterialName: string;
|
||||||
|
unitLabel?: string;
|
||||||
|
items: any[];
|
||||||
|
}
|
||||||
|
|
||||||
|
function getGroupedItems(items: any[]): GroupedPurchaseItems[] {
|
||||||
|
const groups: Record<number, GroupedPurchaseItems> = {};
|
||||||
|
|
||||||
|
items.forEach((item) => {
|
||||||
|
const rawMaterial = item.raw_material_price?.raw_material;
|
||||||
|
const rawMaterialId = rawMaterial?.id ?? 0;
|
||||||
|
const rawMaterialName = rawMaterial?.name ?? 'Bahan Baku Tidak Diketahui';
|
||||||
|
const unitLabel = rawMaterial?.unit_label;
|
||||||
|
|
||||||
|
if (!groups[rawMaterialId]) {
|
||||||
|
groups[rawMaterialId] = {
|
||||||
|
rawMaterialId,
|
||||||
|
rawMaterialName,
|
||||||
|
unitLabel,
|
||||||
|
items: [],
|
||||||
|
};
|
||||||
|
}
|
||||||
|
groups[rawMaterialId].items.push(item);
|
||||||
|
});
|
||||||
|
|
||||||
|
return Object.values(groups);
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
@ -102,7 +133,6 @@ function rowNumber(index: number): number {
|
|||||||
<Table>
|
<Table>
|
||||||
<TableHeader>
|
<TableHeader>
|
||||||
<TableRow>
|
<TableRow>
|
||||||
<TableHead>Bahan Baku</TableHead>
|
|
||||||
<TableHead>Varian</TableHead>
|
<TableHead>Varian</TableHead>
|
||||||
<TableHead>Jumlah</TableHead>
|
<TableHead>Jumlah</TableHead>
|
||||||
<TableHead>Harga Satuan</TableHead>
|
<TableHead>Harga Satuan</TableHead>
|
||||||
@ -111,15 +141,21 @@ function rowNumber(index: number): number {
|
|||||||
</TableHeader>
|
</TableHeader>
|
||||||
<TableBody>
|
<TableBody>
|
||||||
<TableRow v-if="!purchase.items.length" :key="`${purchase.id}-empty`">
|
<TableRow v-if="!purchase.items.length" :key="`${purchase.id}-empty`">
|
||||||
<TableCell colspan="5" class="text-muted-foreground">
|
<TableCell colspan="4" class="text-muted-foreground">
|
||||||
Belum ada item
|
Belum ada item
|
||||||
</TableCell>
|
</TableCell>
|
||||||
</TableRow>
|
</TableRow>
|
||||||
<TableRow v-for="item in purchase.items" :key="item.id">
|
<template v-else v-for="group in getGroupedItems(purchase.items)" :key="group.rawMaterialId">
|
||||||
<TableCell class="font-medium">
|
<TableRow class="bg-muted/20 hover:bg-muted/20">
|
||||||
{{ item.raw_material_price?.raw_material?.name }}
|
<TableCell colspan="4" class="font-semibold text-foreground">
|
||||||
|
{{ group.rawMaterialName }}
|
||||||
|
<Badge v-if="group.unitLabel" variant="secondary" class="ml-2 font-normal">
|
||||||
|
{{ group.unitLabel }}
|
||||||
|
</Badge>
|
||||||
</TableCell>
|
</TableCell>
|
||||||
<TableCell>
|
</TableRow>
|
||||||
|
<TableRow v-for="item in group.items" :key="item.id">
|
||||||
|
<TableCell class="pl-6 font-medium">
|
||||||
{{ item.raw_material_price?.variant }}
|
{{ item.raw_material_price?.variant }}
|
||||||
</TableCell>
|
</TableCell>
|
||||||
<TableCell class="tabular-nums">
|
<TableCell class="tabular-nums">
|
||||||
@ -132,6 +168,7 @@ function rowNumber(index: number): number {
|
|||||||
{{ item.subtotal_formatted }}
|
{{ item.subtotal_formatted }}
|
||||||
</TableCell>
|
</TableCell>
|
||||||
</TableRow>
|
</TableRow>
|
||||||
|
</template>
|
||||||
</TableBody>
|
</TableBody>
|
||||||
</Table>
|
</Table>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@ -17,15 +17,25 @@ export type PurchaseItemListItem = {
|
|||||||
|
|
||||||
export type PurchaseListItem = {
|
export type PurchaseListItem = {
|
||||||
id: number;
|
id: number;
|
||||||
supplier_name: string;
|
supplier_name?: string;
|
||||||
|
supplier?: {
|
||||||
|
id: number;
|
||||||
|
name: string;
|
||||||
|
} | null;
|
||||||
subtotal_formatted: string;
|
subtotal_formatted: string;
|
||||||
discount_formatted: string;
|
discount_formatted: string;
|
||||||
total_formatted: string;
|
total_formatted: string;
|
||||||
notes: string | null;
|
notes: string | null;
|
||||||
created_at_formatted: string;
|
created_at_formatted: string;
|
||||||
created_by_name: string;
|
created_by_name?: string;
|
||||||
|
created_by?: {
|
||||||
|
username: string;
|
||||||
|
profile?: {
|
||||||
|
full_name: string;
|
||||||
|
} | null;
|
||||||
|
} | null;
|
||||||
photos?: MediaItem | null;
|
photos?: MediaItem | null;
|
||||||
items: PurchaseItemListItem[];
|
items: any[];
|
||||||
};
|
};
|
||||||
|
|
||||||
export type PurchaseCartItem = {
|
export type PurchaseCartItem = {
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user