Skip to content

Commit 5b3067d

Browse files
committed
Create Talks view and update data format
1 parent 1933859 commit 5b3067d

5 files changed

Lines changed: 480 additions & 145 deletions

File tree

src/app/talks/page.js

Lines changed: 247 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,247 @@
1+
"use client";
2+
3+
import { useState, useEffect } from "react";
4+
import TalkCard from "@/components/TalkCard";
5+
import Link from "next/link";
6+
import allTalks from "@/data/talks";
7+
import cityData from "@/data/cities";
8+
9+
export default function TalksPage() {
10+
const [selectedCity, setSelectedCity] = useState("");
11+
const [selectedCategory, setSelectedCategory] = useState("");
12+
const [searchQuery, setSearchQuery] = useState("");
13+
const [debouncedSearch, setDebouncedSearch] = useState("");
14+
const [selectedDay, setSelectedDay] = useState("");
15+
16+
useEffect(() => {
17+
const handler = setTimeout(() => {
18+
setDebouncedSearch(searchQuery);
19+
}, 300);
20+
21+
return () => clearTimeout(handler);
22+
}, [searchQuery]);
23+
24+
const filteredTalks = allTalks.filter((talk) => {
25+
const cityInfo = cityData[talk.city];
26+
const searchTerms = debouncedSearch.toLowerCase();
27+
28+
const cityMatch = !selectedCity || talk.city === selectedCity;
29+
const categoryMatch = !selectedCategory || talk.category === selectedCategory;
30+
const dateMatch = selectedDay ? cityInfo.date === selectedDay : true;
31+
32+
const searchMatch =
33+
talk.title.toLowerCase().includes(searchTerms) ||
34+
talk.description.toLowerCase().includes(searchTerms) ||
35+
talk.tags?.some(tag => tag.toLowerCase().includes(searchTerms)) ||
36+
talk.speaker.name.toLowerCase().includes(searchTerms);
37+
38+
return cityMatch && categoryMatch && dateMatch && searchMatch;
39+
});
40+
41+
// Obtener fechas únicas para los tabs
42+
const cityDates = Object.values(cityData).map((city) => ({
43+
date: city.date,
44+
name: city.name,
45+
}));
46+
47+
return (
48+
<div className="container-py">
49+
{/* Breadcrumb */}
50+
<div className="mb-6">
51+
<div className="flex items-center text-sm text-white/70">
52+
<Link href="/" className="hover:text-white transition-colors">
53+
Inicio
54+
</Link>
55+
<span className="mx-2">/</span>
56+
<span className="text-white">Charlas</span>
57+
</div>
58+
</div>
59+
60+
{/* Hero Section */}
61+
<div className="mb-12 text-center max-w-4xl mx-auto">
62+
<h1 className="text-3xl md:text-4xl lg:text-5xl font-bold mb-6">
63+
Charlas PyDay Chile 2025
64+
</h1>
65+
<p className="text-lg text-white/80">
66+
Explora todas las charlas programadas para los eventos de PyDay Chile
67+
2025. Desde principiantes hasta expertos, tenemos contenido para todos
68+
los niveles y áreas de interés.
69+
</p>
70+
</div>
71+
72+
{/* Tabs de días modificados */}
73+
<div className="mb-8 flex overflow-x-auto scrollbar-hidden">
74+
<div className="flex space-x-2 md:space-x-4 min-w-full pb-2">
75+
<button
76+
onClick={() => setSelectedDay("")}
77+
className={`px-4 py-2 rounded-lg text-sm md:text-base font-medium whitespace-nowrap ${
78+
!selectedDay ? "bg-green-700" : "bg-black/20 hover:bg-black/30"
79+
}`}
80+
>
81+
Todos los días
82+
</button>
83+
{cityDates.map((city) => (
84+
<button
85+
key={city.date}
86+
onClick={() => setSelectedDay(city.date)}
87+
className={`px-4 py-2 rounded-lg text-sm md:text-base font-medium whitespace-nowrap ${
88+
selectedDay === city.date
89+
? "bg-green-700"
90+
: "bg-black/20 hover:bg-black/30"
91+
}`}
92+
>
93+
{city.date.split(",")[0]} - {city.name}
94+
</button>
95+
))}
96+
</div>
97+
</div>
98+
99+
{/* Filtros de charlas */}
100+
<div className="mb-8 bg-black/20 backdrop-blur-sm rounded-lg p-4 md:p-6">
101+
<div className="flex flex-col md:flex-row gap-4 md:items-center justify-between">
102+
<div className="flex items-center gap-2 flex-wrap">
103+
<select
104+
value={selectedCity}
105+
onChange={(e) => setSelectedCity(e.target.value)}
106+
className="bg-black/30 text-white border border-white/20 rounded px-3 py-1.5 text-sm"
107+
>
108+
<option value="">Todas las sedes</option>
109+
{Object.keys(cityData).map((cityKey) => (
110+
<option key={cityKey} value={cityKey}>
111+
{cityData[cityKey].name}
112+
</option>
113+
))}
114+
</select>
115+
116+
<select
117+
value={selectedCategory}
118+
onChange={(e) => setSelectedCategory(e.target.value)}
119+
className="bg-black/30 text-white border border-white/20 rounded px-3 py-1.5 text-sm"
120+
>
121+
<option value="">Todas las categorías</option>
122+
<option value="tecnica">Técnica</option>
123+
<option value="comunidad">Comunidad</option>
124+
<option value="caso-de-exito">Caso de Éxito</option>
125+
<option value="keynote">Keynote</option>
126+
</select>
127+
</div>
128+
<div className="relative">
129+
<input
130+
type="text"
131+
placeholder="Buscar charlas..."
132+
value={searchQuery}
133+
onChange={(e) => setSearchQuery(e.target.value)}
134+
className="bg-black/30 text-white border border-white/20 rounded pl-9 pr-3 py-1.5 w-full md:w-64 text-sm focus:outline-none focus:ring-2 focus:ring-green-500"
135+
/>
136+
<svg
137+
xmlns="http://www.w3.org/2000/svg"
138+
className="h-4 w-4 absolute left-3 top-2 text-white/60"
139+
fill="none"
140+
viewBox="0 0 24 24"
141+
stroke="currentColor"
142+
>
143+
<path
144+
strokeLinecap="round"
145+
strokeLinejoin="round"
146+
strokeWidth={2}
147+
d="M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z"
148+
/>
149+
</svg>
150+
</div>
151+
</div>
152+
</div>
153+
154+
{/* Renderizado de charlas */}
155+
{filteredTalks.length > 0 ? (
156+
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6">
157+
{filteredTalks.map((talk) => {
158+
const cityInfo = cityData[talk.city];
159+
return (
160+
<TalkCard
161+
key={talk.id}
162+
talk={{
163+
...talk,
164+
date: cityInfo.date,
165+
location: cityInfo.name,
166+
cityInfo: cityInfo,
167+
}}
168+
/>
169+
);
170+
})}
171+
</div>
172+
) : (
173+
<div className="bg-black/20 backdrop-blur-sm rounded-lg p-6 md:p-8 text-center">
174+
<div className="w-16 h-16 md:w-20 md:h-20 bg-purple-600/30 rounded-full flex items-center justify-center mx-auto mb-4">
175+
<svg
176+
xmlns="http://www.w3.org/2000/svg"
177+
className="h-8 w-8 md:h-10 md:w-10"
178+
fill="none"
179+
viewBox="0 0 24 24"
180+
stroke="currentColor"
181+
>
182+
<path
183+
strokeLinecap="round"
184+
strokeLinejoin="round"
185+
strokeWidth={2}
186+
d="M19 11a7 7 0 01-7 7m0 0a7 7 0 01-7-7m7 7v4m0 0H8m4 0h4m-4-8a3 3 0 01-3-3V5a3 3 0 116 0v6a3 3 0 01-3 3z"
187+
/>
188+
</svg>
189+
</div>
190+
<h3 className="text-lg md:text-xl font-bold mb-2">
191+
No hay charlas disponibles aún
192+
</h3>
193+
<p className="text-white/80 mb-6">
194+
Estamos finalizando el programa. ¡Vuelve pronto para ver las charlas
195+
confirmadas!
196+
</p>
197+
<Link
198+
href="https://sessionize.com/pyday-valparaiso-2025/"
199+
target="_blank"
200+
className="btn-secondary inline-flex items-center"
201+
>
202+
<span>Proponer una charla</span>
203+
<svg
204+
xmlns="http://www.w3.org/2000/svg"
205+
className="h-5 w-5 ml-2"
206+
viewBox="0 0 20 20"
207+
fill="currentColor"
208+
>
209+
<path
210+
fillRule="evenodd"
211+
d="M10.293 5.293a1 1 0 011.414 0l4 4a1 1 0 010 1.414l-4 4a1 1 0 01-1.414-1.414L12.586 11H5a1 1 0 110-2h7.586l-2.293-2.293a1 1 0 010-1.414z"
212+
clipRule="evenodd"
213+
/>
214+
</svg>
215+
</Link>
216+
</div>
217+
)}
218+
219+
{/* Subscribe for updates */}
220+
<div className="mt-16 bg-green-800/30 backdrop-blur-sm rounded-lg p-6 md:p-8">
221+
<div className="max-w-4xl mx-auto">
222+
<div className="flex flex-col md:flex-row md:items-center justify-between gap-6">
223+
<div>
224+
<h3 className="text-xl md:text-2xl font-bold mb-2">
225+
¿Quieres recibir actualizaciones?
226+
</h3>
227+
<p className="text-sm md:text-base text-white/80">
228+
Suscríbete para recibir notificaciones sobre nuevas charlas y
229+
cambios en el programa.
230+
</p>
231+
</div>
232+
<div className="flex flex-col sm:flex-row gap-3">
233+
<input
234+
type="email"
235+
placeholder="Tu correo electrónico"
236+
className="bg-black/30 text-white border border-white/20 rounded px-4 py-2 w-full sm:w-64 text-sm focus:outline-none focus:ring-2 focus:ring-green-500"
237+
/>
238+
<button className="btn-primary whitespace-nowrap py-2">
239+
Suscribirse
240+
</button>
241+
</div>
242+
</div>
243+
</div>
244+
</div>
245+
</div>
246+
);
247+
}

src/components/TalkCard.js

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import Image from 'next/image';
1+
import Image from "next/image";
22

33
export default function TalkCard({ talk }) {
44
return (
@@ -24,12 +24,15 @@ export default function TalkCard({ talk }) {
2424
<p className="text-sm opacity-80 line-clamp-2">{talk.description}</p>
2525
<div className="mt-4 flex flex-wrap gap-2">
2626
{talk.tags.map((tag, index) => (
27-
<span key={index} className="text-xs bg-green-600/40 px-2 py-1 rounded-full">
27+
<span
28+
key={index}
29+
className="text-xs bg-green-600/40 hover:bg-green-600/60 px-2 py-1 rounded-full transition-colors cursor-pointer"
30+
>
2831
{tag}
2932
</span>
3033
))}
3134
</div>
3235
</div>
3336
</div>
3437
);
35-
}
38+
}

0 commit comments

Comments
 (0)