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 && (