- Add DomainsModule with full CRUD, search, and activity logging - Add IdeasModule with quick capture endpoint - Add LayoutsModule for user dashboard layouts - Add WidgetsModule for widget definitions (read-only) - Update ActivityService with domain/idea logging methods - Register all new modules in AppModule
67 lines
2.3 KiB
TypeScript
67 lines
2.3 KiB
TypeScript
import type { Event } from "@mosaic/shared";
|
|
import { formatTime, formatDate } from "@/lib/utils/date-format";
|
|
import Link from "next/link";
|
|
|
|
interface UpcomingEventsWidgetProps {
|
|
events: Event[];
|
|
isLoading: boolean;
|
|
}
|
|
|
|
export function UpcomingEventsWidget({ events, isLoading }: UpcomingEventsWidgetProps) {
|
|
if (isLoading) {
|
|
return (
|
|
<div className="bg-white rounded-lg shadow-sm border border-gray-200 p-6">
|
|
<div className="flex justify-center items-center">
|
|
<div className="animate-spin rounded-full h-6 w-6 border-b-2 border-gray-900"></div>
|
|
<span className="ml-3 text-gray-600">Loading events...</span>
|
|
</div>
|
|
</div>
|
|
);
|
|
}
|
|
|
|
const upcomingEvents = events.slice(0, 4);
|
|
|
|
return (
|
|
<div className="bg-white rounded-lg shadow-sm border border-gray-200 p-6">
|
|
<div className="flex items-center justify-between mb-4">
|
|
<h2 className="text-lg font-semibold text-gray-900">Upcoming Events</h2>
|
|
<Link
|
|
href="/calendar"
|
|
className="text-sm text-blue-600 hover:text-blue-700"
|
|
>
|
|
View calendar →
|
|
</Link>
|
|
</div>
|
|
{upcomingEvents.length === 0 ? (
|
|
<p className="text-sm text-gray-500 text-center py-4">No upcoming events</p>
|
|
) : (
|
|
<div className="space-y-3">
|
|
{upcomingEvents.map((event) => (
|
|
<div
|
|
key={event.id}
|
|
className="flex items-start gap-3 p-3 rounded-lg border-l-4 border-blue-500 bg-gray-50"
|
|
>
|
|
<div className="flex-shrink-0 text-center min-w-[3.5rem]">
|
|
<div className="text-xs text-gray-500 uppercase font-semibold">
|
|
{formatDate(event.startTime).split(',')[0]}
|
|
</div>
|
|
<div className="text-sm font-medium text-gray-900">
|
|
{formatTime(event.startTime)}
|
|
</div>
|
|
</div>
|
|
<div className="flex-1 min-w-0">
|
|
<h3 className="font-medium text-gray-900 text-sm truncate">
|
|
{event.title}
|
|
</h3>
|
|
{event.location && (
|
|
<p className="text-xs text-gray-500 mt-0.5">📍 {event.location}</p>
|
|
)}
|
|
</div>
|
|
</div>
|
|
))}
|
|
</div>
|
|
)}
|
|
</div>
|
|
);
|
|
}
|