disable download and email service when there is no report data
This commit is contained in:
parent
50bc85ff2a
commit
d54fc1c2ab
|
|
@ -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]);
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
Loading…
Reference in New Issue