fix(reports): add error handler and improve error messages for diagnosis #51
@@ -16,6 +16,11 @@ import {
|
|||||||
|
|
||||||
export const reportsRouter = new Hono();
|
export const reportsRouter = new Hono();
|
||||||
|
|
||||||
|
reportsRouter.onError((err, c) => {
|
||||||
|
console.error("[reports] unhandled error:", err);
|
||||||
|
return c.json({ error: "Internal server error", message: err.message }, 500);
|
||||||
|
});
|
||||||
|
|
||||||
// ─── Helpers ──────────────────────────────────────────────────────────────────
|
// ─── Helpers ──────────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
function parseDate(value: string | undefined, fallback: Date): Date {
|
function parseDate(value: string | undefined, fallback: Date): Date {
|
||||||
@@ -279,6 +284,7 @@ reportsRouter.get("/clients", async (c) => {
|
|||||||
// Clients with no appointment in last 90 days (churn risk)
|
// Clients with no appointment in last 90 days (churn risk)
|
||||||
const ninetyDaysAgo = new Date();
|
const ninetyDaysAgo = new Date();
|
||||||
ninetyDaysAgo.setDate(ninetyDaysAgo.getDate() - 90);
|
ninetyDaysAgo.setDate(ninetyDaysAgo.getDate() - 90);
|
||||||
|
const ninetyDaysAgoISO = ninetyDaysAgo.toISOString();
|
||||||
|
|
||||||
const churnRisk = await db
|
const churnRisk = await db
|
||||||
.select({
|
.select({
|
||||||
@@ -290,7 +296,7 @@ reportsRouter.get("/clients", async (c) => {
|
|||||||
.leftJoin(appointments, eq(appointments.clientId, clients.id))
|
.leftJoin(appointments, eq(appointments.clientId, clients.id))
|
||||||
.groupBy(clients.id, clients.name)
|
.groupBy(clients.id, clients.name)
|
||||||
.having(
|
.having(
|
||||||
sql`MAX(${appointments.startTime}) < ${ninetyDaysAgo} OR MAX(${appointments.startTime}) IS NULL`
|
sql`MAX(${appointments.startTime}) < ${ninetyDaysAgoISO}::timestamptz OR MAX(${appointments.startTime}) IS NULL`
|
||||||
)
|
)
|
||||||
.orderBy(sql`MAX(${appointments.startTime}) ASC NULLS FIRST`);
|
.orderBy(sql`MAX(${appointments.startTime}) ASC NULLS FIRST`);
|
||||||
|
|
||||||
|
|||||||
@@ -176,8 +176,23 @@ export function ReportsPage() {
|
|||||||
fetch(`/api/reports/clients?${qs}`),
|
fetch(`/api/reports/clients?${qs}`),
|
||||||
]);
|
]);
|
||||||
|
|
||||||
if (!summRes.ok || !revRes.ok || !apptRes.ok || !svcRes.ok || !clientRes.ok) {
|
const failures = [
|
||||||
throw new Error("Failed to load report data");
|
["summary", summRes],
|
||||||
|
["revenue", revRes],
|
||||||
|
["appointments", apptRes],
|
||||||
|
["services", svcRes],
|
||||||
|
["clients", clientRes],
|
||||||
|
].filter(([, r]) => !(r as Response).ok);
|
||||||
|
if (failures.length > 0) {
|
||||||
|
const details = await Promise.all(
|
||||||
|
failures.map(async ([name, r]) => {
|
||||||
|
const res = r as Response;
|
||||||
|
let body = "";
|
||||||
|
try { body = await res.text(); } catch { /* ignore */ }
|
||||||
|
return `${name} (HTTP ${res.status}${body ? `: ${body.slice(0, 120)}` : ""})`;
|
||||||
|
})
|
||||||
|
);
|
||||||
|
throw new Error(`Failed to load report data — ${details.join(", ")}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
const [summData, revData, apptData, svcData, clientData] = await Promise.all([
|
const [summData, revData, apptData, svcData, clientData] = await Promise.all([
|
||||||
|
|||||||
Reference in New Issue
Block a user