diff --git a/frontend/src/components/site-finder/analysis/ExportButtons.tsx b/frontend/src/components/site-finder/analysis/ExportButtons.tsx index 72821253..fd1fcb6a 100644 --- a/frontend/src/components/site-finder/analysis/ExportButtons.tsx +++ b/frontend/src/components/site-finder/analysis/ExportButtons.tsx @@ -9,13 +9,14 @@ */ import { useState } from "react"; -import { Download, FileText, FileBarChart } from "lucide-react"; +import { Download, FileText, FileBarChart, FileType } from "lucide-react"; import { API_BASE_URL } from "@/lib/api"; import { triggerDownload } from "@/lib/download"; import { useFullReport, formatElapsed, readyReportLabel, + readyDocxLabel, } from "@/hooks/useFullReport"; import type { ParcelAnalyzeResponse, ParcelEgrn } from "@/lib/site-finder-api"; @@ -238,6 +239,46 @@ export function ExportButtons({ cad, analyzeData, egrn }: Props) { ? readyReportLabel(fullReport.reportGeneratedAt) : "Полный отчёт (PDF)"} + + {/* + Full-report DOCX — вторичный компакт рядом с «Полный отчёт (PDF)». + Тот же общий прогон useFullReport (один enqueue→poll даёт pdf+docx), + финальный download в ?format=docx. Компактнее PDF-кнопки (иконка + + «DOCX»), чтобы не перегружать ряд. Building/ready-состояние — общее с + PDF-кнопкой (обе дизейблятся на сборке). + */} + {/* Full-report timeout / enqueue-error note (contract has no "failed") */} @@ -257,6 +298,15 @@ export function ExportButtons({ cad, analyzeData, egrn }: Props) { Не удалось поставить сборку отчёта — повторите
)} + {/* Легаси-ран без docx (собран до PR-F) → download?format=docx = 404. */} + {fullReport.downloadError && ( ++ {fullReport.downloadError} +
+ )} {/* Error message */} {error && ( diff --git a/frontend/src/components/site-finder/ptica/reports/PticaReports.tsx b/frontend/src/components/site-finder/ptica/reports/PticaReports.tsx index fd64fd41..2f2947d1 100644 --- a/frontend/src/components/site-finder/ptica/reports/PticaReports.tsx +++ b/frontend/src/components/site-finder/ptica/reports/PticaReports.tsx @@ -14,10 +14,17 @@ * being ready (useParcelForecastQuery): until then the cards are disabled * with an honest «готовится» tag. * - * Full-report DOCX / PPTX (the prototype's editable-Word / presentation cards) - * have NO backend endpoint — the only Word/PowerPoint export the backend ships - * is the §22 forecast above. So those two cards render DISABLED with a «скоро» - * tag (honest — we never trigger a download that 404s). + * Full-report DOCX (#2259 R2 PR-F) — REAL: GET /report/download?format=docx. + * One report build produces BOTH pdf + docx, so the DOCX card shares the SAME + * useFullReport(cad) run as the full-report PDF card (enqueue → poll → download) + * — clicking either kicks the same job; only the final download format differs + * (remembered by the hook via pendingFormatRef). Legacy runs (built before PR-F) + * carry no docx_path → the hook HEAD-preflights the docx URL and, on 404, shows + * «Пересоберите отчёт» instead of silently downloading an HTML error page. + * + * Full-report PPTX (the prototype's presentation card) has NO backend endpoint — + * the only PowerPoint export the backend ships is the §22 forecast above. So that + * card renders DISABLED with a «скоро» tag (honest — we never trigger a 404). * * The «Состав отчёта» list is a visual checklist of the PDF snapshot's sections * (matches the prototype TOC); the PDF snapshot is the real multi-section @@ -34,6 +41,7 @@ import { useFullReport, formatElapsed, readyReportLabel, + readyDocxLabel, } from "@/hooks/useFullReport"; import type { ParcelAnalysis } from "@/types/site-finder"; import { StatusRow } from "@/components/site-finder/ptica/drawers/DrawerPrimitives"; @@ -364,6 +372,55 @@ export function PticaReports({ cad, analysis }: Props) { )} + {/* Full-report DOCX — REAL (#2259 R2 PR-F): same run as PDF card, + final download ?format=docx. Building-спиннер общий на прогон. */} + + {/* Full-report PPTX — NO endpoint → honest disabled «скоро» */}+ {fullReport.downloadError} +
+ )} + {/* ── Параметры — что войдёт в сгенерированный отчёт ─────────────── */}