2026-03-10 08:17:45 -04:00
|
|
|
|
"use client";
|
|
|
|
|
|
|
|
|
|
|
|
import * as React from "react";
|
|
|
|
|
|
import { useRouter, useSearchParams } from "next/navigation";
|
|
|
|
|
|
import { toast } from "sonner";
|
Unified dashboard redesign: Quicksand font, charcoal theme, merged upload
- Switch font from Geist to Google Quicksand
- Replace indigo/violet primary with charcoal gray (rgb 31,32,29)
- Update gradient-mesh to warm neutral tones (amber/sage)
- Remove sidebar, add fixed top header bar with profile dropdown
- Merge dashboard and upload pages into single table-centric view
- Stat cards now act as clickable table filters with active highlight
- Simplified filter toolbar: search + dropdowns left, export + upload right
- Upload modal with drag-drop, per-file XHR progress, background uploads
- Global drag-and-drop listener opens upload modal from anywhere
- Table rows are clickable (navigate to detail page)
- Right-click column headers for visibility context menu
- Mobile-responsive card layout below md breakpoint
- Uploading row placeholders with animated progress bars
- Floating selection toolbar slides in at bottom on multi-select
- Delete sidebar, dashboard-hero, quick-actions, upload page
Made-with: Cursor
2026-03-11 16:26:56 -04:00
|
|
|
|
|
|
|
|
|
|
import { StatCards, type StatFilter } from "./stat-cards";
|
2026-03-10 08:17:45 -04:00
|
|
|
|
import { Filters } from "./filters";
|
2026-04-09 11:35:58 -04:00
|
|
|
|
import { DataTable, getDefaultColumnVisibility, ALL_TOGGLEABLE_COLUMNS } from "./data-table";
|
Unified dashboard redesign: Quicksand font, charcoal theme, merged upload
- Switch font from Geist to Google Quicksand
- Replace indigo/violet primary with charcoal gray (rgb 31,32,29)
- Update gradient-mesh to warm neutral tones (amber/sage)
- Remove sidebar, add fixed top header bar with profile dropdown
- Merge dashboard and upload pages into single table-centric view
- Stat cards now act as clickable table filters with active highlight
- Simplified filter toolbar: search + dropdowns left, export + upload right
- Upload modal with drag-drop, per-file XHR progress, background uploads
- Global drag-and-drop listener opens upload modal from anywhere
- Table rows are clickable (navigate to detail page)
- Right-click column headers for visibility context menu
- Mobile-responsive card layout below md breakpoint
- Uploading row placeholders with animated progress bars
- Floating selection toolbar slides in at bottom on multi-select
- Delete sidebar, dashboard-hero, quick-actions, upload page
Made-with: Cursor
2026-03-11 16:26:56 -04:00
|
|
|
|
import { SelectionToolbar } from "./selection-toolbar";
|
|
|
|
|
|
import { UploadModal, type UploadingFile } from "./upload-modal";
|
Enhanced table view with cell copy, column toggles, and ClickUp-style toolbar
- Expand ResponseCard type to include all 30+ Prisma fields
- Add 18 new toggleable columns (Personal, Survey, Prayer, Workflow groups)
- ClickUp-style Copy popover in selection toolbar with field toggle switches
- Click-to-copy on individual cells with visual feedback
- Ctrl/Cmd+click column headers to copy column values
- Columns dropdown in filter bar for column visibility management
- Sticky select/name columns when scrolling horizontally
- Keyboard shortcuts: Ctrl+C to copy, Escape to clear selection
- Grouped context menu for column visibility (Core, Personal, Survey, Prayer, Workflow)
Made-with: Cursor
2026-04-08 16:49:07 -04:00
|
|
|
|
import { createColumns, COPYABLE_FIELDS, type ResponseCard } from "./columns";
|
2026-03-10 08:17:45 -04:00
|
|
|
|
|
|
|
|
|
|
const VISIT_TYPE_OPTIONS = [
|
|
|
|
|
|
"First/Second Time Guest",
|
|
|
|
|
|
"Update My Information",
|
|
|
|
|
|
];
|
|
|
|
|
|
|
|
|
|
|
|
const ATTENDANCE_OPTIONS = [
|
|
|
|
|
|
"Less than 6 months",
|
|
|
|
|
|
"6 Months - 1 Year",
|
|
|
|
|
|
"1-3 Years",
|
|
|
|
|
|
"4-6 Years",
|
|
|
|
|
|
"7+ Years",
|
|
|
|
|
|
];
|
|
|
|
|
|
|
|
|
|
|
|
const SERVICE_OPTIONS = ["A", "B", "C", "D"];
|
|
|
|
|
|
|
|
|
|
|
|
export function DashboardContent() {
|
|
|
|
|
|
const router = useRouter();
|
|
|
|
|
|
const searchParams = useSearchParams();
|
|
|
|
|
|
|
|
|
|
|
|
const page = parseInt(searchParams.get("page") || "1");
|
|
|
|
|
|
const limit = parseInt(searchParams.get("limit") || "20");
|
|
|
|
|
|
const sortBy = searchParams.get("sortBy") || "createdAt";
|
|
|
|
|
|
const sortOrder = (searchParams.get("sortOrder") || "desc") as "asc" | "desc";
|
|
|
|
|
|
|
|
|
|
|
|
const [data, setData] = React.useState<ResponseCard[]>([]);
|
|
|
|
|
|
const [total, setTotal] = React.useState(0);
|
|
|
|
|
|
const [loading, setLoading] = React.useState(true);
|
Enhanced table view with cell copy, column toggles, and ClickUp-style toolbar
- Expand ResponseCard type to include all 30+ Prisma fields
- Add 18 new toggleable columns (Personal, Survey, Prayer, Workflow groups)
- ClickUp-style Copy popover in selection toolbar with field toggle switches
- Click-to-copy on individual cells with visual feedback
- Ctrl/Cmd+click column headers to copy column values
- Columns dropdown in filter bar for column visibility management
- Sticky select/name columns when scrolling horizontally
- Keyboard shortcuts: Ctrl+C to copy, Escape to clear selection
- Grouped context menu for column visibility (Core, Personal, Survey, Prayer, Workflow)
Made-with: Cursor
2026-04-08 16:49:07 -04:00
|
|
|
|
const [columnVisibility, setColumnVisibility] = React.useState<Record<string, boolean>>(getDefaultColumnVisibility);
|
Unified dashboard redesign: Quicksand font, charcoal theme, merged upload
- Switch font from Geist to Google Quicksand
- Replace indigo/violet primary with charcoal gray (rgb 31,32,29)
- Update gradient-mesh to warm neutral tones (amber/sage)
- Remove sidebar, add fixed top header bar with profile dropdown
- Merge dashboard and upload pages into single table-centric view
- Stat cards now act as clickable table filters with active highlight
- Simplified filter toolbar: search + dropdowns left, export + upload right
- Upload modal with drag-drop, per-file XHR progress, background uploads
- Global drag-and-drop listener opens upload modal from anywhere
- Table rows are clickable (navigate to detail page)
- Right-click column headers for visibility context menu
- Mobile-responsive card layout below md breakpoint
- Uploading row placeholders with animated progress bars
- Floating selection toolbar slides in at bottom on multi-select
- Delete sidebar, dashboard-hero, quick-actions, upload page
Made-with: Cursor
2026-03-11 16:26:56 -04:00
|
|
|
|
const [selectedIds, setSelectedIds] = React.useState<string[]>([]);
|
|
|
|
|
|
const [uploadModalOpen, setUploadModalOpen] = React.useState(false);
|
|
|
|
|
|
const [uploadingRows, setUploadingRows] = React.useState<UploadingFile[]>([]);
|
|
|
|
|
|
const [activeStatFilter, setActiveStatFilter] = React.useState<StatFilter>(null);
|
|
|
|
|
|
const [globalDragOver, setGlobalDragOver] = React.useState(false);
|
2026-03-10 08:17:45 -04:00
|
|
|
|
|
|
|
|
|
|
const fetchCards = React.useCallback(async () => {
|
|
|
|
|
|
setLoading(true);
|
|
|
|
|
|
try {
|
|
|
|
|
|
const params = new URLSearchParams(searchParams.toString());
|
|
|
|
|
|
if (!params.has("page")) params.set("page", "1");
|
|
|
|
|
|
if (!params.has("limit")) params.set("limit", "20");
|
|
|
|
|
|
|
|
|
|
|
|
const res = await fetch(`/api/cards?${params.toString()}`);
|
|
|
|
|
|
if (!res.ok) throw new Error("Failed to fetch cards");
|
|
|
|
|
|
const json = await res.json();
|
|
|
|
|
|
|
|
|
|
|
|
const cards = json.cards.map((card: Record<string, unknown>) => ({
|
|
|
|
|
|
...card,
|
|
|
|
|
|
createdAt: card.createdAt as string,
|
|
|
|
|
|
frontImageUrl: card.frontImagePath
|
|
|
|
|
|
? `/api/images/${card.frontImagePath}`
|
|
|
|
|
|
: null,
|
|
|
|
|
|
backImageUrl: card.backImagePath
|
|
|
|
|
|
? `/api/images/${card.backImagePath}`
|
|
|
|
|
|
: null,
|
|
|
|
|
|
}));
|
|
|
|
|
|
|
|
|
|
|
|
setData(cards);
|
|
|
|
|
|
setTotal(json.total);
|
|
|
|
|
|
} catch {
|
|
|
|
|
|
toast.error("Failed to load cards");
|
|
|
|
|
|
} finally {
|
|
|
|
|
|
setLoading(false);
|
|
|
|
|
|
}
|
|
|
|
|
|
}, [searchParams]);
|
|
|
|
|
|
|
|
|
|
|
|
React.useEffect(() => {
|
|
|
|
|
|
fetchCards();
|
|
|
|
|
|
}, [fetchCards]);
|
|
|
|
|
|
|
Unified dashboard redesign: Quicksand font, charcoal theme, merged upload
- Switch font from Geist to Google Quicksand
- Replace indigo/violet primary with charcoal gray (rgb 31,32,29)
- Update gradient-mesh to warm neutral tones (amber/sage)
- Remove sidebar, add fixed top header bar with profile dropdown
- Merge dashboard and upload pages into single table-centric view
- Stat cards now act as clickable table filters with active highlight
- Simplified filter toolbar: search + dropdowns left, export + upload right
- Upload modal with drag-drop, per-file XHR progress, background uploads
- Global drag-and-drop listener opens upload modal from anywhere
- Table rows are clickable (navigate to detail page)
- Right-click column headers for visibility context menu
- Mobile-responsive card layout below md breakpoint
- Uploading row placeholders with animated progress bars
- Floating selection toolbar slides in at bottom on multi-select
- Delete sidebar, dashboard-hero, quick-actions, upload page
Made-with: Cursor
2026-03-11 16:26:56 -04:00
|
|
|
|
const updateUrl = React.useCallback(
|
|
|
|
|
|
(updates: Record<string, string | number | undefined>) => {
|
|
|
|
|
|
const params = new URLSearchParams(searchParams.toString());
|
|
|
|
|
|
for (const [key, value] of Object.entries(updates)) {
|
|
|
|
|
|
if (value === undefined || value === "") {
|
|
|
|
|
|
params.delete(key);
|
|
|
|
|
|
} else {
|
|
|
|
|
|
params.set(key, String(value));
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
router.push(`/?${params.toString()}`);
|
|
|
|
|
|
},
|
|
|
|
|
|
[router, searchParams]
|
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
|
|
const handleStatFilterChange = React.useCallback(
|
|
|
|
|
|
(filter: StatFilter) => {
|
|
|
|
|
|
setActiveStatFilter(filter);
|
|
|
|
|
|
const params = new URLSearchParams(searchParams.toString());
|
|
|
|
|
|
|
|
|
|
|
|
params.delete("ocrStatus");
|
|
|
|
|
|
params.delete("reviewStatus");
|
|
|
|
|
|
|
|
|
|
|
|
if (filter === "complete") {
|
|
|
|
|
|
params.set("ocrStatus", "complete");
|
|
|
|
|
|
} else if (filter === "error") {
|
|
|
|
|
|
params.set("ocrStatus", "error");
|
|
|
|
|
|
} else if (filter === "unreviewed") {
|
|
|
|
|
|
params.set("reviewStatus", "unreviewed");
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
params.set("page", "1");
|
|
|
|
|
|
router.push(`/?${params.toString()}`);
|
|
|
|
|
|
},
|
|
|
|
|
|
[router, searchParams]
|
|
|
|
|
|
);
|
2026-03-10 08:17:45 -04:00
|
|
|
|
|
|
|
|
|
|
const handleBulkAction = async (
|
|
|
|
|
|
ids: string[],
|
|
|
|
|
|
action: "reviewed" | "exported" | "delete"
|
|
|
|
|
|
) => {
|
|
|
|
|
|
try {
|
|
|
|
|
|
if (action === "delete") {
|
|
|
|
|
|
await Promise.all(
|
|
|
|
|
|
ids.map((id) => fetch(`/api/cards/${id}`, { method: "DELETE" }))
|
|
|
|
|
|
);
|
|
|
|
|
|
toast.success(`Deleted ${ids.length} card(s)`);
|
|
|
|
|
|
} else {
|
|
|
|
|
|
await Promise.all(
|
|
|
|
|
|
ids.map((id) =>
|
|
|
|
|
|
fetch(`/api/cards/${id}`, {
|
|
|
|
|
|
method: "PUT",
|
|
|
|
|
|
headers: { "Content-Type": "application/json" },
|
|
|
|
|
|
body: JSON.stringify({ reviewStatus: action }),
|
|
|
|
|
|
})
|
|
|
|
|
|
)
|
|
|
|
|
|
);
|
|
|
|
|
|
toast.success(`Marked ${ids.length} card(s) as ${action}`);
|
|
|
|
|
|
}
|
|
|
|
|
|
fetchCards();
|
|
|
|
|
|
} catch {
|
|
|
|
|
|
toast.error("Bulk action failed");
|
|
|
|
|
|
}
|
|
|
|
|
|
};
|
|
|
|
|
|
|
2026-04-07 18:22:33 -04:00
|
|
|
|
const handleBulkReprocess = async (ids: string[]) => {
|
|
|
|
|
|
try {
|
|
|
|
|
|
const res = await fetch("/api/cards/reprocess-batch", {
|
|
|
|
|
|
method: "POST",
|
|
|
|
|
|
headers: { "Content-Type": "application/json" },
|
|
|
|
|
|
body: JSON.stringify({ ids }),
|
|
|
|
|
|
});
|
|
|
|
|
|
const data = await res.json();
|
|
|
|
|
|
if (res.ok) {
|
|
|
|
|
|
toast.success(
|
|
|
|
|
|
`Reprocessing ${data.queued} card(s)${data.skipped > 0 ? ` (${data.skipped} skipped)` : ""}`
|
|
|
|
|
|
);
|
|
|
|
|
|
setSelectedIds([]);
|
|
|
|
|
|
fetchCards();
|
|
|
|
|
|
} else {
|
|
|
|
|
|
toast.error(data.error || "Batch reprocess failed");
|
|
|
|
|
|
}
|
|
|
|
|
|
} catch {
|
|
|
|
|
|
toast.error("Failed to start batch reprocessing");
|
|
|
|
|
|
}
|
|
|
|
|
|
};
|
|
|
|
|
|
|
2026-04-09 16:14:48 -04:00
|
|
|
|
const handleBulkSyncMonday = async (ids: string[]) => {
|
|
|
|
|
|
try {
|
|
|
|
|
|
toast.info(`Syncing ${ids.length} card(s) to Monday.com…`);
|
|
|
|
|
|
const res = await fetch("/api/integrations/monday/sync-all", {
|
|
|
|
|
|
method: "POST",
|
|
|
|
|
|
headers: { "Content-Type": "application/json" },
|
|
|
|
|
|
body: JSON.stringify({ cardIds: ids, mode: "all" }),
|
|
|
|
|
|
});
|
|
|
|
|
|
const result = await res.json();
|
|
|
|
|
|
if (!res.ok) {
|
|
|
|
|
|
toast.error(result.error || "Monday.com sync failed");
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
const parts: string[] = [];
|
|
|
|
|
|
if (result.created > 0) parts.push(`${result.created} pushed`);
|
|
|
|
|
|
if (result.updated > 0) parts.push(`${result.updated} updated`);
|
|
|
|
|
|
if (result.failed > 0) parts.push(`${result.failed} failed`);
|
|
|
|
|
|
if (result.failed > 0 && result.synced === 0) {
|
|
|
|
|
|
toast.error(`Monday.com: ${parts.join(", ")}`, {
|
|
|
|
|
|
description: result.errors?.[0],
|
|
|
|
|
|
});
|
|
|
|
|
|
} else if (result.failed > 0) {
|
|
|
|
|
|
toast.warning(`Monday.com: ${parts.join(", ")}`);
|
|
|
|
|
|
} else if (result.synced === 0) {
|
|
|
|
|
|
toast.info("No eligible cards to sync");
|
|
|
|
|
|
} else {
|
|
|
|
|
|
toast.success(`Monday.com: ${parts.join(", ")}`);
|
|
|
|
|
|
}
|
|
|
|
|
|
fetchCards();
|
|
|
|
|
|
} catch {
|
|
|
|
|
|
toast.error("Failed to sync to Monday.com");
|
|
|
|
|
|
}
|
|
|
|
|
|
};
|
|
|
|
|
|
|
2026-03-10 08:17:45 -04:00
|
|
|
|
const handleExportCsv = () => {
|
|
|
|
|
|
if (data.length === 0) {
|
|
|
|
|
|
toast.error("No data to export");
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-04-09 11:35:58 -04:00
|
|
|
|
const visibleCols = ALL_TOGGLEABLE_COLUMNS.filter(
|
|
|
|
|
|
(col) => columnVisibility[col.id] !== false
|
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
|
|
if (visibleCols.length === 0) {
|
|
|
|
|
|
toast.error("No columns visible to export");
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
const headers = visibleCols.map((c) => c.label);
|
|
|
|
|
|
const rows = data.map((card) =>
|
|
|
|
|
|
visibleCols.map((col) => {
|
|
|
|
|
|
const val = (card as Record<string, unknown>)[col.id];
|
|
|
|
|
|
if (val == null) return "";
|
|
|
|
|
|
if (typeof val === "boolean") return val ? "Yes" : "No";
|
|
|
|
|
|
if (Array.isArray(val)) return val.filter(Boolean).join(", ");
|
|
|
|
|
|
return String(val);
|
|
|
|
|
|
})
|
|
|
|
|
|
);
|
2026-03-10 08:17:45 -04:00
|
|
|
|
|
Unified dashboard redesign: Quicksand font, charcoal theme, merged upload
- Switch font from Geist to Google Quicksand
- Replace indigo/violet primary with charcoal gray (rgb 31,32,29)
- Update gradient-mesh to warm neutral tones (amber/sage)
- Remove sidebar, add fixed top header bar with profile dropdown
- Merge dashboard and upload pages into single table-centric view
- Stat cards now act as clickable table filters with active highlight
- Simplified filter toolbar: search + dropdowns left, export + upload right
- Upload modal with drag-drop, per-file XHR progress, background uploads
- Global drag-and-drop listener opens upload modal from anywhere
- Table rows are clickable (navigate to detail page)
- Right-click column headers for visibility context menu
- Mobile-responsive card layout below md breakpoint
- Uploading row placeholders with animated progress bars
- Floating selection toolbar slides in at bottom on multi-select
- Delete sidebar, dashboard-hero, quick-actions, upload page
Made-with: Cursor
2026-03-11 16:26:56 -04:00
|
|
|
|
const csv = [headers, ...rows]
|
|
|
|
|
|
.map((r) => r.map((v) => `"${v.replace(/"/g, '""')}"`).join(","))
|
|
|
|
|
|
.join("\n");
|
2026-03-10 08:17:45 -04:00
|
|
|
|
|
|
|
|
|
|
const blob = new Blob([csv], { type: "text/csv" });
|
|
|
|
|
|
const url = URL.createObjectURL(blob);
|
|
|
|
|
|
const a = document.createElement("a");
|
|
|
|
|
|
a.href = url;
|
|
|
|
|
|
a.download = `response-cards-${new Date().toISOString().split("T")[0]}.csv`;
|
|
|
|
|
|
a.click();
|
|
|
|
|
|
URL.revokeObjectURL(url);
|
2026-04-09 11:35:58 -04:00
|
|
|
|
toast.success(`Exported ${data.length} rows × ${visibleCols.length} columns`);
|
2026-03-10 08:17:45 -04:00
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
const columns = React.useMemo(
|
|
|
|
|
|
() =>
|
|
|
|
|
|
createColumns({
|
|
|
|
|
|
onViewDetails: (card) => router.push(`/cards/${card.id}`),
|
|
|
|
|
|
onMarkReviewed: async (card) => {
|
|
|
|
|
|
await fetch(`/api/cards/${card.id}`, {
|
|
|
|
|
|
method: "PUT",
|
|
|
|
|
|
headers: { "Content-Type": "application/json" },
|
|
|
|
|
|
body: JSON.stringify({ reviewStatus: "reviewed" }),
|
|
|
|
|
|
});
|
|
|
|
|
|
toast.success("Marked as reviewed");
|
|
|
|
|
|
fetchCards();
|
|
|
|
|
|
},
|
2026-03-12 11:46:15 -04:00
|
|
|
|
onReprocess: async (card) => {
|
|
|
|
|
|
try {
|
|
|
|
|
|
const res = await fetch(`/api/cards/${card.id}/reprocess`, {
|
|
|
|
|
|
method: "POST",
|
|
|
|
|
|
});
|
|
|
|
|
|
if (!res.ok) {
|
|
|
|
|
|
const err = await res.json();
|
|
|
|
|
|
throw new Error(err.error || "Failed to start reprocessing");
|
|
|
|
|
|
}
|
|
|
|
|
|
toast.success("Reprocessing started");
|
|
|
|
|
|
fetchCards();
|
|
|
|
|
|
} catch (err) {
|
|
|
|
|
|
toast.error(
|
|
|
|
|
|
err instanceof Error ? err.message : "Failed to start reprocessing"
|
|
|
|
|
|
);
|
|
|
|
|
|
}
|
|
|
|
|
|
},
|
2026-03-10 08:17:45 -04:00
|
|
|
|
onDelete: async (card) => {
|
|
|
|
|
|
await fetch(`/api/cards/${card.id}`, { method: "DELETE" });
|
|
|
|
|
|
toast.success("Card deleted");
|
|
|
|
|
|
fetchCards();
|
|
|
|
|
|
},
|
|
|
|
|
|
}),
|
|
|
|
|
|
[router, fetchCards]
|
|
|
|
|
|
);
|
|
|
|
|
|
|
Unified dashboard redesign: Quicksand font, charcoal theme, merged upload
- Switch font from Geist to Google Quicksand
- Replace indigo/violet primary with charcoal gray (rgb 31,32,29)
- Update gradient-mesh to warm neutral tones (amber/sage)
- Remove sidebar, add fixed top header bar with profile dropdown
- Merge dashboard and upload pages into single table-centric view
- Stat cards now act as clickable table filters with active highlight
- Simplified filter toolbar: search + dropdowns left, export + upload right
- Upload modal with drag-drop, per-file XHR progress, background uploads
- Global drag-and-drop listener opens upload modal from anywhere
- Table rows are clickable (navigate to detail page)
- Right-click column headers for visibility context menu
- Mobile-responsive card layout below md breakpoint
- Uploading row placeholders with animated progress bars
- Floating selection toolbar slides in at bottom on multi-select
- Delete sidebar, dashboard-hero, quick-actions, upload page
Made-with: Cursor
2026-03-11 16:26:56 -04:00
|
|
|
|
const handleUploadStart = (files: UploadingFile[]) => {
|
|
|
|
|
|
setUploadingRows((prev) => [...prev, ...files]);
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
const handleUploadProgress = (fileId: string, progress: number) => {
|
|
|
|
|
|
setUploadingRows((prev) =>
|
|
|
|
|
|
prev.map((f) => (f.id === fileId ? { ...f, progress } : f))
|
|
|
|
|
|
);
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
const handleUploadComplete = (fileId: string) => {
|
|
|
|
|
|
setUploadingRows((prev) => prev.filter((f) => f.id !== fileId));
|
|
|
|
|
|
fetchCards();
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
const handleUploadError = (fileId: string, error: string) => {
|
|
|
|
|
|
setUploadingRows((prev) =>
|
|
|
|
|
|
prev.map((f) =>
|
|
|
|
|
|
f.id === fileId ? { ...f, status: "error" as const, error } : f
|
|
|
|
|
|
)
|
|
|
|
|
|
);
|
|
|
|
|
|
};
|
|
|
|
|
|
|
Enhanced table view with cell copy, column toggles, and ClickUp-style toolbar
- Expand ResponseCard type to include all 30+ Prisma fields
- Add 18 new toggleable columns (Personal, Survey, Prayer, Workflow groups)
- ClickUp-style Copy popover in selection toolbar with field toggle switches
- Click-to-copy on individual cells with visual feedback
- Ctrl/Cmd+click column headers to copy column values
- Columns dropdown in filter bar for column visibility management
- Sticky select/name columns when scrolling horizontally
- Keyboard shortcuts: Ctrl+C to copy, Escape to clear selection
- Grouped context menu for column visibility (Core, Personal, Survey, Prayer, Workflow)
Made-with: Cursor
2026-04-08 16:49:07 -04:00
|
|
|
|
const selectedRows = React.useMemo(
|
|
|
|
|
|
() => {
|
|
|
|
|
|
if (selectedIds.length === 0) return [];
|
|
|
|
|
|
const idSet = new Set(selectedIds);
|
|
|
|
|
|
return data.filter((c) => idSet.has(c.id));
|
|
|
|
|
|
},
|
|
|
|
|
|
[data, selectedIds]
|
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
|
|
React.useEffect(() => {
|
|
|
|
|
|
const handleKeyDown = (e: KeyboardEvent) => {
|
|
|
|
|
|
if (e.target instanceof HTMLInputElement || e.target instanceof HTMLTextAreaElement) return;
|
|
|
|
|
|
|
|
|
|
|
|
if (e.key === "Escape" && selectedIds.length > 0) {
|
|
|
|
|
|
setSelectedIds([]);
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if ((e.metaKey || e.ctrlKey) && e.key === "c" && selectedIds.length > 0) {
|
|
|
|
|
|
e.preventDefault();
|
|
|
|
|
|
const fields = COPYABLE_FIELDS.filter((f) =>
|
|
|
|
|
|
f.field === "name" || f.field === "email"
|
|
|
|
|
|
);
|
|
|
|
|
|
const values = selectedRows.map((row) =>
|
|
|
|
|
|
fields.map((f) => {
|
|
|
|
|
|
const v = row[f.field];
|
|
|
|
|
|
return v != null ? String(v) : "";
|
|
|
|
|
|
}).join("\t")
|
|
|
|
|
|
);
|
|
|
|
|
|
const header = fields.map((f) => f.label).join("\t");
|
|
|
|
|
|
navigator.clipboard.writeText([header, ...values].join("\n")).then(() => {
|
|
|
|
|
|
toast.success(`Copied ${selectedRows.length} row(s) to clipboard`);
|
|
|
|
|
|
});
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
window.addEventListener("keydown", handleKeyDown);
|
|
|
|
|
|
return () => window.removeEventListener("keydown", handleKeyDown);
|
|
|
|
|
|
}, [selectedIds, selectedRows]);
|
|
|
|
|
|
|
Unified dashboard redesign: Quicksand font, charcoal theme, merged upload
- Switch font from Geist to Google Quicksand
- Replace indigo/violet primary with charcoal gray (rgb 31,32,29)
- Update gradient-mesh to warm neutral tones (amber/sage)
- Remove sidebar, add fixed top header bar with profile dropdown
- Merge dashboard and upload pages into single table-centric view
- Stat cards now act as clickable table filters with active highlight
- Simplified filter toolbar: search + dropdowns left, export + upload right
- Upload modal with drag-drop, per-file XHR progress, background uploads
- Global drag-and-drop listener opens upload modal from anywhere
- Table rows are clickable (navigate to detail page)
- Right-click column headers for visibility context menu
- Mobile-responsive card layout below md breakpoint
- Uploading row placeholders with animated progress bars
- Floating selection toolbar slides in at bottom on multi-select
- Delete sidebar, dashboard-hero, quick-actions, upload page
Made-with: Cursor
2026-03-11 16:26:56 -04:00
|
|
|
|
React.useEffect(() => {
|
|
|
|
|
|
const handleOpenUpload = () => setUploadModalOpen(true);
|
|
|
|
|
|
window.addEventListener("open-upload-modal", handleOpenUpload);
|
|
|
|
|
|
return () => window.removeEventListener("open-upload-modal", handleOpenUpload);
|
|
|
|
|
|
}, []);
|
|
|
|
|
|
|
|
|
|
|
|
React.useEffect(() => {
|
|
|
|
|
|
const handleDragOver = (e: DragEvent) => {
|
|
|
|
|
|
e.preventDefault();
|
|
|
|
|
|
if (e.dataTransfer?.types.includes("Files")) {
|
|
|
|
|
|
setGlobalDragOver(true);
|
|
|
|
|
|
}
|
|
|
|
|
|
};
|
|
|
|
|
|
const handleDragLeave = (e: DragEvent) => {
|
|
|
|
|
|
if (e.relatedTarget === null) {
|
|
|
|
|
|
setGlobalDragOver(false);
|
|
|
|
|
|
}
|
|
|
|
|
|
};
|
|
|
|
|
|
const handleDrop = (e: DragEvent) => {
|
|
|
|
|
|
e.preventDefault();
|
|
|
|
|
|
setGlobalDragOver(false);
|
|
|
|
|
|
if (e.dataTransfer?.files.length) {
|
|
|
|
|
|
setUploadModalOpen(true);
|
|
|
|
|
|
}
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
document.addEventListener("dragover", handleDragOver);
|
|
|
|
|
|
document.addEventListener("dragleave", handleDragLeave);
|
|
|
|
|
|
document.addEventListener("drop", handleDrop);
|
|
|
|
|
|
|
|
|
|
|
|
return () => {
|
|
|
|
|
|
document.removeEventListener("dragover", handleDragOver);
|
|
|
|
|
|
document.removeEventListener("dragleave", handleDragLeave);
|
|
|
|
|
|
document.removeEventListener("drop", handleDrop);
|
|
|
|
|
|
};
|
|
|
|
|
|
}, []);
|
|
|
|
|
|
|
|
|
|
|
|
const openUpload = React.useCallback(() => {
|
|
|
|
|
|
setUploadModalOpen(true);
|
|
|
|
|
|
}, []);
|
|
|
|
|
|
|
2026-03-10 08:17:45 -04:00
|
|
|
|
return (
|
|
|
|
|
|
<div className="space-y-6">
|
Unified dashboard redesign: Quicksand font, charcoal theme, merged upload
- Switch font from Geist to Google Quicksand
- Replace indigo/violet primary with charcoal gray (rgb 31,32,29)
- Update gradient-mesh to warm neutral tones (amber/sage)
- Remove sidebar, add fixed top header bar with profile dropdown
- Merge dashboard and upload pages into single table-centric view
- Stat cards now act as clickable table filters with active highlight
- Simplified filter toolbar: search + dropdowns left, export + upload right
- Upload modal with drag-drop, per-file XHR progress, background uploads
- Global drag-and-drop listener opens upload modal from anywhere
- Table rows are clickable (navigate to detail page)
- Right-click column headers for visibility context menu
- Mobile-responsive card layout below md breakpoint
- Uploading row placeholders with animated progress bars
- Floating selection toolbar slides in at bottom on multi-select
- Delete sidebar, dashboard-hero, quick-actions, upload page
Made-with: Cursor
2026-03-11 16:26:56 -04:00
|
|
|
|
{/* Global drag overlay */}
|
|
|
|
|
|
{globalDragOver && (
|
|
|
|
|
|
<div className="pointer-events-none fixed inset-0 z-50 flex items-center justify-center bg-primary/5 backdrop-blur-sm">
|
|
|
|
|
|
<div className="rounded-2xl border-2 border-dashed border-primary/40 bg-background/80 px-12 py-8 text-center shadow-xl">
|
|
|
|
|
|
<p className="text-lg font-semibold">Drop files to upload</p>
|
|
|
|
|
|
<p className="mt-1 text-sm text-muted-foreground">
|
|
|
|
|
|
PDF, JPEG, PNG, WebP
|
|
|
|
|
|
</p>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
)}
|
|
|
|
|
|
|
|
|
|
|
|
{/* Page title */}
|
|
|
|
|
|
<div>
|
|
|
|
|
|
<h1 className="text-2xl font-bold tracking-tight sm:text-3xl">
|
|
|
|
|
|
Response Cards
|
|
|
|
|
|
</h1>
|
|
|
|
|
|
<p className="mt-1 text-sm text-muted-foreground">
|
|
|
|
|
|
Manage and review scanned response cards
|
|
|
|
|
|
</p>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
{/* Stat cards as filters */}
|
|
|
|
|
|
<StatCards
|
|
|
|
|
|
activeFilter={activeStatFilter}
|
|
|
|
|
|
onFilterChange={handleStatFilterChange}
|
|
|
|
|
|
/>
|
|
|
|
|
|
|
|
|
|
|
|
{/* Filter toolbar */}
|
2026-03-10 08:17:45 -04:00
|
|
|
|
<Filters
|
|
|
|
|
|
visitTypeOptions={VISIT_TYPE_OPTIONS}
|
|
|
|
|
|
attendanceDurationOptions={ATTENDANCE_OPTIONS}
|
|
|
|
|
|
serviceAttendedOptions={SERVICE_OPTIONS}
|
|
|
|
|
|
onExportCsv={handleExportCsv}
|
Unified dashboard redesign: Quicksand font, charcoal theme, merged upload
- Switch font from Geist to Google Quicksand
- Replace indigo/violet primary with charcoal gray (rgb 31,32,29)
- Update gradient-mesh to warm neutral tones (amber/sage)
- Remove sidebar, add fixed top header bar with profile dropdown
- Merge dashboard and upload pages into single table-centric view
- Stat cards now act as clickable table filters with active highlight
- Simplified filter toolbar: search + dropdowns left, export + upload right
- Upload modal with drag-drop, per-file XHR progress, background uploads
- Global drag-and-drop listener opens upload modal from anywhere
- Table rows are clickable (navigate to detail page)
- Right-click column headers for visibility context menu
- Mobile-responsive card layout below md breakpoint
- Uploading row placeholders with animated progress bars
- Floating selection toolbar slides in at bottom on multi-select
- Delete sidebar, dashboard-hero, quick-actions, upload page
Made-with: Cursor
2026-03-11 16:26:56 -04:00
|
|
|
|
onUploadClick={openUpload}
|
Enhanced table view with cell copy, column toggles, and ClickUp-style toolbar
- Expand ResponseCard type to include all 30+ Prisma fields
- Add 18 new toggleable columns (Personal, Survey, Prayer, Workflow groups)
- ClickUp-style Copy popover in selection toolbar with field toggle switches
- Click-to-copy on individual cells with visual feedback
- Ctrl/Cmd+click column headers to copy column values
- Columns dropdown in filter bar for column visibility management
- Sticky select/name columns when scrolling horizontally
- Keyboard shortcuts: Ctrl+C to copy, Escape to clear selection
- Grouped context menu for column visibility (Core, Personal, Survey, Prayer, Workflow)
Made-with: Cursor
2026-04-08 16:49:07 -04:00
|
|
|
|
columnVisibility={columnVisibility}
|
|
|
|
|
|
onColumnVisibilityChange={setColumnVisibility}
|
2026-03-10 08:17:45 -04:00
|
|
|
|
/>
|
Unified dashboard redesign: Quicksand font, charcoal theme, merged upload
- Switch font from Geist to Google Quicksand
- Replace indigo/violet primary with charcoal gray (rgb 31,32,29)
- Update gradient-mesh to warm neutral tones (amber/sage)
- Remove sidebar, add fixed top header bar with profile dropdown
- Merge dashboard and upload pages into single table-centric view
- Stat cards now act as clickable table filters with active highlight
- Simplified filter toolbar: search + dropdowns left, export + upload right
- Upload modal with drag-drop, per-file XHR progress, background uploads
- Global drag-and-drop listener opens upload modal from anywhere
- Table rows are clickable (navigate to detail page)
- Right-click column headers for visibility context menu
- Mobile-responsive card layout below md breakpoint
- Uploading row placeholders with animated progress bars
- Floating selection toolbar slides in at bottom on multi-select
- Delete sidebar, dashboard-hero, quick-actions, upload page
Made-with: Cursor
2026-03-11 16:26:56 -04:00
|
|
|
|
|
|
|
|
|
|
{/* Data table */}
|
2026-03-10 08:17:45 -04:00
|
|
|
|
<div className={loading ? "opacity-60 transition-opacity" : ""}>
|
|
|
|
|
|
<DataTable
|
|
|
|
|
|
data={data}
|
|
|
|
|
|
columns={columns}
|
|
|
|
|
|
totalCount={total}
|
|
|
|
|
|
page={page}
|
|
|
|
|
|
limit={limit}
|
|
|
|
|
|
sortBy={sortBy}
|
|
|
|
|
|
sortOrder={sortOrder}
|
|
|
|
|
|
columnVisibility={columnVisibility}
|
|
|
|
|
|
onColumnVisibilityChange={setColumnVisibility}
|
|
|
|
|
|
onPageChange={(p) => updateUrl({ page: p })}
|
|
|
|
|
|
onLimitChange={(l) => updateUrl({ limit: l, page: 1 })}
|
|
|
|
|
|
onSortChange={(sb, so) => updateUrl({ sortBy: sb, sortOrder: so })}
|
Unified dashboard redesign: Quicksand font, charcoal theme, merged upload
- Switch font from Geist to Google Quicksand
- Replace indigo/violet primary with charcoal gray (rgb 31,32,29)
- Update gradient-mesh to warm neutral tones (amber/sage)
- Remove sidebar, add fixed top header bar with profile dropdown
- Merge dashboard and upload pages into single table-centric view
- Stat cards now act as clickable table filters with active highlight
- Simplified filter toolbar: search + dropdowns left, export + upload right
- Upload modal with drag-drop, per-file XHR progress, background uploads
- Global drag-and-drop listener opens upload modal from anywhere
- Table rows are clickable (navigate to detail page)
- Right-click column headers for visibility context menu
- Mobile-responsive card layout below md breakpoint
- Uploading row placeholders with animated progress bars
- Floating selection toolbar slides in at bottom on multi-select
- Delete sidebar, dashboard-hero, quick-actions, upload page
Made-with: Cursor
2026-03-11 16:26:56 -04:00
|
|
|
|
uploadingRows={uploadingRows.filter((f) => f.status === "uploading")}
|
|
|
|
|
|
onSelectionChange={setSelectedIds}
|
2026-03-10 08:17:45 -04:00
|
|
|
|
/>
|
|
|
|
|
|
</div>
|
Unified dashboard redesign: Quicksand font, charcoal theme, merged upload
- Switch font from Geist to Google Quicksand
- Replace indigo/violet primary with charcoal gray (rgb 31,32,29)
- Update gradient-mesh to warm neutral tones (amber/sage)
- Remove sidebar, add fixed top header bar with profile dropdown
- Merge dashboard and upload pages into single table-centric view
- Stat cards now act as clickable table filters with active highlight
- Simplified filter toolbar: search + dropdowns left, export + upload right
- Upload modal with drag-drop, per-file XHR progress, background uploads
- Global drag-and-drop listener opens upload modal from anywhere
- Table rows are clickable (navigate to detail page)
- Right-click column headers for visibility context menu
- Mobile-responsive card layout below md breakpoint
- Uploading row placeholders with animated progress bars
- Floating selection toolbar slides in at bottom on multi-select
- Delete sidebar, dashboard-hero, quick-actions, upload page
Made-with: Cursor
2026-03-11 16:26:56 -04:00
|
|
|
|
|
|
|
|
|
|
{/* Floating selection toolbar */}
|
|
|
|
|
|
<SelectionToolbar
|
|
|
|
|
|
selectedIds={selectedIds}
|
Enhanced table view with cell copy, column toggles, and ClickUp-style toolbar
- Expand ResponseCard type to include all 30+ Prisma fields
- Add 18 new toggleable columns (Personal, Survey, Prayer, Workflow groups)
- ClickUp-style Copy popover in selection toolbar with field toggle switches
- Click-to-copy on individual cells with visual feedback
- Ctrl/Cmd+click column headers to copy column values
- Columns dropdown in filter bar for column visibility management
- Sticky select/name columns when scrolling horizontally
- Keyboard shortcuts: Ctrl+C to copy, Escape to clear selection
- Grouped context menu for column visibility (Core, Personal, Survey, Prayer, Workflow)
Made-with: Cursor
2026-04-08 16:49:07 -04:00
|
|
|
|
selectedRows={selectedRows}
|
Unified dashboard redesign: Quicksand font, charcoal theme, merged upload
- Switch font from Geist to Google Quicksand
- Replace indigo/violet primary with charcoal gray (rgb 31,32,29)
- Update gradient-mesh to warm neutral tones (amber/sage)
- Remove sidebar, add fixed top header bar with profile dropdown
- Merge dashboard and upload pages into single table-centric view
- Stat cards now act as clickable table filters with active highlight
- Simplified filter toolbar: search + dropdowns left, export + upload right
- Upload modal with drag-drop, per-file XHR progress, background uploads
- Global drag-and-drop listener opens upload modal from anywhere
- Table rows are clickable (navigate to detail page)
- Right-click column headers for visibility context menu
- Mobile-responsive card layout below md breakpoint
- Uploading row placeholders with animated progress bars
- Floating selection toolbar slides in at bottom on multi-select
- Delete sidebar, dashboard-hero, quick-actions, upload page
Made-with: Cursor
2026-03-11 16:26:56 -04:00
|
|
|
|
onMarkReviewed={(ids) => handleBulkAction(ids, "reviewed")}
|
|
|
|
|
|
onMarkExported={(ids) => handleBulkAction(ids, "exported")}
|
2026-04-07 18:22:33 -04:00
|
|
|
|
onReprocess={handleBulkReprocess}
|
2026-04-09 16:14:48 -04:00
|
|
|
|
onSyncMonday={handleBulkSyncMonday}
|
Unified dashboard redesign: Quicksand font, charcoal theme, merged upload
- Switch font from Geist to Google Quicksand
- Replace indigo/violet primary with charcoal gray (rgb 31,32,29)
- Update gradient-mesh to warm neutral tones (amber/sage)
- Remove sidebar, add fixed top header bar with profile dropdown
- Merge dashboard and upload pages into single table-centric view
- Stat cards now act as clickable table filters with active highlight
- Simplified filter toolbar: search + dropdowns left, export + upload right
- Upload modal with drag-drop, per-file XHR progress, background uploads
- Global drag-and-drop listener opens upload modal from anywhere
- Table rows are clickable (navigate to detail page)
- Right-click column headers for visibility context menu
- Mobile-responsive card layout below md breakpoint
- Uploading row placeholders with animated progress bars
- Floating selection toolbar slides in at bottom on multi-select
- Delete sidebar, dashboard-hero, quick-actions, upload page
Made-with: Cursor
2026-03-11 16:26:56 -04:00
|
|
|
|
onDelete={(ids) => handleBulkAction(ids, "delete")}
|
|
|
|
|
|
onClear={() => setSelectedIds([])}
|
|
|
|
|
|
/>
|
|
|
|
|
|
|
|
|
|
|
|
{/* Upload modal */}
|
|
|
|
|
|
<UploadModal
|
|
|
|
|
|
open={uploadModalOpen}
|
|
|
|
|
|
onOpenChange={setUploadModalOpen}
|
|
|
|
|
|
onUploadStart={handleUploadStart}
|
|
|
|
|
|
onUploadProgress={handleUploadProgress}
|
|
|
|
|
|
onUploadComplete={handleUploadComplete}
|
|
|
|
|
|
onUploadError={handleUploadError}
|
|
|
|
|
|
/>
|
2026-03-10 08:17:45 -04:00
|
|
|
|
</div>
|
|
|
|
|
|
);
|
|
|
|
|
|
}
|