Spaces:
Running
Running
Improve lines
Browse files- src/pages/Calendar.tsx +99 -61
src/pages/Calendar.tsx
CHANGED
|
@@ -170,73 +170,111 @@ const CalendarPage = () => {
|
|
| 170 |
);
|
| 171 |
};
|
| 172 |
|
| 173 |
-
|
| 174 |
-
|
| 175 |
-
|
| 176 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 177 |
|
| 178 |
-
|
| 179 |
<div className="relative w-full h-full flex flex-col items-center">
|
| 180 |
-
|
| 181 |
-
<div className="
|
| 182 |
-
{
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 183 |
<div
|
| 184 |
-
|
| 185 |
-
|
| 186 |
/>
|
| 187 |
-
)}
|
| 188 |
-
{
|
| 189 |
-
|
| 190 |
-
const endDate = safeParseISO(conf.end);
|
| 191 |
-
|
| 192 |
-
if (!startDate || !endDate) return null;
|
| 193 |
-
|
| 194 |
-
const categoryColor = conf.tags?.[0] ? categoryColors[conf.tags[0]] || "bg-purple-600" : "bg-purple-600";
|
| 195 |
-
|
| 196 |
-
if (day < startDate || day > endDate) return null;
|
| 197 |
-
|
| 198 |
-
const isFirstDayOfMonth = day.getDate() === 1;
|
| 199 |
-
const isStartDate = isSameDay(startDate, day);
|
| 200 |
-
|
| 201 |
-
const lastDayOfMonth = new Date(day.getFullYear(), day.getMonth() + 1, 0);
|
| 202 |
-
const daysUntilMonthEnd = Math.min(
|
| 203 |
-
Math.ceil((endDate.getTime() - day.getTime()) / (1000 * 60 * 60 * 24)) + 1,
|
| 204 |
-
Math.ceil((lastDayOfMonth.getTime() - day.getTime()) / (1000 * 60 * 60 * 24)) + 1
|
| 205 |
-
);
|
| 206 |
-
|
| 207 |
-
if (!isStartDate && !isFirstDayOfMonth) return null;
|
| 208 |
-
|
| 209 |
-
return (
|
| 210 |
-
<div
|
| 211 |
-
key={`${conf.id}-${format(day, 'yyyy-MM')}`}
|
| 212 |
-
className={`h-0.5 ${categoryColor} absolute`}
|
| 213 |
-
style={{ width: `calc(100% * ${daysUntilMonthEnd})` }}
|
| 214 |
-
title={conf.title}
|
| 215 |
-
/>
|
| 216 |
-
);
|
| 217 |
-
})}
|
| 218 |
</div>
|
| 219 |
-
</div>
|
| 220 |
-
);
|
| 221 |
-
|
| 222 |
-
if (!hasDeadlines && !hasConferences) return content;
|
| 223 |
|
| 224 |
-
|
| 225 |
-
|
| 226 |
-
|
| 227 |
-
|
| 228 |
-
|
| 229 |
-
|
| 230 |
-
|
| 231 |
-
|
| 232 |
-
|
| 233 |
-
|
| 234 |
-
|
| 235 |
-
|
| 236 |
-
{renderEventPreview(dayEvents)}
|
| 237 |
-
</TooltipContent>
|
| 238 |
-
</Tooltip>
|
| 239 |
-
</TooltipProvider>
|
| 240 |
);
|
| 241 |
};
|
| 242 |
|
|
|
|
| 170 |
);
|
| 171 |
};
|
| 172 |
|
| 173 |
+
// Add these helper functions at the top of the file
|
| 174 |
+
const isEndOfWeek = (date: Date) => date.getDay() === 6; // Saturday
|
| 175 |
+
const isStartOfWeek = (date: Date) => date.getDay() === 0; // Sunday
|
| 176 |
+
const isSameWeek = (date1: Date, date2: Date) => {
|
| 177 |
+
const diff = Math.abs(date1.getTime() - date2.getTime());
|
| 178 |
+
const diffDays = Math.floor(diff / (1000 * 60 * 60 * 24));
|
| 179 |
+
return diffDays <= 6 &&
|
| 180 |
+
Math.floor(date1.getTime() / (1000 * 60 * 60 * 24 * 7)) ===
|
| 181 |
+
Math.floor(date2.getTime() / (1000 * 60 * 60 * 24 * 7));
|
| 182 |
+
};
|
| 183 |
+
|
| 184 |
+
// Update the getConferenceLineStyle function
|
| 185 |
+
const getConferenceLineStyle = (date: Date) => {
|
| 186 |
+
const styles = [];
|
| 187 |
+
|
| 188 |
+
for (const conf of conferencesData) {
|
| 189 |
+
const startDate = safeParseISO(conf.start);
|
| 190 |
+
const endDate = safeParseISO(conf.end);
|
| 191 |
+
|
| 192 |
+
if (startDate && endDate && date >= startDate && date <= endDate) {
|
| 193 |
+
const isFirst = isSameDay(date, startDate);
|
| 194 |
+
const isLast = isSameDay(date, endDate);
|
| 195 |
+
|
| 196 |
+
// Check if previous and next days are part of the same conference and week
|
| 197 |
+
const prevDate = new Date(date);
|
| 198 |
+
prevDate.setDate(date.getDate() - 1);
|
| 199 |
+
const nextDate = new Date(date);
|
| 200 |
+
nextDate.setDate(date.getDate() + 1);
|
| 201 |
+
|
| 202 |
+
const hasPrevDay = prevDate >= startDate && isSameWeek(date, prevDate);
|
| 203 |
+
const hasNextDay = nextDate <= endDate && isSameWeek(date, nextDate);
|
| 204 |
+
|
| 205 |
+
let lineStyle = "h-1 absolute bottom-0";
|
| 206 |
+
|
| 207 |
+
if (hasPrevDay && hasNextDay) {
|
| 208 |
+
// Middle of a sequence
|
| 209 |
+
lineStyle += " w-[calc(100%+1rem)] -left-2";
|
| 210 |
+
} else if (hasPrevDay) {
|
| 211 |
+
// End of a sequence
|
| 212 |
+
lineStyle += " w-[calc(100%+0.5rem)] left-0";
|
| 213 |
+
} else if (hasNextDay) {
|
| 214 |
+
// Start of a sequence
|
| 215 |
+
lineStyle += " w-[calc(100%+0.5rem)] right-0";
|
| 216 |
+
} else {
|
| 217 |
+
// Single day
|
| 218 |
+
lineStyle += " w-full";
|
| 219 |
+
}
|
| 220 |
+
|
| 221 |
+
// Get the color based on the first tag or default to purple
|
| 222 |
+
const color = conf.tags?.[0] ? categoryColors[conf.tags[0]] : "bg-purple-500";
|
| 223 |
+
|
| 224 |
+
styles.push({
|
| 225 |
+
style: lineStyle,
|
| 226 |
+
color: color
|
| 227 |
+
});
|
| 228 |
+
}
|
| 229 |
+
}
|
| 230 |
+
|
| 231 |
+
return styles;
|
| 232 |
+
};
|
| 233 |
+
|
| 234 |
+
// Update the renderDayContent function
|
| 235 |
+
const renderDayContent = (date: Date) => {
|
| 236 |
+
const dayEvents = getDayEvents(date);
|
| 237 |
+
const hasEvents = dayEvents.deadlines.length > 0 || dayEvents.conferences.length > 0;
|
| 238 |
+
|
| 239 |
+
// Get conference line styles first
|
| 240 |
+
const conferenceStyles = getConferenceLineStyle(date);
|
| 241 |
+
|
| 242 |
+
// Get deadline style
|
| 243 |
+
const hasDeadline = dayEvents.deadlines.length > 0;
|
| 244 |
+
const deadlineStyle = hasDeadline ? "h-1 w-full bg-red-500" : "";
|
| 245 |
|
| 246 |
+
return (
|
| 247 |
<div className="relative w-full h-full flex flex-col items-center">
|
| 248 |
+
{/* Day number at the top */}
|
| 249 |
+
<div className="flex-grow flex items-center justify-center mb-2">
|
| 250 |
+
<span>{format(date, 'd')}</span>
|
| 251 |
+
</div>
|
| 252 |
+
|
| 253 |
+
{/* Event indicator lines at the bottom */}
|
| 254 |
+
<div className="absolute bottom-0 w-full flex flex-col gap-[2px]">
|
| 255 |
+
{/* Conference lines */}
|
| 256 |
+
{conferenceStyles.map((style, index) => (
|
| 257 |
<div
|
| 258 |
+
key={`conf-${index}`}
|
| 259 |
+
className={`${style.style} ${style.color}`}
|
| 260 |
/>
|
| 261 |
+
))}
|
| 262 |
+
{/* Deadline line */}
|
| 263 |
+
{deadlineStyle && <div className={deadlineStyle} />}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 264 |
</div>
|
|
|
|
|
|
|
|
|
|
|
|
|
| 265 |
|
| 266 |
+
{/* Tooltip trigger covering the whole cell */}
|
| 267 |
+
{hasEvents && (
|
| 268 |
+
<TooltipProvider>
|
| 269 |
+
<Tooltip>
|
| 270 |
+
<TooltipTrigger className="absolute inset-0" />
|
| 271 |
+
<TooltipContent>
|
| 272 |
+
{renderEventPreview(dayEvents)}
|
| 273 |
+
</TooltipContent>
|
| 274 |
+
</Tooltip>
|
| 275 |
+
</TooltipProvider>
|
| 276 |
+
)}
|
| 277 |
+
</div>
|
|
|
|
|
|
|
|
|
|
|
|
|
| 278 |
);
|
| 279 |
};
|
| 280 |
|