feat: include and display media images for raw material prices and product variants in cutting management

This commit is contained in:
Yoga Pangestu 2026-06-30 21:22:17 +07:00
parent e76fd8107b
commit 9f0676c923
4 changed files with 61 additions and 28 deletions

View File

@ -35,8 +35,9 @@ public function paginateForIndex(array $tableQuery, User $user): LengthAwarePagi
'createdBy.profile',
'rejection.rejectedBy.profile',
'materials.rawMaterialPrice.rawMaterial:id,name,unit',
'materials.rawMaterialPrice.media',
'results.productVariant.product:id,name',
'results.productVariant:id,product_id,name',
'results.productVariant.media',
])
->when($tableQuery['search'] !== '', function (Builder $query) use ($tableQuery): void {
$search = $tableQuery['search'];
@ -62,6 +63,7 @@ public function paginateForIndex(array $tableQuery, User $user): LengthAwarePagi
$cutting->setAttribute('available_actions', $this->filterActionsForUser($cutting, $user));
$cutting->setAttribute('is_editable', $cutting->status->isEditable());
$this->appendCostPreview($cutting);
$this->appendPhotos($cutting);
$cutting->materials->each(fn (CuttingMaterial $m) => $this->breakMaterialCircularReference($m));
return $cutting;
@ -75,8 +77,9 @@ public function getInProgressCuttings(User $user): Collection
'createdBy.profile',
'rejection.rejectedBy.profile',
'materials.rawMaterialPrice.rawMaterial:id,name,unit',
'materials.rawMaterialPrice.media',
'results.productVariant.product:id,name',
'results.productVariant:id,product_id,name',
'results.productVariant.media',
])
->inProgress()
->latest()
@ -85,6 +88,7 @@ public function getInProgressCuttings(User $user): Collection
$cutting->setAttribute('available_actions', $this->filterActionsForUser($cutting, $user));
$cutting->setAttribute('is_editable', $cutting->status->isEditable());
$this->appendCostPreview($cutting);
$this->appendPhotos($cutting);
$cutting->materials->each(fn (CuttingMaterial $m) => $this->breakMaterialCircularReference($m));
});
}
@ -96,8 +100,9 @@ public function getCompletedCuttings(User $user): Collection
'createdBy.profile',
'rejection.rejectedBy.profile',
'materials.rawMaterialPrice.rawMaterial:id,name,unit',
'materials.rawMaterialPrice.media',
'results.productVariant.product:id,name',
'results.productVariant:id,product_id,name',
'results.productVariant.media',
])
->completed()
->latest()
@ -106,6 +111,7 @@ public function getCompletedCuttings(User $user): Collection
$cutting->setAttribute('available_actions', $this->filterActionsForUser($cutting, $user));
$cutting->setAttribute('is_editable', $cutting->status->isEditable());
$this->appendCostPreview($cutting);
$this->appendPhotos($cutting);
$cutting->materials->each(fn (CuttingMaterial $m) => $this->breakMaterialCircularReference($m));
});
}
@ -187,14 +193,37 @@ public function findForEdit(Cutting $cutting): Cutting
'rejection.rejectedBy.profile',
]);
$this->appendPhotos($cutting);
$cutting->materials->each(fn (CuttingMaterial $m) => $this->breakMaterialCircularReference($m));
return $cutting;
}
public function findForShare(Cutting $cutting): Cutting
{
$cutting->load([
'createdBy.profile',
'materials.rawMaterialPrice.rawMaterial:id,name,unit',
'materials.rawMaterialPrice.media',
'results.productVariant.product:id,name',
'results.productVariant.media',
]);
$this->appendCostPreview($cutting);
$this->appendPhotos($cutting);
$cutting->materials->each(fn (CuttingMaterial $m) => $this->breakMaterialCircularReference($m));
return $cutting;
}
private function appendPhotos(Cutting $cutting): void
{
$cutting->materials->each(function (CuttingMaterial $material): void {
$price = $material->rawMaterialPrice;
if ($price) {
$price->setAttribute('images', MediaPresenter::collection($price, 'images'));
}
$this->breakMaterialCircularReference($material);
});
$cutting->results->each(function (CuttingResult $result): void {
@ -207,23 +236,6 @@ public function findForEdit(Cutting $cutting): Cutting
);
}
});
return $cutting;
}
public function findForShare(Cutting $cutting): Cutting
{
$cutting->load([
'createdBy.profile',
'materials.rawMaterialPrice.rawMaterial:id,name,unit',
'results.productVariant.product:id,name',
'results.productVariant:id,product_id,name',
]);
$this->appendCostPreview($cutting);
$cutting->materials->each(fn (CuttingMaterial $m) => $this->breakMaterialCircularReference($m));
return $cutting;
}
public function draftMaterialsForUser(User $user): array

View File

@ -10,6 +10,7 @@ import {
TableRow,
} from '@/components/ui/table';
import { cuttingStatusBadgeVariant } from '@/constants/cutting-status';
import MediaThumbnailCell from '@/components/media/MediaThumbnailCell.vue';
import type { CuttingListItem } from '@/types/cutting';
const props = defineProps<{
@ -120,13 +121,14 @@ const groupedResults = computed<GroupedResults[]>(() => {
<TableHeader>
<TableRow>
<TableHead>Varian</TableHead>
<TableHead>Foto</TableHead>
<TableHead class="text-right">Pemakaian</TableHead>
</TableRow>
</TableHeader>
<TableBody>
<template v-for="group in groupedMaterials" :key="group.key">
<TableRow class="bg-muted/20 hover:bg-muted/20">
<TableCell colspan="2" class="font-semibold">
<TableCell colspan="3" class="font-semibold">
{{ group.name }}
<Badge v-if="group.unitLabel" variant="secondary" class="ml-2 font-normal">
{{ group.unitLabel }}
@ -137,6 +139,9 @@ const groupedResults = computed<GroupedResults[]>(() => {
<TableCell class="pl-6">
{{ material.variant }}
</TableCell>
<TableCell>
<MediaThumbnailCell :items="material.images ?? []" />
</TableCell>
<TableCell class="text-right tabular-nums">
{{ material.material_usage_formatted }}
</TableCell>
@ -155,6 +160,7 @@ const groupedResults = computed<GroupedResults[]>(() => {
<TableHeader>
<TableRow>
<TableHead>Varian</TableHead>
<TableHead>Foto</TableHead>
<TableHead class="text-right">Hasil</TableHead>
<TableHead class="text-right">Sample</TableHead>
<TableHead class="text-right">Diluar Sample</TableHead>
@ -163,7 +169,7 @@ const groupedResults = computed<GroupedResults[]>(() => {
<TableBody>
<template v-for="group in groupedResults" :key="group.key">
<TableRow class="bg-muted/20 hover:bg-muted/20">
<TableCell colspan="4" class="font-semibold">
<TableCell colspan="5" class="font-semibold">
{{ group.name }}
</TableCell>
</TableRow>
@ -171,6 +177,9 @@ const groupedResults = computed<GroupedResults[]>(() => {
<TableCell class="pl-6">
{{ result.product_variant?.name }}
</TableCell>
<TableCell>
<MediaThumbnailCell :items="result.product_variant?.images ?? []" />
</TableCell>
<TableCell class="text-right tabular-nums">
{{ result.cutting_result }} pcs
</TableCell>

View File

@ -15,6 +15,7 @@ import {
} from '@/components/ui/table';
import { usePaginationSummary } from '@/composables/usePaginationSummary';
import { cuttingStatusBadgeVariant } from '@/constants/cutting-status';
import MediaThumbnailCell from '@/components/media/MediaThumbnailCell.vue';
import { groupedTableRowNumber } from '@/lib/grouped-table';
import type { CuttingListItem } from '@/types/cutting';
import type {
@ -155,19 +156,20 @@ function getGroupedResults(results: any[]): GroupedCuttingResults[] {
<TableHeader>
<TableRow>
<TableHead>Varian</TableHead>
<TableHead>Foto</TableHead>
<TableHead>Pemakaian</TableHead>
</TableRow>
</TableHeader>
<TableBody>
<TableRow v-if="!cutting.materials.length" :key="`${cutting.id}-material-empty`">
<TableCell colspan="2" class="text-muted-foreground">
<TableCell colspan="3" class="text-muted-foreground">
Belum ada bahan baku
</TableCell>
</TableRow>
<template v-else v-for="group in getGroupedMaterials(cutting.materials)"
:key="group.rawMaterialId">
<TableRow class="bg-muted/20 hover:bg-muted/20">
<TableCell colspan="2" class="font-semibold text-foreground">
<TableCell colspan="3" class="font-semibold text-foreground">
{{ group.rawMaterialName }}
<Badge v-if="group.unitLabel" variant="secondary"
class="ml-2 font-normal">
@ -179,6 +181,9 @@ function getGroupedResults(results: any[]): GroupedCuttingResults[] {
<TableCell class="pl-6 font-medium">
{{ material.variant }}
</TableCell>
<TableCell>
<MediaThumbnailCell :items="material.images ?? []" />
</TableCell>
<TableCell class="tabular-nums">
{{ material.material_usage_formatted }}
</TableCell>
@ -196,6 +201,7 @@ function getGroupedResults(results: any[]): GroupedCuttingResults[] {
<TableHeader>
<TableRow>
<TableHead>Varian</TableHead>
<TableHead>Foto</TableHead>
<TableHead>Hasil</TableHead>
<TableHead>Sample</TableHead>
<TableHead>Diluar Sample</TableHead>
@ -203,14 +209,14 @@ function getGroupedResults(results: any[]): GroupedCuttingResults[] {
</TableHeader>
<TableBody>
<TableRow v-if="!cutting.results.length" :key="`${cutting.id}-result-empty`">
<TableCell colspan="4" class="text-muted-foreground">
<TableCell colspan="5" class="text-muted-foreground">
Belum ada hasil produk
</TableCell>
</TableRow>
<template v-else v-for="group in getGroupedResults(cutting.results)"
:key="group.productId">
<TableRow class="bg-muted/20 hover:bg-muted/20">
<TableCell colspan="4" class="font-semibold text-foreground">
<TableCell colspan="5" class="font-semibold text-foreground">
{{ group.productName }}
</TableCell>
</TableRow>
@ -218,6 +224,9 @@ function getGroupedResults(results: any[]): GroupedCuttingResults[] {
<TableCell class="pl-6 font-medium">
{{ result.product_variant?.name }}
</TableCell>
<TableCell>
<MediaThumbnailCell :items="result.product_variant?.images ?? []" />
</TableCell>
<TableCell class="tabular-nums">
{{ result.cutting_result }} pcs
</TableCell>

View File

@ -18,9 +18,11 @@ export type CuttingMaterialListItem = {
raw_material_id?: number;
raw_material_name?: string;
raw_material_unit_label?: string;
images?: MediaItem[];
raw_material_price?: {
id: number;
variant: string;
images?: MediaItem[];
raw_material?: {
id: number;
name: string;
@ -50,6 +52,7 @@ export type CuttingResultListItem = {
product_variant?: {
id: number;
name: string;
images?: MediaItem[];
product?: {
id: number;
name: string;