All checks were successful
		
		
	
	Build and Deploy / build-and-deploy (push) Successful in 2m50s
				
			
		
			
				
	
	
		
			71 lines
		
	
	
		
			2.1 KiB
		
	
	
	
		
			TypeScript
		
	
	
	
	
	
			
		
		
	
	
			71 lines
		
	
	
		
			2.1 KiB
		
	
	
	
		
			TypeScript
		
	
	
	
	
	
| // utils/export.ts
 | |
| export type ExportParams = {
 | |
|   baseUrl?: string;               // e.g. process.env.NEXT_PUBLIC_API_URL
 | |
|   site: string;                   // PROJ-0028
 | |
|   suffix?: "grid" | "solar";      // default "grid"
 | |
|   serial?: string | null;         // device id like "01"
 | |
|   day?: string;                   // "YYYY-MM-DD" (preferred)
 | |
|   start?: string;                 // ISO string (if not using day)
 | |
|   end?: string;                   // ISO string (if not using day)
 | |
|   columns?: string[];             // optional list of columns
 | |
|   localTz?: string;               // default "Asia/Kuala_Lumpur"
 | |
| };
 | |
| 
 | |
| export function buildExportUrl(p: ExportParams): string {
 | |
|   const {
 | |
|     baseUrl = "",
 | |
|     site,
 | |
|     suffix = "grid",
 | |
|     serial,
 | |
|     day,
 | |
|     start,
 | |
|     end,
 | |
|     columns,
 | |
|     localTz = "Asia/Kuala_Lumpur",
 | |
|   } = p;
 | |
| 
 | |
|   const params = new URLSearchParams();
 | |
|   params.set("site", site);
 | |
|   params.set("suffix", suffix);
 | |
|   params.set("local_tz", localTz);
 | |
| 
 | |
|   const s = serial?.trim();
 | |
|   if (s) params.set("serial", s);
 | |
| 
 | |
|   if (day) {
 | |
|     params.set("day", day); // simple whole-day export
 | |
|   } else {
 | |
|     if (!start || !end) throw new Error("Provide either day=YYYY-MM-DD or both start and end.");
 | |
|     params.set("start", start); // URLSearchParams will encode '+' correctly
 | |
|     params.set("end", end);
 | |
|   }
 | |
| 
 | |
|   if (columns?.length) {
 | |
|     // backend expects ?columns=... repeated; append each
 | |
|     columns.forEach(c => params.append("columns", c));
 | |
|   }
 | |
| 
 | |
|   // ensure there's a single slash join for /export/xlsx
 | |
|   const root = baseUrl.replace(/\/+$/, "");
 | |
|   return `${root}/export/xlsx?${params.toString()}`;
 | |
| }
 | |
| 
 | |
| /** Parse filename from Content-Disposition (handles RFC5987 filename*) */
 | |
| export function getFilenameFromCD(cd: string | null): string | null {
 | |
|   if (!cd) return null;
 | |
| 
 | |
|   // filename*=UTF-8''encoded-name.xlsx
 | |
|   const star = /filename\*\s*=\s*([^']*)''([^;]+)/i.exec(cd);
 | |
|   if (star && star[2]) {
 | |
|     try {
 | |
|       return decodeURIComponent(star[2]);
 | |
|     } catch {
 | |
|       return star[2];
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   // filename="name.xlsx" OR filename=name.xlsx
 | |
|   const plain = /filename\s*=\s*("?)([^";]+)\1/i.exec(cd);
 | |
|   return plain ? plain[2] : null;
 | |
| }
 |