diff --git a/src/renderer/components/teams/TeamModal.tsx b/src/renderer/components/teams/TeamModal.tsx new file mode 100644 index 00000000..ce9bab8f --- /dev/null +++ b/src/renderer/components/teams/TeamModal.tsx @@ -0,0 +1,271 @@ +import React, { useEffect, useMemo, useState } from "react"; +import { X } from "lucide-react"; +import { cn } from "../../utils/cn"; +import type { TeamDefinition } from "../../stores/teamStore"; + +interface AuthorOption { + login: string; + avatar_url: string; +} + +interface TeamModalProps { + isOpen: boolean; + onClose: () => void; + onSave: (input: { + id?: string; + name: string; + members: string[]; + color?: string; + icon?: string; + description?: string; + }) => Promise | void; + initialTeam?: TeamDefinition | null; + availableAuthors: AuthorOption[]; + theme: "light" | "dark"; +} + +export default function TeamModal({ + isOpen, + onClose, + onSave, + initialTeam, + availableAuthors, + theme, +}: TeamModalProps) { + const [name, setName] = useState(""); + const [description, setDescription] = useState(""); + const [color, setColor] = useState(""); + const [icon, setIcon] = useState(""); + const [search, setSearch] = useState(""); + const [selected, setSelected] = useState>(new Set()); + + useEffect(() => { + if (isOpen) { + setName(initialTeam?.name || ""); + setDescription(initialTeam?.description || ""); + setColor(initialTeam?.color || ""); + setIcon(initialTeam?.icon || ""); + setSelected(new Set(initialTeam?.members || [])); + setSearch(""); + } + }, [isOpen, initialTeam]); + + const filteredAuthors = useMemo(() => { + const q = search.trim().toLowerCase(); + if (!q) return availableAuthors; + return availableAuthors.filter((a) => a.login.toLowerCase().includes(q)); + }, [availableAuthors, search]); + + const toggleMember = (login: string) => { + setSelected((prev: Set) => { + const next = new Set(prev); + if (next.has(login)) next.delete(login); + else next.add(login); + return next; + }); + }; + + const handleSave = async () => { + const trimmedName = name.trim(); + const members: string[] = Array.from(selected.values()); + if (!trimmedName || members.length === 0) { + alert("Please enter a team name and select at least one member."); + return; + } + await onSave({ + id: initialTeam?.id, + name: trimmedName, + members, + color: color || undefined, + icon: icon || undefined, + description: description || undefined, + }); + onClose(); + }; + + if (!isOpen) return null; + + return ( +
) => { + if (e.key === "Escape") onClose(); + }} + > +
+
+

+ {initialTeam ? "Edit Team" : "Create New Team"} +

+ +
+ +
+
+ + ) => setName(e.target.value)} + className={cn( + "w-full px-2 py-1 text-sm rounded border", + theme === "dark" + ? "bg-gray-900 border-gray-700 text-gray-100 placeholder:text-gray-400" + : "bg-white border-gray-300 text-gray-900 placeholder:text-gray-500", + )} + placeholder="e.g. Frontend Team" + /> +
+ +
+
+ + ) => setColor(e.target.value)} + className="w-full h-8 p-0 border-0 bg-transparent" + /> +
+
+ + ) => setIcon(e.target.value)} + className={cn( + "w-full px-2 py-1 text-sm rounded border", + theme === "dark" + ? "bg-gray-900 border-gray-700 text-gray-100 placeholder:text-gray-400" + : "bg-white border-gray-300 text-gray-900 placeholder:text-gray-500", + )} + placeholder="Optional emoji or short label" + /> +
+
+ +
+ +