feat: create ChartSelector component with dropdown, delete, and empty state
This commit is contained in:
parent
90e1e179cc
commit
a3c7137b01
2 changed files with 123 additions and 3 deletions
|
|
@ -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
|
||||
|
||||
|
|
|
|||
120
src/components/ChartSelector.tsx
Normal file
120
src/components/ChartSelector.tsx
Normal 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 “{charts.find((c) => c.id === confirmDeleteId)?.name}” 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>
|
||||
);
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue