feat: complete Phase 3 — advanced features for Casera web app
Adds sharing (residence share codes, join, user management, .casera file export/import), subscription status with feature comparison, notification preferences with bell icon, profile settings (edit info, change password, theme picker, delete account), onboarding wizard with create/join paths, enhanced dashboard with stats cards, Recharts completion chart, recent activity feed, and task report PDF download. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,111 @@
|
||||
"use client";
|
||||
|
||||
import { useDroppable } from "@dnd-kit/core";
|
||||
import { SortableContext, verticalListSortingStrategy, useSortable } from "@dnd-kit/sortable";
|
||||
import { CSS } from "@dnd-kit/utilities";
|
||||
import { Badge } from "@/components/ui/badge";
|
||||
import { cn } from "@/lib/utils";
|
||||
import { TaskCard } from "./task-card";
|
||||
import type { KanbanColumn as KanbanColumnType } from "@/lib/api/tasks";
|
||||
|
||||
const COLUMN_COLORS: Record<string, string> = {
|
||||
overdue: "border-red-500/50 bg-red-50/50 dark:bg-red-950/20",
|
||||
due_today: "border-orange-500/50 bg-orange-50/50 dark:bg-orange-950/20",
|
||||
due_soon: "border-yellow-500/50 bg-yellow-50/50 dark:bg-yellow-950/20",
|
||||
upcoming: "border-blue-500/50 bg-blue-50/50 dark:bg-blue-950/20",
|
||||
in_progress: "border-green-500/50 bg-green-50/50 dark:bg-green-950/20",
|
||||
completed: "border-gray-500/50 bg-gray-50/50 dark:bg-gray-950/20",
|
||||
};
|
||||
|
||||
const COLUMN_HEADER_COLORS: Record<string, string> = {
|
||||
overdue: "text-red-700 dark:text-red-400",
|
||||
due_today: "text-orange-700 dark:text-orange-400",
|
||||
due_soon: "text-yellow-700 dark:text-yellow-400",
|
||||
upcoming: "text-blue-700 dark:text-blue-400",
|
||||
in_progress: "text-green-700 dark:text-green-400",
|
||||
completed: "text-gray-700 dark:text-gray-400",
|
||||
};
|
||||
|
||||
const COUNT_BADGE_COLORS: Record<string, string> = {
|
||||
overdue: "bg-red-100 text-red-800 dark:bg-red-900 dark:text-red-200",
|
||||
due_today: "bg-orange-100 text-orange-800 dark:bg-orange-900 dark:text-orange-200",
|
||||
due_soon: "bg-yellow-100 text-yellow-800 dark:bg-yellow-900 dark:text-yellow-200",
|
||||
upcoming: "bg-blue-100 text-blue-800 dark:bg-blue-900 dark:text-blue-200",
|
||||
in_progress: "bg-green-100 text-green-800 dark:bg-green-900 dark:text-green-200",
|
||||
completed: "bg-gray-100 text-gray-800 dark:bg-gray-900 dark:text-gray-200",
|
||||
};
|
||||
|
||||
interface KanbanColumnProps {
|
||||
column: KanbanColumnType;
|
||||
}
|
||||
|
||||
function SortableTask({ task }: { task: import("@/lib/api/tasks").TaskResponse }) {
|
||||
const {
|
||||
attributes,
|
||||
listeners,
|
||||
setNodeRef,
|
||||
transform,
|
||||
transition,
|
||||
isDragging,
|
||||
} = useSortable({ id: task.id });
|
||||
|
||||
const style: React.CSSProperties = {
|
||||
transform: CSS.Transform.toString(transform),
|
||||
transition,
|
||||
opacity: isDragging ? 0.5 : 1,
|
||||
};
|
||||
|
||||
return (
|
||||
<div ref={setNodeRef} style={style} {...attributes} {...listeners}>
|
||||
<TaskCard task={task} isDragging={isDragging} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export function KanbanColumn({ column }: KanbanColumnProps) {
|
||||
const { setNodeRef, isOver } = useDroppable({
|
||||
id: column.name,
|
||||
});
|
||||
|
||||
const taskIds = column.tasks.map((t) => t.id);
|
||||
|
||||
return (
|
||||
<div
|
||||
className={cn(
|
||||
"flex flex-col min-w-[280px] max-w-[320px] rounded-lg border-2 p-3",
|
||||
COLUMN_COLORS[column.name] ?? "border-border bg-muted/30",
|
||||
isOver && "ring-2 ring-primary"
|
||||
)}
|
||||
>
|
||||
<div className="flex items-center gap-2 mb-3">
|
||||
<h3
|
||||
className={cn(
|
||||
"font-semibold text-sm",
|
||||
COLUMN_HEADER_COLORS[column.name]
|
||||
)}
|
||||
>
|
||||
{column.display_name}
|
||||
</h3>
|
||||
<Badge
|
||||
variant="secondary"
|
||||
className={cn("text-xs", COUNT_BADGE_COLORS[column.name])}
|
||||
>
|
||||
{column.count}
|
||||
</Badge>
|
||||
</div>
|
||||
|
||||
<div ref={setNodeRef} className="flex-1 space-y-2 min-h-[60px]">
|
||||
<SortableContext items={taskIds} strategy={verticalListSortingStrategy}>
|
||||
{column.tasks.map((task) => (
|
||||
<SortableTask key={task.id} task={task} />
|
||||
))}
|
||||
</SortableContext>
|
||||
{column.tasks.length === 0 && (
|
||||
<div className="flex items-center justify-center h-[60px] text-xs text-muted-foreground rounded-md border border-dashed">
|
||||
No tasks
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user