feat(ST-v3leym34): sorteerbare kolomkoppen met SortHeader in ideeëntabel
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
31dc429b61
commit
02045dc102
1 changed files with 64 additions and 7 deletions
|
|
@ -10,9 +10,10 @@
|
|||
|
||||
import { useMemo, useState, useTransition } from 'react'
|
||||
import { useRouter } from 'next/navigation'
|
||||
import { Plus } from 'lucide-react'
|
||||
import { Plus, ArrowUp, ArrowDown, ArrowUpDown } from 'lucide-react'
|
||||
import { toast } from 'sonner'
|
||||
|
||||
import { cn } from '@/lib/utils'
|
||||
import { Button } from '@/components/ui/button'
|
||||
import { Input } from '@/components/ui/input'
|
||||
import { Textarea } from '@/components/ui/textarea'
|
||||
|
|
@ -50,6 +51,8 @@ interface ProductOption {
|
|||
repo_url: string | null
|
||||
}
|
||||
|
||||
type SortKey = 'code' | 'title' | 'product' | 'status'
|
||||
|
||||
interface IdeaListProps {
|
||||
ideas: IdeaDto[]
|
||||
products: ProductOption[]
|
||||
|
|
@ -67,6 +70,38 @@ const STATUS_FILTERS: { value: IdeaStatusApi; label: string }[] = [
|
|||
{ value: 'plan_failed', label: 'Plan mislukt' },
|
||||
]
|
||||
|
||||
function SortHeader({
|
||||
col,
|
||||
label,
|
||||
sortKey,
|
||||
sortDir,
|
||||
onSort,
|
||||
}: {
|
||||
col: SortKey
|
||||
label: string
|
||||
sortKey: SortKey
|
||||
sortDir: 'asc' | 'desc'
|
||||
onSort: (col: SortKey) => void
|
||||
}) {
|
||||
const active = sortKey === col
|
||||
const Icon = active
|
||||
? sortDir === 'asc' ? ArrowUp : ArrowDown
|
||||
: ArrowUpDown
|
||||
return (
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => onSort(col)}
|
||||
className={cn(
|
||||
'flex items-center gap-1 text-xs font-medium hover:text-foreground transition-colors',
|
||||
active ? 'text-foreground' : 'text-muted-foreground'
|
||||
)}
|
||||
>
|
||||
{label}
|
||||
<Icon className="size-3" />
|
||||
</button>
|
||||
)
|
||||
}
|
||||
|
||||
export function IdeaList({ ideas, products, isDemo }: IdeaListProps) {
|
||||
const router = useRouter()
|
||||
const [isPending, startTransition] = useTransition()
|
||||
|
|
@ -76,6 +111,10 @@ export function IdeaList({ ideas, products, isDemo }: IdeaListProps) {
|
|||
const [productFilter, setProductFilter] = useState<string>('all')
|
||||
const [statusFilter, setStatusFilter] = useState<Set<IdeaStatusApi>>(new Set())
|
||||
|
||||
// Sort state
|
||||
const [sortKey, setSortKey] = useState<SortKey>('code')
|
||||
const [sortDir, setSortDir] = useState<'asc' | 'desc'>('asc')
|
||||
|
||||
// Create-form state
|
||||
const [showCreate, setShowCreate] = useState(false)
|
||||
const [newTitle, setNewTitle] = useState('')
|
||||
|
|
@ -84,7 +123,7 @@ export function IdeaList({ ideas, products, isDemo }: IdeaListProps) {
|
|||
|
||||
const filtered = useMemo(() => {
|
||||
const q = search.trim().toLowerCase()
|
||||
return ideas.filter((idea) => {
|
||||
const result = ideas.filter((idea) => {
|
||||
if (q && !idea.title.toLowerCase().includes(q)) return false
|
||||
if (productFilter !== 'all') {
|
||||
if (productFilter === 'none') {
|
||||
|
|
@ -99,7 +138,25 @@ export function IdeaList({ ideas, products, isDemo }: IdeaListProps) {
|
|||
if (statusFilter.size > 0 && !statusFilter.has(idea.status)) return false
|
||||
return true
|
||||
})
|
||||
}, [ideas, search, productFilter, statusFilter])
|
||||
const dir = sortDir === 'asc' ? 1 : -1
|
||||
return [...result].sort((a, b) => {
|
||||
switch (sortKey) {
|
||||
case 'code': return dir * a.code.localeCompare(b.code)
|
||||
case 'title': return dir * a.title.localeCompare(b.title)
|
||||
case 'product': return dir * (a.product?.name ?? '').localeCompare(b.product?.name ?? '')
|
||||
case 'status': return dir * a.status.localeCompare(b.status)
|
||||
}
|
||||
})
|
||||
}, [ideas, search, productFilter, statusFilter, sortKey, sortDir])
|
||||
|
||||
function handleSort(col: SortKey) {
|
||||
if (sortKey === col) {
|
||||
setSortDir((d) => (d === 'asc' ? 'desc' : 'asc'))
|
||||
} else {
|
||||
setSortKey(col)
|
||||
setSortDir('asc')
|
||||
}
|
||||
}
|
||||
|
||||
function toggleStatus(s: IdeaStatusApi) {
|
||||
setStatusFilter((prev) => {
|
||||
|
|
@ -264,10 +321,10 @@ export function IdeaList({ ideas, products, isDemo }: IdeaListProps) {
|
|||
<Table>
|
||||
<TableHeader>
|
||||
<TableRow>
|
||||
<TableHead className="w-24">Code</TableHead>
|
||||
<TableHead>Titel</TableHead>
|
||||
<TableHead className="w-40">Product</TableHead>
|
||||
<TableHead className="w-32">Status</TableHead>
|
||||
<TableHead className="w-24"><SortHeader col="code" label="Code" sortKey={sortKey} sortDir={sortDir} onSort={handleSort} /></TableHead>
|
||||
<TableHead><SortHeader col="title" label="Titel" sortKey={sortKey} sortDir={sortDir} onSort={handleSort} /></TableHead>
|
||||
<TableHead className="w-40"><SortHeader col="product" label="Product" sortKey={sortKey} sortDir={sortDir} onSort={handleSort} /></TableHead>
|
||||
<TableHead className="w-32"><SortHeader col="status" label="Status" sortKey={sortKey} sortDir={sortDir} onSort={handleSort} /></TableHead>
|
||||
<TableHead className="w-72">Acties</TableHead>
|
||||
</TableRow>
|
||||
</TableHeader>
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue