Clicking a card -- in the "all tasks" list, a kanban column, or the
inbox -- now opens /cards/:id instead of editing the text in place:
- CardRow's text is now plain (its RouterLink wraps the text + status
badge; the delete button stays a sibling so it isn't nested inside
the link). KanbanCard is now itself a RouterLink.
- New CardView.vue: header follows the project view's layout, but the
back link sits inline inside the title (before the card's text)
rather than off in the actions corner, since it isn't paired with a
manage menu here. Inbox cards have no project to link back to, so
they go to the dashboard instead. Below the header, an "Edit text"
section (styled like the project rename form) replaces the inline
editing that used to live in the list row, and a delete button in
the header actions replaces CardRow's per-row delete for cards
reached via kanban/inbox (which never had one).
- No backend changes: GET/PATCH/DELETE /cards/{id} already existed.
- Renamed .card-row__status to the more general .status-badge, now
shared by the list row and the card view.
- cards store: dropped setText, now unused now that editing goes
through a direct PATCH + store refresh in CardView instead.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
81 lines
2.3 KiB
TypeScript
81 lines
2.3 KiB
TypeScript
import { defineStore } from 'pinia'
|
|
import { ref } from 'vue'
|
|
import { apiRequest } from '../lib/api'
|
|
import type { Card } from '../types'
|
|
|
|
type CardPatch = Partial<Pick<Card, 'text' | 'complete'>>
|
|
|
|
export const useCardsStore = defineStore('cards', () => {
|
|
// One project's cards, grouped by status then position. Views re-sort as
|
|
// needed (the "all tasks" list is alphabetical). Moving a card in or out of
|
|
// this project (including via the inbox) goes through lib/cardOrder.ts,
|
|
// not this store -- callers re-load() afterwards.
|
|
const cards = ref<Card[]>([])
|
|
const projectId = ref<number | null>(null)
|
|
const loading = ref(false)
|
|
const loaded = ref(false)
|
|
|
|
const completedCount = () => cards.value.filter((c) => c.complete).length
|
|
|
|
async function load(id: number): Promise<void> {
|
|
projectId.value = id
|
|
loaded.value = false
|
|
loading.value = true
|
|
try {
|
|
const { cards: fetched } = await apiRequest<{ cards: Card[] }>(`/projects/${id}/cards`, {
|
|
auth: true,
|
|
})
|
|
cards.value = fetched
|
|
loaded.value = true
|
|
} finally {
|
|
loading.value = false
|
|
}
|
|
}
|
|
|
|
async function add(text: string): Promise<void> {
|
|
const { card } = await apiRequest<{ card: Card }>(`/projects/${projectId.value}/cards`, {
|
|
method: 'POST',
|
|
auth: true,
|
|
body: { text },
|
|
})
|
|
// The API appends the card, so the end of the array is its correct place.
|
|
cards.value.push(card)
|
|
}
|
|
|
|
async function patch(card: Card, fields: CardPatch): Promise<void> {
|
|
const { card: updated } = await apiRequest<{ card: Card }>(`/cards/${card.id}`, {
|
|
method: 'PATCH',
|
|
auth: true,
|
|
body: fields,
|
|
})
|
|
const i = cards.value.findIndex((x) => x.id === updated.id)
|
|
if (i !== -1) cards.value[i] = updated
|
|
}
|
|
|
|
const setComplete = (card: Card, complete: boolean) => patch(card, { complete })
|
|
|
|
async function remove(card: Card): Promise<void> {
|
|
await apiRequest(`/cards/${card.id}`, { method: 'DELETE', auth: true })
|
|
cards.value = cards.value.filter((c) => c.id !== card.id)
|
|
}
|
|
|
|
function reset(): void {
|
|
cards.value = []
|
|
projectId.value = null
|
|
loaded.value = false
|
|
}
|
|
|
|
return {
|
|
cards,
|
|
projectId,
|
|
loading,
|
|
loaded,
|
|
completedCount,
|
|
load,
|
|
add,
|
|
setComplete,
|
|
remove,
|
|
reset,
|
|
}
|
|
})
|