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 ----------
 |   // ---------- RENDER ----------
 | ||||||
|   if (!authChecked) { |   if (!authChecked) { | ||||||
|     return <div>Checking authentication…</div>; |     return <div>Checking authentication…</div>; | ||||||
| @ -459,25 +500,154 @@ useEffect(() => { | |||||||
|             /> |             /> | ||||||
| 
 | 
 | ||||||
|             <div className="flex flex-col md:flex-row gap-4 justify-center"> |             <div className="flex flex-col md:flex-row gap-4 justify-center"> | ||||||
|               <button |               <button onClick={handlePDFExport} className="text-sm lg:text-lg btn-primary"> | ||||||
|                 onClick={handlePDFExport} |  | ||||||
|                 className="text-sm lg:text-lg btn-primary" |  | ||||||
|               > |  | ||||||
|                 Export Chart Images to PDF |                 Export Chart Images to PDF | ||||||
|               </button> |               </button> | ||||||
| 
 | 
 | ||||||
|               <a |               <button | ||||||
|                 href={`https://drive.google.com/drive/folders/${process.env.NEXT_PUBLIC_GOOGLE_DRIVE_FOLDER_ID}`} |                 onClick={() => setIsDownloadOpen(true)} | ||||||
|                 target="_blank" |  | ||||||
|                 rel="noopener noreferrer" |  | ||||||
|                 className="text-sm lg:text-lg btn-primary" |                 className="text-sm lg:text-lg btn-primary" | ||||||
|               > |               > | ||||||
|                 View Excel Logs |                 Download Excel Log | ||||||
|               </a> |               </button> | ||||||
|             </div> |             </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> |       </div> | ||||||
|     </DashboardLayout> |     </DashboardLayout> | ||||||
|  | |||||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user