diff --git a/website/src/lib/components/self/DataTable.svelte b/website/src/lib/components/self/DataTable.svelte index a9fd26c..709671f 100644 --- a/website/src/lib/components/self/DataTable.svelte +++ b/website/src/lib/components/self/DataTable.svelte @@ -6,12 +6,14 @@ import CoinIcon from './CoinIcon.svelte'; import UserProfilePreview from './UserProfilePreview.svelte'; import { getPublicUrl } from '$lib/utils'; - import { ArrowUp, ArrowDown } from 'lucide-svelte'; + import { ArrowUp, ArrowDown, ArrowUpDown } from 'lucide-svelte'; + interface Column { key: string; label: string; class?: string; sortable?: boolean; + defaultSort?: boolean | 'asc' | 'desc'; render?: (value: any, row: any, index: number) => any; } @@ -34,6 +36,49 @@ emptyDescription?: string; enableUserPreview?: boolean; } = $props(); + + const defaultSortColumn = columns.find((col) => col.defaultSort); + let sortColumn = $state(defaultSortColumn?.key || null); + let sortDirection = $state<'asc' | 'desc'>( + defaultSortColumn?.defaultSort === 'asc' ? 'asc' : 'desc' + ); + + let sortedData = $derived.by(() => { + if (!sortColumn) return data; + + return [...data].sort((a, b) => { + let aValue = a[sortColumn!]; + let bValue = b[sortColumn!]; + + // Handle numeric values + if (typeof aValue === 'string' && !isNaN(Number(aValue))) { + aValue = Number(aValue); + } + if (typeof bValue === 'string' && !isNaN(Number(bValue))) { + bValue = Number(bValue); + } + + // Handle null/undefined values + if (aValue == null && bValue == null) return 0; + if (aValue == null) return sortDirection === 'asc' ? -1 : 1; + if (bValue == null) return sortDirection === 'asc' ? 1 : -1; + + // Compare values + if (aValue < bValue) return sortDirection === 'asc' ? -1 : 1; + if (aValue > bValue) return sortDirection === 'asc' ? 1 : -1; + return 0; + }); + }); + + function handleSort(columnKey: string) { + if (sortColumn === columnKey) { + sortDirection = sortDirection === 'asc' ? 'desc' : 'asc'; + } else { + sortColumn = columnKey; + sortDirection = 'desc'; + } + } + function renderCell(column: any, row: any, value: any, index: number) { if (column.render) { const rendered = column.render(value, row, index); @@ -101,11 +146,32 @@ {#each columns as column (column.key)} - {column.label} + + {#if column.sortable} + + {:else} + {column.label} + {/if} + {/each} - - {#each data as row, index (row.symbol || row.id || index)} + + + {#each sortedData as row, index (row.symbol || row.id || index)} onRowClick(row) : undefined} @@ -132,7 +198,7 @@
- +
#{cellData.number} diff --git a/website/src/routes/portfolio/+page.svelte b/website/src/routes/portfolio/+page.svelte index 8bb4fbe..56a4d4d 100644 --- a/website/src/routes/portfolio/+page.svelte +++ b/website/src/routes/portfolio/+page.svelte @@ -84,18 +84,21 @@ key: 'quantity', label: 'Quantity', class: 'w-[20%] min-w-[80px] md:w-[12%] font-mono', + sortable: true, render: (value: any) => formatQuantity(value) }, { key: 'currentPrice', label: 'Price', class: 'w-[15%] min-w-[70px] md:w-[12%] font-mono', + sortable: true, render: (value: any) => `$${formatPrice(value)}` }, { key: 'change24h', label: '24h Change', class: 'w-[20%] min-w-[80px] md:w-[12%]', + sortable: true, render: (value: any) => ({ component: 'badge', variant: value >= 0 ? 'success' : 'destructive', @@ -106,6 +109,8 @@ key: 'value', label: 'Value', class: 'w-[15%] min-w-[70px] md:w-[12%] font-mono font-medium', + sortable: true, + defaultSort: true, render: (value: any) => formatValue(value) }, { @@ -231,6 +236,19 @@
+
+
+

Portfolio

+

Manage your investments and transactions

+
+
+ +
+
+ {#if loading} {:else if !$USER_DATA} @@ -252,20 +270,6 @@ {:else}
-
-
-

Portfolio

-

Manage your investments and transactions

-
-
- - -
-
-