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:
Jason Woltje
2026-01-29 13:47:03 -06:00
parent 973502f26e
commit f47dd8bc92
66 changed files with 4277 additions and 29 deletions

View 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>
);
}