diff --git a/app/components/WorkspacePanel.tsx b/app/components/WorkspacePanel.tsx index 49102da..fc2f176 100644 --- a/app/components/WorkspacePanel.tsx +++ b/app/components/WorkspacePanel.tsx @@ -4,7 +4,7 @@ import { Workspace, Repository } from '../types/workspace'; import { PlusIcon, PencilIcon, TrashIcon } from '@heroicons/react/24/outline'; export const WorkspacePanel: React.FC = () => { - const { +const { workspaces, currentWorkspace, setCurrentWorkspace, @@ -14,8 +14,19 @@ export const WorkspacePanel: React.FC = () => { addRepository, removeRepository, updateRepositorySettings, + createGroup, + updateGroup, + deleteGroup, + getGroup, } = useWorkspaces(); + const [showNewGroupForm, setShowNewGroupForm] = useState(false); + const [newGroupName, setNewGroupName] = useState(""); + const [selectedGroup, setSelectedGroup] = useState<string | null>(null); + const [isRenamingGroup, setIsRenamingGroup] = useState(false); + const [renamingGroupId, setRenamingGroupId] = useState<string | null>(null); + const [editedGroupName, setEditedGroupName] = useState(""); + const [showNewWorkspace, setShowNewWorkspace] = React.useState(false); const [newWorkspaceName, setNewWorkspaceName] = React.useState(''); const [newWorkspaceVisibility, setNewWorkspaceVisibility] = React.useState<'private' | 'public' | 'team'>('private'); @@ -75,7 +86,55 @@ export const WorkspacePanel: React.FC = () => { } }; - const currentWorkspaceData = workspaces.find(w => w.id === currentWorkspace); +const currentWorkspaceData = workspaces.find((w) =>w.id === currentWorkspace); + + const handleCreateGroup = () =>{ + if (newGroupName && currentWorkspace) { + createGroup(currentWorkspace, newGroupName); + setNewGroupName(""); + setShowNewGroupForm(false); + } + }; + + const handleRenameGroup = (group: Group) =>{ + setRenamingGroupId(group.id); + setEditedGroupName(group.name); + setIsRenamingGroup(true); + }; + + const handleSaveGroupName = (groupId: string) =>{ + if (currentWorkspace && editedGroupName) { + updateGroup(currentWorkspace, groupId, { name: editedGroupName }); + } + setIsRenamingGroup(false); + setRenamingGroupId(null); + setEditedGroupName(""); + }; + + const handleDeleteGroup = (groupId: string) =>{ + if ( + currentWorkspace && + confirm("Are you sure you want to delete this group?") + ) { + deleteGroup(currentWorkspace, groupId); + setSelectedGroup(null); + } + }; + + const handleSelectGroup = (groupId: string) =>{ + setSelectedGroup(groupId); + }; + + const filteredRepositories = React.useMemo(() =>{ + if (!currentWorkspaceData) return []; + if (!selectedGroup) return currentWorkspaceData.repositories; + + const group = getGroup(currentWorkspaceData.id, selectedGroup); + if (!group) return []; + + return currentWorkspaceData.repositories.filter((repo) =>group.repositoryIds.includes(repo.id) + ); + }, [currentWorkspaceData, selectedGroup]); return (
@@ -90,81 +149,195 @@ export const WorkspacePanel: React.FC = () => {
-
- {workspaces.map(workspace => ( -
setCurrentWorkspace(workspace.id)} - > -
-
-
-

{workspace.name}

- - {workspace.settings?.visibility || 'private'} - -
- {workspace.description && ( -

{workspace.description}

- )} - {workspace.settings?.collaborators?.length > 0 && ( -
- {workspace.settings.collaborators.length} collaborator(s) -
- )} -
-
- - -
-
-
- {workspace.repositories.length} repositories - -
-
- ))} -
+ > + <div className="flex justify-between items-start mb-2"> + <div> + <div className="flex items-center gap-2"> + <h3 className="font-medium">{workspace.name}</h3> + <span + className={`text-xs px-2 py-0.5 rounded-full ${ + workspace.settings?.visibility === "public" + ? "bg-green-600/20 text-green-300" + : workspace.settings?.visibility === "team" + ? "bg-blue-600/20 text-blue-300" + : "bg-gray-600/20 text-gray-300" + }`} + > + {workspace.settings?.visibility || "private"} + </span> + </div> + {workspace.description && ( + <p className="text-sm text-gray-400"> + {workspace.description} + </p> + )} + {workspace.settings?.collaborators?.length > 0 && ( + <div className="text-xs text-gray-400 mt-1"> + {workspace.settings.collaborators.length} collaborator(s) + </div> + )} + </div> + <div className="flex items-center gap-2"> + <button + onClick={(e) => { + e.stopPropagation(); + updateWorkspace(workspace.id, { + name: + prompt("New workspace name:", workspace.name) || + workspace.name, + }); + }} + className="p-1 rounded hover:bg-[#444444] transition-colors" + > + <PencilIcon className="h-4 w-4" /> + </button> + <button + onClick={(e) => { + e.stopPropagation(); + if ( + confirm("Are you sure you want to delete this workspace?") + ) { + deleteWorkspace(workspace.id); + } + }} + className="p-1 rounded hover:bg-[#444444] transition-colors" + > + <TrashIcon className="h-4 w-4" /> + </button> + </div> + </div> + <div className="flex items-center gap-2 text-sm text-gray-400"> + <span>{workspace.repositories.length} repositories</span> + <button + onClick={(e) => { + e.stopPropagation(); + setCurrentWorkspace(workspace.id); + setShowCollaborators(true); + }} + className="px-2 py-1 rounded bg-[#444444] hover:bg-[#555555] transition-colors" + > + Manage Collaborators + </button> + </div> + </div> + {workspace.id === currentWorkspace && ( + <div className="mt-4"> + <div className="flex justify-between items-center mb-2"> + <h4 className="text-lg font-medium">Groups</h4> + <button + onClick={() => setShowNewGroupForm(true)} + className="px-3 py-1.5 rounded text-sm bg-indigo-600 hover:bg-indigo-700 transition-colors flex items-center gap-2" + > + <PlusIcon className="h-4 w-4" /> + New Group + </button> + </div> + <div className="space-y-2"> + {workspace.groups.map((group) => ( + <div + key={group.id} + className={`p-2 rounded-lg ${ + selectedGroup === group.id + ? "bg-indigo-600/20 border border-indigo-500/30" + : "bg-[#444444] hover:bg-[#4A4A4A]" + } transition-colors cursor-pointer`} + onClick={() => handleSelectGroup(group.id)} + > + <div className="flex justify-between items-center"> + <span className="font-medium"> + {isRenamingGroup && renamingGroupId === group.id ? ( + <input + type="text" + value={editedGroupName} + onChange={(e) => + setEditedGroupName(e.target.value) + } + onBlur={() => handleSaveGroupName(group.id)} + onKeyDown={(e) => { + if (e.key === "Enter") { + handleSaveGroupName(group.id); + } + }} + className="px-2 py-1 rounded bg-[#333333] text-white focus:outline-none focus:ring-2 focus:ring-indigo-500" + autoFocus + /> + ) : ( + group.name + )} + </span> + <div className="flex items-center gap-2"> + <button + onClick={(e) => { + e.stopPropagation(); + handleRenameGroup(group); + }} + className="p-1 rounded hover:bg-[#555555] transition-colors" + > + <PencilIcon className="h-4 w-4" /> + </button> + <button + onClick={(e) => { + e.stopPropagation(); + handleDeleteGroup(group.id); + }} + className="p-1 rounded hover:bg-[#555555] transition-colors" + > + <TrashIcon className="h-4 w-4" /> + </button> + </div> + </div> + </div> + ))} + </div> + </div> + )} + {/* New Group Form */} + {showNewGroupForm && ( + <div className="fixed inset-0 bg-black/50 flex items-center justify-center z-50"> + <div className="bg-[#212121] p-6 rounded-lg w-full max-w-md mx-4"> + <h2 className="text-xl font-semibold mb-4"> + Create New Group + </h2> + <input + type="text" + value={newGroupName} + onChange={(e) => setNewGroupName(e.target.value)} + placeholder="Group name" + className="w-full p-2 rounded bg-[#333333] text-white mb-4" + /> + <div className="flex justify-end gap-4"> + <button + onClick={() => setShowNewGroupForm(false)} + className="px-4 py-2 rounded bg-[#444444] hover:bg-[#555555] transition-colors" + > + Cancel + </button> + <button + onClick={handleCreateGroup} + className="px-4 py-2 rounded bg-indigo-600 hover:bg-indigo-700 transition-colors" + > + Create + </button> + </div> + </div> + </div> + )} + </div> + ))} + </div> {currentWorkspaceData && (