feat: Todo altijd gekoppeld aan product backlog
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
b541379964
commit
cb7eb36fbb
9 changed files with 128 additions and 46 deletions
|
|
@ -14,11 +14,20 @@ async function getSession() {
|
|||
export async function createTodoAction(_prevState: unknown, formData: FormData) {
|
||||
const session = await getSession()
|
||||
if (!session.userId) return { error: 'Niet ingelogd' }
|
||||
if (session.isDemo) return { error: 'Niet beschikbaar in demo-modus' }
|
||||
|
||||
const title = (formData.get('title') as string)?.trim()
|
||||
if (!title) return { error: 'Titel is verplicht' }
|
||||
const productId = (formData.get('productId') as string)?.trim()
|
||||
|
||||
await prisma.todo.create({ data: { user_id: session.userId, title } })
|
||||
if (!title) return { error: 'Titel is verplicht' }
|
||||
if (!productId) return { error: 'Product is verplicht' }
|
||||
|
||||
const product = await prisma.product.findFirst({
|
||||
where: { id: productId, user_id: session.userId, archived: false },
|
||||
})
|
||||
if (!product) return { error: 'Product niet gevonden' }
|
||||
|
||||
await prisma.todo.create({ data: { user_id: session.userId, product_id: productId, title } })
|
||||
revalidatePath('/todos')
|
||||
return { success: true }
|
||||
}
|
||||
|
|
|
|||
|
|
@ -10,6 +10,7 @@ export default async function TodosPage() {
|
|||
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({
|
||||
|
|
@ -24,7 +25,14 @@ export default async function TodosPage() {
|
|||
<div className="p-6 max-w-2xl mx-auto w-full">
|
||||
<h1 className="text-xl font-medium text-foreground mb-6">Todo's</h1>
|
||||
<TodoList
|
||||
todos={todos.map(t => ({ id: t.id, title: t.title, done: t.done, created_at: t.created_at.toISOString() }))}
|
||||
todos={todos.map(t => ({
|
||||
id: t.id,
|
||||
title: t.title,
|
||||
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,
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ import { z } from 'zod'
|
|||
|
||||
const bodySchema = z.object({
|
||||
title: z.string().min(1, 'Titel is verplicht').max(500),
|
||||
product_id: z.string().min(1, 'Product is verplicht'),
|
||||
})
|
||||
|
||||
export async function POST(request: Request) {
|
||||
|
|
@ -21,9 +22,17 @@ export async function POST(request: Request) {
|
|||
return Response.json({ error: parsed.error.flatten() }, { status: 400 })
|
||||
}
|
||||
|
||||
const product = await prisma.product.findFirst({
|
||||
where: { id: parsed.data.product_id, user_id: auth.userId, archived: false },
|
||||
})
|
||||
if (!product) {
|
||||
return Response.json({ error: 'Product niet gevonden' }, { status: 404 })
|
||||
}
|
||||
|
||||
const todo = await prisma.todo.create({
|
||||
data: {
|
||||
user_id: auth.userId,
|
||||
product_id: parsed.data.product_id,
|
||||
title: parsed.data.title,
|
||||
},
|
||||
})
|
||||
|
|
|
|||
|
|
@ -19,6 +19,8 @@ interface Todo {
|
|||
title: string
|
||||
done: boolean
|
||||
created_at: string
|
||||
product_id: string | null
|
||||
product_name: string | null
|
||||
}
|
||||
|
||||
interface Pbi {
|
||||
|
|
@ -38,7 +40,7 @@ interface TodoListProps {
|
|||
isDemo: boolean
|
||||
}
|
||||
|
||||
function QuickInput({ isDemo }: { isDemo: boolean }) {
|
||||
function QuickInput({ products, isDemo }: { products: Product[]; isDemo: boolean }) {
|
||||
const [, formAction] = useActionState(createTodoAction, undefined)
|
||||
const ref = useRef<HTMLFormElement>(null)
|
||||
|
||||
|
|
@ -49,15 +51,27 @@ function QuickInput({ isDemo }: { isDemo: boolean }) {
|
|||
onSubmit={() => setTimeout(() => ref.current?.reset(), 0)}
|
||||
className="flex gap-2 mb-6"
|
||||
>
|
||||
<select
|
||||
name="productId"
|
||||
required
|
||||
disabled={isDemo || products.length === 0}
|
||||
className="border border-border rounded-lg px-3 py-1.5 text-sm bg-input-background shrink-0"
|
||||
>
|
||||
{products.length === 0 ? (
|
||||
<option value="">Geen producten</option>
|
||||
) : (
|
||||
products.map(p => <option key={p.id} value={p.id}>{p.name}</option>)
|
||||
)}
|
||||
</select>
|
||||
<Input
|
||||
name="title"
|
||||
placeholder={isDemo ? 'Alleen-lezen in demo' : 'Nieuwe todo… (Enter om op te slaan)'}
|
||||
disabled={isDemo}
|
||||
disabled={isDemo || products.length === 0}
|
||||
className="flex-1"
|
||||
autoComplete="off"
|
||||
/>
|
||||
<DemoTooltip show={isDemo}>
|
||||
<QuickSubmitButton isDemo={isDemo} />
|
||||
<QuickSubmitButton isDemo={isDemo || products.length === 0} />
|
||||
</DemoTooltip>
|
||||
</form>
|
||||
)
|
||||
|
|
@ -79,7 +93,10 @@ function PromotePbiDialog({
|
|||
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])
|
||||
useEffect(() => {
|
||||
document.addEventListener('keydown', handleKey)
|
||||
return () => document.removeEventListener('keydown', handleKey)
|
||||
}, [handleKey])
|
||||
|
||||
const [state, formAction] = useActionState(
|
||||
async (_prev: unknown, fd: FormData) => {
|
||||
|
|
@ -106,7 +123,12 @@ function PromotePbiDialog({
|
|||
{products.length === 0 ? (
|
||||
<p className="text-sm text-muted-foreground">Maak eerst een product aan.</p>
|
||||
) : (
|
||||
<select name="productId" required className="w-full border border-border rounded-lg px-3 py-1.5 text-sm bg-input-background">
|
||||
<select
|
||||
name="productId"
|
||||
required
|
||||
defaultValue={todo.product_id ?? products[0]?.id}
|
||||
className="w-full border border-border rounded-lg px-3 py-1.5 text-sm bg-input-background"
|
||||
>
|
||||
{products.map(p => <option key={p.id} value={p.id}>{p.name}</option>)}
|
||||
</select>
|
||||
)}
|
||||
|
|
@ -115,7 +137,7 @@ function PromotePbiDialog({
|
|||
<label className="text-sm font-medium">Prioriteit</label>
|
||||
<select name="priority" required className="w-full border border-border rounded-lg px-3 py-1.5 text-sm bg-input-background">
|
||||
<option value="1">Kritiek</option>
|
||||
<option value="2" selected>Hoog</option>
|
||||
<option value="2">Hoog</option>
|
||||
<option value="3">Gemiddeld</option>
|
||||
<option value="4">Laag</option>
|
||||
</select>
|
||||
|
|
@ -138,9 +160,12 @@ function PromoteStoryDialog({
|
|||
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])
|
||||
useEffect(() => {
|
||||
document.addEventListener('keydown', handleKey)
|
||||
return () => document.removeEventListener('keydown', handleKey)
|
||||
}, [handleKey])
|
||||
|
||||
const [selectedProductId, setSelectedProductId] = useState(products[0]?.id ?? '')
|
||||
const [selectedProductId, setSelectedProductId] = useState(todo.product_id ?? products[0]?.id ?? '')
|
||||
const selectedProduct = products.find(p => p.id === selectedProductId)
|
||||
|
||||
const [state, formAction] = useActionState(
|
||||
|
|
@ -192,7 +217,7 @@ function PromoteStoryDialog({
|
|||
<label className="text-sm font-medium">Prioriteit</label>
|
||||
<select name="priority" required className="w-full border border-border rounded-lg px-3 py-1.5 text-sm bg-input-background">
|
||||
<option value="1">Kritiek</option>
|
||||
<option value="2" selected>Hoog</option>
|
||||
<option value="2">Hoog</option>
|
||||
<option value="3">Gemiddeld</option>
|
||||
<option value="4">Laag</option>
|
||||
</select>
|
||||
|
|
@ -233,13 +258,17 @@ export function TodoList({ todos, products, isDemo }: TodoListProps) {
|
|||
|
||||
return (
|
||||
<div className="space-y-4">
|
||||
<QuickInput isDemo={isDemo} />
|
||||
<QuickInput products={products} isDemo={isDemo} />
|
||||
|
||||
{todos.length === 0 ? (
|
||||
{products.length === 0 && (
|
||||
<p className="text-sm text-muted-foreground">Maak eerst een product aan om todo's toe te voegen.</p>
|
||||
)}
|
||||
|
||||
{todos.length === 0 && products.length > 0 ? (
|
||||
<div className="bg-surface-container-low border border-border rounded-xl p-12 text-center">
|
||||
<p className="text-muted-foreground text-sm">Geen todo's. Voeg er een toe hierboven.</p>
|
||||
</div>
|
||||
) : (
|
||||
) : todos.length > 0 ? (
|
||||
<>
|
||||
<div className="bg-surface-container-low border border-border rounded-xl divide-y divide-border">
|
||||
{open.map(todo => (
|
||||
|
|
@ -252,6 +281,11 @@ export function TodoList({ todos, products, isDemo }: TodoListProps) {
|
|||
className="w-4 h-4 rounded accent-primary cursor-pointer"
|
||||
/>
|
||||
<span className="flex-1 text-sm">{todo.title}</span>
|
||||
{todo.product_name && (
|
||||
<span className="text-xs text-muted-foreground bg-surface-container px-1.5 py-0.5 rounded shrink-0">
|
||||
{todo.product_name}
|
||||
</span>
|
||||
)}
|
||||
{!isDemo && (
|
||||
<div className="opacity-0 group-hover:opacity-100 flex gap-2 shrink-0">
|
||||
<button onClick={() => setPromotePbi(todo)} className="text-xs text-muted-foreground hover:text-foreground">→ PBI</button>
|
||||
|
|
@ -270,6 +304,11 @@ export function TodoList({ todos, products, isDemo }: TodoListProps) {
|
|||
className="w-4 h-4 rounded accent-primary cursor-pointer"
|
||||
/>
|
||||
<span className="flex-1 text-sm line-through text-muted-foreground">{todo.title}</span>
|
||||
{todo.product_name && (
|
||||
<span className="text-xs text-muted-foreground bg-surface-container px-1.5 py-0.5 rounded shrink-0">
|
||||
{todo.product_name}
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
|
|
@ -282,7 +321,7 @@ export function TodoList({ todos, products, isDemo }: TodoListProps) {
|
|||
</div>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
) : null}
|
||||
|
||||
{promotePbi && (
|
||||
<PromotePbiDialog todo={promotePbi} products={products} onClose={() => setPromotePbi(null)} />
|
||||
|
|
|
|||
|
|
@ -208,13 +208,14 @@ Scrum4Me is een desktop-first Next.js 15 webapplicatie die server-side wordt ger
|
|||
|---|---|---|---|
|
||||
| id | String (cuid) | PK | |
|
||||
| user_id | String | FK → users, not null | |
|
||||
| product_id | String | FK → products, nullable | Verplicht in UI en API; nullable voor backward compatibility |
|
||||
| title | String | not null | |
|
||||
| done | Boolean | default false | |
|
||||
| archived | Boolean | default false | |
|
||||
| created_at | DateTime | default now() | |
|
||||
| updated_at | DateTime | auto-update | |
|
||||
|
||||
**Indexes:** `(user_id, done, archived)` — standaard weergave filtert op actieve todo's
|
||||
**Indexes:** `(user_id, done, archived)` — standaard weergave filtert op actieve todo's; `(user_id, product_id)` — filteren per product
|
||||
|
||||
---
|
||||
|
||||
|
|
@ -405,6 +406,8 @@ model Todo {
|
|||
id String @id @default(cuid())
|
||||
user User @relation(fields: [user_id], references: [id], onDelete: Cascade)
|
||||
user_id String
|
||||
product Product? @relation(fields: [product_id], references: [id], onDelete: SetNull)
|
||||
product_id String?
|
||||
title String
|
||||
done Boolean @default(false)
|
||||
archived Boolean @default(false)
|
||||
|
|
@ -412,6 +415,7 @@ model Todo {
|
|||
updated_at DateTime @updatedAt
|
||||
|
||||
@@index([user_id, done, archived])
|
||||
@@index([user_id, product_id])
|
||||
}
|
||||
```
|
||||
|
||||
|
|
|
|||
|
|
@ -233,13 +233,13 @@ De MVP is klaar wanneer Lars — de primaire persona — de volledige cyclus kan
|
|||
- Route Handler; body: `{ type, content, status?, commit_hash?, commit_message? }`; Zod-validatie per type; schrijf naar `story_logs`; retourneer `{ id, created_at }`
|
||||
- Done when: drie typen werken (IMPLEMENTATION_PLAN, TEST_RESULT, COMMIT); log-entry zichtbaar in story-detail UI na aanmaken via API; ontbrekend verplicht veld geeft 400
|
||||
|
||||
- [ ] **ST-408** `PATCH /api/tasks/:id` — taakstatus bijwerken
|
||||
- Route Handler; body: `{ status: "TO_DO" | "IN_PROGRESS" | "DONE" }`; valideer dat taak aan requester's product behoort; update status; retourneer `{ id, status }`
|
||||
- Done when: status update via API zichtbaar in Sprint Planning UI; ongeldige status geeft 400; andere gebruikers taak geeft 403
|
||||
- [ ] **ST-408** `PATCH /api/tasks/:id` — taakstatus en implementatieplan bijwerken
|
||||
- Route Handler; body: `{ status?: "TO_DO" | "IN_PROGRESS" | "DONE", implementation_plan?: string }`; minimaal één veld verplicht; valideer dat taak aan requester's product behoort; retourneer `{ id, status, implementation_plan }`
|
||||
- Done when: status update via API zichtbaar in Sprint Planning UI; implementation_plan opgeslagen en opvraagbaar; lege body geeft 400; andere gebruikers taak geeft 403
|
||||
|
||||
- [ ] **ST-409** `POST /api/todos` — todo aanmaken
|
||||
- Route Handler; body: `{ title: string }`; schrijf naar `todos` voor de geverifieerde gebruiker; retourneer `{ id, title, created_at }`
|
||||
- Done when: todo aangemaakt via API verschijnt in todo-lijst UI; lege titel geeft 400
|
||||
- Route Handler; body: `{ title: string, product_id: string }`; valideer dat product bij de geverifieerde gebruiker hoort; schrijf naar `todos`; retourneer `{ id, title, created_at }`
|
||||
- Done when: todo aangemaakt via API met product_id verschijnt in todo-lijst UI gekoppeld aan het juiste product; lege titel of ontbrekend product_id geeft 400; onbekend product geeft 404
|
||||
|
||||
- [ ] **ST-410** Story-activiteitenlog UI
|
||||
- Activiteitenlog sectie in story-detail slide-over; haal `story_logs` op via Server Component; render chronologisch; visuele stijl per type (IMPLEMENTATION_PLAN = blauw, TEST_RESULT passed = groen, failed = rood, COMMIT = paars); commit-hash klikbaar als `repo_url` ingesteld; lege staat
|
||||
|
|
@ -250,8 +250,8 @@ De MVP is klaar wanneer Lars — de primaire persona — de volledige cyclus kan
|
|||
### M5: Todo-lijst
|
||||
|
||||
- [ ] **ST-501** Todo-lijst pagina
|
||||
- `/todos` pagina; haal actieve (niet-gearchiveerde) todos op; snel-invoerveld bovenaan (Enter om op te slaan); `createTodo` Server Action; lege staat met instructie
|
||||
- Done when: todo aanmaken via Enter persisteert en verschijnt in lijst; lege staat zichtbaar bij geen todos
|
||||
- `/todos` pagina; haal actieve (niet-gearchiveerde) todos op inclusief productnaam; snel-invoerveld bovenaan met product-dropdown (verplicht) en titel (Enter om op te slaan); `createTodo` Server Action; lege staat met instructie; productnaam-badge per todo-item
|
||||
- Done when: todo aanmaken via Enter persisteert en verschijnt in lijst met productnaam; aanmaken zonder product geblokkeerd; lege staat zichtbaar bij geen todos
|
||||
|
||||
- [ ] **ST-502** Todo afvinken
|
||||
- Checkbox per todo; `toggleTodo` Server Action; afgevinkte todos visueel doorgestreept; afgevinkte todos blijven zichtbaar onderaan de lijst
|
||||
|
|
|
|||
|
|
@ -220,16 +220,16 @@ Elke story heeft een activiteitenlog die alle door Claude Code vastgelegde stapp
|
|||
**Persona:** Lars (snelle vastlegging), Dina (losse klantnotities)
|
||||
|
||||
**Omschrijving:**
|
||||
Een snelle todo-lijst voor losse taken die (nog) niet aan een product of Sprint gekoppeld zijn. Todo-items kunnen worden afgevinkt en gepromoveerd naar een PBI of story in een bestaand product.
|
||||
Een snelle todo-lijst voor taken die aan een specifiek product zijn gekoppeld. Todo-items kunnen worden afgevinkt en gepromoveerd naar een PBI of story in dat product. Zowel de UI als de REST API vereisen een `product_id` bij aanmaken — zodat Claude Code altijd werkt binnen de context van de actieve product backlog.
|
||||
|
||||
**Acceptatiecriteria:**
|
||||
- [ ] Todo aanmaken via snel-invoerveld (Enter om op te slaan); alleen titel verplicht
|
||||
- [ ] Todo aanmaken ook mogelijk via REST API: `POST /api/todos` (body: `{ "title": string }`) — zodat Claude Code losse bevindingen kan vastleggen zonder de planningsflow te onderbreken
|
||||
- [ ] Todo aanmaken via snel-invoerveld (Enter om op te slaan); product (dropdown) en titel verplicht
|
||||
- [ ] Todo aanmaken ook mogelijk via REST API: `POST /api/todos` (body: `{ "title": string, "product_id": string }`) — zodat Claude Code bevindingen kan vastleggen binnen de actieve product backlog
|
||||
- [ ] Todo-lijst is zichtbaar als apart scherm of persistent zijpaneel
|
||||
- [ ] Todo afvinken markeert het als afgerond (visueel doorgestreept)
|
||||
- [ ] Afgevinkte todo's blijven zichtbaar; kunnen worden gearchiveerd via "Archiveer afgeronde items"
|
||||
- [ ] Todo promoveren naar PBI: dialoog vraagt om product en prioriteit; todo verdwijnt na promotie
|
||||
- [ ] Todo promoveren naar story: dialoog vraagt om product, PBI en prioriteit; todo verdwijnt na promotie
|
||||
- [ ] Todo promoveren naar PBI: dialoog pre-selecteert het gekoppelde product (bewerkbaar), vraagt prioriteit; todo verdwijnt na promotie
|
||||
- [ ] Todo promoveren naar story: dialoog pre-selecteert het gekoppelde product (bewerkbaar), vraagt PBI en prioriteit; todo verdwijnt na promotie
|
||||
- [ ] Titel van het todo-item is vooringevuld in de promotiedialoog (bewerkbaar)
|
||||
- [ ] Promotie is niet ongedaan te maken; dialoog waarschuwt hiervoor
|
||||
|
||||
|
|
@ -238,7 +238,7 @@ Een snelle todo-lijst voor losse taken die (nog) niet aan een product of Sprint
|
|||
- Promoveren naar story zonder PBI's in het product → dialoog toont melding "Maak eerst een PBI aan"
|
||||
|
||||
**Data:**
|
||||
- Opgeslagen: `todos` (id, user_id, title, done, archived, created_at, updated_at)
|
||||
- Opgeslagen: `todos` (id, user_id, product_id, title, done, archived, created_at, updated_at)
|
||||
|
||||
---
|
||||
|
||||
|
|
@ -316,8 +316,8 @@ Een REST API waarmee Claude Code stories en taken kan ophalen, de taakvolgorde k
|
|||
- [ ] `GET /api/sprints/:id/tasks?limit=10` — eerste N taken in huidige volgorde
|
||||
- [ ] `PATCH /api/stories/:id/tasks/reorder` — accepteert geordende lijst van taak-id's
|
||||
- [ ] `POST /api/stories/:id/log` — vastleggen van implementatieplan, testresultaat of commit
|
||||
- [ ] `PATCH /api/tasks/:id` — status bijwerken (TO_DO → IN_PROGRESS → DONE)
|
||||
- [ ] `POST /api/todos` — todo aanmaken vanuit Claude Code (body: `{ "title": string }`)
|
||||
- [ ] `PATCH /api/tasks/:id` — status bijwerken (TO_DO → IN_PROGRESS → DONE) en/of `implementation_plan` opslaan
|
||||
- [ ] `POST /api/todos` — todo aanmaken vanuit Claude Code (body: `{ "title": string, "product_id": string }`)
|
||||
|
||||
**Authenticatie:**
|
||||
- [ ] Alle endpoints vereisen `Authorization: Bearer <token>` header
|
||||
|
|
@ -404,8 +404,8 @@ De app is deployable op Vercel + Neon PostgreSQL en lokaal draaibaar met een Neo
|
|||
| `stories` | id, pbi_id, product_id, title, description, acceptance_criteria, priority, sort_order, status, sprint_id? | Status: OPEN / IN_SPRINT / DONE |
|
||||
| `story_logs` | id, story_id, type, content, status?, commit_hash?, commit_message?, created_at | Aangemaakt via API; read-only in UI |
|
||||
| `sprints` | id, product_id, sprint_goal, status (ACTIVE / COMPLETED), created_at, completed_at? | Max. 1 actieve Sprint per product |
|
||||
| `tasks` | id, story_id, sprint_id, title, description, priority, sort_order, status | Status: TO_DO / IN_PROGRESS / DONE |
|
||||
| `todos` | id, user_id, title, done, archived, created_at | Ontkoppeld van producthiërarchie |
|
||||
| `tasks` | id, story_id, sprint_id, title, description, implementation_plan?, priority, sort_order, status | Status: TO_DO / IN_PROGRESS / DONE; implementation_plan door MCP |
|
||||
| `todos` | id, user_id, product_id, title, done, archived, created_at | Gekoppeld aan product backlog; verplicht in UI en API |
|
||||
|
||||
---
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,8 @@
|
|||
-- AlterTable
|
||||
ALTER TABLE "todos" ADD COLUMN "product_id" TEXT;
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "todos_user_id_product_id_idx" ON "todos"("user_id", "product_id");
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "todos" ADD CONSTRAINT "todos_product_id_fkey" FOREIGN KEY ("product_id") REFERENCES "products"("id") ON DELETE SET NULL ON UPDATE CASCADE;
|
||||
|
|
@ -92,6 +92,7 @@ model Product {
|
|||
pbis Pbi[]
|
||||
sprints Sprint[]
|
||||
stories Story[]
|
||||
todos Todo[]
|
||||
|
||||
@@unique([user_id, name])
|
||||
@@index([user_id, archived])
|
||||
|
|
@ -170,18 +171,19 @@ model Sprint {
|
|||
}
|
||||
|
||||
model Task {
|
||||
id String @id @default(cuid())
|
||||
story Story @relation(fields: [story_id], references: [id], onDelete: Cascade)
|
||||
story_id String
|
||||
sprint Sprint? @relation(fields: [sprint_id], references: [id])
|
||||
sprint_id String?
|
||||
title String
|
||||
description String?
|
||||
priority Int
|
||||
sort_order Float
|
||||
status TaskStatus @default(TO_DO)
|
||||
created_at DateTime @default(now())
|
||||
updated_at DateTime @updatedAt
|
||||
id String @id @default(cuid())
|
||||
story Story @relation(fields: [story_id], references: [id], onDelete: Cascade)
|
||||
story_id String
|
||||
sprint Sprint? @relation(fields: [sprint_id], references: [id])
|
||||
sprint_id String?
|
||||
title String
|
||||
description String?
|
||||
implementation_plan String?
|
||||
priority Int
|
||||
sort_order Float
|
||||
status TaskStatus @default(TO_DO)
|
||||
created_at DateTime @default(now())
|
||||
updated_at DateTime @updatedAt
|
||||
|
||||
@@index([story_id, priority, sort_order])
|
||||
@@index([sprint_id, status])
|
||||
|
|
@ -192,6 +194,8 @@ model Todo {
|
|||
id String @id @default(cuid())
|
||||
user User @relation(fields: [user_id], references: [id], onDelete: Cascade)
|
||||
user_id String
|
||||
product Product? @relation(fields: [product_id], references: [id], onDelete: SetNull)
|
||||
product_id String?
|
||||
title String
|
||||
done Boolean @default(false)
|
||||
archived Boolean @default(false)
|
||||
|
|
@ -199,5 +203,6 @@ model Todo {
|
|||
updated_at DateTime @updatedAt
|
||||
|
||||
@@index([user_id, done, archived])
|
||||
@@index([user_id, product_id])
|
||||
@@map("todos")
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue