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">
|
<script setup lang="ts">
|
||||||
|
import { watch } from 'vue';
|
||||||
import {
|
import {
|
||||||
Dialog,
|
Dialog,
|
||||||
DialogContent,
|
DialogContent,
|
||||||
@ -8,10 +9,20 @@ import {
|
|||||||
|
|
||||||
const open = defineModel<boolean>('open', { default: false });
|
const open = defineModel<boolean>('open', { default: false });
|
||||||
|
|
||||||
|
const emit = defineEmits<{
|
||||||
|
close: [];
|
||||||
|
}>();
|
||||||
|
|
||||||
defineProps<{
|
defineProps<{
|
||||||
url: string | null;
|
url: string | null;
|
||||||
title?: string;
|
title?: string;
|
||||||
}>();
|
}>();
|
||||||
|
|
||||||
|
watch(open, (val) => {
|
||||||
|
if (!val) {
|
||||||
|
emit('close');
|
||||||
|
}
|
||||||
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
|
|||||||
@ -1,6 +1,12 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { computed, ref } from 'vue';
|
import { computed, ref } from 'vue';
|
||||||
import MediaPreviewDialog from '@/components/media/MediaPreviewDialog.vue';
|
import MediaPreviewDialog from '@/components/media/MediaPreviewDialog.vue';
|
||||||
|
import {
|
||||||
|
Dialog,
|
||||||
|
DialogContent,
|
||||||
|
DialogHeader,
|
||||||
|
DialogTitle,
|
||||||
|
} from '@/components/ui/dialog';
|
||||||
import type { MediaItem } from '@/types/media';
|
import type { MediaItem } from '@/types/media';
|
||||||
|
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
@ -10,14 +16,78 @@ const props = defineProps<{
|
|||||||
|
|
||||||
const previewOpen = ref(false);
|
const previewOpen = ref(false);
|
||||||
const previewUrl = ref<string | null>(null);
|
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 visibleItems = computed(() => props.items.slice(0, props.maxVisible ?? 3));
|
||||||
const hiddenCount = computed(() => Math.max(props.items.length - visibleItems.value.length, 0));
|
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) {
|
function openPreview(url: string) {
|
||||||
previewUrl.value = url;
|
previewUrl.value = url;
|
||||||
previewOpen.value = true;
|
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>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
@ -26,10 +96,27 @@ function openPreview(url: string) {
|
|||||||
class="size-8 overflow-hidden rounded border bg-muted/30" @click="openPreview(item.url)">
|
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">
|
<img :src="item.thumb_url" alt="Foto" class="size-full object-cover">
|
||||||
</button>
|
</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 }}
|
+{{ hiddenCount }}
|
||||||
</span>
|
</button>
|
||||||
</div>
|
</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>
|
</template>
|
||||||
|
|||||||
@ -86,8 +86,10 @@ function openVerificationDetail(requestId: number | undefined) {
|
|||||||
</h3>
|
</h3>
|
||||||
</div>
|
</div>
|
||||||
<p v-if="product.has_pending_request" class="text-sm text-amber-600">
|
<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 —
|
{{ product.pending_request_submitted_by_name }} mengajukan {{
|
||||||
<button type="button" class="underline-offset-2 hover:underline" @click="openVerificationDetail(product.pending_request_id)">lihat</button>
|
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>
|
</p>
|
||||||
<div v-if="product.categories.length" class="flex flex-wrap gap-1">
|
<div v-if="product.categories.length" class="flex flex-wrap gap-1">
|
||||||
<Badge v-for="category in product.categories" :key="category.id" variant="outline">
|
<Badge v-for="category in product.categories" :key="category.id" variant="outline">
|
||||||
@ -142,7 +144,7 @@ function openVerificationDetail(requestId: number | undefined) {
|
|||||||
{{ variant.name }}
|
{{ variant.name }}
|
||||||
</TableCell>
|
</TableCell>
|
||||||
<TableCell>
|
<TableCell>
|
||||||
<MediaThumbnailCell :items="variant.images ?? []" />
|
<MediaThumbnailCell :items="variant.images ?? []" :max-visible="1" />
|
||||||
</TableCell>
|
</TableCell>
|
||||||
<TableCell class="tabular-nums">
|
<TableCell class="tabular-nums">
|
||||||
{{ variant.stock_formatted }}
|
{{ variant.stock_formatted }}
|
||||||
|
|||||||
@ -126,7 +126,7 @@ function openVerificationDetail(requestId: number | undefined) {
|
|||||||
{{ price.variant }}
|
{{ price.variant }}
|
||||||
</TableCell>
|
</TableCell>
|
||||||
<TableCell>
|
<TableCell>
|
||||||
<MediaThumbnailCell :items="price.images ?? []" />
|
<MediaThumbnailCell :items="price.images ?? []" :max-visible="1" />
|
||||||
</TableCell>
|
</TableCell>
|
||||||
<TableCell class="tabular-nums">
|
<TableCell class="tabular-nums">
|
||||||
{{ price.stock_formatted }}
|
{{ price.stock_formatted }}
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user