From 49304a78bcf5198cae43bdca8d8a86a90a85b603 Mon Sep 17 00:00:00 2001
From: bot-backend
Date: Fri, 3 Jul 2026 22:40:11 +0000
Subject: [PATCH] =?UTF-8?q?feat(ptica/reports):=20DOCX-=D1=81=D0=BA=D0=B0?=
=?UTF-8?q?=D1=87=D0=B8=D0=B2=D0=B0=D0=BD=D0=B8=D0=B5=20=D0=BF=D0=BE=D0=BB?=
=?UTF-8?q?=D0=BD=D0=BE=D0=B3=D0=BE=20=D0=BE=D1=82=D1=87=D1=91=D1=82=D0=B0?=
=?UTF-8?q?=20=D0=B2=20UI=20(#2259=20R2)=20(#2338)?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
.../site-finder/analysis/ExportButtons.tsx | 52 +++++-
.../ptica/reports/PticaReports.tsx | 73 +++++++-
frontend/src/hooks/useFullReport.ts | 171 ++++++++++++++----
3 files changed, 257 insertions(+), 39 deletions(-)
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 «скоро» */}
)}
+ {/* Легаси-раны без docx (собраны до PR-F) → download?format=docx = 404.
+ Хук ловит это HEAD-preflight'ом и просит пересобрать отчёт. */}
+ {fullReport.downloadError && (
+
+ {fullReport.downloadError}
+
+ )}
+
{/* ── Параметры — что войдёт в сгенерированный отчёт ─────────────── */}