Add per-project card statuses and a kanban board
Statuses
- Migration 006: card_statuses table (project-scoped) and cards.status_id, a
nullable FK with ON DELETE SET NULL. Every new project is seeded with
"To do" / "Doing" / "Done"; GET /api/projects/{id}/statuses lists them.
- New cards have no status -- they sit in an "inbox" until moved.
Project view
- Full-width and tabbed: "All tasks" (a flat list, sorted by name
case-insensitively) and "Kanban" (Inbox plus one column per status).
- Drag a card within or between columns to reorder / restatus; the Inbox
column has its own name + Add form.
Ordering
- Migration 007: `position` is now a dense 0..n-1 rank within a
(project_id, status_id) column, not a project-wide order. New composite
index idx_cards_project_status_position; existing rows re-ranked.
- PUT /api/projects/{id}/cards/order takes { status_id, card_ids } and sets one
column's contents and order, re-parenting moved-in cards and re-packing their
source column in a single transaction. PATCH status_id appends the card to the
end of the destination column.
58 phpunit tests pass; the frontend type-checks and builds.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
@@ -4,7 +4,6 @@ import type { Card } from '../types'
|
||||
|
||||
const props = defineProps<{ card: Card }>()
|
||||
const emit = defineEmits<{
|
||||
toggle: [complete: boolean]
|
||||
'save-text': [text: string]
|
||||
delete: []
|
||||
}>()
|
||||
@@ -28,17 +27,7 @@ function commit() {
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<li class="card-row" :class="{ 'card-row--done': card.complete }">
|
||||
<span class="card-row__handle" aria-hidden="true" title="Drag to reorder">⠿</span>
|
||||
|
||||
<input
|
||||
class="card-row__check"
|
||||
type="checkbox"
|
||||
:checked="card.complete"
|
||||
:aria-label="card.complete ? 'Mark as not done' : 'Mark as done'"
|
||||
@change="emit('toggle', ($event.target as HTMLInputElement).checked)"
|
||||
/>
|
||||
|
||||
<li class="card-row">
|
||||
<input
|
||||
v-model="text"
|
||||
class="card-row__text"
|
||||
@@ -49,6 +38,10 @@ function commit() {
|
||||
@keyup.enter="($event.target as HTMLInputElement).blur()"
|
||||
/>
|
||||
|
||||
<span class="card-row__status" :class="{ 'card-row__status--none': !card.status }">
|
||||
{{ card.status?.name ?? 'No status' }}
|
||||
</span>
|
||||
|
||||
<button type="button" class="card-row__delete" aria-label="Delete card" @click="emit('delete')">
|
||||
✕
|
||||
</button>
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
<script setup lang="ts">
|
||||
import type { Card } from '../types'
|
||||
|
||||
defineProps<{ card: Card }>()
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="kanban-card">{{ card.text }}</div>
|
||||
</template>
|
||||
Reference in New Issue
Block a user