1	const express = require('express');
2	const router = express.Router();
3	const Anthropic = require('@anthropic-ai/sdk');
4
5	const SYSTEM_PROMPT = `You are a workspace assistant for a Senior Manager at DEWA (Dubai Electricity and Water Authority).
6	Extract structured data from natural language input and return ONLY valid JSON.
7
8	Categories: ai, vendor, gov, people, events, strategic, adhoc
9	People at DEWA: Chandini, Hamid, Wafa, Ayesha, Shabeer, Hadi, Abdulla, Ahmed, Yasa, Sunil, Umme, Nixon, Rakish, Ummehani, Saif, Santhosh, Abida, Marwan, Moaza, Meera AlKitbi
10
11	Return this exact structure:
12	{
13	  "type": "task" | "note" | "project",
14	  "title": "concise action-oriented title",
15	  "details": "full context preserved",
16	  "category": "category id",
17	  "priority": "Urgent" | "High" | "Medium" | "Low",
18	  "assignee": "person name or empty string",
19	  "due": "YYYY-MM-DD or empty string",
20	  "isFollowUp": true | false,
21	  "projectHint": "most likely related project name or empty string",
22	  "suggestedTodayFocus": true | false
23	}`;
24
25	// POST /api/ai/parse
26	router.post('/parse', async (req, res) => {
27	  const { text } = req.body;
28	  if (!text || !text.trim()) return res.status(400).json({ error: 'text is required' });
29
30	  if (!process.env.ANTHROPIC_API_KEY) {
31	    return res.status(503).json({ error: 'ANTHROPIC_API_KEY not configured' });
32	  }
33
34	  try {
35	    const client = new Anthropic({ apiKey: process.env.ANTHROPIC_API_KEY });
36	    const message = await client.messages.create({
37	      model: 'claude-sonnet-4-6',
38	      max_tokens: 1024,
39	      system: SYSTEM_PROMPT,
40	      messages: [{ role: 'user', content: text }]
41	    });
42
43	    const raw = message.content[0].text.trim();
44	    // Strip markdown code fences if present
45	    const jsonText = raw.replace(/^```(?:json)?\n?/, '').replace(/\n?```$/, '');
46	    const parsed = JSON.parse(jsonText);
47	    res.json(parsed);
48	  } catch (err) {
49	    console.error('AI parse error:', err.message);
50	    if (err instanceof SyntaxError) {
51	      return res.status(422).json({ error: 'AI returned invalid JSON', raw: err.message });
52	    }
53	    res.status(500).json({ error: err.message });
54	  }
55	});
56
57	// POST /api/ai/parse-file  — parse an attached image, PDF, or text file
58	router.post('/parse-file', async (req, res) => {
59	  const { fileData, mimeType, fileName, text } = req.body;
60	  if (!fileData && !text) return res.status(400).json({ error: 'fileData or text is required' });
61
62	  if (!process.env.ANTHROPIC_API_KEY) {
63	    return res.status(503).json({ error: 'ANTHROPIC_API_KEY not configured' });
64	  }
65
66	  try {
67	    const client = new Anthropic({ apiKey: process.env.ANTHROPIC_API_KEY });
68
69	    let contentBlocks = [];
70
71	    if (fileData) {
72	      const base64 = fileData.replace(/^data:[^;]+;base64,/, '');
73	      const type = mimeType || 'application/octet-stream';
74
75	      if (type === 'application/pdf') {
76	        contentBlocks.push({
77	          type: 'document',
78	          source: { type: 'base64', media_type: 'application/pdf', data: base64 }
79	        });
80	      } else if (type.startsWith('image/')) {
81	        contentBlocks.push({
82	          type: 'image',
83	          source: { type: 'base64', media_type: type, data: base64 }
84	        });
85	      } else {
86	        // Treat as text (txt, csv, etc.)
87	        const decoded = Buffer.from(base64, 'base64').toString('utf-8');
88	        contentBlocks.push({ type: 'text', text: `File: ${fileName || 'attachment'}\n\n${decoded}` });
89	      }
90	    }
91
92	    if (text && text.trim()) {
93	      contentBlocks.push({ type: 'text', text: text.trim() });
94	    }
95
96	    if (contentBlocks.length === 0) {
97	      contentBlocks.push({ type: 'text', text: `Analyze the attached file: ${fileName || 'file'}` });
98	    }
99
100	    const message = await client.messages.create({
101	      model: 'claude-sonnet-4-6',
102	      max_tokens: 1024,
103	      system: SYSTEM_PROMPT,
104	      messages: [{ role: 'user', content: contentBlocks }]
105	    });
106
107	    const raw = message.content[0].text.trim();
108	    const jsonText = raw.replace(/^```(?:json)?\n?/, '').replace(/\n?```$/, '');
109	    const parsed = JSON.parse(jsonText);
110	    res.json(parsed);
111	  } catch (err) {
112	    console.error('AI parse-file error:', err.message);
113	    if (err instanceof SyntaxError) {
114	      return res.status(422).json({ error: 'AI returned invalid JSON', raw: err.message });
115	    }
116	    res.status(500).json({ error: err.message });
117	  }
118	});
119
120	module.exports = router;
121

const express = require('express');
const router = express.Router();
const Anthropic = require('@anthropic-ai/sdk');

const SYSTEM_PROMPT = `You are a workspace assistant for a Senior Manager at DEWA (Dubai Electricity and Water Authority).
Extract structured data from natural language input and return ONLY valid JSON.

Categories: ai, vendor, gov, people, events, strategic, adhoc
People at DEWA: Chandini, Hamid, Wafa, Ayesha, Shabeer, Hadi, Abdulla, Ahmed, Yasa, Sunil, Umme, Nixon, Rakish, Ummehani, Saif, Santhosh, Abida, Marwan, Moaza, Meera AlKitbi

Return this exact structure:
{
  "type": "task" | "note" | "project",
  "title": "concise action-oriented title",
  "details": "full context preserved",
  "category": "category id",
  "priority": "Urgent" | "High" | "Medium" | "Low",
  "assignee": "person name or empty string",
  "due": "YYYY-MM-DD or empty string",
  "isFollowUp": true | false,
  "projectHint": "most likely related project name or empty string",
  "suggestedTodayFocus": true | false
}`;

// POST /api/ai/parse
router.post('/parse', async (req, res) => {
  const { text } = req.body;
  if (!text || !text.trim()) return res.status(400).json({ error: 'text is required' });

  if (!process.env.ANTHROPIC_API_KEY) {
    return res.status(503).json({ error: 'ANTHROPIC_API_KEY not configured' });
  }

  try {
    const client = new Anthropic({ apiKey: process.env.ANTHROPIC_API_KEY });
    const message = await client.messages.create({
      model: 'claude-sonnet-4-6',
      max_tokens: 1024,
      system: SYSTEM_PROMPT,
      messages: [{ role: 'user', content: text }]
    });

    const raw = message.content[0].text.trim();
    // Strip markdown code fences if present
    const jsonText = raw.replace(/^```(?:json)?\n?/, '').replace(/\n?```$/, '');
    const parsed = JSON.parse(jsonText);
    res.json(parsed);
  } catch (err) {
    console.error('AI parse error:', err.message);
    if (err instanceof SyntaxError) {
      return res.status(422).json({ error: 'AI returned invalid JSON', raw: err.message });
    }
    res.status(500).json({ error: err.message });
  }
});

// POST /api/ai/parse-file  — parse an attached image, PDF, or text file
router.post('/parse-file', async (req, res) => {
  const { fileData, mimeType, fileName, text } = req.body;
  if (!fileData && !text) return res.status(400).json({ error: 'fileData or text is required' });

  if (!process.env.ANTHROPIC_API_KEY) {
    return res.status(503).json({ error: 'ANTHROPIC_API_KEY not configured' });
  }

  try {
    const client = new Anthropic({ apiKey: process.env.ANTHROPIC_API_KEY });

    let contentBlocks = [];

    if (fileData) {
      const base64 = fileData.replace(/^data:[^;]+;base64,/, '');
      const type = mimeType || 'application/octet-stream';

      if (type === 'application/pdf') {
        contentBlocks.push({
          type: 'document',
          source: { type: 'base64', media_type: 'application/pdf', data: base64 }
        });
      } else if (type.startsWith('image/')) {
        contentBlocks.push({
          type: 'image',
          source: { type: 'base64', media_type: type, data: base64 }
        });
      } else {
        // Treat as text (txt, csv, etc.)
        const decoded = Buffer.from(base64, 'base64').toString('utf-8');
        contentBlocks.push({ type: 'text', text: `File: ${fileName || 'attachment'}\n\n${decoded}` });
      }
    }

    if (text && text.trim()) {
      contentBlocks.push({ type: 'text', text: text.trim() });
    }

    if (contentBlocks.length === 0) {
      contentBlocks.push({ type: 'text', text: `Analyze the attached file: ${fileName || 'file'}` });
    }

    const message = await client.messages.create({
      model: 'claude-sonnet-4-6',
      max_tokens: 1024,
      system: SYSTEM_PROMPT,
      messages: [{ role: 'user', content: contentBlocks }]
    });

    const raw = message.content[0].text.trim();
    const jsonText = raw.replace(/^```(?:json)?\n?/, '').replace(/\n?```$/, '');
    const parsed = JSON.parse(jsonText);
    res.json(parsed);
  } catch (err) {
    console.error('AI parse-file error:', err.message);
    if (err instanceof SyntaxError) {
      return res.status(422).json({ error: 'AI returned invalid JSON', raw: err.message });
    }
    res.status(500).json({ error: err.message });
  }
});

module.exports = router;


1	import { useState, useEffect } from 'react';
2	import { CheckCircle2, Clock, Search, RotateCcw } from 'lucide-react';
3	import toast from 'react-hot-toast';
4	import api from '../api';
5	import TaskModal from './TaskModal';
6	import { getPriorityClass, getCategoryClass, CATEGORIES, formatDate } from '../utils';
7
8	const PRIORITY_BORDER = {
9	  Urgent: 'border-l-red-500',
10	  High:   'border-l-amber-400',
11	  Medium: 'border-l-blue-400',
12	  Low:    'border-l-green-400',
13	};
14
15	export default function History() {
16	  const [tasks, setTasks]       = useState([]);
17	  const [projects, setProjects] = useState([]);
18	  const [loading, setLoading]   = useState(true);
19	  const [search, setSearch]     = useState('');
20	  const [viewTask, setViewTask] = useState(null);
21
22	  const load = () => {
23	    setLoading(true);
24	    Promise.all([api.get('/tasks?status=Done'), api.get('/projects')])
25	      .then(([t, p]) => { setTasks(t); setProjects(p); })
26	      .finally(() => setLoading(false));
27	  };
28
29	  const restore = async (e, task) => {
30	    e.stopPropagation();
31	    await api.patch(`/tasks/${task.id}/status`, { status: 'Open' });
32	    toast.success('Task restored to Open');
33	    load();
34	  };
35
36	  useEffect(() => { load(); }, []);
37
38	  const filtered = tasks.filter(t =>
39	    !search || t.title.toLowerCase().includes(search.toLowerCase()) ||
40	    (t.assignee || '').toLowerCase().includes(search.toLowerCase())
41	  );
42
43	  return (
44

45	      {/* Header */}
46

47

48

49
History

50	            {!loading && (
51
{tasks.length} completed task{tasks.length !== 1 ? 's' : ''}

52	            )}
53

54

55

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

64

65
66

67	        {loading ? (
68

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

71	        ) : filtered.length === 0 ? (
72

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

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

77

78	        ) : (
79

80	            {filtered.map(task => (
81
 setViewTask(task)}
85	              >
86	                {/* Restore button — visible on hover */}
87	                <button
88	                  onClick={e => restore(e, task)}
89	                  title="Restore to Open"
90	                  className="absolute top-2 right-2 opacity-0 group-hover:opacity-100 transition-opacity flex items-center gap-1 px-2 py-1 rounded-lg bg-primary/10 text-primary hover:bg-primary hover:text-white text-[10px] font-semibold"
91	                >
92	                  <RotateCcw size={9} /> Restore
93	                </button>
94
95

96

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

99

100	                    {task.title}
101

102

103
104	                {task.notes && (
105
{task.notes}

106	                )}
107
108

109	                  <span className={`badge text-[10px] py-0.5 ${getPriorityClass(task.priority)}`}>{task.priority}
110	                  <span className={`badge text-[10px] py-0.5 ${getCategoryClass(task.category)}`}>
111	                    {CATEGORIES[task.category]?.label?.split(' ')[0] || task.category}
112
113	                  {task.project_name && (
114
115	                      {task.project_name}
116
117	                  )}
118

119
120

121	                  <Clock size={9} />
122	                  {formatDate(task.updated_at || task.created_at)}
123	                  {task.assignee && (
124	                    {task.assignee}
125	                  )}
126

127

128	            ))}
129

130	        )}
131

132
133	      {viewTask && (
134	        <TaskModal
135	          task={viewTask}
136	          projects={projects}
137	          onSave={async (form) => {
138	            await api.put(`/tasks/${viewTask.id}`, form);
139	            setViewTask(null);
140	            load();
141	          }}
142	          onClose={() => setViewTask(null)}
143	        />
144	      )}
145

146	  );
147	}
148

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

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

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

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

  const restore = async (e, task) => {
    e.stopPropagation();
    await api.patch(`/tasks/${task.id}/status`, { status: 'Open' });
    toast.success('Task restored to Open');
    load();
  };

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

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

  return (

      {/* Header */}

History

            {!loading && (

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

            )}

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

        {loading ? (

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

        ) : filtered.length === 0 ? (

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

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

        ) : (

            {filtered.map(task => (

 setViewTask(task)}
              >
                {/* Restore button — visible on hover */}
                <button
                  onClick={e => restore(e, task)}
                  title="Restore to Open"
                  className="absolute top-2 right-2 opacity-0 group-hover:opacity-100 transition-opacity flex items-center gap-1 px-2 py-1 rounded-lg bg-primary/10 text-primary hover:bg-primary hover:text-white text-[10px] font-semibold"
                >
                  <RotateCcw size={9} /> Restore
                </button>

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

                    {task.title}

                {task.notes && (

{task.notes}

                )}

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

                  {task.project_name && (

                      {task.project_name}

                  )}

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

            ))}

        )}

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

  );
}


Let me read the remaining files I haven't checked yet.


1	import React from 'react';
2	import ReactDOM from 'react-dom/client';
3	import { Toaster } from 'react-hot-toast';
4	import App from './App.jsx';
5	import './index.css';
6
7	ReactDOM.createRoot(document.getElementById('root')).render(
8	  <React.StrictMode>
9	    <App />
10	    <Toaster
11	      position="top-right"
12	      toastOptions={{
13	        duration: 3000,
14	        style: {
15	          borderRadius: '8px',
16	          fontSize: '14px',
17	        },
18	      }}
19	    />
20	  </React.StrictMode>
21	);
22

import React from 'react';
import ReactDOM from 'react-dom/client';
import { Toaster } from 'react-hot-toast';
import App from './App.jsx';
import './index.css';

ReactDOM.createRoot(document.getElementById('root')).render(
  <React.StrictMode>
    <App />
    <Toaster
      position="top-right"
      toastOptions={{
        duration: 3000,
        style: {
          borderRadius: '8px',
          fontSize: '14px',
        },
      }}
    />
  </React.StrictMode>
);


1	const path = require('path');
2
3	/** @type {import('tailwindcss').Config} */
4	module.exports = {
5	  darkMode: 'class',
6	  content: [
7	    path.join(__dirname, './src/**/*.{js,jsx,ts,tsx}'),
8	    path.join(__dirname, './index.html'),
9	  ],
10	  theme: {
11	    extend: {
12	      fontFamily: {
13	        sans: ['Inter', 'ui-sans-serif', 'system-ui', 'sans-serif'],
14	      },
15	      colors: {
16	        primary: { DEFAULT: '#7C5CFC', light: '#9B7EFE', dark: '#5B3FD4', soft: '#EDE9FF' },
17	        brand:   { purple: '#7C5CFC', pink: '#F472B6', orange: '#FB923C', teal: '#2DD4BF' },
18	      },
19	      boxShadow: {
20	        card:  '0 2px 8px rgba(0,0,0,0.06)',
21	        hover: '0 8px 24px rgba(0,0,0,0.12)',
22	        glow:  '0 0 24px rgba(124,92,252,0.25)',
23	      },
24	      backgroundImage: {
25	        'hero-gradient': 'linear-gradient(135deg, #ddd6fe 0%, #f5d0fe 45%, #fed7aa 100%)',
26	        'card-gradient': 'linear-gradient(135deg, #7C5CFC 0%, #F472B6 100%)',
27	      },
28	      animation: {
29	        'fade-in':    'fadeIn 0.2s ease-out',
30	        'slide-up':   'slideUp 0.25s ease-out',
31	        'pulse-slow': 'pulse 3s cubic-bezier(0.4,0,0.6,1) infinite',
32	      },
33	      keyframes: {
34	        fadeIn:  { from: { opacity: '0' },                      to: { opacity: '1' } },
35	        slideUp: { from: { opacity: '0', transform: 'translateY(8px)' }, to: { opacity: '1', transform: 'translateY(0)' } },
36	      },
37	    },
38	  },
39	  plugins: [],
40	};
41

const path = require('path');

/** @type {import('tailwindcss').Config} */
module.exports = {
  darkMode: 'class',
  content: [
    path.join(__dirname, './src/**/*.{js,jsx,ts,tsx}'),
    path.join(__dirname, './index.html'),
  ],
  theme: {
    extend: {
      fontFamily: {
        sans: ['Inter', 'ui-sans-serif', 'system-ui', 'sans-serif'],
      },
      colors: {
        primary: { DEFAULT: '#7C5CFC', light: '#9B7EFE', dark: '#5B3FD4', soft: '#EDE9FF' },
        brand:   { purple: '#7C5CFC', pink: '#F472B6', orange: '#FB923C', teal: '#2DD4BF' },
      },
      boxShadow: {
        card:  '0 2px 8px rgba(0,0,0,0.06)',
        hover: '0 8px 24px rgba(0,0,0,0.12)',
        glow:  '0 0 24px rgba(124,92,252,0.25)',
      },
      backgroundImage: {
        'hero-gradient': 'linear-gradient(135deg, #ddd6fe 0%, #f5d0fe 45%, #fed7aa 100%)',
        'card-gradient': 'linear-gradient(135deg, #7C5CFC 0%, #F472B6 100%)',
      },
      animation: {
        'fade-in':    'fadeIn 0.2s ease-out',
        'slide-up':   'slideUp 0.25s ease-out',
        'pulse-slow': 'pulse 3s cubic-bezier(0.4,0,0.6,1) infinite',
      },
      keyframes: {
        fadeIn:  { from: { opacity: '0' },                      to: { opacity: '1' } },
        slideUp: { from: { opacity: '0', transform: 'translateY(8px)' }, to: { opacity: '1', transform: 'translateY(0)' } },
      },
    },
  },
  plugins: [],
};


1	import { useState, useEffect } from 'react';
2	import { X } from 'lucide-react';
3	import { CATEGORIES, PRIORITIES, STATUSES } from '../utils';
4
5	const EMPTY = {
6	  title: '', notes: '', project_id: null, category: 'adhoc', priority: 'Medium',
7	  status: 'Open', assignee: '', due_date: '', is_follow_up: false, is_today_focus: false,
8	};
9
10	export default function TaskModal({ task, projects = [], onSave, onClose }) {
11	  const [form, setForm] = useState(EMPTY);
12	  const isEdit = !!task?.id;
13
14	  useEffect(() => {
15	    if (task) {
16	      setForm({
17	        ...EMPTY,
18	        ...task,
19	        is_follow_up: !!task.is_follow_up,
20	        is_today_focus: !!task.is_today_focus,
21	      });
22	    }
23	  }, [task]);
24
25	  const set = (k, v) => setForm(f => ({ ...f, [k]: v }));
26
27	  const handleSubmit = (e) => {
28	    e.preventDefault();
29	    if (!form.title.trim()) return;
30	    onSave(form);
31	  };
32
33	  return (
34

35

36
 e.stopPropagation()}
39	      >
40	        {/* Header */}
41

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

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

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

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

54
55	          {/* Notes */}
56

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

60
61	          {/* Row: Category + Priority */}
62

63

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

71

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

77

78
79	          {/* Row: Status + Assignee */}
80

81

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

87

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

91

92
93	          {/* Row: Due date + Project */}
94

95

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

99

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

106

107
108	          {/* Checkboxes */}
109

110
111	              <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)} />
112	              Follow-up needed
113
114
115	              <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)} />
116	              Today's focus
117
118

119
120	          {/* Actions */}
121

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

125	        </form>
126

127

128	  );
129	}
130

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>

  );
}


node_modules/supports-preserve-symlinks-flag/.eslintrc
node_modules/is-core-module/.eslintrc
node_modules/call-bound/.eslintrc
node_modules/object-inspect/.eslintrc
node_modules/minimist/.eslintrc
node_modules/dunder-proto/.eslintrc
node_modules/math-intrinsics/.eslintrc
node_modules/get-proto/.eslintrc
node_modules/has-symbols/.eslintrc
node_modules/call-bind-apply-helpers/.eslintrc
node_modules/gopd/.eslintrc
node_modules/function-bind/.eslintrc
node_modules/es-errors/.eslintrc
node_modules/es-object-atoms/.eslintrc
node_modules/get-intrinsic/.eslintrc
node_modules/es-define-property/.eslintrc
node_modules/es-set-tostringtag/.eslintrc
node_modules/has-tostringtag/.eslintrc
node_modules/hasown/.eslintrc
node_modules/resolve/.eslintrc
node_modules/side-channel-list/.eslintrc
node_modules/side-channel-map/.eslintrc
node_modules/side-channel/.eslintrc
node_modules/side-channel-weakmap/.eslintrc
node_modules/function-bind/test/.eslintrc
node_modules/shell-quote/.eslintrc
node_modules/send/node_modules/debug/.eslintrc
node_modules/body-parser/node_modules/debug/.eslintrc
node_modules/express/node_modules/debug/.eslintrc
node_modules/finalhandler/node_modules/debug/.eslintrc


1	@tailwind base;
2	@tailwind components;
3	@tailwind utilities;
4
5	@layer base {
6	  * { -webkit-tap-highlight-color: transparent; }
7	  body { @apply font-sans antialiased bg-[#F0F2F5] text-gray-900 dark:bg-gray-950 dark:text-gray-100; }
8	  ::-webkit-scrollbar       { @apply w-1 h-1; }
9	  ::-webkit-scrollbar-track { @apply bg-transparent; }
10	  ::-webkit-scrollbar-thumb { @apply bg-gray-300 dark:bg-gray-700 rounded-full; }
11	}
12
13	@layer components {
14	  /* Cards */
15	  .card          { @apply bg-white dark:bg-gray-900 border border-gray-100 dark:border-gray-800 rounded-2xl shadow-card; }
16	  .card-hover    { @apply card transition-all duration-200 hover:shadow-hover hover:-translate-y-0.5 cursor-pointer; }
17
18	  /* Buttons */
19	  .btn-primary   { @apply bg-primary text-white px-4 py-2 rounded-xl text-sm font-medium hover:bg-primary-light active:bg-primary-dark transition-all duration-150 disabled:opacity-40 disabled:cursor-not-allowed shadow-sm; }
20	  .btn-secondary { @apply bg-white dark:bg-gray-800 text-gray-700 dark:text-gray-300 border border-gray-200 dark:border-gray-700 px-4 py-2 rounded-xl text-sm font-medium hover:bg-gray-50 dark:hover:bg-gray-700 transition-all duration-150; }
21	  .btn-ghost     { @apply text-gray-500 dark:text-gray-400 px-3 py-1.5 rounded-xl text-sm hover:bg-gray-100 dark:hover:bg-gray-800 transition-colors duration-150; }
22	  .btn-danger    { @apply bg-red-500 text-white px-4 py-2 rounded-xl text-sm font-medium hover:bg-red-600 transition-colors; }
23	  .btn-icon      { @apply p-2 rounded-xl text-gray-500 hover:bg-gray-100 dark:hover:bg-gray-800 hover:text-gray-800 dark:hover:text-white transition-all duration-150; }
24
25	  /* Inputs */
26	  .input  { @apply w-full px-3.5 py-2.5 text-sm border border-gray-200 dark:border-gray-700 rounded-xl bg-white dark:bg-gray-900 text-gray-900 dark:text-gray-100 placeholder-gray-400 focus:outline-none focus:ring-2 focus:ring-primary/30 focus:border-primary transition-all duration-150; }
27	  .select { @apply input pr-8 appearance-none cursor-pointer; }
28
29	  /* Badges */
30	  .badge { @apply inline-flex items-center px-2.5 py-1 rounded-full text-xs font-medium; }
31
32	  /* Navigation */
33	  .nav-item         { @apply flex items-center gap-3 px-3 py-2 rounded-xl text-sm font-medium text-gray-600 dark:text-gray-400 hover:bg-gray-50 dark:hover:bg-gray-800 hover:text-gray-900 dark:hover:text-white transition-all duration-150; }
34	  .nav-item-active  { @apply nav-item bg-primary/10 text-primary dark:text-primary-light; }
35	  .nav-icon         { @apply flex items-center justify-center w-9 h-9 rounded-xl text-gray-400 hover:bg-gray-100 dark:hover:bg-gray-800 hover:text-gray-700 dark:hover:text-white cursor-pointer transition-all duration-150; }
36	  .nav-icon-active  { @apply nav-icon bg-primary/10 text-primary; }
37
38	  /* Kanban */
39	  .kanban-col       { @apply flex flex-col bg-[#F7F7FB] dark:bg-gray-800/50 rounded-2xl p-3 min-w-[270px] w-[270px] max-h-[calc(100vh-260px)] overflow-y-auto; }
40	  .kanban-card      { @apply bg-white dark:bg-gray-900 rounded-xl border border-gray-100 dark:border-gray-800 p-4 shadow-card hover:shadow-hover transition-all duration-200 cursor-pointer hover:-translate-y-0.5 animate-fade-in; }
41
42	  /* Misc */
43	  .page-title       { @apply text-lg font-semibold text-gray-900 dark:text-white; }
44	  .section-label    { @apply text-[10px] font-bold text-gray-400 dark:text-gray-500 uppercase tracking-widest mb-2; }
45	  .skeleton         { @apply animate-pulse bg-gray-100 dark:bg-gray-800 rounded-xl; }
46	  .divider          { @apply border-t border-gray-100 dark:border-gray-800; }
47
48	  /* Priority left border */
49	  .priority-urgent  { @apply border-l-4 border-l-red-500; }
50	  .priority-high    { @apply border-l-4 border-l-amber-400; }
51	  .priority-medium  { @apply border-l-4 border-l-blue-400; }
52	  .priority-low     { @apply border-l-4 border-l-green-400; }
53
54	  /* Hero gradient blobs */
55	  .blob {
56	    @apply absolute rounded-full opacity-40 blur-3xl pointer-events-none;
57	    animation: float 6s ease-in-out infinite;
58	  }
59	  @keyframes float {
60	    0%, 100% { transform: translateY(0px) scale(1); }
61	    50%       { transform: translateY(-10px) scale(1.05); }
62	  }
63	}
64

@tailwind base;
@tailwind components;
@tailwind utilities;

@layer base {
  * { -webkit-tap-highlight-color: transparent; }
  body { @apply font-sans antialiased bg-[#F0F2F5] text-gray-900 dark:bg-gray-950 dark:text-gray-100; }
  ::-webkit-scrollbar       { @apply w-1 h-1; }
  ::-webkit-scrollbar-track { @apply bg-transparent; }
  ::-webkit-scrollbar-thumb { @apply bg-gray-300 dark:bg-gray-700 rounded-full; }
}

@layer components {
  /* Cards */
  .card          { @apply bg-white dark:bg-gray-900 border border-gray-100 dark:border-gray-800 rounded-2xl shadow-card; }
  .card-hover    { @apply card transition-all duration-200 hover:shadow-hover hover:-translate-y-0.5 cursor-pointer; }

  /* Buttons */
  .btn-primary   { @apply bg-primary text-white px-4 py-2 rounded-xl text-sm font-medium hover:bg-primary-light active:bg-primary-dark transition-all duration-150 disabled:opacity-40 disabled:cursor-not-allowed shadow-sm; }
  .btn-secondary { @apply bg-white dark:bg-gray-800 text-gray-700 dark:text-gray-300 border border-gray-200 dark:border-gray-700 px-4 py-2 rounded-xl text-sm font-medium hover:bg-gray-50 dark:hover:bg-gray-700 transition-all duration-150; }
  .btn-ghost     { @apply text-gray-500 dark:text-gray-400 px-3 py-1.5 rounded-xl text-sm hover:bg-gray-100 dark:hover:bg-gray-800 transition-colors duration-150; }
  .btn-danger    { @apply bg-red-500 text-white px-4 py-2 rounded-xl text-sm font-medium hover:bg-red-600 transition-colors; }
  .btn-icon      { @apply p-2 rounded-xl text-gray-500 hover:bg-gray-100 dark:hover:bg-gray-800 hover:text-gray-800 dark:hover:text-white transition-all duration-150; }

  /* Inputs */
  .input  { @apply w-full px-3.5 py-2.5 text-sm border border-gray-200 dark:border-gray-700 rounded-xl bg-white dark:bg-gray-900 text-gray-900 dark:text-gray-100 placeholder-gray-400 focus:outline-none focus:ring-2 focus:ring-primary/30 focus:border-primary transition-all duration-150; }
  .select { @apply input pr-8 appearance-none cursor-pointer; }

  /* Badges */
  .badge { @apply inline-flex items-center px-2.5 py-1 rounded-full text-xs font-medium; }

  /* Navigation */
  .nav-item         { @apply flex items-center gap-3 px-3 py-2 rounded-xl text-sm font-medium text-gray-600 dark:text-gray-400 hover:bg-gray-50 dark:hover:bg-gray-800 hover:text-gray-900 dark:hover:text-white transition-all duration-150; }
  .nav-item-active  { @apply nav-item bg-primary/10 text-primary dark:text-primary-light; }
  .nav-icon         { @apply flex items-center justify-center w-9 h-9 rounded-xl text-gray-400 hover:bg-gray-100 dark:hover:bg-gray-800 hover:text-gray-700 dark:hover:text-white cursor-pointer transition-all duration-150; }
  .nav-icon-active  { @apply nav-icon bg-primary/10 text-primary; }

  /* Kanban */
  .kanban-col       { @apply flex flex-col bg-[#F7F7FB] dark:bg-gray-800/50 rounded-2xl p-3 min-w-[270px] w-[270px] max-h-[calc(100vh-260px)] overflow-y-auto; }
  .kanban-card      { @apply bg-white dark:bg-gray-900 rounded-xl border border-gray-100 dark:border-gray-800 p-4 shadow-card hover:shadow-hover transition-all duration-200 cursor-pointer hover:-translate-y-0.5 animate-fade-in; }

  /* Misc */
  .page-title       { @apply text-lg font-semibold text-gray-900 dark:text-white; }
  .section-label    { @apply text-[10px] font-bold text-gray-400 dark:text-gray-500 uppercase tracking-widest mb-2; }
  .skeleton         { @apply animate-pulse bg-gray-100 dark:bg-gray-800 rounded-xl; }
  .divider          { @apply border-t border-gray-100 dark:border-gray-800; }

  /* Priority left border */
  .priority-urgent  { @apply border-l-4 border-l-red-500; }
  .priority-high    { @apply border-l-4 border-l-amber-400; }
  .priority-medium  { @apply border-l-4 border-l-blue-400; }
  .priority-low     { @apply border-l-4 border-l-green-400; }

  /* Hero gradient blobs */
  .blob {
    @apply absolute rounded-full opacity-40 blur-3xl pointer-events-none;
    animation: float 6s ease-in-out infinite;
  }
  @keyframes float {
    0%, 100% { transform: translateY(0px) scale(1); }
    50%       { transform: translateY(-10px) scale(1.05); }
  }
}


Let me also check the remaining components I haven't read.


1	import { useState, useEffect, useRef } from 'react';
2	import { Search as SearchIcon, X, CheckSquare, FolderKanban, FileText } from 'lucide-react';
3	import { useNavigate } from 'react-router-dom';
4	import api from '../api';
5	import { getCategoryClass, getPriorityClass, getStatusClass, CATEGORIES } from '../utils';
6
7	function useDebounce(v, d = 300) {
8	  const [dv, setDv] = useState(v);
9	  useEffect(() => { const t = setTimeout(() => setDv(v), d); return () => clearTimeout(t); }, [v, d]);
10	  return dv;
11	}
12
13	export default function Search({ onClose }) {
14	  const [query, setQuery] = useState('');
15	  const [results, setResults] = useState(null);
16	  const [loading, setLoading] = useState(false);
17	  const inputRef = useRef(null);
18	  const navigate = useNavigate();
19	  const dq = useDebounce(query);
20
21	  useEffect(() => { inputRef.current?.focus(); }, []);
22
23	  useEffect(() => {
24	    if (!dq.trim() || dq.trim().length < 2) { setResults(null); return; }
25	    setLoading(true);
26	    api.get(`/search?q=${encodeURIComponent(dq)}`)
27	      .then(setResults)
28	      .finally(() => setLoading(false));
29	  }, [dq]);
30
31	  const total = results ? results.tasks.length + results.projects.length + results.notes.length : 0;
32
33	  const go = (path) => { navigate(path); onClose(); };
34
35	  return (
36

37

38
 e.stopPropagation()}
41	      >
42	        {/* Input */}
43

44	          <SearchIcon size={16} className="text-gray-400 shrink-0" />
45	          <input
46	            ref={inputRef}
47	            className="flex-1 text-sm bg-transparent border-none outline-none text-gray-900 dark:text-white placeholder-gray-400"
48	            placeholder="Search tasks, projects, notes…"
49	            value={query}
50	            onChange={e => setQuery(e.target.value)}
51	          />
52	          {query && (
53	            <button onClick={() => setQuery('')} className="p-0.5 text-gray-400 hover:text-gray-600 dark:hover:text-gray-200">
54	              <X size={14} />
55	            </button>
56	          )}
57	          Esc
58

59
60	        {/* Results */}
61

62	          {loading && (
63

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

66	          )}
67
68	          {!loading && results && total === 0 && (
69

70
No results for "{query}"

71

72	          )}
73
74	          {!loading && results && total > 0 && (
75

76	              {results.tasks.length > 0 && (
77

78

79	                    <CheckSquare size={11} /> Tasks
80

81	                  {results.tasks.map(t => (
82	                    <button
83	                      key={t.id}
84	                      onClick={() => go('/tasks')}
85	                      className="w-full text-left flex items-center gap-3 px-3 py-2.5 rounded-lg hover:bg-gray-100 dark:hover:bg-gray-800 transition-colors"
86	                    >
87

88
{t.title}

89

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

93

94	                    </button>
95	                  ))}
96

97	              )}
98
99	              {results.projects.length > 0 && (
100

101

102	                    <FolderKanban size={11} /> Projects
103

104	                  {results.projects.map(p => (
105	                    <button
106	                      key={p.id}
107	                      onClick={() => go(`/projects/${p.id}`)}
108	                      className="w-full text-left flex items-center gap-3 px-3 py-2.5 rounded-lg hover:bg-gray-100 dark:hover:bg-gray-800 transition-colors"
109	                    >
110

111
{p.name}

112

113	                          <span className={`badge ${getCategoryClass(p.category)}`}>{CATEGORIES[p.category]?.label || p.category}
114	                          <span className={`badge ${getStatusClass(p.status)}`}>{p.status}
115

116

117	                    </button>
118	                  ))}
119

120	              )}
121
122	              {results.notes.length > 0 && (
123

124

125	                    <FileText size={11} /> Notes
126

127	                  {results.notes.map(n => (
128	                    <button
129	                      key={n.id}
130	                      onClick={() => go('/notes')}
131	                      className="w-full text-left flex items-center gap-3 px-3 py-2.5 rounded-lg hover:bg-gray-100 dark:hover:bg-gray-800 transition-colors"
132	                    >
133

134
{n.title}

135	                        {n.body &&
{n.body}
}
136

137	                    </button>
138	                  ))}
139

140	              )}
141

142	          )}
143
144	          {!query && (
145

146
Start typing to search across all your workspace

147
Tasks · Projects · Notes

148

149	          )}
150

151

152

153	  );
154	}
155

import { useState, useEffect, useRef } from 'react';
import { Search as SearchIcon, X, CheckSquare, FolderKanban, FileText } from 'lucide-react';
import { useNavigate } from 'react-router-dom';
import api from '../api';
import { getCategoryClass, getPriorityClass, getStatusClass, CATEGORIES } from '../utils';

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

export default function Search({ onClose }) {
  const [query, setQuery] = useState('');
  const [results, setResults] = useState(null);
  const [loading, setLoading] = useState(false);
  const inputRef = useRef(null);
  const navigate = useNavigate();
  const dq = useDebounce(query);

  useEffect(() => { inputRef.current?.focus(); }, []);

  useEffect(() => {
    if (!dq.trim() || dq.trim().length < 2) { setResults(null); return; }
    setLoading(true);
    api.get(`/search?q=${encodeURIComponent(dq)}`)
      .then(setResults)
      .finally(() => setLoading(false));
  }, [dq]);

  const total = results ? results.tasks.length + results.projects.length + results.notes.length : 0;

  const go = (path) => { navigate(path); onClose(); };

  return (

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

          <SearchIcon size={16} className="text-gray-400 shrink-0" />
          <input
            ref={inputRef}
            className="flex-1 text-sm bg-transparent border-none outline-none text-gray-900 dark:text-white placeholder-gray-400"
            placeholder="Search tasks, projects, notes…"
            value={query}
            onChange={e => setQuery(e.target.value)}
          />
          {query && (
            <button onClick={() => setQuery('')} className="p-0.5 text-gray-400 hover:text-gray-600 dark:hover:text-gray-200">
              <X size={14} />
            </button>
          )}
          Esc

        {/* Results */}

          {loading && (

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

          )}

          {!loading && results && total === 0 && (

No results for "{query}"

          )}

          {!loading && results && total > 0 && (

              {results.tasks.length > 0 && (

                    <CheckSquare size={11} /> Tasks

                  {results.tasks.map(t => (
                    <button
                      key={t.id}
                      onClick={() => go('/tasks')}
                      className="w-full text-left flex items-center gap-3 px-3 py-2.5 rounded-lg hover:bg-gray-100 dark:hover:bg-gray-800 transition-colors"
                    >

{t.title}

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

                    </button>
                  ))}

              )}

              {results.projects.length > 0 && (

                    <FolderKanban size={11} /> Projects

                  {results.projects.map(p => (
                    <button
                      key={p.id}
                      onClick={() => go(`/projects/${p.id}`)}
                      className="w-full text-left flex items-center gap-3 px-3 py-2.5 rounded-lg hover:bg-gray-100 dark:hover:bg-gray-800 transition-colors"
                    >

{p.name}

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

                    </button>
                  ))}

              )}

              {results.notes.length > 0 && (

                    <FileText size={11} /> Notes

                  {results.notes.map(n => (
                    <button
                      key={n.id}
                      onClick={() => go('/notes')}
                      className="w-full text-left flex items-center gap-3 px-3 py-2.5 rounded-lg hover:bg-gray-100 dark:hover:bg-gray-800 transition-colors"
                    >

{n.title}

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

                    </button>
                  ))}

              )}

          )}

          {!query && (

Start typing to search across all your workspace

Tasks · Projects · Notes

          )}

  );
}


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

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

29	          </button>
30	        )}
31
32

33	          {/* Title */}
34

39	            {task.title}
40

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

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

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

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

62	              {task.assignee && (
63

64

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

67	                  {task.assignee}
68

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

80	          )}
81

82
83	        {/* Actions */}
84

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

96

97

98	  );
99	}
100

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

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

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

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

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

          </button>
        )}

          {/* Title */}

            {task.title}

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

{task.notes}

          )}

          {/* Badges */}

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

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

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

              {task.assignee && (

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

                  {task.assignee}

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

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

          )}

        {/* Actions */}

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

  );
}


Now I have a complete picture. Here are the issues across the files:

1. **`main.jsx`** — duplicate `<Toaster>` (also in `App.jsx`)
2. **`App.jsx`** — unused `RIGHT_PANEL_ROUTES` constant
3. **`Today.jsx`** — unused `stats` prop on `DroppableColumn`, double `;;` semicolon
4. **`Tasks.jsx`** — unused `useRef` import
5. **`ProjectDetail.jsx`** — loose comparison `!=` between number and string

Fixing all of them now.


Edit

import React from 'react';
import ReactDOM from 'react-dom/client';
import { Toaster } from 'react-hot-toast';
import App from './App.jsx';
import './index.css';

ReactDOM.createRoot(document.getElementById('root')).render(
  <React.StrictMode>
    <App />
    <Toaster
      position="top-right"
      toastOptions={{
        duration: 3000,
        style: {
          borderRadius: '8px',
          fontSize: '14px',
        },
      }}
    />
  </React.StrictMode>
);

import React from 'react';
import ReactDOM from 'react-dom/client';
import App from './App.jsx';
import './index.css';

ReactDOM.createRoot(document.getElementById('root')).render(
  <React.StrictMode>
    <App />
  </React.StrictMode>
);


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

import React from 'react';
import ReactDOM from 'react-dom/client';
import { Toaster } from 'react-hot-toast';
import App from './App.jsx';
import './index.css';

ReactDOM.createRoot(document.getElementById('root')).render(
  <React.StrictMode>
    <App />
    <Toaster
      position="top-right"
      toastOptions={{
        duration: 3000,
        style: {
          borderRadius: '8px',
          fontSize: '14px',
        },
      }}
    />
  </React.StrictMode>
);

import React from 'react';
import ReactDOM from 'react-dom/client';
import App from './App.jsx';
import './index.css';

ReactDOM.createRoot(document.getElementById('root')).render(
  <React.StrictMode>
    <App />
  </React.StrictMode>
);

-import { Toaster } from 'react-hot-toast';


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

export default function App() {


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

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

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

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

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

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

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

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

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

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

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

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

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

-const RIGHT_PANEL_ROUTES = ['/today', '/'];


function DroppableColumn({ col, tasks, stats, onAddTask, onEdit, onDelete, onMarkDone }) {

function DroppableColumn({ col, tasks, onAddTask, onEdit, onDelete, onMarkDone }) {


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

import { useState, useEffect, useCallback } from 'react';
import {
  DndContext, DragOverlay, useDroppable, useDraggable,
  PointerSensor, TouchSensor, useSensor, useSensors, closestCenter
} from '@dnd-kit/core';
import {
  Plus, User, Calendar, ArrowRight, FolderKanban,
  CheckSquare, Bell, Target, AlertTriangle, MoreHorizontal, GripVertical, PenLine, Send
} from 'lucide-react';
import { Link, useNavigate } from 'react-router-dom';
import toast from 'react-hot-toast';
import api from '../api';
import TaskModal from './TaskModal';
import ConfirmDialog from './ConfirmDialog';
import { getCategoryClass, getPriorityClass, CATEGORIES, formatDate, isOverdue } from '../utils';

// ── Column config ────────────────────────────────────────────────────────────
const COLUMNS = [
  { id: 'focus',      title: "Today's Focus", color: 'text-primary',    bg: 'bg-primary/10',  border: 'border-l-primary'  },
  { id: 'inprogress', title: 'In Progress',   color: 'text-blue-600',   bg: 'bg-blue-50 dark:bg-blue-900/20',   border: 'border-l-blue-400'   },
  { id: 'followups',  title: 'Follow-ups',    color: 'text-amber-600',  bg: 'bg-amber-50 dark:bg-amber-900/20', border: 'border-l-amber-400'  },
  { id: 'open',       title: 'Open',          color: 'text-gray-500',   bg: 'bg-gray-100 dark:bg-gray-800',     border: 'border-l-gray-300'   },
];

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

function getColumnId(task) {
  if (task.status === 'Done') return null;
  if (task.is_today_focus)    return 'focus';
  if (task.status === 'In Progress') return 'inprogress';
  if (task.is_follow_up)      return 'followups';
  return 'open';
}

function columnUpdates(col, task) {
  const base = { ...task };
  if (col === 'focus')      return { ...base, is_today_focus: 1 };
  if (col === 'inprogress') return { ...base, status: 'In Progress', is_today_focus: 0 };
  if (col === 'followups')  return { ...base, is_follow_up: 1,  is_today_focus: 0 };
  if (col === 'open')       return { ...base, is_today_focus: 0, status: 'Open' };
  return base;
}

// ── Draggable card ───────────────────────────────────────────────────────────
function DraggableCard({ task, onEdit, onDelete, onMarkDone }) {
  const { attributes, listeners, setNodeRef, isDragging } = useDraggable({ id: task.id });
  const overdue = isOverdue(task.due_date);

  return (

          {/* Drag handle */}
          <button {...listeners} {...attributes}
            className="shrink-0 mt-0.5 text-gray-300 hover:text-gray-500 cursor-grab active:cursor-grabbing touch-none">
            <GripVertical size={14} />
          </button>

{task.title}

              <button onClick={() => onEdit(task)}
                className="shrink-0 opacity-0 group-hover:opacity-100 p-1 rounded-lg hover:bg-gray-100 dark:hover:bg-gray-800 text-gray-400 transition-all">
                <MoreHorizontal size={13} />
              </button>

            {task.notes &&
{task.notes}
}

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

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

                {task.assignee ? (

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

                ) :
<User size={9} className="text-gray-400" />
}
                {task.assignee && {task.assignee}}

                {task.due_date && (
                  <span className={`flex items-center gap-0.5 text-[10px] ${overdue ? 'text-red-500 font-semibold' : 'text-gray-400'}`}>
                    {overdue && <AlertTriangle size={9} />}{formatDate(task.due_date)}

                )}
                <button onClick={() => onMarkDone(task)}
                  className="opacity-0 group-hover:opacity-100 text-[10px] px-2 py-0.5 bg-green-100 text-green-700 dark:bg-green-900/30 dark:text-green-400 rounded-full hover:bg-green-200 transition-all font-medium">
                  Done
                </button>

  );
}

// ── Drag overlay card (ghost while dragging) ─────────────────────────────────
function OverlayCard({ task }) {
  return (

{task.title}

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

  );
}

// ── Droppable column ─────────────────────────────────────────────────────────
function DroppableColumn({ col, tasks, stats, onAddTask, onEdit, onDelete, onMarkDone }) {
  const { setNodeRef, isOver } = useDroppable({ id: col.id });

  return (

      {/* Column header */}

{col.title}

          <span className={`text-xs font-bold px-2 py-0.5 rounded-full ${col.bg} ${col.color}`}>{tasks.length}

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

      {/* Cards */}

        {tasks.length === 0 ? (

Drop here

        ) : (
          tasks.map(task => (
            <DraggableCard key={task.id} task={task} onEdit={onEdit} onDelete={onDelete} onMarkDone={onMarkDone} />
          ))
        )}

  );
}

// ── Main Dashboard ───────────────────────────────────────────────────────────
export default function Today() {
  const [allTasks, setAllTasks]     = 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 [showNew, setShowNew]       = useState(false);
  const [newDefaults, setNewDefaults] = useState({});
  const [activeTask, setActiveTask] = useState(null);
  const [activeTab, setActiveTab]   = useState('dashboard');
  const [quickInput, setQuickInput] = useState('');
  const navigate = useNavigate();

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

  const load = useCallback(async () => {
    try {
      const [tasks, st, pr] = await Promise.all([api.get('/tasks'), api.get('/tasks/stats'), api.get('/projects')]);
      setAllTasks(tasks); setStats(st); setProjects(pr);
    } finally { setLoading(false); }
  }, []);

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

  // Bucket tasks into columns
  const columns = COLUMNS.map(col => ({
    ...col,
    tasks: allTasks.filter(t => getColumnId(t) === col.id),
  }));

  // DnD sensors — support mouse, touch (mobile), delay to prevent accidental drags
  const sensors = useSensors(
    useSensor(PointerSensor, { activationConstraint: { distance: 8 } }),
    useSensor(TouchSensor,   { activationConstraint: { delay: 180, tolerance: 6 } })
  );

  const onDragStart = ({ active }) => {
    setActiveTask(allTasks.find(t => t.id === active.id) || null);
  };

  const onDragEnd = async ({ active, over }) => {
    setActiveTask(null);
    if (!over) return;
    const task = allTasks.find(t => t.id === active.id);
    if (!task) return;
    const targetCol = COLUMNS.find(c => c.id === over.id);
    if (!targetCol || getColumnId(task) === targetCol.id) return;

    // Optimistic update
    const updated = columnUpdates(targetCol.id, task);
    setAllTasks(prev => prev.map(t => t.id === task.id ? { ...t, ...updated } : t));
    try {
      await api.put(`/tasks/${task.id}`, updated);
      toast.success(`Moved to "${targetCol.title}"`);
    } catch (e) {
      toast.error('Failed to move task'); load();
    }
  };

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

  const openNew = (defaults = {}) => { setNewDefaults(defaults); setShowNew(true); }

  const quickCapture = async (e) => {
    e.preventDefault();
    const title = quickInput.trim();
    if (!title) return;
    try {
      await api.post('/tasks', { title, priority: 'Medium', status: 'Open', category: 'adhoc', is_today_focus: 1 });
      setQuickInput('');
      toast.success('Task added to Today\'s Focus');
      load();
    } catch { toast.error('Failed to add task'); }
  };;

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

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

  const TABS = [
    { id: 'dashboard', label: 'Dashboard' },
    { id: 'all',       label: 'All Tasks'      },
    { id: 'projects',  label: 'Projects'       },
  ];

  return (

      {/* Hero banner */}

DEWA AI Workspace

{dateLabel}

            {[
              { label: 'Projects',     value: stats?.activeProjects ?? '—', Icon: FolderKanban },
              { label: 'Open Tasks',   value: stats?.openTasks      ?? '—', Icon: CheckSquare  },
              { label: 'Follow-ups',   value: stats?.openFollowUps  ?? '—', Icon: Bell         },
              { label: "Today's Focus",value: stats?.todayFocus     ?? '—', Icon: Target       },
            ].map(({ label, value, Icon }) => (

{value}

                  <Icon size={9} />{label}

            ))}

      {/* Quick Capture bar */}

        <form onSubmit={quickCapture} className="flex items-center gap-2 bg-white dark:bg-gray-900 border border-gray-200 dark:border-gray-700 rounded-2xl px-3 py-2 shadow-card focus-within:ring-2 focus-within:ring-primary/30 focus-within:border-primary/50 transition-all">
          <PenLine size={14} className="text-gray-400 shrink-0" />
          <input
            className="flex-1 bg-transparent text-sm text-gray-800 dark:text-gray-100 placeholder-gray-400 outline-none"
            placeholder="Capture a quick task… (Enter to add to Today's Focus)"
            value={quickInput}
            onChange={e => setQuickInput(e.target.value)}
          />
          {quickInput.trim() && (
            <button type="submit" className="shrink-0 flex items-center gap-1 px-2.5 py-1 bg-primary text-white text-xs font-medium rounded-xl hover:bg-primary/90 transition-colors">
              <Send size={11} /> Add
            </button>
          )}
          <button type="button" onClick={() => navigate('/inbox')}
            className="shrink-0 text-[10px] text-gray-400 hover:text-primary transition-colors whitespace-nowrap">
            AI Parse →
          </button>
        </form>

      {/* Tabs */}

        {TABS.map(t => (
          <button key={t.id} onClick={() => setActiveTab(t.id)}
            className={`px-3.5 py-1.5 rounded-xl text-sm font-medium transition-all duration-150 ${
              activeTab === t.id
                ? 'bg-primary text-white shadow-sm'
                : 'text-gray-500 dark:text-gray-400 hover:bg-white dark:hover:bg-gray-800 hover:text-gray-800 dark:hover:text-white'
            }`}>
            {t.label}
          </button>
        ))}

        <button onClick={() => openNew({ is_today_focus: 1 })} className="btn-primary flex items-center gap-1 py-1.5 text-xs">
          <Plus size={12} /> New Task
        </button>

      {/* ── Kanban Board (drag & drop) ─────────────────────────────────── */}
      {loading ? (

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

      ) : (
        <>
          {activeTab === 'dashboard' && (
            <DndContext sensors={sensors} collisionDetection={closestCenter} onDragStart={onDragStart} onDragEnd={onDragEnd}>
              {/* Horizontal scrollable kanban — snaps on mobile */}

                {columns.map(col => (
                  <DroppableColumn
                    key={col.id} col={col} tasks={col.tasks}
                    onAddTask={() => openNew(
                      col.id === 'focus'      ? { is_today_focus: 1 } :
                      col.id === 'inprogress' ? { status: 'In Progress' } :
                      col.id === 'followups'  ? { is_follow_up: 1 } : {}
                    )}
                    onEdit={setEditTask}
                    onDelete={setDeleteTask}
                    onMarkDone={markDone}
                  />
                ))}

              {/* Drag overlay */}
              <DragOverlay dropAnimation={{ duration: 180, easing: 'cubic-bezier(0.18,0.67,0.6,1.22)' }}>
                {activeTask && <OverlayCard task={activeTask} />}
              </DragOverlay>
            </DndContext>
          )}

          {/* ALL TASKS — horizontal grid */}
          {activeTab === 'all' && (

                {allTasks.filter(t => t.status !== 'Done').map(task => (

 setEditTask(task)}>

{task.title}

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

                    {task.assignee && (

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

                        {task.assignee}

                    )}

                ))}

          )}

          {/* PROJECTS — horizontal grid */}
          {activeTab === 'projects' && (

                {projects.filter(p => p.status !== 'Done').map(p => (
                  <button key={p.id} onClick={() => navigate(`/projects/${p.id}`)}
                    className="card-hover text-left p-4 group">

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

                      <ArrowRight size={12} className="text-gray-300 group-hover:text-primary transition-colors" />

{p.name}

                      <span className={`font-semibold ${p.priority === 'Urgent' ? 'text-red-500' : p.priority === 'High' ? 'text-amber-500' : ''}`}>{p.priority}
                      {p.open_task_count || 0} tasks

                  </button>
                ))}

          )}
        </>
      )}

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

  );
}

-function DroppableColumn({ col, tasks, stats, onAddTask, onEdit, onDelete, onMarkDone }) {

+function DroppableColumn({ col, tasks, onAddTask, onEdit, onDelete, onMarkDone }) {


    } catch { toast.error('Failed to add task'); }
  };;

    } catch { toast.error('Failed to add task'); }
  };


import { useState, useEffect, useCallback } from 'react';
import {
  DndContext, DragOverlay, useDroppable, useDraggable,
  PointerSensor, TouchSensor, useSensor, useSensors, closestCenter
} from '@dnd-kit/core';
import {
  Plus, User, Calendar, ArrowRight, FolderKanban,
  CheckSquare, Bell, Target, AlertTriangle, MoreHorizontal, GripVertical, PenLine, Send
} from 'lucide-react';
import { Link, useNavigate } from 'react-router-dom';
import toast from 'react-hot-toast';
import api from '../api';
import TaskModal from './TaskModal';
import ConfirmDialog from './ConfirmDialog';
import { getCategoryClass, getPriorityClass, CATEGORIES, formatDate, isOverdue } from '../utils';

// ── Column config ────────────────────────────────────────────────────────────
const COLUMNS = [
  { id: 'focus',      title: "Today's Focus", color: 'text-primary',    bg: 'bg-primary/10',  border: 'border-l-primary'  },
  { id: 'inprogress', title: 'In Progress',   color: 'text-blue-600',   bg: 'bg-blue-50 dark:bg-blue-900/20',   border: 'border-l-blue-400'   },
  { id: 'followups',  title: 'Follow-ups',    color: 'text-amber-600',  bg: 'bg-amber-50 dark:bg-amber-900/20', border: 'border-l-amber-400'  },
  { id: 'open',       title: 'Open',          color: 'text-gray-500',   bg: 'bg-gray-100 dark:bg-gray-800',     border: 'border-l-gray-300'   },
];

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

function getColumnId(task) {
  if (task.status === 'Done') return null;
  if (task.is_today_focus)    return 'focus';
  if (task.status === 'In Progress') return 'inprogress';
  if (task.is_follow_up)      return 'followups';
  return 'open';
}

function columnUpdates(col, task) {
  const base = { ...task };
  if (col === 'focus')      return { ...base, is_today_focus: 1 };
  if (col === 'inprogress') return { ...base, status: 'In Progress', is_today_focus: 0 };
  if (col === 'followups')  return { ...base, is_follow_up: 1,  is_today_focus: 0 };
  if (col === 'open')       return { ...base, is_today_focus: 0, status: 'Open' };
  return base;
}

// ── Draggable card ───────────────────────────────────────────────────────────
function DraggableCard({ task, onEdit, onDelete, onMarkDone }) {
  const { attributes, listeners, setNodeRef, isDragging } = useDraggable({ id: task.id });
  const overdue = isOverdue(task.due_date);

  return (

          {/* Drag handle */}
          <button {...listeners} {...attributes}
            className="shrink-0 mt-0.5 text-gray-300 hover:text-gray-500 cursor-grab active:cursor-grabbing touch-none">
            <GripVertical size={14} />
          </button>

{task.title}

              <button onClick={() => onEdit(task)}
                className="shrink-0 opacity-0 group-hover:opacity-100 p-1 rounded-lg hover:bg-gray-100 dark:hover:bg-gray-800 text-gray-400 transition-all">
                <MoreHorizontal size={13} />
              </button>

            {task.notes &&
{task.notes}
}

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

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

                {task.assignee ? (

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

                ) :
<User size={9} className="text-gray-400" />
}
                {task.assignee && {task.assignee}}

                {task.due_date && (
                  <span className={`flex items-center gap-0.5 text-[10px] ${overdue ? 'text-red-500 font-semibold' : 'text-gray-400'}`}>
                    {overdue && <AlertTriangle size={9} />}{formatDate(task.due_date)}

                )}
                <button onClick={() => onMarkDone(task)}
                  className="opacity-0 group-hover:opacity-100 text-[10px] px-2 py-0.5 bg-green-100 text-green-700 dark:bg-green-900/30 dark:text-green-400 rounded-full hover:bg-green-200 transition-all font-medium">
                  Done
                </button>

  );
}

// ── Drag overlay card (ghost while dragging) ─────────────────────────────────
function OverlayCard({ task }) {
  return (

{task.title}

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

  );
}

// ── Droppable column ─────────────────────────────────────────────────────────
function DroppableColumn({ col, tasks, onAddTask, onEdit, onDelete, onMarkDone }) {
  const { setNodeRef, isOver } = useDroppable({ id: col.id });

  return (

      {/* Column header */}

{col.title}

          <span className={`text-xs font-bold px-2 py-0.5 rounded-full ${col.bg} ${col.color}`}>{tasks.length}

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

      {/* Cards */}

        {tasks.length === 0 ? (

Drop here

        ) : (
          tasks.map(task => (
            <DraggableCard key={task.id} task={task} onEdit={onEdit} onDelete={onDelete} onMarkDone={onMarkDone} />
          ))
        )}

  );
}

// ── Main Dashboard ───────────────────────────────────────────────────────────
export default function Today() {
  const [allTasks, setAllTasks]     = 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 [showNew, setShowNew]       = useState(false);
  const [newDefaults, setNewDefaults] = useState({});
  const [activeTask, setActiveTask] = useState(null);
  const [activeTab, setActiveTab]   = useState('dashboard');
  const [quickInput, setQuickInput] = useState('');
  const navigate = useNavigate();

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

  const load = useCallback(async () => {
    try {
      const [tasks, st, pr] = await Promise.all([api.get('/tasks'), api.get('/tasks/stats'), api.get('/projects')]);
      setAllTasks(tasks); setStats(st); setProjects(pr);
    } finally { setLoading(false); }
  }, []);

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

  // Bucket tasks into columns
  const columns = COLUMNS.map(col => ({
    ...col,
    tasks: allTasks.filter(t => getColumnId(t) === col.id),
  }));

  // DnD sensors — support mouse, touch (mobile), delay to prevent accidental drags
  const sensors = useSensors(
    useSensor(PointerSensor, { activationConstraint: { distance: 8 } }),
    useSensor(TouchSensor,   { activationConstraint: { delay: 180, tolerance: 6 } })
  );

  const onDragStart = ({ active }) => {
    setActiveTask(allTasks.find(t => t.id === active.id) || null);
  };

  const onDragEnd = async ({ active, over }) => {
    setActiveTask(null);
    if (!over) return;
    const task = allTasks.find(t => t.id === active.id);
    if (!task) return;
    const targetCol = COLUMNS.find(c => c.id === over.id);
    if (!targetCol || getColumnId(task) === targetCol.id) return;

    // Optimistic update
    const updated = columnUpdates(targetCol.id, task);
    setAllTasks(prev => prev.map(t => t.id === task.id ? { ...t, ...updated } : t));
    try {
      await api.put(`/tasks/${task.id}`, updated);
      toast.success(`Moved to "${targetCol.title}"`);
    } catch (e) {
      toast.error('Failed to move task'); load();
    }
  };

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

  const openNew = (defaults = {}) => { setNewDefaults(defaults); setShowNew(true); }

  const quickCapture = async (e) => {
    e.preventDefault();
    const title = quickInput.trim();
    if (!title) return;
    try {
      await api.post('/tasks', { title, priority: 'Medium', status: 'Open', category: 'adhoc', is_today_focus: 1 });
      setQuickInput('');
      toast.success('Task added to Today\'s Focus');
      load();
    } catch { toast.error('Failed to add task'); }
  };;

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

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

  const TABS = [
    { id: 'dashboard', label: 'Dashboard' },
    { id: 'all',       label: 'All Tasks'      },
    { id: 'projects',  label: 'Projects'       },
  ];

  return (

      {/* Hero banner */}

DEWA AI Workspace

{dateLabel}

            {[
              { label: 'Projects',     value: stats?.activeProjects ?? '—', Icon: FolderKanban },
              { label: 'Open Tasks',   value: stats?.openTasks      ?? '—', Icon: CheckSquare  },
              { label: 'Follow-ups',   value: stats?.openFollowUps  ?? '—', Icon: Bell         },
              { label: "Today's Focus",value: stats?.todayFocus     ?? '—', Icon: Target       },
            ].map(({ label, value, Icon }) => (

{value}

                  <Icon size={9} />{label}

            ))}

      {/* Quick Capture bar */}

        <form onSubmit={quickCapture} className="flex items-center gap-2 bg-white dark:bg-gray-900 border border-gray-200 dark:border-gray-700 rounded-2xl px-3 py-2 shadow-card focus-within:ring-2 focus-within:ring-primary/30 focus-within:border-primary/50 transition-all">
          <PenLine size={14} className="text-gray-400 shrink-0" />
          <input
            className="flex-1 bg-transparent text-sm text-gray-800 dark:text-gray-100 placeholder-gray-400 outline-none"
            placeholder="Capture a quick task… (Enter to add to Today's Focus)"
            value={quickInput}
            onChange={e => setQuickInput(e.target.value)}
          />
          {quickInput.trim() && (
            <button type="submit" className="shrink-0 flex items-center gap-1 px-2.5 py-1 bg-primary text-white text-xs font-medium rounded-xl hover:bg-primary/90 transition-colors">
              <Send size={11} /> Add
            </button>
          )}
          <button type="button" onClick={() => navigate('/inbox')}
            className="shrink-0 text-[10px] text-gray-400 hover:text-primary transition-colors whitespace-nowrap">
            AI Parse →
          </button>
        </form>

      {/* Tabs */}

        {TABS.map(t => (
          <button key={t.id} onClick={() => setActiveTab(t.id)}
            className={`px-3.5 py-1.5 rounded-xl text-sm font-medium transition-all duration-150 ${
              activeTab === t.id
                ? 'bg-primary text-white shadow-sm'
                : 'text-gray-500 dark:text-gray-400 hover:bg-white dark:hover:bg-gray-800 hover:text-gray-800 dark:hover:text-white'
            }`}>
            {t.label}
          </button>
        ))}

        <button onClick={() => openNew({ is_today_focus: 1 })} className="btn-primary flex items-center gap-1 py-1.5 text-xs">
          <Plus size={12} /> New Task
        </button>

      {/* ── Kanban Board (drag & drop) ─────────────────────────────────── */}
      {loading ? (

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

      ) : (
        <>
          {activeTab === 'dashboard' && (
            <DndContext sensors={sensors} collisionDetection={closestCenter} onDragStart={onDragStart} onDragEnd={onDragEnd}>
              {/* Horizontal scrollable kanban — snaps on mobile */}

                {columns.map(col => (
                  <DroppableColumn
                    key={col.id} col={col} tasks={col.tasks}
                    onAddTask={() => openNew(
                      col.id === 'focus'      ? { is_today_focus: 1 } :
                      col.id === 'inprogress' ? { status: 'In Progress' } :
                      col.id === 'followups'  ? { is_follow_up: 1 } : {}
                    )}
                    onEdit={setEditTask}
                    onDelete={setDeleteTask}
                    onMarkDone={markDone}
                  />
                ))}

              {/* Drag overlay */}
              <DragOverlay dropAnimation={{ duration: 180, easing: 'cubic-bezier(0.18,0.67,0.6,1.22)' }}>
                {activeTask && <OverlayCard task={activeTask} />}
              </DragOverlay>
            </DndContext>
          )}

          {/* ALL TASKS — horizontal grid */}
          {activeTab === 'all' && (

                {allTasks.filter(t => t.status !== 'Done').map(task => (

 setEditTask(task)}>

{task.title}

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

                    {task.assignee && (

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

                        {task.assignee}

                    )}

                ))}

          )}

          {/* PROJECTS — horizontal grid */}
          {activeTab === 'projects' && (

                {projects.filter(p => p.status !== 'Done').map(p => (
                  <button key={p.id} onClick={() => navigate(`/projects/${p.id}`)}
                    className="card-hover text-left p-4 group">

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

                      <ArrowRight size={12} className="text-gray-300 group-hover:text-primary transition-colors" />

{p.name}

                      <span className={`font-semibold ${p.priority === 'Urgent' ? 'text-red-500' : p.priority === 'High' ? 'text-amber-500' : ''}`}>{p.priority}
                      {p.open_task_count || 0} tasks

                  </button>
                ))}

          )}
        </>
      )}

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

  );
}


import { useState, useEffect, useRef } from 'react';

import { useState, useEffect } from 'react';