From 70e6bee3d1832d5e4069ad96ed4e9ee5f4a474b6 Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Tue, 30 Sep 2025 07:37:36 +0000 Subject: [PATCH] feat: Add team management functionality This commit introduces the ability to create, edit, delete, and manage teams of authors. It includes UI components for team creation and management, as well as state management for teams. Co-authored-by: meta.alex.r --- src/renderer/components/teams/TeamModal.tsx | 271 ++++++++++++++++++ .../components/teams/TeamsManagerModal.tsx | 237 +++++++++++++++ src/renderer/stores/teamStore.ts | 188 ++++++++++++ src/renderer/views/PRListView.tsx | 132 ++++++++- 4 files changed, 827 insertions(+), 1 deletion(-) create mode 100644 src/renderer/components/teams/TeamModal.tsx create mode 100644 src/renderer/components/teams/TeamsManagerModal.tsx create mode 100644 src/renderer/stores/teamStore.ts 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" + /> +
+
+ +
+ +