download excel
All checks were successful
Build and Deploy / build-and-deploy (push) Successful in 2m51s
All checks were successful
Build and Deploy / build-and-deploy (push) Successful in 2m51s
This commit is contained in:
parent
eac2bb51e2
commit
ed131acab4
@ -336,6 +336,47 @@ useEffect(() => {
|
||||
}
|
||||
};
|
||||
|
||||
// helpers
|
||||
// helpers
|
||||
const ymd = (d: Date) => d.toISOString().slice(0, 10);
|
||||
const excelUrl = (site: string, device: string, fn: 'grid' | 'solar', dateYMD: string) =>
|
||||
`${API}/excel-fs/${encodeURIComponent(site)}/${encodeURIComponent(device)}/${fn}/${dateYMD}.xlsx`;
|
||||
|
||||
// popup state
|
||||
const [isDownloadOpen, setIsDownloadOpen] = useState(false);
|
||||
const [meter, setMeter] = useState('01'); // ADW300 device id
|
||||
const [fn, setFn] = useState<'grid' | 'solar'>('grid'); // which function
|
||||
const [downloadDate, setDownloadDate] = useState(ymd(new Date())); // YYYY-MM-DD
|
||||
const [downloading, setDownloading] = useState(false);
|
||||
|
||||
// action
|
||||
const downloadExcel = async () => {
|
||||
if (!selectedProject) return;
|
||||
try {
|
||||
setDownloading(true);
|
||||
const url = excelUrl(selectedProject.name, meter.trim(), fn, downloadDate);
|
||||
const resp = await fetch(url, { credentials: 'include' });
|
||||
if (!resp.ok) {
|
||||
const text = await resp.text().catch(() => '');
|
||||
throw new Error(text || `HTTP ${resp.status}`);
|
||||
}
|
||||
const blob = await resp.blob();
|
||||
const a = document.createElement('a');
|
||||
a.href = URL.createObjectURL(blob);
|
||||
a.download = `${meter}_${fn}_${downloadDate}.xlsx`;
|
||||
document.body.appendChild(a);
|
||||
a.click();
|
||||
a.remove();
|
||||
URL.revokeObjectURL(a.href);
|
||||
setIsDownloadOpen(false);
|
||||
} catch (e: any) {
|
||||
alert(`Download failed: ${e?.message ?? e}`);
|
||||
} finally {
|
||||
setDownloading(false);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
// ---------- RENDER ----------
|
||||
if (!authChecked) {
|
||||
return <div>Checking authentication…</div>;
|
||||
@ -459,25 +500,154 @@ useEffect(() => {
|
||||
/>
|
||||
|
||||
<div className="flex flex-col md:flex-row gap-4 justify-center">
|
||||
<button
|
||||
onClick={handlePDFExport}
|
||||
className="text-sm lg:text-lg btn-primary"
|
||||
>
|
||||
<button onClick={handlePDFExport} className="text-sm lg:text-lg btn-primary">
|
||||
Export Chart Images to PDF
|
||||
</button>
|
||||
|
||||
<a
|
||||
href={`https://drive.google.com/drive/folders/${process.env.NEXT_PUBLIC_GOOGLE_DRIVE_FOLDER_ID}`}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
<button
|
||||
onClick={() => setIsDownloadOpen(true)}
|
||||
className="text-sm lg:text-lg btn-primary"
|
||||
>
|
||||
View Excel Logs
|
||||
</a>
|
||||
Download Excel Log
|
||||
</button>
|
||||
</div>
|
||||
|
||||
</>
|
||||
)}
|
||||
|
||||
{isDownloadOpen && (
|
||||
<div
|
||||
className="fixed inset-0 z-50 flex items-center justify-center"
|
||||
aria-modal="true"
|
||||
role="dialog"
|
||||
onKeyDown={(e) => e.key === 'Escape' && setIsDownloadOpen(false)}
|
||||
>
|
||||
{/* Backdrop */}
|
||||
<div
|
||||
className="absolute inset-0 bg-black/50"
|
||||
onClick={() => setIsDownloadOpen(false)}
|
||||
/>
|
||||
|
||||
{/* Modal */}
|
||||
<div className="relative w-full max-w-lg mx-4 rounded-2xl bg-white dark:bg-rtgray-800 shadow-2xl">
|
||||
<div className="p-5 sm:p-6 border-b border-black/5 dark:border-white/10">
|
||||
<h3 className="text-lg font-semibold text-black/90 dark:text-white">
|
||||
Download Excel Log
|
||||
</h3>
|
||||
<p className="mt-1 text-sm text-black/60 dark:text-white/60">
|
||||
Choose device, function, and date to export the .xlsx generated by the logger.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className="p-5 sm:p-6 space-y-5">
|
||||
{/* Site (read-only preview) */}
|
||||
<div>
|
||||
<label className="block text-sm opacity-80 mb-1 dark:text-white">Site</label>
|
||||
<div className="px-3 py-2 rounded-lg bg-black/5 dark:bg-white/5 text-sm truncate dark:text-white">
|
||||
{selectedProject?.project_name || selectedProject?.name}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Device + Function */}
|
||||
<div className="grid grid-cols-1 sm:grid-cols-2 gap-4">
|
||||
<div>
|
||||
<label className="block text-sm opacity-80 mb-1 dark:text-white">Meter (Device)</label>
|
||||
<input
|
||||
value={meter}
|
||||
onChange={(e) => setMeter(e.target.value)}
|
||||
placeholder="01"
|
||||
className="input input-bordered w-full pl-2 rounded-lg"
|
||||
/>
|
||||
<p className="mt-1 text-xs opacity-70 dark:text-white">
|
||||
Matches topic: <code>ADW300/<site>/<b>{meter || '01'}</b>/…</code>
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label className="block text-sm opacity-80 mb-1 dark:text-white">Function</label>
|
||||
<div className="flex rounded-xl overflow-hidden border border-black/10 dark:border-white/10">
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => setFn('grid')}
|
||||
className={`flex-1 px-3 py-2 text-sm ${
|
||||
fn === 'grid'
|
||||
? 'bg-rtyellow-200 text-black'
|
||||
: 'bg-black/5 dark:bg-white/5 hover:bg-black/10 dark:hover:bg-white/10 dark:text-white'
|
||||
}`}
|
||||
>
|
||||
Grid
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => setFn('solar')}
|
||||
className={`flex-1 px-3 py-2 text-sm ${
|
||||
fn === 'solar'
|
||||
? 'bg-rtyellow-200 text-black'
|
||||
: 'bg-black/5 dark:bg-white/5 hover:bg-black/10 dark:hover:bg-white/10 dark:text-white'
|
||||
}`}
|
||||
>
|
||||
Solar
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Date + quick picks */}
|
||||
<div>
|
||||
<label className="block text-sm opacity-80 mb-1 dark:text-white">Date</label>
|
||||
<div className="flex items-center gap-3">
|
||||
<input
|
||||
type="date"
|
||||
value={downloadDate}
|
||||
onChange={(e) => setDownloadDate(e.target.value)}
|
||||
className="input input-bordered w-48 pl-2 rounded-lg"
|
||||
/>
|
||||
<div className="flex gap-2">
|
||||
<button
|
||||
type="button"
|
||||
className="px-3 py-1 rounded-full text-xs border border-black/10 dark:border-white/15 hover:bg-black/5 dark:hover:bg-white/10 dark:text-white"
|
||||
onClick={() => setDownloadDate(ymd(new Date()))}
|
||||
>
|
||||
Today
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
className="px-3 py-1 rounded-full text-xs border border-black/10 dark:border-white/15 hover:bg-black/5 dark:hover:bg:white/10 dark:text-white"
|
||||
onClick={() => {
|
||||
const d = new Date();
|
||||
d.setDate(d.getDate() - 1);
|
||||
setDownloadDate(ymd(d));
|
||||
}}
|
||||
>
|
||||
Yesterday
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Footer */}
|
||||
<div className="p-5 sm:p-6 flex justify-end gap-3 border-t border-black/5 dark:border-white/10">
|
||||
<button
|
||||
type="button"
|
||||
className="btn btn-primary bg-red-500 hover:bg-red-600 border-transparent"
|
||||
onClick={() => setIsDownloadOpen(false)}
|
||||
disabled={downloading}
|
||||
>
|
||||
Cancel
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
className="btn btn-primary border-transparent"
|
||||
onClick={downloadExcel}
|
||||
disabled={downloading || !meter || !downloadDate}
|
||||
>
|
||||
{downloading ? 'Preparing…' : 'Download'}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
</div>
|
||||
</DashboardLayout>
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user