feat(report page) - export button added
This commit is contained in:
@@ -12,6 +12,7 @@ import {
|
||||
SelectValue,
|
||||
} from "@/components/ui/select";
|
||||
import { DoctorBalancesAndSummary } from "@repo/db/types";
|
||||
import ExportReportButton from "./export-button";
|
||||
|
||||
type StaffOption = { id: number; name: string };
|
||||
|
||||
@@ -259,6 +260,15 @@ export default function CollectionsByDoctorReport({
|
||||
onNext={handleNext}
|
||||
hasPrev={cursorIndex > 0}
|
||||
hasNext={hasMore}
|
||||
headerRight={
|
||||
<ExportReportButton
|
||||
reportType="collections_by_doctor"
|
||||
from={startDate}
|
||||
to={endDate}
|
||||
staffId={Number(staffId)}
|
||||
className="mr-2"
|
||||
/>
|
||||
}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
|
||||
71
apps/Frontend/src/components/reports/export-button.tsx
Normal file
71
apps/Frontend/src/components/reports/export-button.tsx
Normal file
@@ -0,0 +1,71 @@
|
||||
import React, { useState } from "react";
|
||||
import { apiRequest } from "@/lib/queryClient";
|
||||
|
||||
export default function ExportReportButton({
|
||||
reportType,
|
||||
from,
|
||||
to,
|
||||
staffId,
|
||||
className,
|
||||
labelCsv = "Download CSV",
|
||||
}: {
|
||||
reportType: string; // e.g. "collections_by_doctor" or "patients_with_balance"
|
||||
from?: string;
|
||||
to?: string;
|
||||
staffId?: number | string | null;
|
||||
className?: string;
|
||||
labelCsv?: string;
|
||||
}) {
|
||||
const [loading, setLoading] = useState(false);
|
||||
|
||||
async function downloadCsv() {
|
||||
setLoading(true);
|
||||
try {
|
||||
const params = new URLSearchParams();
|
||||
params.set("type", reportType);
|
||||
if (from) params.set("from", from);
|
||||
if (to) params.set("to", to);
|
||||
if (staffId) params.set("staffId", String(staffId));
|
||||
params.set("format", "csv"); // server expects format=csv
|
||||
|
||||
const url = `/api/export-payments-reports/export?${params.toString()}`;
|
||||
|
||||
// Use apiRequest for consistent auth headers/cookies
|
||||
const res = await apiRequest("GET", url);
|
||||
if (!res.ok) {
|
||||
const body = await res.text().catch(() => "Export failed");
|
||||
throw new Error(body || "Export failed");
|
||||
}
|
||||
|
||||
const blob = await res.blob();
|
||||
const href = URL.createObjectURL(blob);
|
||||
const a = document.createElement("a");
|
||||
a.href = href;
|
||||
const safeFrom = from || "all";
|
||||
const safeTo = to || "all";
|
||||
a.download = `${reportType}_${safeFrom}_${safeTo}.csv`;
|
||||
document.body.appendChild(a);
|
||||
a.click();
|
||||
a.remove();
|
||||
URL.revokeObjectURL(href);
|
||||
} catch (err: any) {
|
||||
console.error("Export CSV failed", err);
|
||||
alert("Export failed: " + (err?.message ?? "unknown error"));
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<div className={className ?? "flex items-center gap-2"}>
|
||||
<button
|
||||
type="button"
|
||||
onClick={downloadCsv}
|
||||
disabled={loading}
|
||||
className="inline-flex items-center px-3 py-2 rounded border text-sm"
|
||||
>
|
||||
{loading ? "Preparing..." : labelCsv}
|
||||
</button>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -23,6 +23,7 @@ export default function PatientsBalancesList({
|
||||
onNext,
|
||||
hasPrev,
|
||||
hasNext,
|
||||
headerRight, // optional UI node to render in header
|
||||
}: {
|
||||
rows: GenericRow[];
|
||||
reportType?: string | null;
|
||||
@@ -37,6 +38,7 @@ export default function PatientsBalancesList({
|
||||
onNext: () => void;
|
||||
hasPrev: boolean;
|
||||
hasNext: boolean;
|
||||
headerRight?: React.ReactNode;
|
||||
}) {
|
||||
const fmt = (v: number) =>
|
||||
new Intl.NumberFormat("en-US", {
|
||||
@@ -66,6 +68,9 @@ export default function PatientsBalancesList({
|
||||
<h3 className="font-medium text-gray-900">
|
||||
{reportTypeTitle(reportType)}
|
||||
</h3>
|
||||
|
||||
{/* headerRight rendered here (if provided) */}
|
||||
<div>{headerRight ?? null}</div>
|
||||
</div>
|
||||
|
||||
<div className="divide-y min-h-[120px]">
|
||||
|
||||
@@ -3,6 +3,7 @@ import { useQuery } from "@tanstack/react-query";
|
||||
import { apiRequest } from "@/lib/queryClient";
|
||||
import type { PatientBalanceRow } from "@repo/db/types";
|
||||
import PatientsBalancesList from "./patients-balances-list";
|
||||
import ExportReportButton from "./export-button";
|
||||
|
||||
type Resp = {
|
||||
balances: PatientBalanceRow[];
|
||||
@@ -117,6 +118,14 @@ export default function PatientsWithBalanceReport({
|
||||
onNext={handleNext}
|
||||
hasPrev={cursorIndex > 0}
|
||||
hasNext={hasMore}
|
||||
headerRight={
|
||||
<ExportReportButton
|
||||
reportType="patients_with_balance"
|
||||
from={startDate}
|
||||
to={endDate}
|
||||
className="mr-2"
|
||||
/>
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -1,11 +1,9 @@
|
||||
import React, { useState } from "react";
|
||||
import { Download } from "lucide-react";
|
||||
import { useAuth } from "@/hooks/use-auth";
|
||||
import ReportConfig from "@/components/reports/report-config";
|
||||
import PatientsWithBalanceReport from "@/components/reports/patients-with-balance-report";
|
||||
import CollectionsByDoctorReport from "@/components/reports/collections-by-doctor-report";
|
||||
import SummaryCards from "@/components/reports/summary-cards";
|
||||
import { Button } from "@/components/ui/button";
|
||||
|
||||
type ReportType =
|
||||
| "patients_with_balance"
|
||||
@@ -32,17 +30,6 @@ export default function ReportPage() {
|
||||
const [selectedReportType, setSelectedReportType] = useState<ReportType>(
|
||||
"patients_with_balance"
|
||||
);
|
||||
const [isGenerating, setIsGenerating] = useState(false);
|
||||
|
||||
const generateReport = async () => {
|
||||
setIsGenerating(true);
|
||||
try {
|
||||
// placeholder: implement export per-report endpoint
|
||||
await new Promise((r) => setTimeout(r, 900));
|
||||
} finally {
|
||||
setIsGenerating(false);
|
||||
}
|
||||
};
|
||||
|
||||
if (!user) {
|
||||
return (
|
||||
@@ -62,16 +49,6 @@ export default function ReportPage() {
|
||||
Generate comprehensive financial reports for your practice
|
||||
</p>
|
||||
</div>
|
||||
|
||||
{/* Export Button (Top Right) */}
|
||||
<Button
|
||||
onClick={generateReport}
|
||||
disabled={isGenerating}
|
||||
className="default"
|
||||
>
|
||||
<Download className="h-4 w-4 mr-2" />
|
||||
{isGenerating ? "Generating..." : "Export Report"}
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
<div className="mb-4">
|
||||
|
||||
Reference in New Issue
Block a user