wip: fixed pagination
This commit is contained in:
@@ -4,7 +4,7 @@ name: Deployment (Staging)
|
||||
on:
|
||||
# Runs on pushes targeting the default branch
|
||||
push:
|
||||
branches: ['main']
|
||||
branches: ["main"]
|
||||
|
||||
# Allows you to run this workflow manually from the Actions tab
|
||||
workflow_dispatch:
|
||||
@@ -20,8 +20,8 @@ jobs:
|
||||
- name: Set up Node
|
||||
uses: actions/setup-node@v6
|
||||
with:
|
||||
node-version: 'lts/*'
|
||||
cache: 'npm'
|
||||
node-version: "lts/*"
|
||||
cache: "npm"
|
||||
|
||||
- name: Set environment variables
|
||||
run: |
|
||||
@@ -44,6 +44,9 @@ jobs:
|
||||
if [[ -n "${{ secrets.VITE_BC_EMAIL }}" ]]; then
|
||||
echo "VITE_BC_EMAIL=${{ secrets.VITE_BC_EMAIL }}" >> .env
|
||||
fi
|
||||
if [[ -n "${{ secrets.VITE_EV_CARTO_KEY }}" ]]; then
|
||||
echo "VITE_EV_CARTO_KEY=${{ secrets.VITE_EV_CARTO_KEY }}" >> .env
|
||||
fi
|
||||
|
||||
- name: Install dependencies
|
||||
run: npm ci --omit=dev
|
||||
|
||||
@@ -63,8 +63,8 @@ const EventDialog: React.FC<EventDialogProps> = ({ event, onClose }) => {
|
||||
</>
|
||||
)}
|
||||
</DialogContent>
|
||||
<DialogActions sx={{ px: 3, pb: 2, justifyContent: 'space-between' }}>
|
||||
<Stack direction="row" spacing={1}>
|
||||
<DialogActions sx={{ px: 3, pb: 2, justifyContent: 'space-between', gap: 1, flexWrap: 'wrap' }}>
|
||||
<Stack direction="row" spacing={1} useFlexGap flexWrap="wrap" sx={{ minWidth: 0 }}>
|
||||
{event.link && (
|
||||
<Chip
|
||||
component={Link}
|
||||
|
||||
@@ -0,0 +1,58 @@
|
||||
import { fireEvent, render, screen, within } from '@testing-library/react'
|
||||
|
||||
import ListView from './ListView.js'
|
||||
import type { Event } from './types.js'
|
||||
|
||||
const makeEvent = (id: string, title: string, date: string, city = 'Ferrara'): Event => ({
|
||||
id,
|
||||
title,
|
||||
date: new Date(date),
|
||||
location: { name: 'Piazza', city, lat: 44, lng: 11 },
|
||||
})
|
||||
|
||||
describe('Events list', () => {
|
||||
beforeEach(() => {
|
||||
vi.useFakeTimers()
|
||||
vi.setSystemTime(new Date('2026-10-19T12:00:00'))
|
||||
})
|
||||
|
||||
afterEach(() => vi.useRealTimers())
|
||||
|
||||
it('separates events and sorts upcoming ascending and past descending', () => {
|
||||
render(
|
||||
<ListView
|
||||
events={[
|
||||
makeEvent('past-old', 'Past old', '2026-01-01'),
|
||||
makeEvent('future-late', 'Future late', '2026-12-01'),
|
||||
makeEvent('past-new', 'Past new', '2026-10-01'),
|
||||
makeEvent('future-soon', 'Future soon', '2026-11-01'),
|
||||
]}
|
||||
onSelect={vi.fn()}
|
||||
/>
|
||||
)
|
||||
|
||||
const upcoming = within(screen.getByRole('region', { name: 'prossimi eventi' }))
|
||||
const past = within(screen.getByRole('region', { name: 'eventi passati' }))
|
||||
expect(upcoming.getAllByRole('heading', { level: 2 }).map(node => node.textContent)).toEqual(['Future soon', 'Future late'])
|
||||
expect(past.getAllByRole('heading', { level: 2 }).map(node => node.textContent)).toEqual(['Past new', 'Past old'])
|
||||
})
|
||||
|
||||
it('renders date ranges and invokes onSelect for the clicked card', () => {
|
||||
const onSelect = vi.fn()
|
||||
const event = { ...makeEvent('range', 'Long medieval event', '2026-11-01', 'San Martino'), endDate: new Date('2026-11-03') }
|
||||
render(<ListView events={[event]} onSelect={onSelect} />)
|
||||
|
||||
expect(screen.getByText(/1-3 novembre 2026/)).toBeInTheDocument()
|
||||
expect(screen.getByText('San Martino')).toBeInTheDocument()
|
||||
fireEvent.click(screen.getByRole('heading', { name: 'Long medieval event' }))
|
||||
expect(onSelect).toHaveBeenCalledWith(event)
|
||||
})
|
||||
|
||||
it('uses a single fluid column on small screens', () => {
|
||||
render(<ListView events={[makeEvent('one', 'A very long event title that must wrap', '2026-11-01')]} onSelect={vi.fn()} />)
|
||||
|
||||
const card = screen.getByRole('heading', { name: /very long event/ }).closest('.MuiCard-root')
|
||||
expect(card).toHaveStyle({ width: '100%', minWidth: '0', boxSizing: 'border-box' })
|
||||
expect(screen.getByRole('region', { name: 'prossimi eventi' })).toHaveStyle({ width: '100%', minWidth: '0' })
|
||||
})
|
||||
})
|
||||
@@ -31,7 +31,11 @@ const EventCard: React.FC<{ event: Event; onSelect: (event: Event) => void }> =
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
opacity: isUpcoming(event.date) ? 1 : 0.6,
|
||||
width: theme => theme.breakpoints.values.lg * 0.3,
|
||||
// Cards must be allowed to shrink on phones. The previous fixed
|
||||
// width (30% of the `lg` breakpoint) was wider than small screens.
|
||||
width: '100%',
|
||||
minWidth: 0,
|
||||
boxSizing: 'border-box',
|
||||
}}>
|
||||
<CardActionArea
|
||||
onClick={() => onSelect(event)}
|
||||
@@ -39,27 +43,27 @@ const EventCard: React.FC<{ event: Event; onSelect: (event: Event) => void }> =
|
||||
{event.image && <CardMedia component="img" height={160} image={event.image} alt={event.title} />}
|
||||
<CardContent>
|
||||
<Stack direction="row" justifyContent="space-between" alignItems="flex-start" mb={1}>
|
||||
<Typography variant="h6" component="h2" sx={{ flexGrow: 1, mr: 1 }}>
|
||||
<Typography variant="h6" component="h2" sx={{ flexGrow: 1, minWidth: 0, mr: 1, overflowWrap: 'anywhere' }}>
|
||||
{event.title}
|
||||
</Typography>
|
||||
{!isUpcoming(event.date) && <Chip label="Passato" size="small" color="default" />}
|
||||
</Stack>
|
||||
<Stack direction="row" alignItems="center" justifyContent="space-between">
|
||||
<div>
|
||||
<Stack direction="row" alignItems="center" justifyContent="space-between" sx={{ minWidth: 0 }}>
|
||||
<Box sx={{ minWidth: 0 }}>
|
||||
<Stack direction="row" alignItems="center" spacing={0.5} mb={0.5}>
|
||||
<CalendarMonthIcon fontSize="small" color="primary" />
|
||||
<Typography variant="body2" color="text.secondary">
|
||||
<Typography variant="body2" color="text.secondary" sx={{ overflowWrap: 'anywhere' }}>
|
||||
{formatDateRange(event.date, event.endDate)}
|
||||
</Typography>
|
||||
</Stack>
|
||||
<Stack direction="row" alignItems="center" spacing={0.5}>
|
||||
<LocationOnIcon fontSize="small" color="primary" />
|
||||
<Typography variant="body2" color="text.secondary">
|
||||
<Typography variant="body2" color="text.secondary" sx={{ overflowWrap: 'anywhere' }}>
|
||||
{event.location.city}
|
||||
</Typography>
|
||||
</Stack>
|
||||
</div>
|
||||
<InfoOutline sx={{ color: 'text.secondary' }} />
|
||||
</Box>
|
||||
<InfoOutline sx={{ color: 'text.secondary', flexShrink: 0, ml: 1 }} />
|
||||
</Stack>
|
||||
</CardContent>
|
||||
</CardActionArea>
|
||||
@@ -85,13 +89,22 @@ const ListView: React.FC<ListViewProps> = ({ events, onSelect }) => {
|
||||
return groups
|
||||
}, [events])
|
||||
return (
|
||||
<Box sx={{ display: 'flex', flexDirection: 'column', gap: 4, alignItems: 'flex-start', pb: 4 }}>
|
||||
<Box sx={{ display: 'flex', flexDirection: 'column', gap: 4, width: '100%', minWidth: 0, pb: 4 }}>
|
||||
{eventGroups.upcoming.length > 0 && (
|
||||
<>
|
||||
<Typography variant="h5" component="h2" textAlign="center" mt={4} mb={2}>
|
||||
Prossimi eventi
|
||||
</Typography>
|
||||
<Box sx={{ display: 'flex', gap: 3, flexWrap: 'wrap' }}>
|
||||
<Box
|
||||
role="region"
|
||||
aria-label="prossimi eventi"
|
||||
sx={{
|
||||
display: 'grid',
|
||||
width: '100%',
|
||||
minWidth: 0,
|
||||
gridTemplateColumns: { xs: 'minmax(0, 1fr)', sm: 'repeat(2, minmax(0, 1fr))', lg: 'repeat(3, minmax(0, 1fr))' },
|
||||
gap: 3,
|
||||
}}>
|
||||
{eventGroups.upcoming.map(event => (
|
||||
<EventCard key={event.id} event={event} onSelect={onSelect} />
|
||||
))}
|
||||
@@ -103,7 +116,16 @@ const ListView: React.FC<ListViewProps> = ({ events, onSelect }) => {
|
||||
<Typography variant="h5" component="h3" textAlign="center" mt={4} mb={2}>
|
||||
Eventi passati
|
||||
</Typography>
|
||||
<Box sx={{ display: 'flex', gap: 3, flexWrap: 'wrap' }}>
|
||||
<Box
|
||||
role="region"
|
||||
aria-label="eventi passati"
|
||||
sx={{
|
||||
display: 'grid',
|
||||
width: '100%',
|
||||
minWidth: 0,
|
||||
gridTemplateColumns: { xs: 'minmax(0, 1fr)', sm: 'repeat(2, minmax(0, 1fr))', lg: 'repeat(3, minmax(0, 1fr))' },
|
||||
gap: 3,
|
||||
}}>
|
||||
{eventGroups.past.map(event => (
|
||||
<EventCard key={event.id} event={event} onSelect={onSelect} />
|
||||
))}
|
||||
|
||||
@@ -68,7 +68,7 @@ const MapView: React.FC<MapViewProps> = ({ events, onSelect }) => {
|
||||
<MapContainer center={CENTER} zoom={6} scrollWheelZoom style={{ height: 560, width: '100%' }}>
|
||||
<TileLayer
|
||||
attribution='© <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> © <a href="https://carto.com/">CARTO</a>'
|
||||
url="https://{s}.basemaps.cartocdn.com/rastertiles/voyager/{z}/{x}/{y}{r}.png"
|
||||
url={`https://basemaps.cartocdn.com/rastertiles/voyager/{z}/{x}/{y}.png?key=${import.meta.env.VITE_EV_CARTO_KEY}`}
|
||||
/>
|
||||
{sorted.map((event, idx) => (
|
||||
<Marker
|
||||
|
||||
@@ -105,7 +105,26 @@ export const events: Event[] = [
|
||||
'https://scontent-fco2-1.xx.fbcdn.net/v/t39.30808-6/475772202_928196622760603_187755049466880515_n.jpg?_nc_cat=108&ccb=1-7&_nc_sid=2a1932&_nc_ohc=GFNIVf-3cKoQ7kNvwHWpMoA&_nc_oc=AdqCNjkyT6mYI9D8sIsmAgfQdXtrVMNOthjth3dfcGCvVgxVSJSG_SVWroEcBPbyI7k&_nc_zt=23&_nc_ht=scontent-fco2-1.xx&_nc_gid=nD82EIE8zEzhzr8oQIF78A&_nc_ss=7b2a8&oh=00_Af6Jg9HJBlMy--pZ0eQp_QZ18k1b-hSmMDp-zUC6ID5Yrw&oe=69FFE2CC',
|
||||
},
|
||||
{
|
||||
id: '5',
|
||||
id: 'Autunno Ducale nelle Terre Estensi',
|
||||
title: 'Autunno Ducale nelle Terre Estensi',
|
||||
date: new Date('2026-10-24'),
|
||||
endDate: new Date('2026-10-25'),
|
||||
location: {
|
||||
name: 'Ferrara',
|
||||
city: 'Ferrara',
|
||||
lat: 44.5,
|
||||
lng: 11.37,
|
||||
link: 'https://maps.app.goo.gl/TYLWP51PvjCjsxdw6',
|
||||
},
|
||||
link: 'https://www.prolocoferrara.org/category/eventi/autunno-ducale/',
|
||||
image: 'https://www.prolocoferrara.org/wp-content/uploads/2026/07/locandina-AUTUNNO-DUCALE-2026.jpg',
|
||||
details: {
|
||||
description:
|
||||
"La Compagnia d'Arme del Santo Luca partecipa al tradizionale Palio di Ferrara con uno spettacolo di combattimento storico e una mostra della vita civile medievale.",
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'Usi & Costumi',
|
||||
title: 'Usi & Costumi',
|
||||
date: new Date('2026-11-07'),
|
||||
endDate: new Date('2026-11-08'),
|
||||
@@ -117,8 +136,7 @@ export const events: Event[] = [
|
||||
link: 'https://maps.app.goo.gl/q6MgoYrjm1uGxrrD8',
|
||||
},
|
||||
link: 'https://www.usiecostumi.org/',
|
||||
image:
|
||||
'https://scontent-fco2-1.xx.fbcdn.net/v/t39.30808-6/688923196_1294084136264066_3431102776522875652_n.jpg?_nc_cat=111&ccb=1-7&_nc_sid=2a1932&_nc_ohc=8DTyQeSJaEUQ7kNvwHpXmkS&_nc_oc=AdoKtRJlPuSnY0LgIEkrFSznA6aOekLn9cj09sScE7fPJEQz4YU7GSHIxHTZ0oOcJSw&_nc_zt=23&_nc_ht=scontent-fco2-1.xx&_nc_gid=6_24uMOlLU5IMhQptMgtsg&_nc_ss=7b2a8&oh=00_Af4WsF0pOUJT70MBLbOaRro0wJ1kct7-4dh_dto93ObmWw&oe=69FFD8CE',
|
||||
image: 'https://www.usiecostumi.org/wp-content/uploads/2024/04/usi-e-costumi-box.jpg',
|
||||
details: {
|
||||
description: 'La prima grande fiera internazionale, dedicata alla tradizione come strumento al servizio del turismo sostenibile.',
|
||||
},
|
||||
|
||||
@@ -22,8 +22,10 @@ const EventsPage = () => {
|
||||
<Page
|
||||
title="Eventi"
|
||||
sx={{
|
||||
width: theme => theme.breakpoints.values.lg,
|
||||
px: 'auto',
|
||||
width: '100%',
|
||||
maxWidth: theme => theme.breakpoints.values.lg,
|
||||
boxSizing: 'border-box',
|
||||
px: { xs: 2, sm: 3, md: 4 },
|
||||
mx: 'auto',
|
||||
}}>
|
||||
<Typography>
|
||||
|
||||
Reference in New Issue
Block a user