File size: 1,496 Bytes
27124e1
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
// Minimal client-side CSV builder + browser download helper.
// Detectability results come from the live backend (with an offline mock
// fallback when it is unreachable); the CSV downloads are real files.

export type CsvValue = string | number | boolean | null | undefined

function escapeCell(value: unknown): string {
  if (value === null || value === undefined) return ''
  const s = String(value)
  if (/[",\n]/.test(s)) {
    return '"' + s.replace(/"/g, '""') + '"'
  }
  return s
}

/** Build a CSV string from an array of row objects, using `columns` order. */
export function toCSV<T>(
  rows: T[],
  columns?: { key: keyof T; header: string }[],
): string {
  if (rows.length === 0 && !columns) return ''
  const cols =
    columns ??
    (Object.keys(rows[0] as object) as (keyof T)[]).map((k) => ({
      key: k,
      header: String(k),
    }))
  const head = cols.map((c) => escapeCell(c.header)).join(',')
  const body = rows
    .map((row) => cols.map((c) => escapeCell(row[c.key])).join(','))
    .join('\n')
  return head + '\n' + body
}

/** Trigger a browser download of `text` as `filename`. */
export function download(
  filename: string,
  text: string,
  mime = 'text/csv;charset=utf-8',
): void {
  const blob = new Blob([text], { type: mime })
  const url = URL.createObjectURL(blob)
  const a = document.createElement('a')
  a.href = url
  a.download = filename
  document.body.appendChild(a)
  a.click()
  document.body.removeChild(a)
  URL.revokeObjectURL(url)
}