From c8f62b6e9511ecf0b49ddfe8c8899470f8276f7d Mon Sep 17 00:00:00 2001 From: Bohdan Garchu Date: Thu, 19 Dec 2024 21:56:54 +0100 Subject: [PATCH] feat: year in review reports --- src/app/download-portal/page.tsx | 13 ++- .../DownloadPortal/CountryReports.tsx | 12 +- .../DownloadPortal/YearInReviewReports.tsx | 52 +++++++++ src/domain/props/YearInReviewReportsProps.ts | 5 + .../DownloadPortalOperations.tsx | 110 ++++++++++++++++-- 5 files changed, 176 insertions(+), 16 deletions(-) create mode 100644 src/components/DownloadPortal/YearInReviewReports.tsx create mode 100644 src/domain/props/YearInReviewReportsProps.ts diff --git a/src/app/download-portal/page.tsx b/src/app/download-portal/page.tsx index 6d50a324..05b08ad1 100644 --- a/src/app/download-portal/page.tsx +++ b/src/app/download-portal/page.tsx @@ -1,6 +1,7 @@ import AccordionContainer from '@/components/Accordions/AccordionContainer'; import DownloadCountryAccordion from '@/components/DownloadCountryAccordions/DownloadCountryAccordions'; import CountryReports from '@/components/DownloadPortal/CountryReports'; +import YearInReviewReports from '@/components/DownloadPortal/YearInReviewReports'; import container from '@/container'; import { TITLE } from '@/domain/entities/download/Country'; import { GlobalDataRepository } from '@/domain/repositories/GlobalDataRepository'; @@ -9,7 +10,12 @@ export default async function DownloadPortal() { const globalRepo = container.resolve('GlobalDataRepository'); const countryMapDataPromise = globalRepo.getMapDataForCountries(); const countryCodesPromise = globalRepo.getCountryCodes(); - const [countryCodesData, countryMapData] = await Promise.all([countryCodesPromise, countryMapDataPromise]); + const yearInReviewsPromise = globalRepo.getYearInReviews(); + const [countryCodesData, countryMapData, yearInReviews] = await Promise.all([ + countryCodesPromise, + countryMapDataPromise, + yearInReviewsPromise, + ]); const countries = countryMapData?.features.map((feature) => ({ @@ -24,7 +30,12 @@ export default async function DownloadPortal() {

Download Portal

, + }, { title: 'Country Reports', content: , diff --git a/src/components/DownloadPortal/CountryReports.tsx b/src/components/DownloadPortal/CountryReports.tsx index 4ebda77c..0ebf493f 100644 --- a/src/components/DownloadPortal/CountryReports.tsx +++ b/src/components/DownloadPortal/CountryReports.tsx @@ -6,6 +6,7 @@ import PdfPreview from '@/components/Pdf/PdfPreview'; import SearchBar from '@/components/Search/SearchBar'; import CustomTable from '@/components/Table/CustomTable'; import { useChatbot } from '@/domain/contexts/ChatbotContext'; +import { useSnackbar } from '@/domain/contexts/SnackbarContext'; import { CountryCodesData } from '@/domain/entities/country/CountryCodesData'; import CountryReportsProps from '@/domain/props/CountryReportsProps'; import { PdfFile } from '@/domain/props/PdfViewerProps'; @@ -18,8 +19,8 @@ export default function CountryReports({ countryCodesData }: CountryReportsProps const [error, setError] = useState(null); const [selectedCountry, setSelectedCountry] = useState(null); - const chatBot = useChatbot(); - const { initiateChatAboutReport } = chatBot; + const { initiateChatAboutReport } = useChatbot(); + const { showSnackBar } = useSnackbar(); const toggleModal = () => setModalOpen((prev) => !prev); @@ -34,13 +35,14 @@ export default function CountryReports({ countryCodesData }: CountryReportsProps
@@ -51,7 +53,7 @@ export default function CountryReports({ countryCodesData }: CountryReportsProps error={error} onDownloadPdf={() => { if (selectedCountry) { - DownloadPortalOperations.downloadPdf(selectedCountry); + DownloadPortalOperations.downloadCountryReport(selectedCountry, showSnackBar); } }} /> diff --git a/src/components/DownloadPortal/YearInReviewReports.tsx b/src/components/DownloadPortal/YearInReviewReports.tsx new file mode 100644 index 00000000..05734986 --- /dev/null +++ b/src/components/DownloadPortal/YearInReviewReports.tsx @@ -0,0 +1,52 @@ +'use client'; + +import { useState } from 'react'; + +import { useChatbot } from '@/domain/contexts/ChatbotContext'; +import { useSnackbar } from '@/domain/contexts/SnackbarContext'; +import { YearInReview } from '@/domain/entities/YearInReview'; +import { PdfFile } from '@/domain/props/PdfViewerProps'; +import YearInReviewReportsProps from '@/domain/props/YearInReviewReportsProps'; +import { DownloadPortalOperations } from '@/operations/download-portal/DownloadPortalOperations'; + +import PdfPreview from '../Pdf/PdfPreview'; +import CustomTable from '../Table/CustomTable'; + +export default function YearInReviewReports({ yearInReviewReports }: YearInReviewReportsProps) { + const [isModalOpen, setModalOpen] = useState(false); + const [pdfFile, setPdfFile] = useState(null); + const [error, setError] = useState(null); + const [selectedReport, setSelectedReport] = useState(null); + const { initiateChatAboutReport } = useChatbot(); + const toggleModal = () => setModalOpen((prev) => !prev); + const { showSnackBar } = useSnackbar(); + + return ( +
+ + { + if (selectedReport) { + DownloadPortalOperations.downloadYearInReview(selectedReport, showSnackBar); + } + }} + /> +
+ ); +} diff --git a/src/domain/props/YearInReviewReportsProps.ts b/src/domain/props/YearInReviewReportsProps.ts new file mode 100644 index 00000000..ba7336fa --- /dev/null +++ b/src/domain/props/YearInReviewReportsProps.ts @@ -0,0 +1,5 @@ +import { YearInReview } from '../entities/YearInReview'; + +export default interface YearInReviewReportsProps { + yearInReviewReports: YearInReview[]; +} diff --git a/src/operations/download-portal/DownloadPortalOperations.tsx b/src/operations/download-portal/DownloadPortalOperations.tsx index e92724cc..68649e98 100644 --- a/src/operations/download-portal/DownloadPortalOperations.tsx +++ b/src/operations/download-portal/DownloadPortalOperations.tsx @@ -4,7 +4,11 @@ import { Bot } from 'lucide-react'; import { CountryCodesData } from '@/domain/entities/country/CountryCodesData'; import { ICountryData } from '@/domain/entities/download/Country'; +import { SNACKBAR_SHORT_DURATION } from '@/domain/entities/snackbar/Snackbar'; +import { YearInReview } from '@/domain/entities/YearInReview'; +import { SnackbarPosition, SnackbarStatus } from '@/domain/enums/Snackbar'; import { CustomTableColumns } from '@/domain/props/CustomTableProps'; +import { SnackbarProps } from '@/domain/props/SnackbarProps'; export class DownloadPortalOperations { static getColumns(): CustomTableColumns { @@ -16,13 +20,14 @@ export class DownloadPortalOperations { ] as CustomTableColumns; } - static formatTableData( + static formatCountryTableData( data: CountryCodesData[], setSelectedCountry: (countryData: CountryCodesData) => void, initiateChatAboutReport: (country: string, report: string) => Promise, setPdfFile: (file: Blob | null) => void, setError: (error: string | null) => void, - toggleModal: () => void + toggleModal: () => void, + showSnackBar: (snackbarProps: SnackbarProps) => void ) { return data.map((item) => ({ keyColumn: item.country.name, @@ -41,7 +46,7 @@ export class DownloadPortalOperations {
DownloadPortalOperations.downloadPdf(item)} + onClick={() => DownloadPortalOperations.downloadCountryReport(item, showSnackBar)} className="cursor-pointer" />
@@ -58,6 +63,53 @@ export class DownloadPortalOperations { })); } + static formatYearInReviewTableData( + data: YearInReview[], + setSelectedReport: (report: YearInReview) => void, + initiateChatAboutReport: (country: string, report: string) => Promise, + setPdfFile: (file: Blob | null) => void, + setError: (error: string | null) => void, + toggleModal: () => void, + showSnackBar: (snackbarProps: SnackbarProps) => void + ) { + return data.map((item) => ({ + keyColumn: item.label, + preview: ( +
+ { + setSelectedReport(item); + DownloadPortalOperations.handlePreview(item.url, setPdfFile, setError); + toggleModal(); + }} + className="cursor-pointer" + /> +
+ ), + download: ( +
+ DownloadPortalOperations.downloadYearInReview(item, showSnackBar)} + className="cursor-pointer" + /> +
+ ), + chat: ( +
+ { + initiateChatAboutReport(item.label, item.url); + }} + className="cursor-pointer" + /> +
+ ), + })); + } + static downloadJsonFile(data: ICountryData[], country: string): void { const a = document.createElement('a'); const file = new Blob([JSON.stringify(data)], { type: 'application/json' }); @@ -77,13 +129,51 @@ export class DownloadPortalOperations { return Math.ceil(diffTime / (1000 * 60 * 60 * 24)); } - static downloadPdf(country: CountryCodesData) { - const link = document.createElement('a'); - link.href = country.url.summary; - link.download = `${country.country.name}.pdf`; - document.body.appendChild(link); - link.click(); - document.body.removeChild(link); + static async downloadCountryReport( + country: CountryCodesData, + showSnackBar: (snackbarProps: SnackbarProps) => void + ): Promise { + await this.downloadFile(country.url.summary, `${country.country.name}.pdf`, showSnackBar); + } + + static async downloadYearInReview( + yearInReview: YearInReview, + showSnackBar: (snackbarProps: SnackbarProps) => void + ): Promise { + await this.downloadFile(yearInReview.url, `${yearInReview.label}.pdf`, showSnackBar); + } + + private static async downloadFile( + fileUrl: string, + fileName: string, + showSnackBar?: (snackbarProps: SnackbarProps) => void + ): Promise { + try { + const response = await fetch(fileUrl); + if (!response.ok) throw new Error('Failed to download file'); + + const blob = await response.blob(); + const url = window.URL.createObjectURL(blob); + + const link = document.createElement('a'); + link.href = url; + link.download = fileName; + document.body.appendChild(link); + link.click(); + document.body.removeChild(link); + + // Cleanup URL object + window.URL.revokeObjectURL(url); + } catch { + if (showSnackBar) { + showSnackBar({ + message: 'Error downloading file', + status: SnackbarStatus.Error, + position: SnackbarPosition.BottomRight, + duration: SNACKBAR_SHORT_DURATION, + }); + } + } } static async fetchPdfAsByteStream(url: string): Promise {