Files
stack/apps/web/src/components/widgets/CalendarWidget.tsx
Jason Woltje 5b77774d91
All checks were successful
ci/woodpecker/push/ci Pipeline was successful
fix(web): remove mock data from dashboard telemetry/tasks/calendar (#656)
Co-authored-by: Jason Woltje <jason@diversecanvas.com>
Co-committed-by: Jason Woltje <jason@diversecanvas.com>
2026-03-02 14:19:27 +00:00

124 lines
3.7 KiB
TypeScript

/**
* Calendar Widget - displays upcoming events
*/
import { useState, useEffect } from "react";
import { Calendar as CalendarIcon, Clock, MapPin } from "lucide-react";
import type { WidgetProps, Event } from "@mosaic/shared";
import { fetchEvents } from "@/lib/api/events";
export function CalendarWidget({ id: _id, config: _config }: WidgetProps): React.JSX.Element {
const [events, setEvents] = useState<Event[]>([]);
const [isLoading, setIsLoading] = useState(true);
useEffect(() => {
let isMounted = true;
const loadEvents = async (): Promise<void> => {
setIsLoading(true);
try {
const data = await fetchEvents();
if (isMounted) {
setEvents(data);
}
} catch {
if (isMounted) {
setEvents([]);
}
} finally {
if (isMounted) {
setIsLoading(false);
}
}
};
void loadEvents();
return (): void => {
isMounted = false;
};
}, []);
const formatTime = (dateValue: Date | string): string => {
const date = new Date(dateValue);
return date.toLocaleTimeString("en-US", {
hour: "numeric",
minute: "2-digit",
hour12: true,
});
};
const formatDay = (dateValue: Date | string): string => {
const date = new Date(dateValue);
const today = new Date();
const tomorrow = new Date(today);
tomorrow.setDate(tomorrow.getDate() + 1);
if (date.toDateString() === today.toDateString()) {
return "Today";
} else if (date.toDateString() === tomorrow.toDateString()) {
return "Tomorrow";
}
return date.toLocaleDateString("en-US", { weekday: "short", month: "short", day: "numeric" });
};
const getUpcomingEvents = (): Event[] => {
const now = new Date();
return events
.filter((e) => new Date(e.startTime) > now)
.sort((a, b) => new Date(a.startTime).getTime() - new Date(b.startTime).getTime())
.slice(0, 5);
};
if (isLoading) {
return (
<div className="flex items-center justify-center h-full">
<div className="text-gray-500 text-sm">Loading events...</div>
</div>
);
}
const upcomingEvents = getUpcomingEvents();
return (
<div className="flex flex-col h-full space-y-3">
{/* Header */}
<div className="flex items-center gap-2 text-gray-700">
<CalendarIcon className="w-4 h-4" />
<span className="text-sm font-medium">Upcoming Events</span>
</div>
{/* Event list */}
<div className="flex-1 overflow-auto space-y-3">
{upcomingEvents.length === 0 ? (
<div className="text-center text-gray-500 text-sm py-4">No upcoming events</div>
) : (
upcomingEvents.map((event) => (
<div key={event.id} className="border-l-2 border-blue-500 pl-3 py-1">
<div className="text-sm font-medium text-gray-900">{event.title}</div>
<div className="flex items-center gap-3 mt-1 text-xs text-gray-500">
{!event.allDay && (
<div className="flex items-center gap-1">
<Clock className="w-3 h-3" />
<span>
{formatTime(event.startTime)}
{event.endTime && ` - ${formatTime(event.endTime)}`}
</span>
</div>
)}
{event.location && (
<div className="flex items-center gap-1">
<MapPin className="w-3 h-3" />
<span>{event.location}</span>
</div>
)}
<div className="text-gray-400">{formatDay(event.startTime)}</div>
</div>
</div>
))
)}
</div>
</div>
);
}