Files
Velocity-OS/webos/src/pillars/pipeline/client360/tabs/Tasks.tsx

105 lines
3.0 KiB
TypeScript

import { motion } from 'framer-motion';
import { useClientTasks } from '../../../../shared/hooks/useClient360';
import styles from './Tasks.module.css';
/**
* Tasks Tab
* Timeline: TODAY → UPCOMING → COMPLETED
* Tasks are AI-generated or manually created reminders.
* "Done" and "Snooze" are the only actions — no task configuration UI.
*/
interface TasksTabProps {
personId: string;
}
export function TasksTab({ personId }: TasksTabProps) {
const { tasks, isLoading, markDone, snooze } = useClientTasks(personId);
const today = tasks.filter(t => t.group === 'today');
const upcoming = tasks.filter(t => t.group === 'upcoming');
const completed = tasks.filter(t => t.group === 'completed');
if (isLoading) {
return (
<div className={styles.root}>
{[0, 1, 2].map(i => <div key={i} className={`${styles.skeleton} shimmer`} />)}
</div>
);
}
return (
<div className={styles.root}>
{today.length > 0 && (
<TaskGroup label="TODAY" tasks={today} onDone={markDone} onSnooze={snooze} />
)}
{upcoming.length > 0 && (
<TaskGroup label="UPCOMING" tasks={upcoming} onDone={markDone} onSnooze={snooze} />
)}
{completed.length > 0 && (
<TaskGroup label="COMPLETED" tasks={completed} dim />
)}
{tasks.length === 0 && (
<p className={styles.empty}>No tasks yet create one to stay on track.</p>
)}
</div>
);
}
interface Task {
id: string;
label: string;
dueAt?: string;
group: 'today' | 'upcoming' | 'completed';
isAIGenerated?: boolean;
}
function TaskGroup({
label, tasks, dim = false, onDone, onSnooze,
}: {
label: string;
tasks: Task[];
dim?: boolean;
onDone?: (id: string) => void;
onSnooze?: (id: string) => void;
}) {
return (
<motion.div
className={styles.group}
initial={{ opacity: 0 }}
animate={{ opacity: dim ? 0.55 : 1 }}
transition={{ duration: 0.3 }}
>
<h3 className={styles.groupLabel}>{label}</h3>
{tasks.map((task, i) => (
<motion.div
key={task.id}
className={styles.taskRow}
initial={{ opacity: 0, x: -8 }}
animate={{ opacity: 1, x: 0 }}
transition={{ delay: i * 0.05 }}
>
<span className={dim ? styles.checkDone : styles.checkOpen}>
{dim ? '✓' : '○'}
</span>
<div className={styles.taskContent}>
<span className={styles.taskLabel}>{task.label}</span>
{task.dueAt && (
<span className={styles.taskDue}>{task.dueAt}</span>
)}
{task.isAIGenerated && (
<span className={styles.aiChip}> AI</span>
)}
</div>
{!dim && onDone && onSnooze && (
<div className={styles.taskActions}>
<button className="btn-ghost" onClick={() => onDone(task.id)}>Done</button>
<button className="btn-ghost" onClick={() => onSnooze(task.id)}>Snooze</button>
</div>
)}
</motion.div>
))}
</motion.div>
);
}