feat: add cart detail dialog in Cutting, Order, and Purchase components for improved user experience and item overview
This commit is contained in:
parent
e6f1fcc97e
commit
4d5901cc63
@ -69,6 +69,7 @@ const productSearch = ref('');
|
||||
const materialCart = ref<CuttingMaterialCartItem[]>([]);
|
||||
const resultCart = ref<CuttingResultCartItem[]>([]);
|
||||
const costModalOpen = ref(false);
|
||||
const cartDetailOpen = ref(false);
|
||||
|
||||
const form = useForm({
|
||||
description: '',
|
||||
@ -723,13 +724,23 @@ function confirmSubmit() {
|
||||
<Scissors class="size-4" />
|
||||
Ringkasan Cutting
|
||||
</span>
|
||||
<Badge
|
||||
v-if="totalResultPieces > 0"
|
||||
variant="secondary"
|
||||
class="tabular-nums font-semibold"
|
||||
>
|
||||
Total: {{ totalResultPieces }} pcs
|
||||
</Badge>
|
||||
<span class="flex items-center gap-2">
|
||||
<button
|
||||
v-if="materialCart.length > 0 || resultCart.length > 0"
|
||||
type="button"
|
||||
class="text-xs font-normal text-primary underline underline-offset-2 hover:text-primary/80"
|
||||
@click="cartDetailOpen = true"
|
||||
>
|
||||
Lihat Detail
|
||||
</button>
|
||||
<Badge
|
||||
v-if="totalResultPieces > 0"
|
||||
variant="secondary"
|
||||
class="tabular-nums font-semibold"
|
||||
>
|
||||
Total: {{ totalResultPieces }} pcs
|
||||
</Badge>
|
||||
</span>
|
||||
</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
@ -762,7 +773,7 @@ function confirmSubmit() {
|
||||
Belum ada bahan baku dipilih.
|
||||
</div>
|
||||
|
||||
<div v-else class="space-y-3">
|
||||
<div v-else class="scrollbar-thin max-h-80 space-y-3 overflow-y-auto overscroll-y-contain">
|
||||
<div
|
||||
v-for="(item, index) in materialCart"
|
||||
:key="item.raw_material_price_id"
|
||||
@ -855,7 +866,7 @@ function confirmSubmit() {
|
||||
Belum ada produk hasil dipilih.
|
||||
</div>
|
||||
|
||||
<div v-else class="space-y-3">
|
||||
<div v-else class="scrollbar-thin max-h-80 space-y-3 overflow-y-auto overscroll-y-contain">
|
||||
<div
|
||||
v-for="(item, index) in resultCart"
|
||||
:key="item.product_variant_id"
|
||||
@ -1064,4 +1075,72 @@ function confirmSubmit() {
|
||||
</DialogFooter>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
|
||||
<Dialog v-model:open="cartDetailOpen">
|
||||
<DialogContent class="sm:max-w-lg">
|
||||
<DialogHeader>
|
||||
<DialogTitle>Detail Keranjang</DialogTitle>
|
||||
</DialogHeader>
|
||||
<div class="max-h-96 space-y-4 overflow-y-auto overscroll-y-contain scrollbar-thin">
|
||||
<div v-if="materialCart.length > 0">
|
||||
<p class="mb-2 text-xs font-medium text-muted-foreground">Bahan Baku</p>
|
||||
<div class="space-y-2">
|
||||
<div
|
||||
v-for="item in materialCart"
|
||||
:key="`detail-mat-${item.raw_material_price_id}`"
|
||||
class="rounded-lg border p-3"
|
||||
>
|
||||
<div class="flex items-start justify-between gap-2">
|
||||
<div class="min-w-0">
|
||||
<p class="truncate text-sm font-medium">{{ item.raw_material_name }}</p>
|
||||
<p class="truncate text-xs text-muted-foreground">
|
||||
{{ item.variant }} · Stok {{ item.stock_input }} {{ item.unit_abbreviation }}
|
||||
</p>
|
||||
</div>
|
||||
<span class="shrink-0 text-xs font-medium tabular-nums">
|
||||
{{ item.material_usage }} {{ item.uses_length_unit ? 'cm' : item.unit_abbreviation }}
|
||||
</span>
|
||||
</div>
|
||||
<div class="mt-1.5 flex items-center justify-between gap-2 text-xs text-muted-foreground">
|
||||
<span>Sisa: {{ item.remaining_material }} {{ item.uses_length_unit ? 'cm' : item.unit_abbreviation }}</span>
|
||||
<span class="font-medium text-foreground">{{ formatRupiah(materialLineCost(item)) }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div v-if="resultCart.length > 0">
|
||||
<p class="mb-2 text-xs font-medium text-muted-foreground">Hasil Produk</p>
|
||||
<div class="space-y-2">
|
||||
<div
|
||||
v-for="item in resultCart"
|
||||
:key="`detail-res-${item.product_variant_id}`"
|
||||
class="rounded-lg border p-3"
|
||||
>
|
||||
<div class="flex items-start justify-between gap-2">
|
||||
<div class="min-w-0">
|
||||
<p class="truncate text-sm font-medium">{{ item.product_name }}</p>
|
||||
<p class="truncate text-xs text-muted-foreground">
|
||||
{{ item.variant_name }} · Stok {{ item.stock }} pcs
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="mt-1.5 flex items-center gap-3 text-xs tabular-nums">
|
||||
<span>Hasil: <strong>{{ item.cutting_result }}</strong></span>
|
||||
<span class="text-muted-foreground">·</span>
|
||||
<span>Bagus: <strong>{{ item.warehouse_stock }}</strong></span>
|
||||
<span class="text-muted-foreground">·</span>
|
||||
<span>Reject: <strong>{{ item.cutting_reject }}</strong></span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="border-t pt-3">
|
||||
<div class="flex items-center justify-between text-sm font-semibold">
|
||||
<span>Total Harga Modal</span>
|
||||
<span class="text-primary">{{ formatRupiah(totalProductionCost) }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
</template>
|
||||
|
||||
@ -10,6 +10,12 @@ import { NumberInput } from '@/components/form/number-input';
|
||||
import { RupiahInput } from '@/components/form/rupiah-input';
|
||||
import { Button } from '@/components/ui/button';
|
||||
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';
|
||||
import {
|
||||
Dialog,
|
||||
DialogContent,
|
||||
DialogHeader,
|
||||
DialogTitle,
|
||||
} from '@/components/ui/dialog';
|
||||
import {
|
||||
Empty,
|
||||
EmptyDescription,
|
||||
@ -91,6 +97,7 @@ const search = ref('');
|
||||
const selectedStockQuality = ref<'good' | 'reject'>('good');
|
||||
const cart = ref<OrderCartItem[]>([]);
|
||||
const customerFormOpen = ref(false);
|
||||
const cartDetailOpen = ref(false);
|
||||
const printAfterSave = ref(false);
|
||||
const selectedPaperSize = ref<'58mm' | '80mm'>('80mm');
|
||||
|
||||
@ -580,9 +587,19 @@ function submit() {
|
||||
|
||||
<Card class="h-fit xl:sticky xl:top-4">
|
||||
<CardHeader class="pb-3">
|
||||
<CardTitle class="flex items-center gap-2 text-base">
|
||||
<ShoppingCart class="size-4" />
|
||||
Ringkasan Pesanan
|
||||
<CardTitle class="flex items-center justify-between gap-2 text-base">
|
||||
<span class="flex items-center gap-2">
|
||||
<ShoppingCart class="size-4" />
|
||||
Ringkasan Pesanan
|
||||
</span>
|
||||
<button
|
||||
v-if="cart.length > 0"
|
||||
type="button"
|
||||
class="text-xs font-normal text-primary underline underline-offset-2 hover:text-primary/80"
|
||||
@click="cartDetailOpen = true"
|
||||
>
|
||||
Lihat Detail
|
||||
</button>
|
||||
</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
@ -856,4 +873,40 @@ function submit() {
|
||||
</div>
|
||||
|
||||
<CustomerQuickCreateModal v-model:open="customerFormOpen" @created="onCustomerCreated" />
|
||||
|
||||
<Dialog v-model:open="cartDetailOpen">
|
||||
<DialogContent class="sm:max-w-lg">
|
||||
<DialogHeader>
|
||||
<DialogTitle>Detail Keranjang</DialogTitle>
|
||||
</DialogHeader>
|
||||
<div class="max-h-96 space-y-3 overflow-y-auto overscroll-y-contain scrollbar-thin">
|
||||
<div
|
||||
v-for="item in cart"
|
||||
:key="`detail-${item.product_variant_id}-${item.stock_quality}`"
|
||||
class="rounded-lg border p-3"
|
||||
>
|
||||
<div class="flex items-start justify-between gap-2">
|
||||
<div class="min-w-0">
|
||||
<p class="truncate text-sm font-medium">{{ item.product_name }}</p>
|
||||
<p class="truncate text-xs text-muted-foreground">
|
||||
{{ item.variant_name }}
|
||||
· {{ item.stock_quality_label ?? (item.stock_quality === 'reject' ? 'Reject' : 'Bagus') }}
|
||||
</p>
|
||||
</div>
|
||||
<span class="shrink-0 text-xs font-medium tabular-nums">{{ item.quantity }} pcs</span>
|
||||
</div>
|
||||
<div class="mt-1.5 flex items-center justify-between gap-2 text-xs text-muted-foreground">
|
||||
<span>@ Rp {{ formatRupiah(item.unit_price) }}</span>
|
||||
<span class="font-medium text-foreground">Rp {{ formatRupiah(lineSubtotal(item)) }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="border-t pt-3">
|
||||
<div class="flex items-center justify-between text-sm font-semibold">
|
||||
<span>Total</span>
|
||||
<span class="text-primary">Rp {{ formatRupiah(totalAmount) }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
</template>
|
||||
|
||||
@ -11,6 +11,12 @@ import { RupiahInput } from '@/components/form/rupiah-input';
|
||||
import { Badge } from '@/components/ui/badge';
|
||||
import { Button } from '@/components/ui/button';
|
||||
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';
|
||||
import {
|
||||
Dialog,
|
||||
DialogContent,
|
||||
DialogHeader,
|
||||
DialogTitle,
|
||||
} from '@/components/ui/dialog';
|
||||
import {
|
||||
Empty,
|
||||
EmptyDescription,
|
||||
@ -63,6 +69,7 @@ const props = defineProps<{
|
||||
const isCreateMode = computed(() => props.method === 'post');
|
||||
const search = ref('');
|
||||
const cart = ref<PurchaseCartItem[]>([]);
|
||||
const cartDetailOpen = ref(false);
|
||||
const existingPhotoId = ref<number | null>(null);
|
||||
|
||||
const currentPhotoUrl = computed(() => props.initialData?.photos?.url ?? null);
|
||||
@ -415,9 +422,19 @@ function submit() {
|
||||
|
||||
<Card class="h-fit xl:sticky xl:top-4">
|
||||
<CardHeader class="pb-3">
|
||||
<CardTitle class="flex items-center gap-2 text-base">
|
||||
<ShoppingCart class="size-4" />
|
||||
Ringkasan Belanja
|
||||
<CardTitle class="flex items-center justify-between gap-2 text-base">
|
||||
<span class="flex items-center gap-2">
|
||||
<ShoppingCart class="size-4" />
|
||||
Ringkasan Belanja
|
||||
</span>
|
||||
<button
|
||||
v-if="cart.length > 0"
|
||||
type="button"
|
||||
class="text-xs font-normal text-primary underline underline-offset-2 hover:text-primary/80"
|
||||
@click="cartDetailOpen = true"
|
||||
>
|
||||
Lihat Detail
|
||||
</button>
|
||||
</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
@ -547,4 +564,39 @@ function submit() {
|
||||
</CardContent>
|
||||
</Card>
|
||||
</div>
|
||||
|
||||
<Dialog v-model:open="cartDetailOpen">
|
||||
<DialogContent class="sm:max-w-lg">
|
||||
<DialogHeader>
|
||||
<DialogTitle>Detail Keranjang</DialogTitle>
|
||||
</DialogHeader>
|
||||
<div class="max-h-96 space-y-3 overflow-y-auto overscroll-y-contain scrollbar-thin">
|
||||
<div
|
||||
v-for="item in cart"
|
||||
:key="`detail-${item.raw_material_price_id}`"
|
||||
class="rounded-lg border p-3"
|
||||
>
|
||||
<div class="flex items-start justify-between gap-2">
|
||||
<div class="min-w-0">
|
||||
<p class="truncate text-sm font-medium">{{ item.raw_material_name }}</p>
|
||||
<p class="truncate text-xs text-muted-foreground">{{ item.variant }}</p>
|
||||
</div>
|
||||
<span class="shrink-0 text-xs font-medium tabular-nums">
|
||||
{{ item.quantity }} {{ item.unit_abbreviation }}
|
||||
</span>
|
||||
</div>
|
||||
<div class="mt-1.5 flex items-center justify-between gap-2 text-xs text-muted-foreground">
|
||||
<span>@ Rp {{ formatRupiah(item.unit_price) }}</span>
|
||||
<span class="font-medium text-foreground">Rp {{ formatRupiah(lineSubtotal(item)) }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="border-t pt-3">
|
||||
<div class="flex items-center justify-between text-sm font-semibold">
|
||||
<span>Total</span>
|
||||
<span class="text-primary">Rp {{ formatRupiah(total) }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
</template>
|
||||
|
||||
Loading…
Reference in New Issue
Block a user