tasks/frontend/src/pages/TodayView.tsx
jtricerolph d8dc4e2b81 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).
2026-09-17 08:07:00 +00:00

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>
)
}