feat: extract and consolidate common UI patterns across market module

## Component Extraction
  - Create MarketSearchBar component with dual-mode support (enhanced/simple)
    - Enhanced mode: suggestions, keyboard shortcuts, category filters
    - Simple mode: basic search functionality
    - Dynamic imports for performance optimization
  - Extract LoadingErrorState component for consistent loading/error handling
    - Configurable compact/full modes with custom messages
    - Built-in retry functionality
    - Standardized spinner and error displays
  - Consolidate CartButton component (already extracted in previous commit)

  ## UI Standardization
  - Replace inline category badges in StallView with CategoryFilterBar component
  - Add missing state management for category filtering (filterMode, setFilterMode)
  - Ensure consistent filtering UI between MarketPage and StallView
  - Standardize loading states across MarketPage, ProductGrid, and MerchantStore

  ## Code Organization
  - MarketPage: Uses enhanced MarketSearchBar with full feature set
  - StallView: Uses simple MarketSearchBar for cleaner stall-specific search
  - Both views now share CategoryFilterBar, CartButton, and ProductGrid
  - LoadingErrorState provides unified loading/error UX patterns

  ## Technical Improvements
  - Eliminate code duplication following DRY principles
  - Improve maintainability with single source of truth for UI patterns
  - Optimize performance with conditional feature loading
  - Enhance accessibility with consistent keyboard shortcuts and ARIA labels
  - Ensure mobile-responsive designs with unified behavior

  BREAKING CHANGE: MarketFuzzySearch component replaced by MarketSearchBar
This commit is contained in:
padreug 2025-09-27 09:45:33 +02:00
parent 8821f604be
commit c8860dc937
6 changed files with 586 additions and 94 deletions

View file

@ -1,24 +1,22 @@
<template>
<div class="product-grid-container">
<!-- 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>
<LoadingErrorState
:is-loading="isLoading"
:loading-message="loadingMessage"
:has-error="false"
:full-height="false"
>
<!-- Empty State -->
<div v-if="products.length === 0" 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>
</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">
<!-- Product Grid -->
<div v-else :class="gridClasses">
<ProductCard
v-for="product in products"
:key="product.id"
@ -27,16 +25,17 @@
@view-details="handleViewDetails"
@view-stall="$emit('view-stall', $event)"
/>
</div>
</div>
<!-- Product Detail Dialog - Now managed internally -->
<ProductDetailDialog
v-if="selectedProduct"
:product="selectedProduct"
:isOpen="showProductDetail"
@close="closeProductDetail"
@add-to-cart="handleDialogAddToCart"
/>
<!-- Product Detail Dialog - Now managed internally -->
<ProductDetailDialog
v-if="selectedProduct"
:product="selectedProduct"
:isOpen="showProductDetail"
@close="closeProductDetail"
@add-to-cart="handleDialogAddToCart"
/>
</LoadingErrorState>
</div>
</template>
@ -45,6 +44,7 @@ import { computed, ref } from 'vue'
import { Package as EmptyIcon } from 'lucide-vue-next'
import ProductCard from './ProductCard.vue'
import ProductDetailDialog from './ProductDetailDialog.vue'
import LoadingErrorState from './LoadingErrorState.vue'
import type { Product } from '../types/market'
interface Props {