Initial commit: personal task planner with categories, projects, and tiered assignment
Personal-first lists/tasks/reminders/recurrence, plus an admin-editable status pipeline, shared categories, project containers, and a three-tier permission model for cross-user work: assign (hand off your own tasks), reallocate (dispatcher — move anyone's tasks), admin (reserved, full manage-all).
This commit is contained in:
commit
d8dc4e2b81
54 changed files with 10908 additions and 0 deletions
82
frontend/src/api.ts
Normal file
82
frontend/src/api.ts
Normal file
|
|
@ -0,0 +1,82 @@
|
|||
import type { List, Task, TaskDetail, TaskItem, TaskComment, AppConfig } from './types'
|
||||
|
||||
const BASE = '/tasks/api'
|
||||
|
||||
async function request<T>(path: string, opts: RequestInit = {}): Promise<T> {
|
||||
const res = await fetch(`${BASE}${path}`, {
|
||||
credentials: 'include',
|
||||
headers: { 'Content-Type': 'application/json', ...opts.headers },
|
||||
...opts,
|
||||
})
|
||||
if (res.status === 401) {
|
||||
;(window.top ?? window).location.href = '/login'
|
||||
throw new Error('Unauthenticated')
|
||||
}
|
||||
if (!res.ok) {
|
||||
const err = await res.json().catch(() => ({ error: res.statusText }))
|
||||
throw new Error(err.error || `Request failed: ${res.status}`)
|
||||
}
|
||||
if (res.status === 204) return undefined as T
|
||||
return res.json()
|
||||
}
|
||||
|
||||
// ── Lists ────────────────────────────────────────────────────
|
||||
export function fetchLists(includeArchived = false): Promise<List[]> {
|
||||
return request(`/lists${includeArchived ? '?archived=1' : ''}`)
|
||||
}
|
||||
export function createList(body: { name: string; colour?: string; icon?: string }): Promise<List> {
|
||||
return request('/lists', { method: 'POST', body: JSON.stringify(body) })
|
||||
}
|
||||
export function updateList(id: number, body: Partial<List>): Promise<List> {
|
||||
return request(`/lists/${id}`, { method: 'PATCH', body: JSON.stringify(body) })
|
||||
}
|
||||
export function deleteList(id: number): Promise<{ ok: boolean }> {
|
||||
return request(`/lists/${id}`, { method: 'DELETE' })
|
||||
}
|
||||
|
||||
// ── Tasks ────────────────────────────────────────────────────
|
||||
export function fetchTasks(params: { list_id?: number; view?: 'today'; status?: string } = {}): Promise<Task[]> {
|
||||
const qs = new URLSearchParams()
|
||||
for (const [k, v] of Object.entries(params)) if (v !== undefined) qs.set(k, String(v))
|
||||
const s = qs.toString()
|
||||
return request(`/tasks${s ? `?${s}` : ''}`)
|
||||
}
|
||||
export function fetchTask(id: number): Promise<TaskDetail> {
|
||||
return request(`/tasks/${id}`)
|
||||
}
|
||||
export function createTask(body: Record<string, unknown>): Promise<Task> {
|
||||
return request('/tasks', { method: 'POST', body: JSON.stringify(body) })
|
||||
}
|
||||
export function updateTask(id: number, body: Record<string, unknown>): Promise<Task> {
|
||||
return request(`/tasks/${id}`, { method: 'PATCH', body: JSON.stringify(body) })
|
||||
}
|
||||
export function deleteTask(id: number): Promise<{ ok: boolean }> {
|
||||
return request(`/tasks/${id}`, { method: 'DELETE' })
|
||||
}
|
||||
export function reorderTasks(ids: number[]): Promise<{ ok: boolean }> {
|
||||
return request('/tasks/reorder', { method: 'POST', body: JSON.stringify({ ids }) })
|
||||
}
|
||||
|
||||
// ── Checklist items ──────────────────────────────────────────
|
||||
export function addItem(taskId: number, label: string): Promise<TaskItem> {
|
||||
return request(`/tasks/${taskId}/items`, { method: 'POST', body: JSON.stringify({ label }) })
|
||||
}
|
||||
export function updateItem(itemId: number, body: Partial<TaskItem>): Promise<TaskItem> {
|
||||
return request(`/tasks/items/${itemId}`, { method: 'PATCH', body: JSON.stringify(body) })
|
||||
}
|
||||
export function deleteItem(itemId: number): Promise<{ ok: boolean }> {
|
||||
return request(`/tasks/items/${itemId}`, { method: 'DELETE' })
|
||||
}
|
||||
|
||||
// ── Comments ─────────────────────────────────────────────────
|
||||
export function addComment(taskId: number, body: string): Promise<TaskComment> {
|
||||
return request(`/tasks/${taskId}/comments`, { method: 'POST', body: JSON.stringify({ body }) })
|
||||
}
|
||||
|
||||
// ── Config ───────────────────────────────────────────────────
|
||||
export function fetchConfig(): Promise<AppConfig> {
|
||||
return request('/config')
|
||||
}
|
||||
export function updateConfig(key: string, value: unknown): Promise<{ ok: boolean }> {
|
||||
return request(`/config/${key}`, { method: 'PUT', body: JSON.stringify({ value }) })
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue