diff --git a/frontend/src/components/site-finder/analysis/ForecastExportButtons.tsx b/frontend/src/components/site-finder/analysis/ForecastExportButtons.tsx index 4cf9979e..d5f9b403 100644 --- a/frontend/src/components/site-finder/analysis/ForecastExportButtons.tsx +++ b/frontend/src/components/site-finder/analysis/ForecastExportButtons.tsx @@ -11,12 +11,17 @@ * Backend (live on prod): * GET /api/v1/parcels/{cad}/forecast/export?format=md → text/markdown attachment * GET /api/v1/parcels/{cad}/forecast/export?format=json → application/json attachment + * GET /api/v1/parcels/{cad}/forecast/export?format=docx → Word (.docx) attachment + * GET /api/v1/parcels/{cad}/forecast/export?format=pptx → PowerPoint (.pptx) attachment * GET /api/v1/parcels/{cad}/forecast/export?format=tg → text/plain inline (TG snippet) * No forecast run → 404 → inline «Отчёт ещё не готов» (no crash, no console spam). + * + * docx/pptx are binary zip-based OOXML — they go through the SAME response.blob() + * path as md/json (never response.text(), which would corrupt the archive). */ import { useEffect, useRef, useState } from "react"; -import { Download, Copy, Check } from "lucide-react"; +import { Download, Copy, Check, FileText, Presentation } from "lucide-react"; import { API_BASE_URL } from "@/lib/api"; import { triggerDownload } from "@/lib/download"; @@ -25,7 +30,9 @@ interface Props { cad: string; } -type ExportFormat = "md" | "json"; +// Binary OOXML formats (docx/pptx) share md/json's blob download path; only the +// query param + filename extension differ, so one discriminant covers them all. +type ExportFormat = "md" | "json" | "docx" | "pptx"; const NOT_READY_MSG = "Отчёт ещё не готов"; const GENERIC_ERR_MSG = "Не удалось получить отчёт"; @@ -37,7 +44,7 @@ function sanitizeCad(cad: string): string { return cad.replace(/:/g, "_"); } -function exportUrl(cad: string, format: "md" | "json" | "tg"): string { +function exportUrl(cad: string, format: ExportFormat | "tg"): string { return `${API_BASE_URL}/api/v1/parcels/${encodeURIComponent( cad, )}/forecast/export?format=${format}`; @@ -136,6 +143,8 @@ export function ForecastExportButtons({ cad }: Props) { const mdLoading = downloadingFormat === "md"; const jsonLoading = downloadingFormat === "json"; + const docxLoading = downloadingFormat === "docx"; + const pptxLoading = downloadingFormat === "pptx"; const anyLoading = downloadingFormat !== null || tgLoading; return ( @@ -176,6 +185,26 @@ export function ForecastExportButtons({ cad }: Props) { onClick={() => void handleDownload("json")} icon={} /> + + void handleDownload("docx")} + icon={} + /> + + void handleDownload("pptx")} + icon={} + /> {error && (