import React, { useState, useMemo } from 'react'; import type { ProcessNode } from '../../types/process'; import { extractStreamInfo } from '../../utils/streamInfo'; import { exportProjectToCsv } from '../../utils/csvExport'; interface Props { processes: ProcessNode[]; groups: number[][]; groupNames: string[]; groupCoordinates?: Record; } type SortKey = 'group' | 'subprocess' | 'streamName' | 'type' | 'tin' | 'tout' | 'mdot' | 'cp' | 'CP' | 'Q' | 'lat' | 'lon' | 'hours' | 'water_in' | 'water_out' | 'density' | 'pressure' | 'notes'; export default function StreamDataTable({ processes, groups = [], groupNames = [], groupCoordinates = {}, }: Props) { const [sortKey, setSortKey] = useState('Q'); const [sortDir, setSortDir] = useState<'desc' | 'asc'>('desc'); // Flatten streams const allRows = useMemo(() => { const res: any[] = []; groups.forEach((subIdxs, gIdx) => { const gName = groupNames[gIdx] || `Process ${gIdx + 1}`; const gCoord = groupCoordinates[gIdx] || {}; subIdxs.forEach((si) => { const sub = processes[si]; if (!sub) return; const baseInfo = { group: gName, subprocess: sub.name, lat: sub.lat || gCoord.lat || '', lon: sub.lon || gCoord.lon || '', hours: sub.hours || gCoord.hours || '', water_in: sub.extra_info?.water_content_in || '', water_out: sub.extra_info?.water_content_out || '', density: sub.extra_info?.density || '', pressure: sub.extra_info?.pressure || '', notes: sub.extra_info?.notes || '', }; if (!(sub.streams || []).length && !(sub.children || []).some(c => (c.streams || []).length)) { res.push({ ...baseInfo, streamName: '', type: '', tin: null, tout: null, mdot: null, cp: null, CP: null, Q: null }); } (sub.streams || []).forEach((s) => { const si = extractStreamInfo(s as any); res.push({ ...baseInfo, ...si, streamName: s.name, // Prioritize stream-specific metadata if extracted water_in: si.water_in || baseInfo.water_in, water_out: si.water_out || baseInfo.water_out, density: si.density || baseInfo.density, pressure: si.pressure || baseInfo.pressure, }); }); (sub.children || []).forEach((child) => { const childBase = { group: gName, subprocess: `${sub.name} โ€บ ${child.name}`, lat: child.lat || sub.lat || gCoord.lat || '', lon: child.lon || sub.lon || gCoord.lon || '', hours: child.hours || sub.hours || gCoord.hours || '', water_in: child.extra_info?.water_content_in || sub.extra_info?.water_content_in || '', water_out: child.extra_info?.water_content_out || sub.extra_info?.water_content_out || '', density: child.extra_info?.density || sub.extra_info?.density || '', pressure: child.extra_info?.pressure || sub.extra_info?.pressure || '', notes: child.extra_info?.notes || sub.extra_info?.notes || '', }; (child.streams || []).forEach((s) => { const si = extractStreamInfo(s as any); res.push({ ...childBase, ...si, streamName: s.name, water_in: si.water_in || childBase.water_in, water_out: si.water_out || childBase.water_out, density: si.density || childBase.density, pressure: si.pressure || childBase.pressure, }); }); }); }); }); return res; }, [processes, groups, groupNames]); const sortedRows = useMemo(() => { return [...allRows].sort((a, b) => { const vA = a[sortKey]; const vB = b[sortKey]; if (vA === vB) return 0; if (vA == null) return 1; if (vB == null) return -1; let res = (vA < vB ? -1 : 1); return sortDir === 'asc' ? res : -res; }); }, [allRows, sortKey, sortDir]); const toggleSort = (key: SortKey) => { if (sortKey === key) setSortDir(sortDir === 'asc' ? 'desc' : 'asc'); else { setSortKey(key); setSortDir('desc'); } }; const cellStyle: React.CSSProperties = { padding: '4px 8px', fontSize: '11.5px', verticalAlign: 'middle', color: 'var(--text-main)', lineHeight: '1.2', borderBottom: '1px solid var(--border)', whiteSpace: 'nowrap', overflow: 'hidden', textOverflow: 'ellipsis', maxWidth: '200px' }; const headStyle = (key: SortKey): React.CSSProperties => ({ padding: '6px 8px', fontSize: '10.5px', fontWeight: 700, textTransform: 'uppercase', textAlign: 'left', background: 'var(--surface)', color: sortKey === key ? 'var(--primary)' : 'var(--text-muted)', cursor: 'pointer', borderBottom: '2px solid var(--border)', userSelect: 'none', whiteSpace: 'nowrap' }); return (

Stream Data โ€” Click header to sort

{[ {k:'group', l:'Process'}, {k:'subprocess', l:'Subprocess'}, {k:'streamName', l:'Stream'}, {k:'type', l:'Type'}, {k:'tin', l:'Tin'}, {k:'tout', l:'Tout'}, {k:'mdot', l:'แน'}, {k:'cp', l:'cp'}, {k:'CP', l:'CP'}, {k:'Q', l:'Q (kW)'}, {k:'lat', l:'Lat'}, {k:'lon', l:'Lon'}, {k:'hours', l:'Hours'}, {k:'water_in', l:'Water In'}, {k:'water_out', l:'Water Out'}, {k:'density', l:'Density'}, {k:'pressure', l:'Pressure'}, {k:'notes', l:'Notes'}, ].map(h => ( ))} {sortedRows.length === 0 ? ( ) : ( sortedRows.map((r, i) => ( )) )}
toggleSort(h.k as SortKey)}> {h.l} {sortKey === h.k ? (sortDir === 'asc' ? 'โ–ด' : 'โ–พ') : ''}
No stream data found
{r.group} {r.subprocess} {r.streamName} {r.type} {r.tin?.toFixed(1) ?? 'โ€”'} {r.tout?.toFixed(1) ?? 'โ€”'} {r.mdot?.toFixed(2) ?? 'โ€”'} {r.cp?.toFixed(2) ?? 'โ€”'} {r.CP?.toFixed(2) ?? 'โ€”'} {r.Q?.toFixed(1) ?? 'โ€”'} {typeof r.lat === 'number' ? r.lat.toFixed(6) : r.lat} {typeof r.lon === 'number' ? r.lon.toFixed(6) : r.lon} {r.hours} {r.water_in} {r.water_out} {r.density} {r.pressure} {r.notes}
); }