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:
86
apps/web/src/components/dashboard/RecentTasksWidget.tsx
Normal file
86
apps/web/src/components/dashboard/RecentTasksWidget.tsx
Normal file
@@ -0,0 +1,86 @@
|
||||
import type { Task } from "@mosaic/shared";
|
||||
import { TaskPriority } from "@mosaic/shared";
|
||||
import { formatDate } from "@/lib/utils/date-format";
|
||||
import { TaskStatus } from "@mosaic/shared";
|
||||
import Link from "next/link";
|
||||
|
||||
interface RecentTasksWidgetProps {
|
||||
tasks: Task[];
|
||||
isLoading: boolean;
|
||||
}
|
||||
|
||||
const statusIcons: Record<TaskStatus, string> = {
|
||||
[TaskStatus.NOT_STARTED]: "⚪",
|
||||
[TaskStatus.IN_PROGRESS]: "🟢",
|
||||
[TaskStatus.PAUSED]: "⏸️",
|
||||
[TaskStatus.COMPLETED]: "✅",
|
||||
[TaskStatus.ARCHIVED]: "💤",
|
||||
};
|
||||
|
||||
export function RecentTasksWidget({ tasks, isLoading }: RecentTasksWidgetProps) {
|
||||
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 tasks...</span>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
const recentTasks = tasks.slice(0, 5);
|
||||
|
||||
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">Recent Tasks</h2>
|
||||
<Link
|
||||
href="/tasks"
|
||||
className="text-sm text-blue-600 hover:text-blue-700"
|
||||
>
|
||||
View all →
|
||||
</Link>
|
||||
</div>
|
||||
{recentTasks.length === 0 ? (
|
||||
<p className="text-sm text-gray-500 text-center py-4">No tasks yet</p>
|
||||
) : (
|
||||
<ul className="space-y-3">
|
||||
{recentTasks.map((task) => (
|
||||
<li
|
||||
key={task.id}
|
||||
className="flex items-start gap-3 p-3 rounded-lg hover:bg-gray-50 transition-colors"
|
||||
>
|
||||
<span className="text-lg flex-shrink-0" aria-label={`Status: ${task.status}`}>
|
||||
{statusIcons[task.status]}
|
||||
</span>
|
||||
<div className="flex-1 min-w-0">
|
||||
<h3 className="font-medium text-gray-900 text-sm truncate">
|
||||
{task.title}
|
||||
</h3>
|
||||
<div className="flex items-center gap-2 mt-1">
|
||||
{task.priority !== TaskPriority.LOW && (
|
||||
<span
|
||||
className={`text-xs px-2 py-0.5 rounded-full ${
|
||||
task.priority === TaskPriority.HIGH
|
||||
? "bg-red-100 text-red-700"
|
||||
: "bg-blue-100 text-blue-700"
|
||||
}`}
|
||||
>
|
||||
{task.priority}
|
||||
</span>
|
||||
)}
|
||||
{task.dueDate && (
|
||||
<span className="text-xs text-gray-500">
|
||||
{formatDate(task.dueDate)}
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user