Typo'd addresses in the old <input type="email"> fields silently matched nobody (assigned_to/task_tags.user_email have no FK), so a task could vanish into an unreachable owner with no error. New UsersContext + UserPicker pull the real user list from central auth (/api/auth/users?app=tasks), same pattern maintenance already uses for its assignee dropdown.
153 lines
3.3 KiB
TypeScript
153 lines
3.3 KiB
TypeScript
export type Priority = 'low' | 'medium' | 'high'
|
|
// Status is now an admin-editable pipeline (see AppStatus below), not a fixed
|
|
// enum — the string is whatever `statuses.value` the admin has configured.
|
|
export type Status = string
|
|
export type RecurRule = 'daily' | 'weekdays' | 'weekly' | 'monthly'
|
|
|
|
export const PRIORITIES: Priority[] = ['low', 'medium', 'high']
|
|
export const PRIORITY_LABELS: Record<Priority, string> = { low: 'Low', medium: 'Medium', high: 'High' }
|
|
|
|
export const RECUR_RULES: RecurRule[] = ['daily', 'weekdays', 'weekly', 'monthly']
|
|
export const RECUR_LABELS: Record<RecurRule, string> = {
|
|
daily: 'Every day',
|
|
weekdays: 'Every weekday',
|
|
weekly: 'Every week',
|
|
monthly: 'Every month',
|
|
}
|
|
|
|
// Preset list colours (user data, not design-system chrome).
|
|
export const LIST_COLOURS = ['#c9a84c', '#0d9488', '#2563eb', '#7c3aed', '#dc2626', '#d97706', '#16a34a', '#64748b']
|
|
|
|
export interface List {
|
|
id: number
|
|
owner_id: string
|
|
name: string
|
|
colour: string
|
|
icon: string
|
|
sort_order: number
|
|
is_archived: boolean
|
|
open_count: number
|
|
total_count: number
|
|
created_at: string
|
|
}
|
|
|
|
export interface Task {
|
|
id: number
|
|
list_id: number | null
|
|
owner_id: string
|
|
created_by: string
|
|
assigned_to: string | null
|
|
category_id: number | null
|
|
project_id: number | null
|
|
title: string
|
|
notes: string | null
|
|
status: Status
|
|
priority: Priority
|
|
due_at: string | null
|
|
remind_at: string | null
|
|
reminded_at: string | null
|
|
recur_rule: RecurRule | null
|
|
completed_at: string | null
|
|
sort_order: number
|
|
created_at: string
|
|
updated_at: string
|
|
// joined / derived
|
|
list_name?: string
|
|
list_colour?: string
|
|
status_label?: string
|
|
is_done?: boolean
|
|
is_hold?: boolean
|
|
category_name?: string
|
|
category_colour?: string
|
|
project_name?: string
|
|
project_colour?: string
|
|
item_count?: number
|
|
item_done?: number
|
|
}
|
|
|
|
export interface AppStatus {
|
|
id: number
|
|
label: string
|
|
value: string
|
|
position: number
|
|
is_done: boolean
|
|
is_hold: boolean
|
|
}
|
|
|
|
export interface Category {
|
|
id: number
|
|
name: string
|
|
colour: string
|
|
sort_order: number
|
|
created_at: string
|
|
}
|
|
|
|
export interface Project {
|
|
id: number
|
|
created_by: string
|
|
name: string
|
|
description: string | null
|
|
colour: string
|
|
is_archived: boolean
|
|
sort_order: number
|
|
created_at: string
|
|
task_count?: number
|
|
done_count?: number
|
|
}
|
|
|
|
export interface ProjectDetail extends Project {
|
|
tasks: Task[]
|
|
}
|
|
|
|
export interface TaskTagEntry {
|
|
user_email: string
|
|
tagged_by: string
|
|
created_at: string
|
|
}
|
|
|
|
export interface TaskItem {
|
|
id: number
|
|
task_id: number
|
|
label: string
|
|
is_done: boolean
|
|
sort_order: number
|
|
}
|
|
|
|
export interface TaskComment {
|
|
id: number
|
|
task_id: number
|
|
author_email: string | null
|
|
author_name: string | null
|
|
body: string
|
|
created_at: string
|
|
}
|
|
|
|
export interface TaskDetail extends Task {
|
|
items: TaskItem[]
|
|
comments: TaskComment[]
|
|
tags: TaskTagEntry[]
|
|
}
|
|
|
|
export interface AppConfig {
|
|
remind_by_push: boolean
|
|
remind_by_email: boolean
|
|
}
|
|
|
|
export interface User {
|
|
user_id: number
|
|
name: string
|
|
email: string
|
|
is_admin: boolean
|
|
caps: string[] // bare slugs — verify?app=tasks strips the prefix
|
|
}
|
|
|
|
// Users with access to this app, served by the central auth service.
|
|
export interface AuthUser {
|
|
id: number
|
|
email: string
|
|
name: string
|
|
}
|
|
|
|
export function can(user: User, cap: string): boolean {
|
|
return user.is_admin || user.caps.includes(cap)
|
|
}
|