feat: introduce CategoryFilterBar and ProductGrid components for enhanced product filtering and display

- Added CategoryFilterBar.vue to manage category filtering with AND/OR toggle options and clear all functionality.
- Implemented ProductGrid.vue to display products with loading and empty states, improving user experience.
- Refactored MarketPage.vue to utilize the new components, streamlining the layout and enhancing responsiveness.
- Updated StallView.vue to incorporate ProductGrid for consistent product display across views.

These changes enhance the overall usability and visual appeal of the market components, providing users with a more intuitive filtering and browsing experience.
This commit is contained in:
padreug 2025-09-26 23:39:08 +02:00
parent 25d17b481d
commit 3f47d2ff26
6 changed files with 419 additions and 261 deletions

View file

@ -0,0 +1,92 @@
<template>
<div>
<!-- Loading State -->
<div v-if="isLoading" class="flex justify-center items-center min-h-64">
<div class="flex flex-col items-center space-y-4">
<div class="animate-spin rounded-full h-12 w-12 border-b-2 border-blue-600"></div>
<p class="text-gray-600">{{ loadingMessage }}</p>
</div>
</div>
<!-- Empty State -->
<div v-else-if="products.length === 0 && !isLoading" class="text-center py-12">
<slot name="empty">
<EmptyIcon class="w-24 h-24 text-muted-foreground/50 mx-auto mb-4" />
<h3 class="text-xl font-semibold text-gray-600 mb-2">{{ emptyTitle }}</h3>
<p class="text-gray-500">{{ emptyMessage }}</p>
</slot>
</div>
<!-- Product Grid -->
<div v-else :class="gridClasses">
<ProductCard
v-for="product in products"
:key="product.id"
:product="product as Product"
@add-to-cart="$emit('add-to-cart', $event)"
@view-details="$emit('view-details', $event)"
@view-stall="$emit('view-stall', $event)"
/>
</div>
</div>
</template>
<script setup lang="ts">
import { computed } from 'vue'
import { Package as EmptyIcon } from 'lucide-vue-next'
import ProductCard from './ProductCard.vue'
import type { Product } from '../types/market'
interface Props {
products: Product[]
isLoading?: boolean
loadingMessage?: string
emptyTitle?: string
emptyMessage?: string
columns?: {
mobile?: number
sm?: number
md?: number
lg?: number
xl?: number
}
}
const props = withDefaults(defineProps<Props>(), {
isLoading: false,
loadingMessage: 'Loading products...',
emptyTitle: 'No products found',
emptyMessage: 'Try adjusting your filters or search terms',
columns: () => ({
mobile: 1,
sm: 2,
md: 2,
lg: 3,
xl: 4
})
})
defineEmits<{
'add-to-cart': [product: Product]
'view-details': [product: Product]
'view-stall': [stallId: string]
}>()
// Compute grid classes based on column configuration
const gridClasses = computed(() => {
const cols = props.columns
const classes = ['grid', 'gap-6']
// Mobile columns
if (cols.mobile === 1) classes.push('grid-cols-1')
else if (cols.mobile === 2) classes.push('grid-cols-2')
// Responsive columns
if (cols.sm) classes.push(`sm:grid-cols-${cols.sm}`)
if (cols.md) classes.push(`md:grid-cols-${cols.md}`)
if (cols.lg) classes.push(`lg:grid-cols-${cols.lg}`)
if (cols.xl) classes.push(`xl:grid-cols-${cols.xl}`)
return classes.join(' ')
})
</script>