feat: consumer home app layout overhaul + residence kanban board
Layout & Navigation: - Tighten max-width to 6xl, adjust padding, add warm gradient overlay - Add icons to desktop nav links, responsive header height, stronger blur - Active pill highlight on mobile nav icons - Fix middleware blocking static assets (logo.png) behind auth Dashboard restructure: - Merge quick actions into hero area as inline pills - Rename "Coming Up" to "Needs Attention", exclude completed tasks - Promote task cards to #2 with richer card design (2-col grid, colored date badges) - Drop "Your Homes" to #3 with accent bars and larger icons Card redesigns: - Residence cards: accent bar, home icon, warm hover shadow - Contractor cards: letter avatar, text contact links, separator - Document cards: type-colored accent bar, restructured footer - Task cards: warm hover shadow - Empty states: larger icon container, gradient bg, rounded CTA Residence detail page: - Add full kanban board with drag-and-drop for task management - Add "Add Task" button pre-filling residence on task form - Replace broken Users stat with Overdue/Completed stats - Compute task summary from kanban columns (always accurate) Data accuracy fixes: - Fix getMyResidences() to fetch kanban data in parallel and compute real per-residence task counts instead of hardcoding zeros - Task form accepts defaultResidenceId prop for pre-filling - New task page reads residence_id from URL, redirects back after create Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -159,13 +159,37 @@ export function listResidences(): Promise<ResidenceResponse[]> {
|
||||
/** Get the user's residences with task summaries. */
|
||||
export async function getMyResidences(): Promise<MyResidenceResponse[]> {
|
||||
// Go API returns { "residences": [ResidenceResponse, ...] }
|
||||
// Each ResidenceResponse has overdue_count but no task_summary.
|
||||
// We transform into MyResidenceResponse shape for compatibility.
|
||||
const data = await apiFetch<{ residences: ResidenceResponse[] }>('/residences/my-residences/');
|
||||
const residences = data.residences ?? [];
|
||||
// Each ResidenceResponse has overdue_count but no full task_summary.
|
||||
// We fetch the kanban data in parallel and compute real per-residence counts.
|
||||
const [resData, kanbanData] = await Promise.all([
|
||||
apiFetch<{ residences: ResidenceResponse[] }>('/residences/my-residences/'),
|
||||
apiFetch<{ columns: { name: string; tasks: { residence_id: number }[] }[] }>('/tasks/').catch(() => null),
|
||||
]);
|
||||
|
||||
const residences = resData.residences ?? [];
|
||||
|
||||
// Build per-residence task counts from kanban columns
|
||||
const countsMap = new Map<number, TaskSummary>();
|
||||
if (kanbanData?.columns) {
|
||||
for (const col of kanbanData.columns) {
|
||||
for (const task of col.tasks) {
|
||||
let c = countsMap.get(task.residence_id);
|
||||
if (!c) {
|
||||
c = { total: 0, overdue: 0, due_soon: 0, in_progress: 0, completed: 0 };
|
||||
countsMap.set(task.residence_id, c);
|
||||
}
|
||||
c.total++;
|
||||
if (col.name === 'overdue_tasks') c.overdue++;
|
||||
else if (col.name === 'due_soon_tasks') c.due_soon++;
|
||||
else if (col.name === 'in_progress_tasks') c.in_progress++;
|
||||
else if (col.name === 'completed_tasks') c.completed++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return residences.map((r) => ({
|
||||
residence: r,
|
||||
task_summary: {
|
||||
task_summary: countsMap.get(r.id) ?? {
|
||||
total: 0,
|
||||
overdue: r.overdue_count ?? 0,
|
||||
due_soon: 0,
|
||||
|
||||
@@ -0,0 +1,19 @@
|
||||
"use client";
|
||||
|
||||
import { useQuery } from "@tanstack/react-query";
|
||||
import {
|
||||
getTaskTemplatesGrouped,
|
||||
type TaskTemplatesGroupedResponse,
|
||||
} from "@/lib/api/lookups";
|
||||
|
||||
/**
|
||||
* Fetches task templates grouped by category.
|
||||
* 30-minute staleTime since templates rarely change.
|
||||
*/
|
||||
export function useTaskTemplatesGrouped() {
|
||||
return useQuery<TaskTemplatesGroupedResponse>({
|
||||
queryKey: ["task-templates", "grouped"],
|
||||
queryFn: () => getTaskTemplatesGrouped(),
|
||||
staleTime: 30 * 60 * 1000, // 30 minutes
|
||||
});
|
||||
}
|
||||
Reference in New Issue
Block a user