ProjectSelectionModal.tsx 6.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238
  1. import { RouteComponentProps, withRouter } from "react-router";
  2. import styled from "styled-components";
  3. import React, { useContext, useEffect, useState } from "react";
  4. import Modal from "components/porter/Modal";
  5. import Text from "components/porter/Text";
  6. import Spacer from "components/porter/Spacer";
  7. import { Context } from "shared/Context";
  8. import { DetailedClusterType, ProjectListType, ProjectType } from "shared/types";
  9. import { pushFiltered } from "shared/routing";
  10. import SearchBar from "components/porter/SearchBar";
  11. import _ from 'lodash';
  12. import { useMemo } from 'react';
  13. import api from "shared/api";
  14. import Button from "components/porter/Button";
  15. import Container from "components/porter/Container";
  16. type Props = RouteComponentProps & {
  17. closeModal: () => void;
  18. projects: ProjectListType[];
  19. currentProject: ProjectType;
  20. }
  21. const ProjectSelectionModal: React.FC<Props> = ({
  22. closeModal,
  23. projects,
  24. currentProject,
  25. ...props
  26. }) => {
  27. const context = useContext(Context);
  28. const { setCurrentProject, setCurrentCluster, user } = context;
  29. const [searchValue, setSearchValue] = useState("");
  30. const [clusters, setClusters] = useState<DetailedClusterType[]>([]);
  31. const [loading, setLoading] = useState<boolean>(true);
  32. const [error, setError] = useState<string>("");
  33. const filteredProjects = useMemo(() => {
  34. const filteredBySearch = projects.filter((project) => {
  35. return project.id === Number(searchValue) || project.name.toLowerCase().includes(searchValue.toLowerCase());
  36. });
  37. // sort and return all the projects
  38. const sortedProjects = _.sortBy(filteredBySearch, 'name');
  39. // move the selected project to the top
  40. const selectedProjectIndex = sortedProjects.findIndex(project => project.id === currentProject.id);
  41. if (selectedProjectIndex !== -1) {
  42. const selectedProject = sortedProjects.splice(selectedProjectIndex, 1)[0];
  43. sortedProjects.unshift(selectedProject);
  44. }
  45. return sortedProjects;
  46. }, [projects, searchValue, currentProject]);
  47. useEffect(() => {
  48. if (typeof window !== 'undefined') {
  49. const handleKeyDown = (e: KeyboardEvent) => {
  50. if (e.key === "Escape" || e.keyCode === 27) {
  51. closeModal();
  52. }
  53. };
  54. window.addEventListener('keydown', handleKeyDown);
  55. return () => {
  56. window.removeEventListener('keydown', handleKeyDown);
  57. };
  58. }
  59. }, [closeModal]);
  60. const updateClusterList = async (projectId: number) => {
  61. try {
  62. setLoading(true)
  63. const res = await api.getClusters(
  64. "<token>",
  65. {},
  66. { id: projectId }
  67. );
  68. if (res.data) {
  69. setClusters(res.data);
  70. setLoading(false);
  71. setError("");
  72. return res.data;
  73. } else {
  74. setLoading(false);
  75. setError("Response data missing");
  76. }
  77. } catch (err) {
  78. setError(err.toString());
  79. }
  80. };
  81. const renderBlockList = () => {
  82. return filteredProjects.map((projectListEntry: ProjectListType, i: number) => {
  83. return (
  84. <IdContainer
  85. key={i}
  86. selected={projectListEntry.id === currentProject.id}
  87. onClick={async () => {
  88. const project = await api
  89. .getProject("<token>", {}, { id: projectListEntry.id })
  90. .then((res) => res.data as ProjectType);
  91. setCurrentProject(project);
  92. const clusters_list = await updateClusterList(project.id);
  93. if (clusters_list?.length > 0) {
  94. setCurrentCluster(clusters_list[0]);
  95. setCurrentProject(project, () => {
  96. pushFiltered(props, "/dashboard", [], { project_id: project.id });
  97. });
  98. } else {
  99. setCurrentProject(project, () => {
  100. pushFiltered(props, "/dashboard", [], { project_id: project.id });
  101. });
  102. }
  103. closeModal();
  104. }}
  105. >
  106. <BlockTitle>{projectListEntry.name}</BlockTitle>
  107. <BlockDescription>
  108. Project ID: {projectListEntry.id}
  109. </BlockDescription>
  110. </IdContainer>
  111. );
  112. });
  113. };
  114. return (
  115. <Modal closeModal={closeModal} width={'600px'}>
  116. <Text size={16} style={{ marginRight: '10px' }}>
  117. Switch Project
  118. </Text>
  119. <Spacer y={1} />
  120. <Container row spaced>
  121. <SearchBar
  122. value={searchValue}
  123. setValue={(x) => {
  124. setSearchValue(x);
  125. }}
  126. placeholder="Search projects..."
  127. width="100%"
  128. autoFocus={true}
  129. />
  130. <Spacer inline x={1} />
  131. {user.isPorterUser && <Button onClick={() =>
  132. pushFiltered(props, "/new-project", ["project_id"], {
  133. new_project: true,
  134. })} height="30px" width="130px">
  135. <I className="material-icons">add</I> New Project
  136. </Button>}
  137. </Container>
  138. <Spacer y={1} />
  139. <ScrollableContent> {/* Wrap the block list */}
  140. {/* <BlockList>
  141. {renderBlockList()}
  142. </BlockList> */}
  143. {renderBlockList()}
  144. <Spacer height="15px" />
  145. </ScrollableContent>
  146. </Modal >
  147. )
  148. }
  149. export default withRouter(ProjectSelectionModal);
  150. const IdContainer = styled.div`
  151. color: #ffffff;
  152. border-radius: 5px;
  153. padding: 5px;
  154. display: block;
  155. width: 100%;
  156. border-radius: 5px;
  157. background:${(props) => props.theme.clickable.bg};
  158. border: 1px solid ${({ theme }) => theme.border};
  159. margin-bottom: 10px;
  160. margin-top: 5px;
  161. border: ${props => props.selected ? "2px solid #8590ff" : "1px solid #494b4f"};
  162. :hover {
  163. border: ${({ selected }) => (!selected && "1px solid #7a7b80")};
  164. }
  165. cursor: pointer;
  166. animation: fadeIn 0.3s 0s;
  167. @keyframes fadeIn {
  168. from {
  169. opacity: 0;
  170. }
  171. to {
  172. opacity: 1;
  173. }
  174. }
  175. `;
  176. const BlockDescription = styled.div`
  177. color: #ffffff66;
  178. margin-left: -10px;
  179. margin-top: 4px;
  180. text-align: center;
  181. font-weight: default;
  182. font-size: 13px;
  183. padding: 0px 25px;
  184. height: 2.4em;
  185. font-size: 12px;
  186. display: -webkit-box;
  187. overflow: hidden;
  188. -webkit-line-clamp: 2;
  189. -webkit-box-orient: vertical;
  190. `;
  191. const BlockTitle = styled.div`
  192. margin-top: 12px;
  193. width: 100%;
  194. margin-left: -10px;
  195. text-align: center;
  196. font-size: 16px;
  197. justify-content: center;
  198. white-space: nowrap;
  199. overflow: hidden;
  200. text-overflow: ellipsis;
  201. `;
  202. const ScrollableContent = styled.div`
  203. overflow-y: auto; /* Enable vertical scrolling */
  204. height: calc(100vh - 500px); /* Set the maximum height */
  205. padding-right: 15px; /* Add some right padding to account for scrollbar */
  206. `;
  207. const I = styled.i`
  208. color: white;
  209. font-size: 14px;
  210. display: flex;
  211. align-items: center;
  212. margin-right: 5px;
  213. justify-content: center;
  214. `;