gpt-engineer-app[bot] commited on
Commit
d70266a
·
1 Parent(s): 966625f

Fix calendar date display

Browse files

Improve calendar functionality to allow switching between yearly and monthly views. Display submission deadlines and conference dates as lines at the bottom of each date, similar to the original AI deadlines website. Address broken calendar dates.

Files changed (1) hide show
  1. src/pages/Calendar.tsx +140 -263
src/pages/Calendar.tsx CHANGED
@@ -1,341 +1,218 @@
 
1
  import { useState } from "react";
2
- import Header from "@/components/Header";
3
  import conferencesData from "@/data/conferences.yml";
4
  import { Conference } from "@/types/conference";
5
- import { Calendar as CalendarIcon, Tag, CircleDot, Globe, Clock } from "lucide-react";
6
  import { Calendar } from "@/components/ui/calendar";
7
- import { parseISO, format, isValid, isSameMonth } from "date-fns";
 
8
 
9
  const CalendarPage = () => {
10
  const [selectedDate, setSelectedDate] = useState<Date | undefined>(new Date());
11
- const [hoveredDate, setHoveredDate] = useState<Date | undefined>(undefined);
12
- const [searchQuery, setSearchQuery] = useState("");
13
 
14
- // Handle month change
15
- const handleMonthChange = (date: Date) => {
16
- setSelectedDate(date);
17
- };
18
-
19
  // Helper function to safely parse dates
20
- const safeParseISO = (dateString: string | undefined | number | Date): Date | null => {
21
  if (!dateString || dateString === 'TBD') return null;
22
-
23
- // If it's already a Date object, return it
24
- if (dateString instanceof Date) {
25
- return isValid(dateString) ? dateString : null;
26
- }
27
-
28
  const dateStr = typeof dateString === 'number' ? dateString.toString() : dateString;
29
 
30
  try {
31
- // First try to parse the date directly
32
- let parsedDate = parseISO(dateStr);
33
-
34
- // If that fails, try to parse various date formats
35
- if (!isValid(parsedDate)) {
36
- // Try to parse formats like "July 11-19, 2025" or "Sept 9-12, 2025"
37
- const match = dateStr.match(/([A-Za-z]+)\s+(\d+)(?:-\d+)?,\s*(\d{4})/);
38
- if (match) {
39
- const [_, month, day, year] = match;
40
- const normalizedDate = `${year}-${month}-${day.padStart(2, '0')}`;
41
- parsedDate = parseISO(normalizedDate);
42
- }
43
- }
44
-
45
  return isValid(parsedDate) ? parsedDate : null;
46
  } catch (error) {
47
- // Only log error if it's not already a Date object
48
- if (!(dateString instanceof Date)) {
49
- console.error("Error parsing date:", dateString);
50
- }
51
  return null;
52
  }
53
  };
54
 
55
- // Get events for a specific date
56
- const getEventsForDate = (date: Date) => {
57
- return conferencesData.filter((conf: Conference) => {
58
- const deadlineDate = safeParseISO(conf.deadline);
59
-
60
- // Parse conference dates from the 'date' field
61
- let startDate = null;
62
- let endDate = null;
63
-
64
- if (conf.date) {
65
- const dates = conf.date.split(/[-–]/); // Split on both hyphen types
66
- const startDateStr = dates[0].trim();
67
- const endDateMatch = dates[1]?.match(/(\d+),?\s*(\d{4})/);
68
-
69
- // Parse start date
70
- const startMatch = startDateStr.match(/([A-Za-z]+)\s+(\d+)/);
71
- if (startMatch) {
72
- const [_, month, day] = startMatch;
73
- const year = endDateMatch ? endDateMatch[2] : startDateStr.match(/\d{4}/)?.[0];
74
- if (year) {
75
- startDate = new Date(`${year}-${month}-${day.padStart(2, '0')}`);
76
- }
77
- }
78
-
79
- // Parse end date if it exists
80
- if (endDateMatch) {
81
- const [_, day, year] = endDateMatch;
82
- const month = startMatch?.[1]; // Use same month as start date
83
- if (month) {
84
- endDate = new Date(`${year}-${month}-${day.padStart(2, '0')}`);
85
- }
86
- }
87
- }
88
-
89
- const isDeadlineOnDate = deadlineDate &&
90
- format(deadlineDate, 'yyyy-MM-dd') === format(date, 'yyyy-MM-dd');
91
-
92
- let isConferenceOnDate = false;
93
- if (startDate && endDate) {
94
- const currentDate = new Date(date);
95
- currentDate.setHours(0, 0, 0, 0);
96
- startDate.setHours(0, 0, 0, 0);
97
- endDate.setHours(0, 0, 0, 0);
98
- isConferenceOnDate = currentDate >= startDate && currentDate <= endDate;
99
- } else if (startDate) {
100
- isConferenceOnDate = format(startDate, 'yyyy-MM-dd') === format(date, 'yyyy-MM-dd');
101
- }
102
-
103
- return isDeadlineOnDate || isConferenceOnDate;
104
- });
105
- };
106
-
107
- // Get all events (conferences and deadlines) for a given month
108
- const getMonthEvents = (date: Date) => {
109
  return conferencesData.filter((conf: Conference) => {
110
  const deadlineDate = safeParseISO(conf.deadline);
111
  const startDate = safeParseISO(conf.start);
112
  const endDate = safeParseISO(conf.end);
113
 
114
- // Check if deadline is in the selected month
115
- const deadlineInMonth = deadlineDate && isSameMonth(deadlineDate, date);
 
 
116
 
117
- // Check if any part of the conference falls in the selected month
118
- let conferenceInMonth = false;
119
  if (startDate && endDate) {
120
- // For multi-day conferences, check if any day falls in the selected month
121
  let currentDate = new Date(startDate);
122
  while (currentDate <= endDate) {
123
- if (isSameMonth(currentDate, date)) {
124
- conferenceInMonth = true;
125
  break;
126
  }
127
  currentDate.setDate(currentDate.getDate() + 1);
128
  }
129
  } else if (startDate) {
130
- conferenceInMonth = isSameMonth(startDate, date);
131
  }
132
 
133
- return deadlineInMonth || conferenceInMonth;
134
  });
135
  };
136
 
137
- // Update getDatesWithEvents to use the same date parsing logic
138
- const getDatesWithEvents = () => {
139
- const dates = {
140
- conferences: new Set<string>(),
141
- deadlines: new Set<string>()
142
- };
143
-
144
- conferencesData.forEach((conf: Conference) => {
145
- // Handle deadline dates
146
- if (conf.deadline && conf.deadline !== 'TBD') {
147
- const deadlineDate = parseISO(conf.deadline);
148
- if (isValid(deadlineDate)) {
149
- dates.deadlines.add(format(deadlineDate, 'yyyy-MM-dd'));
150
- }
151
- }
152
 
153
- // Handle conference dates
154
- if (conf.date) {
155
- const [startStr, endStr] = conf.date.split(/[-–]/);
156
- const startMatch = startStr.trim().match(/([A-Za-z]+)\s+(\d+)/);
157
-
158
- if (startMatch) {
159
- const [_, month, startDay] = startMatch;
160
- let year = conf.year?.toString() || '';
161
-
162
- if (endStr) {
163
- const endMatch = endStr.trim().match(/(\d+)?,?\s*(\d{4})/);
164
- if (endMatch) {
165
- year = endMatch[2];
166
- }
167
- }
168
 
169
- if (year) {
170
- const startDate = new Date(`${year}-${month}-${startDay.padStart(2, '0')}`);
171
-
172
- if (isValid(startDate)) {
173
- let currentDate = new Date(startDate);
174
-
175
- // If there's an end date, mark all dates in between
176
- if (endStr) {
177
- const endMatch = endStr.trim().match(/(\d+)?,?\s*(\d{4})/);
178
- if (endMatch) {
179
- const endDay = endMatch[1];
180
- const endDate = new Date(`${year}-${month}-${endDay.padStart(2, '0')}`);
181
-
182
- while (currentDate <= endDate) {
183
- dates.conferences.add(format(currentDate, 'yyyy-MM-dd'));
184
- currentDate.setDate(currentDate.getDate() + 1);
185
- }
186
- }
187
- } else {
188
- // Single day conference
189
- dates.conferences.add(format(currentDate, 'yyyy-MM-dd'));
190
- }
191
- }
192
- }
193
  }
 
 
194
  }
195
- });
196
 
197
- return {
198
- conferences: Array.from(dates.conferences).map(date => parseISO(date)),
199
- deadlines: Array.from(dates.deadlines).map(date => parseISO(date))
200
- };
201
  };
202
 
203
- const monthEvents = selectedDate ? getMonthEvents(selectedDate) : [];
204
- const datesWithEvents = getDatesWithEvents();
205
 
206
- // Render event details for a specific date
207
- const renderEventDetails = (date: Date) => {
208
- const events = getEventsForDate(date);
209
- if (!events.length) return null;
 
210
 
211
  return (
212
- <div className="space-y-4">
213
- <h3 className="font-semibold text-lg">
214
- Events on {format(date, 'MMMM d, yyyy')}
215
- </h3>
216
- <div className="space-y-3">
217
- {events.map((conf, index) => (
218
- <div key={index} className="p-4 bg-white rounded-lg shadow-sm border border-neutral-200">
219
- <h4 className="font-medium text-base">{conf.title}</h4>
220
- {conf.full_name && (
221
- <p className="text-sm text-neutral-600 mt-1">{conf.full_name}</p>
222
- )}
223
- <div className="mt-2 space-y-1 text-sm">
224
- <p className="flex items-center gap-2">
225
- <CalendarIcon className="h-4 w-4" />
226
- {conf.date}
227
- </p>
228
- <p className="flex items-center gap-2">
229
- <Globe className="h-4 w-4" />
230
- {conf.place}
231
- </p>
232
- {conf.deadline && conf.deadline !== 'TBD' && (
233
- <p className="flex items-center gap-2 text-red-600">
234
- <Clock className="h-4 w-4" />
235
- Deadline: {format(parseISO(conf.deadline), 'PPP')} ({conf.timezone})
236
- </p>
237
- )}
238
- {conf.tags && (
239
- <div className="flex items-center gap-2 mt-2 flex-wrap">
240
- <Tag className="h-4 w-4" />
241
- {Array.isArray(conf.tags) ? (
242
- conf.tags.map((tag, i) => (
243
- <span key={i} className="bg-neutral-100 px-2 py-1 rounded-full text-xs">
244
- {tag}
245
- </span>
246
- ))
247
- ) : (
248
- <span className="bg-neutral-100 px-2 py-1 rounded-full text-xs">
249
- {conf.tags}
250
- </span>
251
- )}
252
- </div>
253
- )}
254
- </div>
255
- </div>
256
- ))}
257
  </div>
258
  </div>
259
  );
260
  };
261
 
262
  return (
263
- <div className="min-h-screen bg-neutral-light">
264
- <Header onSearch={setSearchQuery} />
265
- <div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-8">
266
- <div className="flex gap-8">
267
- <div className="bg-white rounded-lg shadow p-6 flex-shrink-0">
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
268
  <Calendar
269
  mode="single"
270
  selected={selectedDate}
271
  onSelect={setSelectedDate}
272
- onMonthChange={handleMonthChange}
273
- modifiers={{
274
- conference: (date) => datesWithEvents.conferences.some(d =>
275
- format(d, 'yyyy-MM-dd') === format(date, 'yyyy-MM-dd')
 
 
 
276
  ),
277
- deadline: (date) => datesWithEvents.deadlines.some(d =>
278
- format(d, 'yyyy-MM-dd') === format(date, 'yyyy-MM-dd')
279
- )
280
  }}
281
- className="bg-white rounded-lg shadow-sm mx-auto"
282
  classNames={{
283
- months: "flex flex-col sm:flex-row space-y-4 sm:space-x-4 sm:space-y-0",
284
- month: "space-y-4 w-full",
285
  caption: "flex justify-center pt-1 relative items-center mb-4",
286
  caption_label: "text-lg font-semibold",
287
  table: "w-full border-collapse space-y-1",
288
  head_row: "flex",
289
- head_cell: "text-muted-foreground rounded-md w-14 font-normal text-[0.8rem]",
290
  row: "flex w-full mt-2",
291
- cell: "h-14 w-14 text-center text-sm p-0 relative [&:has([aria-selected])]:bg-accent first:[&:has([aria-selected])]:rounded-l-md last:[&:has([aria-selected])]:rounded-r-md focus-within:relative focus-within:z-20",
292
- day: "h-14 w-14 p-0 font-normal aria-selected:opacity-100 hover:bg-neutral-100 rounded-lg transition-colors",
 
 
293
  day_today: "bg-neutral-100 text-primary font-semibold",
294
  nav: "space-x-1 flex items-center",
295
  nav_button: "h-7 w-7 bg-transparent p-0 opacity-50 hover:opacity-100",
296
  nav_button_previous: "absolute left-1",
297
  nav_button_next: "absolute right-1",
298
  }}
299
- modifiersStyles={{
300
- conference: {
301
- backgroundColor: '#DDD6FE',
302
- color: '#7C3AED',
303
- fontWeight: 'bold'
304
- },
305
- deadline: {
306
- backgroundColor: '#FEE2E2',
307
- color: '#EF4444',
308
- fontWeight: 'bold'
309
- }
310
- }}
311
- components={{
312
- Day: (props) => (
313
- <button
314
- {...props}
315
- onMouseEnter={() => setHoveredDate(props.date)}
316
- onMouseLeave={() => setHoveredDate(undefined)}
317
- />
318
- )
319
- }}
320
  />
321
-
322
- <div className="mt-4 space-y-2">
323
- <div className="flex justify-center gap-6 mb-6">
324
- <div className="flex items-center gap-2">
325
- <CircleDot className="h-4 w-4 text-purple-600" />
326
- <span>Conference Dates</span>
327
- </div>
328
- <div className="flex items-center gap-2">
329
- <CircleDot className="h-4 w-4 text-red-500" />
330
- <span>Submission Deadlines</span>
331
- </div>
332
- </div>
333
- </div>
334
  </div>
335
 
336
- <div className="flex-grow">
337
- {(hoveredDate || selectedDate) && renderEventDetails(hoveredDate || selectedDate)}
338
- </div>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
339
  </div>
340
  </div>
341
  </div>
 
1
+
2
  import { useState } from "react";
 
3
  import conferencesData from "@/data/conferences.yml";
4
  import { Conference } from "@/types/conference";
5
+ import { Calendar as CalendarIcon, Tag } from "lucide-react";
6
  import { Calendar } from "@/components/ui/calendar";
7
+ import { parseISO, format, isValid, isSameMonth, isSameYear, isSameDay } from "date-fns";
8
+ import { Toggle } from "@/components/ui/toggle";
9
 
10
  const CalendarPage = () => {
11
  const [selectedDate, setSelectedDate] = useState<Date | undefined>(new Date());
12
+ const [isYearView, setIsYearView] = useState(false);
 
13
 
 
 
 
 
 
14
  // Helper function to safely parse dates
15
+ const safeParseISO = (dateString: string | undefined | number): Date | null => {
16
  if (!dateString || dateString === 'TBD') return null;
 
 
 
 
 
 
17
  const dateStr = typeof dateString === 'number' ? dateString.toString() : dateString;
18
 
19
  try {
20
+ const normalizedDate = dateStr.replace(/(\d{4})-(\d{1})-(\d{1,2})/, '$1-0$2-$3')
21
+ .replace(/(\d{4})-(\d{2})-(\d{1})/, '$1-$2-0$3');
22
+ const parsedDate = parseISO(normalizedDate);
 
 
 
 
 
 
 
 
 
 
 
23
  return isValid(parsedDate) ? parsedDate : null;
24
  } catch (error) {
25
+ console.error("Error parsing date:", dateString);
 
 
 
26
  return null;
27
  }
28
  };
29
 
30
+ // Get events for the current month/year
31
+ const getEvents = (date: Date) => {
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
32
  return conferencesData.filter((conf: Conference) => {
33
  const deadlineDate = safeParseISO(conf.deadline);
34
  const startDate = safeParseISO(conf.start);
35
  const endDate = safeParseISO(conf.end);
36
 
37
+ const dateMatches = isYearView ? isSameYear : isSameMonth;
38
+
39
+ // Check if deadline is in the selected period
40
+ const deadlineInPeriod = deadlineDate && dateMatches(deadlineDate, date);
41
 
42
+ // Check if any part of the conference falls in the selected period
43
+ let conferenceInPeriod = false;
44
  if (startDate && endDate) {
 
45
  let currentDate = new Date(startDate);
46
  while (currentDate <= endDate) {
47
+ if (dateMatches(currentDate, date)) {
48
+ conferenceInPeriod = true;
49
  break;
50
  }
51
  currentDate.setDate(currentDate.getDate() + 1);
52
  }
53
  } else if (startDate) {
54
+ conferenceInPeriod = dateMatches(startDate, date);
55
  }
56
 
57
+ return deadlineInPeriod || conferenceInPeriod;
58
  });
59
  };
60
 
61
+ // Get all events for day indicators
62
+ const getDayEvents = (date: Date) => {
63
+ return conferencesData.reduce((acc, conf) => {
64
+ const deadlineDate = safeParseISO(conf.deadline);
65
+ const startDate = safeParseISO(conf.start);
66
+ const endDate = safeParseISO(conf.end);
 
 
 
 
 
 
 
 
 
67
 
68
+ if (deadlineDate && isSameDay(deadlineDate, date)) {
69
+ acc.deadlines.push(conf);
70
+ }
 
 
 
 
 
 
 
 
 
 
 
 
71
 
72
+ if (startDate && endDate) {
73
+ if (date >= startDate && date <= endDate) {
74
+ acc.conferences.push(conf);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
75
  }
76
+ } else if (startDate && isSameDay(startDate, date)) {
77
+ acc.conferences.push(conf);
78
  }
 
79
 
80
+ return acc;
81
+ }, { deadlines: [], conferences: [] } as { deadlines: Conference[], conferences: Conference[] });
 
 
82
  };
83
 
84
+ const events = selectedDate ? getEvents(selectedDate) : [];
 
85
 
86
+ // Custom day content renderer
87
+ const renderDayContent = (day: Date) => {
88
+ const dayEvents = getDayEvents(day);
89
+ const hasDeadlines = dayEvents.deadlines.length > 0;
90
+ const hasConferences = dayEvents.conferences.length > 0;
91
 
92
  return (
93
+ <div className="relative w-full h-full flex flex-col items-center">
94
+ <span className="mb-1">{format(day, 'd')}</span>
95
+ <div className="absolute bottom-0 left-0 right-0 flex gap-0.5 px-1">
96
+ {hasDeadlines && (
97
+ <div className="h-0.5 flex-1 bg-red-500" title="Deadline" />
98
+ )}
99
+ {hasConferences && (
100
+ <div className="h-0.5 flex-1 bg-purple-600" title="Conference" />
101
+ )}
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
102
  </div>
103
  </div>
104
  );
105
  };
106
 
107
  return (
108
+ <div className="min-h-screen bg-neutral-light p-6">
109
+ <div className="max-w-7xl mx-auto">
110
+ <div className="flex flex-col items-center mb-8">
111
+ <h1 className="text-3xl font-bold mb-4">Calendar Overview</h1>
112
+ <div className="flex items-center gap-4">
113
+ <Toggle
114
+ pressed={!isYearView}
115
+ onPressedChange={() => setIsYearView(false)}
116
+ variant="outline"
117
+ >
118
+ Month
119
+ </Toggle>
120
+ <Toggle
121
+ pressed={isYearView}
122
+ onPressedChange={() => setIsYearView(true)}
123
+ variant="outline"
124
+ >
125
+ Year
126
+ </Toggle>
127
+ </div>
128
+ </div>
129
+
130
+ <div className="flex justify-center gap-6 mb-6">
131
+ <div className="flex items-center gap-2">
132
+ <div className="w-4 h-1 bg-purple-600" />
133
+ <span>Conference Dates</span>
134
+ </div>
135
+ <div className="flex items-center gap-2">
136
+ <div className="w-4 h-1 bg-red-500" />
137
+ <span>Submission Deadlines</span>
138
+ </div>
139
+ </div>
140
+
141
+ <div className="grid grid-cols-1 gap-8">
142
+ <div className="mx-auto w-full max-w-4xl">
143
  <Calendar
144
  mode="single"
145
  selected={selectedDate}
146
  onSelect={setSelectedDate}
147
+ numberOfMonths={isYearView ? 12 : 1}
148
+ className="bg-white rounded-lg p-6 shadow-sm mx-auto w-full"
149
+ components={{
150
+ Day: ({ date, ...props }) => (
151
+ <button {...props} className="w-full h-full p-2">
152
+ {renderDayContent(date)}
153
+ </button>
154
  ),
 
 
 
155
  }}
 
156
  classNames={{
157
+ months: `grid ${isYearView ? 'grid-cols-3 gap-4' : ''} justify-center`,
158
+ month: "space-y-4",
159
  caption: "flex justify-center pt-1 relative items-center mb-4",
160
  caption_label: "text-lg font-semibold",
161
  table: "w-full border-collapse space-y-1",
162
  head_row: "flex",
163
+ head_cell: "text-muted-foreground rounded-md w-10 font-normal text-[0.8rem]",
164
  row: "flex w-full mt-2",
165
+ cell: `h-10 w-10 text-center text-sm p-0 relative focus-within:relative focus-within:z-20
166
+ [&:has([aria-selected])]:bg-accent first:[&:has([aria-selected])]:rounded-l-md
167
+ last:[&:has([aria-selected])]:rounded-r-md hover:bg-neutral-50`,
168
+ day: "h-10 w-10 p-0 font-normal hover:bg-neutral-100 rounded-lg transition-colors",
169
  day_today: "bg-neutral-100 text-primary font-semibold",
170
  nav: "space-x-1 flex items-center",
171
  nav_button: "h-7 w-7 bg-transparent p-0 opacity-50 hover:opacity-100",
172
  nav_button_previous: "absolute left-1",
173
  nav_button_next: "absolute right-1",
174
  }}
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
175
  />
 
 
 
 
 
 
 
 
 
 
 
 
 
176
  </div>
177
 
178
+ {selectedDate && events.length > 0 && (
179
+ <div className="mx-auto w-full max-w-3xl space-y-4">
180
+ <h2 className="text-xl font-semibold flex items-center gap-2">
181
+ <CalendarIcon className="h-5 w-5" />
182
+ Events in {format(selectedDate, isYearView ? 'yyyy' : 'MMMM yyyy')}
183
+ </h2>
184
+ <div className="space-y-4">
185
+ {events.map((conf: Conference) => (
186
+ <div key={conf.id} className="bg-white p-4 rounded-lg shadow-sm">
187
+ <h3 className="font-semibold text-lg">{conf.title}</h3>
188
+ <div className="space-y-1">
189
+ {conf.deadline && safeParseISO(conf.deadline) && (
190
+ <p className="text-red-500">
191
+ Submission Deadline: {format(safeParseISO(conf.deadline)!, 'MMMM d, yyyy')}
192
+ </p>
193
+ )}
194
+ {conf.start && (
195
+ <p className="text-purple-600">
196
+ Conference Date: {format(safeParseISO(conf.start)!, 'MMMM d')}
197
+ {conf.end ? ` - ${format(safeParseISO(conf.end)!, 'MMMM d, yyyy')}` :
198
+ `, ${format(safeParseISO(conf.start)!, 'yyyy')}`}
199
+ </p>
200
+ )}
201
+ </div>
202
+ <div className="mt-2 flex flex-wrap gap-2">
203
+ {conf.tags.map((tag) => (
204
+ <span key={tag} className="inline-flex items-center px-2 py-1 rounded-full
205
+ text-xs bg-neutral-100">
206
+ <Tag className="h-3 w-3 mr-1" />
207
+ {tag}
208
+ </span>
209
+ ))}
210
+ </div>
211
+ </div>
212
+ ))}
213
+ </div>
214
+ </div>
215
+ )}
216
  </div>
217
  </div>
218
  </div>