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).
62 lines
2.4 KiB
TypeScript
62 lines
2.4 KiB
TypeScript
import { useEffect, useState, useCallback } from 'react'
|
|
import { CalendarDays } from 'lucide-react'
|
|
import type { Task } from '../types'
|
|
import { fetchTasks, updateTask } from '../api'
|
|
import { useTaskDrawer } from '../components/TaskDrawer'
|
|
import TaskRow from '../components/TaskRow'
|
|
|
|
function startOfDay(d: Date) { const x = new Date(d); x.setHours(0, 0, 0, 0); return x }
|
|
|
|
export default function TodayView() {
|
|
const { openTask, changeVersion } = useTaskDrawer()
|
|
const [tasks, setTasks] = useState<Task[]>([])
|
|
const [error, setError] = useState<string | null>(null)
|
|
|
|
const load = useCallback(() => {
|
|
fetchTasks({ view: 'today' }).then(setTasks).catch(e => setError(e.message))
|
|
}, [])
|
|
useEffect(() => { load() }, [load, changeVersion])
|
|
|
|
async function toggle(t: Task) {
|
|
await updateTask(t.id, { status: t.status === 'done' ? 'todo' : 'done' })
|
|
load()
|
|
}
|
|
|
|
const now = new Date()
|
|
const todayStart = startOfDay(now)
|
|
const tomorrowStart = new Date(todayStart); tomorrowStart.setDate(tomorrowStart.getDate() + 1)
|
|
|
|
const overdue = tasks.filter(t => t.due_at && new Date(t.due_at) < todayStart)
|
|
const today = tasks.filter(t => t.due_at && new Date(t.due_at) >= todayStart && new Date(t.due_at) < tomorrowStart)
|
|
const upcoming = tasks.filter(t => t.due_at && new Date(t.due_at) >= tomorrowStart)
|
|
|
|
const groups: Array<{ key: string; title: string; items: Task[]; overdue?: boolean }> = [
|
|
{ key: 'overdue', title: 'Overdue', items: overdue, overdue: true },
|
|
{ key: 'today', title: 'Today', items: today },
|
|
{ key: 'upcoming', title: 'Next 7 days', items: upcoming },
|
|
]
|
|
|
|
return (
|
|
<div className="page">
|
|
<div className="page-header">
|
|
<h1><CalendarDays size={18} strokeWidth={1.75} style={{ verticalAlign: -3, marginRight: 6 }} />Today</h1>
|
|
</div>
|
|
{error && <div className="error-banner">{error}</div>}
|
|
|
|
{tasks.length === 0 && (
|
|
<div className="empty-state">Nothing due in the next 7 days. Enjoy the calm. ☕</div>
|
|
)}
|
|
|
|
{groups.map(g => g.items.length > 0 && (
|
|
<div key={g.key}>
|
|
<div className={`agenda-group-title${g.overdue ? ' overdue' : ''}`}>
|
|
{g.title} <span className="muted" style={{ fontWeight: 400 }}>· {g.items.length}</span>
|
|
</div>
|
|
{g.items.map(t => (
|
|
<TaskRow key={t.id} task={t} showList onToggle={toggle} onOpen={() => openTask(t.id)} />
|
|
))}
|
|
</div>
|
|
))}
|
|
</div>
|
|
)
|
|
}
|