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 materialCart = ref<CuttingMaterialCartItem[]>([]);
|
||||||
const resultCart = ref<CuttingResultCartItem[]>([]);
|
const resultCart = ref<CuttingResultCartItem[]>([]);
|
||||||
const costModalOpen = ref(false);
|
const costModalOpen = ref(false);
|
||||||
|
const cartDetailOpen = ref(false);
|
||||||
|
|
||||||
const form = useForm({
|
const form = useForm({
|
||||||
description: '',
|
description: '',
|
||||||
@ -723,13 +724,23 @@ function confirmSubmit() {
|
|||||||
<Scissors class="size-4" />
|
<Scissors class="size-4" />
|
||||||
Ringkasan Cutting
|
Ringkasan Cutting
|
||||||
</span>
|
</span>
|
||||||
<Badge
|
<span class="flex items-center gap-2">
|
||||||
v-if="totalResultPieces > 0"
|
<button
|
||||||
variant="secondary"
|
v-if="materialCart.length > 0 || resultCart.length > 0"
|
||||||
class="tabular-nums font-semibold"
|
type="button"
|
||||||
>
|
class="text-xs font-normal text-primary underline underline-offset-2 hover:text-primary/80"
|
||||||
Total: {{ totalResultPieces }} pcs
|
@click="cartDetailOpen = true"
|
||||||
</Badge>
|
>
|
||||||
|
Lihat Detail
|
||||||
|
</button>
|
||||||
|
<Badge
|
||||||
|
v-if="totalResultPieces > 0"
|
||||||
|
variant="secondary"
|
||||||
|
class="tabular-nums font-semibold"
|
||||||
|
>
|
||||||
|
Total: {{ totalResultPieces }} pcs
|
||||||
|
</Badge>
|
||||||
|
</span>
|
||||||
</CardTitle>
|
</CardTitle>
|
||||||
</CardHeader>
|
</CardHeader>
|
||||||
<CardContent>
|
<CardContent>
|
||||||
@ -762,7 +773,7 @@ function confirmSubmit() {
|
|||||||
Belum ada bahan baku dipilih.
|
Belum ada bahan baku dipilih.
|
||||||
</div>
|
</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
|
<div
|
||||||
v-for="(item, index) in materialCart"
|
v-for="(item, index) in materialCart"
|
||||||
:key="item.raw_material_price_id"
|
:key="item.raw_material_price_id"
|
||||||
@ -855,7 +866,7 @@ function confirmSubmit() {
|
|||||||
Belum ada produk hasil dipilih.
|
Belum ada produk hasil dipilih.
|
||||||
</div>
|
</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
|
<div
|
||||||
v-for="(item, index) in resultCart"
|
v-for="(item, index) in resultCart"
|
||||||
:key="item.product_variant_id"
|
:key="item.product_variant_id"
|
||||||
@ -1064,4 +1075,72 @@ function confirmSubmit() {
|
|||||||
</DialogFooter>
|
</DialogFooter>
|
||||||
</DialogContent>
|
</DialogContent>
|
||||||
</Dialog>
|
</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>
|
</template>
|
||||||
|
|||||||
@ -10,6 +10,12 @@ import { NumberInput } from '@/components/form/number-input';
|
|||||||
import { RupiahInput } from '@/components/form/rupiah-input';
|
import { RupiahInput } from '@/components/form/rupiah-input';
|
||||||
import { Button } from '@/components/ui/button';
|
import { Button } from '@/components/ui/button';
|
||||||
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';
|
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';
|
||||||
|
import {
|
||||||
|
Dialog,
|
||||||
|
DialogContent,
|
||||||
|
DialogHeader,
|
||||||
|
DialogTitle,
|
||||||
|
} from '@/components/ui/dialog';
|
||||||
import {
|
import {
|
||||||
Empty,
|
Empty,
|
||||||
EmptyDescription,
|
EmptyDescription,
|
||||||
@ -91,6 +97,7 @@ const search = ref('');
|
|||||||
const selectedStockQuality = ref<'good' | 'reject'>('good');
|
const selectedStockQuality = ref<'good' | 'reject'>('good');
|
||||||
const cart = ref<OrderCartItem[]>([]);
|
const cart = ref<OrderCartItem[]>([]);
|
||||||
const customerFormOpen = ref(false);
|
const customerFormOpen = ref(false);
|
||||||
|
const cartDetailOpen = ref(false);
|
||||||
const printAfterSave = ref(false);
|
const printAfterSave = ref(false);
|
||||||
const selectedPaperSize = ref<'58mm' | '80mm'>('80mm');
|
const selectedPaperSize = ref<'58mm' | '80mm'>('80mm');
|
||||||
|
|
||||||
@ -580,9 +587,19 @@ function submit() {
|
|||||||
|
|
||||||
<Card class="h-fit xl:sticky xl:top-4">
|
<Card class="h-fit xl:sticky xl:top-4">
|
||||||
<CardHeader class="pb-3">
|
<CardHeader class="pb-3">
|
||||||
<CardTitle class="flex items-center gap-2 text-base">
|
<CardTitle class="flex items-center justify-between gap-2 text-base">
|
||||||
<ShoppingCart class="size-4" />
|
<span class="flex items-center gap-2">
|
||||||
Ringkasan Pesanan
|
<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>
|
</CardTitle>
|
||||||
</CardHeader>
|
</CardHeader>
|
||||||
<CardContent>
|
<CardContent>
|
||||||
@ -856,4 +873,40 @@ function submit() {
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<CustomerQuickCreateModal v-model:open="customerFormOpen" @created="onCustomerCreated" />
|
<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>
|
</template>
|
||||||
|
|||||||
@ -11,6 +11,12 @@ import { RupiahInput } from '@/components/form/rupiah-input';
|
|||||||
import { Badge } from '@/components/ui/badge';
|
import { Badge } from '@/components/ui/badge';
|
||||||
import { Button } from '@/components/ui/button';
|
import { Button } from '@/components/ui/button';
|
||||||
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';
|
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';
|
||||||
|
import {
|
||||||
|
Dialog,
|
||||||
|
DialogContent,
|
||||||
|
DialogHeader,
|
||||||
|
DialogTitle,
|
||||||
|
} from '@/components/ui/dialog';
|
||||||
import {
|
import {
|
||||||
Empty,
|
Empty,
|
||||||
EmptyDescription,
|
EmptyDescription,
|
||||||
@ -63,6 +69,7 @@ const props = defineProps<{
|
|||||||
const isCreateMode = computed(() => props.method === 'post');
|
const isCreateMode = computed(() => props.method === 'post');
|
||||||
const search = ref('');
|
const search = ref('');
|
||||||
const cart = ref<PurchaseCartItem[]>([]);
|
const cart = ref<PurchaseCartItem[]>([]);
|
||||||
|
const cartDetailOpen = ref(false);
|
||||||
const existingPhotoId = ref<number | null>(null);
|
const existingPhotoId = ref<number | null>(null);
|
||||||
|
|
||||||
const currentPhotoUrl = computed(() => props.initialData?.photos?.url ?? null);
|
const currentPhotoUrl = computed(() => props.initialData?.photos?.url ?? null);
|
||||||
@ -415,9 +422,19 @@ function submit() {
|
|||||||
|
|
||||||
<Card class="h-fit xl:sticky xl:top-4">
|
<Card class="h-fit xl:sticky xl:top-4">
|
||||||
<CardHeader class="pb-3">
|
<CardHeader class="pb-3">
|
||||||
<CardTitle class="flex items-center gap-2 text-base">
|
<CardTitle class="flex items-center justify-between gap-2 text-base">
|
||||||
<ShoppingCart class="size-4" />
|
<span class="flex items-center gap-2">
|
||||||
Ringkasan Belanja
|
<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>
|
</CardTitle>
|
||||||
</CardHeader>
|
</CardHeader>
|
||||||
<CardContent>
|
<CardContent>
|
||||||
@ -547,4 +564,39 @@ function submit() {
|
|||||||
</CardContent>
|
</CardContent>
|
||||||
</Card>
|
</Card>
|
||||||
</div>
|
</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>
|
</template>
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user