disable download and email service when there is no report data

This commit is contained in:
Yinyin Liu 2026-03-12 14:56:26 +01:00
parent 50bc85ff2a
commit d54fc1c2ab
2 changed files with 112 additions and 60 deletions

View File

@ -96,9 +96,11 @@ function getCurrentWeekDays(currentMonday: Date): Date[] {
// ── Main Component ───────────────────────────────────────────
export default function DailySection({
installationId
installationId,
onHasData
}: {
installationId: number;
onHasData?: (hasData: boolean) => void;
}) {
const intl = useIntl();
const currentMonday = useMemo(() => getCurrentMonday(), []);
@ -138,10 +140,12 @@ export default function DailySection({
const hourly = res.data?.hourlyRecords?.records ?? [];
setAllRecords(Array.isArray(daily) ? daily : []);
setAllHourlyRecords(Array.isArray(hourly) ? hourly : []);
onHasData?.(Array.isArray(daily) && daily.length > 0);
})
.catch(() => {
setAllRecords([]);
setAllHourlyRecords([]);
onHasData?.(false);
})
.finally(() => setLoadingWeek(false));
}, [installationId, weekDays]);

View File

@ -1,4 +1,4 @@
import { useEffect, useState } from 'react';
import { useEffect, useImperativeHandle, useRef, useState, forwardRef } from 'react';
import { useIntl, FormattedMessage } from 'react-intl';
import {
Accordion,
@ -231,6 +231,12 @@ function WeeklyReport({ installationId }: WeeklyReportProps) {
const [pendingMonths, setPendingMonths] = useState<PendingMonth[]>([]);
const [pendingYears, setPendingYears] = useState<PendingYear[]>([]);
const [generating, setGenerating] = useState<string | null>(null);
const [selectedMonthlyIdx, setSelectedMonthlyIdx] = useState(0);
const [selectedYearlyIdx, setSelectedYearlyIdx] = useState(0);
const [regenerating, setRegenerating] = useState(false);
const [dailyHasData, setDailyHasData] = useState(false);
const [weeklyHasData, setWeeklyHasData] = useState(false);
const weeklyRef = useRef<WeeklySectionHandle>(null);
const fetchReportData = () => {
const lang = intl.locale;
@ -287,6 +293,15 @@ function WeeklyReport({ installationId }: WeeklyReportProps) {
const safeTab = Math.min(activeTab, tabs.length - 1);
const activeTabHasData = (() => {
const key = tabs[safeTab]?.key;
if (key === 'daily') return dailyHasData;
if (key === 'weekly') return weeklyHasData;
if (key === 'monthly') return monthlyReports.length > 0;
if (key === 'yearly') return yearlyReports.length > 0;
return false;
})();
return (
<Box sx={{ p: 2, width: '100%', maxWidth: 900, mx: 'auto' }} className="report-container">
<style>{`
@ -305,6 +320,7 @@ function WeeklyReport({ installationId }: WeeklyReportProps) {
>
{tabs.map(t => <Tab key={t.key} label={t.label} />)}
</Tabs>
{activeTabHasData && (
<Button
variant="outlined"
startIcon={<DownloadIcon />}
@ -313,19 +329,50 @@ function WeeklyReport({ installationId }: WeeklyReportProps) {
>
<FormattedMessage id="downloadPdf" defaultMessage="Download PDF" />
</Button>
)}
{tabs[safeTab]?.key !== 'daily' && activeTabHasData && (
<Button
variant="outlined"
disabled={regenerating || generating !== null}
startIcon={(regenerating || generating !== null) ? <CircularProgress size={16} /> : <RefreshIcon />}
onClick={async () => {
const key = tabs[safeTab]?.key;
if (key === 'weekly') {
weeklyRef.current?.regenerate();
} else if (key === 'monthly') {
const r = monthlyReports[selectedMonthlyIdx];
if (r) {
setRegenerating(true);
try { await handleGenerateMonthly(r.year, r.month); } finally { setRegenerating(false); }
}
} else if (key === 'yearly') {
const r = yearlyReports[selectedYearlyIdx];
if (r) {
setRegenerating(true);
try { await handleGenerateYearly(r.year); } finally { setRegenerating(false); }
}
}
}}
sx={{ ml: 1, whiteSpace: 'nowrap' }}
>
<FormattedMessage id="regenerateReport" defaultMessage="Regenerate" />
</Button>
)}
</Box>
<Box sx={{ display: tabs[safeTab]?.key === 'daily' ? 'block' : 'none', minHeight: '50vh' }}>
<DailySection installationId={installationId} />
<DailySection installationId={installationId} onHasData={setDailyHasData} />
</Box>
<Box sx={{ display: tabs[safeTab]?.key === 'weekly' ? 'block' : 'none', minHeight: '50vh' }}>
<WeeklySection
ref={weeklyRef}
installationId={installationId}
latestMonthlyPeriodEnd={
monthlyReports.length > 0
? monthlyReports.reduce((a, b) => a.periodEnd > b.periodEnd ? a : b).periodEnd
: null
}
onHasData={setWeeklyHasData}
/>
</Box>
<Box sx={{ display: tabs[safeTab]?.key === 'monthly' ? 'block' : 'none', minHeight: '50vh' }}>
@ -335,6 +382,8 @@ function WeeklyReport({ installationId }: WeeklyReportProps) {
pendingMonths={pendingMonths}
generating={generating}
onGenerate={handleGenerateMonthly}
selectedIdx={selectedMonthlyIdx}
onSelectedIdxChange={setSelectedMonthlyIdx}
/>
</Box>
<Box sx={{ display: tabs[safeTab]?.key === 'yearly' ? 'block' : 'none', minHeight: '50vh' }}>
@ -344,6 +393,8 @@ function WeeklyReport({ installationId }: WeeklyReportProps) {
pendingYears={pendingYears}
generating={generating}
onGenerate={handleGenerateYearly}
selectedIdx={selectedYearlyIdx}
onSelectedIdxChange={setSelectedYearlyIdx}
/>
</Box>
</Box>
@ -352,7 +403,12 @@ function WeeklyReport({ installationId }: WeeklyReportProps) {
// ── Weekly Section (existing weekly report content) ────────────
function WeeklySection({ installationId, latestMonthlyPeriodEnd }: { installationId: number; latestMonthlyPeriodEnd: string | null }) {
interface WeeklySectionHandle {
regenerate: () => void;
}
const WeeklySection = forwardRef<WeeklySectionHandle, { installationId: number; latestMonthlyPeriodEnd: string | null; onHasData?: (hasData: boolean) => void }>(
({ installationId, latestMonthlyPeriodEnd, onHasData }, ref) => {
const intl = useIntl();
const [report, setReport] = useState<WeeklyReportResponse | null>(null);
const [loading, setLoading] = useState(true);
@ -370,17 +426,21 @@ function WeeklySection({ installationId, latestMonthlyPeriodEnd }: { installatio
params: { installationId, language: intl.locale, forceRegenerate }
});
setReport(res.data);
onHasData?.(true);
} catch (err: any) {
const msg =
err.response?.data ||
err.message ||
intl.formatMessage({ id: 'failedToLoadReport' });
setError(typeof msg === 'string' ? msg : JSON.stringify(msg));
onHasData?.(false);
} finally {
setLoading(false);
}
};
useImperativeHandle(ref, () => ({ regenerate: () => fetchReport(true) }));
const handleSendEmail = async (emailAddress: string) => {
await axiosConfig.post('/SendWeeklyReportEmail', null, {
params: { installationId, emailAddress }
@ -464,8 +524,6 @@ function WeeklySection({ installationId, latestMonthlyPeriodEnd }: { installatio
borderRadius: 2
}}
>
<Box sx={{ display: 'flex', justifyContent: 'space-between', alignItems: 'flex-start' }}>
<Box>
<Typography variant="h5" fontWeight="bold">
<FormattedMessage id="reportTitle" defaultMessage="Weekly Performance Report" />
</Typography>
@ -475,17 +533,6 @@ function WeeklySection({ installationId, latestMonthlyPeriodEnd }: { installatio
<Typography variant="body2" sx={{ opacity: 0.7 }}>
{report.periodStart} {report.periodEnd}
</Typography>
</Box>
<Button
variant="outlined"
size="small"
startIcon={<RefreshIcon />}
onClick={() => fetchReport(true)}
sx={{ color: '#fff', borderColor: 'rgba(255,255,255,0.5)', '&:hover': { borderColor: '#fff' } }}
>
<FormattedMessage id="regenerateReport" defaultMessage="Regenerate" />
</Button>
</Box>
</Paper>
{/* Missing days warning */}
@ -638,7 +685,7 @@ function WeeklySection({ installationId, latestMonthlyPeriodEnd }: { installatio
/>
</>
);
}
});
// ── Weekly History (saved weekly reports for current month) ─────
@ -762,13 +809,17 @@ function MonthlySection({
reports,
pendingMonths,
generating,
onGenerate
onGenerate,
selectedIdx,
onSelectedIdxChange
}: {
installationId: number;
reports: MonthlyReport[];
pendingMonths: PendingMonth[];
generating: string | null;
onGenerate: (year: number, month: number) => void;
selectedIdx: number;
onSelectedIdxChange: (idx: number) => void;
}) {
const intl = useIntl();
@ -818,7 +869,8 @@ function MonthlySection({
countFn={(r: MonthlyReport) => r.weekCount}
sendEndpoint="/SendMonthlyReportEmail"
sendParamsFn={(r: MonthlyReport) => ({ installationId, year: r.year, month: r.month })}
onRegenerate={(r: MonthlyReport) => onGenerate(r.year, r.month)}
controlledIdx={selectedIdx}
onIdxChange={onSelectedIdxChange}
/>
) : pendingMonths.length === 0 ? (
<Box sx={{ display: 'flex', justifyContent: 'center', alignItems: 'center', py: 6 }}>
@ -838,13 +890,17 @@ function YearlySection({
reports,
pendingYears,
generating,
onGenerate
onGenerate,
selectedIdx,
onSelectedIdxChange
}: {
installationId: number;
reports: YearlyReport[];
pendingYears: PendingYear[];
generating: string | null;
onGenerate: (year: number) => void;
selectedIdx: number;
onSelectedIdxChange: (idx: number) => void;
}) {
const intl = useIntl();
@ -894,7 +950,8 @@ function YearlySection({
countFn={(r: YearlyReport) => r.monthCount}
sendEndpoint="/SendYearlyReportEmail"
sendParamsFn={(r: YearlyReport) => ({ installationId, year: r.year })}
onRegenerate={(r: YearlyReport) => onGenerate(r.year)}
controlledIdx={selectedIdx}
onIdxChange={onSelectedIdxChange}
/>
) : pendingYears.length === 0 ? (
<Box sx={{ display: 'flex', justifyContent: 'center', alignItems: 'center', py: 6 }}>
@ -917,7 +974,8 @@ function AggregatedSection<T extends ReportSummary>({
countFn,
sendEndpoint,
sendParamsFn,
onRegenerate
controlledIdx,
onIdxChange
}: {
reports: T[];
type: 'monthly' | 'yearly';
@ -926,11 +984,16 @@ function AggregatedSection<T extends ReportSummary>({
countFn: (r: T) => number;
sendEndpoint: string;
sendParamsFn: (r: T) => object;
onRegenerate?: (r: T) => void | Promise<void>;
controlledIdx?: number;
onIdxChange?: (idx: number) => void;
}) {
const intl = useIntl();
const [selectedIdx, setSelectedIdx] = useState(0);
const [regenerating, setRegenerating] = useState(false);
const [internalIdx, setInternalIdx] = useState(0);
const selectedIdx = controlledIdx ?? internalIdx;
const handleIdxChange = (idx: number) => {
setInternalIdx(idx);
onIdxChange?.(idx);
};
if (reports.length === 0) {
return (
@ -966,7 +1029,7 @@ function AggregatedSection<T extends ReportSummary>({
{reports.length > 1 && (
<Select
value={selectedIdx}
onChange={(e) => setSelectedIdx(Number(e.target.value))}
onChange={(e) => handleIdxChange(Number(e.target.value))}
size="small"
sx={{ minWidth: 200 }}
>
@ -975,22 +1038,7 @@ function AggregatedSection<T extends ReportSummary>({
))}
</Select>
)}
<Box sx={{ ml: 'auto', display: 'flex', alignItems: 'center', gap: 1 }}>
{onRegenerate && (
<Button
variant="outlined"
size="small"
disabled={regenerating}
startIcon={regenerating ? <CircularProgress size={14} /> : <RefreshIcon />}
onClick={async () => {
setRegenerating(true);
try { await onRegenerate(r); } finally { setRegenerating(false); }
}}
sx={{ textTransform: 'none' }}
>
<FormattedMessage id="regenerateReport" defaultMessage="Regenerate" />
</Button>
)}
<Box sx={{ ml: 'auto' }}>
<EmailBar onSend={handleSendEmail} />
</Box>
</Box>