Compare commits
1 commit
main
...
feat/story
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
02045dc102 |
1 changed files with 64 additions and 7 deletions
|
|
@ -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>
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue