From ae4068fb859fd9bc4f02933371ff0d6f7ff0c0d3 Mon Sep 17 00:00:00 2001 From: Marco Pedone Date: Wed, 13 May 2026 14:26:18 +0200 Subject: [PATCH] fix estratti conto --- apps/infoalloggi/package-lock.json | 7 + apps/infoalloggi/package.json | 3 +- .../area-riservata/admin/stripe-reports.tsx | 310 +++++++++--------- .../src/server/api/routers/stripe_reports.ts | 8 +- 4 files changed, 168 insertions(+), 160 deletions(-) diff --git a/apps/infoalloggi/package-lock.json b/apps/infoalloggi/package-lock.json index 8c88e34..ca61240 100644 --- a/apps/infoalloggi/package-lock.json +++ b/apps/infoalloggi/package-lock.json @@ -8,6 +8,7 @@ "name": "infoalloggi", "version": "0.1.0", "dependencies": { + "@date-fns/tz": "^1.4.1", "@dnd-kit/core": "^6.3.1", "@dnd-kit/sortable": "^10.0.0", "@dnd-kit/utilities": "^3.2.2", @@ -626,6 +627,12 @@ "node": ">=20.19.0" } }, + "node_modules/@date-fns/tz": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/@date-fns/tz/-/tz-1.4.1.tgz", + "integrity": "sha512-P5LUNhtbj6YfI3iJjw5EL9eUAG6OitD0W3fWQcpQjDRc/QIsL0tRNuO1PcDvPccWL1fSTXXdE1ds+l95DV/OFA==", + "license": "MIT" + }, "node_modules/@discoveryjs/json-ext": { "version": "0.5.7", "license": "MIT", diff --git a/apps/infoalloggi/package.json b/apps/infoalloggi/package.json index 9031c7c..7925d6a 100644 --- a/apps/infoalloggi/package.json +++ b/apps/infoalloggi/package.json @@ -23,6 +23,7 @@ }, "version": "0.1.0", "dependencies": { + "@date-fns/tz": "^1.4.1", "@dnd-kit/core": "^6.3.1", "@dnd-kit/sortable": "^10.0.0", "@dnd-kit/utilities": "^3.2.2", @@ -160,4 +161,4 @@ "axios": "^1.16.0" } } -} \ No newline at end of file +} diff --git a/apps/infoalloggi/src/pages/area-riservata/admin/stripe-reports.tsx b/apps/infoalloggi/src/pages/area-riservata/admin/stripe-reports.tsx index 739b638..c3beeec 100644 --- a/apps/infoalloggi/src/pages/area-riservata/admin/stripe-reports.tsx +++ b/apps/infoalloggi/src/pages/area-riservata/admin/stripe-reports.tsx @@ -1,3 +1,4 @@ +import { TZDate } from "@date-fns/tz"; import { add, differenceInMonths, @@ -5,13 +6,15 @@ import { format, fromUnixTime, getUnixTime, - isBefore, } from "date-fns"; import { it } from "date-fns/locale"; import { RefreshCcw } from "lucide-react"; import type { GetServerSideProps } from "next"; import Head from "next/head"; +import { useMemo } from "react"; import toast from "react-hot-toast"; +import type Stripe from "stripe"; +import LoadingButton from "~/components/custom_ui/loading-button"; import { LoadingPage } from "~/components/loading"; import { Status500 } from "~/components/status-page"; import { Button } from "~/components/ui/button"; @@ -40,67 +43,50 @@ const reportStatusTitles: Record = { failed: "Fallito", }; -const START_DATE = new Date("2026-01-01"); +const getMonthKey = (unix: number) => { + const date = new Date(unix * 1000); // Unix is usually in seconds, JS needs ms + return `${date.getFullYear()}-${date.getMonth() + 1}`; +}; +const START_DATE = new TZDate("2026-01-01", "UTC"); /** * Pagina di gestione reports stripe per admin: /area-riservata/admin/stripe-reports */ const StripeReports: NextPageWithLayout = () => { const { data, isLoading, refetch } = api.stripe_reports.listReports.useQuery(); - const utils = api.useUtils(); - const { mutate: setupEstrattoConto } = - api.stripe_reports.setupEstrattoConto.useMutation({ - onSuccess: async () => { - toast.success("Report richiesto con successo"); - await utils.stripe_reports.listReports.invalidate(); - }, - onError: (error) => { - toast.error(`Errore nella richiesta del report: ${error.message}`); - }, - }); - const { mutate: generateEstrattoConto } = - api.stripe_reports.generateEstrattoConto.useMutation({ - onMutate: () => { - toast.loading("Generazione estratto conto in corso...", { - id: "generate-pdf", - }); - }, - onSuccess: async (res) => { - if (!res.data) { - toast.error("PDF non disponibile al momento. Riprova più tardi.", { - id: "generate-pdf", - }); - return; - } - toast.success("PDF generato con successo!", { - id: "generate-pdf", - }); - const binaryString = atob(res.data.pdf); - const bytes = Uint8Array.from(binaryString, (c) => c.charCodeAt(0)); - const blob = new Blob([bytes], { - type: "application/pdf", - }); - const url = window.URL.createObjectURL(blob); - const link = document.createElement("a"); - link.href = url; - link.download = res.data.title; - document.body.appendChild(link); - link.click(); - link.remove(); - window.URL.revokeObjectURL(url); - toast.dismiss("generate-pdf"); - }, - onError: (error) => { - toast.error(`Errore nella generazione del report: ${error.message}`, { - id: "generate-pdf", - }); - }, - }); const now = new Date(); const monthsToShow = differenceInMonths(now, START_DATE) + 1; + const mappings = Array.from({ length: monthsToShow }, (_, i) => { + const m = add(START_DATE, { months: i }); + const em = endOfMonth(m); + const um = getUnixTime(m); + const uem = getUnixTime(em); + return { + title: format(m, "MMMM yyyy", { locale: it }), + start: um, + end: uem, + }; + }); + + const reportsByMonth = useMemo(() => { + if (!data || data.length === 0) return {}; + const map: Record = {}; + + data.forEach((report) => { + const start = report.parameters?.interval_start; + if (start) { + const key = getMonthKey(start); + if (!map[key]) map[key] = []; + map[key].push(report); + } + }); + + return map; + }, [data]); + if (isLoading) return ; if (!data) return ; return ( @@ -136,110 +122,13 @@ const StripeReports: NextPageWithLayout = () => { - {Array.from({ length: monthsToShow }, (_, i) => { - const m = add(START_DATE, { months: i }); - const em = endOfMonth(m); - const hasReports = data.filter((report) => { - if ( - !report.parameters?.interval_start || - !report.parameters?.interval_end - ) { - return false; - } - const um = getUnixTime(m); - const uem = getUnixTime(em); - // controlla se il report è allineato al mese in esame - return ( - um >= report.parameters.interval_start && - um <= report.parameters.interval_end && - uem >= report.parameters.interval_start && - uem <= report.parameters.interval_end - ); - }); - - const hasBudgetReport = hasReports - .sort((a, b) => b.created - a.created) - .filter((report) => - report.report_type.startsWith( - "balance_change_from_activity.itemized", - ), - ); - - const hasValidBudgetReport = hasBudgetReport.find( - (report) => - report.status === "succeeded" && - isBefore( - now, - fromUnixTime(report.result?.expires_at ?? 0), - ), - ); - const hasSummaryReport = hasReports - .sort((a, b) => b.created - a.created) - .filter((report) => - report.report_type.startsWith("balance.summary"), - ); - const hasValidSummaryReport = hasSummaryReport.find( - (report) => - report.status === "succeeded" && - isBefore( - now, - fromUnixTime(report.result?.expires_at ?? 0), - ), - ); - - const hasReport = - hasBudgetReport.length > 0 && hasSummaryReport.length > 0; - - const hasReportValid = - hasValidBudgetReport && hasValidSummaryReport; - - return ( - - - {format(m, "MMMM yyyy", { locale: it })} - - - {!hasReportValid ? ( - - ) : ( -
- - -
- )} -
-
- ); - })} + {mappings.map((d) => ( + + ))}
@@ -301,6 +190,117 @@ const StripeReports: NextPageWithLayout = () => { ); }; +const MonthRow = ({ + title, + start, + end, + reports, +}: { + title: string; + start: number; + end: number; + reports: Stripe.Reporting.ReportRun[]; +}) => { + const utils = api.useUtils(); + const { mutate: setupEstrattoConto } = + api.stripe_reports.setupEstrattoConto.useMutation({ + onSuccess: async () => { + toast.success("Report richiesto con successo"); + await utils.stripe_reports.listReports.invalidate(); + }, + onError: (error) => { + toast.error(`Errore nella richiesta del report: ${error.message}`); + }, + }); + const { mutate: generateEstrattoConto } = + api.stripe_reports.generateEstrattoConto.useMutation({ + onMutate: () => { + toast.loading("Generazione estratto conto in corso...", { + id: "generate-pdf", + }); + }, + onSuccess: async (res) => { + if (!res.data) { + toast.error("PDF non disponibile al momento. Riprova più tardi.", { + id: "generate-pdf", + }); + return; + } + toast.success("PDF generato con successo!", { + id: "generate-pdf", + }); + + const binaryString = atob(res.data.pdf); + const bytes = Uint8Array.from(binaryString, (c) => c.charCodeAt(0)); + const blob = new Blob([bytes], { + type: "application/pdf", + }); + const url = window.URL.createObjectURL(blob); + const link = document.createElement("a"); + link.href = url; + link.download = res.data.title; + document.body.appendChild(link); + link.click(); + link.remove(); + window.URL.revokeObjectURL(url); + toast.dismiss("generate-pdf"); + }, + onError: (error) => { + toast.error(`Errore nella generazione del report: ${error.message}`, { + id: "generate-pdf", + }); + }, + }); + + const isGenerating = reports.some((r) => r.status === "pending"); + + const hasValidBudgetReport = reports.find( + (r) => + r.report_type.startsWith("balance_change_from_activity.itemized") && + r.status === "succeeded", + ); + + const hasValidSummaryReport = reports.find( + (r) => + r.report_type.startsWith("balance.summary") && r.status === "succeeded", + ); + + return ( + + {title} + + setupEstrattoConto({ start, end })} + size="sm" + variant={isGenerating ? "info" : "default"} + > + {isGenerating ? "Generazione in corso" : "Genera Report"} + + {hasValidBudgetReport && hasValidSummaryReport && ( + + )} + + + ); +}; + export const getServerSideProps = (async (context) => { const access_token = context.req.cookies.access_token; diff --git a/apps/infoalloggi/src/server/api/routers/stripe_reports.ts b/apps/infoalloggi/src/server/api/routers/stripe_reports.ts index 5371032..acab1c0 100644 --- a/apps/infoalloggi/src/server/api/routers/stripe_reports.ts +++ b/apps/infoalloggi/src/server/api/routers/stripe_reports.ts @@ -1,5 +1,5 @@ import { TRPCError } from "@trpc/server"; -import { endOfMonth, format, getUnixTime } from "date-fns"; +import { format } from "date-fns"; import { it } from "date-fns/locale"; import Papa from "papaparse"; import { z } from "zod/v4"; @@ -23,12 +23,12 @@ export const stripeReportsRouter = createTRPCRouter({ setupEstrattoConto: adminProcedure .input( z.object({ - start: z.date(), + start: z.number(), + end: z.number(), }), ) .mutation(async ({ input }) => { - const start = getUnixTime(input.start); - const end = getUnixTime(endOfMonth(input.start)); + const { start, end } = input; try { await stripe.reporting.reportRuns.create({