feat: enhance cutting management UI by adding media thumbnails for product variants and raw materials
This commit is contained in:
parent
2ed24a654b
commit
85269935f6
@ -5,11 +5,13 @@
|
|||||||
use App\Enums\CuttingStatus;
|
use App\Enums\CuttingStatus;
|
||||||
use App\Models\Cutting;
|
use App\Models\Cutting;
|
||||||
use App\Models\CuttingMaterial;
|
use App\Models\CuttingMaterial;
|
||||||
|
use App\Models\CuttingResult;
|
||||||
use App\Models\CuttingResultPrice;
|
use App\Models\CuttingResultPrice;
|
||||||
use App\Models\ProductPrice;
|
use App\Models\ProductPrice;
|
||||||
use App\Models\ProductVariant;
|
use App\Models\ProductVariant;
|
||||||
use App\Models\User;
|
use App\Models\User;
|
||||||
use App\Services\System\PushNotificationService;
|
use App\Services\System\PushNotificationService;
|
||||||
|
use App\Support\Media\MediaPresenter;
|
||||||
use Illuminate\Support\Collection;
|
use Illuminate\Support\Collection;
|
||||||
use Illuminate\Support\Facades\DB;
|
use Illuminate\Support\Facades\DB;
|
||||||
use Illuminate\Support\Facades\Log;
|
use Illuminate\Support\Facades\Log;
|
||||||
@ -28,14 +30,16 @@ public function getPendingVerificationCuttings(User $user): Collection
|
|||||||
'createdBy.profile',
|
'createdBy.profile',
|
||||||
'rejection.rejectedBy.profile',
|
'rejection.rejectedBy.profile',
|
||||||
'materials.rawMaterialPrice.rawMaterial:id,name,unit',
|
'materials.rawMaterialPrice.rawMaterial:id,name,unit',
|
||||||
|
'materials.rawMaterialPrice.media',
|
||||||
'results.productVariant.product:id,name',
|
'results.productVariant.product:id,name',
|
||||||
'results.productVariant:id,product_id,name',
|
'results.productVariant.media',
|
||||||
])
|
])
|
||||||
->completed()
|
->completed()
|
||||||
->latest()
|
->latest()
|
||||||
->get()
|
->get()
|
||||||
->each(function (Cutting $cutting): void {
|
->each(function (Cutting $cutting): void {
|
||||||
$this->appendCostPreview($cutting);
|
$this->appendCostPreview($cutting);
|
||||||
|
$this->appendPhotos($cutting);
|
||||||
$cutting->materials->each(fn (CuttingMaterial $m) => $this->breakMaterialCircularReference($m));
|
$cutting->materials->each(fn (CuttingMaterial $m) => $this->breakMaterialCircularReference($m));
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@ -48,8 +52,9 @@ public function getPendingApprovalCuttings(User $user): Collection
|
|||||||
'submittedBy.profile',
|
'submittedBy.profile',
|
||||||
'rejection.rejectedBy.profile',
|
'rejection.rejectedBy.profile',
|
||||||
'materials.rawMaterialPrice.rawMaterial:id,name,unit',
|
'materials.rawMaterialPrice.rawMaterial:id,name,unit',
|
||||||
|
'materials.rawMaterialPrice.media',
|
||||||
'results.productVariant.product:id,name',
|
'results.productVariant.product:id,name',
|
||||||
'results.productVariant:id,product_id,name',
|
'results.productVariant.media',
|
||||||
'resultPrices.productVariant:id,product_id,name',
|
'resultPrices.productVariant:id,product_id,name',
|
||||||
])
|
])
|
||||||
->pendingVerification()
|
->pendingVerification()
|
||||||
@ -57,6 +62,7 @@ public function getPendingApprovalCuttings(User $user): Collection
|
|||||||
->get()
|
->get()
|
||||||
->each(function (Cutting $cutting): void {
|
->each(function (Cutting $cutting): void {
|
||||||
$this->appendCostPreview($cutting);
|
$this->appendCostPreview($cutting);
|
||||||
|
$this->appendPhotos($cutting);
|
||||||
$cutting->materials->each(fn (CuttingMaterial $m) => $this->breakMaterialCircularReference($m));
|
$cutting->materials->each(fn (CuttingMaterial $m) => $this->breakMaterialCircularReference($m));
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@ -346,4 +352,26 @@ private function appendCostPreview(Cutting $cutting): void
|
|||||||
$cutting->setAttribute('estimated_cost_per_unit', $costPerUnit);
|
$cutting->setAttribute('estimated_cost_per_unit', $costPerUnit);
|
||||||
$cutting->setAttribute('estimated_cost_per_unit_formatted', 'Rp '.number_format($costPerUnit, 0, ',', '.'));
|
$cutting->setAttribute('estimated_cost_per_unit_formatted', 'Rp '.number_format($costPerUnit, 0, ',', '.'));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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'));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
$cutting->results->each(function (CuttingResult $result): void {
|
||||||
|
$variant = $result->productVariant;
|
||||||
|
|
||||||
|
if ($variant) {
|
||||||
|
$variant->setAttribute(
|
||||||
|
'images',
|
||||||
|
MediaPresenter::collection($variant, 'images'),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -104,7 +104,7 @@ const totalCompleted = computed(() => props.cuttings.length);
|
|||||||
<span class="font-medium text-foreground">Hasil Produk:</span>
|
<span class="font-medium text-foreground">Hasil Produk:</span>
|
||||||
<ul class="list-disc pl-4 space-y-0.5 text-muted-foreground">
|
<ul class="list-disc pl-4 space-y-0.5 text-muted-foreground">
|
||||||
<li v-for="res in cutting.results" :key="res.id">
|
<li v-for="res in cutting.results" :key="res.id">
|
||||||
{{ res.product_variant?.product?.name }} ({{ res.product_variant?.name }}) - {{ res.cutting_result }} pcs
|
{{ res.product_variant?.product?.name }} ({{ res.product_variant?.name }})<span v-if="res.cutting_result !== null && res.cutting_result !== undefined"> - {{ res.cutting_result }} pcs</span>
|
||||||
</li>
|
</li>
|
||||||
<li v-if="!cutting.results.length">Belum ada hasil produk</li>
|
<li v-if="!cutting.results.length">Belum ada hasil produk</li>
|
||||||
</ul>
|
</ul>
|
||||||
|
|||||||
@ -77,7 +77,7 @@ defineProps<{
|
|||||||
<span class="font-medium text-foreground">Hasil Produk:</span>
|
<span class="font-medium text-foreground">Hasil Produk:</span>
|
||||||
<ul class="list-disc pl-4 space-y-0.5 text-muted-foreground">
|
<ul class="list-disc pl-4 space-y-0.5 text-muted-foreground">
|
||||||
<li v-for="res in cutting.results" :key="res.id">
|
<li v-for="res in cutting.results" :key="res.id">
|
||||||
{{ res.product_variant?.product?.name }} ({{ res.product_variant?.name }})
|
{{ res.product_variant?.product?.name }} ({{ res.product_variant?.name }})<span v-if="res.cutting_result !== null && res.cutting_result !== undefined"> - {{ res.cutting_result }} pcs</span>
|
||||||
</li>
|
</li>
|
||||||
<li v-if="!cutting.results.length">Belum ada hasil produk</li>
|
<li v-if="!cutting.results.length">Belum ada hasil produk</li>
|
||||||
</ul>
|
</ul>
|
||||||
|
|||||||
@ -1,5 +1,6 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import OwnerVerificationRowActions from '@/components/owner-verification/OwnerVerificationRowActions.vue';
|
import OwnerVerificationRowActions from '@/components/owner-verification/OwnerVerificationRowActions.vue';
|
||||||
|
import MediaThumbnailCell from '@/components/media/MediaThumbnailCell.vue';
|
||||||
import { Badge } from '@/components/ui/badge';
|
import { Badge } from '@/components/ui/badge';
|
||||||
import {
|
import {
|
||||||
Table,
|
Table,
|
||||||
@ -53,6 +54,7 @@ defineProps<{
|
|||||||
<TableHeader>
|
<TableHeader>
|
||||||
<TableRow>
|
<TableRow>
|
||||||
<TableHead>Varian</TableHead>
|
<TableHead>Varian</TableHead>
|
||||||
|
<TableHead>Foto</TableHead>
|
||||||
<TableHead>Hasil</TableHead>
|
<TableHead>Hasil</TableHead>
|
||||||
<TableHead>Bagus</TableHead>
|
<TableHead>Bagus</TableHead>
|
||||||
<TableHead>Reject</TableHead>
|
<TableHead>Reject</TableHead>
|
||||||
@ -60,7 +62,7 @@ defineProps<{
|
|||||||
</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="4" class="text-muted-foreground">
|
<TableCell colspan="5" class="text-muted-foreground">
|
||||||
Belum ada hasil verifikasi
|
Belum ada hasil verifikasi
|
||||||
</TableCell>
|
</TableCell>
|
||||||
</TableRow>
|
</TableRow>
|
||||||
@ -68,6 +70,9 @@ defineProps<{
|
|||||||
<TableCell class="font-medium">
|
<TableCell class="font-medium">
|
||||||
{{ result.product_variant?.product?.name }} ({{ result.product_variant?.name }})
|
{{ result.product_variant?.product?.name }} ({{ result.product_variant?.name }})
|
||||||
</TableCell>
|
</TableCell>
|
||||||
|
<TableCell>
|
||||||
|
<MediaThumbnailCell :items="result.product_variant?.images ?? []" :max-visible="1" />
|
||||||
|
</TableCell>
|
||||||
<TableCell class="tabular-nums">
|
<TableCell class="tabular-nums">
|
||||||
{{ result.cutting_result }} pcs
|
{{ result.cutting_result }} pcs
|
||||||
</TableCell>
|
</TableCell>
|
||||||
|
|||||||
@ -1,4 +1,5 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
|
import MediaThumbnailCell from '@/components/media/MediaThumbnailCell.vue';
|
||||||
import {
|
import {
|
||||||
Table,
|
Table,
|
||||||
TableBody,
|
TableBody,
|
||||||
@ -48,6 +49,7 @@ defineProps<{
|
|||||||
<TableHeader>
|
<TableHeader>
|
||||||
<TableRow>
|
<TableRow>
|
||||||
<TableHead>Varian</TableHead>
|
<TableHead>Varian</TableHead>
|
||||||
|
<TableHead>Foto</TableHead>
|
||||||
<TableHead>Hasil</TableHead>
|
<TableHead>Hasil</TableHead>
|
||||||
<TableHead>Sample</TableHead>
|
<TableHead>Sample</TableHead>
|
||||||
<TableHead>Diluar Sample</TableHead>
|
<TableHead>Diluar Sample</TableHead>
|
||||||
@ -55,7 +57,7 @@ defineProps<{
|
|||||||
</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="4" class="text-muted-foreground">
|
<TableCell colspan="5" class="text-muted-foreground">
|
||||||
Belum ada hasil produk
|
Belum ada hasil produk
|
||||||
</TableCell>
|
</TableCell>
|
||||||
</TableRow>
|
</TableRow>
|
||||||
@ -63,6 +65,9 @@ defineProps<{
|
|||||||
<TableCell class="font-medium">
|
<TableCell class="font-medium">
|
||||||
{{ result.product_variant?.product?.name }} ({{ result.product_variant?.name }})
|
{{ result.product_variant?.product?.name }} ({{ result.product_variant?.name }})
|
||||||
</TableCell>
|
</TableCell>
|
||||||
|
<TableCell>
|
||||||
|
<MediaThumbnailCell :items="result.product_variant?.images ?? []" :max-visible="1" />
|
||||||
|
</TableCell>
|
||||||
<TableCell class="tabular-nums">
|
<TableCell class="tabular-nums">
|
||||||
{{ result.cutting_result }} pcs
|
{{ result.cutting_result }} pcs
|
||||||
</TableCell>
|
</TableCell>
|
||||||
|
|||||||
@ -1,5 +1,6 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { computed } from 'vue';
|
import { computed } from 'vue';
|
||||||
|
import MediaThumbnailCell from '@/components/media/MediaThumbnailCell.vue';
|
||||||
import { Badge } from '@/components/ui/badge';
|
import { Badge } from '@/components/ui/badge';
|
||||||
import {
|
import {
|
||||||
Empty,
|
Empty,
|
||||||
@ -99,6 +100,7 @@ const showingCount = computed(() => props.cuttings.length);
|
|||||||
<TableHeader>
|
<TableHeader>
|
||||||
<TableRow>
|
<TableRow>
|
||||||
<TableHead>Varian</TableHead>
|
<TableHead>Varian</TableHead>
|
||||||
|
<TableHead>Foto</TableHead>
|
||||||
<TableHead>Hasil</TableHead>
|
<TableHead>Hasil</TableHead>
|
||||||
<TableHead>Sample</TableHead>
|
<TableHead>Sample</TableHead>
|
||||||
<TableHead>Diluar Sample</TableHead>
|
<TableHead>Diluar Sample</TableHead>
|
||||||
@ -106,14 +108,14 @@ const showingCount = computed(() => props.cuttings.length);
|
|||||||
</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="4" class="text-muted-foreground">
|
<TableCell colspan="5" class="text-muted-foreground">
|
||||||
Belum ada hasil produk
|
Belum ada hasil produk
|
||||||
</TableCell>
|
</TableCell>
|
||||||
</TableRow>
|
</TableRow>
|
||||||
<template v-else v-for="group in getGroupedResults(cutting.results)"
|
<template v-else v-for="group in getGroupedResults(cutting.results)"
|
||||||
:key="group.productId">
|
:key="group.productId">
|
||||||
<TableRow class="bg-muted/20 hover:bg-muted/20">
|
<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 }}
|
{{ group.productName }}
|
||||||
</TableCell>
|
</TableCell>
|
||||||
</TableRow>
|
</TableRow>
|
||||||
@ -121,6 +123,9 @@ const showingCount = computed(() => props.cuttings.length);
|
|||||||
<TableCell class="pl-6 font-medium">
|
<TableCell class="pl-6 font-medium">
|
||||||
{{ result.product_variant?.name }}
|
{{ result.product_variant?.name }}
|
||||||
</TableCell>
|
</TableCell>
|
||||||
|
<TableCell>
|
||||||
|
<MediaThumbnailCell :items="result.product_variant?.images ?? []" :max-visible="1" />
|
||||||
|
</TableCell>
|
||||||
<TableCell class="tabular-nums">
|
<TableCell class="tabular-nums">
|
||||||
{{ result.cutting_result }} pcs
|
{{ result.cutting_result }} pcs
|
||||||
</TableCell>
|
</TableCell>
|
||||||
|
|||||||
@ -4,6 +4,7 @@ import { computed, watch } from 'vue';
|
|||||||
import { toast } from 'vue-sonner';
|
import { toast } from 'vue-sonner';
|
||||||
import { NumberInput } from '@/components/form/number-input';
|
import { NumberInput } from '@/components/form/number-input';
|
||||||
import { RupiahInput } from '@/components/form/rupiah-input';
|
import { RupiahInput } from '@/components/form/rupiah-input';
|
||||||
|
import MediaThumbnailCell from '@/components/media/MediaThumbnailCell.vue';
|
||||||
import { Button } from '@/components/ui/button';
|
import { Button } from '@/components/ui/button';
|
||||||
import {
|
import {
|
||||||
Dialog,
|
Dialog,
|
||||||
@ -177,6 +178,9 @@ function submitVerify() {
|
|||||||
<div v-for="(result, index) in verifyForm.results" :key="result.product_variant_id"
|
<div v-for="(result, index) in verifyForm.results" :key="result.product_variant_id"
|
||||||
class="flex flex-col gap-1 rounded-lg border p-3">
|
class="flex flex-col gap-1 rounded-lg border p-3">
|
||||||
<div class="flex items-center gap-3">
|
<div class="flex items-center gap-3">
|
||||||
|
<div class="shrink-0">
|
||||||
|
<MediaThumbnailCell :items="cutting.results[index]?.product_variant?.images ?? []" :max-visible="1" />
|
||||||
|
</div>
|
||||||
<div class="flex-1 min-w-0">
|
<div class="flex-1 min-w-0">
|
||||||
<p class="font-medium text-sm truncate">{{ result.name }}</p>
|
<p class="font-medium text-sm truncate">{{ result.name }}</p>
|
||||||
<p class="text-xs text-muted-foreground">
|
<p class="text-xs text-muted-foreground">
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user