Spaces:
Running
Running
File size: 4,555 Bytes
bfd3ad3 38de65f bfd3ad3 a2085ce f46336a 38de65f f46336a 38de65f f46336a 38de65f a2085ce 38de65f a2085ce 38de65f bfd3ad3 38de65f bfd3ad3 a2085ce 1685b02 a2085ce 1685b02 f46336a a2085ce 6eb5e90 1685b02 6eb5e90 a2085ce 6eb5e90 a2085ce f46336a a2085ce 38de65f a2085ce 38de65f a2085ce 1685b02 a2085ce 1685b02 a2085ce 1685b02 a2085ce f46336a |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 |
import { CalendarDays, Globe, Tag, Clock, AlarmClock } from "lucide-react";
import { Conference } from "@/types/conference";
import { formatDistanceToNow, parseISO, isValid } from "date-fns";
import ConferenceDialog from "./ConferenceDialog";
import { useState } from "react";
const ConferenceCard = ({
title,
full_name,
date,
place,
deadline,
timezone,
tags = [],
link,
note,
abstract_deadline,
...conferenceProps
}: Conference) => {
const [dialogOpen, setDialogOpen] = useState(false);
const deadlineDate = deadline && deadline !== 'TBD' ? parseISO(deadline) : null;
const daysLeft = deadlineDate && isValid(deadlineDate) ? formatDistanceToNow(deadlineDate, { addSuffix: true }) : 'TBD';
// Determine countdown color based on days remaining
const getCountdownColor = () => {
if (!deadlineDate || !isValid(deadlineDate)) return "text-neutral-600";
const daysRemaining = Math.ceil((deadlineDate.getTime() - new Date().getTime()) / (1000 * 60 * 60 * 24));
if (daysRemaining <= 7) return "text-red-600";
if (daysRemaining <= 30) return "text-orange-600";
return "text-green-600";
};
const handleCardClick = (e: React.MouseEvent) => {
if (!(e.target as HTMLElement).closest('a') &&
!(e.target as HTMLElement).closest('.tag-button')) {
setDialogOpen(true);
}
};
const handleTagClick = (e: React.MouseEvent, tag: string) => {
e.stopPropagation();
const searchParams = new URLSearchParams(window.location.search);
const currentTags = searchParams.get('tags')?.split(',') || [];
let newTags;
if (currentTags.includes(tag)) {
newTags = currentTags.filter(t => t !== tag);
} else {
newTags = [...currentTags, tag];
}
if (newTags.length > 0) {
searchParams.set('tags', newTags.join(','));
} else {
searchParams.delete('tags');
}
const newUrl = `${window.location.pathname}${searchParams.toString() ? `?${searchParams.toString()}` : ''}`;
window.history.pushState({}, '', newUrl);
window.dispatchEvent(new CustomEvent('urlchange', { detail: { tag } }));
};
return (
<>
<div
className="bg-white rounded-lg shadow-sm hover:shadow-md transition-shadow p-4 flex flex-col cursor-pointer"
onClick={handleCardClick}
>
<div className="flex justify-between items-start mb-2">
<h3 className="text-lg font-semibold text-primary">{title}</h3>
{link && (
<a
href={link}
target="_blank"
rel="noopener noreferrer"
className="hover:underline"
onClick={(e) => e.stopPropagation()}
>
<Globe className="h-4 w-4 mr-2 flex-shrink-0" />
</a>
)}
</div>
<div className="flex flex-col gap-2 mb-3">
<div className="flex items-center text-neutral">
<CalendarDays className="h-4 w-4 mr-2 flex-shrink-0" />
<span className="text-sm truncate">{date}</span>
</div>
<div className="flex items-center text-neutral">
<Globe className="h-4 w-4 mr-2 flex-shrink-0" />
<span className="text-sm truncate">{place}</span>
</div>
<div className="flex items-center text-neutral">
<Clock className="h-4 w-4 mr-2 flex-shrink-0" />
<span className="text-sm truncate">
{deadline === 'TBD' ? 'TBD' : deadline}
</span>
</div>
<div className="flex items-center">
<AlarmClock className={`h-4 w-4 mr-2 flex-shrink-0 ${getCountdownColor()}`} />
<span className={`text-sm font-medium truncate ${getCountdownColor()}`}>
{daysLeft}
</span>
</div>
</div>
{Array.isArray(tags) && tags.length > 0 && (
<div className="flex flex-wrap gap-2">
{tags.map((tag) => (
<button
key={tag}
className="tag tag-button"
onClick={(e) => handleTagClick(e, tag)}
>
<Tag className="h-3 w-3 mr-1" />
{tag}
</button>
))}
</div>
)}
</div>
<ConferenceDialog
conference={{ title, full_name, date, place, deadline, timezone, tags, link, note, abstract_deadline, ...conferenceProps }}
open={dialogOpen}
onOpenChange={setDialogOpen}
/>
</>
);
};
export default ConferenceCard;
|