feat: add raw material selection functionality and improve filtering in CuttingPos components for enhanced user experience
This commit is contained in:
parent
da4467b500
commit
0ec51e1ffb
@ -72,6 +72,7 @@ const form = useForm({
|
||||
|
||||
const {
|
||||
materialSearch,
|
||||
selectedRawMaterialId,
|
||||
productSearch,
|
||||
materialCart,
|
||||
resultCart,
|
||||
@ -267,6 +268,7 @@ function onProductCreated(product: CuttingProductCatalogItem) {
|
||||
<div class="grid min-w-0 gap-4 xl:grid-cols-[1fr_400px]">
|
||||
<div class="min-w-0 space-y-4">
|
||||
<CuttingPosMaterialCatalogPanel v-model:material-search="materialSearch"
|
||||
v-model:selected-raw-material-id="selectedRawMaterialId"
|
||||
:filtered-raw-materials="filteredRawMaterials" :raw-material-catalog="rawMaterialCatalogState"
|
||||
:material-cart="materialCart" :get-material-cart-item="getMaterialCartItem" :units="units"
|
||||
@add-material="addMaterial" @decrease-material-qty="decreaseMaterialQty"
|
||||
|
||||
@ -13,6 +13,13 @@ import {
|
||||
EmptyTitle,
|
||||
} from '@/components/ui/empty';
|
||||
import { Input } from '@/components/ui/input';
|
||||
import {
|
||||
Select,
|
||||
SelectContent,
|
||||
SelectItem,
|
||||
SelectTrigger,
|
||||
SelectValue,
|
||||
} from '@/components/ui/select';
|
||||
import { getFirstCoverImage } from '@/lib/catalog-cover';
|
||||
import type { CuttingMaterialCartItem, CuttingRawMaterialCatalogItem } from '@/types/cutting';
|
||||
import type { EnumOption } from '@/types/raw-material';
|
||||
@ -29,6 +36,7 @@ defineProps<{
|
||||
}>();
|
||||
|
||||
const materialSearch = defineModel<string>('materialSearch', { required: true });
|
||||
const selectedRawMaterialId = defineModel<string>('selectedRawMaterialId', { required: true });
|
||||
|
||||
const emit = defineEmits<{
|
||||
'add-material': [rawMaterial: CuttingRawMaterialCatalogItem, price: CuttingCatalogPrice];
|
||||
@ -61,9 +69,24 @@ function openCombination(rawMaterial: CuttingRawMaterialCatalogItem, price: Cutt
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<div class="space-y-4">
|
||||
<div class="relative">
|
||||
<Search class="absolute top-1/2 left-3 size-4 -translate-y-1/2 text-muted-foreground" />
|
||||
<Input v-model="materialSearch" placeholder="Cari bahan baku..." class="pl-9" />
|
||||
<div class="flex flex-col gap-2 sm:flex-row sm:items-center">
|
||||
<div class="relative flex-1">
|
||||
<Search class="absolute top-1/2 left-3 size-4 -translate-y-1/2 text-muted-foreground" />
|
||||
<Input v-model="materialSearch" placeholder="Cari varian..." class="pl-9" />
|
||||
</div>
|
||||
<div class="w-full sm:w-60">
|
||||
<Select v-model="selectedRawMaterialId">
|
||||
<SelectTrigger class="w-full">
|
||||
<SelectValue placeholder="Semua Bahan Baku" />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectItem value="all">Semua Bahan Baku</SelectItem>
|
||||
<SelectItem v-for="item in rawMaterialCatalog" :key="item.id" :value="String(item.id)">
|
||||
{{ item.name }}
|
||||
</SelectItem>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div v-if="filteredRawMaterials.length === 0" class="py-8">
|
||||
@ -77,7 +100,7 @@ function openCombination(rawMaterial: CuttingRawMaterialCatalogItem, price: Cutt
|
||||
</Empty>
|
||||
</div>
|
||||
|
||||
<div v-else class="grid grid-cols-1 gap-4 md:grid-cols-2">
|
||||
<div v-else class="columns-1 gap-4 sm:columns-2 xl:columns-3">
|
||||
<template v-for="rawMaterial in filteredRawMaterials" :key="rawMaterial.id">
|
||||
<PosCatalogCard v-if="rawMaterial.prices.length > 0"
|
||||
:title="rawMaterial.name" :cover-image="getFirstCoverImage(rawMaterial.prices)">
|
||||
@ -106,46 +129,37 @@ function openCombination(rawMaterial: CuttingRawMaterialCatalogItem, price: Cutt
|
||||
Stok: {{ price.stock_formatted }}
|
||||
</p>
|
||||
</div>
|
||||
<div v-if="getMaterialCartItem(price.id)" class="flex shrink-0 items-center gap-1.5">
|
||||
<div v-if="getMaterialCartItem(price.id)" class="flex shrink-0 items-center gap-1">
|
||||
<Button type="button" variant="outline" size="icon-sm"
|
||||
title="Kurangi Pemakaian"
|
||||
@click.stop="emit('decrease-material-qty', price.id)">
|
||||
<Minus class="size-3.5" />
|
||||
</Button>
|
||||
<span class="min-w-5 text-center text-xs font-semibold tabular-nums">
|
||||
{{ getMaterialCartItem(price.id)!.material_usage }}
|
||||
</span>
|
||||
<div class="flex shrink-0 items-center gap-1.5">
|
||||
<div class="flex flex-col items-center gap-0.5">
|
||||
<Button type="button" variant="outline" size="icon-sm"
|
||||
@click.stop="emit('add-material', rawMaterial, price)">
|
||||
<Plus class="size-3.5" />
|
||||
</Button>
|
||||
<span class="text-[9px] text-muted-foreground leading-none">Keranjang</span>
|
||||
</div>
|
||||
<div class="flex flex-col items-center gap-0.5">
|
||||
<Button type="button" variant="default" size="icon-sm"
|
||||
@click.stop="openCombination(rawMaterial, price)">
|
||||
<Layers class="size-3.5" />
|
||||
</Button>
|
||||
<span class="text-[9px] text-muted-foreground leading-none">Kombinasi</span>
|
||||
</div>
|
||||
</div>
|
||||
<Button type="button" variant="outline" size="icon-sm"
|
||||
title="Tambah ke Keranjang"
|
||||
@click.stop="emit('add-material', rawMaterial, price)">
|
||||
<Plus class="size-3.5" />
|
||||
</Button>
|
||||
<Button type="button" variant="default" size="icon-sm"
|
||||
title="Gunakan Kombinasi"
|
||||
@click.stop="openCombination(rawMaterial, price)">
|
||||
<Layers class="size-3.5" />
|
||||
</Button>
|
||||
</div>
|
||||
<div v-else class="flex shrink-0 items-center gap-1.5">
|
||||
<div class="flex flex-col items-center gap-0.5">
|
||||
<Button type="button" variant="outline" size="icon-sm"
|
||||
@click.stop="emit('add-material', rawMaterial, price)">
|
||||
<Plus class="size-3.5" />
|
||||
</Button>
|
||||
<span class="text-[9px] text-muted-foreground leading-none">Keranjang</span>
|
||||
</div>
|
||||
<div class="flex flex-col items-center gap-0.5">
|
||||
<Button type="button" variant="default" size="icon-sm"
|
||||
@click.stop="openCombination(rawMaterial, price)">
|
||||
<Layers class="size-3.5" />
|
||||
</Button>
|
||||
<span class="text-[9px] text-muted-foreground leading-none">Kombinasi</span>
|
||||
</div>
|
||||
<div v-else class="flex shrink-0 items-center gap-1">
|
||||
<Button type="button" variant="outline" size="icon-sm"
|
||||
title="Tambah ke Keranjang"
|
||||
@click.stop="emit('add-material', rawMaterial, price)">
|
||||
<Plus class="size-3.5" />
|
||||
</Button>
|
||||
<Button type="button" variant="default" size="icon-sm"
|
||||
title="Gunakan Kombinasi"
|
||||
@click.stop="openCombination(rawMaterial, price)">
|
||||
<Layers class="size-3.5" />
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</PosCatalogCard>
|
||||
|
||||
@ -64,7 +64,7 @@ const quickCreateOpen = ref(false);
|
||||
</Empty>
|
||||
</div>
|
||||
|
||||
<div v-else class="grid grid-cols-1 gap-4 sm:grid-cols-2">
|
||||
<div v-else class="columns-1 gap-4 sm:columns-2 xl:columns-3">
|
||||
<PosCatalogCard v-for="product in filteredProducts" :key="product.id" :title="product.name"
|
||||
:cover-image="getFirstCoverImage(product.variants)">
|
||||
<p v-if="!product.variants.length" class="px-3 py-4 text-sm text-muted-foreground">
|
||||
|
||||
@ -21,6 +21,7 @@ export function useCuttingPosCart(options: {
|
||||
isCreateMode: MaybeRefOrGetter<boolean>;
|
||||
}) {
|
||||
const materialSearch = ref('');
|
||||
const selectedRawMaterialId = ref('all');
|
||||
const productSearch = ref('');
|
||||
const materialCart = ref<CuttingMaterialCartItem[]>([]);
|
||||
const resultCart = ref<CuttingResultCartItem[]>([]);
|
||||
@ -61,17 +62,31 @@ export function useCuttingPosCart(options: {
|
||||
|
||||
const filteredRawMaterials = computed(() => {
|
||||
const keyword = materialSearch.value.trim().toLowerCase();
|
||||
const rawMaterialId = selectedRawMaterialId.value;
|
||||
const catalog = toValue(options.rawMaterialCatalog);
|
||||
|
||||
if (!keyword) {
|
||||
return catalog;
|
||||
let items = catalog;
|
||||
if (rawMaterialId !== 'all') {
|
||||
const idNum = Number(rawMaterialId);
|
||||
items = catalog.filter((rawMaterial) => rawMaterial.id === idNum);
|
||||
}
|
||||
|
||||
return catalog.filter(
|
||||
(rawMaterial) =>
|
||||
rawMaterial.name.toLowerCase().includes(keyword)
|
||||
|| rawMaterial.prices.some((price) => price.variant.toLowerCase().includes(keyword)),
|
||||
);
|
||||
if (!keyword) {
|
||||
return items;
|
||||
}
|
||||
|
||||
return items
|
||||
.map((rawMaterial) => {
|
||||
const matchingPrices = rawMaterial.prices.filter((price) =>
|
||||
price.variant.toLowerCase().includes(keyword),
|
||||
);
|
||||
|
||||
return {
|
||||
...rawMaterial,
|
||||
prices: matchingPrices,
|
||||
};
|
||||
})
|
||||
.filter((rawMaterial) => rawMaterial.prices.length > 0);
|
||||
});
|
||||
|
||||
const filteredProducts = computed(() => {
|
||||
@ -502,6 +517,7 @@ export function useCuttingPosCart(options: {
|
||||
|
||||
return {
|
||||
materialSearch,
|
||||
selectedRawMaterialId,
|
||||
productSearch,
|
||||
materialCart,
|
||||
resultCart,
|
||||
|
||||
Loading…
Reference in New Issue
Block a user