feat: enhance MediaThumbnailCell and MediaPreviewDialog components with gallery functionality and improved item display; add close event emission for better dialog management
This commit is contained in:
parent
fff6b147be
commit
78e30c2aba
@ -1,4 +1,5 @@
|
||||
<script setup lang="ts">
|
||||
import { watch } from 'vue';
|
||||
import {
|
||||
Dialog,
|
||||
DialogContent,
|
||||
@ -8,10 +9,20 @@ import {
|
||||
|
||||
const open = defineModel<boolean>('open', { default: false });
|
||||
|
||||
const emit = defineEmits<{
|
||||
close: [];
|
||||
}>();
|
||||
|
||||
defineProps<{
|
||||
url: string | null;
|
||||
title?: string;
|
||||
}>();
|
||||
|
||||
watch(open, (val) => {
|
||||
if (!val) {
|
||||
emit('close');
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
|
||||
@ -1,6 +1,12 @@
|
||||
<script setup lang="ts">
|
||||
import { computed, ref } from 'vue';
|
||||
import MediaPreviewDialog from '@/components/media/MediaPreviewDialog.vue';
|
||||
import {
|
||||
Dialog,
|
||||
DialogContent,
|
||||
DialogHeader,
|
||||
DialogTitle,
|
||||
} from '@/components/ui/dialog';
|
||||
import type { MediaItem } from '@/types/media';
|
||||
|
||||
const props = defineProps<{
|
||||
@ -10,14 +16,78 @@ const props = defineProps<{
|
||||
|
||||
const previewOpen = ref(false);
|
||||
const previewUrl = ref<string | null>(null);
|
||||
const galleryOpen = ref(false);
|
||||
const cameFromGallery = ref(false);
|
||||
|
||||
const visibleItems = computed(() => props.items.slice(0, props.maxVisible ?? 3));
|
||||
const hiddenCount = computed(() => Math.max(props.items.length - visibleItems.value.length, 0));
|
||||
|
||||
const galleryWidthClass = computed(() => {
|
||||
const count = props.items.length;
|
||||
|
||||
if (count <= 1) {
|
||||
return 'sm:max-w-xs';
|
||||
}
|
||||
|
||||
if (count <= 2) {
|
||||
return 'sm:max-w-sm';
|
||||
}
|
||||
|
||||
if (count <= 4) {
|
||||
return 'sm:max-w-md';
|
||||
}
|
||||
|
||||
if (count <= 8) {
|
||||
return 'sm:max-w-lg';
|
||||
}
|
||||
|
||||
return 'sm:max-w-xl';
|
||||
});
|
||||
|
||||
const galleryGridClass = computed(() => {
|
||||
const count = props.items.length;
|
||||
|
||||
if (count === 1) {
|
||||
return 'grid-cols-1';
|
||||
}
|
||||
|
||||
if (count === 2) {
|
||||
return 'grid-cols-2';
|
||||
}
|
||||
|
||||
if (count === 3) {
|
||||
return 'grid-cols-3';
|
||||
}
|
||||
|
||||
return 'grid-cols-4';
|
||||
});
|
||||
|
||||
function openPreview(url: string) {
|
||||
previewUrl.value = url;
|
||||
previewOpen.value = true;
|
||||
}
|
||||
|
||||
function openPreviewFromGallery(url: string) {
|
||||
cameFromGallery.value = true;
|
||||
galleryOpen.value = false;
|
||||
setTimeout(() => {
|
||||
previewUrl.value = url;
|
||||
previewOpen.value = true;
|
||||
}, 200);
|
||||
}
|
||||
|
||||
function onPreviewClose() {
|
||||
if (cameFromGallery.value) {
|
||||
cameFromGallery.value = false;
|
||||
setTimeout(() => {
|
||||
galleryOpen.value = true;
|
||||
}, 200);
|
||||
}
|
||||
}
|
||||
|
||||
function openGallery() {
|
||||
galleryOpen.value = true;
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
@ -26,10 +96,27 @@ function openPreview(url: string) {
|
||||
class="size-8 overflow-hidden rounded border bg-muted/30" @click="openPreview(item.url)">
|
||||
<img :src="item.thumb_url" alt="Foto" class="size-full object-cover">
|
||||
</button>
|
||||
<span v-if="hiddenCount > 0" class="text-muted-foreground text-xs">
|
||||
<button v-if="hiddenCount > 0" type="button"
|
||||
class="flex size-8 items-center justify-center rounded border bg-muted/30 text-muted-foreground text-xs font-medium hover:bg-muted/50 transition-colors"
|
||||
@click="openGallery">
|
||||
+{{ hiddenCount }}
|
||||
</span>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<MediaPreviewDialog v-model:open="previewOpen" :url="previewUrl" />
|
||||
<Dialog v-model:open="galleryOpen">
|
||||
<DialogContent :class="galleryWidthClass">
|
||||
<DialogHeader>
|
||||
<DialogTitle>Semua Foto ({{ items.length }})</DialogTitle>
|
||||
</DialogHeader>
|
||||
<div class="grid gap-2 max-h-[60vh] overflow-y-auto p-1" :class="galleryGridClass">
|
||||
<button v-for="item in items" :key="item.id" type="button"
|
||||
class="aspect-square overflow-hidden rounded border bg-muted/30"
|
||||
@click="openPreviewFromGallery(item.url)">
|
||||
<img :src="item.thumb_url" alt="Foto" class="size-full object-cover">
|
||||
</button>
|
||||
</div>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
|
||||
<MediaPreviewDialog v-model:open="previewOpen" :url="previewUrl" @close="onPreviewClose" />
|
||||
</template>
|
||||
|
||||
@ -86,8 +86,10 @@ function openVerificationDetail(requestId: number | undefined) {
|
||||
</h3>
|
||||
</div>
|
||||
<p v-if="product.has_pending_request" class="text-sm text-amber-600">
|
||||
{{ product.pending_request_submitted_by_name }} mengajukan {{ product.pending_request_action_label?.toLowerCase() }} produk ini —
|
||||
<button type="button" class="underline-offset-2 hover:underline" @click="openVerificationDetail(product.pending_request_id)">lihat</button>
|
||||
{{ product.pending_request_submitted_by_name }} mengajukan {{
|
||||
product.pending_request_action_label?.toLowerCase() }} produk ini —
|
||||
<button type="button" class="underline-offset-2 hover:underline"
|
||||
@click="openVerificationDetail(product.pending_request_id)">lihat</button>
|
||||
</p>
|
||||
<div v-if="product.categories.length" class="flex flex-wrap gap-1">
|
||||
<Badge v-for="category in product.categories" :key="category.id" variant="outline">
|
||||
@ -142,7 +144,7 @@ function openVerificationDetail(requestId: number | undefined) {
|
||||
{{ variant.name }}
|
||||
</TableCell>
|
||||
<TableCell>
|
||||
<MediaThumbnailCell :items="variant.images ?? []" />
|
||||
<MediaThumbnailCell :items="variant.images ?? []" :max-visible="1" />
|
||||
</TableCell>
|
||||
<TableCell class="tabular-nums">
|
||||
{{ variant.stock_formatted }}
|
||||
|
||||
@ -126,7 +126,7 @@ function openVerificationDetail(requestId: number | undefined) {
|
||||
{{ price.variant }}
|
||||
</TableCell>
|
||||
<TableCell>
|
||||
<MediaThumbnailCell :items="price.images ?? []" />
|
||||
<MediaThumbnailCell :items="price.images ?? []" :max-visible="1" />
|
||||
</TableCell>
|
||||
<TableCell class="tabular-nums">
|
||||
{{ price.stock_formatted }}
|
||||
|
||||
Loading…
Reference in New Issue
Block a user