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
62
frontend/src/pages/TodayView.tsx
Normal file
62
frontend/src/pages/TodayView.tsx
Normal file
|
|
@ -0,0 +1,62 @@
|
|||
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>
|
||||
)
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue