feat(forecast): expose DOCX & PPTX export buttons in forecast UI
All checks were successful
CI / changes (pull_request) Successful in 5s
CI / backend-tests (pull_request) Has been skipped
Deploy / changes (push) Successful in 5s
Deploy / build-backend (push) Has been skipped
Deploy / build-worker (push) Has been skipped
CI / changes (push) Successful in 6s
CI / backend-tests (push) Has been skipped
Deploy / build-frontend (push) Successful in 28s
Deploy / deploy (push) Successful in 1m0s
All checks were successful
CI / changes (pull_request) Successful in 5s
CI / backend-tests (pull_request) Has been skipped
Deploy / changes (push) Successful in 5s
Deploy / build-backend (push) Has been skipped
Deploy / build-worker (push) Has been skipped
CI / changes (push) Successful in 6s
CI / backend-tests (push) Has been skipped
Deploy / build-frontend (push) Successful in 28s
Deploy / deploy (push) Successful in 1m0s
Add «Скачать .docx» (Word) and «Скачать .pptx» (PowerPoint) buttons to ForecastExportButtons, alongside the existing .md/.json/Telegram controls. The backend /forecast/export already renders both formats; they were just not reachable from the UI. Both reuse the existing response.blob() → triggerDownload path (correct for binary zip-based OOXML — never .text(), which would corrupt the archive). Per-format loading state; 404 → «Отчёт ещё не готов». No new deps (icons from lucide-react). Part of #958.
This commit is contained in:
parent
a66c35ae49
commit
7131bed1f4
1 changed files with 32 additions and 3 deletions
|
|
@ -11,12 +11,17 @@
|
||||||
* Backend (live on prod):
|
* Backend (live on prod):
|
||||||
* GET /api/v1/parcels/{cad}/forecast/export?format=md → text/markdown attachment
|
* 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=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)
|
* GET /api/v1/parcels/{cad}/forecast/export?format=tg → text/plain inline (TG snippet)
|
||||||
* No forecast run → 404 → inline «Отчёт ещё не готов» (no crash, no console spam).
|
* 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 { 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 { API_BASE_URL } from "@/lib/api";
|
||||||
import { triggerDownload } from "@/lib/download";
|
import { triggerDownload } from "@/lib/download";
|
||||||
|
|
@ -25,7 +30,9 @@ interface Props {
|
||||||
cad: string;
|
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 NOT_READY_MSG = "Отчёт ещё не готов";
|
||||||
const GENERIC_ERR_MSG = "Не удалось получить отчёт";
|
const GENERIC_ERR_MSG = "Не удалось получить отчёт";
|
||||||
|
|
@ -37,7 +44,7 @@ function sanitizeCad(cad: string): string {
|
||||||
return cad.replace(/:/g, "_");
|
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(
|
return `${API_BASE_URL}/api/v1/parcels/${encodeURIComponent(
|
||||||
cad,
|
cad,
|
||||||
)}/forecast/export?format=${format}`;
|
)}/forecast/export?format=${format}`;
|
||||||
|
|
@ -136,6 +143,8 @@ export function ForecastExportButtons({ cad }: Props) {
|
||||||
|
|
||||||
const mdLoading = downloadingFormat === "md";
|
const mdLoading = downloadingFormat === "md";
|
||||||
const jsonLoading = downloadingFormat === "json";
|
const jsonLoading = downloadingFormat === "json";
|
||||||
|
const docxLoading = downloadingFormat === "docx";
|
||||||
|
const pptxLoading = downloadingFormat === "pptx";
|
||||||
const anyLoading = downloadingFormat !== null || tgLoading;
|
const anyLoading = downloadingFormat !== null || tgLoading;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|
@ -176,6 +185,26 @@ export function ForecastExportButtons({ cad }: Props) {
|
||||||
onClick={() => void handleDownload("json")}
|
onClick={() => void handleDownload("json")}
|
||||||
icon={<Download size={14} strokeWidth={1.5} />}
|
icon={<Download size={14} strokeWidth={1.5} />}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
<ExportButton
|
||||||
|
label="Скачать .docx"
|
||||||
|
loadingLabel="Загрузка…"
|
||||||
|
loading={docxLoading}
|
||||||
|
disabled={anyLoading}
|
||||||
|
ariaLabel="Скачать отчёт прогноза в формате Word (.docx)"
|
||||||
|
onClick={() => void handleDownload("docx")}
|
||||||
|
icon={<FileText size={14} strokeWidth={1.5} />}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<ExportButton
|
||||||
|
label="Скачать .pptx"
|
||||||
|
loadingLabel="Загрузка…"
|
||||||
|
loading={pptxLoading}
|
||||||
|
disabled={anyLoading}
|
||||||
|
ariaLabel="Скачать отчёт прогноза в формате PowerPoint (.pptx)"
|
||||||
|
onClick={() => void handleDownload("pptx")}
|
||||||
|
icon={<Presentation size={14} strokeWidth={1.5} />}
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{error && (
|
{error && (
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue