Skip to content

Commit 82c2444

Browse files
committed
Add pdf download
1 parent 5d13e93 commit 82c2444

5 files changed

Lines changed: 442 additions & 8 deletions

File tree

frontend/components/InspectionDetailsPanel.tsx

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ import {
2727
useCallback,
2828
} from "react";
2929
import { apiUrl, authHeaders } from "@/lib/api";
30+
import { downloadMaintenanceReportPdf } from "@/lib/maintenance-report";
3031

3132
const faultToToggleKey = (
3233
fault: string | null | undefined
@@ -463,6 +464,7 @@ const InspectionDetailsPanel = ({
463464
const [maintenanceModalMode, setMaintenanceModalMode] = useState<"form" | "view">("form");
464465
const [showMaintenanceModal, setShowMaintenanceModal] = useState(false);
465466
const [isSavingMaintenance, setIsSavingMaintenance] = useState(false);
467+
const [isReportGenerating, setIsReportGenerating] = useState(false);
466468

467469
const transformer = useMemo(
468470
() =>
@@ -1634,6 +1636,32 @@ const InspectionDetailsPanel = ({
16341636
}
16351637
};
16361638

1639+
const handleDownloadMaintenanceReport = async () => {
1640+
if (!maintenanceRecord || isReportGenerating) return;
1641+
setIsReportGenerating(true);
1642+
try {
1643+
await downloadMaintenanceReportPdf({
1644+
inspection,
1645+
maintenance: maintenanceRecord,
1646+
annotations: {
1647+
boxes: storedBoxes,
1648+
faults: storedFaultTypes,
1649+
annotatedBy: storedAnnotatedBy,
1650+
severity: storedSeverity,
1651+
comments: storedComments,
1652+
},
1653+
imageUrl: storedImageUrl,
1654+
});
1655+
} catch (error) {
1656+
console.error("Failed to download maintenance report", error);
1657+
if (typeof window !== "undefined") {
1658+
window.alert("Unable to generate the maintenance report. Please try again.");
1659+
}
1660+
} finally {
1661+
setIsReportGenerating(false);
1662+
}
1663+
};
1664+
16371665
const uploadBaseline = async (file: File, weather: string) => {
16381666
if (!transformer?.id) return;
16391667
const form = new FormData();
@@ -1736,6 +1764,29 @@ const InspectionDetailsPanel = ({
17361764
</svg>
17371765
<span>{isExporting ? "Preparing…" : "Export data"}</span>
17381766
</button>
1767+
<button
1768+
type="button"
1769+
onClick={handleDownloadMaintenanceReport}
1770+
disabled={!maintenanceRecord || maintenanceLoading || isReportGenerating}
1771+
className="inline-flex items-center gap-2 px-3 py-1 text-sm border rounded custombutton disabled:opacity-60 disabled:cursor-not-allowed"
1772+
title="Download the maintenance form with annotations as a PDF"
1773+
>
1774+
<svg
1775+
className="w-4 h-4"
1776+
fill="none"
1777+
stroke="currentColor"
1778+
strokeWidth={1.5}
1779+
strokeLinecap="round"
1780+
strokeLinejoin="round"
1781+
viewBox="0 0 24 24"
1782+
>
1783+
<path d="M6 2h9l4 4v16H6z" />
1784+
<path d="M15 2v5h5" />
1785+
<path d="M9 13h6" />
1786+
<path d="M9 17h6" />
1787+
</svg>
1788+
<span>{isReportGenerating ? "Preparing…" : "Maintenance PDF"}</span>
1789+
</button>
17391790
<label className="flex items-center gap-2 text-sm">
17401791
<span>Tune model</span>
17411792
<span className="relative inline-flex h-5 w-10 items-center">

frontend/components/MaintenanceAnnotationPreview.tsx

Lines changed: 3 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ interface MaintenanceAnnotationPreviewProps {
3535
error?: string | null;
3636
emptyMessage?: string;
3737
className?: string;
38+
resetKey?: string | number;
3839
}
3940

4041
const MaintenanceAnnotationPreview = ({
@@ -49,6 +50,7 @@ const MaintenanceAnnotationPreview = ({
4950
error = null,
5051
emptyMessage = "No annotations available for this inspection.",
5152
className = "",
53+
resetKey,
5254
}: MaintenanceAnnotationPreviewProps) => {
5355
const normalizedBoxes = useMemo(() => {
5456
if (!Array.isArray(boxes)) return [];
@@ -111,13 +113,6 @@ const MaintenanceAnnotationPreview = ({
111113
[legendEntries, normalizedBoxes]
112114
);
113115

114-
const overlayResetKey = useMemo(() => {
115-
const boxSignature = normalizedBoxes
116-
.map((box) => box.map((value) => Math.round(value)).join(","))
117-
.join("|");
118-
return `${imageUrl ?? "none"}-${boxSignature}`;
119-
}, [imageUrl, normalizedBoxes]);
120-
121116
const hasImage = typeof imageUrl === "string" && imageUrl.length > 0;
122117
const hasAnnotations = normalizedBoxes.length > 0;
123118

@@ -155,8 +150,8 @@ const MaintenanceAnnotationPreview = ({
155150
boxes={normalizedBoxes}
156151
boxInfo={overlayInfo}
157152
toggles={TOGGLES_ALWAYS_ON}
158-
resetKey={overlayResetKey}
159153
containerClassName="w-full h-64 border border-gray-200 dark:border-gray-700 rounded overflow-hidden"
154+
resetKey={resetKey}
160155
/>
161156
</div>
162157
<ol className="mt-3 space-y-2 text-xs text-gray-700 dark:text-gray-300">

0 commit comments

Comments
 (0)