feat: add domains, ideas, layouts, widgets API modules
- 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
This commit is contained in:
141
apps/web/src/components/widgets/CalendarWidget.tsx
Normal file
141
apps/web/src/components/widgets/CalendarWidget.tsx
Normal file
@@ -0,0 +1,141 @@
|
||||
/**
|
||||
* Calendar Widget - displays upcoming events
|
||||
*/
|
||||
|
||||
import { useState, useEffect } from "react";
|
||||
import { Calendar as CalendarIcon, Clock, MapPin } from "lucide-react";
|
||||
import type { WidgetProps } from "@mosaic/shared";
|
||||
|
||||
interface Event {
|
||||
id: string;
|
||||
title: string;
|
||||
startTime: string;
|
||||
endTime?: string;
|
||||
location?: string;
|
||||
allDay: boolean;
|
||||
}
|
||||
|
||||
export function CalendarWidget({ id: _id, config: _config }: WidgetProps) {
|
||||
const [events, setEvents] = useState<Event[]>([]);
|
||||
const [isLoading, setIsLoading] = useState(true);
|
||||
|
||||
// Mock data for now - will fetch from API later
|
||||
useEffect(() => {
|
||||
setIsLoading(true);
|
||||
const now = new Date();
|
||||
const today = new Date(now.getFullYear(), now.getMonth(), now.getDate());
|
||||
const tomorrow = new Date(today);
|
||||
tomorrow.setDate(tomorrow.getDate() + 1);
|
||||
|
||||
setTimeout(() => {
|
||||
setEvents([
|
||||
{
|
||||
id: "1",
|
||||
title: "Team Standup",
|
||||
startTime: new Date(today.setHours(9, 0, 0, 0)).toISOString(),
|
||||
endTime: new Date(today.setHours(9, 30, 0, 0)).toISOString(),
|
||||
location: "Zoom",
|
||||
allDay: false,
|
||||
},
|
||||
{
|
||||
id: "2",
|
||||
title: "Project Review",
|
||||
startTime: new Date(today.setHours(14, 0, 0, 0)).toISOString(),
|
||||
endTime: new Date(today.setHours(15, 0, 0, 0)).toISOString(),
|
||||
location: "Conference Room A",
|
||||
allDay: false,
|
||||
},
|
||||
{
|
||||
id: "3",
|
||||
title: "Sprint Planning",
|
||||
startTime: new Date(tomorrow.setHours(10, 0, 0, 0)).toISOString(),
|
||||
endTime: new Date(tomorrow.setHours(12, 0, 0, 0)).toISOString(),
|
||||
allDay: false,
|
||||
},
|
||||
]);
|
||||
setIsLoading(false);
|
||||
}, 500);
|
||||
}, []);
|
||||
|
||||
const formatTime = (dateString: string) => {
|
||||
const date = new Date(dateString);
|
||||
return date.toLocaleTimeString("en-US", {
|
||||
hour: "numeric",
|
||||
minute: "2-digit",
|
||||
hour12: true,
|
||||
});
|
||||
};
|
||||
|
||||
const formatDay = (dateString: string) => {
|
||||
const date = new Date(dateString);
|
||||
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 = () => {
|
||||
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>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user