diff --git a/src/components/SideBar/SideBar.tsx b/src/components/SideBar/SideBar.tsx
index 17c24fc..10e8793 100644
--- a/src/components/SideBar/SideBar.tsx
+++ b/src/components/SideBar/SideBar.tsx
@@ -2,6 +2,45 @@ import React from "react";
import { Link } from "react-router-dom";
import { Teams } from "./Teams";
import { styled } from "@mui/material/styles";
+import { useTenantMap } from "../../hooks/users";
+import { assignColor, findPinnedByPath, getOrgs, orderBy } from "../../helpers";
+import { usePinned } from "../../services/PinnedProvider";
+
+const RenderOrganizations = () => {
+ const { isLoading, isError, data: tmap } = useTenantMap();
+ const { pinned } = usePinned();
+
+ if (isLoading || isError || !tmap) {
+ return
;
+ }
+
+ const orgs = orderBy("company", getOrgs(tmap)).map(assignColor);
+
+ return (
+ <>
+ {orgs.map(({ name, path, color }, index) => {
+ const pinnedProjects = findPinnedByPath(pinned, path);
+ return (
+
+
+
+ {name}
+
+
+ {(pinnedProjects || []).map((project, index) => (
+
+ {project.name}
+
+ ))}
+
+ );
+ })}
+ >
+ );
+};
export const SideBar = () => {
return (
@@ -14,11 +53,12 @@ export const SideBar = () => {
-
+
Projects
+
@@ -28,7 +68,7 @@ export const SideBar = () => {
const StyledSideBar = styled("div")`
background: var(--white1);
height: calc(100vh + 48px);
- width: var(--p256);
+ width: var(--p300);
font-size: var(--p18);
position: relative;
ul {
@@ -38,12 +78,44 @@ const StyledSideBar = styled("div")`
display: none;
}
`;
+
+const OrgLink = styled(Link)`
+ display: flex;
+ flex-direction: row;
+ align-items: center;
+ font-family: ProximaNova-Semibold;
+ font-size: var(--p16);
+ color: var(--gray7);
+ text-decoration: none;
+
+ aside {
+ height: 30px;
+ width: 30px;
+ margin-right: var(--p14);
+ }
+`;
+
+const ProjectLink = styled(Link)`
+ margin-left: 44px;
+ margin-top: 14px;
+ display: block;
+ text-decoration: none;
+ color: var(--gray7);
+ font-family: ProximaNova-Light;
+`;
+
const PrimaryNav = styled("ul")`
list-style: none;
padding: 0;
li {
padding: var(--p12) var(--p24);
}
+ li.bottom {
+ margin-bottom: 52px;
+ }
+ p {
+ margin: 0;
+ }
`;
const Container = styled("div")`
padding: var(--p16) var(--p24);
@@ -58,7 +130,9 @@ const Logo = styled("img")`
const StyledLink = styled(Link)`
text-decoration: none;
cursor: pointer;
- color: var(--gray3);
+ color: var(--gray6);
+ font-family: ProximaNova-Semibold;
+ font-size: var(--p20);
padding-top: var(--p8);
&:hover {
color: var(--gray5);
diff --git a/src/features/Projects/Card/Card.tsx b/src/features/Projects/Card/Card.tsx
index b5cbece..65ac6ba 100644
--- a/src/features/Projects/Card/Card.tsx
+++ b/src/features/Projects/Card/Card.tsx
@@ -1,4 +1,4 @@
-import React from "react";
+import React, { useState } from "react";
import { Link } from "react-router-dom";
import { Project } from "../../Project/types";
import { styled } from "@mui/material/styles";
@@ -7,33 +7,90 @@ import { Memberships } from "../../Project/types";
import { Avatar } from "../../../components/Avatar";
import { useTenantMap } from "../../../hooks/users";
import { Loader } from "../../../components/Loader";
+import PushPinIcon from "@mui/icons-material/PushPin";
+import {
+ assignColor,
+ getOrgs,
+ getPinState,
+ PinnedProject,
+ orderBy,
+} from "../../../helpers";
+import IconButton from "@mui/material/IconButton";
+import { usePinned } from "../../../services/PinnedProvider";
interface Props {
project: Project;
- seq: number;
}
-export const Card = ({ project, seq }: Props) => {
+export const Card = ({ project }: Props) => {
+ const [pinState, setPinState] = useState(getPinState(project));
const { data: memberships } = useTeamMemberships(project.teamId);
const { data: team } = useTeam(project.teamId);
- const { isLoading, isError, data } = useTenantMap();
+ const { isLoading, isError, data: tmap } = useTenantMap();
+ const { pinned, setPinned } = usePinned();
- const color = (seq % 9) + 1;
-
- if (isLoading || isError || data === undefined) {
+ if (isLoading || isError || !tmap) {
return ;
}
+ const orgs = orderBy("company", getOrgs(tmap)).map(assignColor);
+ const org = (orgs || []).find((org) => org.id === project.tenantId);
+
+ const handlePin = (e: React.MouseEvent) => {
+ e.preventDefault();
+ if (!org) {
+ return;
+ }
+
+ const isPinned = !!pinned.find(
+ (p: PinnedProject) => p.projectId === project.id
+ );
+
+ if (isPinned) {
+ const updatedPins = pinned.filter((p) => p.projectId != project.id);
+ localStorage.setItem("settings.pinned", JSON.stringify(updatedPins));
+ setPinned(updatedPins);
+ setPinState(false);
+ } else {
+ const updatedPins = [
+ ...pinned,
+ {
+ name: project.name,
+ projectId: project.id,
+ tenantPath: org.path,
+ },
+ ];
+
+ localStorage.setItem("settings.pinned", JSON.stringify(updatedPins));
+ setPinned(updatedPins);
+ setPinState(true);
+ }
+ };
+
+ const path = tmap[project.tenantId].path;
+ const link = `/${path}/projects/${project.id}`;
+
+ if (!org) {
+ return <>>;
+ }
+
return (
-
-
+
+
- {project.name}
- Developed by {team ? team.name : "Me"}
+
+ {project.name}
+ Developed by {org.name}
+
+
{project.description}
@@ -116,6 +173,10 @@ const CardHeader = styled("div")`
padding: var(--p16);
box-sizing: border-box;
border-bottom: 1px solid var(--gray1);
+ display: flex;
+ justify-content: space-between;
+ .header {
+ }
`;
const CardBody = styled("div")`
display: flex;
diff --git a/src/features/Projects/List/List.tsx b/src/features/Projects/List/List.tsx
index 8cc3673..e46bd8c 100644
--- a/src/features/Projects/List/List.tsx
+++ b/src/features/Projects/List/List.tsx
@@ -3,21 +3,18 @@ import { CircularProgress, Grid } from "@mui/material";
import { Card } from "../Card";
import { styled } from "@mui/material/styles";
import { Project } from "../../Project/types";
-import { orderBy } from "../helpers";
-
-interface Props {
- isLoading: boolean;
- projects: Project[] | undefined;
-}
+import { orderBy } from "../../../helpers";
+import { useProjects } from "../../../hooks/project";
const renderProjectCards = (projects: Project[]) => {
- const seq = projects.map((_, idx) => idx);
- return orderBy(projects, "name").map((project: Project, index: number) => (
-
+ return orderBy("name", projects).map((project: Project) => (
+
));
};
-export const List = ({ isLoading, projects }: Props) => {
+export const List = () => {
+ const { isLoading, data: projects } = useProjects();
+
if (isLoading) {
return (
diff --git a/src/features/Projects/Projects.tsx b/src/features/Projects/Projects.tsx
index 1e069a0..bd02f5d 100644
--- a/src/features/Projects/Projects.tsx
+++ b/src/features/Projects/Projects.tsx
@@ -1,4 +1,4 @@
-import React, { useState } from "react";
+import React, { useEffect, useState } from "react";
import Grid from "@mui/material/Grid";
import Fab from "@mui/material/Fab";
import Add from "@mui/icons-material/Add";
@@ -10,8 +10,6 @@ import { Layout } from "../../Layout";
export const Projects = () => {
const [isOpen, setOpen] = useState(false);
- const projects = useProjects();
-
const [createProject] = useCreateProject();
const toggleModal = () => {
@@ -28,7 +26,7 @@ export const Projects = () => {
-
+
{
- return values.sort((a, b) => {
- let fieldA, fieldB;
-
- try {
- // Assume it's a string.
- fieldA = a[key].toLowerCase();
- fieldB = b[key].toLowerCase();
- } catch {
- fieldA = a[key];
- fieldB = b[key];
- }
-
- if (sortBy === "desc") {
- return sortByDesc(fieldA, fieldB);
- } else {
- return sortByAsc(fieldA, fieldB);
- }
- });
-};
-
-const sortByDesc = (a: any, b: any) => {
- return a > b ? 1 : a < b ? -1 : 0;
-};
-
-const sortByAsc = (a: any, b: any) => {
- return a < b ? 1 : a > b ? -1 : 0;
-};
-
-export { orderBy };
diff --git a/src/helpers.ts b/src/helpers.ts
index f7a4aab..e8bd50f 100644
--- a/src/helpers.ts
+++ b/src/helpers.ts
@@ -1,3 +1,6 @@
+import { Project } from "./features/Project/types";
+import { TMap } from "./hooks/users";
+
export function getUserInitials(firstName: string, lastName: string) {
return firstName.substring(0, 1) + (lastName || "").substring(0, 1);
}
@@ -19,3 +22,96 @@ export const formatPath = (company: string | undefined): string => {
return company.replaceAll(re, "").toLowerCase();
};
+
+interface Organization {
+ id: string;
+ path: string;
+ name: string;
+ color?: string;
+}
+
+export const getOrgs = (data: TMap) => {
+ return Object.keys(data).map((id) => ({
+ path: data[id].path,
+ id: data[id].id,
+ name: data[id].company,
+ })) as Organization[];
+};
+
+export function assignColor(org: T, index: number) {
+ return {
+ ...org,
+ color: `badge${(index % 9) + 1}`,
+ };
+}
+
+export interface PinnedProject {
+ tenantPath: string;
+ projectId: string;
+ name: string;
+}
+
+export const getPinState = (project: Project) => {
+ const pinnedSettings = localStorage.getItem("settings.pinned");
+ if (pinnedSettings) {
+ const data = JSON.parse(pinnedSettings);
+ if (data && data.length) {
+ return !!data.find((obj: PinnedProject) => {
+ return obj.projectId === project.id;
+ });
+ }
+ }
+
+ return false;
+};
+
+export const findPinnedByPath = (
+ settings: PinnedProject[],
+ path: string
+): PinnedProject[] => {
+ if (settings) {
+ if (settings && settings.length) {
+ return settings.filter((obj: PinnedProject) => {
+ return obj.tenantPath === path;
+ });
+ }
+ }
+
+ return [];
+};
+
+type SortBy = "asc" | "desc";
+
+// Sorts values by given key in descending order unless ascending order is specified.
+function orderBy(key: string, values: T[], sortBy: SortBy = "desc") {
+ return values.sort(
+ (a: { [index: string]: any }, b: { [index: string]: any }) => {
+ let fieldA, fieldB;
+
+ try {
+ // Assume it's a string.
+ fieldA = a[key].toLowerCase();
+ fieldB = b[key].toLowerCase();
+ } catch {
+ fieldA = a[key];
+ fieldB = b[key];
+ }
+
+ if (sortBy === "desc") {
+ return sortByDesc(fieldA, fieldB);
+ } else {
+ return sortByAsc(fieldA, fieldB);
+ }
+ }
+ );
+}
+
+const sortByDesc = (a: any, b: any) => {
+ return a > b ? 1 : a < b ? -1 : 0;
+};
+
+const sortByAsc = (a: any, b: any) => {
+ return a < b ? 1 : a > b ? -1 : 0;
+};
+
+export { orderBy };
diff --git a/src/hooks/users.ts b/src/hooks/users.ts
index cec9210..4b2fadf 100644
--- a/src/hooks/users.ts
+++ b/src/hooks/users.ts
@@ -38,8 +38,12 @@ export function useUser() {
return data;
}
-interface TMap {
- [index: string]: string;
+export interface TMap {
+ [index: string]: {
+ path: string;
+ company: string;
+ id: string;
+ };
}
export function useTenantMap() {
@@ -51,7 +55,11 @@ export function useTenantMap() {
const obj = data[key];
return {
...acc,
- [obj.id]: obj.path,
+ [obj.id]: {
+ path: obj.path,
+ id: obj.id,
+ company: obj.companyName,
+ },
};
}, {});
});
diff --git a/src/index.css b/src/index.css
index 118fb1a..6285252 100644
--- a/src/index.css
+++ b/src/index.css
@@ -86,6 +86,8 @@
--p640: 40rem;
--p512: 32rem;
--p384: 24rem;
+ --p300: 18.75rem;
+ --p288: 18rem;
--p256: 16rem;
--p192: 12rem;
--p128: 8rem;
@@ -94,6 +96,7 @@
--p48: 3rem;
--p32: 2rem;
--p24: 1.5rem;
+ --p20: 1.25rem;
--p18: 1.125rem;
--p16: 1rem;
--p14: 0.875rem;
diff --git a/src/index.tsx b/src/index.tsx
index 2b07fcc..1435250 100644
--- a/src/index.tsx
+++ b/src/index.tsx
@@ -29,6 +29,7 @@ import { Loader } from "./components/Loader";
import { SideBar } from "./components/SideBar";
import Copyright from "@mui/icons-material/Copyright";
import { TopBar } from "./components/TopBar/TopBar";
+import { PinnedProvider } from "./services/PinnedProvider";
Amplify.configure({
...awsconfig,
@@ -51,6 +52,7 @@ const TenantPath = () => {
const user = useUser();
const { tenantPath } = useParams();
const defaultTenantPath = formatPath(user?.company);
+ queryClient.invalidateQueries("projects").then();
if (user) {
const activeTenant = user.connections[tenantPath!];
@@ -96,11 +98,13 @@ const App = withAuthenticator(
<>
-
-
-
-
-
+
+
+
+
+
+
+