Spaces:
Sleeping
Sleeping
Add link to Google Maps
Browse files- src/components/ConferenceDialog.tsx +13 -1
- src/components/FilterBar.tsx +8 -4
- src/pages/Calendar.tsx +9 -2
- src/pages/Index.tsx +4 -1
src/components/ConferenceDialog.tsx
CHANGED
@@ -164,6 +164,10 @@ END:VCALENDAR`;
|
|
164 |
}
|
165 |
};
|
166 |
|
|
|
|
|
|
|
|
|
167 |
return (
|
168 |
<Dialog open={open} onOpenChange={onOpenChange}>
|
169 |
<DialogContent className="dialog-content max-w-md">
|
@@ -184,7 +188,15 @@ END:VCALENDAR`;
|
|
184 |
</div>
|
185 |
<div className="flex items-center text-neutral">
|
186 |
<Globe className="h-5 w-5 mr-3 flex-shrink-0" />
|
187 |
-
<
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
188 |
</div>
|
189 |
<div className="flex items-center text-neutral">
|
190 |
<Clock className="h-5 w-5 mr-3 flex-shrink-0" />
|
|
|
164 |
}
|
165 |
};
|
166 |
|
167 |
+
const generateGoogleMapsUrl = (venue: string | undefined, place: string): string => {
|
168 |
+
return `https://www.google.com/maps/search/?api=1&query=${encodeURIComponent(venue || place)}`;
|
169 |
+
};
|
170 |
+
|
171 |
return (
|
172 |
<Dialog open={open} onOpenChange={onOpenChange}>
|
173 |
<DialogContent className="dialog-content max-w-md">
|
|
|
188 |
</div>
|
189 |
<div className="flex items-center text-neutral">
|
190 |
<Globe className="h-5 w-5 mr-3 flex-shrink-0" />
|
191 |
+
<a
|
192 |
+
href={generateGoogleMapsUrl(conference.venue, conference.place)}
|
193 |
+
target="_blank"
|
194 |
+
rel="noopener noreferrer"
|
195 |
+
className="hover:text-primary hover:underline"
|
196 |
+
onClick={(e) => e.stopPropagation()}
|
197 |
+
>
|
198 |
+
{conference.place}
|
199 |
+
</a>
|
200 |
</div>
|
201 |
<div className="flex items-center text-neutral">
|
202 |
<Clock className="h-5 w-5 mr-3 flex-shrink-0" />
|
src/components/FilterBar.tsx
CHANGED
@@ -7,7 +7,7 @@ interface FilterBarProps {
|
|
7 |
onTagSelect: (tags: Set<string>) => void;
|
8 |
}
|
9 |
|
10 |
-
const FilterBar = ({ selectedTags, onTagSelect }: FilterBarProps) => {
|
11 |
const uniqueTags = useMemo(() => {
|
12 |
const tags = new Set<string>();
|
13 |
if (Array.isArray(conferencesData)) {
|
@@ -26,6 +26,10 @@ const FilterBar = ({ selectedTags, onTagSelect }: FilterBarProps) => {
|
|
26 |
}));
|
27 |
}, []);
|
28 |
|
|
|
|
|
|
|
|
|
29 |
return (
|
30 |
<div className="w-full py-6 bg-white border-b border-neutral-200 animate-fade-in shadow-sm">
|
31 |
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
|
@@ -36,7 +40,7 @@ const FilterBar = ({ selectedTags, onTagSelect }: FilterBarProps) => {
|
|
36 |
title={filter.description}
|
37 |
onClick={() => {
|
38 |
const newTags = new Set(selectedTags);
|
39 |
-
if (
|
40 |
newTags.delete(filter.id);
|
41 |
} else {
|
42 |
newTags.add(filter.id);
|
@@ -46,7 +50,7 @@ const FilterBar = ({ selectedTags, onTagSelect }: FilterBarProps) => {
|
|
46 |
className={`
|
47 |
px-4 py-2 text-sm font-medium rounded-lg transition-all duration-200
|
48 |
filter-tag
|
49 |
-
${
|
50 |
? "bg-primary text-white shadow-sm filter-tag-active"
|
51 |
: "bg-neutral-50 text-neutral-600 hover:bg-neutral-100 hover:text-neutral-900"
|
52 |
}
|
@@ -56,7 +60,7 @@ const FilterBar = ({ selectedTags, onTagSelect }: FilterBarProps) => {
|
|
56 |
</button>
|
57 |
))}
|
58 |
|
59 |
-
{selectedTags
|
60 |
<button
|
61 |
onClick={() => onTagSelect(new Set())}
|
62 |
className="px-4 py-2 text-sm font-medium rounded-lg transition-all duration-200
|
|
|
7 |
onTagSelect: (tags: Set<string>) => void;
|
8 |
}
|
9 |
|
10 |
+
const FilterBar = ({ selectedTags = new Set(), onTagSelect }: FilterBarProps) => {
|
11 |
const uniqueTags = useMemo(() => {
|
12 |
const tags = new Set<string>();
|
13 |
if (Array.isArray(conferencesData)) {
|
|
|
26 |
}));
|
27 |
}, []);
|
28 |
|
29 |
+
const isTagSelected = (tagId: string) => {
|
30 |
+
return selectedTags?.has(tagId) ?? false;
|
31 |
+
};
|
32 |
+
|
33 |
return (
|
34 |
<div className="w-full py-6 bg-white border-b border-neutral-200 animate-fade-in shadow-sm">
|
35 |
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
|
|
|
40 |
title={filter.description}
|
41 |
onClick={() => {
|
42 |
const newTags = new Set(selectedTags);
|
43 |
+
if (isTagSelected(filter.id)) {
|
44 |
newTags.delete(filter.id);
|
45 |
} else {
|
46 |
newTags.add(filter.id);
|
|
|
50 |
className={`
|
51 |
px-4 py-2 text-sm font-medium rounded-lg transition-all duration-200
|
52 |
filter-tag
|
53 |
+
${isTagSelected(filter.id)
|
54 |
? "bg-primary text-white shadow-sm filter-tag-active"
|
55 |
: "bg-neutral-50 text-neutral-600 hover:bg-neutral-100 hover:text-neutral-900"
|
56 |
}
|
|
|
60 |
</button>
|
61 |
))}
|
62 |
|
63 |
+
{selectedTags?.size > 0 && (
|
64 |
<button
|
65 |
onClick={() => onTagSelect(new Set())}
|
66 |
className="px-4 py-2 text-sm font-medium rounded-lg transition-all duration-200
|
src/pages/Calendar.tsx
CHANGED
@@ -44,6 +44,7 @@ const categoryNames: Record<string, string> = {
|
|
44 |
const CalendarPage = () => {
|
45 |
const [selectedDate, setSelectedDate] = useState<Date | undefined>(new Date());
|
46 |
const [isYearView, setIsYearView] = useState(true);
|
|
|
47 |
const [searchQuery, setSearchQuery] = useState("");
|
48 |
const [selectedDayEvents, setSelectedDayEvents] = useState<{ date: Date | null, events: { deadlines: Conference[], conferences: Conference[] } }>({
|
49 |
date: null,
|
@@ -522,6 +523,11 @@ const CalendarPage = () => {
|
|
522 |
);
|
523 |
};
|
524 |
|
|
|
|
|
|
|
|
|
|
|
525 |
return (
|
526 |
<div className="min-h-screen bg-neutral-light">
|
527 |
<Header onSearch={setSearchQuery} />
|
@@ -606,7 +612,8 @@ const CalendarPage = () => {
|
|
606 |
numberOfMonths={isYearView ? 12 : 1}
|
607 |
showOutsideDays={false}
|
608 |
defaultMonth={new Date(currentYear, 0)}
|
609 |
-
month={new Date(currentYear, 0)}
|
|
|
610 |
fromMonth={isYearView ? new Date(currentYear, 0) : undefined}
|
611 |
toMonth={isYearView ? new Date(currentYear, 11) : undefined}
|
612 |
className="bg-white rounded-lg p-6 shadow-sm mx-auto w-full"
|
@@ -641,7 +648,7 @@ const CalendarPage = () => {
|
|
641 |
day_today: "bg-neutral-100 text-primary font-semibold",
|
642 |
day_outside: "hidden",
|
643 |
nav: "space-x-1 flex items-center",
|
644 |
-
nav_button: "h-7 w-7 bg-transparent p-0 opacity-50 hover:opacity-100",
|
645 |
nav_button_previous: "absolute left-1",
|
646 |
nav_button_next: "absolute right-1"
|
647 |
}}
|
|
|
44 |
const CalendarPage = () => {
|
45 |
const [selectedDate, setSelectedDate] = useState<Date | undefined>(new Date());
|
46 |
const [isYearView, setIsYearView] = useState(true);
|
47 |
+
const [currentMonth, setCurrentMonth] = useState<Date>(new Date());
|
48 |
const [searchQuery, setSearchQuery] = useState("");
|
49 |
const [selectedDayEvents, setSelectedDayEvents] = useState<{ date: Date | null, events: { deadlines: Conference[], conferences: Conference[] } }>({
|
50 |
date: null,
|
|
|
523 |
);
|
524 |
};
|
525 |
|
526 |
+
const handleMonthChange = (month: Date) => {
|
527 |
+
setCurrentMonth(month);
|
528 |
+
setSelectedDate(month);
|
529 |
+
};
|
530 |
+
|
531 |
return (
|
532 |
<div className="min-h-screen bg-neutral-light">
|
533 |
<Header onSearch={setSearchQuery} />
|
|
|
612 |
numberOfMonths={isYearView ? 12 : 1}
|
613 |
showOutsideDays={false}
|
614 |
defaultMonth={new Date(currentYear, 0)}
|
615 |
+
month={isYearView ? new Date(currentYear, 0) : currentMonth}
|
616 |
+
onMonthChange={handleMonthChange}
|
617 |
fromMonth={isYearView ? new Date(currentYear, 0) : undefined}
|
618 |
toMonth={isYearView ? new Date(currentYear, 11) : undefined}
|
619 |
className="bg-white rounded-lg p-6 shadow-sm mx-auto w-full"
|
|
|
648 |
day_today: "bg-neutral-100 text-primary font-semibold",
|
649 |
day_outside: "hidden",
|
650 |
nav: "space-x-1 flex items-center",
|
651 |
+
nav_button: isYearView ? "hidden" : "h-7 w-7 bg-transparent p-0 opacity-50 hover:opacity-100",
|
652 |
nav_button_previous: "absolute left-1",
|
653 |
nav_button_next: "absolute right-1"
|
654 |
}}
|
src/pages/Index.tsx
CHANGED
@@ -50,7 +50,10 @@ const Index = () => {
|
|
50 |
<Header onSearch={setSearchQuery} />
|
51 |
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
|
52 |
<div className="space-y-4 py-4">
|
53 |
-
<FilterBar
|
|
|
|
|
|
|
54 |
<div className="flex items-center gap-2">
|
55 |
<label htmlFor="show-past" className="text-sm text-neutral-600">
|
56 |
Show past conferences
|
|
|
50 |
<Header onSearch={setSearchQuery} />
|
51 |
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
|
52 |
<div className="space-y-4 py-4">
|
53 |
+
<FilterBar
|
54 |
+
selectedTags={selectedTags}
|
55 |
+
onTagSelect={setSelectedTags}
|
56 |
+
/>
|
57 |
<div className="flex items-center gap-2">
|
58 |
<label htmlFor="show-past" className="text-sm text-neutral-600">
|
59 |
Show past conferences
|