diff --git a/app/components/PcFundTable.jsx b/app/components/PcFundTable.jsx new file mode 100644 index 0000000..928e651 --- /dev/null +++ b/app/components/PcFundTable.jsx @@ -0,0 +1,555 @@ +'use client'; + +import { useEffect, useMemo, useRef } from 'react'; +import { AnimatePresence, motion } from 'framer-motion'; +import { + flexRender, + getCoreRowModel, + useReactTable, +} from '@tanstack/react-table'; +import { ExitIcon, SettingsIcon, StarIcon, TrashIcon } from './Icons'; + +/** + * PC 端基金列表表格组件(基于 @tanstack/react-table) + * + * @param {Object} props + * @param {Array} props.data - 表格数据 + * 每一行推荐结构(字段命名与 page.jsx 中的数据一致): + * { + * fundName: string; // 基金名称 + * code?: string; // 基金代码(可选,只用于展示在名称下方) + * navOrEstimate: string|number; // 净值/估值 + * yesterdayChangePercent: string|number; // 昨日涨跌幅 + * estimateChangePercent: string|number; // 估值涨跌幅 + * holdingAmount: string|number; // 持仓金额 + * todayProfit: string|number; // 当日收益 + * holdingProfit: string|number; // 持有收益 + * } + * @param {(row: any) => void} [props.onRemoveFund] - 删除基金的回调 + * @param {string} [props.currentTab] - 当前分组 + * @param {Set} [props.favorites] - 自选集合 + * @param {(row: any) => void} [props.onToggleFavorite] - 添加/取消自选 + * @param {(row: any) => void} [props.onRemoveFromGroup] - 从当前分组移除 + * @param {(row: any, meta: { hasHolding: boolean }) => void} [props.onHoldingAmountClick] - 点击持仓金额 + * @param {(row: any) => void} [props.onHoldingProfitClick] - 点击持有收益 + * @param {boolean} [props.refreshing] - 是否处于刷新状态(控制删除按钮禁用态) + */ +export default function PcFundTable({ + data = [], + onRemoveFund, + currentTab, + favorites = new Set(), + onToggleFavorite, + onRemoveFromGroup, + onHoldingAmountClick, + onHoldingProfitClick, + refreshing = false, +}) { + const onRemoveFundRef = useRef(onRemoveFund); + const onToggleFavoriteRef = useRef(onToggleFavorite); + const onRemoveFromGroupRef = useRef(onRemoveFromGroup); + const onHoldingAmountClickRef = useRef(onHoldingAmountClick); + const onHoldingProfitClickRef = useRef(onHoldingProfitClick); + + useEffect(() => { + onRemoveFundRef.current = onRemoveFund; + onToggleFavoriteRef.current = onToggleFavorite; + onRemoveFromGroupRef.current = onRemoveFromGroup; + onHoldingAmountClickRef.current = onHoldingAmountClick; + onHoldingProfitClickRef.current = onHoldingProfitClick; + }, [ + onRemoveFund, + onToggleFavorite, + onRemoveFromGroup, + onHoldingAmountClick, + onHoldingProfitClick, + ]); + const columns = useMemo( + () => [ + { + accessorKey: 'fundName', + header: '基金名称', + size: 240, + minSize: 100, + enablePinning: true, + cell: (info) => { + const original = info.row.original || {}; + const code = original.code; + const isUpdated = original.isUpdated; + const isFavorites = favorites?.has?.(code); + const isGroupTab = currentTab && currentTab !== 'all' && currentTab !== 'fav'; + return ( +
+ {isGroupTab ? ( + + ) : ( + + )} +
+ + {info.getValue() ?? '—'} + + {code ? #{code} : null} +
+
+ ); + }, + meta: { + align: 'left', + cellClassName: 'name-cell', + }, + }, + { + accessorKey: 'navOrEstimate', + header: '净值/估值', + size: 100, + minSize: 80, + cell: (info) => ( + {info.getValue() ?? '—'} + ), + meta: { + align: 'right', + cellClassName: 'value-cell', + }, + }, + { + accessorKey: 'yesterdayChangePercent', + header: '昨日涨跌幅', + cell: (info) => { + const original = info.row.original || {}; + const value = original.yesterdayChangeValue; + const date = original.yesterdayDate ?? '-'; + const cls = value > 0 ? 'up' : value < 0 ? 'down' : ''; + return ( +
+ + {info.getValue() ?? '—'} + + + {date} + +
+ ); + }, + meta: { + align: 'right', + cellClassName: 'change-cell', + }, + }, + { + accessorKey: 'estimateChangePercent', + header: '估值涨跌幅', + size: 100, + minSize: 80, + cell: (info) => { + const original = info.row.original || {}; + const value = original.estimateChangeValue; + const isMuted = original.estimateChangeMuted; + const time = original.estimateTime ?? '-'; + const cls = isMuted ? 'muted' : value > 0 ? 'up' : value < 0 ? 'down' : ''; + return ( +
+ + {info.getValue() ?? '—'} + + + {time} + +
+ ); + }, + meta: { + align: 'right', + cellClassName: 'est-change-cell', + }, + }, + { + accessorKey: 'holdingAmount', + header: '持仓金额', + cell: (info) => { + const original = info.row.original || {}; + if (original.holdingAmountValue == null) { + return ( +
{ + e.stopPropagation?.(); + onHoldingAmountClickRef.current?.(original, { hasHolding: false }); + }} + onKeyDown={(e) => { + if (e.key === 'Enter' || e.key === ' ') { + e.preventDefault(); + onHoldingAmountClickRef.current?.(original, { hasHolding: false }); + } + }} + > + 未设置 +
+ ); + } + return ( +
{ + e.stopPropagation?.(); + onHoldingAmountClickRef.current?.(original, { hasHolding: true }); + }} + > + {info.getValue() ?? '—'} + +
+ ); + }, + meta: { + align: 'right', + cellClassName: 'holding-amount-cell', + }, + }, + { + accessorKey: 'todayProfit', + header: '当日收益', + cell: (info) => { + const original = info.row.original || {}; + const value = original.todayProfitValue; + const hasProfit = value != null; + const cls = hasProfit ? (value > 0 ? 'up' : value < 0 ? 'down' : '') : 'muted'; + return ( + + {hasProfit ? (info.getValue() ?? '') : ''} + + ); + }, + meta: { + align: 'right', + cellClassName: 'profit-cell', + }, + }, + { + accessorKey: 'holdingProfit', + header: '持有收益', + size: 140, + minSize: 100, + cell: (info) => { + const original = info.row.original || {}; + const value = original.holdingProfitValue; + const hasTotal = value != null; + const cls = hasTotal ? (value > 0 ? 'up' : value < 0 ? 'down' : '') : 'muted'; + return ( +
{ + if (!hasTotal) return; + e.stopPropagation?.(); + onHoldingProfitClickRef.current?.(original); + }} + > + + {hasTotal ? (info.getValue() ?? '') : ''} + +
+ ); + }, + meta: { + align: 'right', + cellClassName: 'holding-cell', + }, + }, + { + id: 'actions', + header: '操作', + size: 80, + minSize: 80, + maxSize: 80, + enableResizing: false, + enablePinning: true, + meta: { + align: 'center', + isAction: true, + cellClassName: 'action-cell', + }, + cell: (info) => { + const original = info.row.original || {}; + + const handleClick = (e) => { + e.stopPropagation?.(); + if (refreshing) return; + onRemoveFundRef.current?.(original); + }; + + return ( +
+ +
+ ); + }, + }, + ], + [currentTab, favorites, refreshing], + ); + + const table = useReactTable({ + data, + columns, + enableColumnPinning: true, + enableColumnResizing: true, + columnResizeMode: 'onChange', + initialState: { + columnPinning: { + left: ['fundName'], + right: ['actions'], + }, + }, + getCoreRowModel: getCoreRowModel(), + defaultColumn: { + cell: (info) => info.getValue() ?? '—', + }, + }); + + const headerGroup = table.getHeaderGroups()[0]; + + const getCommonPinningStyles = (column, isHeader) => { + const isPinned = column.getIsPinned(); + const isNameColumn = + column.id === 'fundName' || column.columnDef?.accessorKey === 'fundName'; + const style = { + width: `${column.getSize()}px`, + }; + if (!isPinned) return style; + + const isLeft = isPinned === 'left'; + const isRight = isPinned === 'right'; + + return { + ...style, + position: 'sticky', + left: isLeft ? `${column.getStart('left')}px` : undefined, + right: isRight ? `${column.getAfter('right')}px` : undefined, + zIndex: isHeader ? 11 : 10, + backgroundColor: isHeader ? '#2a394b' : 'var(--row-bg)', + boxShadow: 'none', + textAlign: isNameColumn ? 'left' : 'center', + justifyContent: isNameColumn ? 'flex-start' : 'center', + }; + }; + + return ( + <> + + {/* 表头 */} + {headerGroup && ( +
+ {headerGroup.headers.map((header) => { + const style = getCommonPinningStyles(header.column, true); + const isNameColumn = + header.column.id === 'fundName' || + header.column.columnDef?.accessorKey === 'fundName'; + const align = isNameColumn ? '' : 'text-center'; + return ( +
+ {header.isPlaceholder + ? null + : flexRender( + header.column.columnDef.header, + header.getContext(), + )} +
+
+ ); + })} +
+ )} + + {/* 表体 */} + + {table.getRowModel().rows.map((row) => ( + +
+ {row.getVisibleCells().map((cell) => { + const columnId = cell.column.id || cell.column.columnDef?.accessorKey; + const isNameColumn = columnId === 'fundName'; + const rightAlignedColumns = new Set([ + 'yesterdayChangePercent', + 'estimateChangePercent', + 'holdingAmount', + 'todayProfit', + 'holdingProfit', + ]); + const align = isNameColumn + ? '' + : rightAlignedColumns.has(columnId) + ? 'text-right' + : 'text-center'; + const cellClassName = + (cell.column.columnDef.meta && cell.column.columnDef.meta.cellClassName) || ''; + const style = getCommonPinningStyles(cell.column, false); + return ( +
+ {flexRender( + cell.column.columnDef.cell, + cell.getContext(), + )} +
+ ); + })} +
+
+ ))} +
+ + {table.getRowModel().rows.length === 0 && ( +
+
+ 暂无数据 +
+
+ )} + + ); +} diff --git a/app/globals.css b/app/globals.css index b597532..223ad9d 100644 --- a/app/globals.css +++ b/app/globals.css @@ -844,7 +844,6 @@ input[type="number"] { min-height: 52px; box-sizing: border-box; border-bottom: 1px solid var(--border); - transition: background-color 0.2s ease; } .table-row-scroll:hover, @@ -895,16 +894,15 @@ input[type="number"] { .table-row { display: grid; - grid-template-columns: 2.2fr 0.8fr 1fr 1fr 1.2fr 1.2fr 1.2fr; + grid-template-columns: 2.2fr 0.8fr 1fr 1fr 1.2fr 1.2fr 1.2fr 0.5fr; align-items: center; gap: 12px; padding: 12px 24px !important; border-bottom: 1px solid var(--border); - transition: background-color 0.2s ease; } .table-row:hover { - background: rgba(255, 255, 255, 0.03); + background: #2a394b; } .table-row:last-child { @@ -913,10 +911,10 @@ input[type="number"] { .table-header-row { display: grid; - grid-template-columns: 2.2fr 0.8fr 1fr 1fr 1.2fr 1.2fr 1.2fr; + grid-template-columns: 2.2fr 0.8fr 1fr 1fr 1.2fr 1.2fr 1.2fr 0.5fr; gap: 12px; padding: 16px 24px; - background: rgba(255, 255, 255, 0.05); + background: #2a394b; border-bottom: 1px solid var(--border); } diff --git a/app/page.jsx b/app/page.jsx index 974ab6a..16ce931 100644 --- a/app/page.jsx +++ b/app/page.jsx @@ -43,6 +43,7 @@ import { supabase, isSupabaseConfigured } from './lib/supabase'; import { recordValuation, getAllValuationSeries, clearFund } from './lib/valuationTimeseries'; import { fetchFundData, fetchLatestRelease, fetchShanghaiIndexDate, fetchSmartFundNetValue, searchFunds, extractFundNamesWithLLM } from './api/fund'; import packageJson from '../package.json'; +import PcFundTable from './components/PcFundTable'; dayjs.extend(utc); dayjs.extend(timezone); @@ -560,7 +561,7 @@ export default function HomePage() { }, []); // 计算持仓收益 - const getHoldingProfit = (fund, holding) => { + const getHoldingProfit = useCallback((fund, holding) => { if (!holding || !isNumber(holding.share)) return null; const hasTodayData = fund.jzrq === todayStr; @@ -634,35 +635,121 @@ export default function HomePage() { profitToday, profitTotal }; - }; + }, [isTradingDay, todayStr]); // 过滤和排序后的基金列表 - const displayFunds = funds - .filter(f => { - if (currentTab === 'all') return true; - if (currentTab === 'fav') return favorites.has(f.code); - const group = groups.find(g => g.id === currentTab); - return group ? group.codes.includes(f.code) : true; - }) - .sort((a, b) => { - if (sortBy === 'yield') { - const valA = isNumber(a.estGszzl) ? a.estGszzl : (a.gszzl ?? a.zzl ?? 0); - const valB = isNumber(b.estGszzl) ? b.estGszzl : (b.gszzl ?? a.zzl ?? 0); - return sortOrder === 'asc' ? valA - valB : valB - valA; - } - if (sortBy === 'holding') { - const pa = getHoldingProfit(a, holdings[a.code]); - const pb = getHoldingProfit(b, holdings[b.code]); - const valA = pa?.profitTotal ?? Number.NEGATIVE_INFINITY; - const valB = pb?.profitTotal ?? Number.NEGATIVE_INFINITY; - return sortOrder === 'asc' ? valA - valB : valB - valA; - } - if (sortBy === 'name') { - return sortOrder === 'asc' ? a.name.localeCompare(b.name, 'zh-CN') : b.name.localeCompare(a.name, 'zh-CN'); - } - return 0; - }); + const displayFunds = useMemo( + () => funds + .filter(f => { + if (currentTab === 'all') return true; + if (currentTab === 'fav') return favorites.has(f.code); + const group = groups.find(g => g.id === currentTab); + return group ? group.codes.includes(f.code) : true; + }) + .sort((a, b) => { + if (sortBy === 'yield') { + const valA = isNumber(a.estGszzl) ? a.estGszzl : (a.gszzl ?? a.zzl ?? 0); + const valB = isNumber(b.estGszzl) ? b.estGszzl : (b.gszzl ?? a.zzl ?? 0); + return sortOrder === 'asc' ? valA - valB : valB - valA; + } + if (sortBy === 'holding') { + const pa = getHoldingProfit(a, holdings[a.code]); + const pb = getHoldingProfit(b, holdings[b.code]); + const valA = pa?.profitTotal ?? Number.NEGATIVE_INFINITY; + const valB = pb?.profitTotal ?? Number.NEGATIVE_INFINITY; + return sortOrder === 'asc' ? valA - valB : valB - valA; + } + if (sortBy === 'name') { + return sortOrder === 'asc' ? a.name.localeCompare(b.name, 'zh-CN') : b.name.localeCompare(a.name, 'zh-CN'); + } + return 0; + }), + [funds, currentTab, favorites, groups, sortBy, sortOrder, holdings, getHoldingProfit], + ); + + // PC 端表格数据(用于 PcFundTable) + const pcFundTableData = useMemo( + () => + displayFunds.map((f) => { + const hasTodayData = f.jzrq === todayStr; + const shouldHideChange = isTradingDay && !hasTodayData; + const navOrEstimate = !shouldHideChange + ? (f.dwjz ?? '—') + : (f.noValuation + ? (f.dwjz ?? '—') + : (f.estPricedCoverage > 0.05 + ? (f.estGsz != null ? Number(f.estGsz).toFixed(4) : '—') + : (f.gsz ?? '—'))); + + const yesterdayChangePercent = + f.zzl != null && f.zzl !== '' + ? `${f.zzl > 0 ? '+' : ''}${Number(f.zzl).toFixed(2)}%` + : '—'; + const yesterdayChangeValue = + f.zzl != null && f.zzl !== '' ? Number(f.zzl) : null; + const yesterdayDate = f.jzrq || '-'; + + const estimateChangePercent = f.noValuation + ? '—' + : (f.estPricedCoverage > 0.05 + ? (f.estGszzl != null + ? `${f.estGszzl > 0 ? '+' : ''}${Number(f.estGszzl).toFixed(2)}%` + : '—') + : (isNumber(f.gszzl) + ? `${f.gszzl > 0 ? '+' : ''}${Number(f.gszzl).toFixed(2)}%` + : (f.gszzl ?? '—'))); + const estimateChangeValue = f.noValuation + ? null + : (f.estPricedCoverage > 0.05 + ? (isNumber(f.estGszzl) ? Number(f.estGszzl) : null) + : (isNumber(f.gszzl) ? Number(f.gszzl) : null)); + const estimateTime = f.noValuation ? (f.jzrq || '-') : (f.gztime || f.time || '-'); + + const holding = holdings[f.code]; + const profit = getHoldingProfit(f, holding); + const amount = profit ? profit.amount : null; + const holdingAmount = + amount == null ? '未设置' : `¥${amount.toFixed(2)}`; + const holdingAmountValue = amount; + + const profitToday = profit ? profit.profitToday : null; + const todayProfit = + profitToday == null + ? '' + : `${profitToday > 0 ? '+' : profitToday < 0 ? '-' : ''}¥${Math.abs(profitToday).toFixed(2)}`; + const todayProfitValue = profitToday; + + const total = profit ? profit.profitTotal : null; + const holdingProfit = + total == null + ? '' + : `${total > 0 ? '+' : total < 0 ? '-' : ''}¥${Math.abs(total).toFixed(2)}`; + const holdingProfitValue = total; + + return { + rawFund: f, + code: f.code, + fundName: f.name, + isUpdated: f.jzrq === todayStr, + navOrEstimate, + yesterdayChangePercent, + yesterdayChangeValue, + yesterdayDate, + estimateChangePercent, + estimateChangeValue, + estimateChangeMuted: f.noValuation, + estimateTime, + holdingAmount, + holdingAmountValue, + todayProfit, + todayProfitValue, + holdingProfit, + holdingProfitValue, + }; + }), + [displayFunds, holdings, isTradingDay, todayStr, getHoldingProfit], + ); // 自动滚动选中 Tab 到可视区域 useEffect(() => { @@ -3639,159 +3726,47 @@ export default function HomePage() { className={viewMode === 'card' ? 'grid' : 'table-container glass'} >
- {/* PC 列表:左右分块,左侧 8 列可横向滚动,右侧操作列固定 */} + {/* PC 列表:使用 PcFundTable + 右侧冻结操作列 */} {viewMode === 'list' && !isMobile && ( -
-
-
-
-
基金名称
-
净值/估值
-
昨日涨跌幅
-
估值涨跌幅
-
持仓金额
-
当日收益
-
持有收益
-
- - {displayFunds.map((f) => ( - setHoveredPcRowCode(f.code)} - onMouseLeave={() => setHoveredPcRowCode(null)} - > -
-
- {currentTab !== 'all' && currentTab !== 'fav' ? ( - - ) : ( - - )} -
- {f.name} - #{f.code} -
-
- {(() => { - const hasTodayData = f.jzrq === todayStr; - const shouldHideChange = isTradingDay && !hasTodayData; - const valueDisplay = !shouldHideChange ? (f.dwjz ?? '—') : (f.noValuation ? (f.dwjz ?? '—') : (f.estPricedCoverage > 0.05 ? f.estGsz.toFixed(4) : (f.gsz ?? '—'))); - return ( -
- {valueDisplay} -
- ); - })()} -
-
- 0 ? 'up' : f.zzl < 0 ? 'down' : ''} style={{ fontWeight: 700 }}> - {f.zzl != null && f.zzl !== '' ? `${f.zzl > 0 ? '+' : ''}${Number(f.zzl).toFixed(2)}%` : '—'} - - - {f.jzrq || '-'} - -
-
-
-
- 0.05 ? (f.estGszzl > 0 ? 'up' : f.estGszzl < 0 ? 'down' : '') : (Number(f.gszzl) > 0 ? 'up' : Number(f.gszzl) < 0 ? 'down' : ''))} style={{ fontWeight: 700 }}> - {f.noValuation ? '—' : (f.estPricedCoverage > 0.05 ? (f.estGszzl != null ? `${f.estGszzl > 0 ? '+' : ''}${Number(f.estGszzl).toFixed(2)}%` : '—') : (isNumber(f.gszzl) ? `${f.gszzl > 0 ? '+' : ''}${Number(f.gszzl).toFixed(2)}%` : (f.gszzl ?? '—')))} - - - {f.noValuation ? (f.jzrq || '-') : (f.gztime || f.time || '-')} - -
-
- {(() => { - const holding = holdings[f.code]; - const profit = getHoldingProfit(f, holding); - const amount = profit ? profit.amount : null; - if (amount === null) { - return ( -
{ e.stopPropagation(); setHoldingModal({ open: true, fund: f }); }}> - 未设置 -
- ); - } - return ( -
{ e.stopPropagation(); setActionModal({ open: true, fund: f }); }}> - ¥{amount.toFixed(2)} - -
- ); - })()} - {(() => { - const holding = holdings[f.code]; - const profit = getHoldingProfit(f, holding); - const profitValue = profit ? profit.profitToday : null; - const hasProfit = profitValue !== null; - return ( -
- 0 ? 'up' : profitValue < 0 ? 'down' : '') : 'muted'} style={{ fontWeight: 700 }}> - {hasProfit ? `${profitValue > 0 ? '+' : profitValue < 0 ? '-' : ''}¥${Math.abs(profitValue).toFixed(2)}` : ''} - -
- ); - })()} - {(() => { - const holding = holdings[f.code]; - const profit = getHoldingProfit(f, holding); - const total = profit ? profit.profitTotal : null; - const principal = holding && holding.cost && holding.share ? holding.cost * holding.share : 0; - const asPercent = percentModes[f.code]; - const hasTotal = total !== null; - const formatted = hasTotal ? (asPercent && principal > 0 ? `${total > 0 ? '+' : total < 0 ? '-' : ''}${Math.abs((total / principal) * 100).toFixed(2)}%` : `${total > 0 ? '+' : total < 0 ? '-' : ''}¥${Math.abs(total).toFixed(2)}`) : ''; - const cls = hasTotal ? (total > 0 ? 'up' : total < 0 ? 'down' : '') : 'muted'; - return ( -
{ e.stopPropagation(); if (hasTotal) setPercentModes(prev => ({ ...prev, [f.code]: !prev[f.code] })); }} style={{ cursor: hasTotal ? 'pointer' : 'default' }}> - {formatted} -
- ); - })()} -
-
- ))} -
+
+
+
+ { + if (refreshing) return; + if (!row || !row.code) return; + requestRemoveFund({ code: row.code, name: row.fundName }); + }} + onToggleFavorite={(row) => { + if (!row || !row.code) return; + toggleFavorite(row.code); + }} + onRemoveFromGroup={(row) => { + if (!row || !row.code) return; + removeFundFromCurrentGroup(row.code); + }} + onHoldingAmountClick={(row, meta) => { + if (!row || !row.code) return; + const fund = row.rawFund || { code: row.code, name: row.fundName }; + if (meta?.hasHolding) { + setActionModal({ open: true, fund }); + } else { + setHoldingModal({ open: true, fund }); + } + }} + onHoldingProfitClick={(row) => { + if (!row || !row.code) return; + if (row.holdingProfitValue == null) return; + setPercentModes(prev => ({ ...prev, [row.code]: !prev[row.code] })); + }} + /> +
-
-
操作
- - {displayFunds.map((f) => ( - setHoveredPcRowCode(f.code)} - onMouseLeave={() => setHoveredPcRowCode(null)} - > -
- -
-
- ))} -
-
-
)} {viewMode === 'list' && isMobile && (
@@ -4734,4 +4709,3 @@ export default function HomePage() {
); } - diff --git a/package-lock.json b/package-lock.json index 392f583..bdb90c9 100644 --- a/package-lock.json +++ b/package-lock.json @@ -11,6 +11,7 @@ "@dicebear/collection": "^9.3.1", "@dicebear/core": "^9.3.1", "@supabase/supabase-js": "^2.78.0", + "@tanstack/react-table": "^8.21.3", "chart.js": "^4.5.1", "dayjs": "^1.11.19", "framer-motion": "^12.29.2", @@ -1772,6 +1773,39 @@ "tslib": "^2.8.0" } }, + "node_modules/@tanstack/react-table": { + "version": "8.21.3", + "resolved": "https://registry.npmjs.org/@tanstack/react-table/-/react-table-8.21.3.tgz", + "integrity": "sha512-5nNMTSETP4ykGegmVkhjcS8tTLW6Vl4axfEGQN3v0zdHYbK4UfoqfPChclTrJ4EoK9QynqAu9oUf8VEmrpZ5Ww==", + "license": "MIT", + "dependencies": { + "@tanstack/table-core": "8.21.3" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/tannerlinsley" + }, + "peerDependencies": { + "react": ">=16.8", + "react-dom": ">=16.8" + } + }, + "node_modules/@tanstack/table-core": { + "version": "8.21.3", + "resolved": "https://registry.npmjs.org/@tanstack/table-core/-/table-core-8.21.3.tgz", + "integrity": "sha512-ldZXEhOBb8Is7xLs01fR3YEc3DERiz5silj8tnGkFZytt1abEvl/GhUmCE0PMLaMPTa3Jk4HbKmRlHmu+gCftg==", + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/tannerlinsley" + } + }, "node_modules/@tybys/wasm-util": { "version": "0.10.1", "resolved": "https://registry.npmjs.org/@tybys/wasm-util/-/wasm-util-0.10.1.tgz", diff --git a/package.json b/package.json index 422eb30..e60007d 100644 --- a/package.json +++ b/package.json @@ -14,6 +14,7 @@ "@dicebear/collection": "^9.3.1", "@dicebear/core": "^9.3.1", "@supabase/supabase-js": "^2.78.0", + "@tanstack/react-table": "^8.21.3", "chart.js": "^4.5.1", "dayjs": "^1.11.19", "framer-motion": "^12.29.2",