Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
gitpod-io
GitHub Repository: gitpod-io/gitpod
Path: blob/main/components/dashboard/src/admin/TeamsSearch.tsx
2500 views
1
/**
2
* Copyright (c) 2022 Gitpod GmbH. All rights reserved.
3
* Licensed under the GNU Affero General Public License (AGPL).
4
* See License.AGPL.txt in the project root for license information.
5
*/
6
7
import dayjs from "dayjs";
8
import { useState, useEffect } from "react";
9
10
import TeamDetail from "./TeamDetail";
11
import { useLocation } from "react-router";
12
import { Link } from "react-router-dom";
13
import { getGitpodService } from "../service/service";
14
import { AdminGetListResult, Team } from "@gitpod/gitpod-protocol";
15
import Label from "./Label";
16
import { AdminPageHeader } from "./AdminPageHeader";
17
import Pagination from "../Pagination/Pagination";
18
import { SpinnerLoader } from "../components/Loader";
19
import searchIcon from "../icons/search.svg";
20
21
export default function TeamsSearchPage() {
22
return (
23
<AdminPageHeader title="Admin" subtitle="Configure and manage instance settings.">
24
<div className="app-container">
25
<TeamsSearch />
26
</div>
27
</AdminPageHeader>
28
);
29
}
30
31
export function TeamsSearch() {
32
const location = useLocation();
33
const [searching, setSearching] = useState(false);
34
const [searchTerm, setSearchTerm] = useState("");
35
const [currentTeam, setCurrentTeam] = useState<Team | undefined>(undefined);
36
const [searchResult, setSearchResult] = useState<AdminGetListResult<Team>>({ total: 0, rows: [] });
37
const pageLength = 50;
38
const [currentPage, setCurrentPage] = useState(1);
39
40
useEffect(() => {
41
const teamId = location.pathname.split("/")[3];
42
if (teamId && searchResult) {
43
let foundTeam = searchResult.rows.find((team) => team.id === teamId);
44
if (foundTeam) {
45
setCurrentTeam(foundTeam);
46
} else {
47
getGitpodService()
48
.server.adminGetTeamById(teamId)
49
.then((team) => setCurrentTeam(team))
50
.catch((e) => console.error(e));
51
}
52
} else {
53
setCurrentTeam(undefined);
54
}
55
// eslint-disable-next-line react-hooks/exhaustive-deps
56
}, [location]);
57
58
if (currentTeam) {
59
return <TeamDetail team={currentTeam} />;
60
}
61
62
const search = async (page: number = 1) => {
63
setSearching(true);
64
try {
65
const result = await getGitpodService().server.adminGetTeams({
66
searchTerm,
67
limit: pageLength,
68
orderBy: "creationTime",
69
offset: (page - 1) * pageLength,
70
orderDir: "desc",
71
});
72
setCurrentPage(page);
73
setSearchResult(result);
74
} finally {
75
setSearching(false);
76
}
77
};
78
return (
79
<>
80
<div className="mb-3 mt-3 flex">
81
<div className="flex justify-between w-full">
82
<div className="flex relative h-10 my-auto">
83
{searching ? (
84
<span className="filter-grayscale absolute top-3 left-3">
85
<SpinnerLoader small={true} />
86
</span>
87
) : (
88
<img
89
src={searchIcon}
90
title="Search"
91
className="filter-grayscale absolute top-3 left-3"
92
alt="search icon"
93
/>
94
)}
95
<input
96
className="w-64 pl-9 border-0"
97
type="search"
98
placeholder="Search Organizations"
99
onKeyDown={(k) => k.key === "Enter" && search()}
100
onChange={(v) => {
101
setSearchTerm(v.target.value.trim());
102
}}
103
/>
104
</div>
105
</div>
106
</div>
107
<div className="flex flex-col space-y-2">
108
<div className="px-6 py-3 flex justify-between text-sm text-gray-400 border-t border-b border-gray-200 dark:border-gray-800 mb-2">
109
<div className="w-7/12">Name</div>
110
<div className="w-5/12 flex items-center">
111
<span>Created</span>
112
<svg xmlns="http://www.w3.org/2000/svg" fill="none" className="h-4 w-4" viewBox="0 0 16 16">
113
<path
114
fill="#A8A29E"
115
fillRule="evenodd"
116
d="M13.366 8.234a.8.8 0 010 1.132l-4.8 4.8a.8.8 0 01-1.132 0l-4.8-4.8a.8.8 0 111.132-1.132L7.2 11.67V2.4a.8.8 0 111.6 0v9.269l3.434-3.435a.8.8 0 011.132 0z"
117
clipRule="evenodd"
118
/>
119
</svg>
120
</div>
121
</div>
122
{searchResult.rows.map((team) => (
123
<TeamResultItem team={team} />
124
))}
125
</div>
126
<Pagination
127
currentPage={currentPage}
128
setPage={search}
129
totalNumberOfPages={Math.ceil(searchResult.total / pageLength)}
130
/>
131
</>
132
);
133
134
function TeamResultItem(props: { team: Team }) {
135
return (
136
<Link
137
key={"pr-" + props.team.name}
138
to={"/admin/orgs/" + props.team.id}
139
data-analytics='{"button_type":"sidebar_menu"}'
140
>
141
<div className="rounded-xl whitespace-nowrap flex py-6 px-6 w-full justify-between hover:bg-gray-100 dark:hover:bg-gray-800 focus:bg-kumquat-light group">
142
<div className="flex flex-col w-7/12 truncate">
143
<div className="font-medium text-gray-800 dark:text-gray-100 truncate max-w-sm">
144
{props.team.name}
145
{props.team.markedDeleted && <Label text="Deleted" color="red" />}
146
</div>
147
</div>
148
<div className="flex w-5/12 self-center">
149
<div className="text-sm w-full text-gray-400 truncate">
150
{dayjs(props.team.creationTime).format("MMM D, YYYY")}
151
</div>
152
</div>
153
</div>
154
</Link>
155
);
156
}
157
}
158
159