'use client' import React, { useState, useEffect, useCallback } from 'react' import type { FDDataTableBlock as Props, Media } from '@/payload-types' type TableData = { headers: string[] rows: string[][] } function detectDelimiter(line: string): string { const semicolons = (line.match(/;/g) || []).length const commas = (line.match(/,/g) || []).length return semicolons >= commas ? ';' : ',' } function parseCSVLine(line: string, delimiter: string): string[] { const result: string[] = [] let current = '' let inQuotes = false for (let i = 0; i < line.length; i++) { const char = line[i] if (char === '"') { inQuotes = !inQuotes } else if (char === delimiter && !inQuotes) { result.push(current.trim()) current = '' } else { current += char } } result.push(current.trim()) return result } async function parseFile(url: string, filename: string): Promise { const response = await fetch(url) const isExcel = /\.(xlsx|xls)$/i.test(filename) if (isExcel) { const buffer = await response.arrayBuffer() const ExcelJS = await import('exceljs') const workbook = new ExcelJS.Workbook() await workbook.xlsx.load(buffer) const sheet = workbook.worksheets[0] const data: string[][] = [] sheet.eachRow((row) => { const cells = (row.values as unknown[]) .slice(1) .map((c) => { if (c === null || c === undefined) return '' if (typeof c === 'object' && 'text' in c) return String((c as any).text ?? '') if (typeof c === 'object' && 'result' in c) return String((c as any).result ?? '') return String(c) }) data.push(cells) }) const nonEmpty = data.filter((r) => r.some((c) => c.trim())) const [headerRow, ...bodyRows] = nonEmpty return { headers: headerRow || [], rows: bodyRows } } else { const text = await response.text() const lines = text.split(/\r?\n/).map((l) => l.trim()).filter(Boolean) const delimiter = detectDelimiter(lines[0] || '') const [headerLine, ...bodyLines] = lines return { headers: parseCSVLine(headerLine, delimiter), rows: bodyLines.map((l) => parseCSVLine(l, delimiter)), } } } export const FDDataTableBlockComponent: React.FC = ({ heading, description, dataSource = 'upload', file, headers: manualHeaders = [], rows: manualRows = [], sectionBackground = 'white', headerStyle = 'navy', stripeRows = true, bordered = false, firstColumnBold = false, anchorId, }) => { const [tableData, setTableData] = useState(null) const [loading, setLoading] = useState(false) const [error, setError] = useState(null) const resolveData = useCallback(async () => { if (dataSource === 'manual') { const headers = (manualHeaders || []).map((h) => h.text || '') const rows = (manualRows || []).map((r) => (r.cells || '').split(',').map((c: string) => c.trim()), ) setTableData({ headers, rows }) return } const media = file as Media | undefined if (!media?.url) { setTableData(null) return } setLoading(true) setError(null) try { const filename = (media as any).filename || media.url const data = await parseFile(media.url, filename) setTableData(data) } catch (e) { console.error('Table parse error:', e) setError('Kunde inte läsa filen. Kontrollera att det är en giltig CSV- eller Excel-fil.') setTableData(null) } finally { setLoading(false) } }, [dataSource, file, manualHeaders, manualRows]) useEffect(() => { resolveData() }, [resolveData]) const isDark = sectionBackground === 'navy' // Navy stays navy. White/gray adapt to OS dark mode. const bgClass = sectionBackground === 'navy' ? 'bg-fd-navy' : sectionBackground === 'gray' ? 'bg-fd-surface-alt dark:bg-fd-navy' : 'bg-white dark:bg-fd-navy' const titleClass = isDark ? 'text-fd-yellow' : 'text-fd-navy dark:text-fd-yellow' const bodyClass = isDark ? 'text-white' : 'text-fd-navy dark:text-white' // Header row style — navy header stays navy on dark bg; other styles get dark: adjustments const headerBgClass = headerStyle === 'yellow' ? 'bg-fd-yellow text-fd-navy' : headerStyle === 'mint' ? 'bg-fd-mint text-fd-navy' : headerStyle === 'gray' ? 'bg-gray-200 text-fd-navy dark:bg-white/10 dark:text-white' : isDark ? 'bg-white/10 text-white' : 'bg-fd-navy text-white dark:bg-white/10' const getRowBg = (i: number) => { if (!stripeRows) return isDark ? 'bg-white/5' : 'bg-white dark:bg-white/5' if (isDark) return i % 2 === 0 ? 'bg-white/5' : 'bg-white/10' return i % 2 === 0 ? 'bg-white dark:bg-white/5' : 'bg-fd-surface-alt dark:bg-white/10' } const borderClass = bordered ? isDark ? 'border border-white/10' : 'border border-fd-navy/10 dark:border-white/10' : '' const cellBorderClass = bordered ? isDark ? 'border-r border-b border-white/10 last:border-r-0' : 'border-r border-b border-fd-navy/10 dark:border-white/10 last:border-r-0' : '' return (
{(heading || description) && (
{heading && (

{heading}

)} {description && (

{description}

)}
)} {loading && (
Laddar tabell...
)} {error && (

{error}

)} {!loading && !error && !tableData && (
{dataSource === 'upload' ? 'Ladda upp en CSV- eller Excel-fil för att visa tabellen.' : 'Lägg till kolumnrubriker och rader ovan.'}
)} {!loading && !error && tableData && tableData.headers.length > 0 && (
{tableData.headers.map((header, i) => ( ))} {tableData.rows.map((row, rowIndex) => ( {tableData.headers.map((_, colIndex) => ( ))} ))}
{header}
{row[colIndex] ?? ''}
)} {!loading && !error && tableData && tableData.rows.length > 0 && (

{tableData.rows.length} rader · {tableData.headers.length} kolumner

)}
) }