Spaces:
Running
Running
Merge pull request #118 from laitifranz/feature/deadline-days-countdown
Browse files
src/components/ConferenceDialog.tsx
CHANGED
|
@@ -17,7 +17,7 @@ import {
|
|
| 17 |
} from "@/components/ui/dropdown-menu";
|
| 18 |
import { useState, useEffect } from "react";
|
| 19 |
import { getDeadlineInLocalTime } from '@/utils/dateUtils';
|
| 20 |
-
import { getAllDeadlines, getNextUpcomingDeadline, getUpcomingDeadlines } from '@/utils/deadlineUtils';
|
| 21 |
|
| 22 |
interface ConferenceDialogProps {
|
| 23 |
conference: Conference;
|
|
@@ -218,7 +218,7 @@ END:VCALENDAR`;
|
|
| 218 |
return (
|
| 219 |
<Dialog open={open} onOpenChange={onOpenChange}>
|
| 220 |
<DialogContent
|
| 221 |
-
className="max-w-
|
| 222 |
>
|
| 223 |
<DialogHeader>
|
| 224 |
<DialogTitle className="text-2xl font-bold text-blue-600">
|
|
@@ -257,15 +257,24 @@ END:VCALENDAR`;
|
|
| 257 |
{upcomingDeadlines.length > 0 ? (
|
| 258 |
upcomingDeadlines.map((deadline, index) => {
|
| 259 |
const isNext = nextDeadline && deadline.date === nextDeadline.date && deadline.type === nextDeadline.type;
|
|
|
|
|
|
|
| 260 |
return (
|
| 261 |
<div
|
| 262 |
key={`${deadline.type}-${index}`}
|
| 263 |
className={`rounded-md p-2 ${isNext ? 'bg-blue-100 border border-blue-200' : 'bg-gray-100'}`}
|
| 264 |
>
|
| 265 |
-
<
|
| 266 |
-
{
|
| 267 |
-
|
| 268 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 269 |
</div>
|
| 270 |
);
|
| 271 |
})
|
|
|
|
| 17 |
} from "@/components/ui/dropdown-menu";
|
| 18 |
import { useState, useEffect } from "react";
|
| 19 |
import { getDeadlineInLocalTime } from '@/utils/dateUtils';
|
| 20 |
+
import { getAllDeadlines, getNextUpcomingDeadline, getUpcomingDeadlines, getDaysRemaining, getCountdownColorClass } from '@/utils/deadlineUtils';
|
| 21 |
|
| 22 |
interface ConferenceDialogProps {
|
| 23 |
conference: Conference;
|
|
|
|
| 218 |
return (
|
| 219 |
<Dialog open={open} onOpenChange={onOpenChange}>
|
| 220 |
<DialogContent
|
| 221 |
+
className="max-w-lg w-full"
|
| 222 |
>
|
| 223 |
<DialogHeader>
|
| 224 |
<DialogTitle className="text-2xl font-bold text-blue-600">
|
|
|
|
| 257 |
{upcomingDeadlines.length > 0 ? (
|
| 258 |
upcomingDeadlines.map((deadline, index) => {
|
| 259 |
const isNext = nextDeadline && deadline.date === nextDeadline.date && deadline.type === nextDeadline.type;
|
| 260 |
+
const daysRemaining = getDaysRemaining(deadline, conference.timezone);
|
| 261 |
+
const daysColorClass = getCountdownColorClass(daysRemaining);
|
| 262 |
return (
|
| 263 |
<div
|
| 264 |
key={`${deadline.type}-${index}`}
|
| 265 |
className={`rounded-md p-2 ${isNext ? 'bg-blue-100 border border-blue-200' : 'bg-gray-100'}`}
|
| 266 |
>
|
| 267 |
+
<div className="flex items-center justify-between gap-2">
|
| 268 |
+
<p className={`flex-1 ${isNext ? 'font-medium text-blue-800' : ''}`}>
|
| 269 |
+
{deadline.label}: {formatDeadlineDate(deadline.date)}
|
| 270 |
+
{isNext && <span className="ml-2 text-xs">(Next)</span>}
|
| 271 |
+
</p>
|
| 272 |
+
{daysRemaining !== null && daysRemaining > 0 && (
|
| 273 |
+
<span className={`text-xs font-medium whitespace-nowrap ${daysColorClass}`}>
|
| 274 |
+
{daysRemaining} {daysRemaining === 1 ? 'day' : 'days'}
|
| 275 |
+
</span>
|
| 276 |
+
)}
|
| 277 |
+
</div>
|
| 278 |
</div>
|
| 279 |
);
|
| 280 |
})
|
src/utils/deadlineUtils.ts
CHANGED
|
@@ -193,3 +193,27 @@ export function getUpcomingDeadlines(conference: Conference): Deadline[] {
|
|
| 193 |
|
| 194 |
return upcomingDeadlines;
|
| 195 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 193 |
|
| 194 |
return upcomingDeadlines;
|
| 195 |
}
|
| 196 |
+
|
| 197 |
+
/**
|
| 198 |
+
* Get the number of days remaining until a deadline
|
| 199 |
+
* Returns null if the deadline date is invalid
|
| 200 |
+
*/
|
| 201 |
+
export function getDaysRemaining(deadline: Deadline, fallbackTimezone?: string): number | null {
|
| 202 |
+
const deadlineDate = getDeadlineInLocalTime(deadline.date, deadline.timezone || fallbackTimezone);
|
| 203 |
+
if (!deadlineDate || !isValid(deadlineDate)) return null;
|
| 204 |
+
|
| 205 |
+
const now = new Date();
|
| 206 |
+
const diff = deadlineDate.getTime() - now.getTime();
|
| 207 |
+
return Math.ceil(diff / (1000 * 60 * 60 * 24));
|
| 208 |
+
}
|
| 209 |
+
|
| 210 |
+
/**
|
| 211 |
+
* Get the color class for a countdown based on days remaining
|
| 212 |
+
*/
|
| 213 |
+
export function getCountdownColorClass(daysRemaining: number | null): string {
|
| 214 |
+
if (daysRemaining === null) return "text-neutral-600";
|
| 215 |
+
if (daysRemaining <= 0) return "text-neutral-600";
|
| 216 |
+
if (daysRemaining <= 7) return "text-red-600";
|
| 217 |
+
if (daysRemaining <= 30) return "text-orange-600";
|
| 218 |
+
return "text-green-600";
|
| 219 |
+
}
|