feat: create ChartSelector component with dropdown, delete, and empty state

This commit is contained in:
Marko Djordjevic 2026-02-13 00:14:53 +01:00
parent 90e1e179cc
commit a3c7137b01
2 changed files with 123 additions and 3 deletions

View file

@ -28,9 +28,9 @@
## 5. Chart Selector UI Component
- [ ] 5.1 Create a `ChartSelector` component with dropdown listing all charts (name + created date), positioned in the sidebar between header and file upload
- [ ] 5.2 Add delete button per chart in the dropdown with confirmation dialog
- [ ] 5.3 Show "No charts — upload a CSV to get started" placeholder when no charts exist
- [x] 5.1 Create a `ChartSelector` component with dropdown listing all charts (name + created date), positioned in the sidebar between header and file upload
- [x] 5.2 Add delete button per chart in the dropdown with confirmation dialog
- [x] 5.3 Show "No charts — upload a CSV to get started" placeholder when no charts exist
## 6. Frontend State & Data Flow

View file

@ -0,0 +1,120 @@
'use client';
import { useState } from 'react';
import { ChevronDown, Trash2 } from 'lucide-react';
interface Chart {
id: number;
name: string;
created_at: number;
}
interface ChartSelectorProps {
charts: Chart[];
activeChartId: number | null;
onSelectChart: (chartId: number) => void;
onDeleteChart: (chartId: number) => void;
}
export default function ChartSelector({
charts,
activeChartId,
onSelectChart,
onDeleteChart,
}: ChartSelectorProps) {
const [isOpen, setIsOpen] = useState(false);
const [confirmDeleteId, setConfirmDeleteId] = useState<number | null>(null);
const activeChart = charts.find((c) => c.id === activeChartId);
if (charts.length === 0) {
return (
<div className="text-sm text-muted-foreground italic">
No charts upload a CSV to get started
</div>
);
}
const formatDate = (timestamp: number) => {
return new Date(timestamp * 1000).toLocaleDateString(undefined, {
month: 'short',
day: 'numeric',
year: 'numeric',
});
};
return (
<div className="relative">
<button
onClick={() => setIsOpen(!isOpen)}
className="w-full flex items-center justify-between px-3 py-2 text-sm rounded-md border border-border bg-background hover:bg-accent text-foreground"
>
<span className="truncate">{activeChart?.name || 'Select chart'}</span>
<ChevronDown className={`h-4 w-4 ml-2 flex-shrink-0 transition-transform ${isOpen ? 'rotate-180' : ''}`} />
</button>
{isOpen && (
<div className="absolute z-50 mt-1 w-full rounded-md border border-border bg-popover shadow-md max-h-64 overflow-y-auto">
{charts.map((chart) => (
<div
key={chart.id}
className={`flex items-center justify-between px-3 py-2 text-sm hover:bg-accent cursor-pointer ${
chart.id === activeChartId ? 'bg-accent/50' : ''
}`}
>
<div
className="flex-1 min-w-0"
onClick={() => {
onSelectChart(chart.id);
setIsOpen(false);
}}
>
<div className="truncate text-foreground">{chart.name}</div>
<div className="text-xs text-muted-foreground">{formatDate(chart.created_at)}</div>
</div>
<button
onClick={(e) => {
e.stopPropagation();
setConfirmDeleteId(chart.id);
}}
className="ml-2 p-1 text-muted-foreground hover:text-destructive flex-shrink-0"
title="Delete chart"
>
<Trash2 className="h-3.5 w-3.5" />
</button>
</div>
))}
</div>
)}
{/* Confirmation dialog */}
{confirmDeleteId !== null && (
<div className="fixed inset-0 z-50 flex items-center justify-center bg-black/50">
<div className="bg-popover border border-border rounded-lg p-6 max-w-sm mx-4 shadow-lg">
<p className="text-sm text-foreground">
Delete chart &ldquo;{charts.find((c) => c.id === confirmDeleteId)?.name}&rdquo; and all its candles and annotations? This cannot be undone.
</p>
<div className="flex justify-end gap-2 mt-4">
<button
onClick={() => setConfirmDeleteId(null)}
className="px-3 py-1.5 text-sm rounded-md border border-border hover:bg-accent text-foreground"
>
Cancel
</button>
<button
onClick={() => {
onDeleteChart(confirmDeleteId);
setConfirmDeleteId(null);
setIsOpen(false);
}}
className="px-3 py-1.5 text-sm rounded-md bg-destructive text-destructive-foreground hover:bg-destructive/90"
>
Delete
</button>
</div>
</div>
</div>
)}
</div>
);
}