246 lines
8.2 KiB
TypeScript
246 lines
8.2 KiB
TypeScript
'use client';
|
|
|
|
import { useState, useEffect, useMemo } from 'react';
|
|
import { PawPrint, SlidersHorizontal } from 'lucide-react';
|
|
import AnimalCard from '@/components/animals/AnimalCard';
|
|
import FilterChips, { FILTER_OPTIONS } from '@/components/animals/FilterChips';
|
|
import { MOCK_ANIMALS, Animal } from '@/lib/mock-data';
|
|
|
|
// TODO: substituir por query Prisma quando DATABASE_URL estiver configurada
|
|
|
|
function filterAnimals(animals: Animal[], filter: string): Animal[] {
|
|
switch (filter) {
|
|
case 'dog': return animals.filter(a => a.species === 'DOG');
|
|
case 'cat': return animals.filter(a => a.species === 'CAT');
|
|
case 'urgent': return animals.filter(a => a.urgent);
|
|
case 'lisboa': return animals.filter(a => a.shelter.district.toLowerCase() === 'lisboa');
|
|
case 'porto': return animals.filter(a => a.shelter.district.toLowerCase() === 'porto');
|
|
case 'braga': return animals.filter(a => a.shelter.district.toLowerCase() === 'braga');
|
|
case 'sintra': return animals.filter(a => a.shelter.district.toLowerCase() === 'sintra');
|
|
case 'male': return animals.filter(a => a.sex === 'MALE');
|
|
case 'female': return animals.filter(a => a.sex === 'FEMALE');
|
|
case 'sterilized': return animals.filter(a => a.sterilized);
|
|
default: return animals;
|
|
}
|
|
}
|
|
|
|
function SkeletonCard() {
|
|
return (
|
|
<div
|
|
style={{
|
|
borderRadius: '16px',
|
|
overflow: 'hidden',
|
|
background: 'var(--color-surface)',
|
|
border: '1px solid var(--color-border)',
|
|
}}
|
|
>
|
|
<div className="skeleton" style={{ aspectRatio: '4/3', width: '100%' }} />
|
|
<div style={{ padding: '16px', display: 'flex', flexDirection: 'column', gap: '10px' }}>
|
|
<div className="skeleton" style={{ height: '26px', width: '60%', borderRadius: '6px' }} />
|
|
<div className="skeleton" style={{ height: '16px', width: '80%', borderRadius: '6px' }} />
|
|
<div className="skeleton" style={{ height: '14px', width: '50%', borderRadius: '6px' }} />
|
|
<div style={{ display: 'flex', gap: '8px', marginTop: '8px' }}>
|
|
<div className="skeleton" style={{ height: '40px', flex: 1, borderRadius: '100px' }} />
|
|
<div className="skeleton" style={{ height: '40px', flex: 1, borderRadius: '100px' }} />
|
|
</div>
|
|
</div>
|
|
</div>
|
|
);
|
|
}
|
|
|
|
export default function AnimalsPage() {
|
|
const [activeFilter, setActiveFilter] = useState('all');
|
|
const [loading, setLoading] = useState(true);
|
|
|
|
// Simulate async load
|
|
useEffect(() => {
|
|
const t = setTimeout(() => setLoading(false), 700);
|
|
return () => clearTimeout(t);
|
|
}, []);
|
|
|
|
// Re-show skeleton briefly when filter changes
|
|
const handleFilterChange = (id: string) => {
|
|
setLoading(true);
|
|
setActiveFilter(id);
|
|
setTimeout(() => setLoading(false), 350);
|
|
};
|
|
|
|
const animals = useMemo(() => filterAnimals(MOCK_ANIMALS, activeFilter), [activeFilter]);
|
|
|
|
const urgentCount = MOCK_ANIMALS.filter(a => a.urgent).length;
|
|
|
|
return (
|
|
<div style={{ background: 'var(--color-bg)', minHeight: '100vh' }}>
|
|
{/* Page header */}
|
|
<div
|
|
style={{
|
|
borderBottom: '1px solid var(--color-border)',
|
|
padding: 'clamp(32px, 5vw, 56px) 0 0',
|
|
background: 'var(--color-bg)',
|
|
}}
|
|
>
|
|
<div className="container">
|
|
{/* Breadcrumb */}
|
|
<p
|
|
style={{
|
|
fontFamily: 'var(--font-mono, monospace)',
|
|
fontSize: '11px',
|
|
fontWeight: 500,
|
|
letterSpacing: '0.1em',
|
|
textTransform: 'uppercase',
|
|
color: 'var(--color-muted)',
|
|
marginBottom: '12px',
|
|
}}
|
|
>
|
|
PawLink · Adopção
|
|
</p>
|
|
|
|
<div
|
|
style={{
|
|
display: 'flex',
|
|
alignItems: 'flex-start',
|
|
justifyContent: 'space-between',
|
|
flexWrap: 'wrap',
|
|
gap: '16px',
|
|
marginBottom: '24px',
|
|
}}
|
|
>
|
|
<div>
|
|
<h1
|
|
style={{
|
|
fontFamily: 'var(--font-playfair, Georgia, serif)',
|
|
fontWeight: 900,
|
|
fontSize: 'clamp(32px, 5vw, 48px)',
|
|
color: 'var(--color-text)',
|
|
lineHeight: 1.1,
|
|
marginBottom: '8px',
|
|
}}
|
|
>
|
|
Animais disponíveis
|
|
</h1>
|
|
<p
|
|
style={{
|
|
fontFamily: 'var(--font-body)',
|
|
fontSize: '16px',
|
|
color: 'var(--color-muted)',
|
|
lineHeight: 1.5,
|
|
}}
|
|
>
|
|
{MOCK_ANIMALS.length.toLocaleString('pt-PT')} animais à espera
|
|
{urgentCount > 0 && (
|
|
<span
|
|
style={{
|
|
marginLeft: '10px',
|
|
fontFamily: 'var(--font-mono, monospace)',
|
|
fontSize: '11px',
|
|
fontWeight: 500,
|
|
background: 'var(--color-amber)',
|
|
color: 'var(--color-soil)',
|
|
padding: '3px 8px',
|
|
borderRadius: '100px',
|
|
letterSpacing: '0.06em',
|
|
textTransform: 'uppercase',
|
|
}}
|
|
>
|
|
{urgentCount} urgentes
|
|
</span>
|
|
)}
|
|
</p>
|
|
</div>
|
|
|
|
<button
|
|
className="btn-ghost"
|
|
style={{
|
|
display: 'flex',
|
|
alignItems: 'center',
|
|
gap: '8px',
|
|
border: '1.5px solid var(--color-border)',
|
|
borderRadius: '12px',
|
|
}}
|
|
aria-label="Abrir filtros avançados"
|
|
>
|
|
<SlidersHorizontal size={16} />
|
|
Filtros avançados
|
|
</button>
|
|
</div>
|
|
|
|
{/* Filter chips */}
|
|
<FilterChips
|
|
options={FILTER_OPTIONS}
|
|
active={activeFilter}
|
|
onChange={handleFilterChange}
|
|
/>
|
|
</div>
|
|
</div>
|
|
|
|
{/* Grid */}
|
|
<div className="container" style={{ padding: '40px 16px' }}>
|
|
{loading ? (
|
|
<div className="animal-grid">
|
|
{Array.from({ length: 6 }).map((_, i) => (
|
|
<SkeletonCard key={i} />
|
|
))}
|
|
</div>
|
|
) : animals.length > 0 ? (
|
|
<>
|
|
<div className="animal-grid">
|
|
{animals.map((animal, index) => (
|
|
<AnimalCard key={animal.id} animal={animal} index={index} />
|
|
))}
|
|
</div>
|
|
<p
|
|
style={{
|
|
textAlign: 'center',
|
|
marginTop: '40px',
|
|
fontFamily: 'var(--font-mono, monospace)',
|
|
fontSize: '12px',
|
|
color: 'var(--color-muted)',
|
|
letterSpacing: '0.06em',
|
|
}}
|
|
>
|
|
{animals.length} de {MOCK_ANIMALS.length} animais • Dados de demonstração
|
|
</p>
|
|
</>
|
|
) : (
|
|
<div
|
|
style={{
|
|
textAlign: 'center',
|
|
padding: '80px 24px',
|
|
display: 'flex',
|
|
flexDirection: 'column',
|
|
alignItems: 'center',
|
|
gap: '16px',
|
|
}}
|
|
>
|
|
<PawPrint
|
|
size={48}
|
|
style={{ color: 'var(--color-fog)', strokeWidth: 1.5 }}
|
|
aria-hidden="true"
|
|
/>
|
|
<h2
|
|
style={{
|
|
fontFamily: 'var(--font-display)',
|
|
fontSize: '24px',
|
|
fontWeight: 700,
|
|
color: 'var(--color-text)',
|
|
}}
|
|
>
|
|
Nenhum animal encontrado
|
|
</h2>
|
|
<p style={{ color: 'var(--color-muted)', fontFamily: 'var(--font-body)', fontSize: '16px' }}>
|
|
Tenta um filtro diferente ou vê todos os animais disponíveis.
|
|
</p>
|
|
<button
|
|
onClick={() => handleFilterChange('all')}
|
|
className="btn-secondary"
|
|
style={{ marginTop: '8px' }}
|
|
>
|
|
Remover filtros
|
|
</button>
|
|
</div>
|
|
)}
|
|
</div>
|
|
</div>
|
|
);
|
|
}
|