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:
parent
25d17b481d
commit
3f47d2ff26
6 changed files with 419 additions and 261 deletions
92
src/modules/market/components/ProductGrid.vue
Normal file
92
src/modules/market/components/ProductGrid.vue
Normal 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>
|
||||
Loading…
Add table
Add a link
Reference in a new issue