From 180513b2e589fb63efb330dec5b5f9c02e87eddd Mon Sep 17 00:00:00 2001
From: Scrum4Me Agent <30029041+madhura68@users.noreply.github.com>
Date: Wed, 6 May 2026 11:46:16 +0200
Subject: [PATCH] feat(cleanup): verwijder app/(app)/todos/ en
components/todos/ [cmottjvzo000cx3172472cu4g]
---
app/(app)/todos/loading.tsx | 19 -
app/(app)/todos/page.tsx | 47 ---
components/todos/todo-list.tsx | 687 ---------------------------------
3 files changed, 753 deletions(-)
delete mode 100644 app/(app)/todos/loading.tsx
delete mode 100644 app/(app)/todos/page.tsx
delete mode 100644 components/todos/todo-list.tsx
diff --git a/app/(app)/todos/loading.tsx b/app/(app)/todos/loading.tsx
deleted file mode 100644
index 61d4ec9..0000000
--- a/app/(app)/todos/loading.tsx
+++ /dev/null
@@ -1,19 +0,0 @@
-export default function Loading() {
- return (
-
-
-
-
-
- {[1, 2, 3, 4, 5].map(i => (
-
- ))}
-
-
-
- )
-}
diff --git a/app/(app)/todos/page.tsx b/app/(app)/todos/page.tsx
deleted file mode 100644
index 27f07f4..0000000
--- a/app/(app)/todos/page.tsx
+++ /dev/null
@@ -1,47 +0,0 @@
-import { cookies } from 'next/headers'
-import { getIronSession } from 'iron-session'
-import { SessionData, sessionOptions } from '@/lib/session'
-import { prisma } from '@/lib/prisma'
-import { productAccessFilter } from '@/lib/product-access'
-import { TodoList } from '@/components/todos/todo-list'
-
-export default async function TodosPage() {
- const session = await getIronSession(await cookies(), sessionOptions)
-
- const todos = await prisma.todo.findMany({
- where: { user_id: session.userId, archived: false },
- orderBy: { created_at: 'asc' },
- include: { product: { select: { name: true } } },
- })
-
- const products = await prisma.product.findMany({
- where: { ...productAccessFilter(session.userId), archived: false },
- orderBy: { name: 'asc' },
- include: {
- pbis: { orderBy: [{ priority: 'asc' }, { sort_order: 'asc' }], select: { id: true, title: true } },
- },
- })
-
- return (
-
-
Todo's
- ({
- id: t.id,
- title: t.title,
- description: t.description ?? null,
- done: t.done,
- created_at: t.created_at.toISOString(),
- product_id: t.product_id ?? null,
- product_name: t.product?.name ?? null,
- }))}
- products={products.map(p => ({
- id: p.id,
- name: p.name,
- pbis: p.pbis,
- }))}
- isDemo={session.isDemo ?? false}
- />
-
- )
-}
diff --git a/components/todos/todo-list.tsx b/components/todos/todo-list.tsx
deleted file mode 100644
index 5db21b3..0000000
--- a/components/todos/todo-list.tsx
+++ /dev/null
@@ -1,687 +0,0 @@
-'use client'
-
-import { useState, useTransition, useMemo, useEffect, useRef, useCallback } from 'react'
-import { useActionState } from 'react'
-import { useFormStatus } from 'react-dom'
-import { useRouter } from 'next/navigation'
-import {
- useReactTable,
- getCoreRowModel,
- getPaginationRowModel,
- flexRender,
- type ColumnDef,
- type RowSelectionState,
- type PaginationState,
-} from '@tanstack/react-table'
-import { toast } from 'sonner'
-import { cn } from '@/lib/utils'
-import { Button } from '@/components/ui/button'
-import { Badge } from '@/components/ui/badge'
-import { Input } from '@/components/ui/input'
-import { Textarea } from '@/components/ui/textarea'
-import { DemoTooltip } from '@/components/shared/demo-tooltip'
-import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from '@/components/ui/table'
-import {
- createTodoAction,
- updateTodoAction,
- archiveSelectedTodosAction,
- promoteTodoToPbiAction,
- promoteTodoToStoryAction,
- promoteTodoToIdeaAction,
-} from '@/actions/todos'
-
-interface Todo {
- id: string
- title: string
- description: string | null
- done: boolean
- created_at: string
- product_id: string | null
- product_name: string | null
-}
-
-interface Pbi {
- id: string
- title: string
-}
-
-interface Product {
- id: string
- name: string
- pbis: Pbi[]
-}
-
-interface TodoListProps {
- todos: Todo[]
- products: Product[]
- isDemo: boolean
-}
-
-// Checkbox with indeterminate support for TanStack row selection
-function IndeterminateCheckbox({
- indeterminate,
- className,
- ...props
-}: React.InputHTMLAttributes & { indeterminate?: boolean }) {
- const ref = useRef(null)
- useEffect(() => {
- if (ref.current) ref.current.indeterminate = indeterminate ?? false
- }, [indeterminate])
- return (
-
- )
-}
-
-function SaveButton() {
- const { pending } = useFormStatus()
- return (
-
- )
-}
-
-// --- Promote to PBI dialog ---
-function PromotePbiDialog({
- todo,
- products,
- onClose,
-}: { todo: Todo; products: Product[]; onClose: () => void }) {
- const handleKey = useCallback((e: KeyboardEvent) => { if (e.key === 'Escape') onClose() }, [onClose])
- useEffect(() => {
- document.addEventListener('keydown', handleKey)
- return () => document.removeEventListener('keydown', handleKey)
- }, [handleKey])
-
- const [state, formAction] = useActionState(
- async (_prev: unknown, fd: FormData) => {
- const result = await promoteTodoToPbiAction(_prev, fd)
- if (result?.success) { toast.success('Todo gepromoveerd naar PBI'); onClose() }
- return result
- },
- undefined
- )
-
- return (
-
-
-
Promoveer naar PBI
-
Let op: dit kan niet ongedaan worden gemaakt.
-
-
-
- )
-}
-
-// --- Promote to Story dialog ---
-function PromoteStoryDialog({
- todo,
- products,
- onClose,
-}: { todo: Todo; products: Product[]; onClose: () => void }) {
- const handleKey = useCallback((e: KeyboardEvent) => { if (e.key === 'Escape') onClose() }, [onClose])
- useEffect(() => {
- document.addEventListener('keydown', handleKey)
- return () => document.removeEventListener('keydown', handleKey)
- }, [handleKey])
-
- const [selectedProductId, setSelectedProductId] = useState(todo.product_id ?? products[0]?.id ?? '')
- const selectedProduct = products.find(p => p.id === selectedProductId)
-
- const [state, formAction] = useActionState(
- async (_prev: unknown, fd: FormData) => {
- const result = await promoteTodoToStoryAction(_prev, fd)
- if (result?.success) { toast.success('Todo gepromoveerd naar Story'); onClose() }
- return result
- },
- undefined
- )
-
- return (
-
-
-
Promoveer naar Story
-
Let op: dit kan niet ongedaan worden gemaakt.
-
-
-
- )
-}
-
-// --- Promote to Idea dialog (M12 T-514) ---
-// Geen extra inputs nodig — title/description komen uit de todo, en
-// promoteTodoToIdeaAction archiveert de todo automatisch.
-function PromoteIdeaDialog({
- todo,
- onClose,
-}: { todo: Todo; onClose: () => void }) {
- const router = useRouter()
- const handleKey = useCallback((e: KeyboardEvent) => { if (e.key === 'Escape') onClose() }, [onClose])
- useEffect(() => {
- document.addEventListener('keydown', handleKey)
- return () => document.removeEventListener('keydown', handleKey)
- }, [handleKey])
-
- const [pending, startTransition] = useTransition()
-
- function handleConfirm() {
- startTransition(async () => {
- const r = await promoteTodoToIdeaAction(todo.id)
- if ('error' in r) {
- toast.error(r.error)
- return
- }
- toast.success(`Idee aangemaakt (${r.idea_code})`)
- onClose()
- router.push(`/ideas/${r.idea_id}`)
- })
- }
-
- return (
-
-
-
Promoveer naar Idee
-
- Maak een nieuw idee van ‘{todo.title}’. De Todo wordt
- gearchiveerd; je kunt hem later terugvinden in de archief-filter.
-
-
- Het idee start als DRAFT. Je kunt het daarna grillen, plannen, en
- materialiseren tot een PBI.
-
-
-
-
-
-
-
- )
-}
-
-// --- Detail card ---
-function TodoCard({
- mode,
- activeTodo,
- products,
- isDemo,
- defaultProductId,
- onSuccess,
- onPromotePbi,
- onPromoteStory,
- onPromoteIdea,
-}: {
- mode: 'idle' | 'create' | 'edit'
- activeTodo: Todo | null
- products: Product[]
- isDemo: boolean
- defaultProductId: string
- onSuccess: () => void
- onPromotePbi: (todo: Todo) => void
- onPromoteStory: (todo: Todo) => void
- onPromoteIdea: (todo: Todo) => void
-}) {
- const [createState, createFormAction] = useActionState(createTodoAction, undefined)
- const [editState, editFormAction] = useActionState(updateTodoAction, undefined)
-
- useEffect(() => {
- if (createState && 'success' in createState && createState.success) onSuccess()
- }, [createState, onSuccess])
-
- useEffect(() => {
- if (editState && 'success' in editState && editState.success) onSuccess()
- }, [editState, onSuccess])
-
- if (mode === 'idle') {
- return (
-
-
Selecteer een rij of klik op + om te beginnen.
-
- )
- }
-
- if (mode === 'create') {
- return (
-
- )
- }
-
- // Edit mode
- if (!activeTodo) return null
-
- return (
-
-
Todo bewerken
-
-
- )
-}
-
-// --- Main component ---
-export function TodoList({ todos, products, isDemo }: TodoListProps) {
- const [isPending, startTransition] = useTransition()
- const [selectedProductId, setSelectedProductId] = useState('all')
- const [rowSelection, setRowSelection] = useState({})
- const [pagination, setPagination] = useState({ pageIndex: 0, pageSize: 10 })
- const [activeRowId, setActiveRowId] = useState(null)
- const [mode, setMode] = useState<'idle' | 'create'>('idle')
- const [promotePbi, setPromotePbi] = useState(null)
- const [promoteStory, setPromoteStory] = useState(null)
- const [promoteIdea, setPromoteIdea] = useState(null)
-
- const filtered = useMemo(() => {
- if (selectedProductId === 'all') return todos
- if (selectedProductId === '') return todos.filter(t => t.product_id === null)
- return todos.filter(t => t.product_id === selectedProductId)
- }, [todos, selectedProductId])
-
- useEffect(() => {
- setPagination(p => ({ ...p, pageIndex: 0 }))
- setRowSelection({})
- }, [selectedProductId])
-
- const columns = useMemo[]>(() => [
- {
- id: 'select',
- header: ({ table }) => (
- table.toggleAllPageRowsSelected(e.target.checked)}
- />
- ),
- cell: ({ row }) => (
- row.toggleSelected(e.target.checked)}
- onClick={e => e.stopPropagation()}
- />
- ),
- },
- {
- accessorKey: 'title',
- header: 'Titel',
- cell: ({ row }) => (
-
- {row.original.title}
-
- ),
- },
- {
- accessorKey: 'product_name',
- header: 'Product',
- cell: ({ row }) => row.original.product_name ? (
- {row.original.product_name}
- ) : (
- —
- ),
- },
- {
- accessorKey: 'created_at',
- header: 'Datum',
- cell: ({ row }) => (
-
- {new Date(row.original.created_at).toLocaleDateString('nl-NL')}
-
- ),
- },
- ], [])
-
- const table = useReactTable({
- data: filtered,
- columns,
- state: { rowSelection, pagination },
- enableRowSelection: true,
- onRowSelectionChange: setRowSelection,
- onPaginationChange: setPagination,
- getCoreRowModel: getCoreRowModel(),
- getPaginationRowModel: getPaginationRowModel(),
- })
-
- const selectedRows = table.getSelectedRowModel().rows
- const selectedCount = selectedRows.length
- const { pageIndex, pageSize } = table.getState().pagination
- const totalRows = filtered.length
- const start = totalRows === 0 ? 0 : pageIndex * pageSize + 1
- const end = Math.min((pageIndex + 1) * pageSize, totalRows)
-
- const activeTodo = todos.find(t => t.id === activeRowId) ?? null
- const cardMode = mode === 'create' ? 'create' : activeTodo ? 'edit' : 'idle'
- const defaultProductId = selectedProductId !== 'all' ? selectedProductId : ''
-
- const handleCancel = useCallback(() => {
- setActiveRowId(null)
- setMode('idle')
- }, [])
-
- function handleRowClick(todo: Todo) {
- setActiveRowId(prev => prev === todo.id ? null : todo.id)
- setMode('idle')
- }
-
- function handleNew() {
- setActiveRowId(null)
- setRowSelection({})
- setMode('create')
- }
-
- function handleBulkArchive() {
- const ids = selectedRows.map(r => r.original.id)
- startTransition(async () => {
- const result = await archiveSelectedTodosAction(ids)
- if (result?.error) {
- toast.error(typeof result.error === 'string' ? result.error : 'Archiveren mislukt')
- } else {
- const n = ids.length
- toast.success(`${n} todo${n === 1 ? '' : "'s"} gearchiveerd`)
- setRowSelection({})
- setActiveRowId(null)
- setMode('idle')
- }
- })
- }
-
- return (
-
- {/* Toolbar */}
-
-
-
-
-
- {selectedCount > 0 && !isDemo && (
-
- )}
-
-
-
-
-
-
- {/* Table */}
-
-
-
- {table.getHeaderGroups().map(hg => (
-
- {hg.headers.map(header => (
-
- {header.isPlaceholder ? null : flexRender(header.column.columnDef.header, header.getContext())}
-
- ))}
-
- ))}
-
-
- {table.getRowModel().rows.length > 0 ? (
- table.getRowModel().rows.map(row => (
- handleRowClick(row.original)}
- >
- {row.getVisibleCells().map(cell => (
-
- {flexRender(cell.column.columnDef.cell, cell.getContext())}
-
- ))}
-
- ))
- ) : (
-
-
- {todos.length === 0
- ? "Nog geen todo's. Gebruik + om er een aan te maken."
- : "Geen todo's voor deze selectie."}
-
-
- )}
-
-
-
-
- {/* Pagination */}
- {totalRows > pageSize && (
-
-
{start}–{end} van {totalRows}
-
-
-
-
-
- )}
-
- {/* Detail card */}
-
-
- {promotePbi && (
-
setPromotePbi(null)} />
- )}
- {promoteStory && (
- setPromoteStory(null)} />
- )}
- {promoteIdea && (
- setPromoteIdea(null)} />
- )}
-
- )
-}