feat: enhance media preview components by adding stock information support, improving user experience in managing media items
This commit is contained in:
parent
62beb309a8
commit
bed22579b5
@ -22,11 +22,13 @@ const props = withDefaults(
|
||||
urls?: string[];
|
||||
title?: string;
|
||||
titles?: string[];
|
||||
stocks?: string[];
|
||||
}>(),
|
||||
{
|
||||
urls: () => [],
|
||||
title: 'Pratinjau Foto',
|
||||
titles: () => [],
|
||||
stocks: () => [],
|
||||
}
|
||||
);
|
||||
|
||||
@ -74,6 +76,14 @@ const activeTitle = computed(() => {
|
||||
return props.title ?? 'Pratinjau Foto';
|
||||
});
|
||||
|
||||
const activeStock = computed(() => {
|
||||
if (props.stocks && props.stocks.length > 0 && currentIndex.value >= 0) {
|
||||
return props.stocks[currentIndex.value] ?? null;
|
||||
}
|
||||
|
||||
return null;
|
||||
});
|
||||
|
||||
function nextImage() {
|
||||
if (!hasMultiple.value) {
|
||||
return;
|
||||
@ -142,6 +152,7 @@ function handleTouchEnd(e: TouchEvent) {
|
||||
<DialogContent class="sm:max-w-2xl p-4 overflow-hidden gap-4">
|
||||
<DialogHeader class="pb-2 border-b">
|
||||
<DialogTitle class="text-base font-semibold">{{ activeTitle }}</DialogTitle>
|
||||
<p v-if="activeStock" class="text-sm text-muted-foreground">{{ activeStock }}</p>
|
||||
</DialogHeader>
|
||||
|
||||
<div class="relative flex items-center justify-center min-h-[300px] select-none"
|
||||
|
||||
@ -15,6 +15,7 @@ const props = defineProps<{
|
||||
title?: string;
|
||||
allUrls?: string[];
|
||||
allTitles?: string[];
|
||||
allStocks?: string[];
|
||||
}>();
|
||||
|
||||
const previewOpen = ref(false);
|
||||
@ -121,5 +122,5 @@ function openGallery() {
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
|
||||
<MediaPreviewDialog v-model:open="previewOpen" v-model:url="previewUrl" :urls="allUrls ?? items.map(item => item.url)" :title="title" :titles="allTitles" @close="onPreviewClose" />
|
||||
<MediaPreviewDialog v-model:open="previewOpen" v-model:url="previewUrl" :urls="allUrls ?? items.map(item => item.url)" :title="title" :titles="allTitles" :stocks="allStocks" @close="onPreviewClose" />
|
||||
</template>
|
||||
|
||||
@ -195,6 +195,7 @@ const groupedResults = computed<GroupedResults[]>(() => {
|
||||
?.images ?? []
|
||||
"
|
||||
:max-visible="1"
|
||||
:all-stocks="(result.product_variant?.images ?? []).map(() => `${result.cutting_result} pcs`)"
|
||||
/>
|
||||
</TableCell>
|
||||
<TableCell
|
||||
@ -288,6 +289,7 @@ const groupedResults = computed<GroupedResults[]>(() => {
|
||||
<MediaThumbnailCell
|
||||
:items="material.images ?? []"
|
||||
:max-visible="1"
|
||||
:all-stocks="(material.images ?? []).map(() => `Pemakaian: ${material.material_usage_formatted}`)"
|
||||
/>
|
||||
</TableCell>
|
||||
<TableCell
|
||||
|
||||
@ -38,15 +38,17 @@ const props = defineProps<{
|
||||
const previewOpen = ref(false);
|
||||
const previewUrl = ref<string | null>(null);
|
||||
const previewTitle = ref<string>('');
|
||||
const previewStock = ref<string>('');
|
||||
|
||||
const allVariantImages = computed(() => {
|
||||
const list: { priceId: number; title: string; url: string }[] = [];
|
||||
const list: { priceId: number; title: string; stock: string; url: string }[] = [];
|
||||
props.filteredRawMaterials.forEach((rawMaterial) => {
|
||||
rawMaterial.prices.forEach((price) => {
|
||||
if (price.images && price.images.length > 0) {
|
||||
list.push({
|
||||
priceId: price.id,
|
||||
title: `${rawMaterial.name} - ${price.variant}`,
|
||||
stock: `Stok: ${price.stock_formatted}`,
|
||||
url: price.images[0].url,
|
||||
});
|
||||
}
|
||||
@ -64,6 +66,7 @@ function handleThumbClick(priceId: number) {
|
||||
if (idx !== -1) {
|
||||
previewUrl.value = allVariantImages.value[idx].url;
|
||||
previewTitle.value = allVariantImages.value[idx].title;
|
||||
previewStock.value = allVariantImages.value[idx].stock;
|
||||
previewOpen.value = true;
|
||||
}
|
||||
}
|
||||
@ -73,6 +76,7 @@ watch(previewUrl, (newUrl) => {
|
||||
|
||||
if (matched) {
|
||||
previewTitle.value = matched.title;
|
||||
previewStock.value = matched.stock;
|
||||
}
|
||||
});
|
||||
|
||||
@ -206,5 +210,5 @@ function openCombination(rawMaterial: CuttingRawMaterialCatalogItem, price: Cutt
|
||||
:existing-cart-items="materialCart" :initial-variant="combinationInitialVariant"
|
||||
@combination-created="emit('combination-created', $event)" />
|
||||
|
||||
<MediaPreviewDialog v-model:open="previewOpen" v-model:url="previewUrl" :urls="previewUrls" :title="previewTitle" />
|
||||
<MediaPreviewDialog v-model:open="previewOpen" v-model:url="previewUrl" :urls="previewUrls" :title="previewTitle" :stocks="previewStock ? [previewStock] : []" />
|
||||
</template>
|
||||
|
||||
@ -138,6 +138,7 @@ function setCombinationResult(combinationId: number | undefined, value: string)
|
||||
v-if="item.images?.length"
|
||||
:items="item.images"
|
||||
:max-visible="1"
|
||||
:all-stocks="item.images.map(() => `Stok: ${item.stock_input} ${item.unit_abbreviation}`)"
|
||||
/>
|
||||
<div class="min-w-0">
|
||||
<p class="truncate text-sm font-medium">
|
||||
@ -180,6 +181,7 @@ function setCombinationResult(combinationId: number | undefined, value: string)
|
||||
v-if="item.images?.length"
|
||||
:items="item.images"
|
||||
:max-visible="1"
|
||||
:all-stocks="item.images.map(() => `Stok: ${item.stock_input} ${item.unit_abbreviation}`)"
|
||||
/>
|
||||
<div class="min-w-0">
|
||||
<p class="truncate text-sm font-medium">
|
||||
|
||||
@ -30,15 +30,17 @@ const props = defineProps<{
|
||||
const previewOpen = ref(false);
|
||||
const previewUrl = ref<string | null>(null);
|
||||
const previewTitle = ref<string>('');
|
||||
const previewStock = ref<string>('');
|
||||
|
||||
const allVariantImages = computed(() => {
|
||||
const list: { variantId: number; title: string; url: string }[] = [];
|
||||
const list: { variantId: number; title: string; stock: string; url: string }[] = [];
|
||||
props.filteredProducts.forEach((product) => {
|
||||
product.variants.forEach((variant) => {
|
||||
if (variant.images && variant.images.length > 0) {
|
||||
list.push({
|
||||
variantId: variant.id,
|
||||
title: `${product.name} - ${variant.name}`,
|
||||
stock: `Stok: ${variant.stock} pcs`,
|
||||
url: variant.images[0].url,
|
||||
});
|
||||
}
|
||||
@ -56,6 +58,7 @@ function handleThumbClick(variantId: number) {
|
||||
if (idx !== -1) {
|
||||
previewUrl.value = allVariantImages.value[idx].url;
|
||||
previewTitle.value = allVariantImages.value[idx].title;
|
||||
previewStock.value = allVariantImages.value[idx].stock;
|
||||
previewOpen.value = true;
|
||||
}
|
||||
}
|
||||
@ -65,6 +68,7 @@ watch(previewUrl, (newUrl) => {
|
||||
|
||||
if (matched) {
|
||||
previewTitle.value = matched.title;
|
||||
previewStock.value = matched.stock;
|
||||
}
|
||||
});
|
||||
|
||||
@ -168,5 +172,5 @@ const quickCreateOpen = ref(false);
|
||||
<QuickCreateProductModal v-model:open="quickCreateOpen" :categories="categories" :selected-materials="materialCart"
|
||||
@created="emit('product-created', $event)" />
|
||||
|
||||
<MediaPreviewDialog v-model:open="previewOpen" v-model:url="previewUrl" :urls="previewUrls" :title="previewTitle" />
|
||||
<MediaPreviewDialog v-model:open="previewOpen" v-model:url="previewUrl" :urls="previewUrls" :title="previewTitle" :stocks="previewStock ? [previewStock] : []" />
|
||||
</template>
|
||||
|
||||
@ -50,6 +50,7 @@ const emit = defineEmits<{
|
||||
v-if="item.images?.length"
|
||||
:items="item.images"
|
||||
:max-visible="1"
|
||||
:all-stocks="item.images.map(() => `Stok: ${item.stock} pcs`)"
|
||||
/>
|
||||
<div class="min-w-0">
|
||||
<p class="truncate text-sm font-medium">
|
||||
|
||||
@ -265,7 +265,7 @@ function getGroupedResults(results: any[]): GroupedCuttingResults[] {
|
||||
{{ material.variant }}
|
||||
</TableCell>
|
||||
<TableCell>
|
||||
<MediaThumbnailCell :items="material.images ?? []" :max-visible="1" :title="`${group.rawMaterialName} - ${material.variant}`" />
|
||||
<MediaThumbnailCell :items="material.images ?? []" :max-visible="1" :title="`${group.rawMaterialName} - ${material.variant}`" :all-stocks="(material.images ?? []).map(() => `Pemakaian: ${material.material_usage_formatted}`)" />
|
||||
</TableCell>
|
||||
<TableCell class="tabular-nums">
|
||||
{{
|
||||
@ -322,7 +322,7 @@ function getGroupedResults(results: any[]): GroupedCuttingResults[] {
|
||||
{{ result.product_variant?.name }}
|
||||
</TableCell>
|
||||
<TableCell>
|
||||
<MediaThumbnailCell :items="result.product_variant?.images ?? []" :max-visible="1" :title="`${group.productName} - ${result.product_variant?.name}`" />
|
||||
<MediaThumbnailCell :items="result.product_variant?.images ?? []" :max-visible="1" :title="`${group.productName} - ${result.product_variant?.name}`" :all-stocks="(result.product_variant?.images ?? []).map(() => `Hasil: ${result.cutting_result} pcs`)" />
|
||||
</TableCell>
|
||||
<TableCell class="tabular-nums">
|
||||
{{ result.cutting_result }} pcs
|
||||
|
||||
@ -201,7 +201,7 @@ function submitVerify() {
|
||||
<div v-for="material in cutting.materials" :key="material.id"
|
||||
class="flex items-center gap-3 rounded-lg border p-3 bg-muted/10">
|
||||
<div class="shrink-0">
|
||||
<MediaThumbnailCell :items="material.raw_material_price?.images ?? []" :max-visible="1" />
|
||||
<MediaThumbnailCell :items="material.raw_material_price?.images ?? []" :max-visible="1" :all-stocks="(material.raw_material_price?.images ?? []).map(() => `Pemakaian: ${material.material_usage_formatted}`)" />
|
||||
</div>
|
||||
<div class="flex-1 min-w-0">
|
||||
<p class="font-medium text-sm truncate">
|
||||
|
||||
@ -26,6 +26,7 @@ const props = defineProps<{
|
||||
const previewOpen = ref(false);
|
||||
const previewUrl = ref<string | null>(null);
|
||||
const previewTitle = ref('');
|
||||
const previewStock = ref('');
|
||||
|
||||
const productSearch = defineModel<string>('search', { required: true });
|
||||
|
||||
@ -40,6 +41,7 @@ function handleThumbClick(variantId: number) {
|
||||
if (variant && variant.images && variant.images.length > 0) {
|
||||
previewUrl.value = variant.images[0].url;
|
||||
previewTitle.value = `${product.name} - ${variant.name}`;
|
||||
previewStock.value = `Stok: ${variant.stock} pcs`;
|
||||
previewOpen.value = true;
|
||||
break;
|
||||
}
|
||||
@ -117,5 +119,5 @@ function handleThumbClick(variantId: number) {
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
<MediaPreviewDialog v-model:open="previewOpen" v-model:url="previewUrl" :title="previewTitle" />
|
||||
<MediaPreviewDialog v-model:open="previewOpen" v-model:url="previewUrl" :title="previewTitle" :stocks="previewStock ? [previewStock] : []" />
|
||||
</template>
|
||||
|
||||
@ -200,7 +200,7 @@ function getGroupedMaterials(materials: any[]): GroupedCuttingMaterials[] {
|
||||
</TableCell>
|
||||
<TableCell>
|
||||
<MediaThumbnailCell :items="result.product_variant?.images ?? []"
|
||||
:max-visible="1" />
|
||||
:max-visible="1" :all-stocks="(result.product_variant?.images ?? []).map(() => `${result.cutting_result} pcs`)" />
|
||||
</TableCell>
|
||||
<TableCell class="tabular-nums">
|
||||
{{ result.cutting_result }} pcs
|
||||
|
||||
@ -131,7 +131,7 @@ function getGroupedMaterials(materials: any[]): GroupedCuttingMaterials[] {
|
||||
</TableCell>
|
||||
<TableCell>
|
||||
<MediaThumbnailCell :items="result.product_variant?.images ?? []"
|
||||
:max-visible="1" />
|
||||
:max-visible="1" :all-stocks="(result.product_variant?.images ?? []).map(() => `${result.cutting_result} pcs`)" />
|
||||
</TableCell>
|
||||
<TableCell class="tabular-nums">
|
||||
{{ result.cutting_result }} pcs
|
||||
@ -189,7 +189,7 @@ function getGroupedMaterials(materials: any[]): GroupedCuttingMaterials[] {
|
||||
{{ material.variant }}
|
||||
</TableCell>
|
||||
<TableCell>
|
||||
<MediaThumbnailCell :items="material.images ?? []" :max-visible="1" />
|
||||
<MediaThumbnailCell :items="material.images ?? []" :max-visible="1" :all-stocks="(material.images ?? []).map(() => `${material.material_result ?? '-'} pcs`)" />
|
||||
</TableCell>
|
||||
<TableCell class="tabular-nums">
|
||||
<template
|
||||
|
||||
@ -124,7 +124,7 @@ const showingCount = computed(() => props.cuttings.length);
|
||||
{{ result.product_variant?.name }}
|
||||
</TableCell>
|
||||
<TableCell>
|
||||
<MediaThumbnailCell :items="result.product_variant?.images ?? []" :max-visible="1" />
|
||||
<MediaThumbnailCell :items="result.product_variant?.images ?? []" :max-visible="1" :all-stocks="(result.product_variant?.images ?? []).map(() => `${result.cutting_result} pcs`)" />
|
||||
</TableCell>
|
||||
<TableCell class="tabular-nums">
|
||||
{{ result.cutting_result }} pcs
|
||||
|
||||
@ -221,7 +221,7 @@ function submitVerify() {
|
||||
<div class="flex flex-col gap-3 sm:flex-row sm:items-center sm:justify-between">
|
||||
<div class="flex items-center gap-3 min-w-0">
|
||||
<div class="shrink-0">
|
||||
<MediaThumbnailCell :items="cutting.results[index]?.product_variant?.images ?? []" :max-visible="1" />
|
||||
<MediaThumbnailCell :items="cutting.results[index]?.product_variant?.images ?? []" :max-visible="1" :all-stocks="(cutting.results[index]?.product_variant?.images ?? []).map(() => `${result.cutting_result} pcs`)" />
|
||||
</div>
|
||||
<div class="flex-1 min-w-0">
|
||||
<p class="font-medium text-sm">{{ result.name }}</p>
|
||||
|
||||
@ -67,6 +67,12 @@ function allVariantImageTitles(product: ProductListItem): string[] {
|
||||
);
|
||||
}
|
||||
|
||||
function allVariantImageStocks(product: ProductListItem): string[] {
|
||||
return product.variants.flatMap((variant) =>
|
||||
(variant.images ?? []).map(() => `Stok Bagus: ${variant.stock_formatted} | Reject: ${variant.reject_stock_formatted} | Ecer: ${variant.retail_stock_formatted}`)
|
||||
);
|
||||
}
|
||||
|
||||
const verificationModalOpen = ref(false);
|
||||
const selectedRequestId = ref<number | null>(null);
|
||||
|
||||
@ -172,7 +178,7 @@ function openEditModal(variant: Variant, product: ProductListItem) {
|
||||
{{ variant.name }}
|
||||
</TableCell>
|
||||
<TableCell>
|
||||
<MediaThumbnailCell :items="variant.images ?? []" :max-visible="1" :title="`${product.name} - ${variant.name}`" :all-urls="allVariantImageUrls(product)" :all-titles="allVariantImageTitles(product)" />
|
||||
<MediaThumbnailCell :items="variant.images ?? []" :max-visible="1" :title="`${product.name} - ${variant.name}`" :all-urls="allVariantImageUrls(product)" :all-titles="allVariantImageTitles(product)" :all-stocks="allVariantImageStocks(product)" />
|
||||
</TableCell>
|
||||
<TableCell class="tabular-nums">
|
||||
{{ variant.stock_formatted }}
|
||||
|
||||
@ -61,6 +61,12 @@ function allVariantImageTitles(material: RawMaterialListItem): string[] {
|
||||
);
|
||||
}
|
||||
|
||||
function allVariantImageStocks(material: RawMaterialListItem): string[] {
|
||||
return material.prices.flatMap((price) =>
|
||||
(price.images ?? []).map(() => `Stok: ${price.stock_formatted}`)
|
||||
);
|
||||
}
|
||||
|
||||
const verificationModalOpen = ref(false);
|
||||
const selectedRequestId = ref<number | null>(null);
|
||||
|
||||
@ -167,7 +173,8 @@ function openEditModal(price: RawMaterialPrice, material: RawMaterialListItem) {
|
||||
<MediaThumbnailCell :items="price.images ?? []" :max-visible="1"
|
||||
:title="`${material.name} - ${price.variant}`"
|
||||
:all-urls="allVariantImageUrls(material)"
|
||||
:all-titles="allVariantImageTitles(material)" />
|
||||
:all-titles="allVariantImageTitles(material)"
|
||||
:all-stocks="allVariantImageStocks(material)" />
|
||||
</TableCell>
|
||||
<TableCell class="tabular-nums">
|
||||
{{ price.stock_formatted }}
|
||||
|
||||
Loading…
Reference in New Issue
Block a user