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