The file /Users/khalidbinkalban/my-workspace/client/src/components/Notes.jsx has been updated successfully.

import { useState, useEffect } from 'react';
import { Plus, FileText, Trash2, Pencil, Calendar, FolderKanban } from 'lucide-react';
import toast from 'react-hot-toast';
import api from '../api';
import ConfirmDialog from './ConfirmDialog';
import { formatDate } from '../utils';

const NOTE_COLORS = [
  'bg-violet-50 dark:bg-violet-900/20 border-violet-200 dark:border-violet-800',
  'bg-blue-50 dark:bg-blue-900/20 border-blue-200 dark:border-blue-800',
  'bg-amber-50 dark:bg-amber-900/20 border-amber-200 dark:border-amber-800',
  'bg-teal-50 dark:bg-teal-900/20 border-teal-200 dark:border-teal-800',
  'bg-pink-50 dark:bg-pink-900/20 border-pink-200 dark:border-pink-800',
  'bg-green-50 dark:bg-green-900/20 border-green-200 dark:border-green-800',
];

function NoteModal({ note, projects, onSave, onClose }) {
  const [form, setForm] = useState({
    title: '', body: '', project_id: null,
    date: new Date().toISOString().split('T')[0], ...note,
  });
  const set = (k, v) => setForm(f => ({ ...f, [k]: v }));
  return (

 e.stopPropagation()}>

{note?.id ? 'Edit Note' : 'New Note'}

        <form onSubmit={e => { e.preventDefault(); onSave(form); }} className="px-5 py-4 space-y-3">
          <input className="input font-medium" required placeholder="Title" value={form.title} onChange={e => set('title', e.target.value)} autoFocus />
          <textarea className="input resize-none" rows={7} placeholder="Write your note…" value={form.body} onChange={e => set('body', e.target.value)} />

              Date
              <input type="date" className="input" value={form.date} onChange={e => set('date', e.target.value)} />

              Project
              <select className="select" value={form.project_id || ''} onChange={e => set('project_id', e.target.value || null)}>
                <option value="">No project</option>
                {projects.map(p => <option key={p.id} value={p.id}>{p.name}</option>)}
              </select>

            <button type="button" onClick={onClose} className="btn-secondary">Cancel</button>
            <button type="submit" className="btn-primary">{note?.id ? 'Save' : 'Add Note'}</button>

        </form>

  );
}

function NoteCard({ note, colorClass, onEdit, onDelete }) {
  const [expanded, setExpanded] = useState(false);
  return (
    <div className={`rounded-2xl border p-4 flex flex-col gap-2 group transition-all duration-200 hover:shadow-hover hover:-translate-y-0.5 ${colorClass}`}>

{note.title}

          <button onClick={() => onEdit(note)} className="p-1.5 rounded-lg hover:bg-white/60 dark:hover:bg-gray-700 text-gray-500 transition-colors">
            <Pencil size={12} />
          </button>
          <button onClick={() => onDelete(note)} className="p-1.5 rounded-lg hover:bg-red-100 dark:hover:bg-red-900/30 text-gray-400 hover:text-red-500 transition-colors">
            <Trash2 size={12} />
          </button>

      {note.body && (
        <p className={`text-xs text-gray-600 dark:text-gray-400 leading-relaxed whitespace-pre-wrap ${!expanded ? 'line-clamp-4' : ''}`}>
          {note.body}

      )}
      {note.body && note.body.length > 200 && (
        <button onClick={() => setExpanded(!expanded)} className="text-[10px] font-medium text-primary hover:underline self-start">
          {expanded ? 'Show less' : 'Read more'}
        </button>
      )}

        <Calendar size={10} className="text-gray-400 shrink-0" />
        {formatDate(note.date)}
        {note.project_name && (

            <FolderKanban size={9} />{note.project_name}

        )}

  );
}

export default function Notes() {
  const [notes, setNotes]       = useState([]);
  const [projects, setProjects] = useState([]);
  const [loading, setLoading]   = useState(true);
  const [editNote, setEditNote] = useState(null);
  const [deleteNote, setDeleteNote] = useState(null);
  const [showNew, setShowNew]   = useState(false);

  const load = async () => {
    setLoading(true);
    try {
      const [n, pr] = await Promise.all([api.get('/notes'), api.get('/projects')]);
      setNotes(n); setProjects(pr);
    } finally { setLoading(false); }
  };
  useEffect(() => { load(); }, []);

  const handleSave = async (form) => {
    try {
      if (form.id) { await api.put(`/notes/${form.id}`, form); toast.success('Note updated'); }
      else         { await api.post('/notes', form);            toast.success('Note added');   }
      setEditNote(null); setShowNew(false); load();
    } catch (e) { toast.error(e.message); }
  };

  const handleDelete = async () => {
    await api.delete(`/notes/${deleteNote.id}`); toast.success('Note deleted');
    setDeleteNote(null); load();
  };

  return (

      {/* Header */}

Notes

          {!loading &&
{notes.length} note{notes.length !== 1 ? 's' : ''}
}

        <button onClick={() => setShowNew(true)} className="btn-primary flex items-center gap-1.5">
          <Plus size={14} /> New Note
        </button>

      {/* Grid */}

        {loading ? (

            {[...Array(6)].map((_, i) =>
)}

        ) : notes.length === 0 ? (

            <FileText size={44} className="text-gray-200 dark:text-gray-700" />

No notes yet

Capture ideas, meeting notes, decisions

            <button onClick={() => setShowNew(true)} className="btn-primary">Create your first note</button>

        ) : (
          /* Masonry-style columns */

            {notes.map((note, i) => (

                <NoteCard
                  note={note}
                  colorClass={NOTE_COLORS[i % NOTE_COLORS.length]}
                  onEdit={setEditNote}
                  onDelete={setDeleteNote}
                />

            ))}

        )}

      {(showNew || editNote) && (
        <NoteModal note={editNote} projects={projects} onSave={handleSave} onClose={() => { setShowNew(false); setEditNote(null); }} />
      )}
      {deleteNote && (
        <ConfirmDialog title="Delete note?" message={`"${deleteNote.title}"`} onConfirm={handleDelete} onCancel={() => setDeleteNote(null)} />
      )}

  );
}

import { useState, useEffect } from 'react';

-import { Plus, FileText, ChevronDown, ChevronUp, Trash2 } from 'lucide-react';

+import { Plus, FileText, Trash2, Pencil, Calendar, FolderKanban } from 'lucide-react';

import ConfirmDialog from './ConfirmDialog';

+  'bg-violet-50 dark:bg-violet-900/20 border-violet-200 dark:border-violet-800',

+  'bg-blue-50 dark:bg-blue-900/20 border-blue-200 dark:border-blue-800',

+  'bg-amber-50 dark:bg-amber-900/20 border-amber-200 dark:border-amber-800',

+  'bg-teal-50 dark:bg-teal-900/20 border-teal-200 dark:border-teal-800',

+  'bg-pink-50 dark:bg-pink-900/20 border-pink-200 dark:border-pink-800',

+  'bg-green-50 dark:bg-green-900/20 border-green-200 dark:border-green-800',

-    title: '', body: '', project_id: null, date: new Date().toISOString().split('T')[0],

+    title: '', body: '', project_id: null,

+    date: new Date().toISOString().split('T')[0], ...note,

<div className="fixed inset-0 z-50 flex items-end sm:items-center justify-center p-0 sm:p-4" onClick={onClose}>

<div className="absolute inset-0 bg-black/40 backdrop-blur-sm" />

-      <div className="relative bg-white dark:bg-gray-900 w-full sm:max-w-lg rounded-t-2xl sm:rounded-xl shadow-2xl border border-gray-200 dark:border-gray-800 max-h-[90vh] overflow-y-auto" onClick={e => e.stopPropagation()}>

+      <div className="relative bg-white dark:bg-gray-900 w-full sm:max-w-lg rounded-t-2xl sm:rounded-2xl shadow-2xl border border-gray-200 dark:border-gray-800 max-h-[92vh] overflow-y-auto" onClick={e => e.stopPropagation()}>

<div className="px-5 pt-5 pb-4 border-b border-gray-100 dark:border-gray-800">

-            <label className="block text-xs font-medium text-gray-500 mb-1">Title *</label>

-            <input className="input" required value={form.title} onChange={e => set('title', e.target.value)} autoFocus />

-            <label className="block text-xs font-medium text-gray-500 mb-1">Body</label>

-            <textarea className="input resize-none" rows={6} value={form.body} onChange={e => set('body', e.target.value)} placeholder="Note content…" />

+          <input className="input font-medium" required placeholder="Title" value={form.title} onChange={e => set('title', e.target.value)} autoFocus />

+          <textarea className="input resize-none" rows={7} placeholder="Write your note…" value={form.body} onChange={e => set('body', e.target.value)} />

<div className="grid grid-cols-2 gap-3">

<label className="block text-xs font-medium text-gray-500 mb-1">Date</label>

+function NoteCard({ note, colorClass, onEdit, onDelete }) {

+  const [expanded, setExpanded] = useState(false);

+    <div className={`rounded-2xl border p-4 flex flex-col gap-2 group transition-all duration-200 hover:shadow-hover hover:-translate-y-0.5 ${colorClass}`}>

+      <div className="flex items-start justify-between gap-2">

+        <div className="flex items-center gap-1 opacity-0 group-hover:opacity-100 transition-opacity shrink-0">

+          <button onClick={() => onEdit(note)} className="p-1.5 rounded-lg hover:bg-white/60 dark:hover:bg-gray-700 text-gray-500 transition-colors">

+          <button onClick={() => onDelete(note)} className="p-1.5 rounded-lg hover:bg-red-100 dark:hover:bg-red-900/30 text-gray-400 hover:text-red-500 transition-colors">

+        <p className={`text-xs text-gray-600 dark:text-gray-400 leading-relaxed whitespace-pre-wrap ${!expanded ? 'line-clamp-4' : ''}`}>

+      {note.body && note.body.length > 200 && (

+        <button onClick={() => setExpanded(!expanded)} className="text-[10px] font-medium text-primary hover:underline self-start">

+          {expanded ? 'Show less' : 'Read more'}

+      <div className="flex items-center gap-2 mt-auto pt-1 border-t border-black/5 dark:border-white/5">

+        <Calendar size={10} className="text-gray-400 shrink-0" />

+          <span className="text-[10px] text-gray-400 flex items-center gap-1 ml-auto truncate max-w-[120px]">

+            <FolderKanban size={9} />{note.project_name}

-  const [notes, setNotes] = useState([]);

+  const [notes, setNotes]       = useState([]);

const [projects, setProjects] = useState([]);

-  const [loading, setLoading] = useState(true);

-  const [expanded, setExpanded] = useState(new Set());

+  const [loading, setLoading]   = useState(true);

const [editNote, setEditNote] = useState(null);

const [deleteNote, setDeleteNote] = useState(null);

-  const [showNew, setShowNew] = useState(false);

+  const [showNew, setShowNew]   = useState(false);

-      else { await api.post('/notes', form); toast.success('Note added'); }

+      else         { await api.post('/notes', form);            toast.success('Note added');   }

-      await api.delete(`/notes/${deleteNote.id}`); toast.success('Note deleted');

-    } catch (e) { toast.error(e.message); }

+    await api.delete(`/notes/${deleteNote.id}`); toast.success('Note deleted');

-    setExpanded(s => { const n = new Set(s); n.has(id) ? n.delete(id) : n.add(id); return n; });

-    <div className="max-w-2xl mx-auto px-4 py-6">

+    <div className="flex flex-col h-full overflow-hidden">

+      <div className="shrink-0 px-4 pt-4 pb-3 bg-white dark:bg-gray-900 border-b border-gray-100 dark:border-gray-800 flex items-center justify-between">

+          {!loading &&
{notes.length} note{notes.length !== 1 ? 's' : ''}
}

-          {[...Array(4)].map((_, i) => <div key={i} className="skeleton h-20 rounded-lg" />)}

-        <div className="text-center py-16">

-          <FileText size={36} className="text-gray-300 dark:text-gray-700 mx-auto mb-3" />

-          <button onClick={() => setShowNew(true)} className="btn-primary">Create your first note</button>

-            const isOpen = expanded.has(note.id);

-              <div key={note.id} className="card group">

-                <div className="flex items-start gap-3">

-                  <div className="flex-1 min-w-0">

-                    <div className="flex items-start gap-2">

-                      <div className="flex-1">

-                        <div className="flex items-center gap-2 mt-0.5">

-                          {formatDate(note.date)}

-                          {note.project_name && (

-                            · {note.project_name}

-                          onClick={() => toggleExpand(note.id)}

-                          className="flex items-center gap-1 text-xs text-primary mt-1.5 hover:underline"

-                          {isOpen ? <><ChevronUp size={11} /> Show less</> : <><ChevronDown size={11} /> Read more</>}

-                  <div className="flex items-center gap-1 opacity-0 group-hover:opacity-100 transition-opacity shrink-0">

-                    <button onClick={() => setEditNote(note)} className="p-1.5 rounded hover:bg-gray-100 dark:hover:bg-gray-800 text-gray-400 hover:text-gray-700 dark:hover:text-white transition-colors">

-                    <button onClick={() => setDeleteNote(note)} className="p-1.5 rounded hover:bg-red-50 dark:hover:bg-red-900/30 text-gray-400 hover:text-red-500 transition-colors">

+      <div className="flex-1 overflow-y-auto p-4">

+          <div className="columns-1 sm:columns-2 lg:columns-3 xl:columns-4 gap-3 space-y-3">

+            {[...Array(6)].map((_, i) => <div key={i} className="skeleton rounded-2xl break-inside-avoid" style={{ height: [120,160,100,140,180,110][i] }} />)}

+          <div className="flex flex-col items-center justify-center h-full gap-4 py-16">

+            <FileText size={44} className="text-gray-200 dark:text-gray-700" />

+            <div className="text-center">

+
Capture ideas, meeting notes, decisions

+            <button onClick={() => setShowNew(true)} className="btn-primary">Create your first note</button>

+          <div className="columns-1 sm:columns-2 lg:columns-3 xl:columns-4 gap-3">

+              <div key={note.id} className="break-inside-avoid mb-3">

+                  colorClass={NOTE_COLORS[i % NOTE_COLORS.length]}

+                  onDelete={setDeleteNote}

import { useState, useEffect } from 'react';
import { Plus, FileText, ChevronDown, ChevronUp, Trash2 } from 'lucide-react';
import toast from 'react-hot-toast';
import api from '../api';
import ConfirmDialog from './ConfirmDialog';
import { formatDate } from '../utils';

function NoteModal({ note, projects, onSave, onClose }) {
  const [form, setForm] = useState({
    title: '', body: '', project_id: null, date: new Date().toISOString().split('T')[0],
    ...note
  });
  const set = (k, v) => setForm(f => ({ ...f, [k]: v }));
  return (

 e.stopPropagation()}>

{note?.id ? 'Edit Note' : 'New Note'}

        <form onSubmit={e => { e.preventDefault(); onSave(form); }} className="px-5 py-4 space-y-3">

            Title *
            <input className="input" required value={form.title} onChange={e => set('title', e.target.value)} autoFocus />

            Body
            <textarea className="input resize-none" rows={6} value={form.body} onChange={e => set('body', e.target.value)} placeholder="Note content…" />

              Date
              <input type="date" className="input" value={form.date} onChange={e => set('date', e.target.value)} />

              Project
              <select className="select" value={form.project_id || ''} onChange={e => set('project_id', e.target.value || null)}>
                <option value="">No project</option>
                {projects.map(p => <option key={p.id} value={p.id}>{p.name}</option>)}
              </select>

            <button type="button" onClick={onClose} className="btn-secondary">Cancel</button>
            <button type="submit" className="btn-primary">{note?.id ? 'Save' : 'Add Note'}</button>

        </form>

  );
}

export default function Notes() {
  const [notes, setNotes] = useState([]);
  const [projects, setProjects] = useState([]);
  const [loading, setLoading] = useState(true);
  const [expanded, setExpanded] = useState(new Set());
  const [editNote, setEditNote] = useState(null);
  const [deleteNote, setDeleteNote] = useState(null);
  const [showNew, setShowNew] = useState(false);

  const load = async () => {
    setLoading(true);
    try {
      const [n, pr] = await Promise.all([api.get('/notes'), api.get('/projects')]);
      setNotes(n); setProjects(pr);
    } finally { setLoading(false); }
  };

  useEffect(() => { load(); }, []);

  const handleSave = async (form) => {
    try {
      if (form.id) { await api.put(`/notes/${form.id}`, form); toast.success('Note updated'); }
      else { await api.post('/notes', form); toast.success('Note added'); }
      setEditNote(null); setShowNew(false); load();
    } catch (e) { toast.error(e.message); }
  };

  const handleDelete = async () => {
    try {
      await api.delete(`/notes/${deleteNote.id}`); toast.success('Note deleted');
      setDeleteNote(null); load();
    } catch (e) { toast.error(e.message); }
  };

  const toggleExpand = (id) => {
    setExpanded(s => { const n = new Set(s); n.has(id) ? n.delete(id) : n.add(id); return n; });
  };

  return (

Notes

        <button onClick={() => setShowNew(true)} className="btn-primary flex items-center gap-1.5">
          <Plus size={15} /> New Note
        </button>

      {loading ? (

          {[...Array(4)].map((_, i) =>
)}

      ) : notes.length === 0 ? (

          <FileText size={36} className="text-gray-300 dark:text-gray-700 mx-auto mb-3" />

No notes yet

          <button onClick={() => setShowNew(true)} className="btn-primary">Create your first note</button>

      ) : (

          {notes.map(note => {
            const isOpen = expanded.has(note.id);
            return (

{note.title}

                          {formatDate(note.date)}
                          {note.project_name && (
                            · {note.project_name}
                          )}

                    {note.body && (
                      <>
                        {!isOpen && (

{note.body}

                        )}
                        {isOpen && (

{note.body}

                        )}
                        <button
                          onClick={() => toggleExpand(note.id)}
                          className="flex items-center gap-1 text-xs text-primary mt-1.5 hover:underline"
                        >
                          {isOpen ? <><ChevronUp size={11} /> Show less</> : <><ChevronDown size={11} /> Read more</>}
                        </button>
                      </>
                    )}

                    <button onClick={() => setEditNote(note)} className="p-1.5 rounded hover:bg-gray-100 dark:hover:bg-gray-800 text-gray-400 hover:text-gray-700 dark:hover:text-white transition-colors">
                      <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="13" height="13" fill="none" stroke="currentColor" strokeWidth="2"><path d="M11 4H4a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h14a2 2 0 0 0 2-2v-7"/><path d="M18.5 2.5a2.121 2.121 0 0 1 3 3L12 15l-4 1 1-4 9.5-9.5z"/></svg>
                    </button>
                    <button onClick={() => setDeleteNote(note)} className="p-1.5 rounded hover:bg-red-50 dark:hover:bg-red-900/30 text-gray-400 hover:text-red-500 transition-colors">
                      <Trash2 size={13} />
                    </button>

            );
          })}

      )}

      {(showNew || editNote) && (
        <NoteModal note={editNote} projects={projects} onSave={handleSave} onClose={() => { setShowNew(false); setEditNote(null); }} />
      )}
      {deleteNote && (
        <ConfirmDialog title="Delete note?" message={`"${deleteNote.title}"`} onConfirm={handleDelete} onCancel={() => setDeleteNote(null)} />
      )}

  );
}


Write

import { useState, useEffect } from 'react';
import { Bell, Copy, Check, User, AlertTriangle } from 'lucide-react';
import toast from 'react-hot-toast';
import api from '../api';
import { getPriorityClass, getCategoryClass, CATEGORIES, daysAgo, formatDate } from '../utils';

export default function FollowUps() {
  const [tasks, setTasks]   = useState([]);
  const [loading, setLoading] = useState(true);
  const [copied, setCopied] = useState(null);

  useEffect(() => {
    api.get('/tasks?follow_up_only=true').then(setTasks).finally(() => setLoading(false));
  }, []);

  const nudge = async (task) => {
    const name = task.assignee || 'there';
    const msg  = `Hi ${name}, just following up on: "${task.title}". Could you share an update when you get a chance? Thanks!`;
    try {
      await navigator.clipboard.writeText(msg);
      setCopied(task.id);
      toast.success('Message copied!');
      setTimeout(() => setCopied(null), 2000);
    } catch { toast.error('Could not copy to clipboard'); }
  };

  // Group by assignee
  const grouped = {};
  for (const t of tasks) {
    const key = t.assignee || 'Unassigned';
    if (!grouped[key]) grouped[key] = [];
    grouped[key].push(t);
  }
  const sortedGroups = Object.entries(grouped).sort((a, b) => b[1].length - a[1].length);

  return (

      {/* Header */}

Follow-ups

          {loading ? '…' : `${tasks.length} open · grouped by person`}

        {loading ? (

            {[...Array(4)].map((_, i) =>
)}

        ) : tasks.length === 0 ? (

              <Bell size={28} className="text-green-500" />

All clear!

No open follow-ups. You're on top of things.

        ) : (
          /* Horizontal scroll on mobile, grid on desktop */

            {sortedGroups.map(([person, personTasks]) => {
              const avatarLetter = person === 'Unassigned' ? '?' : person[0].toUpperCase();
              const staleCount = personTasks.filter(t => daysAgo(t.created_at) >= 7).length;
              return (

                  {/* Person header */}

                      {avatarLetter}

{person}

                        {personTasks.length} item{personTasks.length !== 1 ? 's' : ''}
                        {staleCount > 0 &&  · {staleCount} stale}

                  {/* Tasks */}

                    {personTasks.map(task => {
                      const age  = daysAgo(task.created_at);
                      const stale = age >= 7;
                      return (

{task.title}

                                <span className={`badge text-[10px] py-0 ${getPriorityClass(task.priority)}`}>{task.priority}
                                {stale && (

                                    <AlertTriangle size={9} />{age}d old

                                )}
                                {!stale && age > 0 && {age}d ago}
                                {task.due_date && {formatDate(task.due_date)}}

                            <button
                              onClick={() => nudge(task)}
                              className={`shrink-0 flex items-center gap-1 px-2 py-1 rounded-lg text-[10px] font-medium transition-all ${
                                copied === task.id
                                  ? 'bg-green-100 text-green-700 dark:bg-green-900/30 dark:text-green-400'
                                  : 'opacity-0 group-hover:opacity-100 bg-gray-100 dark:bg-gray-800 text-gray-600 hover:bg-primary/10 hover:text-primary'
                              }`}>
                              {copied === task.id ? <><Check size={10} /> Copied</> : <><Copy size={10} /> Nudge</>}
                            </button>

                      );
                    })}

              );
            })}

        )}

  );
}


The file /Users/khalidbinkalban/my-workspace/client/src/components/FollowUps.jsx has been updated successfully.

import { useState, useEffect } from 'react';
import { Bell, Copy, Check, User, AlertTriangle } from 'lucide-react';
import toast from 'react-hot-toast';
import api from '../api';
import { getPriorityClass, getCategoryClass, CATEGORIES, daysAgo, formatDate } from '../utils';

export default function FollowUps() {
  const [tasks, setTasks]   = useState([]);
  const [loading, setLoading] = useState(true);
  const [copied, setCopied] = useState(null);

  useEffect(() => {
    api.get('/tasks?follow_up_only=true').then(setTasks).finally(() => setLoading(false));
  }, []);

  const nudge = async (task) => {
    const name = task.assignee || 'there';
    const msg  = `Hi ${name}, just following up on: "${task.title}". Could you share an update when you get a chance? Thanks!`;
    try {
      await navigator.clipboard.writeText(msg);
      setCopied(task.id);
      toast.success('Message copied!');
      setTimeout(() => setCopied(null), 2000);
    } catch { toast.error('Could not copy to clipboard'); }
  };

  // Group by assignee
  const grouped = {};
  for (const t of tasks) {
    const key = t.assignee || 'Unassigned';
    if (!grouped[key]) grouped[key] = [];
    grouped[key].push(t);
  }
  const sortedGroups = Object.entries(grouped).sort((a, b) => b[1].length - a[1].length);

  return (

      {/* Header */}

Follow-ups

          {loading ? '…' : `${tasks.length} open · grouped by person`}

        {loading ? (

            {[...Array(4)].map((_, i) =>
)}

        ) : tasks.length === 0 ? (

              <Bell size={28} className="text-green-500" />

All clear!

No open follow-ups. You're on top of things.

        ) : (
          /* Horizontal scroll on mobile, grid on desktop */

            {sortedGroups.map(([person, personTasks]) => {
              const avatarLetter = person === 'Unassigned' ? '?' : person[0].toUpperCase();
              const staleCount = personTasks.filter(t => daysAgo(t.created_at) >= 7).length;
              return (

                  {/* Person header */}

                      {avatarLetter}

{person}

                        {personTasks.length} item{personTasks.length !== 1 ? 's' : ''}
                        {staleCount > 0 &&  · {staleCount} stale}

                  {/* Tasks */}

                    {personTasks.map(task => {
                      const age  = daysAgo(task.created_at);
                      const stale = age >= 7;
                      return (

{task.title}

                                <span className={`badge text-[10px] py-0 ${getPriorityClass(task.priority)}`}>{task.priority}
                                {stale && (

                                    <AlertTriangle size={9} />{age}d old

                                )}
                                {!stale && age > 0 && {age}d ago}
                                {task.due_date && {formatDate(task.due_date)}}

                            <button
                              onClick={() => nudge(task)}
                              className={`shrink-0 flex items-center gap-1 px-2 py-1 rounded-lg text-[10px] font-medium transition-all ${
                                copied === task.id
                                  ? 'bg-green-100 text-green-700 dark:bg-green-900/30 dark:text-green-400'
                                  : 'opacity-0 group-hover:opacity-100 bg-gray-100 dark:bg-gray-800 text-gray-600 hover:bg-primary/10 hover:text-primary'
                              }`}>
                              {copied === task.id ? <><Check size={10} /> Copied</> : <><Copy size={10} /> Nudge</>}
                            </button>

                      );
                    })}

              );
            })}

        )}

  );
}

import { useState, useEffect } from 'react';

-import { Bell, Copy, Check, User } from 'lucide-react';

+import { Bell, Copy, Check, User, AlertTriangle } from 'lucide-react';

-  const [tasks, setTasks] = useState([]);

+  const [tasks, setTasks]   = useState([]);

const [copied, setCopied] = useState(null);

-    api.get('/tasks?follow_up_only=true')

-      .finally(() => setLoading(false));

+    api.get('/tasks?follow_up_only=true').then(setTasks).finally(() => setLoading(false));

-    const msg = `Hi ${name}, just following up on: "${task.title}". Could you share an update when you get a chance? Thanks!`;

+    const msg  = `Hi ${name}, just following up on: "${task.title}". Could you share an update when you get a chance? Thanks!`;

-      toast.success('Message copied to clipboard!');

+      toast.success('Message copied!');

setTimeout(() => setCopied(null), 2000);

-      toast.error('Could not copy to clipboard');

+    } catch { toast.error('Could not copy to clipboard'); }

-    <div className="max-w-2xl mx-auto px-4 py-6">

-          <p className="text-sm text-gray-500 dark:text-gray-400 mt-0.5">

-            {loading ? '…' : `${tasks.length} open follow-up${tasks.length !== 1 ? 's' : ''}`} — grouped by person

+    <div className="flex flex-col h-full overflow-hidden">

+      <div className="shrink-0 px-4 pt-4 pb-3 bg-white dark:bg-gray-900 border-b border-gray-100 dark:border-gray-800">

+        <p className="text-xs text-gray-400 mt-0.5">

+          {loading ? '…' : `${tasks.length} open · grouped by person`}

-          {[...Array(3)].map((_, i) => <div key={i} className="skeleton h-32 rounded-lg" />)}

-        <div className="text-center py-16">

-          <div className="w-14 h-14 bg-green-100 dark:bg-green-900/30 rounded-full flex items-center justify-center mx-auto mb-4">

-            <Bell size={24} className="text-green-600 dark:text-green-400" />

+      <div className="flex-1 overflow-y-auto p-4">

+          <div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4 gap-4">

+            {[...Array(4)].map((_, i) => <div key={i} className="skeleton h-48 rounded-2xl" />)}

-
No open follow-ups. You're on top of things.

-          {sortedGroups.map(([person, personTasks]) => (

-              <div className="flex items-center gap-2 mb-2.5">

-                <div className="w-7 h-7 bg-gray-200 dark:bg-gray-700 rounded-full flex items-center justify-center shrink-0">

-                  <User size={13} className="text-gray-500 dark:text-gray-400" />

-                <span className="badge bg-gray-100 text-gray-600 dark:bg-gray-800 dark:text-gray-400 ml-1">

+          <div className="flex flex-col items-center justify-center h-full gap-4 py-16">

+            <div className="w-16 h-16 bg-green-100 dark:bg-green-900/30 rounded-full flex items-center justify-center">

+              <Bell size={28} className="text-green-500" />

+            <div className="text-center">

+
No open follow-ups. You're on top of things.

+          /* Horizontal scroll on mobile, grid on desktop */

+            {sortedGroups.map(([person, personTasks]) => {

+              const avatarLetter = person === 'Unassigned' ? '?' : person[0].toUpperCase();

+              const staleCount = personTasks.filter(t => daysAgo(t.created_at) >= 7).length;

+                <div key={person} className="bg-white dark:bg-gray-900 rounded-2xl border border-gray-100 dark:border-gray-800 shadow-card flex flex-col overflow-hidden">

+                  {/* Person header */}

+                  <div className="flex items-center gap-3 px-4 py-3 border-b border-gray-100 dark:border-gray-800 bg-gray-50 dark:bg-gray-800/50">

+                    <div className="w-8 h-8 rounded-full bg-gradient-to-br from-primary to-brand-pink flex items-center justify-center text-white text-sm font-bold shrink-0">

+                    <div className="flex-1 min-w-0">

+                      <p className="text-[10px] text-gray-400">

+                        {personTasks.length} item{personTasks.length !== 1 ? 's' : ''}

+                        {staleCount > 0 &&  · {staleCount} stale}

-              <div className="space-y-2 ml-9">

-                {personTasks.map(task => {

-                  const age = daysAgo(task.created_at);

-                    <div key={task.id} className="card group">

-                      <div className="flex items-start gap-3">

-                        <div className="flex-1 min-w-0">

-                          <div className="flex flex-wrap items-center gap-1.5 mt-1.5">

-                            <span className={`badge ${getPriorityClass(task.priority)}`}>{task.priority}

-                            <span className={`badge ${getCategoryClass(task.category)}`}>{CATEGORIES[task.category]?.label || task.category}

-                            <span className={`text-xs ${age >= 7 ? 'text-red-500 dark:text-red-400 font-medium' : 'text-gray-400'}`}>

-                            {task.due_date && (

-                              Due {formatDate(task.due_date)}

+                  <div className="flex-1 overflow-y-auto divide-y divide-gray-100 dark:divide-gray-800 max-h-64">

+                    {personTasks.map(task => {

+                      const age  = daysAgo(task.created_at);

+                      const stale = age >= 7;

+                        <div key={task.id} className="px-4 py-2.5 group hover:bg-gray-50 dark:hover:bg-gray-800/50 transition-colors">

+                          <div className="flex items-start justify-between gap-2">

+                            <div className="flex-1 min-w-0">

+                              <div className="flex flex-wrap items-center gap-1.5 mt-1">

+                                <span className={`badge text-[10px] py-0 ${getPriorityClass(task.priority)}`}>{task.priority}

+                                {stale && (

+                                  <span className="flex items-center gap-0.5 text-[10px] text-red-500 font-medium">

+                                    <AlertTriangle size={9} />{age}d old

+                                  </span>

+                                {!stale && age > 0 && {age}d ago}

+                                {task.due_date && {formatDate(task.due_date)}}

+                              onClick={() => nudge(task)}

+                              className={`shrink-0 flex items-center gap-1 px-2 py-1 rounded-lg text-[10px] font-medium transition-all ${

+                                copied === task.id

+                                  ? 'bg-green-100 text-green-700 dark:bg-green-900/30 dark:text-green-400'

+                                  : 'opacity-0 group-hover:opacity-100 bg-gray-100 dark:bg-gray-800 text-gray-600 hover:bg-primary/10 hover:text-primary'

+                              {copied === task.id ? <><Check size={10} /> Copied</> : <><Copy size={10} /> Nudge</>}

-                          onClick={() => nudge(task)}

-                          title="Copy follow-up message"

-                          className={`shrink-0 flex items-center gap-1 px-2.5 py-1.5 rounded-lg text-xs font-medium transition-all ${

-                            copied === task.id

-                              ? 'bg-green-100 text-green-700 dark:bg-green-900/30 dark:text-green-400'

-                              : 'bg-gray-100 dark:bg-gray-800 text-gray-600 dark:text-gray-400 hover:bg-primary/10 hover:text-primary opacity-0 group-hover:opacity-100'

-                          {copied === task.id ? <><Check size={11} /> Copied!</> : <><Copy size={11} /> Nudge</>}

import { useState, useEffect } from 'react';
import { Bell, Copy, Check, User } from 'lucide-react';
import toast from 'react-hot-toast';
import api from '../api';
import { getPriorityClass, getCategoryClass, CATEGORIES, daysAgo, formatDate } from '../utils';

export default function FollowUps() {
  const [tasks, setTasks] = useState([]);
  const [loading, setLoading] = useState(true);
  const [copied, setCopied] = useState(null);

  useEffect(() => {
    api.get('/tasks?follow_up_only=true')
      .then(setTasks)
      .finally(() => setLoading(false));
  }, []);

  const nudge = async (task) => {
    const name = task.assignee || 'there';
    const msg = `Hi ${name}, just following up on: "${task.title}". Could you share an update when you get a chance? Thanks!`;
    try {
      await navigator.clipboard.writeText(msg);
      setCopied(task.id);
      toast.success('Message copied to clipboard!');
      setTimeout(() => setCopied(null), 2000);
    } catch {
      toast.error('Could not copy to clipboard');
    }
  };

  // Group by assignee
  const grouped = {};
  for (const t of tasks) {
    const key = t.assignee || 'Unassigned';
    if (!grouped[key]) grouped[key] = [];
    grouped[key].push(t);
  }

  // Sort by number of tasks desc
  const sortedGroups = Object.entries(grouped).sort((a, b) => b[1].length - a[1].length);

  return (

Follow-ups

            {loading ? '…' : `${tasks.length} open follow-up${tasks.length !== 1 ? 's' : ''}`} — grouped by person

      {loading ? (

          {[...Array(3)].map((_, i) =>
)}

      ) : tasks.length === 0 ? (

            <Bell size={24} className="text-green-600 dark:text-green-400" />

All clear!

No open follow-ups. You're on top of things.

      ) : (

          {sortedGroups.map(([person, personTasks]) => (

                  <User size={13} className="text-gray-500 dark:text-gray-400" />

                {person}

                  {personTasks.length}

                {personTasks.map(task => {
                  const age = daysAgo(task.created_at);
                  return (

{task.title}

                            <span className={`badge ${getPriorityClass(task.priority)}`}>{task.priority}
                            <span className={`badge ${getCategoryClass(task.category)}`}>{CATEGORIES[task.category]?.label || task.category}
                            <span className={`text-xs ${age >= 7 ? 'text-red-500 dark:text-red-400 font-medium' : 'text-gray-400'}`}>
                              {age === 0 ? 'Today' : `${age}d ago`}

                            {task.due_date && (
                              Due {formatDate(task.due_date)}
                            )}

                        <button
                          onClick={() => nudge(task)}
                          title="Copy follow-up message"
                          className={`shrink-0 flex items-center gap-1 px-2.5 py-1.5 rounded-lg text-xs font-medium transition-all ${
                            copied === task.id
                              ? 'bg-green-100 text-green-700 dark:bg-green-900/30 dark:text-green-400'
                              : 'bg-gray-100 dark:bg-gray-800 text-gray-600 dark:text-gray-400 hover:bg-primary/10 hover:text-primary opacity-0 group-hover:opacity-100'
                          }`}
                        >
                          {copied === task.id ? <><Check size={11} /> Copied!</> : <><Copy size={11} /> Nudge</>}
                        </button>

                  );
                })}

          ))}

      )}

  );
}


import { useState, useEffect } from 'react';
import { BarChart2, CheckCircle2, TrendingUp, AlertCircle, Clock, FolderKanban, RefreshCw, Sparkles } from 'lucide-react';
import { Link } from 'react-router-dom';
import toast from 'react-hot-toast';
import api from '../api';
import { getPriorityClass, getStatusClass, formatDate } from '../utils';
import ConfirmDialog from './ConfirmDialog';

export default function WeeklyReview() {
  const [data, setData]             = useState(null);
  const [loading, setLoading]       = useState(true);
  const [confirmReset, setConfirm]  = useState(false);

  const load = () => {
    setLoading(true);
    api.get('/weekly-review').then(setData).finally(() => setLoading(false));
  };
  useEffect(() => { load(); }, []);

  const handleReset = async () => {
    try {
      await api.post('/weekly-review/reset');
      toast.success('New week started! Today focus cleared.');
      setConfirm(false); load();
    } catch (e) { toast.error(e.message); }
  };

  const weekStart = data?.weekStart
    ? new Date(data.weekStart).toLocaleDateString('en-GB', { weekday: 'short', day: 'numeric', month: 'short' })
    : '';

  return (

      {/* Header */}

Weekly Review

            {weekStart &&
Week of {weekStart}
}

            <button onClick={load} className="btn-secondary flex items-center gap-1.5 py-2 text-sm">
              <RefreshCw size={13} /> Refresh
            </button>
            <button onClick={() => setConfirm(true)} className="btn-primary flex items-center gap-1.5 py-2 text-sm">
              <Sparkles size={13} /> Start New Week
            </button>

        {loading ? (

            {[...Array(4)].map((_, i) =>
)}

        ) : data && (
          <>
            {/* ── Top stat cards — horizontal ─────────────────── */}

              {[
                { icon: CheckCircle2, label: 'Completed',   value: data.completedThisWeek.length, color: 'text-green-600', bg: 'bg-green-50 dark:bg-green-900/20',   bar: 'bg-green-500' },
                { icon: TrendingUp,   label: 'Added',        value: data.addedThisWeek.length,     color: 'text-blue-600',  bg: 'bg-blue-50 dark:bg-blue-900/20',     bar: 'bg-blue-500'  },
                { icon: AlertCircle,  label: 'Overdue',      value: data.overdue.length,           color: 'text-red-600',   bg: 'bg-red-50 dark:bg-red-900/20',       bar: 'bg-red-500'   },
                { icon: Clock,        label: 'Stale Follow-ups', value: data.staleFollowUps.length, color: 'text-amber-600', bg: 'bg-amber-50 dark:bg-amber-900/20', bar: 'bg-amber-500' },
              ].map(({ icon: Icon, label, value, color, bg, bar }) => (
                <div key={label} className={`${bg} rounded-2xl p-4 flex flex-col gap-2`}>
                  <Icon size={18} className={color} />
                  <p className={`text-3xl font-bold ${color}`}>{value}

{label}

                    <div className={`h-full ${bar} rounded-full transition-all duration-700`}
                      style={{ width: `${Math.min(value * 10, 100)}%` }} />

              ))}

            {/* ── Detail sections — horizontal 2-col grid on desktop ── */}

              {/* Completed */}

 (

                    <CheckCircle2 size={13} className="text-green-500 shrink-0 mt-0.5" />

{t.title}

                        <span className={`badge text-[10px] py-0 ${getPriorityClass(t.priority)}`}>{t.priority}

                )}
              />

              {/* Overdue */}

 (

                    <AlertCircle size={13} className="text-red-500 shrink-0 mt-0.5" />

{t.title}

                        <span className={`badge text-[10px] py-0 ${getPriorityClass(t.priority)}`}>{t.priority}
                        Due {formatDate(t.due_date)}

                    <Link to="/tasks" className="text-[10px] text-primary hover:underline shrink-0 mt-0.5">View</Link>

                )}
              />

              {/* Stale follow-ups */}

 (

                    <Clock size={13} className="text-amber-500 shrink-0 mt-0.5" />

{t.title}

                )}
              />

              {/* Inactive projects */}

 (

                    <FolderKanban size={13} className="text-gray-400 shrink-0" />

{p.name}

                      <span className={`badge text-[10px] py-0 ${getPriorityClass(p.priority)}`}>{p.priority}
                      <Link to={`/projects/${p.id}`} className="text-[10px] text-primary hover:underline">Open</Link>

                )}
              />

            {/* Added this week - full width */}

 (

                  <span className={`badge text-[10px] py-0 ${getPriorityClass(t.priority)}`}>{t.priority}

{t.title}

              )}
              wrapContent
            />
          </>
        )}

      {confirmReset && (
        <ConfirmDialog
          title="Start new week?"
          message="This clears all today-focus pins so you can choose fresh focus items."
          onConfirm={handleReset} onCancel={() => setConfirm(false)} danger={false}
        />
      )}

  );
}

function Section({ icon: Icon, color, title, items, empty, renderItem, wrapContent }) {
  return (

        <Icon size={14} className={color} />

{title}

        {items.length}

      {items.length === 0
        ?
{empty}

        : <div className={`px-4 py-1 max-h-56 overflow-y-auto ${wrapContent ? 'flex flex-wrap py-2' : ''}`}>
            {items.map(renderItem)}

      }

  );
}


The file /Users/khalidbinkalban/my-workspace/client/src/components/WeeklyReview.jsx has been updated successfully.

import { useState, useEffect } from 'react';
import { BarChart2, CheckCircle2, TrendingUp, AlertCircle, Clock, FolderKanban, RefreshCw, Sparkles } from 'lucide-react';
import { Link } from 'react-router-dom';
import toast from 'react-hot-toast';
import api from '../api';
import { getPriorityClass, getStatusClass, formatDate } from '../utils';
import ConfirmDialog from './ConfirmDialog';

export default function WeeklyReview() {
  const [data, setData]             = useState(null);
  const [loading, setLoading]       = useState(true);
  const [confirmReset, setConfirm]  = useState(false);

  const load = () => {
    setLoading(true);
    api.get('/weekly-review').then(setData).finally(() => setLoading(false));
  };
  useEffect(() => { load(); }, []);

  const handleReset = async () => {
    try {
      await api.post('/weekly-review/reset');
      toast.success('New week started! Today focus cleared.');
      setConfirm(false); load();
    } catch (e) { toast.error(e.message); }
  };

  const weekStart = data?.weekStart
    ? new Date(data.weekStart).toLocaleDateString('en-GB', { weekday: 'short', day: 'numeric', month: 'short' })
    : '';

  return (

      {/* Header */}

Weekly Review

            {weekStart &&
Week of {weekStart}
}

            <button onClick={load} className="btn-secondary flex items-center gap-1.5 py-2 text-sm">
              <RefreshCw size={13} /> Refresh
            </button>
            <button onClick={() => setConfirm(true)} className="btn-primary flex items-center gap-1.5 py-2 text-sm">
              <Sparkles size={13} /> Start New Week
            </button>

        {loading ? (

            {[...Array(4)].map((_, i) =>
)}

        ) : data && (
          <>
            {/* ── Top stat cards — horizontal ─────────────────── */}

              {[
                { icon: CheckCircle2, label: 'Completed',   value: data.completedThisWeek.length, color: 'text-green-600', bg: 'bg-green-50 dark:bg-green-900/20',   bar: 'bg-green-500' },
                { icon: TrendingUp,   label: 'Added',        value: data.addedThisWeek.length,     color: 'text-blue-600',  bg: 'bg-blue-50 dark:bg-blue-900/20',     bar: 'bg-blue-500'  },
                { icon: AlertCircle,  label: 'Overdue',      value: data.overdue.length,           color: 'text-red-600',   bg: 'bg-red-50 dark:bg-red-900/20',       bar: 'bg-red-500'   },
                { icon: Clock,        label: 'Stale Follow-ups', value: data.staleFollowUps.length, color: 'text-amber-600', bg: 'bg-amber-50 dark:bg-amber-900/20', bar: 'bg-amber-500' },
              ].map(({ icon: Icon, label, value, color, bg, bar }) => (
                <div key={label} className={`${bg} rounded-2xl p-4 flex flex-col gap-2`}>
                  <Icon size={18} className={color} />
                  <p className={`text-3xl font-bold ${color}`}>{value}

{label}

                    <div className={`h-full ${bar} rounded-full transition-all duration-700`}
                      style={{ width: `${Math.min(value * 10, 100)}%` }} />

              ))}

            {/* ── Detail sections — horizontal 2-col grid on desktop ── */}

              {/* Completed */}

 (

                    <CheckCircle2 size={13} className="text-green-500 shrink-0 mt-0.5" />

{t.title}

                        <span className={`badge text-[10px] py-0 ${getPriorityClass(t.priority)}`}>{t.priority}

                )}
              />

              {/* Overdue */}

 (

                    <AlertCircle size={13} className="text-red-500 shrink-0 mt-0.5" />

{t.title}

                        <span className={`badge text-[10px] py-0 ${getPriorityClass(t.priority)}`}>{t.priority}
                        Due {formatDate(t.due_date)}

                    <Link to="/tasks" className="text-[10px] text-primary hover:underline shrink-0 mt-0.5">View</Link>

                )}
              />

              {/* Stale follow-ups */}

 (

                    <Clock size={13} className="text-amber-500 shrink-0 mt-0.5" />

{t.title}

                )}
              />

              {/* Inactive projects */}

 (

                    <FolderKanban size={13} className="text-gray-400 shrink-0" />

{p.name}

                      <span className={`badge text-[10px] py-0 ${getPriorityClass(p.priority)}`}>{p.priority}
                      <Link to={`/projects/${p.id}`} className="text-[10px] text-primary hover:underline">Open</Link>

                )}
              />

            {/* Added this week - full width */}

 (

                  <span className={`badge text-[10px] py-0 ${getPriorityClass(t.priority)}`}>{t.priority}

{t.title}

              )}
              wrapContent
            />
          </>
        )}

      {confirmReset && (
        <ConfirmDialog
          title="Start new week?"
          message="This clears all today-focus pins so you can choose fresh focus items."
          onConfirm={handleReset} onCancel={() => setConfirm(false)} danger={false}
        />
      )}

  );
}

function Section({ icon: Icon, color, title, items, empty, renderItem, wrapContent }) {
  return (

        <Icon size={14} className={color} />

{title}

        {items.length}

      {items.length === 0
        ?
{empty}

        : <div className={`px-4 py-1 max-h-56 overflow-y-auto ${wrapContent ? 'flex flex-wrap py-2' : ''}`}>
            {items.map(renderItem)}

      }

  );
}

import { useState, useEffect } from 'react';

-import { BarChart2, CheckCircle, Plus, AlertCircle, Clock, FolderKanban, RefreshCw } from 'lucide-react';

+import { BarChart2, CheckCircle2, TrendingUp, AlertCircle, Clock, FolderKanban, RefreshCw, Sparkles } from 'lucide-react';

import { Link } from 'react-router-dom';

-import { getPriorityClass, getStatusClass, getCategoryClass, CATEGORIES, formatDate } from '../utils';

+import { getPriorityClass, getStatusClass, formatDate } from '../utils';

import ConfirmDialog from './ConfirmDialog';

-function Section({ icon: Icon, title, color, items, renderItem, emptyText }) {

-      <div className="flex items-center gap-2 mb-3">

-        <Icon size={16} className={color} />

-    <div className="flex items-start gap-2">

-        <div className="flex flex-wrap gap-1 mt-1">

-          <span className={`badge ${getPriorityClass(task.priority)}`}>{task.priority}

-          <span className={`badge ${getCategoryClass(task.category)}`}>{CATEGORIES[task.category]?.label || task.category}

export default function WeeklyReview() {

-  const [data, setData] = useState(null);

-  const [loading, setLoading] = useState(true);

-  const [confirmReset, setConfirmReset] = useState(false);

+  const [data, setData]             = useState(null);

+  const [loading, setLoading]       = useState(true);

+  const [confirmReset, setConfirm]  = useState(false);

toast.success('New week started! Today focus cleared.');

-    <div className="max-w-2xl mx-auto px-4 py-6 space-y-4">

-      {[...Array(5)].map((_, i) => <div key={i} className="skeleton h-32 rounded-lg" />)}

+    ? new Date(data.weekStart).toLocaleDateString('en-GB', { weekday: 'short', day: 'numeric', month: 'short' })

-  const weekStart = data?.weekStart ? new Date(data.weekStart).toLocaleDateString('en-GB', { weekday: 'short', day: 'numeric', month: 'short' }) : '';

-    <div className="max-w-2xl mx-auto px-4 py-6">

-          {weekStart &&
Week starting {weekStart}
}

+    <div className="flex flex-col h-full overflow-hidden">

+      <div className="shrink-0 px-4 pt-4 pb-3 bg-white dark:bg-gray-900 border-b border-gray-100 dark:border-gray-800">

+        <div className="flex items-center justify-between">

+            {weekStart &&
Week of {weekStart}
}

+            <button onClick={load} className="btn-secondary flex items-center gap-1.5 py-2 text-sm">

+              <RefreshCw size={13} /> Refresh

+            <button onClick={() => setConfirm(true)} className="btn-primary flex items-center gap-1.5 py-2 text-sm">

+              <Sparkles size={13} /> Start New Week

-          <button onClick={load} className="btn-secondary flex items-center gap-1.5 text-sm">

-            <RefreshCw size={13} /> Refresh

-          <button onClick={() => setConfirmReset(true)} className="btn-primary flex items-center gap-1.5">

-            <Plus size={14} /> Start New Week

-      <div className="grid grid-cols-2 sm:grid-cols-4 gap-3 mb-6">

-        <div className="card text-center">

-          icon={CheckCircle} title="Completed This Week" color="text-green-600 dark:text-green-400"

-          items={data.completedThisWeek}

-          renderItem={t => <TaskRow key={t.id} task={t} />}

-          emptyText="Nothing completed this week yet."

-          icon={AlertCircle} title="Overdue Tasks" color="text-red-500 dark:text-red-400"

-            <div key={t.id} className="flex items-start gap-2">

-                <div className="flex gap-1 mt-1 flex-wrap">

-                  <span className={`badge ${getPriorityClass(t.priority)}`}>{t.priority}

-                  Due {formatDate(t.due_date)}

+      <div className="flex-1 overflow-y-auto p-4 space-y-5">

+          <div className="grid grid-cols-2 sm:grid-cols-4 gap-3">

+            {[...Array(4)].map((_, i) => <div key={i} className="skeleton h-24 rounded-2xl" />)}

+            {/* ── Top stat cards — horizontal ─────────────────── */}

+            <div className="grid grid-cols-2 sm:grid-cols-4 gap-3">

+                { icon: CheckCircle2, label: 'Completed',   value: data.completedThisWeek.length, color: 'text-green-600', bg: 'bg-green-50 dark:bg-green-900/20',   bar: 'bg-green-500' },

+                { icon: TrendingUp,   label: 'Added',        value: data.addedThisWeek.length,     color: 'text-blue-600',  bg: 'bg-blue-50 dark:bg-blue-900/20',     bar: 'bg-blue-500'  },

+                { icon: AlertCircle,  label: 'Overdue',      value: data.overdue.length,           color: 'text-red-600',   bg: 'bg-red-50 dark:bg-red-900/20',       bar: 'bg-red-500'   },

+                { icon: Clock,        label: 'Stale Follow-ups', value: data.staleFollowUps.length, color: 'text-amber-600', bg: 'bg-amber-50 dark:bg-amber-900/20', bar: 'bg-amber-500' },

+              ].map(({ icon: Icon, label, value, color, bg, bar }) => (

+                <div key={label} className={`${bg} rounded-2xl p-4 flex flex-col gap-2`}>

+                  <Icon size={18} className={color} />

+                  <p className={`text-3xl font-bold ${color}`}>{value}

+                  <div className="h-1.5 bg-black/5 dark:bg-white/5 rounded-full overflow-hidden">

+                    <div className={`h-full ${bar} rounded-full transition-all duration-700`}

-              <Link to="/tasks" className="text-xs text-primary hover:underline shrink-0">View</Link>

-          emptyText="No overdue tasks. Great work!"

-          icon={Clock} title="Stale Follow-ups (7+ days)" color="text-amber-600 dark:text-amber-400"

-          renderItem={t => <TaskRow key={t.id} task={t} />}

-          emptyText="No stale follow-ups."

+            {/* ── Detail sections — horizontal 2-col grid on desktop ── */}

+            <div className="grid grid-cols-1 lg:grid-cols-2 gap-4">

-          icon={FolderKanban} title="Inactive Projects This Week" color="text-gray-500"

-          items={data.inactiveProjects}

-            <div key={p.id} className="flex items-center justify-between gap-2">

-              <div className="flex-1 min-w-0">

-                <div className="flex gap-1 mt-1">

-                  <span className={`badge ${getPriorityClass(p.priority)}`}>{p.priority}

-                  <span className={`badge ${getStatusClass(p.status)}`}>{p.status}

-              <Link to={`/projects/${p.id}`} className="text-xs text-primary hover:underline shrink-0">Open</Link>

+              <Section icon={CheckCircle2} color="text-green-600" title="Completed This Week" items={data.completedThisWeek}

+                empty="Nothing completed yet this week."

+                  <div key={t.id} className="flex items-start gap-2 py-2 border-b border-gray-100 dark:border-gray-800 last:border-0">

+                    <CheckCircle2 size={13} className="text-green-500 shrink-0 mt-0.5" />

+                    <div className="flex-1 min-w-0">

+                      <div className="flex gap-1 mt-1">

+                        <span className={`badge text-[10px] py-0 ${getPriorityClass(t.priority)}`}>{t.priority}

+              <Section icon={AlertCircle} color="text-red-500" title="Overdue Tasks" items={data.overdue}

+                empty="No overdue tasks — great work!"

+                    <AlertCircle size={13} className="text-red-500 shrink-0 mt-0.5" />

+                        Due {formatDate(t.due_date)}

+                    <Link to="/tasks" className="text-[10px] text-primary hover:underline shrink-0 mt-0.5">View</Link>

+              <Section icon={Clock} color="text-amber-600" title="Stale Follow-ups (7+ days)" items={data.staleFollowUps}

+                empty="No stale follow-ups."

+                    <Clock size={13} className="text-amber-500 shrink-0 mt-0.5" />

+              {/* Inactive projects */}

+              <Section icon={FolderKanban} color="text-gray-400" title="Inactive Projects" items={data.inactiveProjects}

+                empty="All projects had activity this week."

+                  <div key={p.id} className="flex items-center gap-2 py-2 border-b border-gray-100 dark:border-gray-800 last:border-0">

+                    <FolderKanban size={13} className="text-gray-400 shrink-0" />

+                    <div className="flex gap-1 shrink-0">

+                      <span className={`badge text-[10px] py-0 ${getPriorityClass(p.priority)}`}>{p.priority}

+                      <Link to={`/projects/${p.id}`} className="text-[10px] text-primary hover:underline">Open</Link>

-          emptyText="All projects had activity this week."

-          icon={Plus} title="Added This Week" color="text-blue-500 dark:text-blue-400"

-          renderItem={t => <TaskRow key={t.id} task={t} />}

-          emptyText="No new tasks this week."

+            {/* Added this week - full width */}

+            <Section icon={TrendingUp} color="text-blue-500" title="Added This Week" items={data.addedThisWeek}

+              empty="No new tasks this week."

+                <div key={t.id} className="inline-flex items-center gap-2 bg-gray-50 dark:bg-gray-800 rounded-xl px-3 py-2 m-1">

-          message="This will clear all today-focus pins. You can re-pin tasks for the new week."

-          onCancel={() => setConfirmReset(false)}

+          message="This clears all today-focus pins so you can choose fresh focus items."

+          onConfirm={handleReset} onCancel={() => setConfirm(false)} danger={false}

+function Section({ icon: Icon, color, title, items, empty, renderItem, wrapContent }) {

+    <div className="bg-white dark:bg-gray-900 rounded-2xl border border-gray-100 dark:border-gray-800 shadow-card overflow-hidden">

+      <div className="flex items-center gap-2 px-4 py-3 border-b border-gray-100 dark:border-gray-800 bg-gray-50 dark:bg-gray-800/50">

+        <Icon size={14} className={color} />

+        : <div className={`px-4 py-1 max-h-56 overflow-y-auto ${wrapContent ? 'flex flex-wrap py-2' : ''}`}>

import { useState, useEffect } from 'react';
import { BarChart2, CheckCircle, Plus, AlertCircle, Clock, FolderKanban, RefreshCw } from 'lucide-react';
import { Link } from 'react-router-dom';
import toast from 'react-hot-toast';
import api from '../api';
import { getPriorityClass, getStatusClass, getCategoryClass, CATEGORIES, formatDate } from '../utils';
import ConfirmDialog from './ConfirmDialog';

function Section({ icon: Icon, title, color, items, renderItem, emptyText }) {
  return (

        <Icon size={16} className={color} />

{title}

        {items.length}

      {items.length === 0
        ?
{emptyText}

        :
{items.map(renderItem)}

      }

  );
}

function TaskRow({ task }) {
  return (

{task.title}

          <span className={`badge ${getPriorityClass(task.priority)}`}>{task.priority}
          <span className={`badge ${getCategoryClass(task.category)}`}>{CATEGORIES[task.category]?.label || task.category}

  );
}

export default function WeeklyReview() {
  const [data, setData] = useState(null);
  const [loading, setLoading] = useState(true);
  const [confirmReset, setConfirmReset] = useState(false);

  const load = () => {
    setLoading(true);
    api.get('/weekly-review').then(setData).finally(() => setLoading(false));
  };

  useEffect(() => { load(); }, []);

  const handleReset = async () => {
    try {
      await api.post('/weekly-review/reset');
      toast.success('New week started! Today focus cleared.');
      setConfirmReset(false);
      load();
    } catch (e) { toast.error(e.message); }
  };

  if (loading) return (

      {[...Array(5)].map((_, i) =>
)}

  );

  const weekStart = data?.weekStart ? new Date(data.weekStart).toLocaleDateString('en-GB', { weekday: 'short', day: 'numeric', month: 'short' }) : '';

  return (

Weekly Review

          {weekStart &&
Week starting {weekStart}
}

          <button onClick={load} className="btn-secondary flex items-center gap-1.5 text-sm">
            <RefreshCw size={13} /> Refresh
          </button>
          <button onClick={() => setConfirmReset(true)} className="btn-primary flex items-center gap-1.5">
            <Plus size={14} /> Start New Week
          </button>

      {/* Summary stats */}

{data.completedThisWeek.length}

Completed

{data.addedThisWeek.length}

Added

{data.overdue.length}

Overdue

{data.staleFollowUps.length}

Stale Follow-ups

 <TaskRow key={t.id} task={t} />}
          emptyText="Nothing completed this week yet."
        />

 (

{t.title}

                  <span className={`badge ${getPriorityClass(t.priority)}`}>{t.priority}
                  Due {formatDate(t.due_date)}

              <Link to="/tasks" className="text-xs text-primary hover:underline shrink-0">View</Link>

          )}
          emptyText="No overdue tasks. Great work!"
        />

 <TaskRow key={t.id} task={t} />}
          emptyText="No stale follow-ups."
        />

 (

{p.name}

                  <span className={`badge ${getPriorityClass(p.priority)}`}>{p.priority}
                  <span className={`badge ${getStatusClass(p.status)}`}>{p.status}

              <Link to={`/projects/${p.id}`} className="text-xs text-primary hover:underline shrink-0">Open</Link>

          )}
          emptyText="All projects had activity this week."
        />

 <TaskRow key={t.id} task={t} />}
          emptyText="No new tasks this week."
        />

      {confirmReset && (
        <ConfirmDialog
          title="Start new week?"
          message="This will clear all today-focus pins. You can re-pin tasks for the new week."
          onConfirm={handleReset}
          onCancel={() => setConfirmReset(false)}
          danger={false}
        />
      )}

  );
}


import { useState, useEffect } from 'react';
import { Users, Plus, User, X } from 'lucide-react';
import toast from 'react-hot-toast';
import api from '../api';
import { getPriorityClass, getStatusClass, getCategoryClass, CATEGORIES } from '../utils';

const STATUS_ORDER = ['Open', 'In Progress', 'Waiting', 'Done'];
const STATUS_COLORS = {
  Open:          'bg-gray-100 text-gray-600 dark:bg-gray-800 dark:text-gray-400',
  'In Progress': 'bg-blue-100 text-blue-700 dark:bg-blue-900/40 dark:text-blue-300',
  Waiting:       'bg-amber-100 text-amber-700 dark:bg-amber-900/40 dark:text-amber-300',
  Done:          'bg-green-100 text-green-700 dark:bg-green-900/40 dark:text-green-300',
};

function Avatar({ name, size = 'md' }) {
  const s = size === 'lg' ? 'w-12 h-12 text-base' : size === 'sm' ? 'w-7 h-7 text-[10px]' : 'w-9 h-9 text-sm';
  return (
    <div className={`${s} rounded-full bg-gradient-to-br from-primary to-brand-pink flex items-center justify-center text-white font-bold shrink-0`}>
      {name === 'Unassigned' ? <User size={14} /> : name[0].toUpperCase()}

  );
}

export default function People() {
  const [people, setPeople]           = useState([]);
  const [selectedPerson, setSelected] = useState(null);
  const [personTasks, setPersonTasks] = useState([]);
  const [loadingPeople, setLoadingP]  = useState(true);
  const [loadingTasks, setLoadingT]   = useState(false);
  const [showAdd, setShowAdd]         = useState(false);
  const [newName, setNewName]         = useState('');

  useEffect(() => {
    api.get('/people').then(setPeople).finally(() => setLoadingP(false));
  }, []);

  const selectPerson = async (person) => {
    setSelected(person);
    setLoadingT(true);
    try {
      setPersonTasks(await api.get(`/people/${encodeURIComponent(person.name)}/tasks`));
    } finally { setLoadingT(false); }
  };

  const addPerson = async (e) => {
    e.preventDefault();
    if (!newName.trim()) return;
    try {
      const p = await api.post('/people', { name: newName.trim() });
      setPeople(prev => [...prev, p].sort((a, b) => a.name.localeCompare(b.name)));
      setNewName(''); setShowAdd(false);
      toast.success('Person added');
    } catch (e) { toast.error(e.message); }
  };

  const groupedTasks = STATUS_ORDER.reduce((acc, s) => {
    const t = personTasks.filter(t => t.status === s);
    if (t.length) acc[s] = t;
    return acc;
  }, {});

  return (

      {/* Header */}

People

{people.length} team members

        <button onClick={() => setShowAdd(!showAdd)} className="btn-secondary flex items-center gap-1.5 py-2 text-sm">
          <Plus size={13} /> Add
        </button>

      {showAdd && (

          <form onSubmit={addPerson} className="flex gap-2">
            <input className="input flex-1 py-2" placeholder="Full name" value={newName} onChange={e => setNewName(e.target.value)} autoFocus />
            <button type="submit" className="btn-primary py-2 text-sm">Add</button>
            <button type="button" onClick={() => setShowAdd(false)} className="btn-icon"><X size={16} /></button>
          </form>

      )}

        {/* ── People grid (left on desktop, top on mobile) ─────────────── */}
        <div className={`${selectedPerson ? 'hidden md:block' : ''} md:w-64 shrink-0 overflow-y-auto border-r border-gray-100 dark:border-gray-800 bg-white dark:bg-gray-900`}>
          {loadingPeople ? (

              {[...Array(8)].map((_, i) =>
)}

          ) : (

              {people.map(person => (
                <button key={person.id} onClick={() => selectPerson(person)}
                  className={`flex flex-col items-center gap-2 p-3 rounded-2xl text-center transition-all duration-150 ${
                    selectedPerson?.id === person.id
                      ? 'bg-primary/10 ring-2 ring-primary/30'
                      : 'bg-gray-50 dark:bg-gray-800 hover:bg-gray-100 dark:hover:bg-gray-700'
                  }`}>
                  <Avatar name={person.name} size="md" />
                  {person.name}
                </button>
              ))}
              {people.length === 0 && (

No people yet

              )}

          )}

        {/* ── Person detail (right panel) ───────────────────────────────── */}
        <div className={`${!selectedPerson ? 'hidden md:flex' : 'flex'} flex-1 flex-col overflow-hidden min-w-0`}>
          {!selectedPerson ? (

                <Users size={28} className="text-gray-300 dark:text-gray-600" />

Select a person to see their tasks

          ) : (
            <>
              {/* Person header */}

                <button onClick={() => setSelected(null)} className="md:hidden btn-icon mr-1">
                  <X size={16} />
                </button>
                <Avatar name={selectedPerson.name} size="lg" />

{selectedPerson.name}

{personTasks.length} tasks total

              {/* Tasks in horizontal status columns */}
              {loadingTasks ? (

                  {[1,2,3].map(i =>
)}

              ) : personTasks.length === 0 ? (

No tasks assigned to {selectedPerson.name}

              ) : (

                  {STATUS_ORDER.filter(s => groupedTasks[s]).map(status => (

{status}

                        <span className={`badge text-[10px] py-0.5 ${STATUS_COLORS[status]}`}>{groupedTasks[status].length}

                        {groupedTasks[status].map(t => (

{t.title}

                              <span className={`badge text-[10px] py-0.5 ${getPriorityClass(t.priority)}`}>{t.priority}
                              <span className={`badge text-[10px] py-0.5 ${getCategoryClass(t.category)}`}>
                                {CATEGORIES[t.category]?.label?.split(' ')[0] || t.category}

                        ))}

                  ))}

              )}
            </>
          )}

  );
}


The file /Users/khalidbinkalban/my-workspace/client/src/components/People.jsx has been updated successfully.

import { useState, useEffect } from 'react';
import { Users, Plus, User, X } from 'lucide-react';
import toast from 'react-hot-toast';
import api from '../api';
import { getPriorityClass, getStatusClass, getCategoryClass, CATEGORIES } from '../utils';

const STATUS_ORDER = ['Open', 'In Progress', 'Waiting', 'Done'];
const STATUS_COLORS = {
  Open:          'bg-gray-100 text-gray-600 dark:bg-gray-800 dark:text-gray-400',
  'In Progress': 'bg-blue-100 text-blue-700 dark:bg-blue-900/40 dark:text-blue-300',
  Waiting:       'bg-amber-100 text-amber-700 dark:bg-amber-900/40 dark:text-amber-300',
  Done:          'bg-green-100 text-green-700 dark:bg-green-900/40 dark:text-green-300',
};

function Avatar({ name, size = 'md' }) {
  const s = size === 'lg' ? 'w-12 h-12 text-base' : size === 'sm' ? 'w-7 h-7 text-[10px]' : 'w-9 h-9 text-sm';
  return (
    <div className={`${s} rounded-full bg-gradient-to-br from-primary to-brand-pink flex items-center justify-center text-white font-bold shrink-0`}>
      {name === 'Unassigned' ? <User size={14} /> : name[0].toUpperCase()}

  );
}

export default function People() {
  const [people, setPeople]           = useState([]);
  const [selectedPerson, setSelected] = useState(null);
  const [personTasks, setPersonTasks] = useState([]);
  const [loadingPeople, setLoadingP]  = useState(true);
  const [loadingTasks, setLoadingT]   = useState(false);
  const [showAdd, setShowAdd]         = useState(false);
  const [newName, setNewName]         = useState('');

  useEffect(() => {
    api.get('/people').then(setPeople).finally(() => setLoadingP(false));
  }, []);

  const selectPerson = async (person) => {
    setSelected(person);
    setLoadingT(true);
    try {
      setPersonTasks(await api.get(`/people/${encodeURIComponent(person.name)}/tasks`));
    } finally { setLoadingT(false); }
  };

  const addPerson = async (e) => {
    e.preventDefault();
    if (!newName.trim()) return;
    try {
      const p = await api.post('/people', { name: newName.trim() });
      setPeople(prev => [...prev, p].sort((a, b) => a.name.localeCompare(b.name)));
      setNewName(''); setShowAdd(false);
      toast.success('Person added');
    } catch (e) { toast.error(e.message); }
  };

  const groupedTasks = STATUS_ORDER.reduce((acc, s) => {
    const t = personTasks.filter(t => t.status === s);
    if (t.length) acc[s] = t;
    return acc;
  }, {});

  return (

      {/* Header */}

People

{people.length} team members

        <button onClick={() => setShowAdd(!showAdd)} className="btn-secondary flex items-center gap-1.5 py-2 text-sm">
          <Plus size={13} /> Add
        </button>

      {showAdd && (

          <form onSubmit={addPerson} className="flex gap-2">
            <input className="input flex-1 py-2" placeholder="Full name" value={newName} onChange={e => setNewName(e.target.value)} autoFocus />
            <button type="submit" className="btn-primary py-2 text-sm">Add</button>
            <button type="button" onClick={() => setShowAdd(false)} className="btn-icon"><X size={16} /></button>
          </form>

      )}

        {/* ── People grid (left on desktop, top on mobile) ─────────────── */}
        <div className={`${selectedPerson ? 'hidden md:block' : ''} md:w-64 shrink-0 overflow-y-auto border-r border-gray-100 dark:border-gray-800 bg-white dark:bg-gray-900`}>
          {loadingPeople ? (

              {[...Array(8)].map((_, i) =>
)}

          ) : (

              {people.map(person => (
                <button key={person.id} onClick={() => selectPerson(person)}
                  className={`flex flex-col items-center gap-2 p-3 rounded-2xl text-center transition-all duration-150 ${
                    selectedPerson?.id === person.id
                      ? 'bg-primary/10 ring-2 ring-primary/30'
                      : 'bg-gray-50 dark:bg-gray-800 hover:bg-gray-100 dark:hover:bg-gray-700'
                  }`}>
                  <Avatar name={person.name} size="md" />
                  {person.name}
                </button>
              ))}
              {people.length === 0 && (

No people yet

              )}

          )}

        {/* ── Person detail (right panel) ───────────────────────────────── */}
        <div className={`${!selectedPerson ? 'hidden md:flex' : 'flex'} flex-1 flex-col overflow-hidden min-w-0`}>
          {!selectedPerson ? (

                <Users size={28} className="text-gray-300 dark:text-gray-600" />

Select a person to see their tasks

          ) : (
            <>
              {/* Person header */}

                <button onClick={() => setSelected(null)} className="md:hidden btn-icon mr-1">
                  <X size={16} />
                </button>
                <Avatar name={selectedPerson.name} size="lg" />

{selectedPerson.name}

{personTasks.length} tasks total

              {/* Tasks in horizontal status columns */}
              {loadingTasks ? (

                  {[1,2,3].map(i =>
)}

              ) : personTasks.length === 0 ? (

No tasks assigned to {selectedPerson.name}

              ) : (

                  {STATUS_ORDER.filter(s => groupedTasks[s]).map(status => (

{status}

                        <span className={`badge text-[10px] py-0.5 ${STATUS_COLORS[status]}`}>{groupedTasks[status].length}

                        {groupedTasks[status].map(t => (

{t.title}

                              <span className={`badge text-[10px] py-0.5 ${getPriorityClass(t.priority)}`}>{t.priority}
                              <span className={`badge text-[10px] py-0.5 ${getCategoryClass(t.category)}`}>
                                {CATEGORIES[t.category]?.label?.split(' ')[0] || t.category}

                        ))}

                  ))}

              )}
            </>
          )}

  );
}

import { useState, useEffect } from 'react';

-import { Users, Plus, User, ChevronRight } from 'lucide-react';

+import { Users, Plus, User, X } from 'lucide-react';

const STATUS_ORDER = ['Open', 'In Progress', 'Waiting', 'Done'];

+  Open:          'bg-gray-100 text-gray-600 dark:bg-gray-800 dark:text-gray-400',

+  'In Progress': 'bg-blue-100 text-blue-700 dark:bg-blue-900/40 dark:text-blue-300',

+  Waiting:       'bg-amber-100 text-amber-700 dark:bg-amber-900/40 dark:text-amber-300',

+  Done:          'bg-green-100 text-green-700 dark:bg-green-900/40 dark:text-green-300',

+function Avatar({ name, size = 'md' }) {

+  const s = size === 'lg' ? 'w-12 h-12 text-base' : size === 'sm' ? 'w-7 h-7 text-[10px]' : 'w-9 h-9 text-sm';

+    <div className={`${s} rounded-full bg-gradient-to-br from-primary to-brand-pink flex items-center justify-center text-white font-bold shrink-0`}>

+      {name === 'Unassigned' ? <User size={14} /> : name[0].toUpperCase()}

-  const [people, setPeople] = useState([]);

-  const [selectedPerson, setSelectedPerson] = useState(null);

+  const [people, setPeople]           = useState([]);

+  const [selectedPerson, setSelected] = useState(null);

const [personTasks, setPersonTasks] = useState([]);

-  const [loadingPeople, setLoadingPeople] = useState(true);

-  const [loadingTasks, setLoadingTasks] = useState(false);

-  const [showAdd, setShowAdd] = useState(false);

-  const [newName, setNewName] = useState('');

+  const [loadingPeople, setLoadingP]  = useState(true);

+  const [loadingTasks, setLoadingT]   = useState(false);

+  const [showAdd, setShowAdd]         = useState(false);

+  const [newName, setNewName]         = useState('');

-    api.get('/people').then(setPeople).finally(() => setLoadingPeople(false));

+    api.get('/people').then(setPeople).finally(() => setLoadingP(false));

const selectPerson = async (person) => {

-      const tasks = await api.get(`/people/${encodeURIComponent(person.name)}/tasks`);

-    } finally { setLoadingTasks(false); }

+      setPersonTasks(await api.get(`/people/${encodeURIComponent(person.name)}/tasks`));

const groupedTasks = STATUS_ORDER.reduce((acc, s) => {

const t = personTasks.filter(t => t.status === s);

-  const waiting = personTasks.filter(t => t.is_follow_up && t.status !== 'Done');

-    <div className="max-w-4xl mx-auto px-4 py-6">

-        <button onClick={() => setShowAdd(!showAdd)} className="btn-secondary flex items-center gap-1.5">

-          <Plus size={14} /> Add person

+    <div className="flex flex-col h-full overflow-hidden">

+      <div className="shrink-0 px-4 pt-4 pb-3 bg-white dark:bg-gray-900 border-b border-gray-100 dark:border-gray-800 flex items-center justify-between">

+        <button onClick={() => setShowAdd(!showAdd)} className="btn-secondary flex items-center gap-1.5 py-2 text-sm">

-        <form onSubmit={addPerson} className="card mb-4 flex items-center gap-2">

-          <input className="input flex-1" placeholder="Full name" value={newName} onChange={e => setNewName(e.target.value)} autoFocus />

-          <button type="submit" className="btn-primary text-sm py-2">Add</button>

-          <button type="button" onClick={() => setShowAdd(false)} className="btn-secondary text-sm py-2">Cancel</button>

+        <div className="shrink-0 px-4 py-2 bg-white dark:bg-gray-900 border-b border-gray-100 dark:border-gray-800">

+          <form onSubmit={addPerson} className="flex gap-2">

+            <input className="input flex-1 py-2" placeholder="Full name" value={newName} onChange={e => setNewName(e.target.value)} autoFocus />

+            <button type="submit" className="btn-primary py-2 text-sm">Add</button>

+            <button type="button" onClick={() => setShowAdd(false)} className="btn-icon"><X size={16} /></button>

-      <div className="flex flex-col md:flex-row gap-4">

-        <div className="w-full md:w-56 shrink-0">

+      <div className="flex-1 overflow-hidden flex flex-col md:flex-row min-h-0">

+        {/* ── People grid (left on desktop, top on mobile) ─────────────── */}

+        <div className={`${selectedPerson ? 'hidden md:block' : ''} md:w-64 shrink-0 overflow-y-auto border-r border-gray-100 dark:border-gray-800 bg-white dark:bg-gray-900`}>

-            <div className="space-y-2">

-              {[...Array(6)].map((_, i) => <div key={i} className="skeleton h-10 rounded-lg" />)}

+            <div className="p-4 grid grid-cols-3 sm:grid-cols-4 md:grid-cols-2 gap-3">

+              {[...Array(8)].map((_, i) => <div key={i} className="skeleton h-20 rounded-2xl" />)}

-            <div className="space-y-1">

+            <div className="p-3 grid grid-cols-3 sm:grid-cols-4 md:grid-cols-2 gap-2.5">

-                  onClick={() => selectPerson(person)}

-                  className={`w-full flex items-center gap-2.5 px-3 py-2.5 rounded-lg text-sm transition-colors text-left ${

+                <button key={person.id} onClick={() => selectPerson(person)}

+                  className={`flex flex-col items-center gap-2 p-3 rounded-2xl text-center transition-all duration-150 ${

-                      ? 'bg-primary/10 text-primary font-medium'

-                      : 'text-gray-700 dark:text-gray-300 hover:bg-gray-100 dark:hover:bg-gray-800'

-                  <div className="w-7 h-7 bg-gray-200 dark:bg-gray-700 rounded-full flex items-center justify-center shrink-0 text-xs font-bold text-gray-600 dark:text-gray-400">

-                  <ChevronRight size={13} className="shrink-0 text-gray-400" />

+                      ? 'bg-primary/10 ring-2 ring-primary/30'

+                      : 'bg-gray-50 dark:bg-gray-800 hover:bg-gray-100 dark:hover:bg-gray-700'

+                  <Avatar name={person.name} size="md" />

-        <div className="flex-1 min-w-0">

+        {/* ── Person detail (right panel) ───────────────────────────────── */}

+        <div className={`${!selectedPerson ? 'hidden md:flex' : 'flex'} flex-1 flex-col overflow-hidden min-w-0`}>

-            <div className="text-center py-16 border-2 border-dashed border-gray-200 dark:border-gray-800 rounded-xl">

-              <User size={28} className="text-gray-300 dark:text-gray-700 mx-auto mb-3" />

+            <div className="flex-1 flex flex-col items-center justify-center gap-3 text-center p-8">

+              <div className="w-16 h-16 bg-gray-100 dark:bg-gray-800 rounded-full flex items-center justify-center">

+                <Users size={28} className="text-gray-300 dark:text-gray-600" />

-            <div className="space-y-3">

-              {[...Array(3)].map((_, i) => <div key={i} className="skeleton h-16 rounded-lg" />)}

-              <div className="flex items-center gap-3 mb-4">

-                <div className="w-9 h-9 bg-primary/10 rounded-full flex items-center justify-center text-primary font-bold">

-                  {selectedPerson.name[0]}

+              <div className="shrink-0 px-4 py-3 bg-white dark:bg-gray-900 border-b border-gray-100 dark:border-gray-800 flex items-center gap-3">

+                <button onClick={() => setSelected(null)} className="md:hidden btn-icon mr-1">

+                <Avatar name={selectedPerson.name} size="lg" />

-
{personTasks.length} task{personTasks.length !== 1 ? 's' : ''} total

-                  <div className="space-y-2">

-                    {waiting.map(t => (

-                      <div key={t.id} className="card border-amber-200 dark:border-amber-900/50">

-                        <div className="flex gap-1.5 mt-1.5">

-                          <span className={`badge ${getPriorityClass(t.priority)}`}>{t.priority}

-                          <span className={`badge ${getStatusClass(t.status)}`}>{t.status}

+              {/* Tasks in horizontal status columns */}

+                <div className="flex gap-3 p-4 overflow-x-auto">

+                  {[1,2,3].map(i => <div key={i} className="skeleton rounded-2xl shrink-0 w-56 h-48" />)}

-              {Object.entries(groupedTasks).map(([status, statusTasks]) => (

-                <div key={status} className="mb-4">

-                    {statusTasks.map(t => (

-                      <div key={t.id} className="card">

-                        <div className="flex flex-wrap gap-1.5 mt-1.5">

-                          <span className={`badge ${getCategoryClass(t.category)}`}>{CATEGORIES[t.category]?.label || t.category}

-                          {t.project_name && · {t.project_name}}

-              {personTasks.length === 0 && (

-                <div className="text-center py-10">

+              ) : personTasks.length === 0 ? (

+                <div className="flex-1 flex items-center justify-center">

No tasks assigned to {selectedPerson.name}

+                <div className="flex gap-3 p-4 overflow-x-auto flex-1 min-h-0"

+                  style={{ scrollSnapType: 'x mandatory', WebkitOverflowScrolling: 'touch' }}>

+                  {STATUS_ORDER.filter(s => groupedTasks[s]).map(status => (

+                    <div key={status} className="shrink-0 flex flex-col bg-[#F7F7FB] dark:bg-gray-800/40 rounded-2xl overflow-hidden"

+                      style={{ width: 'calc(100vw - 80px)', maxWidth: 260, scrollSnapAlign: 'start' }}>

+                      <div className="flex items-center gap-2 px-3 py-2.5 border-b border-gray-100 dark:border-gray-800">

+                        <span className={`badge text-[10px] py-0.5 ${STATUS_COLORS[status]}`}>{groupedTasks[status].length}

+                      <div className="flex-1 overflow-y-auto p-3 space-y-2.5">

+                        {groupedTasks[status].map(t => (

+                          <div key={t.id} className="bg-white dark:bg-gray-900 rounded-xl border border-gray-100 dark:border-gray-800 p-3 shadow-card">

+                            <div className="flex flex-wrap gap-1.5 mt-2">

+                              <span className={`badge text-[10px] py-0.5 ${getPriorityClass(t.priority)}`}>{t.priority}

+                              <span className={`badge text-[10px] py-0.5 ${getCategoryClass(t.category)}`}>

+                                {CATEGORIES[t.category]?.label?.split(' ')[0] || t.category}

import { useState, useEffect } from 'react';
import { Users, Plus, User, ChevronRight } from 'lucide-react';
import toast from 'react-hot-toast';
import api from '../api';
import { getPriorityClass, getStatusClass, getCategoryClass, CATEGORIES } from '../utils';

const STATUS_ORDER = ['Open', 'In Progress', 'Waiting', 'Done'];

export default function People() {
  const [people, setPeople] = useState([]);
  const [selectedPerson, setSelectedPerson] = useState(null);
  const [personTasks, setPersonTasks] = useState([]);
  const [loadingPeople, setLoadingPeople] = useState(true);
  const [loadingTasks, setLoadingTasks] = useState(false);
  const [showAdd, setShowAdd] = useState(false);
  const [newName, setNewName] = useState('');

  useEffect(() => {
    api.get('/people').then(setPeople).finally(() => setLoadingPeople(false));
  }, []);

  const selectPerson = async (person) => {
    setSelectedPerson(person);
    setLoadingTasks(true);
    try {
      const tasks = await api.get(`/people/${encodeURIComponent(person.name)}/tasks`);
      setPersonTasks(tasks);
    } finally { setLoadingTasks(false); }
  };

  const addPerson = async (e) => {
    e.preventDefault();
    if (!newName.trim()) return;
    try {
      const p = await api.post('/people', { name: newName.trim() });
      setPeople(prev => [...prev, p].sort((a, b) => a.name.localeCompare(b.name)));
      setNewName(''); setShowAdd(false);
      toast.success('Person added');
    } catch (e) { toast.error(e.message); }
  };

  // Group tasks by status
  const groupedTasks = STATUS_ORDER.reduce((acc, s) => {
    const t = personTasks.filter(t => t.status === s);
    if (t.length) acc[s] = t;
    return acc;
  }, {});

  const waiting = personTasks.filter(t => t.is_follow_up && t.status !== 'Done');

  return (

People

        <button onClick={() => setShowAdd(!showAdd)} className="btn-secondary flex items-center gap-1.5">
          <Plus size={14} /> Add person
        </button>

      {showAdd && (
        <form onSubmit={addPerson} className="card mb-4 flex items-center gap-2">
          <input className="input flex-1" placeholder="Full name" value={newName} onChange={e => setNewName(e.target.value)} autoFocus />
          <button type="submit" className="btn-primary text-sm py-2">Add</button>
          <button type="button" onClick={() => setShowAdd(false)} className="btn-secondary text-sm py-2">Cancel</button>
        </form>
      )}

        {/* People list */}

          {loadingPeople ? (

              {[...Array(6)].map((_, i) =>
)}

          ) : (

              {people.map(person => (
                <button
                  key={person.id}
                  onClick={() => selectPerson(person)}
                  className={`w-full flex items-center gap-2.5 px-3 py-2.5 rounded-lg text-sm transition-colors text-left ${
                    selectedPerson?.id === person.id
                      ? 'bg-primary/10 text-primary font-medium'
                      : 'text-gray-700 dark:text-gray-300 hover:bg-gray-100 dark:hover:bg-gray-800'
                  }`}
                >

                    {person.name[0]}

                  {person.name}
                  <ChevronRight size={13} className="shrink-0 text-gray-400" />
                </button>
              ))}
              {people.length === 0 && (

No people yet

              )}

          )}

        {/* Tasks panel */}

          {!selectedPerson ? (

              <User size={28} className="text-gray-300 dark:text-gray-700 mx-auto mb-3" />

Select a person to see their tasks

          ) : loadingTasks ? (

              {[...Array(3)].map((_, i) =>
)}

          ) : (

                  {selectedPerson.name[0]}

{selectedPerson.name}

{personTasks.length} task{personTasks.length !== 1 ? 's' : ''} total

              {waiting.length > 0 && (

Waiting on them ({waiting.length})

                    {waiting.map(t => (

{t.title}

                          <span className={`badge ${getPriorityClass(t.priority)}`}>{t.priority}
                          <span className={`badge ${getStatusClass(t.status)}`}>{t.status}

                    ))}

              )}

              {Object.entries(groupedTasks).map(([status, statusTasks]) => (

{status} ({statusTasks.length})

                    {statusTasks.map(t => (

{t.title}

                          <span className={`badge ${getPriorityClass(t.priority)}`}>{t.priority}
                          <span className={`badge ${getCategoryClass(t.category)}`}>{CATEGORIES[t.category]?.label || t.category}
                          {t.project_name && · {t.project_name}}

                    ))}

              ))}

              {personTasks.length === 0 && (

No tasks assigned to {selectedPerson.name}

              )}

          )}

  );
}


Now build and verify:


Bash

pkill -f "vite client" 2>/dev/null; pkill -f "nodemon" 2>/dev/null; sleep 1
npx vite build client 2>&1 | tail -8

Build to verify no errors


transforming...
✓ 1604 modules transformed.
rendering chunks...
computing gzip size...
../dist/index.html                   0.94 kB │ gzip:  0.55 kB
../dist/assets/index-C1KISGdc.css   48.76 kB │ gzip:  7.82 kB
../dist/assets/index-CMR73TDD.js   335.86 kB │ gzip: 97.72 kB
✓ built in 1.18s


npm run dev &
sleep 3
curl -s http://localhost:3002/api/tasks/stats && echo ""

Start dev server


> my-workspace@1.0.0 dev
> concurrently "npm run server" "npm run client"

[0]
[0] > my-workspace@1.0.0 server
[0] > nodemon server/index.js
[0]
[1]
[1] > my-workspace@1.0.0 client
[1] > vite client
[1]
[0] [nodemon] 3.1.14
[0] [nodemon] to restart at any time, enter `rs`
[0] [nodemon] watching path(s): *.*
[0] [nodemon] watching extensions: js,mjs,cjs,json
[0] [nodemon] starting `node server/index.js`
[1]
[1]   VITE v6.4.1  ready in 100 ms
[1]
[1]   ➜  Local:   http://localhost:5173/
[1]   ➜  Network: http://192.168.1.140:5173/
[1]   ➜  Network: http://100.72.97.47:5173/
[0] 🚀 Server running on http://localhost:3002
{"todayFocus":0,"openTasks":20,"openFollowUps":8,"activeProjects":36}


Done. Everything is live. Here's what was added:

---

### Drag & Drop (Dashboard)
- Grab any task card by the **grip handle** and drag it between the 4 columns: *Today's Focus · In Progress · Follow-ups · Open*
- Dropping into a column automatically updates the task in the database (optimistic UI — updates instantly)
- **Touch support** on mobile/Android: hold for 180ms then drag
- Column highlights with a purple glow when you hover over it
- Ghost overlay card rotates slightly while dragging

### Horizontal layouts on every page
| Page | Layout |
|---|---|
| **Dashboard** | 4-column horizontal-scroll kanban with snap |
| **Tasks** | **Grid view** (responsive 1→2→3→4 cols) + **Board view** (status kanban) — toggle top-right |
| **Notes** | Masonry grid (1→2→3→4 cols) with color-coded cards |
| **Follow-ups** | Person cards in a horizontal grid — each person gets their own card |
| **People** | Person grid + horizontal status columns for their tasks |
| **Weekly Review** | 4 stat cards + 2-col detail grid |

### Mobile friendly
- All kanban boards scroll horizontally with **snap points** — swipe between columns
- Touch drag-and-drop enabled with delay activation
- People page: shows person grid first, tap to drill in, back button to return
- All grids collapse to single column on small screens


fix the Inbox tab content, rename it with quick not, add in the main page a quick note button where i note a fast tasks, enhance the tab content


1	import { useState, useEffect, useRef } from 'react';
2	import { Mic, MicOff, Sparkles, Save, Trash2, Lightbulb } from 'lucide-react';
3	import toast from 'react-hot-toast';
4	import api from '../api';
5	import { CATEGORIES, PRIORITIES, getCategoryClass, getPriorityClass } from '../utils';
6
7	const HISTORY_KEY = 'inbox_history';
8
9	function loadHistory() {
10	  try { return JSON.parse(localStorage.getItem(HISTORY_KEY) || '[]'); } catch { return []; }
11	}
12	function saveHistory(item) {
13	  const h = loadHistory();
14	  h.unshift({ ...item, savedAt: new Date().toISOString() });
15	  localStorage.setItem(HISTORY_KEY, JSON.stringify(h.slice(0, 5)));
16	}
17
18	export default function Inbox() {
19	  const [text, setText] = useState('');
20	  const [listening, setListening] = useState(false);
21	  const [processing, setProcessing] = useState(false);
22	  const [parsed, setParsed] = useState(null);
23	  const [editParsed, setEditParsed] = useState(null);
24	  const [projects, setProjects] = useState([]);
25	  const [history, setHistory] = useState(loadHistory);
26	  const recognitionRef = useRef(null);
27
28	  useEffect(() => {
29	    api.get('/projects').then(setProjects).catch(() => {});
30	  }, []);
31
32	  // Voice setup
33	  const startVoice = () => {
34	    const SR = window.SpeechRecognition || window.webkitSpeechRecognition;
35	    if (!SR) { toast.error('Voice not supported in this browser'); return; }
36	    const r = new SR();
37	    r.lang = 'en-US';
38	    r.interimResults = false;
39	    r.onresult = e => setText(e.results[0][0].transcript);
40	    r.onerror = () => { setListening(false); toast.error('Voice capture failed'); };
41	    r.onend = () => setListening(false);
42	    recognitionRef.current = r;
43	    r.start();
44	    setListening(true);
45	  };
46
47	  const stopVoice = () => {
48	    recognitionRef.current?.stop();
49	    setListening(false);
50	  };

import { useState, useEffect, useRef } from 'react';
import { Mic, MicOff, Sparkles, Save, Trash2, Lightbulb } from 'lucide-react';
import toast from 'react-hot-toast';
import api from '../api';
import { CATEGORIES, PRIORITIES, getCategoryClass, getPriorityClass } from '../utils';

const HISTORY_KEY = 'inbox_history';

function loadHistory() {
  try { return JSON.parse(localStorage.getItem(HISTORY_KEY) || '[]'); } catch { return []; }
}
function saveHistory(item) {
  const h = loadHistory();
  h.unshift({ ...item, savedAt: new Date().toISOString() });
  localStorage.setItem(HISTORY_KEY, JSON.stringify(h.slice(0, 5)));
}

export default function Inbox() {
  const [text, setText] = useState('');
  const [listening, setListening] = useState(false);
  const [processing, setProcessing] = useState(false);
  const [parsed, setParsed] = useState(null);
  const [editParsed, setEditParsed] = useState(null);
  const [projects, setProjects] = useState([]);
  const [history, setHistory] = useState(loadHistory);
  const recognitionRef = useRef(null);

  useEffect(() => {
    api.get('/projects').then(setProjects).catch(() => {});
  }, []);

  // Voice setup
  const startVoice = () => {
    const SR = window.SpeechRecognition || window.webkitSpeechRecognition;
    if (!SR) { toast.error('Voice not supported in this browser'); return; }
    const r = new SR();
    r.lang = 'en-US';
    r.interimResults = false;
    r.onresult = e => setText(e.results[0][0].transcript);
    r.onerror = () => { setListening(false); toast.error('Voice capture failed'); };
    r.onend = () => setListening(false);
    recognitionRef.current = r;
    r.start();
    setListening(true);
  };

  const stopVoice = () => {
    recognitionRef.current?.stop();
    setListening(false);
  };


Now I'll rewrite everything. All changes in parallel: