Refactor: Update user list item interfaces

This commit is contained in:
gpt-engineer-app[bot]
2025-10-02 12:02:36 +00:00
parent 8ec0d59e75
commit ee1ea52f2e
5 changed files with 530 additions and 51 deletions

View File

@@ -0,0 +1,277 @@
import { useState } from 'react';
import { Label } from '@/components/ui/label';
import { Input } from '@/components/ui/input';
import { Button } from '@/components/ui/button';
import { Badge } from '@/components/ui/badge';
import { Collapsible, CollapsibleContent, CollapsibleTrigger } from '@/components/ui/collapsible';
import { ChevronDown, Plus, X } from 'lucide-react';
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select';
export interface TechnicalSpecFilter {
spec_name: string;
spec_value?: string;
min_value?: number;
max_value?: number;
operator: 'equals' | 'contains' | 'range' | 'has_spec';
}
export interface CoasterStatFilter {
stat_name: string;
min_value?: number;
max_value?: number;
}
interface AdvancedRideFiltersProps {
technicalSpecFilters: TechnicalSpecFilter[];
coasterStatFilters: CoasterStatFilter[];
onTechnicalSpecFiltersChange: (filters: TechnicalSpecFilter[]) => void;
onCoasterStatFiltersChange: (filters: CoasterStatFilter[]) => void;
}
const COMMON_TECH_SPECS = [
'Track Material',
'Manufacturer Model',
'Train Configuration',
'Launch System',
'Braking System',
'Control System',
'Safety System',
];
const COMMON_COASTER_STATS = [
'Max Speed',
'Height',
'Drop Height',
'Length',
'Duration',
'Inversions',
'G-Force',
'Capacity',
];
export function AdvancedRideFilters({
technicalSpecFilters,
coasterStatFilters,
onTechnicalSpecFiltersChange,
onCoasterStatFiltersChange,
}: AdvancedRideFiltersProps) {
const [isSpecsOpen, setIsSpecsOpen] = useState(false);
const [isStatsOpen, setIsStatsOpen] = useState(false);
const addTechnicalSpecFilter = () => {
onTechnicalSpecFiltersChange([
...technicalSpecFilters,
{ spec_name: '', operator: 'has_spec' },
]);
};
const updateTechnicalSpecFilter = (index: number, updates: Partial<TechnicalSpecFilter>) => {
const updated = [...technicalSpecFilters];
updated[index] = { ...updated[index], ...updates };
onTechnicalSpecFiltersChange(updated);
};
const removeTechnicalSpecFilter = (index: number) => {
onTechnicalSpecFiltersChange(technicalSpecFilters.filter((_, i) => i !== index));
};
const addCoasterStatFilter = () => {
onCoasterStatFiltersChange([
...coasterStatFilters,
{ stat_name: '' },
]);
};
const updateCoasterStatFilter = (index: number, updates: Partial<CoasterStatFilter>) => {
const updated = [...coasterStatFilters];
updated[index] = { ...updated[index], ...updates };
onCoasterStatFiltersChange(updated);
};
const removeCoasterStatFilter = (index: number) => {
onCoasterStatFiltersChange(coasterStatFilters.filter((_, i) => i !== index));
};
return (
<div className="space-y-4">
{/* Technical Specifications Filters */}
<Collapsible open={isSpecsOpen} onOpenChange={setIsSpecsOpen}>
<CollapsibleTrigger asChild>
<div className="flex items-center justify-between cursor-pointer p-3 bg-muted/50 rounded-lg hover:bg-muted transition-colors">
<div className="flex items-center gap-2">
<h4 className="font-semibold">Technical Specifications</h4>
{technicalSpecFilters.length > 0 && (
<Badge variant="secondary">{technicalSpecFilters.length}</Badge>
)}
</div>
<ChevronDown className={`w-4 h-4 transition-transform ${isSpecsOpen ? 'rotate-180' : ''}`} />
</div>
</CollapsibleTrigger>
<CollapsibleContent className="space-y-3 pt-3">
{technicalSpecFilters.map((filter, index) => (
<div key={index} className="p-3 border rounded-lg space-y-3">
<div className="flex items-start justify-between gap-2">
<div className="flex-1 grid grid-cols-1 md:grid-cols-2 gap-3">
<div className="space-y-2">
<Label>Specification Name</Label>
<Select
value={filter.spec_name}
onValueChange={(value) => updateTechnicalSpecFilter(index, { spec_name: value })}
>
<SelectTrigger>
<SelectValue placeholder="Select specification" />
</SelectTrigger>
<SelectContent>
{COMMON_TECH_SPECS.map((spec) => (
<SelectItem key={spec} value={spec}>
{spec}
</SelectItem>
))}
</SelectContent>
</Select>
</div>
<div className="space-y-2">
<Label>Filter Type</Label>
<Select
value={filter.operator}
onValueChange={(value: any) => updateTechnicalSpecFilter(index, { operator: value })}
>
<SelectTrigger>
<SelectValue />
</SelectTrigger>
<SelectContent>
<SelectItem value="has_spec">Has Specification</SelectItem>
<SelectItem value="equals">Equals Value</SelectItem>
<SelectItem value="contains">Contains Text</SelectItem>
</SelectContent>
</Select>
</div>
</div>
<Button
variant="ghost"
size="icon"
onClick={() => removeTechnicalSpecFilter(index)}
>
<X className="w-4 h-4" />
</Button>
</div>
{(filter.operator === 'equals' || filter.operator === 'contains') && (
<div className="space-y-2">
<Label>Value</Label>
<Input
value={filter.spec_value || ''}
onChange={(e) => updateTechnicalSpecFilter(index, { spec_value: e.target.value })}
placeholder="Enter value to match"
/>
</div>
)}
</div>
))}
<Button
variant="outline"
size="sm"
onClick={addTechnicalSpecFilter}
className="w-full"
>
<Plus className="w-4 h-4 mr-2" />
Add Technical Spec Filter
</Button>
</CollapsibleContent>
</Collapsible>
{/* Coaster Statistics Filters */}
<Collapsible open={isStatsOpen} onOpenChange={setIsStatsOpen}>
<CollapsibleTrigger asChild>
<div className="flex items-center justify-between cursor-pointer p-3 bg-muted/50 rounded-lg hover:bg-muted transition-colors">
<div className="flex items-center gap-2">
<h4 className="font-semibold">Coaster Statistics</h4>
{coasterStatFilters.length > 0 && (
<Badge variant="secondary">{coasterStatFilters.length}</Badge>
)}
</div>
<ChevronDown className={`w-4 h-4 transition-transform ${isStatsOpen ? 'rotate-180' : ''}`} />
</div>
</CollapsibleTrigger>
<CollapsibleContent className="space-y-3 pt-3">
{coasterStatFilters.map((filter, index) => (
<div key={index} className="p-3 border rounded-lg space-y-3">
<div className="flex items-start justify-between gap-2">
<div className="flex-1 space-y-3">
<div className="space-y-2">
<Label>Statistic Name</Label>
<Select
value={filter.stat_name}
onValueChange={(value) => updateCoasterStatFilter(index, { stat_name: value })}
>
<SelectTrigger>
<SelectValue placeholder="Select statistic" />
</SelectTrigger>
<SelectContent>
{COMMON_COASTER_STATS.map((stat) => (
<SelectItem key={stat} value={stat}>
{stat}
</SelectItem>
))}
</SelectContent>
</Select>
</div>
<div className="grid grid-cols-2 gap-3">
<div className="space-y-2">
<Label>Min Value</Label>
<Input
type="number"
value={filter.min_value || ''}
onChange={(e) =>
updateCoasterStatFilter(index, {
min_value: e.target.value ? parseFloat(e.target.value) : undefined,
})
}
placeholder="Min"
/>
</div>
<div className="space-y-2">
<Label>Max Value</Label>
<Input
type="number"
value={filter.max_value || ''}
onChange={(e) =>
updateCoasterStatFilter(index, {
max_value: e.target.value ? parseFloat(e.target.value) : undefined,
})
}
placeholder="Max"
/>
</div>
</div>
</div>
<Button
variant="ghost"
size="icon"
onClick={() => removeCoasterStatFilter(index)}
>
<X className="w-4 h-4" />
</Button>
</div>
</div>
))}
<Button
variant="outline"
size="sm"
onClick={addCoasterStatFilter}
className="w-full"
>
<Plus className="w-4 h-4 mr-2" />
Add Coaster Stat Filter
</Button>
</CollapsibleContent>
</Collapsible>
</div>
);
}

View File

@@ -11,6 +11,7 @@ import { ChevronDown, Filter, X } from 'lucide-react';
import { Combobox } from '@/components/ui/combobox';
import { useCountries, useStatesProvinces, useManufacturers, useCompanyHeadquarters } from '@/hooks/useAutocompleteData';
import { DateRangePicker, DateRange } from '@/components/ui/date-range-picker';
import { AdvancedRideFilters, TechnicalSpecFilter, CoasterStatFilter } from './AdvancedRideFilters';
export interface SearchFilters {
// Park filters
@@ -36,6 +37,8 @@ export interface SearchFilters {
speedMax?: number;
intensityLevel?: string;
rideDateRange?: DateRange;
technicalSpecFilters?: TechnicalSpecFilter[];
coasterStatFilters?: CoasterStatFilter[];
// Company filters
companyType?: string;
@@ -391,6 +394,16 @@ export function SearchFiltersComponent({ filters, onFiltersChange, activeTab }:
/>
</div>
</div>
{/* Advanced Ride Filters */}
<div className="pt-4 border-t">
<AdvancedRideFilters
technicalSpecFilters={filters.technicalSpecFilters || []}
coasterStatFilters={filters.coasterStatFilters || []}
onTechnicalSpecFiltersChange={(filters) => updateFilter('technicalSpecFilters', filters)}
onCoasterStatFiltersChange={(filters) => updateFilter('coasterStatFilters', filters)}
/>
</div>
</div>
)}