File size: 3,678 Bytes
5e8f957
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95

import React, { useState, useEffect } from 'react';
import { api } from '../services/api';
import { Todo } from '../types';
import { Plus, CheckCircle, Circle, Trash2, CalendarCheck } from 'lucide-react';

export const TodoList: React.FC = () => {
  const [todos, setTodos] = useState<Todo[]>([]);
  const [inputValue, setInputValue] = useState('');
  const [loading, setLoading] = useState(false);

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

  const loadTodos = async () => {
    try {
      const list = await api.todos.getAll();
      setTodos(list);
    } catch (e) { console.error(e); }
  };

  const handleAdd = async (e?: React.FormEvent) => {
    e?.preventDefault();
    if (!inputValue.trim()) return;
    setLoading(true);
    await api.todos.add(inputValue);
    setInputValue('');
    setLoading(false);
    loadTodos();
  };

  const handleToggle = async (t: Todo) => {
    // Optimistic update
    const newStatus = !t.isCompleted;
    setTodos(todos.map(item => item._id === t._id ? { ...item, isCompleted: newStatus } : item));
    await api.todos.update(t._id!, { isCompleted: newStatus });
  };

  const handleDelete = async (id: string) => {
    setTodos(todos.filter(t => t._id !== id));
    await api.todos.delete(id);
  };

  return (
    <div className="bg-white rounded-xl shadow-sm border border-gray-100 flex flex-col h-full">
      <div className="p-4 border-b border-gray-100 bg-gradient-to-r from-blue-50 to-indigo-50 rounded-t-xl">
        <h3 className="font-bold text-gray-800 flex items-center text-sm">
          <CalendarCheck size={16} className="mr-2 text-indigo-600"/> 个人备忘录
        </h3>
      </div>
      
      <div className="flex-1 overflow-y-auto p-2 custom-scrollbar min-h-[200px]">
        {todos.length === 0 ? (
          <div className="text-center text-gray-400 py-10 text-xs">
            <p>暂无待办事项</p>
            <p>记录今天的教学/学习任务吧!</p>
          </div>
        ) : (
          <ul className="space-y-1">
            {todos.map(t => (
              <li key={t._id} className="group flex items-start gap-2 p-2 hover:bg-gray-50 rounded transition-colors text-sm">
                <button onClick={() => handleToggle(t)} className={`mt-0.5 shrink-0 ${t.isCompleted ? 'text-green-500' : 'text-gray-400 hover:text-blue-500'}`}>
                  {t.isCompleted ? <CheckCircle size={16}/> : <Circle size={16}/>}
                </button>
                <span className={`flex-1 break-all ${t.isCompleted ? 'text-gray-400 line-through' : 'text-gray-700'}`}>
                  {t.content}
                </span>
                <button onClick={() => handleDelete(t._id!)} className="text-gray-300 hover:text-red-500 opacity-0 group-hover:opacity-100 transition-opacity">
                  <Trash2 size={14}/>
                </button>
              </li>
            ))}
          </ul>
        )}
      </div>

      <div className="p-3 border-t border-gray-100 bg-gray-50 rounded-b-xl">
        <form onSubmit={handleAdd} className="flex gap-2">
          <input 
            className="flex-1 border border-gray-200 rounded-lg px-3 py-1.5 text-sm outline-none focus:ring-2 focus:ring-indigo-200 focus:border-indigo-400 bg-white"
            placeholder="添加新任务..."
            value={inputValue}
            onChange={e => setInputValue(e.target.value)}
            disabled={loading}
          />
          <button type="submit" disabled={!inputValue.trim() || loading} className="bg-indigo-600 text-white p-1.5 rounded-lg hover:bg-indigo-700 disabled:opacity-50">
            <Plus size={18}/>
          </button>
        </form>
      </div>
    </div>
  );
};