Compare commits

...
Sign in to create a new pull request.

1 commit

Author SHA1 Message Date
Scrum4Me Agent
02045dc102 feat(ST-v3leym34): sorteerbare kolomkoppen met SortHeader in ideeëntabel
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-06 06:36:25 +02:00

View file

@ -10,9 +10,10 @@
import { useMemo, useState, useTransition } from 'react' import { useMemo, useState, useTransition } from 'react'
import { useRouter } from 'next/navigation' import { useRouter } from 'next/navigation'
import { Plus } from 'lucide-react' import { Plus, ArrowUp, ArrowDown, ArrowUpDown } from 'lucide-react'
import { toast } from 'sonner' import { toast } from 'sonner'
import { cn } from '@/lib/utils'
import { Button } from '@/components/ui/button' import { Button } from '@/components/ui/button'
import { Input } from '@/components/ui/input' import { Input } from '@/components/ui/input'
import { Textarea } from '@/components/ui/textarea' import { Textarea } from '@/components/ui/textarea'
@ -50,6 +51,8 @@ interface ProductOption {
repo_url: string | null repo_url: string | null
} }
type SortKey = 'code' | 'title' | 'product' | 'status'
interface IdeaListProps { interface IdeaListProps {
ideas: IdeaDto[] ideas: IdeaDto[]
products: ProductOption[] products: ProductOption[]
@ -67,6 +70,38 @@ const STATUS_FILTERS: { value: IdeaStatusApi; label: string }[] = [
{ value: 'plan_failed', label: 'Plan mislukt' }, { 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) { export function IdeaList({ ideas, products, isDemo }: IdeaListProps) {
const router = useRouter() const router = useRouter()
const [isPending, startTransition] = useTransition() const [isPending, startTransition] = useTransition()
@ -76,6 +111,10 @@ export function IdeaList({ ideas, products, isDemo }: IdeaListProps) {
const [productFilter, setProductFilter] = useState<string>('all') const [productFilter, setProductFilter] = useState<string>('all')
const [statusFilter, setStatusFilter] = useState<Set<IdeaStatusApi>>(new Set()) 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 // Create-form state
const [showCreate, setShowCreate] = useState(false) const [showCreate, setShowCreate] = useState(false)
const [newTitle, setNewTitle] = useState('') const [newTitle, setNewTitle] = useState('')
@ -84,7 +123,7 @@ export function IdeaList({ ideas, products, isDemo }: IdeaListProps) {
const filtered = useMemo(() => { const filtered = useMemo(() => {
const q = search.trim().toLowerCase() const q = search.trim().toLowerCase()
return ideas.filter((idea) => { const result = ideas.filter((idea) => {
if (q && !idea.title.toLowerCase().includes(q)) return false if (q && !idea.title.toLowerCase().includes(q)) return false
if (productFilter !== 'all') { if (productFilter !== 'all') {
if (productFilter === 'none') { if (productFilter === 'none') {
@ -99,7 +138,25 @@ export function IdeaList({ ideas, products, isDemo }: IdeaListProps) {
if (statusFilter.size > 0 && !statusFilter.has(idea.status)) return false if (statusFilter.size > 0 && !statusFilter.has(idea.status)) return false
return true 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) { function toggleStatus(s: IdeaStatusApi) {
setStatusFilter((prev) => { setStatusFilter((prev) => {
@ -264,10 +321,10 @@ export function IdeaList({ ideas, products, isDemo }: IdeaListProps) {
<Table> <Table>
<TableHeader> <TableHeader>
<TableRow> <TableRow>
<TableHead className="w-24">Code</TableHead> <TableHead className="w-24"><SortHeader col="code" label="Code" sortKey={sortKey} sortDir={sortDir} onSort={handleSort} /></TableHead>
<TableHead>Titel</TableHead> <TableHead><SortHeader col="title" label="Titel" sortKey={sortKey} sortDir={sortDir} onSort={handleSort} /></TableHead>
<TableHead className="w-40">Product</TableHead> <TableHead className="w-40"><SortHeader col="product" label="Product" sortKey={sortKey} sortDir={sortDir} onSort={handleSort} /></TableHead>
<TableHead className="w-32">Status</TableHead> <TableHead className="w-32"><SortHeader col="status" label="Status" sortKey={sortKey} sortDir={sortDir} onSort={handleSort} /></TableHead>
<TableHead className="w-72">Acties</TableHead> <TableHead className="w-72">Acties</TableHead>
</TableRow> </TableRow>
</TableHeader> </TableHeader>