From e26220adc57f4f34f153df2cbff00f91c296f867 Mon Sep 17 00:00:00 2001 From: Isarge05 Date: Wed, 22 Jul 2026 15:48:11 -0400 Subject: [PATCH 1/7] tables now display what is populating the chart --- .../components/Charts/DemographicsTable.tsx | 12 +- frontend/src/components/Charts/TableView.tsx | 190 ++++++++++++++--- .../src/components/Charts/TrendCharts.tsx | 192 +++++++++++------- .../components/Charts/configs/ChartDefs.tsx | 2 +- frontend/src/components/Charts/index.tsx | 33 +-- 5 files changed, 309 insertions(+), 120 deletions(-) diff --git a/frontend/src/components/Charts/DemographicsTable.tsx b/frontend/src/components/Charts/DemographicsTable.tsx index 86ef9fd0..f4ea38fd 100644 --- a/frontend/src/components/Charts/DemographicsTable.tsx +++ b/frontend/src/components/Charts/DemographicsTable.tsx @@ -150,11 +150,7 @@ export const renderTable = ({ chart }: { chart: ChartItem }) => ( /> ); -export const renderTableEstimates = ({ - chart, -}: { - chart: ChartItem; -}) => ( +export const renderTableEstimates = ({ chart }: { chart: ChartItem }) => ( @@ -164,11 +160,7 @@ export const renderTableEstimates = ({ ); /** Shows Percent when available, falls back to Value — for mixed tables like Housing. */ -export const renderTableMixed = ({ - chart, -}: { - chart: ChartItem; -}) => ( +export const renderTableMixed = ({ chart }: { chart: ChartItem }) => ( { diff --git a/frontend/src/components/Charts/TableView.tsx b/frontend/src/components/Charts/TableView.tsx index 79305f22..05da6708 100644 --- a/frontend/src/components/Charts/TableView.tsx +++ b/frontend/src/components/Charts/TableView.tsx @@ -1,51 +1,193 @@ // TableView.tsx - +import { useState } from 'react'; +import { Box, Button, Group, ScrollArea, Table, Text, Title, SegmentedControl } from '@mantine/core'; import { ChartItem, DataRow } from '@/types/cachedCharts'; -import { Table } from '@mantine/core'; -import { Group, Title, SegmentedControl, ScrollArea } from '@mantine/core'; interface TableViewProps { chart: ChartItem; + rows?: DataRow[]; // derived/reshaped rows (e.g. trend plotData) take priority } +const HOME_BG = 'var(--mantine-color-green-0)'; +const COMP_BG = 'var(--mantine-color-blue-0)'; +const SPLIT_BORDER = '1px solid var(--mantine-color-gray-3)'; +const CMP_SUFFIX = ' (cmp)'; + +const formatCell = (v: unknown) => { + if (v == null) return '—'; + if (typeof v === 'number') + return v.toLocaleString(undefined, { maximumFractionDigits: 1 }); + return String(v); +}; + export const TableView = ({ chart, + rows: rowsOverride, }: TableViewProps) => { - const rows = (chart.tableData || chart.data) as TData[]; + const rows = (rowsOverride ?? chart.tableData ?? chart.data) as DataRow[]; + const [showCompare, setShowCompare] = useState(false); + const usePivot = chart.trendChart if (!rows || rows.length === 0) return null; - // start with show cols, fall back to x and y field provided they're in there. - const columns = - chart.showCols?.filter((c) => c.visible ?? true) ?? - (rows[0] ? [chart.xField, chart.yField].filter((k) => k in rows[0]) : []); + const labels = chart.chartParams?.legendLabels as + [string, string] | undefined; + const homeLabel = labels?.[0] ?? 'Primary'; + const compareLabel = labels?.[1] ?? 'Comparison'; + + if (usePivot) { + // x-axis key: prefer chart.xField if it's an actual key on the rows, else fall back to 'year' + const xKey = + chart.xField && rows[0] && chart.xField in rows[0] ? chart.xField : 'year'; + + const xValues = Array.from(new Set(rows.map((r) => r[xKey]))).filter( + (v) => v != null, + ); + // series/row keys = every column except the x key and its "(cmp)" counterpart + const allKeys = rows[0] ? Object.keys(rows[0]) : []; + const seriesKeys = allKeys.filter( + (k) => k !== xKey && !k.endsWith(CMP_SUFFIX), + ); + + const hasCompare = seriesKeys.some((k) => + rows.some((r) => r[`${k}${CMP_SUFFIX}`] != null), + ); + + const findRow = (x: unknown) => rows.find((r) => r[xKey] === x); + return ( - + + {hasCompare && ( + + + {showCompare && ( + + + {homeLabel} + + {compareLabel} + + )} + + )} + + +
+ + + + {xValues.map((x) => ( + {String(x)} + ))} + + + + {seriesKeys.map((key) => ( + + {key} + {xValues.map((x) => { + const row = findRow(x); + const mainVal = row?.[key]; + if (!showCompare || !hasCompare) { + return ( + + {formatCell(mainVal)} + + ); + } + const cmpVal = row?.[`${key}${CMP_SUFFIX}`]; + return ( + +
+
+ {formatCell(mainVal)} +
+
+ {formatCell(cmpVal)} +
+
+
+ ); + })} +
+ ))} +
+
+ + + ); +}; + +const columns = rows[0] ? Object.keys(rows[0]) : []; + +return ( + + - {columns.map((col) => - typeof col === 'string' ? ( - {col} - ) : ( - {col.label ?? col.key} - ), - )} + {columns.map((column) => ( + {column} + ))} + {rows.map((row, i) => ( - {columns.map((col) => { - const key = typeof col === 'string' ? col : col.key; - return {String(row[key])}; - })} + {columns.map((column) => ( + + {formatCell(row[column])} + + ))} ))}
- ); -}; +
+); +} interface ViewSwitchProps { view: 'chart' | 'table'; @@ -57,7 +199,7 @@ export const ViewSwitch = ({ view, setView }: ViewSwitchProps) => ( setView(v as 'chart' | 'table')} @@ -67,4 +209,4 @@ export const ViewSwitch = ({ view, setView }: ViewSwitchProps) => ( ]} /> -); +); \ No newline at end of file diff --git a/frontend/src/components/Charts/TrendCharts.tsx b/frontend/src/components/Charts/TrendCharts.tsx index 2025baa9..11e6a0a0 100644 --- a/frontend/src/components/Charts/TrendCharts.tsx +++ b/frontend/src/components/Charts/TrendCharts.tsx @@ -1,7 +1,7 @@ // TrendCharts.tsx 'use client'; -import { useState } from 'react'; +import { useState, useEffect, useMemo} from 'react'; import { CartesianGrid, Legend, @@ -115,33 +115,27 @@ export const SingleSeriesTrendChart = ({ chart, config, view, + onPlotData, }: { chart: ChartItem; config: SingleSeriesConfig; view?: 'gallery' | 'report'; + onPlotData?: (rows: DataRow[]) => void; }) => { const isGallery = view === 'gallery'; const { - seriesKey, - valueField, - format, - decimals, - color = '#154734', - compareColor = '#1c7ed6', - lineWidth = 3, - showHelperText = true, + seriesKey, valueField, format, decimals, + color = '#154734', compareColor = '#1c7ed6', + lineWidth = 3, showHelperText = true, } = config; const { hidden, toggleSeries, legendFormatter } = useToggle(); const data = chart.data as any[]; - const compareData = (chart.compareData ?? []) as any[]; - if (!data || data.length === 0) return null; + const compareData = chart.compareData as any[]; - const years = Array.from(new Set(data.map((r) => r.year))).sort(); - const labels = chart.chartParams?.legendLabels as - [string, string] | undefined; const seriesName = seriesKey ?? valueField; + const labels = chart.chartParams?.legendLabels as [string, string] | undefined; const findValue = (rows: any[], year: number) => { const row = seriesKey @@ -150,13 +144,27 @@ export const SingleSeriesTrendChart = ({ return row?.[valueField] ?? null; }; - const plotData = years.map((year) => ({ - year, - [seriesName]: findValue(data, year), - ...(compareData.length > 0 - ? { [`${seriesName} (cmp)`]: findValue(compareData, year) } - : {}), - })); + const years = useMemo( + () => (data ? Array.from(new Set(data.map((r) => r.year))).sort() : []), + [data], + ); + + const plotData = useMemo(() => { + if (!data || data.length === 0) return []; + return years.map((year) => ({ + year, + [seriesName]: findValue(data, year), + ...(compareData && compareData.length > 0 + ? { [`${seriesName} (cmp)`]: findValue(compareData, year) } + : {}), + })); + }, [years, data, compareData, seriesName]); + + useEffect(() => { + onPlotData?.(plotData); + }, [plotData, onPlotData]); + + if (!data || data.length === 0) return null; // early return now AFTER all hooks const fmt = FORMATTERS[format]; @@ -256,61 +264,62 @@ export const MultiSeriesTrendChart = ({ chart, config, view, + onPlotData, }: { chart: ChartItem; config: MultiSeriesConfig; view?: 'gallery' | 'report'; + onPlotData?: (rows: DataRow[]) => void; }) => { const isGallery = view === 'gallery'; - const { - series, - valueField, - format, - showHelperText = true, - showCompareNote = true, - legendPosition = 'bottom-right', - nameSuffix = true, + series, valueField, format, + showHelperText = true, showCompareNote = true, + legendPosition = 'bottom-right', nameSuffix = true, } = config; const { hidden, toggleSeries, legendFormatter } = useToggle(); const data = chart.data as any[]; - const compareData = (chart.compareData ?? []) as any[]; - if (!data || data.length === 0) return null; - - const years = Array.from(new Set(data.map((r) => r.year))).sort(); - const labels = chart.chartParams?.legendLabels as - [string, string] | undefined; + const compareData = chart.compareData as any[]; + const labels = chart.chartParams?.legendLabels as [string, string] | undefined; const getValue = (rows: any[], year: number, s: SeriesDef) => { if (s.aggregateFrom) { const sum = s.aggregateFrom.reduce((acc, label) => { - const v = - rows.find((r) => r.year === year && r.Variable === label)?.[ - valueField - ] ?? 0; + const v = rows.find((r) => r.year === year && r.Variable === label)?.[valueField] ?? 0; return acc + v; }, 0); return sum > 0 ? Math.round(sum * 10) / 10 : null; } const label = s.matchVariable ?? s.key; - return ( - rows.find((r) => r.year === year && r.Variable === label)?.[valueField] ?? - null - ); + return rows.find((r) => r.year === year && r.Variable === label)?.[valueField] ?? null; }; - const plotData = years.map((year) => { - const pt: Record = { year }; - for (const s of series) { - pt[s.key] = getValue(data, year, s); - if (compareData.length > 0) - pt[`${s.key} (cmp)`] = getValue(compareData, year, s); - } - return pt; - }); + const years = useMemo( + () => (data ? Array.from(new Set(data.map((r) => r.year))).sort() : []), + [data], + ); + + const plotData = useMemo(() => { + if (!data || data.length === 0) return []; + return years.map((year) => { + const pt: Record = { year }; + for (const s of series) { + pt[s.key] = getValue(data, year, s); + if (compareData && compareData.length > 0) + pt[`${s.key} (cmp)`] = getValue(compareData, year, s); + } + return pt; + }); + }, [years, data, compareData, JSON.stringify(series), valueField]); + useEffect(() => { + onPlotData?.(plotData); + }, [plotData, onPlotData]); + + if (!data || data.length === 0) return null; + const fmt = FORMATTERS[format]; return ( @@ -394,66 +403,88 @@ const single = ( chart: ChartItem, config: SingleSeriesConfig, view?: 'gallery' | 'report', -) => ; + onPlotData?: (rows: DataRow[]) => void, +) => ( + +); export const PopulationTrendChart = ({ chart, view, + onPlotData, }: { chart: ChartItem; view?: 'gallery' | 'report'; + onPlotData?: (rows: DataRow[]) => void; }) => single( chart, - { seriesKey: null, valueField: 'Population', format: 'number' }, + { seriesKey: null, valueField: 'Value', format: 'number' }, view, + onPlotData, ); export const MedianAgeTrendChart = ({ chart, view, + onPlotData, }: { chart: ChartItem; view?: 'gallery' | 'report'; + onPlotData?: (rows: DataRow[]) => void; }) => single( chart, { seriesKey: 'Median Age', valueField: 'Value', format: 'years' }, view, + onPlotData, ); export const HomeValueTrendChart = ({ chart, view, + onPlotData, }: { chart: ChartItem; view?: 'gallery' | 'report'; + onPlotData?: (rows: DataRow[]) => void; }) => single( chart, { seriesKey: 'Median Home Value', valueField: 'Value', format: 'currency' }, view, + onPlotData, ); export const HousingUnitsTrendChart = ({ chart, view, + onPlotData, }: { chart: ChartItem; view?: 'gallery' | 'report'; + onPlotData?: (rows: DataRow[]) => void; }) => single( chart, { seriesKey: 'Total Housing Units', valueField: 'Value', format: 'number' }, view, + onPlotData, ); export const HousingTenureAreaChart = ({ chart, view, + onPlotData, }: { chart: ChartItem; view?: 'gallery' | 'report'; + onPlotData?: (rows: DataRow[]) => void; }) => single( chart, @@ -463,14 +494,17 @@ export const HousingTenureAreaChart = ({ format: 'percent', }, view, + onPlotData, ); export const LaborForceTrendChart = ({ chart, view, + onPlotData, }: { chart: ChartItem; view?: 'gallery' | 'report'; + onPlotData?: (rows: DataRow[]) => void; }) => single( chart, @@ -480,14 +514,17 @@ export const LaborForceTrendChart = ({ format: 'percent', }, view, + onPlotData, ); export const LaborForceTrendChartPrimeAge = ({ chart, view, + onPlotData, }: { chart: ChartItem; view?: 'gallery' | 'report'; + onPlotData?: (rows: DataRow[]) => void; }) => single( chart, @@ -497,14 +534,17 @@ export const LaborForceTrendChartPrimeAge = ({ format: 'percent', }, view, + onPlotData, ); export const UnemploymentTrendChart = ({ chart, view, + onPlotData, }: { chart: ChartItem; view?: 'gallery' | 'report'; + onPlotData?: (rows: DataRow[]) => void; }) => single( chart, @@ -515,14 +555,17 @@ export const UnemploymentTrendChart = ({ decimals: 1, }, view, + onPlotData, ); export const HouseholdIncomeTrendChart = ({ chart, view, + onPlotData, }: { chart: ChartItem; view?: 'gallery' | 'report'; + onPlotData?: (rows: DataRow[]) => void; }) => single( chart, @@ -533,14 +576,17 @@ export const HouseholdIncomeTrendChart = ({ showHelperText: false, }, view, + onPlotData, ); export const PerCapitaIncomeTrendChart = ({ chart, view, + onPlotData, }: { chart: ChartItem; view?: 'gallery' | 'report'; + onPlotData?: (rows: DataRow[]) => void; }) => single( chart, @@ -551,6 +597,7 @@ export const PerCapitaIncomeTrendChart = ({ showHelperText: false, }, view, + onPlotData, ); // MULTI CHARTS @@ -558,14 +605,24 @@ const multi = ( chart: ChartItem, config: MultiSeriesConfig, view?: 'gallery' | 'report', -) => ; + onPlotData?: (rows: DataRow[]) => void, +) => ( + +); export const DemographicsTrendChart = ({ chart, view, + onPlotData, }: { chart: ChartItem; view?: 'gallery' | 'report'; + onPlotData?: (rows: DataRow[]) => void; }) => multi( chart, @@ -574,22 +631,21 @@ export const DemographicsTrendChart = ({ format: 'percent', series: [ { key: 'Under 18', color: '#154734' }, - { - key: '65+', - aggregateFrom: ['65 to 74', '75 Plus'], - color: '#1c7ed6', - }, + { key: '65+', aggregateFrom: ['65 to 74', '75 Plus'], color: '#1c7ed6' }, ], }, view, + onPlotData, ); export const EducationTrendChart = ({ chart, view, + onPlotData, }: { chart: ChartItem; view?: 'gallery' | 'report'; + onPlotData?: (rows: DataRow[]) => void; }) => multi( chart, @@ -607,14 +663,17 @@ export const EducationTrendChart = ({ ], }, view, + onPlotData, ); export const EarningsTrendChart = ({ chart, view, + onPlotData, }: { chart: ChartItem; view?: 'gallery' | 'report'; + onPlotData?: (rows: DataRow[]) => void; }) => multi( chart, @@ -622,20 +681,13 @@ export const EarningsTrendChart = ({ valueField: 'Value', format: 'currency', series: [ - { - key: 'Male Full-Time Workers', - matchVariable: 'DP03_0093', - color: '#161E54', - }, - { - key: 'Female Full-Time Workers', - matchVariable: 'DP03_0094', - color: '#F16D34', - }, + { key: 'Male Full-Time Workers', matchVariable: 'DP03_0093', color: '#161E54' }, + { key: 'Female Full-Time Workers', matchVariable: 'DP03_0094', color: '#F16D34' }, { key: 'All Workers', matchVariable: 'DP03_0092', color: '#9BB0C1' }, ], }, view, + onPlotData, ); // --------------------------------------------------------------------------- diff --git a/frontend/src/components/Charts/configs/ChartDefs.tsx b/frontend/src/components/Charts/configs/ChartDefs.tsx index 194df267..0f83c4f1 100644 --- a/frontend/src/components/Charts/configs/ChartDefs.tsx +++ b/frontend/src/components/Charts/configs/ChartDefs.tsx @@ -293,7 +293,7 @@ export const chartDefs: ChartDef[] = [ // Median Earnings { id: 'earnings', - title: 'Median Earnings - Value', + title: 'Median Earnings by Sex', url: `${BASE_API_URL}/load/acs5-db/tidy/median-earnings`, xField: '', yField: '', diff --git a/frontend/src/components/Charts/index.tsx b/frontend/src/components/Charts/index.tsx index 9a0a00fd..3ed9dcdb 100644 --- a/frontend/src/components/Charts/index.tsx +++ b/frontend/src/components/Charts/index.tsx @@ -30,7 +30,7 @@ export { } from './TrendCharts'; export { EmploymentAreaChart } from './EmploymentAreaChart'; -import { ChartItem } from '@/types/cachedCharts'; +import { ChartItem, DataRow } from '@/types/cachedCharts'; import { Badge, Card, @@ -54,13 +54,11 @@ import { usePdfMode } from '@/contexts/PdfModeContext'; // ChartCard interface ChartCardProps { chart: ChartItem; - ChartComponent: React.FC<{ - chart: ChartItem; - view?: 'gallery' | 'report'; - }>; + ChartComponent: React.FC<{ chart: ChartItem; view?: 'gallery' | 'report' }>; TrendComponent?: React.FC<{ chart: ChartItem; view?: 'gallery' | 'report'; + onPlotData?: (rows: DataRow[]) => void; }>; matchedCategories?: string[]; action?: 'add' | 'remove' | 'toggle'; @@ -90,6 +88,7 @@ export const ChartCard = ({ const isTablePrimary = chart.subtype.startsWith('renderTable'); const [localView, setLocalView] = useState<'chart' | 'table'>('chart'); + const selfManagesViews = !!chart.chartParams?.noViewSwitch; const showViewSwitch = @@ -97,19 +96,23 @@ export const ChartCard = ({ !isGallery && (isTablePrimary ? !!TrendComponent : true); + const [trendPlotData, setTrendPlotData] = useState(); + const content = selfManagesViews ? ( - ) : isTablePrimary ? ( - localView === 'chart' && TrendComponent ? ( - - ) : ( + ) : isTablePrimary ? ( + localView === 'chart' && TrendComponent ? ( + + ) : TrendComponent ? ( + + ) : ( + + ) + ) : localView === 'chart' ? ( - ) - ) : localView === 'chart' ? ( - - ) : ( - - ); + ) : ( + + ); const isHighlighted = matchedCategories.length > 0; const allCategories = chart.categories ?? []; From 2344ca1e73a0f5b4b765b0fd13f53044d82028ca Mon Sep 17 00:00:00 2001 From: Isarge05 Date: Thu, 23 Jul 2026 10:20:50 -0400 Subject: [PATCH 2/7] fixed datatype bug with fixed year + scrolling table ability is back --- .../src/app/data-viewer/MetricsPanels.tsx | 1 - frontend/src/app/data-viewer/page.tsx | 8 +- frontend/src/app/working-report/page.tsx | 9 +- frontend/src/components/Charts/TableView.tsx | 108 +++++++++--------- frontend/src/components/Charts/index.tsx | 59 +++++----- frontend/src/types/cachedCharts.ts | 8 ++ 6 files changed, 104 insertions(+), 89 deletions(-) diff --git a/frontend/src/app/data-viewer/MetricsPanels.tsx b/frontend/src/app/data-viewer/MetricsPanels.tsx index 5ea50e0b..d923e7fc 100644 --- a/frontend/src/app/data-viewer/MetricsPanels.tsx +++ b/frontend/src/app/data-viewer/MetricsPanels.tsx @@ -35,7 +35,6 @@ export function MetricsPanel({ metrics }: { metrics: Record }) { {METRICS.map(({ label, field, prefix }) => ( - s {prefix} {formatNumber(metrics[field])} diff --git a/frontend/src/app/data-viewer/page.tsx b/frontend/src/app/data-viewer/page.tsx index 880ea55d..3454f97d 100644 --- a/frontend/src/app/data-viewer/page.tsx +++ b/frontend/src/app/data-viewer/page.tsx @@ -27,6 +27,8 @@ import { useApplyFilters, buildFilters, } from '@/components/FilterUI/useApplyFilters'; +import { DataRow, ChartPayload, ChartMetadata } from '@/types/cachedCharts'; + import { motion } from 'motion/react'; import classes from './Tabs.module.css'; @@ -583,7 +585,11 @@ export default function DataViewerPage() { charts={visibleItems} action="add" userInterests={interests} - defIds={visibleItems.map((c) => c.chartParams?.defId)} + defIds={visibleItems.map( + (c) => c.chartParams?.defId) + .filter((id): id is string => + id !== undefined + )} view="gallery" /> diff --git a/frontend/src/app/working-report/page.tsx b/frontend/src/app/working-report/page.tsx index 7584c249..4d2642bd 100644 --- a/frontend/src/app/working-report/page.tsx +++ b/frontend/src/app/working-report/page.tsx @@ -29,14 +29,9 @@ import { XIcon, } from '@phosphor-icons/react'; import { ChartDef, chartDefs } from '@/components/Charts/configs/ChartDefs'; -import { ChartItem, ChartMetadata, DataRow } from '@/types/cachedCharts'; +import { ChartItem, ChartMetadata, DataRow, ChartPayload } from '@/types/cachedCharts'; + -// one chart's backend payload, keyed by chart def id in state below -type ChartPayload = { - data: DataRow[]; - metadata?: ChartMetadata; - tableData?: DataRow[]; -}; import { createChartItem, createTableItem } from '@/utils/itemFactory'; import { useItems } from '@/components/ItemsProvider'; import { PdfModeContext } from '@/contexts/PdfModeContext'; diff --git a/frontend/src/components/Charts/TableView.tsx b/frontend/src/components/Charts/TableView.tsx index 05da6708..4429e10f 100644 --- a/frontend/src/components/Charts/TableView.tsx +++ b/frontend/src/components/Charts/TableView.tsx @@ -57,7 +57,7 @@ export const TableView = ({ const findRow = (x: unknown) => rows.find((r) => r[xKey] === x); return ( - + {hasCompare && ( + {showCompare && ( + + + {homeLabel} + + {compareLabel} + + )} + + ) : null + ); + + // --- MODE A: PIVOTED TABLE (FOR TREND CHARTS) --- if (usePivot) { - // x-axis key: prefer chart.xField if it's an actual key on the rows, else fall back to 'year' const xKey = - chart.xField && rows[0] && chart.xField in rows[0] ? chart.xField : 'year'; + chart.xField && rows[0] && chart.xField in rows[0] + ? chart.xField + : 'year'; const xValues = Array.from(new Set(rows.map((r) => r[xKey]))).filter( (v) => v != null, ); - // series/row keys = every column except the x key and its "(cmp)" counterpart - const allKeys = rows[0] ? Object.keys(rows[0]) : []; const seriesKeys = allKeys.filter( (k) => k !== xKey && !k.endsWith(CMP_SUFFIX), ); - const hasCompare = seriesKeys.some((k) => - rows.some((r) => r[`${k}${CMP_SUFFIX}`] != null), - ); - const findRow = (x: unknown) => rows.find((r) => r[xKey] === x); - - return ( - - {hasCompare && ( - - - {showCompare && ( - - - {homeLabel} - - {compareLabel} - - )} - - )} - + return ( + + + @@ -155,40 +163,89 @@ export const TableView = ({ ))}
+
+
+ ); + } + + // --- MODE B: DIRECT TABLE (FOR BAR CHARTS) --- + // Determine standard columns vs comparison columns + const baseColumns = allKeys.filter((k) => !k.endsWith(CMP_SUFFIX)); + + return ( + + + + + + + {baseColumns.map((column) => { + const hasColCompare = + hasCompare && rows.some((r) => r[`${column}${CMP_SUFFIX}`] != null); + + return ( + + {column} + + ); + })} + + + + + {rows.map((row, i) => ( + + {baseColumns.map((column) => { + const mainVal = row[column]; + const cmpVal = row[`${column}${CMP_SUFFIX}`]; + const isComparableCell = + hasCompare && showCompare && cmpVal != null; + + if (isComparableCell) { + return ( + +
+
+ {formatCell(mainVal)} +
+
+ {formatCell(cmpVal)} +
+
+
+ ); + } + + return ( + + {formatCell(mainVal)} + + ); + })} +
+ ))} +
+
); }; -const columns = rows[0] ? Object.keys(rows[0]) : []; - -return ( - - - - - {columns.map((column) => ( - {column} - ))} - - - - - {rows.map((row, i) => ( - - {columns.map((column) => ( - - {formatCell(row[column])} - - ))} - - ))} - -
-
-); -} - interface ViewSwitchProps { view: 'chart' | 'table'; setView: (view: 'chart' | 'table') => void; diff --git a/frontend/src/components/Charts/index.tsx b/frontend/src/components/Charts/index.tsx index f3363ec1..87703921 100644 --- a/frontend/src/components/Charts/index.tsx +++ b/frontend/src/components/Charts/index.tsx @@ -55,7 +55,11 @@ import { usePdfMode } from '@/contexts/PdfModeContext'; // ChartCard interface ChartCardProps { chart: ChartItem; - ChartComponent: React.FC<{ chart: ChartItem; view?: 'gallery' | 'report' }>; + ChartComponent: React.FC<{ + chart: ChartItem; + view?: 'gallery' | 'report'; + onPlotData?: (rows: DataRow[]) => void; + }>; TrendComponent?: React.FC<{ chart: ChartItem; view?: 'gallery' | 'report'; @@ -113,12 +117,22 @@ export const ChartCard = ({ ) : ( ) - ) : localView === 'chart' ? ( - ) : ( - - - + <> + + + + + {localView === 'table' && ( + + + + )} + ); const isHighlighted = matchedCategories.length > 0; From 373bbc6c56546424586319b04dbcaa75db88f92c Mon Sep 17 00:00:00 2001 From: Isarge05 Date: Thu, 23 Jul 2026 11:49:33 -0400 Subject: [PATCH 4/7] population trend chart bug fix --- frontend/src/components/Charts/TrendCharts.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frontend/src/components/Charts/TrendCharts.tsx b/frontend/src/components/Charts/TrendCharts.tsx index 11e6a0a0..2d03c48b 100644 --- a/frontend/src/components/Charts/TrendCharts.tsx +++ b/frontend/src/components/Charts/TrendCharts.tsx @@ -424,7 +424,7 @@ export const PopulationTrendChart = ({ }) => single( chart, - { seriesKey: null, valueField: 'Value', format: 'number' }, + { seriesKey: null, valueField: 'Population', format: 'number' }, view, onPlotData, ); From 77af276f1efa71b39a860272cea4fb622f4b449a Mon Sep 17 00:00:00 2001 From: Isarge05 Date: Thu, 23 Jul 2026 13:24:03 -0400 Subject: [PATCH 5/7] Changed add to working report button to be more specific with chart IDs --- frontend/src/components/Charts/index.tsx | 24 +++++----- frontend/src/components/Charts/saving.tsx | 53 ++++++++++++++++------- 2 files changed, 49 insertions(+), 28 deletions(-) diff --git a/frontend/src/components/Charts/index.tsx b/frontend/src/components/Charts/index.tsx index 87703921..5f6c9020 100644 --- a/frontend/src/components/Charts/index.tsx +++ b/frontend/src/components/Charts/index.tsx @@ -246,19 +246,17 @@ export const ChartCard = ({
- - - {content} - - + + {content} + {!isGallery && ( diff --git a/frontend/src/components/Charts/saving.tsx b/frontend/src/components/Charts/saving.tsx index 3f1f9f75..7192ecb5 100644 --- a/frontend/src/components/Charts/saving.tsx +++ b/frontend/src/components/Charts/saving.tsx @@ -3,7 +3,7 @@ import { useItems } from '../ItemsProvider'; import { useProfile } from '@/components/profile/profileStore'; import { Button, Transition } from '@mantine/core'; import { CheckIcon, XIcon } from '@phosphor-icons/react'; -import { ChartItem } from '@/types/cachedCharts'; +import { ChartItem, DataRow } from '@/types/cachedCharts'; // Toggle include/exclude for auto-populated working report charts interface ToggleProps { @@ -31,26 +31,49 @@ interface AddChartProps { chart: ChartItem; defId?: string; } -export function AddChart({ chart, defId }: AddChartProps) { - const { addItem, removeItem, includeById, excludeById, items } = useItems(); +export function AddChart({ chart, defId }: AddChartProps) { + const { addItem, removeItem, items } = useItems(); const { interests } = useProfile(); - const stableId = [ - chart.title, - chart.subtype, - ...(chart.chartParams?.legendLabels ?? []), - ].join('::'); + const stableId = + defId ?? + chart.id ?? + [ + chart.title, + chart.subtype, + chart.chartParams?.xKey, + chart.chartParams?.yKey, + ...(chart.chartParams?.legendLabels ?? []), + ] + .filter(Boolean) + .join('::'); + + // Check if the chart categories match any of the user's profile interests + const chartCategories = chart.categories ?? []; // Adjust field name to match your ChartItem schema + const matchesInterests = + interests.length === 0 || // If no interests set, default to showing/including + chartCategories.some((category) => interests.includes(category)); + // Auto-exclude initial state check based on interests if needed, + // or track state relative to items in report: const inReport = items.some((item) => item.id === stableId); - const handleClick = () => { - if (inReport) { - removeItem(stableId); - } else { - addItem({ ...chart, id: stableId }); - } - }; + const handleClick = (e: React.MouseEvent) => { + // Prevent parent elements (like ChartCard or Modal triggers) from catching the click + e.stopPropagation(); + + if (inReport) { + removeItem(stableId); + } else { + addItem({ ...chart, id: stableId }); + } +}; + + // Optional: If you want to dim or hide the button when interests don't match + if (!matchesInterests && !inReport) { + return null; // or render a muted/disabled state based on your UX needs + } return (