            {/* Toolbar */}
            <div className="flex items-center gap-2 px-4 pb-3 pt-1">
              {/* Voice */}
              <button
                onClick={listening ? stopVoice : startVoice}
                className={`flex items-center gap-2 px-3.5 py-2 rounded-xl text-sm font-medium transition-all ${
                  listening
                    ? 'bg-red-500 text-white animate-pulse shadow-md'
                    : 'bg-gray-100 dark:bg-gray-800 text-gray-600 dark:text-gray-400 hover:bg-gray-200 dark:hover:bg-gray-700'
                }`}
              >
                {listening ? <MicOff size={14} /> : <Mic size={14} />}
                {listening ? 'Stop' : 'Voice'}
              </button>

              {/* Examples */}

            {/* Toolbar */}
            <div className="flex items-center gap-2 px-4 pb-3 pt-1">
              {/* Examples */}

import { useState, useEffect, useRef } from 'react';
import {
  Sparkles, Save, Trash2, Lightbulb,
  CheckSquare, FileText, FolderKanban, Clock,
  User, Calendar, Zap, ArrowRight
} from 'lucide-react';
import toast from 'react-hot-toast';
import api from '../api';
import { CATEGORIES, PRIORITIES, STATUSES, getCategoryClass, getPriorityClass } from '../utils';

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

const TYPE_CONFIG = {
  task:    { icon: CheckSquare,  color: 'text-primary',   bg: 'bg-primary/10',   label: 'Task'    },
  note:    { icon: FileText,     color: 'text-teal-600',  bg: 'bg-teal-50 dark:bg-teal-900/20',   label: 'Note'    },
  project: { icon: FolderKanban, color: 'text-violet-600',bg: 'bg-violet-50 dark:bg-violet-900/20',label: 'Project' },
};

const EXAMPLES = [
  'Follow up with Hamid about WEF Lighthouse document, urgent by end of week',
  'Benchmark presentation meeting with Shabeer on 13 Apr, high priority',
  'Note: Google NDA meeting happened — next step is Legal Affairs review',
  'New project: AI Ethics workshop for Champions Program, strategic initiative',
];

// ── Parsed result card ───────────────────────────────────────────────────────
function ParsedCard({ data, onChange, projects, onSave, onDiscard }) {
  const TypeCfg = TYPE_CONFIG[data.type] || TYPE_CONFIG.task;
  const TypeIcon = TypeCfg.icon;

  return (

      {/* Card header */}

        <div className={`w-8 h-8 rounded-xl ${TypeCfg.bg} flex items-center justify-center`}>
          <TypeIcon size={15} className={TypeCfg.color} />

AI Parsed Result

Review and edit before saving

        <select
          value={data.type}
          onChange={e => onChange('type', e.target.value)}
          className="text-xs font-semibold border border-primary/30 rounded-lg px-2.5 py-1.5 bg-white dark:bg-gray-800 text-primary focus:outline-none focus:ring-2 focus:ring-primary/30"
        >
          <option value="task">Task</option>
          <option value="note">Note</option>
          <option value="project">Project</option>
        </select>

        {/* Title */}

          Title
          <input
            className="input font-semibold text-base"
            value={data.title}
            onChange={e => onChange('title', e.target.value)}
            placeholder="Title…"
          />

        {/* Category + Priority */}

            Category
            <select className="select text-sm" value={data.category} onChange={e => onChange('category', e.target.value)}>
              {Object.entries(CATEGORIES).map(([k, v]) => <option key={k} value={k}>{v.label}</option>)}
            </select>

            Priority
            <select className="select text-sm" value={data.priority} onChange={e => onChange('priority', e.target.value)}>
              {PRIORITIES.map(p => <option key={p}>{p}</option>)}
            </select>

        {/* Task-specific fields */}
        {data.type === 'task' && (

              <User size={10} /> Assignee
              <input className="input text-sm" value={data.assignee || ''} onChange={e => onChange('assignee', e.target.value)} placeholder="Person name" />

              <Calendar size={10} /> Due Date
              <input type="date" className="input text-sm" value={data.due || ''} onChange={e => onChange('due', e.target.value)} />

        )}

        {/* Details */}

          Details / Notes
          <textarea className="input resize-none text-sm" rows={3} value={data.details || ''} onChange={e => onChange('details', e.target.value)} placeholder="Context…" />

        {/* Task checkboxes */}
        {data.type === 'task' && (

              <input type="checkbox" className="w-4 h-4 rounded accent-primary" checked={!!data.isFollowUp} onChange={e => onChange('isFollowUp', e.target.checked)} />
              Follow-up needed

              <input type="checkbox" className="w-4 h-4 rounded accent-primary" checked={!!data.suggestedTodayFocus} onChange={e => onChange('suggestedTodayFocus', e.target.checked)} />
              Pin to today

        )}

        {/* Project hint */}
        {data.projectHint && (

            <Lightbulb size={13} className="text-amber-600 shrink-0" />

              Suggested project: {data.projectHint}

        )}

      {/* Actions */}

        <button onClick={onDiscard} className="btn-ghost flex items-center gap-1.5 text-sm text-red-500 hover:text-red-600 hover:bg-red-50 dark:hover:bg-red-900/20">
          <Trash2 size={13} /> Discard
        </button>

        <button onClick={onSave} className="btn-primary flex items-center gap-2">
          <Save size={14} /> Save to Workspace
        </button>

  );
}

// ── History card ─────────────────────────────────────────────────────────────
function HistoryItem({ item }) {
  const TypeCfg = TYPE_CONFIG[item.type] || TYPE_CONFIG.task;
  const TypeIcon = TypeCfg.icon;
  const when = new Date(item.savedAt);
  const timeStr = when.toLocaleDateString('en-GB', { day: 'numeric', month: 'short', hour: '2-digit', minute: '2-digit' });

  return (

      <div className={`w-8 h-8 rounded-xl ${TypeCfg.bg} flex items-center justify-center shrink-0`}>
        <TypeIcon size={14} className={TypeCfg.color} />

{item.title}

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

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

  );
}

// ── Main component ────────────────────────────────────────────────────────────
export default function QuickNote() {
  const [text, setText]             = useState('');
  const [processing, setProcessing] = useState(false);
  const [parsed, setParsed]         = useState(null);
  const [editData, setEditData]     = useState(null);
  const [projects, setProjects]     = useState([]);
  const [history, setHistory]       = useState(loadHistory);
  const [showExamples, setShowEx]   = useState(false);
  const textareaRef = useRef(null);

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

  // Process with AI
  const process = async () => {
    if (!text.trim()) { toast.error('Type something first'); return; }
    setProcessing(true);
    try {
      const result = await api.post('/ai/parse', { text });
      setParsed(result);
      setEditData({ ...result });
    } catch (e) {
      toast.error('AI parsing failed: ' + e.message);
    } finally { setProcessing(false); }
  };

  const changeField = (k, v) => setEditData(d => ({ ...d, [k]: v }));

  // Save to workspace
  const save = async () => {
    if (!editData) return;
    try {
      const d = editData;
      if (d.type === 'task') {
        await api.post('/tasks', {
          title: d.title, notes: d.details, category: d.category, priority: d.priority,
          assignee: d.assignee || '', due_date: d.due || '',
          is_follow_up: d.isFollowUp ? 1 : 0,
          is_today_focus: d.suggestedTodayFocus ? 1 : 0,
        });
        toast.success('Task saved!');
      } else if (d.type === 'note') {
        await api.post('/notes', { title: d.title, body: d.details });
        toast.success('Note saved!');
      } else {
        await api.post('/projects', { name: d.title, category: d.category, description: d.details, priority: d.priority });
        toast.success('Project created!');
      }
      pushHistory({ type: d.type, title: d.title, category: d.category, priority: d.priority });
      setHistory(loadHistory());
      setParsed(null); setEditData(null); setText('');
      textareaRef.current?.focus();
    } catch (e) { toast.error(e.message); }
  };

  const discard = () => { setParsed(null); setEditData(null); };

  const useExample = (ex) => { setText(ex); setShowEx(false); textareaRef.current?.focus(); };

  return (

      {/* Header */}

            <Zap size={16} className="text-white" />

Quick Note

Speak or type — AI structures it instantly

      {/* Main content — horizontal split on desktop */}

        {/* ── LEFT: Input + Parsed result ────────────────────────────── */}

          {/* Input card */}

            {/* Textarea */}
            <textarea
              ref={textareaRef}
              value={text}
              onChange={e => setText(e.target.value)}
              onKeyDown={e => { if ((e.metaKey || e.ctrlKey) && e.key === 'Enter') process(); }}
              rows={5}
              placeholder={"Type anything — task, idea, follow-up, meeting note…\n\nExample: \"Follow up with Hamid on WEF doc, urgent, this week\""}
              className="w-full px-5 pt-5 pb-3 text-sm bg-transparent border-none outline-none resize-none text-gray-900 dark:text-white placeholder-gray-300 dark:placeholder-gray-600 font-medium leading-relaxed"
            />

            {/* Toolbar */}

              {/* Voice */}
              <button
                onClick={listening ? stopVoice : startVoice}
                className={`flex items-center gap-2 px-3.5 py-2 rounded-xl text-sm font-medium transition-all ${
                  listening
                    ? 'bg-red-500 text-white animate-pulse shadow-md'
                    : 'bg-gray-100 dark:bg-gray-800 text-gray-600 dark:text-gray-400 hover:bg-gray-200 dark:hover:bg-gray-700'
                }`}
              >
                {listening ? <MicOff size={14} /> : <Mic size={14} />}
                {listening ? 'Stop' : 'Voice'}
              </button>

              {/* Examples */}
              <button
                onClick={() => setShowEx(!showExamples)}
                className="flex items-center gap-1.5 px-3 py-2 rounded-xl text-xs text-gray-500 hover:bg-gray-100 dark:hover:bg-gray-800 transition-colors"
              >
                <Lightbulb size={13} />
                Examples
              </button>

              {text.trim() && !processing && !parsed && (
                <button onClick={() => { setText(''); discard(); }} className="text-xs text-gray-400 hover:text-gray-600 dark:hover:text-gray-200 px-2 transition-colors">
                  Clear
                </button>
              )}

              <button
                onClick={process}
                disabled={!text.trim() || processing || !!parsed}
                className="btn-primary flex items-center gap-2 py-2 text-sm disabled:opacity-40"
              >
                {processing ? (
                  <> Parsing…</>
                ) : (
                  <><Sparkles size={14} /> Process ⌘↵</>
                )}
              </button>

            {/* Example suggestions */}
            {showExamples && (

Try these

                {EXAMPLES.map((ex, i) => (
                  <button key={i} onClick={() => useExample(ex)}
                    className="flex items-center gap-2 w-full text-left px-3 py-2 rounded-xl text-xs text-gray-600 dark:text-gray-400 hover:bg-white dark:hover:bg-gray-700 hover:text-gray-900 dark:hover:text-white transition-colors group">
                    <ArrowRight size={11} className="shrink-0 text-gray-400 group-hover:text-primary transition-colors" />
                    {ex}
                  </button>
                ))}

            )}

          {/* Parsed result */}
          {editData && (
            <ParsedCard
              data={editData}
              onChange={changeField}
              projects={projects}
              onSave={save}
              onDiscard={discard}
            />
          )}

          {/* Empty state when no parsed result */}
          {!editData && !processing && (

              <Sparkles size={32} className="text-gray-300 dark:text-gray-700 mb-3" />

Type or speak, then click Process

Claude will parse it into a task, note, or project

          )}

        {/* ── RIGHT: History ──────────────────────────────────────────── */}

Recent Captures

Last {history.length} items saved via Quick Note

          {history.length === 0 ? (

              <Clock size={28} className="text-gray-200 dark:text-gray-700 mb-3" />

Nothing captured yet

Your saved items will appear here

          ) : (

              {history.map((item, i) => <HistoryItem key={i} item={item} />)}

          )}

          {/* Tip box */}

Pro tip

              Press ⌘ Enter to process instantly. Mention a person's name and AI will auto-assign.

  );
}

-                onClick={listening ? stopVoice : startVoice}

-                className={`flex items-center gap-2 px-3.5 py-2 rounded-xl text-sm font-medium transition-all ${

-                    ? 'bg-red-500 text-white animate-pulse shadow-md'

-                    : 'bg-gray-100 dark:bg-gray-800 text-gray-600 dark:text-gray-400 hover:bg-gray-200 dark:hover:bg-gray-700'

-                {listening ? <MicOff size={14} /> : <Mic size={14} />}

-                {listening ? 'Stop' : 'Voice'}


Done. Removed:
- `Mic`, `MicOff` icon imports
- `listening` state
- `recognitionRef`
- `startVoice` / `stopVoice` functions
- The Voice button from the toolbar
- `disabled={listening}` on the textarea


- add a tab for the history of all the completed tasks, and applicable to click on them and review everything in the tasks card
- in Tasks tab enhance the tick box of complated task make it inside the card with green box
- People Tab, make it My team instead
- Create a new tab and call it Projects, which when i click on it i car create new projects and add from the exsiting tasks


client/src/components/ConfirmDialog.jsx
client/src/components/TaskModal.jsx
client/src/components/Projects.jsx
client/src/components/ProjectDetail.jsx
client/src/components/Search.jsx
client/src/components/TaskCard.jsx
client/src/components/RightPanel.jsx
client/src/components/Tasks.jsx
client/src/components/Notes.jsx
client/src/components/FollowUps.jsx
client/src/components/WeeklyReview.jsx
client/src/components/People.jsx
client/src/components/Sidebar.jsx
client/src/components/BottomNav.jsx
client/src/components/Today.jsx
client/src/components/Inbox.jsx


1	import { useState, useEffect, createContext, useContext } from 'react';
2	import { BrowserRouter, Routes, Route, Navigate } from 'react-router-dom';
3	import Sidebar from './components/Sidebar';
4	import BottomNav from './components/BottomNav';
5	import RightPanel from './components/RightPanel';
6	import Search from './components/Search';
7	import Today from './components/Today';
8	import Inbox from './components/Inbox';
9	import Projects from './components/Projects';
10	import ProjectDetail from './components/ProjectDetail';
11	import Tasks from './components/Tasks';
12	import Notes from './components/Notes';
13	import FollowUps from './components/FollowUps';
14	import People from './components/People';
15	import WeeklyReview from './components/WeeklyReview';
16	import { Toaster } from 'react-hot-toast';
17
18	export const AppContext = createContext({});
19	export const useApp = () => useContext(AppContext);
20
21	// Pages that show the right panel
22	const RIGHT_PANEL_ROUTES = ['/today', '/'];
23
24	export default function App() {
25	  const [darkMode, setDarkMode] = useState(() => localStorage.getItem('darkMode') === 'true');
26	  const [searchOpen, setSearchOpen] = useState(false);
27
28	  useEffect(() => {
29	    document.documentElement.classList.toggle('dark', darkMode);
30	    localStorage.setItem('darkMode', darkMode);
31	  }, [darkMode]);
32
33	  useEffect(() => {
34	    const onKey = (e) => {
35	      if ((e.metaKey || e.ctrlKey) && e.key === 'k') { e.preventDefault(); setSearchOpen(true); }
36	      if (e.key === 'Escape') setSearchOpen(false);
37	    };
38	    window.addEventListener('keydown', onKey);
39	    return () => window.removeEventListener('keydown', onKey);
40	  }, []);
41
42	  return (
43	    <AppContext.Provider value={{ darkMode, setDarkMode, setSearchOpen }}>
44	      <BrowserRouter>
45	        <div className="flex h-screen overflow-hidden bg-[#F0F2F5] dark:bg-gray-950">
46	          {/* Left sidebar */}
47	          <Sidebar onSearchOpen={() => setSearchOpen(true)} />
48
49	          {/* Main content */}
50	          <main className="flex-1 overflow-y-auto pb-20 md:pb-0 min-w-0">
51	            <Routes>
52	              <Route path="/"               element={<Navigate to="/today" replace />} />
53	              <Route path="/today"          element={<Today />} />
54	              <Route path="/inbox"          element={<Inbox />} />
55	              <Route path="/projects"       element={<Projects />} />
56	              <Route path="/projects/:id"   element={<ProjectDetail />} />
57	              <Route path="/tasks"          element={<Tasks />} />
58	              <Route path="/notes"          element={<Notes />} />
59	              <Route path="/follow-ups"     element={<FollowUps />} />
60	              <Route path="/people"         element={<People />} />
61	              <Route path="/weekly-review"  element={<WeeklyReview />} />
62	            </Routes>
63	          </main>
64
65	          {/* Right executive panel (Today only, desktop XL) */}
66	          <Routes>
67	            <Route path="/"      element={<RightPanel />} />
68	            <Route path="/today" element={<RightPanel />} />
69	          </Routes>
70
71	          {/* Mobile bottom nav */}
72	          <BottomNav />
73
74	          {/* Search overlay */}
75	          {searchOpen && <Search onClose={() => setSearchOpen(false)} />}
76	        </div>
77
78	        <Toaster
79	          position="top-right"
80	          toastOptions={{
81	            duration: 3000,
82	            style: { borderRadius: '12px', fontSize: '13px', fontFamily: 'Inter, sans-serif' },
83	          }}
84	        />
85	      </BrowserRouter>
86	    </AppContext.Provider>
87	  );
88	}
89

import { useState, useEffect, createContext, useContext } from 'react';
import { BrowserRouter, Routes, Route, Navigate } from 'react-router-dom';
import Sidebar from './components/Sidebar';
import BottomNav from './components/BottomNav';
import RightPanel from './components/RightPanel';
import Search from './components/Search';
import Today from './components/Today';
import Inbox from './components/Inbox';
import Projects from './components/Projects';
import ProjectDetail from './components/ProjectDetail';
import Tasks from './components/Tasks';
import Notes from './components/Notes';
import FollowUps from './components/FollowUps';
import People from './components/People';
import WeeklyReview from './components/WeeklyReview';
import { Toaster } from 'react-hot-toast';

export const AppContext = createContext({});
export const useApp = () => useContext(AppContext);

// Pages that show the right panel
const RIGHT_PANEL_ROUTES = ['/today', '/'];

export default function App() {
  const [darkMode, setDarkMode] = useState(() => localStorage.getItem('darkMode') === 'true');
  const [searchOpen, setSearchOpen] = useState(false);

  useEffect(() => {
    document.documentElement.classList.toggle('dark', darkMode);
    localStorage.setItem('darkMode', darkMode);
  }, [darkMode]);

  useEffect(() => {
    const onKey = (e) => {
      if ((e.metaKey || e.ctrlKey) && e.key === 'k') { e.preventDefault(); setSearchOpen(true); }
      if (e.key === 'Escape') setSearchOpen(false);
    };
    window.addEventListener('keydown', onKey);
    return () => window.removeEventListener('keydown', onKey);
  }, []);

  return (
    <AppContext.Provider value={{ darkMode, setDarkMode, setSearchOpen }}>
      <BrowserRouter>
        <div className="flex h-screen overflow-hidden bg-[#F0F2F5] dark:bg-gray-950">
          {/* Left sidebar */}
          <Sidebar onSearchOpen={() => setSearchOpen(true)} />

          {/* Main content */}
          <main className="flex-1 overflow-y-auto pb-20 md:pb-0 min-w-0">
            <Routes>
              <Route path="/"               element={<Navigate to="/today" replace />} />
              <Route path="/today"          element={<Today />} />
              <Route path="/inbox"          element={<Inbox />} />
              <Route path="/projects"       element={<Projects />} />
              <Route path="/projects/:id"   element={<ProjectDetail />} />
              <Route path="/tasks"          element={<Tasks />} />
              <Route path="/notes"          element={<Notes />} />
              <Route path="/follow-ups"     element={<FollowUps />} />
              <Route path="/people"         element={<People />} />
              <Route path="/weekly-review"  element={<WeeklyReview />} />
            </Routes>
          </main>

          {/* Right executive panel (Today only, desktop XL) */}
          <Routes>
            <Route path="/"      element={<RightPanel />} />
            <Route path="/today" element={<RightPanel />} />
          </Routes>

          {/* Mobile bottom nav */}
          <BottomNav />

          {/* Search overlay */}
          {searchOpen && <Search onClose={() => setSearchOpen(false)} />}
        </div>

        <Toaster
          position="top-right"
          toastOptions={{
            duration: 3000,
            style: { borderRadius: '12px', fontSize: '13px', fontFamily: 'Inter, sans-serif' },
          }}
        />
      </BrowserRouter>
    </AppContext.Provider>
  );
}


1	import { useState, useEffect } from 'react';
2	import { Plus, FolderKanban, ChevronRight } from 'lucide-react';
3	import { Link } from 'react-router-dom';
4	import toast from 'react-hot-toast';
5	import api from '../api';
6	import ConfirmDialog from './ConfirmDialog';
7	import { CATEGORIES, STATUSES, PRIORITIES, getCategoryClass, getPriorityClass, getStatusClass, formatDate } from '../utils';
8
9	const ALL_CATS = [{ id: '', label: 'All' }, ...Object.entries(CATEGORIES).map(([id, v]) => ({ id, label: v.label }))];
10	const ALL_STATUSES = ['All', ...STATUSES.filter(s => s !== 'Done'), 'Done'];
11
12	function ProjectModal({ project, onSave, onClose }) {
13	  const [form, setForm] = useState({
14	    name: '', category: 'ai', status: 'Open', priority: 'Medium',
15	    description: '', last_update: '', next_step: '',
16	    ...project,
17	  });
18	  const set = (k, v) => setForm(f => ({ ...f, [k]: v }));
19	  const submit = async (e) => {
20	    e.preventDefault();
21	    onSave(form);
22	  };
23	  return (
24

25

26
 e.stopPropagation()}>
27

28
{project?.id ? 'Edit Project' : 'New Project'}

29

30	        <form onSubmit={submit} className="px-5 py-4 space-y-3">
31

32	            Project Name *
33	            <input className="input" required value={form.name} onChange={e => set('name', e.target.value)} autoFocus />
34

35

36

37	              Category
38	              <select className="select" value={form.category} onChange={e => set('category', e.target.value)}>
39	                {Object.entries(CATEGORIES).map(([k, v]) => <option key={k} value={k}>{v.label}</option>)}
40	              </select>
41

42

43	              Priority
44	              <select className="select" value={form.priority} onChange={e => set('priority', e.target.value)}>
45	                {PRIORITIES.map(p => <option key={p}>{p}</option>)}
46	              </select>
47

48

49

50	            Status
51	            <select className="select" value={form.status} onChange={e => set('status', e.target.value)}>
52	              {STATUSES.map(s => <option key={s}>{s}</option>)}
53	            </select>
54

55

56	            Description
57	            <textarea className="input resize-none" rows={2} value={form.description} onChange={e => set('description', e.target.value)} />
58

59

60	            <button type="button" onClick={onClose} className="btn-secondary">Cancel</button>
61	            <button type="submit" className="btn-primary">{project?.id ? 'Save' : 'Create Project'}</button>
62

63	        </form>
64

65

66	  );
67	}
68
69	export default function Projects() {
70	  const [projects, setProjects] = useState([]);
71	  const [loading, setLoading] = useState(true);
72	  const [catFilter, setCatFilter] = useState('');
73	  const [statusFilter, setStatusFilter] = useState('All');
74	  const [editProject, setEditProject] = useState(null);
75	  const [deleteProject, setDeleteProject] = useState(null);
76	  const [showNew, setShowNew] = useState(false);
77
78	  const load = async () => {
79	    setLoading(true);
80	    const params = new URLSearchParams();
81	    if (catFilter) params.set('category', catFilter);
82	    if (statusFilter !== 'All') params.set('status', statusFilter);
83	    try {
84	      setProjects(await api.get('/projects?' + params));
85	    } finally { setLoading(false); }
86	  };
87
88	  useEffect(() => { load(); }, [catFilter, statusFilter]);
89
90	  const handleSave = async (form) => {
91	    try {
92	      if (form.id) { await api.put(`/projects/${form.id}`, form); toast.success('Project updated'); }
93	      else { await api.post('/projects', form); toast.success('Project created'); }
94	      setEditProject(null); setShowNew(false); load();
95	    } catch (e) { toast.error(e.message); }
96	  };
97
98	  const handleDelete = async () => {
99	    try {
100	      await api.delete(`/projects/${deleteProject.id}`);
101	      toast.success('Project deleted');
102	      setDeleteProject(null);
103	      load();
104	    } catch (e) { toast.error(e.message); }
105	  };
106
107	  return (
108

109	      {/* Header */}
110

111
Projects

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

116
117	      {/* Category pills */}
118

119	        {ALL_CATS.map(c => (
120	          <button
121	            key={c.id}
122	            onClick={() => setCatFilter(c.id)}
123	            className={`shrink-0 px-3 py-1.5 rounded-full text-xs font-medium transition-colors ${
124	              catFilter === c.id
125	                ? 'bg-primary text-white'
126	                : 'bg-gray-100 dark:bg-gray-800 text-gray-600 dark:text-gray-400 hover:bg-gray-200 dark:hover:bg-gray-700'
127	            }`}
128	          >
129	            {c.label}
130	          </button>
131	        ))}
132

133
134	      {/* Status filter */}
135

136	        {ALL_STATUSES.map(s => (
137	          <button
138	            key={s}
139	            onClick={() => setStatusFilter(s)}
140	            className={`px-3 py-1 rounded-lg text-xs font-medium transition-colors ${
141	              statusFilter === s
142	                ? 'bg-gray-800 dark:bg-white text-white dark:text-gray-900'
143	                : 'bg-gray-100 dark:bg-gray-800 text-gray-500 dark:text-gray-400 hover:bg-gray-200 dark:hover:bg-gray-700'
144	            }`}
145	          >
146	            {s}
147	          </button>
148	        ))}
149

150
151	      {/* Grid */}
152	      {loading ? (
153

154	          {[...Array(9)].map((_, i) =>
)}
155

156	      ) : projects.length === 0 ? (
157

158	          <FolderKanban size={40} className="text-gray-300 dark:text-gray-700 mx-auto mb-3" />
159
No projects found

160	          <button onClick={() => setShowNew(true)} className="btn-primary mt-4">Create a project</button>
161

162	      ) : (
163

164	          {projects.map(p => (
165	            <Link to={`/projects/${p.id}`} key={p.id} className="card-hover block group">
166

167	                <span className={`badge ${getCategoryClass(p.category)}`}>{CATEGORIES[p.category]?.label || p.category}
168	                <ChevronRight size={14} className="text-gray-400 mt-0.5 opacity-0 group-hover:opacity-100 transition-opacity" />
169

170
{p.name}

171

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

175

176	                {p.open_task_count || 0} open task{p.open_task_count !== 1 ? 's' : ''}
177	                {p.updated_at && {formatDate(p.updated_at)}}
178

179	            </Link>
180	          ))}
181

182	      )}
183
184	      {(showNew || editProject) && (
185	        <ProjectModal
186	          project={editProject}
187	          onSave={handleSave}
188	          onClose={() => { setShowNew(false); setEditProject(null); }}
189	        />
190	      )}
191	      {deleteProject && (
192	        <ConfirmDialog
193	          title="Delete project?"
194	          message={`"${deleteProject.name}" and its associations will be removed.`}
195	          onConfirm={handleDelete}
196	          onCancel={() => setDeleteProject(null)}
197	        />
198	      )}
199

200	  );
201	}
202

import { useState, useEffect } from 'react';
import { Plus, FolderKanban, ChevronRight } from 'lucide-react';
import { Link } from 'react-router-dom';
import toast from 'react-hot-toast';
import api from '../api';
import ConfirmDialog from './ConfirmDialog';
import { CATEGORIES, STATUSES, PRIORITIES, getCategoryClass, getPriorityClass, getStatusClass, formatDate } from '../utils';

const ALL_CATS = [{ id: '', label: 'All' }, ...Object.entries(CATEGORIES).map(([id, v]) => ({ id, label: v.label }))];
const ALL_STATUSES = ['All', ...STATUSES.filter(s => s !== 'Done'), 'Done'];

function ProjectModal({ project, onSave, onClose }) {
  const [form, setForm] = useState({
    name: '', category: 'ai', status: 'Open', priority: 'Medium',
    description: '', last_update: '', next_step: '',
    ...project,
  });
  const set = (k, v) => setForm(f => ({ ...f, [k]: v }));
  const submit = async (e) => {
    e.preventDefault();
    onSave(form);
  };
  return (

 e.stopPropagation()}>

{project?.id ? 'Edit Project' : 'New Project'}

        <form onSubmit={submit} className="px-5 py-4 space-y-3">

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

              Category
              <select className="select" value={form.category} onChange={e => set('category', e.target.value)}>
                {Object.entries(CATEGORIES).map(([k, v]) => <option key={k} value={k}>{v.label}</option>)}
              </select>

              Priority
              <select className="select" value={form.priority} onChange={e => set('priority', e.target.value)}>
                {PRIORITIES.map(p => <option key={p}>{p}</option>)}
              </select>

            Status
            <select className="select" value={form.status} onChange={e => set('status', e.target.value)}>
              {STATUSES.map(s => <option key={s}>{s}</option>)}
            </select>

            Description
            <textarea className="input resize-none" rows={2} value={form.description} onChange={e => set('description', e.target.value)} />

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

        </form>

  );
}

export default function Projects() {
  const [projects, setProjects] = useState([]);
  const [loading, setLoading] = useState(true);
  const [catFilter, setCatFilter] = useState('');
  const [statusFilter, setStatusFilter] = useState('All');
  const [editProject, setEditProject] = useState(null);
  const [deleteProject, setDeleteProject] = useState(null);
  const [showNew, setShowNew] = useState(false);

  const load = async () => {
    setLoading(true);
    const params = new URLSearchParams();
    if (catFilter) params.set('category', catFilter);
    if (statusFilter !== 'All') params.set('status', statusFilter);
    try {
      setProjects(await api.get('/projects?' + params));
    } finally { setLoading(false); }
  };

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

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

  const handleDelete = async () => {
    try {
      await api.delete(`/projects/${deleteProject.id}`);
      toast.success('Project deleted');
      setDeleteProject(null);
      load();
    } catch (e) { toast.error(e.message); }
  };

  return (

      {/* Header */}

Projects

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

      {/* Category pills */}

        {ALL_CATS.map(c => (
          <button
            key={c.id}
            onClick={() => setCatFilter(c.id)}
            className={`shrink-0 px-3 py-1.5 rounded-full text-xs font-medium transition-colors ${
              catFilter === c.id
                ? 'bg-primary text-white'
                : 'bg-gray-100 dark:bg-gray-800 text-gray-600 dark:text-gray-400 hover:bg-gray-200 dark:hover:bg-gray-700'
            }`}
          >
            {c.label}
          </button>
        ))}

      {/* Status filter */}

        {ALL_STATUSES.map(s => (
          <button
            key={s}
            onClick={() => setStatusFilter(s)}
            className={`px-3 py-1 rounded-lg text-xs font-medium transition-colors ${
              statusFilter === s
                ? 'bg-gray-800 dark:bg-white text-white dark:text-gray-900'
                : 'bg-gray-100 dark:bg-gray-800 text-gray-500 dark:text-gray-400 hover:bg-gray-200 dark:hover:bg-gray-700'
            }`}
          >
            {s}
          </button>
        ))}

      {/* Grid */}
      {loading ? (

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

      ) : projects.length === 0 ? (

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

No projects found

          <button onClick={() => setShowNew(true)} className="btn-primary mt-4">Create a project</button>

      ) : (

          {projects.map(p => (
            <Link to={`/projects/${p.id}`} key={p.id} className="card-hover block group">

                <span className={`badge ${getCategoryClass(p.category)}`}>{CATEGORIES[p.category]?.label || p.category}
                <ChevronRight size={14} className="text-gray-400 mt-0.5 opacity-0 group-hover:opacity-100 transition-opacity" />

{p.name}

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

                {p.open_task_count || 0} open task{p.open_task_count !== 1 ? 's' : ''}
                {p.updated_at && {formatDate(p.updated_at)}}

            </Link>
          ))}

      )}

      {(showNew || editProject) && (
        <ProjectModal
          project={editProject}
          onSave={handleSave}
          onClose={() => { setShowNew(false); setEditProject(null); }}
        />
      )}
      {deleteProject && (
        <ConfirmDialog
          title="Delete project?"
          message={`"${deleteProject.name}" and its associations will be removed.`}
          onConfirm={handleDelete}
          onCancel={() => setDeleteProject(null)}
        />
      )}

  );
}


1	import { useState, useEffect, useRef } from 'react';
2	import { Plus, Search, SlidersHorizontal, CheckSquare, X } from 'lucide-react';
3	import toast from 'react-hot-toast';
4	import api from '../api';
5	import TaskCard from './TaskCard';
6	import TaskModal from './TaskModal';
7	import ConfirmDialog from './ConfirmDialog';
8	import { CATEGORIES, STATUSES, PRIORITIES } from '../utils';
9
10	function useDebounce(v, d = 300) {
11	  const [dv, setDv] = useState(v);
12	  useEffect(() => { const t = setTimeout(() => setDv(v), d); return () => clearTimeout(t); }, [v, d]);
13	  return dv;
14	}
15
16	// Status columns for horizontal kanban-style layout
17	const STATUS_COLS = [
18	  { id: '',          label: 'All',         color: 'bg-gray-100 text-gray-600 dark:bg-gray-800 dark:text-gray-400' },
19	  { id: 'Open',      label: 'Open',        color: 'bg-gray-100 text-gray-600 dark:bg-gray-800 dark:text-gray-400' },
20	  { id: 'In Progress',label:'In Progress', color: 'bg-blue-100 text-blue-700 dark:bg-blue-900/40 dark:text-blue-300' },
21	  { id: 'Waiting',   label: 'Waiting',     color: 'bg-amber-100 text-amber-700 dark:bg-amber-900/40 dark:text-amber-300' },
22	  { id: 'Done',      label: 'Done',        color: 'bg-green-100 text-green-700 dark:bg-green-900/40 dark:text-green-300' },
23	];
24
25	export default function Tasks() {
26	  const [tasks, setTasks]           = useState([]);
27	  const [projects, setProjects]     = useState([]);
28	  const [loading, setLoading]       = useState(true);
29	  const [search, setSearch]         = useState('');
30	  const [statusFilter, setStatus]   = useState('');
31	  const [catFilter, setCat]         = useState('');
32	  const [priorityFilter, setPri]    = useState('');
33	  const [showFilters, setShowFilters] = useState(false);
34	  const [editTask, setEditTask]     = useState(null);
35	  const [deleteTask, setDeleteTask] = useState(null);
36	  const [showNew, setShowNew]       = useState(false);
37	  const [selected, setSelected]     = useState(new Set());
38	  const [viewMode, setViewMode]     = useState('grid'); // 'grid' | 'kanban'
39	  const debouncedSearch = useDebounce(search);
40
41	  const load = async () => {
42	    setLoading(true);
43	    const p = new URLSearchParams();
44	    if (debouncedSearch)  p.set('search',   debouncedSearch);
45	    if (statusFilter)     p.set('status',   statusFilter);
46	    if (catFilter)        p.set('category', catFilter);
47	    if (priorityFilter)   p.set('priority', priorityFilter);
48	    try {
49	      const [t, pr] = await Promise.all([api.get('/tasks?' + p), api.get('/projects')]);
50	      setTasks(t); setProjects(pr);
51	    } finally { setLoading(false); }
52	  };
53
54	  useEffect(() => { load(); }, [debouncedSearch, statusFilter, catFilter, priorityFilter]);
55
56	  const handleSave = async (form) => {
57	    try {
58	      if (editTask?.id) { await api.put(`/tasks/${editTask.id}`, form); toast.success('Task updated'); }
59	      else              { await api.post('/tasks', form);               toast.success('Task added');   }
60	      setEditTask(null); setShowNew(false); load();
61	    } catch (e) { toast.error(e.message); }
62	  };
63
64	  const handleDelete = async () => {
65	    await api.delete(`/tasks/${deleteTask.id}`); toast.success('Deleted');
66	    setDeleteTask(null); load();
67	  };
68
69	  const toggleFocus = async (task) => { await api.patch(`/tasks/${task.id}/focus`); load(); };
70
71	  const toggleSelect = (id) => setSelected(s => {
72	    const n = new Set(s); n.has(id) ? n.delete(id) : n.add(id); return n;
73	  });
74
75	  const bulkDone = async () => {
76	    await Promise.all([...selected].map(id => api.patch(`/tasks/${id}/status`, { status: 'Done' })));
77	    toast.success(`${selected.size} tasks done`);
78	    setSelected(new Set()); load();
79	  };
80
81	  const hasFilters = statusFilter || catFilter || priorityFilter;
82
83	  // Kanban grouping by status
84	  const KANBAN_COLS = ['Open', 'In Progress', 'Waiting', 'Done'];
85	  const byStatus = KANBAN_COLS.reduce((acc, s) => {
86	    acc[s] = tasks.filter(t => t.status === s);
87	    return acc;
88	  }, {});
89
90	  return (
91

92
93	      {/* Header */}
94

95

96
Tasks

97

98	            {/* View toggle */}
99

100	              {[{ id:'grid', label:'Grid' }, { id:'kanban', label:'Board' }].map(v => (
101	                <button key={v.id} onClick={() => setViewMode(v.id)}
102	                  className={`px-3 py-1.5 rounded-lg text-xs font-medium transition-all ${
103	                    viewMode === v.id ? 'bg-white dark:bg-gray-700 text-gray-900 dark:text-white shadow-sm' : 'text-gray-500'
104	                  }`}>
105	                  {v.label}
106	                </button>
107	              ))}
108

109	            <button onClick={() => setShowNew(true)} className="btn-primary flex items-center gap-1.5 py-2">
110	              <Plus size={14} /> New Task
111	            </button>
112

113

114
115	        {/* Search + filter row */}
116

117

118	            <Search size={13} className="absolute left-3 top-1/2 -translate-y-1/2 text-gray-400" />
119	            <input className="input pl-8 py-2 text-sm" placeholder="Search tasks…" value={search} onChange={e => setSearch(e.target.value)} />
120

121	          <button onClick={() => setShowFilters(!showFilters)}
122	            className={`btn-secondary flex items-center gap-1.5 py-2 text-sm ${hasFilters ? 'border-primary text-primary' : ''}`}>
123	            <SlidersHorizontal size={13} />
124	            {hasFilters ? 'Filtered' : 'Filter'}
125	          </button>
126

127
128	        {/* Filter chips */}
129	        {showFilters && (
130

131	            <select className="select text-xs py-1.5 w-auto" value={statusFilter} onChange={e => setStatus(e.target.value)}>
132	              <option value="">All Status</option>
133	              {STATUSES.map(s => <option key={s}>{s}</option>)}
134	            </select>
135	            <select className="select text-xs py-1.5 w-auto" value={catFilter} onChange={e => setCat(e.target.value)}>
136	              <option value="">All Categories</option>
137	              {Object.entries(CATEGORIES).map(([k, v]) => <option key={k} value={k}>{v.label}</option>)}
138	            </select>
139	            <select className="select text-xs py-1.5 w-auto" value={priorityFilter} onChange={e => setPri(e.target.value)}>
140	              <option value="">All Priorities</option>
141	              {PRIORITIES.map(p => <option key={p}>{p}</option>)}
142	            </select>
143	            {hasFilters && (
144	              <button onClick={() => { setStatus(''); setCat(''); setPri(''); }}
145	                className="flex items-center gap-1 text-xs text-primary hover:underline px-2">
146	                <X size={11} /> Clear
147	              </button>
148	            )}
149

150	        )}
151
152	        {/* Bulk actions */}
153	        {selected.size > 0 && (
154

155	            {selected.size} selected
156	            <button onClick={bulkDone} className="btn-primary py-1 text-xs flex items-center gap-1">
157	              <CheckSquare size={11} /> Mark done
158	            </button>
159	            <button onClick={() => setSelected(new Set())} className="btn-ghost text-xs">Clear</button>
160

161	        )}
162

163
164	      {/* Content area */}
165	      {loading ? (
166

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

169	      ) : tasks.length === 0 ? (
170

171	          <CheckSquare size={40} className="text-gray-200 dark:text-gray-700" />
172
No tasks found

173	          {(search || hasFilters) && (
174	            <button onClick={() => { setSearch(''); setStatus(''); setCat(''); setPri(''); }} className="btn-secondary text-sm">
175	              Clear filters
176	            </button>
177	          )}
178

179	      ) : viewMode === 'grid' ? (
180	        /* ── GRID view ─────────────────────────────────── */
181

182
{tasks.length} task{tasks.length !== 1 ? 's' : ''}

183

184	            {tasks.map(task => (
185

186	                <input type="checkbox" className="absolute top-3 left-3 z-10 w-3.5 h-3.5 accent-primary cursor-pointer rounded"
187	                  checked={selected.has(task.id)} onChange={() => toggleSelect(task.id)} />
188

189	                  <TaskCard task={task} onEdit={setEditTask} onDelete={setDeleteTask} onToggleFocus={toggleFocus} />
190

191

192	            ))}
193

194

195	      ) : (
196	        /* ── KANBAN / BOARD view ────────────────────────── */
197

199	          {KANBAN_COLS.map(status => {
200	            const col = STATUS_COLS.find(c => c.id === status);
201	            const colTasks = byStatus[status] || [];
202	            return (
203

205

206

207
{status}

208	                    <span className={`badge text-[10px] py-0.5 ${col?.color}`}>{colTasks.length}
209

210	                  <button onClick={() => setShowNew(true)}
211	                    className="w-6 h-6 rounded-lg bg-white dark:bg-gray-700 border border-gray-200 dark:border-gray-600 flex items-center justify-center text-gray-400 hover:text-primary hover:border-primary transition-all">
212	                    <Plus size={12} />
213	                  </button>
214

215

216	                  {colTasks.map(task => (
217	                    <TaskCard key={task.id} task={task} onEdit={setEditTask} onDelete={setDeleteTask} onToggleFocus={toggleFocus} />
218	                  ))}
219	                  {colTasks.length === 0 && (
220

221
No tasks

222

223	                  )}
224

225

226	            );
227	          })}
228

229	      )}
230
231	      {(showNew || editTask) && (
232	        <TaskModal task={editTask} projects={projects} onSave={handleSave} onClose={() => { setShowNew(false); setEditTask(null); }} />
233	      )}
234	      {deleteTask && (
235	        <ConfirmDialog title="Delete task?" message={`"${deleteTask.title}"`} onConfirm={handleDelete} onCancel={() => setDeleteTask(null)} />
236	      )}
237

238	  );
239	}
240

import { useState, useEffect, useRef } from 'react';
import { Plus, Search, SlidersHorizontal, CheckSquare, X } from 'lucide-react';
import toast from 'react-hot-toast';
import api from '../api';
import TaskCard from './TaskCard';
import TaskModal from './TaskModal';
import ConfirmDialog from './ConfirmDialog';
import { CATEGORIES, STATUSES, PRIORITIES } from '../utils';

function useDebounce(v, d = 300) {
  const [dv, setDv] = useState(v);
  useEffect(() => { const t = setTimeout(() => setDv(v), d); return () => clearTimeout(t); }, [v, d]);
  return dv;
}

// Status columns for horizontal kanban-style layout
const STATUS_COLS = [
  { id: '',          label: 'All',         color: 'bg-gray-100 text-gray-600 dark:bg-gray-800 dark:text-gray-400' },
  { id: 'Open',      label: 'Open',        color: 'bg-gray-100 text-gray-600 dark:bg-gray-800 dark:text-gray-400' },
  { id: 'In Progress',label:'In Progress', color: 'bg-blue-100 text-blue-700 dark:bg-blue-900/40 dark:text-blue-300' },
  { id: 'Waiting',   label: 'Waiting',     color: 'bg-amber-100 text-amber-700 dark:bg-amber-900/40 dark:text-amber-300' },
  { id: 'Done',      label: 'Done',        color: 'bg-green-100 text-green-700 dark:bg-green-900/40 dark:text-green-300' },
];

export default function Tasks() {
  const [tasks, setTasks]           = useState([]);
  const [projects, setProjects]     = useState([]);
  const [loading, setLoading]       = useState(true);
  const [search, setSearch]         = useState('');
  const [statusFilter, setStatus]   = useState('');
  const [catFilter, setCat]         = useState('');
  const [priorityFilter, setPri]    = useState('');
  const [showFilters, setShowFilters] = useState(false);
  const [editTask, setEditTask]     = useState(null);
  const [deleteTask, setDeleteTask] = useState(null);
  const [showNew, setShowNew]       = useState(false);
  const [selected, setSelected]     = useState(new Set());
  const [viewMode, setViewMode]     = useState('grid'); // 'grid' | 'kanban'
  const debouncedSearch = useDebounce(search);

  const load = async () => {
    setLoading(true);
    const p = new URLSearchParams();
    if (debouncedSearch)  p.set('search',   debouncedSearch);
    if (statusFilter)     p.set('status',   statusFilter);
    if (catFilter)        p.set('category', catFilter);
    if (priorityFilter)   p.set('priority', priorityFilter);
    try {
      const [t, pr] = await Promise.all([api.get('/tasks?' + p), api.get('/projects')]);
      setTasks(t); setProjects(pr);
    } finally { setLoading(false); }
  };

  useEffect(() => { load(); }, [debouncedSearch, statusFilter, catFilter, priorityFilter]);

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

  const handleDelete = async () => {
    await api.delete(`/tasks/${deleteTask.id}`); toast.success('Deleted');
    setDeleteTask(null); load();
  };

  const toggleFocus = async (task) => { await api.patch(`/tasks/${task.id}/focus`); load(); };

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

  const bulkDone = async () => {
    await Promise.all([...selected].map(id => api.patch(`/tasks/${id}/status`, { status: 'Done' })));
    toast.success(`${selected.size} tasks done`);
    setSelected(new Set()); load();
  };

  const hasFilters = statusFilter || catFilter || priorityFilter;

  // Kanban grouping by status
  const KANBAN_COLS = ['Open', 'In Progress', 'Waiting', 'Done'];
  const byStatus = KANBAN_COLS.reduce((acc, s) => {
    acc[s] = tasks.filter(t => t.status === s);
    return acc;
  }, {});

  return (

      {/* Header */}

Tasks

            {/* View toggle */}

              {[{ id:'grid', label:'Grid' }, { id:'kanban', label:'Board' }].map(v => (
                <button key={v.id} onClick={() => setViewMode(v.id)}
                  className={`px-3 py-1.5 rounded-lg text-xs font-medium transition-all ${
                    viewMode === v.id ? 'bg-white dark:bg-gray-700 text-gray-900 dark:text-white shadow-sm' : 'text-gray-500'
                  }`}>
                  {v.label}
                </button>
              ))}

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

        {/* Search + filter row */}

            <Search size={13} className="absolute left-3 top-1/2 -translate-y-1/2 text-gray-400" />
            <input className="input pl-8 py-2 text-sm" placeholder="Search tasks…" value={search} onChange={e => setSearch(e.target.value)} />

          <button onClick={() => setShowFilters(!showFilters)}
            className={`btn-secondary flex items-center gap-1.5 py-2 text-sm ${hasFilters ? 'border-primary text-primary' : ''}`}>
            <SlidersHorizontal size={13} />
            {hasFilters ? 'Filtered' : 'Filter'}
          </button>

        {/* Filter chips */}
        {showFilters && (

            <select className="select text-xs py-1.5 w-auto" value={statusFilter} onChange={e => setStatus(e.target.value)}>
              <option value="">All Status</option>
              {STATUSES.map(s => <option key={s}>{s}</option>)}
            </select>
            <select className="select text-xs py-1.5 w-auto" value={catFilter} onChange={e => setCat(e.target.value)}>
              <option value="">All Categories</option>
              {Object.entries(CATEGORIES).map(([k, v]) => <option key={k} value={k}>{v.label}</option>)}
            </select>
            <select className="select text-xs py-1.5 w-auto" value={priorityFilter} onChange={e => setPri(e.target.value)}>
              <option value="">All Priorities</option>
              {PRIORITIES.map(p => <option key={p}>{p}</option>)}
            </select>
            {hasFilters && (
              <button onClick={() => { setStatus(''); setCat(''); setPri(''); }}
                className="flex items-center gap-1 text-xs text-primary hover:underline px-2">
                <X size={11} /> Clear
              </button>
            )}

        )}

        {/* Bulk actions */}
        {selected.size > 0 && (

            {selected.size} selected
            <button onClick={bulkDone} className="btn-primary py-1 text-xs flex items-center gap-1">
              <CheckSquare size={11} /> Mark done
            </button>
            <button onClick={() => setSelected(new Set())} className="btn-ghost text-xs">Clear</button>

        )}

      {/* Content area */}
      {loading ? (

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

      ) : tasks.length === 0 ? (

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

No tasks found

          {(search || hasFilters) && (
            <button onClick={() => { setSearch(''); setStatus(''); setCat(''); setPri(''); }} className="btn-secondary text-sm">
              Clear filters
            </button>
          )}

      ) : viewMode === 'grid' ? (
        /* ── GRID view ─────────────────────────────────── */

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

            {tasks.map(task => (

                <input type="checkbox" className="absolute top-3 left-3 z-10 w-3.5 h-3.5 accent-primary cursor-pointer rounded"
                  checked={selected.has(task.id)} onChange={() => toggleSelect(task.id)} />

                  <TaskCard task={task} onEdit={setEditTask} onDelete={setDeleteTask} onToggleFocus={toggleFocus} />

            ))}

      ) : (
        /* ── KANBAN / BOARD view ────────────────────────── */

          {KANBAN_COLS.map(status => {
            const col = STATUS_COLS.find(c => c.id === status);
            const colTasks = byStatus[status] || [];
            return (

{status}

                    <span className={`badge text-[10px] py-0.5 ${col?.color}`}>{colTasks.length}

                  <button onClick={() => setShowNew(true)}
                    className="w-6 h-6 rounded-lg bg-white dark:bg-gray-700 border border-gray-200 dark:border-gray-600 flex items-center justify-center text-gray-400 hover:text-primary hover:border-primary transition-all">
                    <Plus size={12} />
                  </button>

                  {colTasks.map(task => (
                    <TaskCard key={task.id} task={task} onEdit={setEditTask} onDelete={setDeleteTask} onToggleFocus={toggleFocus} />
                  ))}
                  {colTasks.length === 0 && (

No tasks

                  )}

            );
          })}

      )}

      {(showNew || editTask) && (
        <TaskModal task={editTask} projects={projects} onSave={handleSave} onClose={() => { setShowNew(false); setEditTask(null); }} />
      )}
      {deleteTask && (
        <ConfirmDialog title="Delete task?" message={`"${deleteTask.title}"`} onConfirm={handleDelete} onCancel={() => setDeleteTask(null)} />
      )}

  );
}


Let me also read TaskCard and ProjectDetail:


1	import { Pencil, Trash2, User, Calendar, AlertTriangle } from 'lucide-react';
2	import { getCategoryClass, getPriorityClass, getStatusClass, isOverdue, formatDate, CATEGORIES } from '../utils';
3
4	const PRIORITY_BORDER = {
5	  Urgent: 'border-l-red-500',
6	  High:   'border-l-amber-400',
7	  Medium: 'border-l-blue-400',
8	  Low:    'border-l-green-400',
9	};
10
11	export default function TaskCard({ task, onEdit, onDelete, onToggleFocus, showFocus = true, compact = false }) {
12	  const overdue = isOverdue(task.due_date) && task.status !== 'Done';
13
14	  return (
15	    <div className={`kanban-card border-l-4 ${PRIORITY_BORDER[task.priority] || 'border-l-gray-200'} group ${task.status === 'Done' ? 'opacity-50' : ''}`}>
16

17	        {/* Focus toggle */}
18	        {showFocus && (
19	          <button
20	            onClick={() => onToggleFocus?.(task)}
21	            title={task.is_today_focus ? 'Remove from today' : 'Pin to today'}
22	            className="shrink-0 mt-0.5"
23	          >
24

29	          </button>
30	        )}
31
32

33	          {/* Title */}
34

39	            {task.title}
40

41
42	          {/* Notes */}
43	          {task.notes && !compact && (
44
{task.notes}

45	          )}
46
47	          {/* Badges */}
48

49	            <span className={`badge text-[10px] py-0.5 ${getPriorityClass(task.priority)}`}>{task.priority}
50	            <span className={`badge text-[10px] py-0.5 ${getCategoryClass(task.category)}`}>
51	              {CATEGORIES[task.category]?.label?.split(' ')[0] || task.category}
52
53	            <span className={`badge text-[10px] py-0.5 ${getStatusClass(task.status)}`}>{task.status}
54	            {task.is_follow_up === 1 && (
55	              Follow-up
56	            )}
57

58
59	          {/* Meta */}
60	          {(task.assignee || task.due_date || task.project_name) && (
61

62	              {task.assignee && (
63

64

65	                    {task.assignee[0]?.toUpperCase()}
66

67	                  {task.assignee}
68

69	              )}
70	              {task.due_date && (
71	                <span className={`flex items-center gap-1 text-xs ${overdue ? 'text-red-600 dark:text-red-400 font-medium' : 'text-gray-400'}`}>
72	                  <Calendar size={10} />
73	                  {overdue && <AlertTriangle size={10} className="text-red-500 shrink-0" />}{formatDate(task.due_date)}
74
75	              )}
76	              {task.project_name && (
77	                · {task.project_name}
78	              )}
79

80	          )}
81

82
83	        {/* Actions */}
84

85	          {onEdit && (
86	            <button onClick={() => onEdit(task)} className="p-1.5 rounded-lg hover:bg-gray-100 dark:hover:bg-gray-800 text-gray-400 hover:text-primary transition-colors">
87	              <Pencil size={12} />
88	            </button>
89	          )}
90	          {onDelete && (
91	            <button onClick={() => onDelete(task)} className="p-1.5 rounded-lg hover:bg-red-50 dark:hover:bg-red-900/30 text-gray-400 hover:text-red-500 transition-colors">
92	              <Trash2 size={12} />
93	            </button>
94	          )}
95

96

97

98	  );
99	}
100

import { Pencil, Trash2, User, Calendar, AlertTriangle } from 'lucide-react';
import { getCategoryClass, getPriorityClass, getStatusClass, isOverdue, formatDate, CATEGORIES } from '../utils';

const PRIORITY_BORDER = {
  Urgent: 'border-l-red-500',
  High:   'border-l-amber-400',
  Medium: 'border-l-blue-400',
  Low:    'border-l-green-400',
};

export default function TaskCard({ task, onEdit, onDelete, onToggleFocus, showFocus = true, compact = false }) {
  const overdue = isOverdue(task.due_date) && task.status !== 'Done';

  return (
    <div className={`kanban-card border-l-4 ${PRIORITY_BORDER[task.priority] || 'border-l-gray-200'} group ${task.status === 'Done' ? 'opacity-50' : ''}`}>

        {/* Focus toggle */}
        {showFocus && (
          <button
            onClick={() => onToggleFocus?.(task)}
            title={task.is_today_focus ? 'Remove from today' : 'Pin to today'}
            className="shrink-0 mt-0.5"
          >

          </button>
        )}

          {/* Title */}

            {task.title}

          {/* Notes */}
          {task.notes && !compact && (

{task.notes}

          )}

          {/* Badges */}

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

            <span className={`badge text-[10px] py-0.5 ${getStatusClass(task.status)}`}>{task.status}
            {task.is_follow_up === 1 && (
              Follow-up
            )}

          {/* Meta */}
          {(task.assignee || task.due_date || task.project_name) && (

              {task.assignee && (

                    {task.assignee[0]?.toUpperCase()}

                  {task.assignee}

              )}
              {task.due_date && (
                <span className={`flex items-center gap-1 text-xs ${overdue ? 'text-red-600 dark:text-red-400 font-medium' : 'text-gray-400'}`}>
                  <Calendar size={10} />
                  {overdue && <AlertTriangle size={10} className="text-red-500 shrink-0" />}{formatDate(task.due_date)}

              )}
              {task.project_name && (
                · {task.project_name}
              )}

          )}

        {/* Actions */}

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

  );
}


1	import { useState, useEffect } from 'react';
2	import { useParams, useNavigate } from 'react-router-dom';
3	import { ArrowLeft, Pencil, Check, Plus, Trash2, FileText, CheckSquare } from 'lucide-react';
4	import toast from 'react-hot-toast';
5	import api from '../api';
6	import TaskCard from './TaskCard';
7	import TaskModal from './TaskModal';
8	import ConfirmDialog from './ConfirmDialog';
9	import { CATEGORIES, STATUSES, PRIORITIES, getCategoryClass, getPriorityClass, getStatusClass } from '../utils';
10
11	function InlineEdit({ value, onSave, multiline = false, placeholder = '' }) {
12	  const [editing, setEditing] = useState(false);
13	  const [val, setVal] = useState(value || '');
14	  useEffect(() => setVal(value || ''), [value]);
15	  const save = () => { setEditing(false); if (val !== value) onSave(val); };
16	  if (!editing) return (
17	     setEditing(true)}
19	      className="group cursor-text text-gray-700 dark:text-gray-300 hover:text-gray-900 dark:hover:text-white flex items-center gap-1"
20	    >
21	      {val || placeholder}
22	      <Pencil size={11} className="opacity-0 group-hover:opacity-50 shrink-0" />
23
24	  );
25	  return multiline
26	    ? <textarea autoFocus className="input w-full resize-none" rows={3} value={val} onChange={e => setVal(e.target.value)} onBlur={save} />
27	    : <input autoFocus className="input w-full" value={val} onChange={e => setVal(e.target.value)} onBlur={save} onKeyDown={e => e.key === 'Enter' && save()} />;
28	}
29
30	function NoteForm({ projectId, onSave, onCancel }) {
31	  const [title, setTitle] = useState('');
32	  const [body, setBody] = useState('');
33	  const submit = async (e) => {
34	    e.preventDefault();
35	    if (!title.trim()) return;
36	    await onSave({ title, body, project_id: projectId, date: new Date().toISOString().split('T')[0] });
37	    setTitle(''); setBody('');
38	  };
39	  return (
40	    <form onSubmit={submit} className="card border-dashed space-y-2">
41	      <input className="input" placeholder="Note title" value={title} onChange={e => setTitle(e.target.value)} autoFocus required />
42	      <textarea className="input resize-none" rows={3} placeholder="Note body…" value={body} onChange={e => setBody(e.target.value)} />
43

44	        <button type="button" onClick={onCancel} className="btn-secondary text-xs py-1 px-3">Cancel</button>
45	        <button type="submit" className="btn-primary text-xs py-1 px-3">Save Note</button>
46

47	    </form>
48	  );
49	}
50
51	export default function ProjectDetail() {
52	  const { id } = useParams();
53	  const navigate = useNavigate();
54	  const [project, setProject] = useState(null);
55	  const [tasks, setTasks] = useState([]);
56	  const [notes, setNotes] = useState([]);
57	  const [projects, setProjects] = useState([]);
58	  const [tab, setTab] = useState('tasks');
59	  const [loading, setLoading] = useState(true);
60	  const [editTask, setEditTask] = useState(null);
61	  const [deleteTask, setDeleteTask] = useState(null);
62	  const [deleteNote, setDeleteNote] = useState(null);
63	  const [showNewTask, setShowNewTask] = useState(false);
64	  const [showNewNote, setShowNewNote] = useState(false);
65
66	  const load = async () => {
67	    try {
68	      const [p, t, n, pr] = await Promise.all([
69	        api.get(`/projects/${id}`),
70	        api.get(`/tasks?project_id=${id}`),
71	        api.get(`/notes?project_id=${id}`),
72	        api.get('/projects'),
73	      ]);
74	      setProject(p); setTasks(t); setNotes(n); setProjects(pr);
75	    } catch { navigate('/projects'); }
76	    finally { setLoading(false); }
77	  };
78
79	  useEffect(() => { load(); }, [id]);
80
81	  const updateProject = async (field, value) => {
82	    const updated = { ...project, [field]: value };
83	    setProject(updated);
84	    await api.put(`/projects/${id}`, updated);
85	  };
86
87	  const handleSaveTask = async (form) => {
88	    try {
89	      if (editTask?.id) { await api.put(`/tasks/${editTask.id}`, { ...form, project_id: id }); toast.success('Task updated'); }
90	      else { await api.post('/tasks', { ...form, project_id: id }); toast.success('Task added'); }
91	      setEditTask(null); setShowNewTask(false); load();
92	    } catch (e) { toast.error(e.message); }
93	  };
94
95	  const handleSaveNote = async (form) => {
96	    try {
97	      await api.post('/notes', form); toast.success('Note saved');
98	      setShowNewNote(false); load();
99	    } catch (e) { toast.error(e.message); }
100	  };
101
102	  const handleDeleteTask = async () => {
103	    await api.delete(`/tasks/${deleteTask.id}`); toast.success('Task deleted');
104	    setDeleteTask(null); load();
105	  };
106
107	  const handleDeleteNote = async () => {
108	    await api.delete(`/notes/${deleteNote.id}`); toast.success('Note deleted');
109	    setDeleteNote(null); load();
110	  };
111
112	  const toggleFocus = async (task) => { await api.patch(`/tasks/${task.id}/focus`); load(); };
113
114	  if (loading) return (
115

116

117

118

119

120	  );
121
122	  if (!project) return null;
123
124	  return (
125

126	      {/* Back */}
127	      <button onClick={() => navigate('/projects')} className="flex items-center gap-1.5 text-sm text-gray-500 hover:text-gray-800 dark:hover:text-white mb-4 transition-colors">
128	        <ArrowLeft size={15} /> Projects
129	      </button>
130
131	      {/* Project header */}
132

133

134	          <select
135	            className="badge cursor-pointer border-0 outline-none bg-transparent p-0"
136	            style={{ minWidth: 0 }}
137	            value={project.category}
138	            onChange={e => updateProject('category', e.target.value)}
139	          >
140	            {Object.entries(CATEGORIES).map(([k, v]) => <option key={k} value={k}>{v.label}</option>)}
141	          </select>
142	          <select
143	            className={`badge cursor-pointer border-0 outline-none bg-transparent p-0 ${getPriorityClass(project.priority)}`}
144	            value={project.priority}
145	            onChange={e => updateProject('priority', e.target.value)}
146	          >
147	            {PRIORITIES.map(p => <option key={p}>{p}</option>)}
148	          </select>
149	          <select
150	            className={`badge cursor-pointer border-0 outline-none bg-transparent p-0 ${getStatusClass(project.status)}`}
151	            value={project.status}
152	            onChange={e => updateProject('status', e.target.value)}
153	          >
154	            {STATUSES.map(s => <option key={s}>{s}</option>)}
155	          </select>
156

157
158

159	          <InlineEdit value={project.name} onSave={v => updateProject('name', v)} placeholder="Project name" />
160

161
162

163

164
Description

165	            <InlineEdit value={project.description} onSave={v => updateProject('description', v)} multiline placeholder="Add description…" />
166

167

168
Last Update

169	            <InlineEdit value={project.last_update} onSave={v => updateProject('last_update', v)} placeholder="What happened last?" />
170

171

172
Next Step

173	            <InlineEdit value={project.next_step} onSave={v => updateProject('next_step', v)} placeholder="What needs to happen next?" />
174

175

176

177
178	      {/* Tabs */}
179

180	        <button
181	          onClick={() => setTab('tasks')}
182	          className={`flex-1 flex items-center justify-center gap-1.5 py-2 px-3 rounded-md text-sm font-medium transition-colors ${
183	            tab === 'tasks' ? 'bg-white dark:bg-gray-900 text-gray-900 dark:text-white shadow-sm' : 'text-gray-500 hover:text-gray-700 dark:hover:text-gray-300'
184	          }`}
185	        >
186	          <CheckSquare size={14} /> Tasks ({tasks.filter(t => t.status !== 'Done').length})
187	        </button>
188	        <button
189	          onClick={() => setTab('notes')}
190	          className={`flex-1 flex items-center justify-center gap-1.5 py-2 px-3 rounded-md text-sm font-medium transition-colors ${
191	            tab === 'notes' ? 'bg-white dark:bg-gray-900 text-gray-900 dark:text-white shadow-sm' : 'text-gray-500 hover:text-gray-700 dark:hover:text-gray-300'
192	          }`}
193	        >
194	          <FileText size={14} /> Notes ({notes.length})
195	        </button>
196

197
198	      {/* Tab content */}
199	      {tab === 'tasks' && (
200

201

202	            <button onClick={() => setShowNewTask(true)} className="btn-secondary flex items-center gap-1.5 text-xs">
203	              <Plus size={13} /> Add Task
204	            </button>
205

206	          {tasks.length === 0
207	            ?
No tasks yet. Add one above.

208	            : tasks.map(t => (
209	                <TaskCard key={t.id} task={t} onEdit={setEditTask} onDelete={setDeleteTask} onToggleFocus={toggleFocus} />
210	              ))
211	          }
212

213	      )}
214
215	      {tab === 'notes' && (
216

217

218	            <button onClick={() => setShowNewNote(!showNewNote)} className="btn-secondary flex items-center gap-1.5 text-xs">
219	              <Plus size={13} /> Add Note
220	            </button>
221

222	          {showNewNote && <NoteForm projectId={id} onSave={handleSaveNote} onCancel={() => setShowNewNote(false)} />}
223	          {notes.length === 0 && !showNewNote
224	            ?
No notes yet.

225	            : notes.map(n => (
226

227

228

229
{n.title}

230
{n.date}

231	                      {n.body &&
{n.body}
}
232

233	                    <button onClick={() => setDeleteNote(n)} className="p-1.5 rounded hover:bg-red-50 text-gray-400 hover:text-red-500 opacity-0 group-hover:opacity-100 transition-all ml-2">
234	                      <Trash2 size={13} />
235	                    </button>
236

237

238	              ))
239	          }
240

241	      )}
242
243	      {/* Modals */}
244	      {(showNewTask || editTask) && (
245	        <TaskModal task={editTask} projects={projects} onSave={handleSaveTask} onClose={() => { setShowNewTask(false); setEditTask(null); }} />
246	      )}
247	      {deleteTask && <ConfirmDialog title="Delete task?" message={`"${deleteTask.title}"`} onConfirm={handleDeleteTask} onCancel={() => setDeleteTask(null)} />}
248	      {deleteNote && <ConfirmDialog title="Delete note?" message={`"${deleteNote.title}"`} onConfirm={handleDeleteNote} onCancel={() => setDeleteNote(null)} />}
249

250	  );
251	}
252

import { useState, useEffect } from 'react';
import { useParams, useNavigate } from 'react-router-dom';
import { ArrowLeft, Pencil, Check, Plus, Trash2, FileText, CheckSquare } from 'lucide-react';
import toast from 'react-hot-toast';
import api from '../api';
import TaskCard from './TaskCard';
import TaskModal from './TaskModal';
import ConfirmDialog from './ConfirmDialog';
import { CATEGORIES, STATUSES, PRIORITIES, getCategoryClass, getPriorityClass, getStatusClass } from '../utils';

function InlineEdit({ value, onSave, multiline = false, placeholder = '' }) {
  const [editing, setEditing] = useState(false);
  const [val, setVal] = useState(value || '');
  useEffect(() => setVal(value || ''), [value]);
  const save = () => { setEditing(false); if (val !== value) onSave(val); };
  if (!editing) return (
     setEditing(true)}
      className="group cursor-text text-gray-700 dark:text-gray-300 hover:text-gray-900 dark:hover:text-white flex items-center gap-1"
    >
      {val || placeholder}
      <Pencil size={11} className="opacity-0 group-hover:opacity-50 shrink-0" />

  );
  return multiline
    ? <textarea autoFocus className="input w-full resize-none" rows={3} value={val} onChange={e => setVal(e.target.value)} onBlur={save} />
    : <input autoFocus className="input w-full" value={val} onChange={e => setVal(e.target.value)} onBlur={save} onKeyDown={e => e.key === 'Enter' && save()} />;
}

function NoteForm({ projectId, onSave, onCancel }) {
  const [title, setTitle] = useState('');
  const [body, setBody] = useState('');
  const submit = async (e) => {
    e.preventDefault();
    if (!title.trim()) return;
    await onSave({ title, body, project_id: projectId, date: new Date().toISOString().split('T')[0] });
    setTitle(''); setBody('');
  };
  return (
    <form onSubmit={submit} className="card border-dashed space-y-2">
      <input className="input" placeholder="Note title" value={title} onChange={e => setTitle(e.target.value)} autoFocus required />
      <textarea className="input resize-none" rows={3} placeholder="Note body…" value={body} onChange={e => setBody(e.target.value)} />

        <button type="button" onClick={onCancel} className="btn-secondary text-xs py-1 px-3">Cancel</button>
        <button type="submit" className="btn-primary text-xs py-1 px-3">Save Note</button>

    </form>
  );
}

export default function ProjectDetail() {
  const { id } = useParams();
  const navigate = useNavigate();
  const [project, setProject] = useState(null);
  const [tasks, setTasks] = useState([]);
  const [notes, setNotes] = useState([]);
  const [projects, setProjects] = useState([]);
  const [tab, setTab] = useState('tasks');
  const [loading, setLoading] = useState(true);
  const [editTask, setEditTask] = useState(null);
  const [deleteTask, setDeleteTask] = useState(null);
  const [deleteNote, setDeleteNote] = useState(null);
  const [showNewTask, setShowNewTask] = useState(false);
  const [showNewNote, setShowNewNote] = useState(false);

  const load = async () => {
    try {
      const [p, t, n, pr] = await Promise.all([
        api.get(`/projects/${id}`),
        api.get(`/tasks?project_id=${id}`),
        api.get(`/notes?project_id=${id}`),
        api.get('/projects'),
      ]);
      setProject(p); setTasks(t); setNotes(n); setProjects(pr);
    } catch { navigate('/projects'); }
    finally { setLoading(false); }
  };

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

  const updateProject = async (field, value) => {
    const updated = { ...project, [field]: value };
    setProject(updated);
    await api.put(`/projects/${id}`, updated);
  };

  const handleSaveTask = async (form) => {
    try {
      if (editTask?.id) { await api.put(`/tasks/${editTask.id}`, { ...form, project_id: id }); toast.success('Task updated'); }
      else { await api.post('/tasks', { ...form, project_id: id }); toast.success('Task added'); }
      setEditTask(null); setShowNewTask(false); load();
    } catch (e) { toast.error(e.message); }
  };

  const handleSaveNote = async (form) => {
    try {
      await api.post('/notes', form); toast.success('Note saved');
      setShowNewNote(false); load();
    } catch (e) { toast.error(e.message); }
  };

  const handleDeleteTask = async () => {
    await api.delete(`/tasks/${deleteTask.id}`); toast.success('Task deleted');
    setDeleteTask(null); load();
  };

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

  const toggleFocus = async (task) => { await api.patch(`/tasks/${task.id}/focus`); load(); };

  if (loading) return (

  );

  if (!project) return null;

  return (

      {/* Back */}
      <button onClick={() => navigate('/projects')} className="flex items-center gap-1.5 text-sm text-gray-500 hover:text-gray-800 dark:hover:text-white mb-4 transition-colors">
        <ArrowLeft size={15} /> Projects
      </button>

      {/* Project header */}

          <select
            className="badge cursor-pointer border-0 outline-none bg-transparent p-0"
            style={{ minWidth: 0 }}
            value={project.category}
            onChange={e => updateProject('category', e.target.value)}
          >
            {Object.entries(CATEGORIES).map(([k, v]) => <option key={k} value={k}>{v.label}</option>)}
          </select>
          <select
            className={`badge cursor-pointer border-0 outline-none bg-transparent p-0 ${getPriorityClass(project.priority)}`}
            value={project.priority}
            onChange={e => updateProject('priority', e.target.value)}
          >
            {PRIORITIES.map(p => <option key={p}>{p}</option>)}
          </select>
          <select
            className={`badge cursor-pointer border-0 outline-none bg-transparent p-0 ${getStatusClass(project.status)}`}
            value={project.status}
            onChange={e => updateProject('status', e.target.value)}
          >
            {STATUSES.map(s => <option key={s}>{s}</option>)}
          </select>

          <InlineEdit value={project.name} onSave={v => updateProject('name', v)} placeholder="Project name" />

Description

            <InlineEdit value={project.description} onSave={v => updateProject('description', v)} multiline placeholder="Add description…" />

Last Update

            <InlineEdit value={project.last_update} onSave={v => updateProject('last_update', v)} placeholder="What happened last?" />

Next Step

            <InlineEdit value={project.next_step} onSave={v => updateProject('next_step', v)} placeholder="What needs to happen next?" />

      {/* Tabs */}

        <button
          onClick={() => setTab('tasks')}
          className={`flex-1 flex items-center justify-center gap-1.5 py-2 px-3 rounded-md text-sm font-medium transition-colors ${
            tab === 'tasks' ? 'bg-white dark:bg-gray-900 text-gray-900 dark:text-white shadow-sm' : 'text-gray-500 hover:text-gray-700 dark:hover:text-gray-300'
          }`}
        >
          <CheckSquare size={14} /> Tasks ({tasks.filter(t => t.status !== 'Done').length})
        </button>
        <button
          onClick={() => setTab('notes')}
          className={`flex-1 flex items-center justify-center gap-1.5 py-2 px-3 rounded-md text-sm font-medium transition-colors ${
            tab === 'notes' ? 'bg-white dark:bg-gray-900 text-gray-900 dark:text-white shadow-sm' : 'text-gray-500 hover:text-gray-700 dark:hover:text-gray-300'
          }`}
        >
          <FileText size={14} /> Notes ({notes.length})
        </button>

      {/* Tab content */}
      {tab === 'tasks' && (

            <button onClick={() => setShowNewTask(true)} className="btn-secondary flex items-center gap-1.5 text-xs">
              <Plus size={13} /> Add Task
            </button>

          {tasks.length === 0
            ?
No tasks yet. Add one above.

            : tasks.map(t => (
                <TaskCard key={t.id} task={t} onEdit={setEditTask} onDelete={setDeleteTask} onToggleFocus={toggleFocus} />
              ))
          }

      )}

      {tab === 'notes' && (

            <button onClick={() => setShowNewNote(!showNewNote)} className="btn-secondary flex items-center gap-1.5 text-xs">
              <Plus size={13} /> Add Note
            </button>

          {showNewNote && <NoteForm projectId={id} onSave={handleSaveNote} onCancel={() => setShowNewNote(false)} />}
          {notes.length === 0 && !showNewNote
            ?
No notes yet.

            : notes.map(n => (

{n.title}

{n.date}

                      {n.body &&
{n.body}
}

                    <button onClick={() => setDeleteNote(n)} className="p-1.5 rounded hover:bg-red-50 text-gray-400 hover:text-red-500 opacity-0 group-hover:opacity-100 transition-all ml-2">
                      <Trash2 size={13} />
                    </button>

              ))
          }

      )}

      {/* Modals */}
      {(showNewTask || editTask) && (
        <TaskModal task={editTask} projects={projects} onSave={handleSaveTask} onClose={() => { setShowNewTask(false); setEditTask(null); }} />
      )}
      {deleteTask && <ConfirmDialog title="Delete task?" message={`"${deleteTask.title}"`} onConfirm={handleDeleteTask} onCancel={() => setDeleteTask(null)} />}
      {deleteNote && <ConfirmDialog title="Delete note?" message={`"${deleteNote.title}"`} onConfirm={handleDeleteNote} onCancel={() => setDeleteNote(null)} />}

  );
}


7:  const { project_id, status, category, priority, search, today_only, follow_up_only } = req.query;
16:  if (status) { sql += ' AND t.status = ?'; params.push(status); }
19:  if (today_only === 'true') { sql += " AND t.is_today_focus = 1 AND t.status != 'Done'"; }
20:  if (follow_up_only === 'true') { sql += " AND t.is_follow_up = 1 AND t.status != 'Done'"; }
28:    CASE t.status WHEN 'Open' THEN 1 WHEN 'In Progress' THEN 2 WHEN 'Waiting' THEN 3 ELSE 4 END,
35:  const todayFocus = db.prepare("SELECT COUNT(*) as count FROM tasks WHERE is_today_focus = 1 AND status != 'Done'").get().count;
36:  const openTasks = db.prepare("SELECT COUNT(*) as count FROM tasks WHERE status != 'Done'").get().count;
37:  const openFollowUps = db.prepare("SELECT COUNT(*) as count FROM tasks WHERE is_follow_up = 1 AND status != 'Done'").get().count;
38:  const activeProjects = db.prepare("SELECT COUNT(*) as count FROM projects WHERE status != 'Done'").get().count;
45:  if (!task) return res.status(404).json({ error: 'Not found' });
53:    status = 'Open', assignee = '', due_date = '', is_follow_up = 0, is_today_focus = 0
55:  if (!title) return res.status(400).json({ error: 'title is required' });
57:    INSERT INTO tasks (title, notes, project_id, category, priority, status, assignee, due_date, is_follow_up, is_today_focus)
59:  `).run(title, notes, project_id, category, priority, status, assignee, due_date, is_follow_up ? 1 : 0, is_today_focus ? 1 : 0);
66:    title, notes, project_id, category, priority, status, assignee, due_date, is_follow_up, is_today_focus
69:  if (!prev) return res.status(404).json({ error: 'Not found' });
70:  const completedAt = status === 'Done' && prev.status !== 'Done' ? new Date().toISOString() : (status !== 'Done' ? '' : prev.completed_at);
72:    UPDATE tasks SET title=?, notes=?, project_id=?, category=?, priority=?, status=?, assignee=?, due_date=?,
75:  `).run(title, notes, project_id, category, priority, status, assignee, due_date,
89:  if (!task) return res.status(404).json({ error: 'Not found' });
95:// PATCH /api/tasks/:id/status — quick status update
96:router.patch('/:id/status', (req, res) => {
97:  const { status } = req.body;
99:  if (!prev) return res.status(404).json({ error: 'Not found' });
100:  const completedAt = status === 'Done' ? new Date().toISOString() : '';
101:  db.prepare("UPDATE tasks SET status=?, completed_at=?, updated_at=datetime('now') WHERE id=?")
102:    .run(status, completedAt, req.params.id);


Good - `?status=Done` works. Now I'll implement all 4 changes. Let me read the full Sidebar to know where the project section is:


49
50	  const grouped = projects.reduce((acc, p) => {
51	    if (!acc[p.category]) acc[p.category] = [];
52	    acc[p.category].push(p);
53	    return acc;
54	  }, {});
55
56	  const toggleCat = (cat) => setOpenCats(s => ({ ...s, [cat]: !s[cat] }));
57
58	  return (
59
60	      {/* Logo */}
61

62

63

64	            <Zap size={15} className="text-white" />
65

66

67
My Workspace

68
DEWA · Senior Manager

69

70

71

72
73	      {/* Search */}
74	      <button
75	        onClick={onSearchOpen}
76	        className="mx-3 mt-3 flex items-center gap-2 px-3 py-2 rounded-xl bg-gray-50 dark:bg-gray-800 text-gray-400 text-xs hover:bg-gray-100 dark:hover:bg-gray-700 transition-colors group border border-gray-100 dark:border-gray-700"
77	      >
78	        <Search size={12} />
79	        Search…
80	        ⌘K
81	      </button>
82
83	      {/* Main nav */}
84
85	        {NAV.map(({ to, icon: Icon, label }) => (
86	          <NavLink key={to} to={to} className={({ isActive }) =>
87	            isActive ? 'nav-item-active flex items-center gap-2.5 px-3 py-2 rounded-xl text-sm font-medium bg-primary/10 text-primary'
88	                     : 'nav-item flex items-center gap-2.5 px-3 py-2 rounded-xl text-sm font-medium text-gray-600 dark:text-gray-400 hover:bg-gray-50 dark:hover:bg-gray-800 hover:text-gray-900 dark:hover:text-white transition-all'
89	          }>
90	            <Icon size={15} />
91	            {label}
92	          </NavLink>
93	        ))}
94
95
96	      {/* Projects grouped */}
97

98
Projects

99	        {Object.entries(grouped).map(([cat, items]) => (
100

101	            <button
102	              onClick={() => toggleCat(cat)}
103	              className="flex items-center gap-2 w-full px-2 py-1.5 text-xs font-semibold text-gray-500 dark:text-gray-400 hover:text-gray-800 dark:hover:text-white transition-colors rounded-lg hover:bg-gray-50 dark:hover:bg-gray-800"
104	            >
105	              <div className={`w-2 h-2 rounded-full shrink-0 ${CATEGORY_DOT[cat]}`} />
106	              {CATEGORY_LABEL[cat]}
107	              {items.length}
108	              {openCats[cat] ? <ChevronDown size={11} /> : <ChevronRight size={11} />}
109	            </button>
110	            {openCats[cat] && (
111

112	                {items.slice(0, 5).map(p => (
113	                  <button
114	                    key={p.id}
115	                    onClick={() => navigate(`/projects/${p.id}`)}
116	                    className="flex items-center gap-1.5 w-full px-2 py-1 text-xs text-gray-500 dark:text-gray-400 hover:text-gray-800 dark:hover:text-white hover:bg-gray-50 dark:hover:bg-gray-800 rounded-lg transition-colors text-left"
117	                  >
118	                    <div className={`w-1.5 h-1.5 rounded-full shrink-0 ${CATEGORY_DOT[cat]} opacity-60`} />
119	                    {p.name}
120	                  </button>
121	                ))}
122	                {items.length > 5 && (
123	                  <button onClick={() => navigate('/projects')} className="text-[10px] text-gray-400 hover:text-primary px-2 py-0.5 transition-colors">
124	                    +{items.length - 5} more
125	                  </button>
126	                )}
127

128	            )}
129

130	        ))}
131

132
133	      {/* Bottom */}
134

135	        <button
136	          onClick={() => setDarkMode(!darkMode)}
137	          className="flex items-center gap-2 w-full px-3 py-2 rounded-xl text-xs text-gray-500 dark:text-gray-400 hover:bg-gray-50 dark:hover:bg-gray-800 transition-colors"
138	        >
139	          {darkMode ? <Sun size={13} /> : <Moon size={13} />}
140	          {darkMode ? 'Light mode' : 'Dark mode'}
141	        </button>
142

143
144	  );
145	}
146

const grouped = projects.reduce((acc, p) => {
    if (!acc[p.category]) acc[p.category] = [];
    acc[p.category].push(p);
    return acc;
  }, {});

  const toggleCat = (cat) => setOpenCats(s => ({ ...s, [cat]: !s[cat] }));

  return (

      {/* Logo */}

            <Zap size={15} className="text-white" />

My Workspace

DEWA · Senior Manager

      {/* Search */}
      <button
        onClick={onSearchOpen}
        className="mx-3 mt-3 flex items-center gap-2 px-3 py-2 rounded-xl bg-gray-50 dark:bg-gray-800 text-gray-400 text-xs hover:bg-gray-100 dark:hover:bg-gray-700 transition-colors group border border-gray-100 dark:border-gray-700"
      >
        <Search size={12} />
        Search…
        ⌘K
      </button>

      {/* Main nav */}

        {NAV.map(({ to, icon: Icon, label }) => (
          <NavLink key={to} to={to} className={({ isActive }) =>
            isActive ? 'nav-item-active flex items-center gap-2.5 px-3 py-2 rounded-xl text-sm font-medium bg-primary/10 text-primary'
                     : 'nav-item flex items-center gap-2.5 px-3 py-2 rounded-xl text-sm font-medium text-gray-600 dark:text-gray-400 hover:bg-gray-50 dark:hover:bg-gray-800 hover:text-gray-900 dark:hover:text-white transition-all'
          }>
            <Icon size={15} />
            {label}
          </NavLink>
        ))}

      {/* Projects grouped */}

Projects

        {Object.entries(grouped).map(([cat, items]) => (

            <button
              onClick={() => toggleCat(cat)}
              className="flex items-center gap-2 w-full px-2 py-1.5 text-xs font-semibold text-gray-500 dark:text-gray-400 hover:text-gray-800 dark:hover:text-white transition-colors rounded-lg hover:bg-gray-50 dark:hover:bg-gray-800"
            >
              <div className={`w-2 h-2 rounded-full shrink-0 ${CATEGORY_DOT[cat]}`} />
              {CATEGORY_LABEL[cat]}
              {items.length}
              {openCats[cat] ? <ChevronDown size={11} /> : <ChevronRight size={11} />}
            </button>
            {openCats[cat] && (

                {items.slice(0, 5).map(p => (
                  <button
                    key={p.id}
                    onClick={() => navigate(`/projects/${p.id}`)}
                    className="flex items-center gap-1.5 w-full px-2 py-1 text-xs text-gray-500 dark:text-gray-400 hover:text-gray-800 dark:hover:text-white hover:bg-gray-50 dark:hover:bg-gray-800 rounded-lg transition-colors text-left"
                  >
                    <div className={`w-1.5 h-1.5 rounded-full shrink-0 ${CATEGORY_DOT[cat]} opacity-60`} />
                    {p.name}
                  </button>
                ))}
                {items.length > 5 && (
                  <button onClick={() => navigate('/projects')} className="text-[10px] text-gray-400 hover:text-primary px-2 py-0.5 transition-colors">
                    +{items.length - 5} more
                  </button>
                )}

            )}

        ))}

      {/* Bottom */}

        <button
          onClick={() => setDarkMode(!darkMode)}
          className="flex items-center gap-2 w-full px-3 py-2 rounded-xl text-xs text-gray-500 dark:text-gray-400 hover:bg-gray-50 dark:hover:bg-gray-800 transition-colors"
        >
          {darkMode ? <Sun size={13} /> : <Moon size={13} />}
          {darkMode ? 'Light mode' : 'Dark mode'}
        </button>

  );
}


Now I have everything I need. Let me implement all four changes:


Write

import { useState, useEffect } from 'react';
import { CheckCircle2, Clock, Search } from 'lucide-react';
import api from '../api';
import TaskModal from './TaskModal';
import { getPriorityClass, getCategoryClass, CATEGORIES, formatDate } from '../utils';

const PRIORITY_BORDER = {
  Urgent: 'border-l-red-500',
  High:   'border-l-amber-400',
  Medium: 'border-l-blue-400',
  Low:    'border-l-green-400',
};

export default function History() {
  const [tasks, setTasks]       = useState([]);
  const [projects, setProjects] = useState([]);
  const [loading, setLoading]   = useState(true);
  const [search, setSearch]     = useState('');
  const [viewTask, setViewTask] = useState(null);

  const load = () => {
    setLoading(true);
    Promise.all([api.get('/tasks?status=Done'), api.get('/projects')])
      .then(([t, p]) => { setTasks(t); setProjects(p); })
      .finally(() => setLoading(false));
  };

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

  const filtered = tasks.filter(t =>
    !search || t.title.toLowerCase().includes(search.toLowerCase()) ||
    (t.assignee || '').toLowerCase().includes(search.toLowerCase())
  );

  return (

      {/* Header */}

History

            {!loading && (

{tasks.length} completed task{tasks.length !== 1 ? 's' : ''}

            )}

          <Search size={13} className="absolute left-3 top-1/2 -translate-y-1/2 text-gray-400" />
          <input
            className="input pl-8 py-2 text-sm w-full"
            placeholder="Search completed tasks…"
            value={search}
            onChange={e => setSearch(e.target.value)}
          />

        {loading ? (

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

        ) : filtered.length === 0 ? (

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

              {search ? 'No results found' : 'No completed tasks yet'}

        ) : (

            {filtered.map(task => (
              <button
                key={task.id}
                onClick={() => setViewTask(task)}
                className={`text-left bg-white dark:bg-gray-900 rounded-xl border border-gray-100 dark:border-gray-800 border-l-4 ${PRIORITY_BORDER[task.priority] || 'border-l-gray-200'} p-3 shadow-card hover:shadow-hover hover:-translate-y-0.5 transition-all duration-150 group`}
              >

                    <CheckCircle2 size={11} className="text-white" />

                    {task.title}

                {task.notes && (

{task.notes}

                )}

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

                  {task.project_name && (

                      {task.project_name}

                  )}

                  <Clock size={9} />
                  {formatDate(task.updated_at || task.created_at)}
                  {task.assignee && (
                    {task.assignee}
                  )}

              </button>
            ))}

        )}

      {viewTask && (
        <TaskModal
          task={viewTask}
          projects={projects}
          onSave={async (form) => {
            await api.put(`/tasks/${viewTask.id}`, form);
            setViewTask(null);
            load();
          }}
          onClose={() => setViewTask(null)}
        />
      )}

  );
}


File created successfully at: /Users/khalidbinkalban/my-workspace/client/src/components/History.jsx

import { useState, useEffect } from 'react';
import { CheckCircle2, Clock, Search } from 'lucide-react';
import api from '../api';
import TaskModal from './TaskModal';
import { getPriorityClass, getCategoryClass, CATEGORIES, formatDate } from '../utils';

const PRIORITY_BORDER = {
  Urgent: 'border-l-red-500',
  High:   'border-l-amber-400',
  Medium: 'border-l-blue-400',
  Low:    'border-l-green-400',
};

export default function History() {
  const [tasks, setTasks]       = useState([]);
  const [projects, setProjects] = useState([]);
  const [loading, setLoading]   = useState(true);
  const [search, setSearch]     = useState('');
  const [viewTask, setViewTask] = useState(null);

  const load = () => {
    setLoading(true);
    Promise.all([api.get('/tasks?status=Done'), api.get('/projects')])
      .then(([t, p]) => { setTasks(t); setProjects(p); })
      .finally(() => setLoading(false));
  };

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

  const filtered = tasks.filter(t =>
    !search || t.title.toLowerCase().includes(search.toLowerCase()) ||
    (t.assignee || '').toLowerCase().includes(search.toLowerCase())
  );

  return (

      {/* Header */}

History

            {!loading && (

{tasks.length} completed task{tasks.length !== 1 ? 's' : ''}

            )}

          <Search size={13} className="absolute left-3 top-1/2 -translate-y-1/2 text-gray-400" />
          <input
            className="input pl-8 py-2 text-sm w-full"
            placeholder="Search completed tasks…"
            value={search}
            onChange={e => setSearch(e.target.value)}
          />

        {loading ? (

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

        ) : filtered.length === 0 ? (

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

              {search ? 'No results found' : 'No completed tasks yet'}

        ) : (

            {filtered.map(task => (
              <button
                key={task.id}
                onClick={() => setViewTask(task)}
                className={`text-left bg-white dark:bg-gray-900 rounded-xl border border-gray-100 dark:border-gray-800 border-l-4 ${PRIORITY_BORDER[task.priority] || 'border-l-gray-200'} p-3 shadow-card hover:shadow-hover hover:-translate-y-0.5 transition-all duration-150 group`}
              >

                    <CheckCircle2 size={11} className="text-white" />

                    {task.title}

                {task.notes && (

{task.notes}

                )}

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

                  {task.project_name && (

                      {task.project_name}

                  )}

                  <Clock size={9} />
                  {formatDate(task.updated_at || task.created_at)}
                  {task.assignee && (
                    {task.assignee}
                  )}

              </button>
            ))}

        )}

      {viewTask && (
        <TaskModal
          task={viewTask}
          projects={projects}
          onSave={async (form) => {
            await api.put(`/tasks/${viewTask.id}`, form);
            setViewTask(null);
            load();
          }}
          onClose={() => setViewTask(null)}
        />
      )}

  );
}


Now update Tasks.jsx grid view with inline green checkbox:


Edit

import { useState, useEffect, useRef } from 'react';
import { Plus, Search, SlidersHorizontal, CheckSquare, X } from 'lucide-react';

import { useState, useEffect, useRef } from 'react';
import { Plus, Search, SlidersHorizontal, CheckSquare, X, Check, Calendar, User, AlertTriangle } from 'lucide-react';