- {r.name} -
- {address && ( -
-
+ {r.name} +
+ {address && ( +
+
Coming Up
+Needs Attention
- {task.title} -
-- {task.residence_name} -
-+ {task.title} +
++ {task.residence_name} +
+
Welcome to Casera{name ? `, ${name}` : ""}
@@ -338,10 +328,18 @@ export default function DashboardPage() {
);
}
+ /* ─── Quick action pill data ─── */
+ const quickActions = [
+ { label: "Add task", href: `${basePath}/tasks/new`, icon: CheckSquare },
+ { label: "Add pro", href: `${basePath}/contractors/new`, icon: HardHat },
+ { label: "Save doc", href: `${basePath}/documents/new`, icon: FileText },
+ { label: "Add home", href: `${basePath}/residences/new`, icon: Home },
+ ];
+
/* ─── Main dashboard ─── */
return (
- {/* Greeting */}
+ {/* Hero — Greeting + inline quick actions */}
{greeting}
@@ -349,9 +347,24 @@ export default function DashboardPage() {
{statusMsg && (
{statusMsg}
)}
+
+ {quickActions.map((a) => (
+
+
+ {a.label}
+
+ ))}
+
- {/* Your Homes — the main content */}
+ {/* Needs Attention — overdue and upcoming tasks */}
+
+
+ {/* Your Homes — dropped to #3 */}
Your Homes
@@ -364,7 +377,7 @@ export default function DashboardPage() {
-
- {/* Coming Up — clean task list */}
-
-
- {/* Quick actions — subtle pills at the bottom */}
-
+ {/* Template suggestions — at the bottom */}
+ 0} activeTaskCount={allTasks.length} />
);
}
diff --git a/src/app/app/residences/[id]/page.tsx b/src/app/app/residences/[id]/page.tsx
index 7f876d8..c0f4de1 100644
--- a/src/app/app/residences/[id]/page.tsx
+++ b/src/app/app/residences/[id]/page.tsx
@@ -2,7 +2,9 @@
import { use, useState } from "react";
import { useRouter } from "next/navigation";
-import { MapPin, Pencil, Share2, Trash2, FileDown } from "lucide-react";
+import dynamic from "next/dynamic";
+import Link from "next/link";
+import { MapPin, Pencil, Share2, Trash2, FileDown, Plus, ClipboardList, ArrowRight } from "lucide-react";
import { toast } from "sonner";
import { useDataProvider } from "@/lib/demo/data-provider-context";
@@ -13,7 +15,13 @@ import { LoadingSkeleton } from "@/components/shared/loading-skeleton";
import { ErrorBanner } from "@/components/shared/error-banner";
import { ConfirmDialog } from "@/components/shared/confirm-dialog";
import { ResidenceSummary } from "@/components/residences/residence-summary";
-import { useResidence, useResidences, useDeleteResidence } from "@/lib/hooks/use-residences";
+import { useResidence, useDeleteResidence } from "@/lib/hooks/use-residences";
+import { useTasksByResidence } from "@/lib/hooks/use-tasks";
+
+const KanbanBoard = dynamic(
+ () => import("@/components/tasks/kanban-board").then((mod) => ({ default: mod.KanbanBoard })),
+ { loading: () => }
+);
interface ResidenceDetailPageProps {
params: Promise<{ id: string }>;
@@ -26,8 +34,8 @@ export default function ResidenceDetailPage({ params }: ResidenceDetailPageProps
const { basePath, sharing } = useDataProvider();
const { data: residence, isLoading, error, refetch } = useResidence(id);
- const { data: residences } = useResidences();
const deleteResidence = useDeleteResidence();
+ const { data: kanbanData, isLoading: tasksLoading } = useTasksByResidence(id);
const [deleteOpen, setDeleteOpen] = useState(false);
const [reportLoading, setReportLoading] = useState(false);
@@ -49,10 +57,15 @@ export default function ResidenceDetailPage({ params }: ResidenceDetailPageProps
}
};
- // Find the task summary from the residences list
- const resList = Array.isArray(residences) ? residences : [];
- const myResidence = resList.find((r) => r.residence.id === id);
- const taskSummary = myResidence?.task_summary;
+ // Compute task summary directly from kanban columns (always accurate)
+ const taskSummary = kanbanData
+ ? {
+ total: kanbanData.columns.reduce((sum, col) => sum + col.tasks.length, 0),
+ in_progress: kanbanData.columns.find((c) => c.name === "in_progress_tasks")?.tasks.length ?? 0,
+ overdue: kanbanData.columns.find((c) => c.name === "overdue_tasks")?.tasks.length ?? 0,
+ completed: kanbanData.columns.find((c) => c.name === "completed_tasks")?.tasks.length ?? 0,
+ }
+ : undefined;
if (isLoading) {
return (
@@ -152,10 +165,55 @@ export default function ResidenceDetailPage({ params }: ResidenceDetailPageProps
)}
+ {/* Tasks Kanban Board */}
+
+
+
+ Tasks
+
+ View all
+
+
+
+
+
+
+ {tasksLoading && }
+
+ {!tasksLoading && kanbanData && kanbanData.columns.every((col) => col.tasks.length === 0) && (
+
+
+ No tasks yet
+
+ Add your first task to start tracking what needs to get done around this home.
+
+
+
+ )}
+
+ {!tasksLoading && kanbanData && !kanbanData.columns.every((col) => col.tasks.length === 0) && (
+
+ )}
+
+
{/* Description */}
{residence.description && (
diff --git a/src/app/app/residences/page.tsx b/src/app/app/residences/page.tsx
index 39e10a0..c3e5ec1 100644
--- a/src/app/app/residences/page.tsx
+++ b/src/app/app/residences/page.tsx
@@ -45,7 +45,7 @@ export default function ResidencesPage() {
)}
{!isLoading && !error && Array.isArray(residences) && residences.length > 0 && (
-
+
{residences.map((item) => (
))}
diff --git a/src/app/app/tasks/new/page.tsx b/src/app/app/tasks/new/page.tsx
index 0286b0d..5857dd0 100644
--- a/src/app/app/tasks/new/page.tsx
+++ b/src/app/app/tasks/new/page.tsx
@@ -1,6 +1,6 @@
"use client";
-import { useRouter } from "next/navigation";
+import { useRouter, useSearchParams } from "next/navigation";
import { toast } from "sonner";
import { PageHeader } from "@/components/shared/page-header";
import { Card, CardContent } from "@/components/ui/card";
@@ -10,9 +10,13 @@ import { useDataProvider } from "@/lib/demo/data-provider-context";
export default function NewTaskPage() {
const router = useRouter();
+ const searchParams = useSearchParams();
const { basePath } = useDataProvider();
const createTask = useCreateTask();
+ const residenceId = searchParams.get("residence_id");
+ const defaultResidenceId = residenceId ? Number(residenceId) : undefined;
+
return (
@@ -20,11 +24,16 @@ export default function NewTaskPage() {
{
createTask.mutate(data, {
onSuccess: () => {
toast.success("Task created");
- router.push(`${basePath}/tasks`);
+ if (defaultResidenceId) {
+ router.push(`${basePath}/residences/${defaultResidenceId}`);
+ } else {
+ router.push(`${basePath}/tasks`);
+ }
},
onError: () => {
toast.error("Failed to create task");
diff --git a/src/app/demo/app/layout.tsx b/src/app/demo/app/layout.tsx
index 8e4ff1c..3bfa11a 100644
--- a/src/app/demo/app/layout.tsx
+++ b/src/app/demo/app/layout.tsx
@@ -10,10 +10,11 @@ export default function DemoAppLayout({ children }: { children: React.ReactNode
return (
+
-
+
{children}
diff --git a/src/components/contractors/contractor-card.tsx b/src/components/contractors/contractor-card.tsx
index f5471de..6bb84f6 100644
--- a/src/components/contractors/contractor-card.tsx
+++ b/src/components/contractors/contractor-card.tsx
@@ -5,6 +5,7 @@ import { Phone, Mail, Star } from "lucide-react";
import { Badge } from "@/components/ui/badge";
import { Button } from "@/components/ui/button";
import { useDataProvider } from "@/lib/demo/data-provider-context";
+
import type { ContractorResponse } from "@/lib/api/contractors";
interface ContractorCardProps {
@@ -15,18 +16,23 @@ interface ContractorCardProps {
export function ContractorCard({ contractor, onToggleFavorite }: ContractorCardProps) {
const { basePath } = useDataProvider();
return (
-
+
-
-
- {contractor.name}
-
- {contractor.company && (
- {contractor.company}
- )}
+
+
+ {contractor.name[0]?.toUpperCase()}
+
+
+
+ {contractor.name}
+
+ {contractor.company && (
+ {contractor.company}
+ )}
+
{greeting} @@ -349,9 +347,24 @@ export default function DashboardPage() { {statusMsg && (
{statusMsg}
)} +Your Homes
@@ -364,7 +377,7 @@ export default function DashboardPage() {Tasks
+ + View all +No tasks yet
++ Add your first task to start tracking what needs to get done around this home. +
+ +{contractor.company}
- )} +{contractor.company}
+ )} +