/** CSV / SQL helpers for per-component export & copy. */ export function rowsToCsv(rows: any[]): string { if (!rows?.length) return ""; const cols = Object.keys(rows[0]); const esc = (v: any): string => { if (v == null) return ""; const s = typeof v === "string" ? v : JSON.stringify(v); if (/[",\n]/.test(s)) return `"${s.replace(/"/g, '""')}"`; return s; }; const head = cols.join(","); const body = rows.map((r) => cols.map((c) => esc(r[c])).join(",")).join("\n"); return head + "\n" + body; } export function downloadFile(name: string, content: string, mime = "text/csv") { const blob = new Blob([content], { type: mime }); const url = URL.createObjectURL(blob); const a = document.createElement("a"); a.href = url; a.download = name; document.body.appendChild(a); a.click(); setTimeout(() => { document.body.removeChild(a); URL.revokeObjectURL(url); }, 0); } export async function copyToClipboard(text: string): Promise { try { await navigator.clipboard.writeText(text); return true; } catch { return false; } } export function safeFilename(s: string, ext: string): string { const base = (s || "export") .toLowerCase() .replace(/[^a-z0-9]+/g, "_") .replace(/^_+|_+$/g, "") .slice(0, 50); return `${base || "export"}.${ext}`; }