sort assets in portfolio

This commit is contained in:
Face 2025-06-08 22:16:46 +03:00
parent 5001deed68
commit 7829be19f1
2 changed files with 89 additions and 19 deletions

View file

@ -6,12 +6,14 @@
import CoinIcon from './CoinIcon.svelte'; import CoinIcon from './CoinIcon.svelte';
import UserProfilePreview from './UserProfilePreview.svelte'; import UserProfilePreview from './UserProfilePreview.svelte';
import { getPublicUrl } from '$lib/utils'; import { getPublicUrl } from '$lib/utils';
import { ArrowUp, ArrowDown } from 'lucide-svelte'; import { ArrowUp, ArrowDown, ArrowUpDown } from 'lucide-svelte';
interface Column { interface Column {
key: string; key: string;
label: string; label: string;
class?: string; class?: string;
sortable?: boolean; sortable?: boolean;
defaultSort?: boolean | 'asc' | 'desc';
render?: (value: any, row: any, index: number) => any; render?: (value: any, row: any, index: number) => any;
} }
@ -34,6 +36,49 @@
emptyDescription?: string; emptyDescription?: string;
enableUserPreview?: boolean; enableUserPreview?: boolean;
} = $props(); } = $props();
const defaultSortColumn = columns.find((col) => col.defaultSort);
let sortColumn = $state<string | null>(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) { function renderCell(column: any, row: any, value: any, index: number) {
if (column.render) { if (column.render) {
const rendered = column.render(value, row, index); const rendered = column.render(value, row, index);
@ -101,11 +146,32 @@
<Table.Header> <Table.Header>
<Table.Row> <Table.Row>
{#each columns as column (column.key)} {#each columns as column (column.key)}
<Table.Head class={column.class || 'min-w-[80px]'}>{column.label}</Table.Head> <Table.Head class={column.class || 'min-w-[80px]'}>
{#if column.sortable}
<button
onclick={() => handleSort(column.key)}
class="hover:text-foreground flex items-center gap-1 transition-colors"
>
{column.label}
{#if sortColumn === column.key}
{#if sortDirection === 'asc'}
<ArrowUp class="text-primary h-4 w-4" />
{:else}
<ArrowDown class="text-primary h-4 w-4" />
{/if}
{:else}
<ArrowUpDown class="h-4 w-4 opacity-50" />
{/if}
</button>
{:else}
{column.label}
{/if}
</Table.Head>
{/each} {/each}
</Table.Row> </Table.Row>
</Table.Header><Table.Body> </Table.Header>
{#each data as row, index (row.symbol || row.id || index)} <Table.Body>
{#each sortedData as row, index (row.symbol || row.id || index)}
<Table.Row <Table.Row
class={onRowClick ? 'hover:bg-muted/50 cursor-pointer transition-colors' : ''} class={onRowClick ? 'hover:bg-muted/50 cursor-pointer transition-colors' : ''}
onclick={onRowClick ? () => onRowClick(row) : undefined} onclick={onRowClick ? () => onRowClick(row) : undefined}
@ -132,7 +198,7 @@
<div <div
class={`${cellData.color} flex h-7 w-7 items-center justify-center rounded-full text-xs font-medium`} class={`${cellData.color} flex h-7 w-7 items-center justify-center rounded-full text-xs font-medium`}
> >
<svelte:component this={cellData.icon} class="h-3.5 w-3.5" /> <cellData.icon class="h-3.5 w-3.5" />
</div> </div>
<span class="text-sm font-medium">#{cellData.number}</span> <span class="text-sm font-medium">#{cellData.number}</span>
</div> </div>

View file

@ -84,18 +84,21 @@
key: 'quantity', key: 'quantity',
label: 'Quantity', label: 'Quantity',
class: 'w-[20%] min-w-[80px] md:w-[12%] font-mono', class: 'w-[20%] min-w-[80px] md:w-[12%] font-mono',
sortable: true,
render: (value: any) => formatQuantity(value) render: (value: any) => formatQuantity(value)
}, },
{ {
key: 'currentPrice', key: 'currentPrice',
label: 'Price', label: 'Price',
class: 'w-[15%] min-w-[70px] md:w-[12%] font-mono', class: 'w-[15%] min-w-[70px] md:w-[12%] font-mono',
sortable: true,
render: (value: any) => `$${formatPrice(value)}` render: (value: any) => `$${formatPrice(value)}`
}, },
{ {
key: 'change24h', key: 'change24h',
label: '24h Change', label: '24h Change',
class: 'w-[20%] min-w-[80px] md:w-[12%]', class: 'w-[20%] min-w-[80px] md:w-[12%]',
sortable: true,
render: (value: any) => ({ render: (value: any) => ({
component: 'badge', component: 'badge',
variant: value >= 0 ? 'success' : 'destructive', variant: value >= 0 ? 'success' : 'destructive',
@ -106,6 +109,8 @@
key: 'value', key: 'value',
label: 'Value', label: 'Value',
class: 'w-[15%] min-w-[70px] md:w-[12%] font-mono font-medium', class: 'w-[15%] min-w-[70px] md:w-[12%] font-mono font-medium',
sortable: true,
defaultSort: true,
render: (value: any) => formatValue(value) render: (value: any) => formatValue(value)
}, },
{ {
@ -231,6 +236,19 @@
<SendMoneyModal bind:open={sendMoneyModalOpen} onSuccess={handleTransferSuccess} /> <SendMoneyModal bind:open={sendMoneyModalOpen} onSuccess={handleTransferSuccess} />
<div class="container mx-auto max-w-7xl p-6"> <div class="container mx-auto max-w-7xl p-6">
<div class="mb-6 flex flex-col items-start justify-between gap-4 sm:flex-row sm:items-center">
<div>
<h1 class="text-3xl font-bold">Portfolio</h1>
<p class="text-muted-foreground">Manage your investments and transactions</p>
</div>
<div class="flex gap-2">
<Button onclick={() => (sendMoneyModalOpen = true)}>
<Send class="h-4 w-4" />
Send Money
</Button>
</div>
</div>
{#if loading} {#if loading}
<PortfolioSkeleton /> <PortfolioSkeleton />
{:else if !$USER_DATA} {:else if !$USER_DATA}
@ -252,20 +270,6 @@
{:else} {:else}
<!-- Portfolio Overview --> <!-- Portfolio Overview -->
<div class="mb-8"> <div class="mb-8">
<div class="mb-6 flex flex-col items-start justify-between gap-4 sm:flex-row sm:items-center">
<div>
<h1 class="text-3xl font-bold">Portfolio</h1>
<p class="text-muted-foreground">Manage your investments and transactions</p>
</div>
<div class="flex gap-2">
<Button onclick={() => (sendMoneyModalOpen = true)}>
<Send class="h-4 w-4" />
Send Money
</Button>
<!-- ...existing buttons... -->
</div>
</div>
<!-- Portfolio Summary Cards --> <!-- Portfolio Summary Cards -->
<div class="mb-8 grid grid-cols-1 gap-4 lg:grid-cols-3"> <div class="mb-8 grid grid-cols-1 gap-4 lg:grid-cols-3">
<!-- Total Portfolio Value --> <!-- Total Portfolio Value -->