theo-agent-dashboard

Unnamed repository; edit this file 'description' to name the repository.
Log | Files | Refs | README

TopicList.tsx (3279B)


      1 import { useState } from "react";
      2 import { useTopicStore } from "../../store/topic";
      3 import { TopicItem } from "./TopicItem";
      4 
      5 export function TopicList() {
      6   const { topics, selectedTopicId, selectTopic, createTopic, updateTopic } =
      7     useTopicStore();
      8   const [filter, setFilter] = useState("");
      9   const [adding, setAdding] = useState(false);
     10   const [newName, setNewName] = useState("");
     11   const [dragId, setDragId] = useState<string | null>(null);
     12 
     13   const filtered = topics.filter((t) =>
     14     t.name.toLowerCase().includes(filter.toLowerCase()),
     15   );
     16 
     17   const handleAdd = async () => {
     18     if (!newName.trim()) return;
     19     await createTopic(newName.trim());
     20     setNewName("");
     21     setAdding(false);
     22   };
     23 
     24   const handleDragStart = (id: string) => setDragId(id);
     25 
     26   const handleDragOver = (e: React.DragEvent, targetId: string) => {
     27     e.preventDefault();
     28     if (!dragId || dragId === targetId) return;
     29     const fromIdx = topics.findIndex((t) => t.id === dragId);
     30     const toIdx = topics.findIndex((t) => t.id === targetId);
     31     if (fromIdx === -1 || toIdx === -1) return;
     32     updateTopic(dragId, { sort_order: toIdx });
     33     updateTopic(targetId, { sort_order: fromIdx });
     34   };
     35 
     36   const handleDragEnd = () => setDragId(null);
     37 
     38   return (
     39     <div className="mb-4">
     40       <div className="mb-2 flex items-center justify-between">
     41         <h2 className="text-xs font-semibold uppercase text-gray-500">
     42           Topics
     43         </h2>
     44         <button
     45           className="text-xs text-gray-500 hover:text-gray-300"
     46           onClick={() => setAdding(true)}
     47         >
     48           + New
     49         </button>
     50       </div>
     51 
     52       <input
     53         type="text"
     54         placeholder="Search topics..."
     55         value={filter}
     56         onChange={(e) => setFilter(e.target.value)}
     57         className="mb-2 w-full rounded border border-gray-700 bg-gray-800 px-2 py-1 text-xs text-gray-300 outline-none focus:border-gray-500"
     58       />
     59 
     60       {adding && (
     61         <div className="mb-1 flex gap-1">
     62           <input
     63             autoFocus
     64             value={newName}
     65             onChange={(e) => setNewName(e.target.value)}
     66             onKeyDown={(e) => e.key === "Enter" && handleAdd()}
     67             placeholder="Topic name"
     68             className="flex-1 rounded border border-gray-700 bg-gray-800 px-2 py-1 text-xs text-gray-300 outline-none"
     69           />
     70           <button
     71             className="rounded bg-indigo-600 px-2 py-1 text-xs text-white hover:bg-indigo-500"
     72             onClick={handleAdd}
     73           >
     74             Add
     75           </button>
     76         </div>
     77       )}
     78 
     79       <div className="space-y-0.5">
     80         {filtered.map((topic) => (
     81           <div
     82             key={topic.id}
     83             draggable
     84             onDragStart={() => handleDragStart(topic.id)}
     85             onDragOver={(e) => handleDragOver(e, topic.id)}
     86             onDragEnd={handleDragEnd}
     87           >
     88             <TopicItem
     89               topic={topic}
     90               isSelected={selectedTopicId === topic.id}
     91               onSelect={() =>
     92                 selectTopic(selectedTopicId === topic.id ? null : topic.id)
     93               }
     94             />
     95           </div>
     96         ))}
     97       </div>
     98 
     99       {filtered.length === 0 && !adding && (
    100         <p className="text-xs text-gray-600">No topics yet</p>
    101       )}
    102     </div>
    103   );
    104 }