← back to Wine Finder Next

components/FilterBar.tsx

188 lines

'use client'

import { useState } from 'react'

interface FilterBarProps {
  filters: {
    sources: string[]
    minPrice: number
    maxPrice: number
    minRating: number
    region: string
    wineType: string
  }
  onChange: (filters: any) => void
}

export default function FilterBar({ filters, onChange }: FilterBarProps) {
  const [isExpanded, setIsExpanded] = useState(false)

  const sources = [
    { id: 'vivino', label: 'Vivino', icon: '🍇' },
    { id: 'totalwine', label: 'Total Wine', icon: '🏪' },
    { id: 'kl', label: 'K&L', icon: '🍷' },
  ]

  const wineTypes = [
    { value: '', label: 'All Types' },
    { value: 'red', label: 'Red' },
    { value: 'white', label: 'White' },
    { value: 'rosé', label: 'Rosé' },
    { value: 'sparkling', label: 'Sparkling' },
    { value: 'dessert', label: 'Dessert' },
  ]

  const priceRanges = [
    { min: 0, max: 1000, label: 'All Prices' },
    { min: 0, max: 20, label: 'Under $20' },
    { min: 20, max: 50, label: '$20 - $50' },
    { min: 50, max: 100, label: '$50 - $100' },
    { min: 100, max: 1000, label: '$100+' },
  ]

  const handleSourceToggle = (sourceId: string) => {
    const newSources = filters.sources.includes(sourceId)
      ? filters.sources.filter((s) => s !== sourceId)
      : [...filters.sources, sourceId]

    // Ensure at least one source is selected
    if (newSources.length > 0) {
      onChange({ sources: newSources })
    }
  }

  const handlePriceRangeChange = (min: number, max: number) => {
    onChange({ minPrice: min, maxPrice: max })
  }

  return (
    <div className="bg-white rounded-2xl shadow-wine p-4 sm:p-6 border border-wine-100">
      {/* Mobile Toggle */}
      <button
        onClick={() => setIsExpanded(!isExpanded)}
        className="w-full flex items-center justify-between sm:hidden mb-4"
        aria-label={isExpanded ? 'Hide filters' : 'Show filters'}
      >
        <span className="font-semibold text-gray-900 flex items-center gap-2">
          <svg className="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
            <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M3 4a1 1 0 011-1h16a1 1 0 011 1v2.586a1 1 0 01-.293.707l-6.414 6.414a1 1 0 00-.293.707V17l-4 4v-6.586a1 1 0 00-.293-.707L3.293 7.293A1 1 0 013 6.586V4z" />
          </svg>
          Filters
        </span>
        <svg
          className={`w-5 h-5 transform transition-transform ${isExpanded ? 'rotate-180' : ''}`}
          fill="none"
          stroke="currentColor"
          viewBox="0 0 24 24"
        >
          <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M19 9l-7 7-7-7" />
        </svg>
      </button>

      <div className={`space-y-6 ${isExpanded ? 'block' : 'hidden sm:block'}`}>
        {/* Source Filters */}
        <div>
          <h3 className="text-sm font-semibold text-gray-700 mb-3 flex items-center gap-2">
            <span>Data Sources</span>
            <span className="text-xs text-gray-500 font-normal">
              ({filters.sources.length} selected)
            </span>
          </h3>
          <div className="flex flex-wrap gap-2">
            {sources.map((source) => (
              <button
                key={source.id}
                onClick={() => handleSourceToggle(source.id)}
                className={`px-4 py-2 rounded-full text-sm font-medium transition-all min-h-[44px] ${
                  filters.sources.includes(source.id)
                    ? 'bg-wine-600 text-white shadow-wine hover:bg-wine-700'
                    : 'bg-gray-100 text-gray-700 hover:bg-gray-200'
                }`}
                aria-pressed={filters.sources.includes(source.id)}
              >
                <span className="mr-1">{source.icon}</span>
                {source.label}
              </button>
            ))}
          </div>
        </div>

        {/* Price Range */}
        <div>
          <h3 className="text-sm font-semibold text-gray-700 mb-3">Price Range</h3>
          <div className="grid grid-cols-2 sm:grid-cols-3 lg:grid-cols-5 gap-2">
            {priceRanges.map((range) => (
              <button
                key={range.label}
                onClick={() => handlePriceRangeChange(range.min, range.max)}
                className={`px-3 py-2 rounded-lg text-sm font-medium transition-all min-h-[44px] ${
                  filters.minPrice === range.min && filters.maxPrice === range.max
                    ? 'bg-gold-500 text-white shadow-gold'
                    : 'bg-gray-100 text-gray-700 hover:bg-gray-200'
                }`}
                aria-pressed={filters.minPrice === range.min && filters.maxPrice === range.max}
              >
                {range.label}
              </button>
            ))}
          </div>
        </div>

        {/* Minimum Rating */}
        <div>
          <h3 className="text-sm font-semibold text-gray-700 mb-3">
            Minimum Rating: {filters.minRating > 0 ? `${filters.minRating}/100` : 'Any'}
          </h3>
          <input
            type="range"
            min="0"
            max="100"
            step="10"
            value={filters.minRating}
            onChange={(e) => onChange({ minRating: parseInt(e.target.value) })}
            className="w-full h-2 bg-gray-200 rounded-lg appearance-none cursor-pointer accent-wine-600"
            aria-label="Minimum rating slider"
          />
          <div className="flex justify-between text-xs text-gray-500 mt-2">
            <span>0</span>
            <span>50</span>
            <span>100</span>
          </div>
        </div>

        {/* Wine Type */}
        <div>
          <h3 className="text-sm font-semibold text-gray-700 mb-3">Wine Type</h3>
          <select
            value={filters.wineType}
            onChange={(e) => onChange({ wineType: e.target.value })}
            className="w-full px-4 py-3 rounded-lg border-2 border-gray-200 focus:border-wine-600 focus:ring-2 focus:ring-wine-200 outline-none transition-all text-gray-900"
            style={{ fontSize: '16px' }} // Prevent iOS zoom
            aria-label="Select wine type"
          >
            {wineTypes.map((type) => (
              <option key={type.value} value={type.value}>
                {type.label}
              </option>
            ))}
          </select>
        </div>

        {/* Region Search */}
        <div>
          <h3 className="text-sm font-semibold text-gray-700 mb-3">Region</h3>
          <input
            type="text"
            value={filters.region}
            onChange={(e) => onChange({ region: e.target.value })}
            placeholder="e.g. Napa Valley, Bordeaux..."
            className="w-full px-4 py-3 rounded-lg border-2 border-gray-200 focus:border-wine-600 focus:ring-2 focus:ring-wine-200 outline-none transition-all"
            style={{ fontSize: '16px' }} // Prevent iOS zoom
            aria-label="Filter by region"
          />
        </div>
      </div>
    </div>
  )
}