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

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

  return (
    <div className={`card group relative transition-all duration-150 ${task.status === 'Done' ? 'opacity-60' : ''}`}>

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

          </button>
        )}

        {/* Content */}

          <p className={`text-sm font-medium leading-snug ${task.status === 'Done' ? 'line-through text-gray-400' : 'text-gray-900 dark:text-white'}`}>
            {task.title}

          {task.notes && !compact && (

{task.notes}

          )}

          {/* Meta row */}

            <span className={`badge ${getPriorityClass(task.priority)}`}>{task.priority}
            <span className={`badge ${getCategoryClass(task.category)}`}>{CATEGORIES[task.category]?.label || task.category}
            <span className={`badge ${getStatusClass(task.status)}`}>{task.status}
            {task.is_follow_up === 1 && (
              Follow-up
            )}

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

              {task.assignee && (

                  <User size={11} />
                  {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-500 dark:text-gray-400'}`}>
                  <Calendar size={11} />
                  {overdue && '⚠ '}
                  {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-gray-700 dark:hover:text-gray-200 transition-colors">
              <Pencil size={13} />
            </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-600 dark:hover:text-red-400 transition-colors">
              <Trash2 size={13} />
            </button>
          )}

  );
}


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

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

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

  return (
    <div className={`card group relative transition-all duration-150 ${task.status === 'Done' ? 'opacity-60' : ''}`}>

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

          </button>
        )}

        {/* Content */}

          <p className={`text-sm font-medium leading-snug ${task.status === 'Done' ? 'line-through text-gray-400' : 'text-gray-900 dark:text-white'}`}>
            {task.title}

          {task.notes && !compact && (

{task.notes}

          )}

          {/* Meta row */}

            <span className={`badge ${getPriorityClass(task.priority)}`}>{task.priority}
            <span className={`badge ${getCategoryClass(task.category)}`}>{CATEGORIES[task.category]?.label || task.category}
            <span className={`badge ${getStatusClass(task.status)}`}>{task.status}
            {task.is_follow_up === 1 && (
              Follow-up
            )}

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

              {task.assignee && (

                  <User size={11} />
                  {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-500 dark:text-gray-400'}`}>
                  <Calendar size={11} />
                  {overdue && '⚠ '}
                  {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-gray-700 dark:hover:text-gray-200 transition-colors">
              <Pencil size={13} />
            </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-600 dark:hover:text-red-400 transition-colors">
              <Trash2 size={13} />
            </button>
          )}

  );
}


Write

import { useState, useEffect } from 'react';
import { X } from 'lucide-react';
import { CATEGORIES, PRIORITIES, STATUSES } from '../utils';

const EMPTY = {
  title: '', notes: '', project_id: null, category: 'adhoc', priority: 'Medium',
  status: 'Open', assignee: '', due_date: '', is_follow_up: false, is_today_focus: false,
};

export default function TaskModal({ task, projects = [], onSave, onClose }) {
  const [form, setForm] = useState(EMPTY);
  const isEdit = !!task?.id;

  useEffect(() => {
    if (task) {
      setForm({
        ...EMPTY,
        ...task,
        is_follow_up: !!task.is_follow_up,
        is_today_focus: !!task.is_today_focus,
      });
    }
  }, [task]);

  const set = (k, v) => setForm(f => ({ ...f, [k]: v }));

  const handleSubmit = (e) => {
    e.preventDefault();
    if (!form.title.trim()) return;
    onSave(form);
  };

  return (

 e.stopPropagation()}
      >
        {/* Header */}

{isEdit ? 'Edit Task' : 'New Task'}

          <button onClick={onClose} className="p-1.5 rounded-lg hover:bg-gray-100 dark:hover:bg-gray-800 text-gray-400 transition-colors">
            <X size={16} />
          </button>

        <form onSubmit={handleSubmit} className="px-5 py-4 space-y-4">
          {/* Title */}

            Title *
            <input className="input" value={form.title} onChange={e => set('title', e.target.value)} placeholder="What needs to be done?" autoFocus required />

          {/* Notes */}

            Notes
            <textarea className="input resize-none" rows={2} value={form.notes} onChange={e => set('notes', e.target.value)} placeholder="Additional context…" />

          {/* Row: Category + Priority */}

              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>

          {/* Row: Status + Assignee */}

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

              Assignee
              <input className="input" value={form.assignee} onChange={e => set('assignee', e.target.value)} placeholder="Person name" />

          {/* Row: Due date + Project */}

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

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

          {/* Checkboxes */}

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

              <input type="checkbox" className="w-4 h-4 rounded accent-primary" checked={form.is_today_focus} onChange={e => set('is_today_focus', e.target.checked)} />
              Today's focus

          {/* Actions */}

            <button type="button" onClick={onClose} className="btn-secondary">Cancel</button>
            <button type="submit" className="btn-primary">{isEdit ? 'Save Changes' : 'Add Task'}</button>

        </form>

  );
}


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

import { useState, useEffect } from 'react';
import { X } from 'lucide-react';
import { CATEGORIES, PRIORITIES, STATUSES } from '../utils';

const EMPTY = {
  title: '', notes: '', project_id: null, category: 'adhoc', priority: 'Medium',
  status: 'Open', assignee: '', due_date: '', is_follow_up: false, is_today_focus: false,
};

export default function TaskModal({ task, projects = [], onSave, onClose }) {
  const [form, setForm] = useState(EMPTY);
  const isEdit = !!task?.id;

  useEffect(() => {
    if (task) {
      setForm({
        ...EMPTY,
        ...task,
        is_follow_up: !!task.is_follow_up,
        is_today_focus: !!task.is_today_focus,
      });
    }
  }, [task]);

  const set = (k, v) => setForm(f => ({ ...f, [k]: v }));

  const handleSubmit = (e) => {
    e.preventDefault();
    if (!form.title.trim()) return;
    onSave(form);
  };

  return (

 e.stopPropagation()}
      >
        {/* Header */}

{isEdit ? 'Edit Task' : 'New Task'}

          <button onClick={onClose} className="p-1.5 rounded-lg hover:bg-gray-100 dark:hover:bg-gray-800 text-gray-400 transition-colors">
            <X size={16} />
          </button>

        <form onSubmit={handleSubmit} className="px-5 py-4 space-y-4">
          {/* Title */}

            Title *
            <input className="input" value={form.title} onChange={e => set('title', e.target.value)} placeholder="What needs to be done?" autoFocus required />

          {/* Notes */}

            Notes
            <textarea className="input resize-none" rows={2} value={form.notes} onChange={e => set('notes', e.target.value)} placeholder="Additional context…" />

          {/* Row: Category + Priority */}

              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>

          {/* Row: Status + Assignee */}

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

              Assignee
              <input className="input" value={form.assignee} onChange={e => set('assignee', e.target.value)} placeholder="Person name" />

          {/* Row: Due date + Project */}

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

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

          {/* Checkboxes */}

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

              <input type="checkbox" className="w-4 h-4 rounded accent-primary" checked={form.is_today_focus} onChange={e => set('is_today_focus', e.target.checked)} />
              Today's focus

          {/* Actions */}

            <button type="button" onClick={onClose} className="btn-secondary">Cancel</button>
            <button type="submit" className="btn-primary">{isEdit ? 'Save Changes' : 'Add Task'}</button>

        </form>

  );
}


import { useState, useEffect, useCallback } from 'react';
import { Plus, CalendarDays } from 'lucide-react';
import { Link } from 'react-router-dom';
import toast from 'react-hot-toast';
import api from '../api';
import TaskCard from './TaskCard';
import TaskModal from './TaskModal';
import ConfirmDialog from './ConfirmDialog';

function MetricCard({ label, value, color = 'primary', loading }) {
  const colors = {
    primary: 'text-primary',
    amber:   'text-amber-600 dark:text-amber-400',
    teal:    'text-teal-600 dark:text-teal-400',
    green:   'text-green-600 dark:text-green-400',
  };
  return (

      {loading
        ?

        : <p className={`text-2xl font-bold ${colors[color]}`}>{value}

      }

{label}

  );
}

export default function Today() {
  const [todayTasks, setTodayTasks] = useState([]);
  const [suggestTasks, setSuggestTasks] = useState([]);
  const [stats, setStats] = useState(null);
  const [projects, setProjects] = useState([]);
  const [loading, setLoading] = useState(true);
  const [editTask, setEditTask] = useState(null);
  const [deleteTask, setDeleteTask] = useState(null);
  const [showNewTask, setShowNewTask] = useState(false);

  const today = new Date();
  const dateLabel = today.toLocaleDateString('en-GB', { weekday: 'long', day: 'numeric', month: 'long' });

  const load = useCallback(async () => {
    try {
      const [t, all, st, pr] = await Promise.all([
        api.get('/tasks?today_only=true'),
        api.get('/tasks?status=Open'),
        api.get('/tasks/stats'),
        api.get('/projects'),
      ]);
      setTodayTasks(t);
      setStats(st);
      setProjects(pr);
      // Suggest top 5 urgent/high tasks not in today focus
      const notPinned = all.filter(tk => !tk.is_today_focus && (tk.priority === 'Urgent' || tk.priority === 'High'));
      setSuggestTasks(notPinned.slice(0, 5));
    } finally {
      setLoading(false);
    }
  }, []);

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

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

  const markDone = async (task) => {
    await api.patch(`/tasks/${task.id}/status`, { status: 'Done' });
    toast.success('Marked as done');
    load();
  };

  const handleSaveTask = 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);
      setShowNewTask(false);
      load();
    } catch (e) {
      toast.error(e.message);
    }
  };

  const handleDelete = async () => {
    try {
      await api.delete(`/tasks/${deleteTask.id}`);
      toast.success('Task deleted');
      setDeleteTask(null);
      load();
    } catch (e) {
      toast.error(e.message);
    }
  };

  return (

      {/* Header */}

            <CalendarDays size={14} />
            {dateLabel}

Today

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

      {/* Metrics */}

        <MetricCard label="Today's Focus" value={stats?.todayFocus ?? 0} color="primary" loading={loading} />
        <MetricCard label="Open Tasks" value={stats?.openTasks ?? 0} color="amber" loading={loading} />
        <MetricCard label="Follow-ups" value={stats?.openFollowUps ?? 0} color="teal" loading={loading} />
        <MetricCard label="Active Projects" value={stats?.activeProjects ?? 0} color="green" loading={loading} />

      {/* Today's focus tasks */}
      {loading ? (

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

      ) : todayTasks.length === 0 ? (

            <CalendarDays size={24} className="text-gray-400" />

Nothing pinned for today

Pin tasks from below or from the <Link to="/tasks" className="text-primary hover:underline">Tasks view</Link>.

      ) : (
        <>

Pinned for today — {todayTasks.length}

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

        </>
      )}

      {/* Suggestions */}
      {!loading && suggestTasks.length > 0 && (

Add to today

            {suggestTasks.map(task => (

                <button
                  onClick={() => toggleFocus(task)}
                  className="shrink-0 w-5 h-5 rounded-full border-2 border-gray-300 dark:border-gray-600 hover:border-primary transition-colors"
                />

{task.title}

                  <span className={`badge text-xs ${task.priority === 'Urgent' ? 'bg-red-100 text-red-700' : 'bg-amber-100 text-amber-700'}`}>
                    {task.priority}

            ))}

      )}

      {/* Modals */}
      {(showNewTask || editTask) && (
        <TaskModal
          task={editTask}
          projects={projects}
          onSave={handleSaveTask}
          onClose={() => { setShowNewTask(false); setEditTask(null); }}
        />
      )}
      {deleteTask && (
        <ConfirmDialog
          title="Delete task?"
          message={`"${deleteTask.title}" will be permanently removed.`}
          onConfirm={handleDelete}
          onCancel={() => setDeleteTask(null)}
        />
      )}

  );
}


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

import { useState, useEffect, useCallback } from 'react';
import { Plus, CalendarDays } from 'lucide-react';
import { Link } from 'react-router-dom';
import toast from 'react-hot-toast';
import api from '../api';
import TaskCard from './TaskCard';
import TaskModal from './TaskModal';
import ConfirmDialog from './ConfirmDialog';

function MetricCard({ label, value, color = 'primary', loading }) {
  const colors = {
    primary: 'text-primary',
    amber:   'text-amber-600 dark:text-amber-400',
    teal:    'text-teal-600 dark:text-teal-400',
    green:   'text-green-600 dark:text-green-400',
  };
  return (

      {loading
        ?

        : <p className={`text-2xl font-bold ${colors[color]}`}>{value}

      }

{label}

  );
}

export default function Today() {
  const [todayTasks, setTodayTasks] = useState([]);
  const [suggestTasks, setSuggestTasks] = useState([]);
  const [stats, setStats] = useState(null);
  const [projects, setProjects] = useState([]);
  const [loading, setLoading] = useState(true);
  const [editTask, setEditTask] = useState(null);
  const [deleteTask, setDeleteTask] = useState(null);
  const [showNewTask, setShowNewTask] = useState(false);

  const today = new Date();
  const dateLabel = today.toLocaleDateString('en-GB', { weekday: 'long', day: 'numeric', month: 'long' });

  const load = useCallback(async () => {
    try {
      const [t, all, st, pr] = await Promise.all([
        api.get('/tasks?today_only=true'),
        api.get('/tasks?status=Open'),
        api.get('/tasks/stats'),
        api.get('/projects'),
      ]);
      setTodayTasks(t);
      setStats(st);
      setProjects(pr);
      // Suggest top 5 urgent/high tasks not in today focus
      const notPinned = all.filter(tk => !tk.is_today_focus && (tk.priority === 'Urgent' || tk.priority === 'High'));
      setSuggestTasks(notPinned.slice(0, 5));
    } finally {
      setLoading(false);
    }
  }, []);

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

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

  const markDone = async (task) => {
    await api.patch(`/tasks/${task.id}/status`, { status: 'Done' });
    toast.success('Marked as done');
    load();
  };

  const handleSaveTask = 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);
      setShowNewTask(false);
      load();
    } catch (e) {
      toast.error(e.message);
    }
  };

  const handleDelete = async () => {
    try {
      await api.delete(`/tasks/${deleteTask.id}`);
      toast.success('Task deleted');
      setDeleteTask(null);
      load();
    } catch (e) {
      toast.error(e.message);
    }
  };

  return (

      {/* Header */}

            <CalendarDays size={14} />
            {dateLabel}

Today

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

      {/* Metrics */}

        <MetricCard label="Today's Focus" value={stats?.todayFocus ?? 0} color="primary" loading={loading} />
        <MetricCard label="Open Tasks" value={stats?.openTasks ?? 0} color="amber" loading={loading} />
        <MetricCard label="Follow-ups" value={stats?.openFollowUps ?? 0} color="teal" loading={loading} />
        <MetricCard label="Active Projects" value={stats?.activeProjects ?? 0} color="green" loading={loading} />

      {/* Today's focus tasks */}
      {loading ? (

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

      ) : todayTasks.length === 0 ? (

            <CalendarDays size={24} className="text-gray-400" />

Nothing pinned for today

Pin tasks from below or from the <Link to="/tasks" className="text-primary hover:underline">Tasks view</Link>.

      ) : (
        <>

Pinned for today — {todayTasks.length}

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

        </>
      )}

      {/* Suggestions */}
      {!loading && suggestTasks.length > 0 && (

Add to today

            {suggestTasks.map(task => (

                <button
                  onClick={() => toggleFocus(task)}
                  className="shrink-0 w-5 h-5 rounded-full border-2 border-gray-300 dark:border-gray-600 hover:border-primary transition-colors"
                />

{task.title}

                  <span className={`badge text-xs ${task.priority === 'Urgent' ? 'bg-red-100 text-red-700' : 'bg-amber-100 text-amber-700'}`}>
                    {task.priority}

            ))}

      )}

      {/* Modals */}
      {(showNewTask || editTask) && (
        <TaskModal
          task={editTask}
          projects={projects}
          onSave={handleSaveTask}
          onClose={() => { setShowNewTask(false); setEditTask(null); }}
        />
      )}
      {deleteTask && (
        <ConfirmDialog
          title="Delete task?"
          message={`"${deleteTask.title}" will be permanently removed.`}
          onConfirm={handleDelete}
          onCancel={() => setDeleteTask(null)}
        />
      )}

  );
}


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

const HISTORY_KEY = 'inbox_history';

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

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

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

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

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

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

  const setEdit = (k, v) => setEditParsed(p => ({ ...p, [k]: v }));

  const saveToWorkspace = async () => {
    if (!editParsed) return;
    try {
      const p = editParsed;
      if (p.type === 'task') {
        await api.post('/tasks', {
          title: p.title, notes: p.details, category: p.category, priority: p.priority,
          assignee: p.assignee, due_date: p.due || '', is_follow_up: p.isFollowUp ? 1 : 0,
          is_today_focus: p.suggestedTodayFocus ? 1 : 0,
        });
        toast.success('Task saved to workspace!');
      } else if (p.type === 'note') {
        await api.post('/notes', { title: p.title, body: p.details });
        toast.success('Note saved to workspace!');
      } else if (p.type === 'project') {
        await api.post('/projects', { name: p.title, category: p.category, description: p.details, priority: p.priority });
        toast.success('Project saved to workspace!');
      }
      saveHistory({ type: p.type, title: p.title, category: p.category, priority: p.priority });
      setHistory(loadHistory());
      setParsed(null);
      setEditParsed(null);
      setText('');
    } catch (e) {
      toast.error(e.message);
    }
  };

  return (

Inbox

        Speak or type anything — AI will parse it into a task, note, or project.

      {/* Input area */}

        <textarea
          className="w-full bg-transparent border-none outline-none text-sm text-gray-900 dark:text-white placeholder-gray-400 resize-none min-h-[120px]"
          placeholder="e.g. Follow up with Hamid about WEF Lighthouse document, urgent, needs to happen this week…"
          value={text}
          onChange={e => setText(e.target.value)}
          disabled={listening}
        />

          <button
            onClick={listening ? stopVoice : startVoice}
            className={`flex items-center gap-1.5 px-3 py-2 rounded-lg text-sm font-medium transition-all ${
              listening
                ? 'bg-red-100 text-red-600 dark:bg-red-900/30 dark:text-red-400 animate-pulse'
                : 'btn-secondary'
            }`}
          >
            {listening ? <><MicOff size={14} /> Listening…</> : <><Mic size={14} /> Voice</>}
          </button>

          {text.trim() && (
            <button onClick={() => { setText(''); setParsed(null); setEditParsed(null); }} className="btn-ghost text-xs">
              Clear
            </button>
          )}
          <button
            onClick={process}
            disabled={!text.trim() || processing}
            className="btn-primary flex items-center gap-1.5"
          >
            {processing
              ? <> Parsing…</>
              : <><Sparkles size={14} /> Process</>
            }
          </button>

      {/* Parsed result */}
      {editParsed && (

            <Sparkles size={15} className="text-primary" />
            AI parsed result — review before saving

            {/* Type + Title */}

                Type
                <select className="select" value={editParsed.type} onChange={e => setEdit('type', e.target.value)}>
                  <option value="task">Task</option>
                  <option value="note">Note</option>
                  <option value="project">Project</option>
                </select>

                Title
                <input className="input" value={editParsed.title} onChange={e => setEdit('title', e.target.value)} />

            {/* Category + Priority */}

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

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

            {/* Assignee + Due */}
            {editParsed.type === 'task' && (

                  Assignee
                  <input className="input" value={editParsed.assignee || ''} onChange={e => setEdit('assignee', e.target.value)} placeholder="Person name" />

                  Due Date
                  <input type="date" className="input" value={editParsed.due || ''} onChange={e => setEdit('due', e.target.value)} />

            )}

            {/* Details */}

              Details / Notes
              <textarea className="input resize-none" rows={2} value={editParsed.details || ''} onChange={e => setEdit('details', e.target.value)} />

            {/* Checkboxes */}
            {editParsed.type === 'task' && (

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

                  <input type="checkbox" className="accent-primary" checked={!!editParsed.suggestedTodayFocus} onChange={e => setEdit('suggestedTodayFocus', e.target.checked)} />
                  Today's focus

            )}

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

                💡 Suggested project: {editParsed.projectHint}

            )}

            <button onClick={() => { setParsed(null); setEditParsed(null); }} className="btn-ghost flex items-center gap-1.5">
              <Trash2 size={13} /> Discard
            </button>

            <button onClick={saveToWorkspace} className="btn-primary flex items-center gap-1.5">
              <Save size={13} /> Save to workspace
            </button>

      )}

      {/* History */}
      {history.length > 0 && (

Recently added via Inbox

            {history.map((h, i) => (

                <span className={`badge ${h.type === 'task' ? 'bg-blue-100 text-blue-700' : h.type === 'note' ? 'bg-gray-100 text-gray-700' : 'bg-green-100 text-green-700'}`}>
                  {h.type}

                {h.title}
                {new Date(h.savedAt).toLocaleDateString()}

            ))}

      )}

  );
}


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

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

const HISTORY_KEY = 'inbox_history';

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

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

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

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

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

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

  const setEdit = (k, v) => setEditParsed(p => ({ ...p, [k]: v }));

  const saveToWorkspace = async () => {
    if (!editParsed) return;
    try {
      const p = editParsed;
      if (p.type === 'task') {
        await api.post('/tasks', {
          title: p.title, notes: p.details, category: p.category, priority: p.priority,
          assignee: p.assignee, due_date: p.due || '', is_follow_up: p.isFollowUp ? 1 : 0,
          is_today_focus: p.suggestedTodayFocus ? 1 : 0,
        });
        toast.success('Task saved to workspace!');
      } else if (p.type === 'note') {
        await api.post('/notes', { title: p.title, body: p.details });
        toast.success('Note saved to workspace!');
      } else if (p.type === 'project') {
        await api.post('/projects', { name: p.title, category: p.category, description: p.details, priority: p.priority });
        toast.success('Project saved to workspace!');
      }
      saveHistory({ type: p.type, title: p.title, category: p.category, priority: p.priority });
      setHistory(loadHistory());
      setParsed(null);
      setEditParsed(null);
      setText('');
    } catch (e) {
      toast.error(e.message);
    }
  };

  return (

Inbox

        Speak or type anything — AI will parse it into a task, note, or project.

      {/* Input area */}

        <textarea
          className="w-full bg-transparent border-none outline-none text-sm text-gray-900 dark:text-white placeholder-gray-400 resize-none min-h-[120px]"
          placeholder="e.g. Follow up with Hamid about WEF Lighthouse document, urgent, needs to happen this week…"
          value={text}
          onChange={e => setText(e.target.value)}
          disabled={listening}
        />

          <button
            onClick={listening ? stopVoice : startVoice}
            className={`flex items-center gap-1.5 px-3 py-2 rounded-lg text-sm font-medium transition-all ${
              listening
                ? 'bg-red-100 text-red-600 dark:bg-red-900/30 dark:text-red-400 animate-pulse'
                : 'btn-secondary'
            }`}
          >
            {listening ? <><MicOff size={14} /> Listening…</> : <><Mic size={14} /> Voice</>}
          </button>

          {text.trim() && (
            <button onClick={() => { setText(''); setParsed(null); setEditParsed(null); }} className="btn-ghost text-xs">
              Clear
            </button>
          )}
          <button
            onClick={process}
            disabled={!text.trim() || processing}
            className="btn-primary flex items-center gap-1.5"
          >
            {processing
              ? <> Parsing…</>
              : <><Sparkles size={14} /> Process</>
            }
          </button>

      {/* Parsed result */}
      {editParsed && (

            <Sparkles size={15} className="text-primary" />
            AI parsed result — review before saving

            {/* Type + Title */}

                Type
                <select className="select" value={editParsed.type} onChange={e => setEdit('type', e.target.value)}>
                  <option value="task">Task</option>
                  <option value="note">Note</option>
                  <option value="project">Project</option>
                </select>

                Title
                <input className="input" value={editParsed.title} onChange={e => setEdit('title', e.target.value)} />

            {/* Category + Priority */}

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

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

            {/* Assignee + Due */}
            {editParsed.type === 'task' && (

                  Assignee
                  <input className="input" value={editParsed.assignee || ''} onChange={e => setEdit('assignee', e.target.value)} placeholder="Person name" />

                  Due Date
                  <input type="date" className="input" value={editParsed.due || ''} onChange={e => setEdit('due', e.target.value)} />

            )}

            {/* Details */}

              Details / Notes
              <textarea className="input resize-none" rows={2} value={editParsed.details || ''} onChange={e => setEdit('details', e.target.value)} />

            {/* Checkboxes */}
            {editParsed.type === 'task' && (

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

                  <input type="checkbox" className="accent-primary" checked={!!editParsed.suggestedTodayFocus} onChange={e => setEdit('suggestedTodayFocus', e.target.checked)} />
                  Today's focus

            )}

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

                💡 Suggested project: {editParsed.projectHint}

            )}

            <button onClick={() => { setParsed(null); setEditParsed(null); }} className="btn-ghost flex items-center gap-1.5">
              <Trash2 size={13} /> Discard
            </button>

            <button onClick={saveToWorkspace} className="btn-primary flex items-center gap-1.5">
              <Save size={13} /> Save to workspace
            </button>

      )}

      {/* History */}
      {history.length > 0 && (

Recently added via Inbox

            {history.map((h, i) => (

                <span className={`badge ${h.type === 'task' ? 'bg-blue-100 text-blue-700' : h.type === 'note' ? 'bg-gray-100 text-gray-700' : 'bg-green-100 text-green-700'}`}>
                  {h.type}

                {h.title}
                {new Date(h.savedAt).toLocaleDateString()}

            ))}

      )}

  );
}


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

  );
}


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

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

  );
}


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

  );
}


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

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

  );
}


import { useState, useEffect, useRef } from 'react';
import { Plus, Search, Filter, 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 } from '../utils';

const SORT_OPTIONS = [
  { value: 'priority', label: 'Priority' },
  { value: 'due_date', label: 'Due Date' },
  { value: 'created_at', label: 'Created' },
];

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

export default function Tasks() {
  const [tasks, setTasks] = useState([]);
  const [projects, setProjects] = useState([]);
  const [loading, setLoading] = useState(true);
  const [search, setSearch] = useState('');
  const [statusFilter, setStatusFilter] = useState('');
  const [catFilter, setCatFilter] = useState('');
  const [priorityFilter, setPriorityFilter] = useState('');
  const [editTask, setEditTask] = useState(null);
  const [deleteTask, setDeleteTask] = useState(null);
  const [showNew, setShowNew] = useState(false);
  const [selected, setSelected] = useState(new Set());
  const [showFilters, setShowFilters] = useState(false);
  const debouncedSearch = useDebounce(search);

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

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

  const handleSaveTask = 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 () => {
    try {
      await api.delete(`/tasks/${deleteTask.id}`); toast.success('Deleted');
      setDeleteTask(null); load();
    } catch (e) { toast.error(e.message); }
  };

  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} task${selected.size > 1 ? 's' : ''} marked done`);
    setSelected(new Set()); load();
  };

  const hasFilters = statusFilter || catFilter || priorityFilter;

  return (

Tasks

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

      {/* Search + filter bar */}

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

        <button
          onClick={() => setShowFilters(!showFilters)}
          className={`btn-secondary flex items-center gap-1.5 shrink-0 ${hasFilters ? 'bg-primary/10 text-primary border-primary/30' : ''}`}
        >
          <Filter size={14} />
          {hasFilters ? 'Filtered' : 'Filter'}
        </button>

      {/* Filters */}
      {showFilters && (

            Status
            <select className="select text-xs py-1.5" value={statusFilter} onChange={e => setStatusFilter(e.target.value)}>
              <option value="">All</option>
              {STATUSES.map(s => <option key={s}>{s}</option>)}
            </select>

            Category
            <select className="select text-xs py-1.5" value={catFilter} onChange={e => setCatFilter(e.target.value)}>
              <option value="">All</option>
              {Object.entries(CATEGORIES).map(([k, v]) => <option key={k} value={k}>{v.label}</option>)}
            </select>

            Priority
            <select className="select text-xs py-1.5" value={priorityFilter} onChange={e => setPriorityFilter(e.target.value)}>
              <option value="">All</option>
              {PRIORITIES.map(p => <option key={p}>{p}</option>)}
            </select>

          {hasFilters && (

              <button onClick={() => { setStatusFilter(''); setCatFilter(''); setPriorityFilter(''); }} className="text-xs text-primary hover:underline">
                Clear filters
              </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={12} /> Mark done
          </button>
          <button onClick={() => setSelected(new Set())} className="btn-ghost text-xs">Clear</button>

      )}

      {/* Tasks */}
      {loading ? (

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

      ) : tasks.length === 0 ? (

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

No tasks found

          {search && <button onClick={() => setSearch('')} className="btn-ghost mt-2 text-sm">Clear search</button>}

      ) : (

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

          {tasks.map(task => (

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

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

          ))}

      )}

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

  );
}


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

import { useState, useEffect, useRef } from 'react';
import { Plus, Search, Filter, 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 } from '../utils';

const SORT_OPTIONS = [
  { value: 'priority', label: 'Priority' },
  { value: 'due_date', label: 'Due Date' },
  { value: 'created_at', label: 'Created' },
];

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

export default function Tasks() {
  const [tasks, setTasks] = useState([]);
  const [projects, setProjects] = useState([]);
  const [loading, setLoading] = useState(true);
  const [search, setSearch] = useState('');
  const [statusFilter, setStatusFilter] = useState('');
  const [catFilter, setCatFilter] = useState('');
  const [priorityFilter, setPriorityFilter] = useState('');
  const [editTask, setEditTask] = useState(null);
  const [deleteTask, setDeleteTask] = useState(null);
  const [showNew, setShowNew] = useState(false);
  const [selected, setSelected] = useState(new Set());
  const [showFilters, setShowFilters] = useState(false);
  const debouncedSearch = useDebounce(search);

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

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

  const handleSaveTask = 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 () => {
    try {
      await api.delete(`/tasks/${deleteTask.id}`); toast.success('Deleted');
      setDeleteTask(null); load();
    } catch (e) { toast.error(e.message); }
  };

  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} task${selected.size > 1 ? 's' : ''} marked done`);
    setSelected(new Set()); load();
  };

  const hasFilters = statusFilter || catFilter || priorityFilter;

  return (

Tasks

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

      {/* Search + filter bar */}

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

        <button
          onClick={() => setShowFilters(!showFilters)}
          className={`btn-secondary flex items-center gap-1.5 shrink-0 ${hasFilters ? 'bg-primary/10 text-primary border-primary/30' : ''}`}
        >
          <Filter size={14} />
          {hasFilters ? 'Filtered' : 'Filter'}
        </button>

      {/* Filters */}
      {showFilters && (

            Status
            <select className="select text-xs py-1.5" value={statusFilter} onChange={e => setStatusFilter(e.target.value)}>
              <option value="">All</option>
              {STATUSES.map(s => <option key={s}>{s}</option>)}
            </select>

            Category
            <select className="select text-xs py-1.5" value={catFilter} onChange={e => setCatFilter(e.target.value)}>
              <option value="">All</option>
              {Object.entries(CATEGORIES).map(([k, v]) => <option key={k} value={k}>{v.label}</option>)}
            </select>

            Priority
            <select className="select text-xs py-1.5" value={priorityFilter} onChange={e => setPriorityFilter(e.target.value)}>
              <option value="">All</option>
              {PRIORITIES.map(p => <option key={p}>{p}</option>)}
            </select>

          {hasFilters && (

              <button onClick={() => { setStatusFilter(''); setCatFilter(''); setPriorityFilter(''); }} className="text-xs text-primary hover:underline">
                Clear filters
              </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={12} /> Mark done
          </button>
          <button onClick={() => setSelected(new Set())} className="btn-ghost text-xs">Clear</button>

      )}

      {/* Tasks */}
      {loading ? (

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

      ) : tasks.length === 0 ? (

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

No tasks found

          {search && <button onClick={() => setSearch('')} className="btn-ghost mt-2 text-sm">Clear search</button>}

      ) : (

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

          {tasks.map(task => (

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

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

          ))}

      )}

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

  );
}


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

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

 e.stopPropagation()}>

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

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

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

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

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

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

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

        </form>

  );
}

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

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

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

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

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

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

  return (

Notes

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

      {loading ? (

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

      ) : notes.length === 0 ? (

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

No notes yet

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

      ) : (

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

{note.title}

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

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

{note.body}

                        )}
                        {isOpen && (

{note.body}

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

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

            );
          })}

      )}

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

  );
}


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

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

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

 e.stopPropagation()}>

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

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

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

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

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

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

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

        </form>

  );
}

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

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

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

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

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

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

  return (

Notes

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

      {loading ? (

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

      ) : notes.length === 0 ? (

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

No notes yet

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

      ) : (

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

{note.title}

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

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

{note.body}

                        )}
                        {isOpen && (

{note.body}

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

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

            );
          })}

      )}

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

  );
}