adding teams page (#91)

* adding teams page

* adding API and Docs link to the appbar

* minor authhook changes

* minor changes

* minor changes

* making separate avatar component

* teams can add users now

* adding placeholder for no data

* adding search in adduser modal

* hiding delete action for user card

* addressing comment
This commit is contained in:
Sachin Chaurasiya 2021-08-13 20:56:28 +05:30 committed by GitHub
parent a2f244f8ea
commit 81c9966610
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
17 changed files with 843 additions and 40 deletions

View File

@ -0,0 +1,33 @@
import { AxiosResponse } from 'axios';
import { Team } from 'Models';
import { getURLWithQueryFields } from '../utils/APIUtils';
import APIClient from './index';
export const getTeams: Function = (
arrQueryFields?: string
): Promise<AxiosResponse> => {
const url = getURLWithQueryFields('/teams', arrQueryFields);
return APIClient.get(`${url}&limit=1000000`);
};
export const getTeamByName: Function = (
name: string,
arrQueryFields?: string
): Promise<AxiosResponse> => {
const url = getURLWithQueryFields(`/teams/name/${name}`, arrQueryFields);
return APIClient.get(url);
};
export const createTeam: Function = (data: Team) => {
return APIClient.post('/teams', data);
};
export const patchTeamDetail: Function = (id: string, data: Team) => {
const configOptions = {
headers: { 'Content-type': 'application/json-patch+json' },
};
return APIClient.patch(`/teams/${id}`, data, configOptions);
};

View File

@ -15,15 +15,17 @@
* limitations under the License.
*/
import { Team } from 'Models';
import React, { useRef, useState } from 'react';
import { TagsCategory } from '../../../pages/tags/tagsTypes';
import { Button } from '../../buttons/Button/Button';
type FormData = TagsCategory | Team;
type FormModalProp = {
onCancel: () => void;
onSave: (data: TagsCategory) => void;
form: React.ElementType;
header: string;
initialData: TagsCategory;
initialData: FormData;
};
type FormRef = {
fetchMarkDownData: () => string;
@ -36,7 +38,7 @@ const FormModal = ({
initialData,
}: FormModalProp) => {
const formRef = useRef<FormRef>();
const [data, setData] = useState<TagsCategory>(initialData);
const [data, setData] = useState<FormData>(initialData);
const onSubmitHandler = (e: React.FormEvent<HTMLFormElement>) => {
e.preventDefault();

View File

@ -131,7 +131,27 @@ const Appbar: React.FC = (): JSX.Element => {
/>
</div>
</div>
<NavLink
className="tw-nav focus:tw-no-underline"
data-testid="appbar-item"
style={navStyle(location.pathname.startsWith('/documents'))}
target="_blank"
to={{
pathname: 'https://docs.open-metadata.org/',
}}>
{/* <i className="fas fa-file-alt tw-pr-2" /> */}
<span>Docs</span>
</NavLink>
<NavLink
className="tw-nav focus:tw-no-underline"
data-testid="appbar-item"
style={navStyle(location.pathname.startsWith('/docs'))}
to={{
pathname: '/docs',
}}>
{/* <i className="fas fa-sitemap tw-pr-2" /> */}
<span>API</span>
</NavLink>
<div data-testid="dropdown-profile">
<DropDown
dropDownList={[

View File

@ -0,0 +1,33 @@
import React from 'react';
const Avatar = ({ name }: { name: string }) => {
const getBgColorByCode = (code: number) => {
if (code >= 65 && code <= 71) {
return '#B02AAC40';
}
if (code >= 72 && code <= 78) {
return '#7147E840';
}
if (code >= 79 && code <= 85) {
return '#FFC34E40';
} else {
return '#1890FF40';
}
};
return (
<div
className="tw-flex tw-justify-center tw-items-center tw-align-middle"
style={{
height: '36px',
width: '36px',
borderRadius: '50%',
background: getBgColorByCode(name.charCodeAt(0)),
color: 'black',
}}>
<p>{name[0]}</p>
</div>
);
};
export default Avatar;

View File

@ -131,7 +131,9 @@ const RichTextEditor = forwardRef<editorRef, EditorProp>(
onEditorStateChange={onEditorStateChange}
/>
</div>
<p className="tw-pt-2">Using headings in markdown is not allowed</p>
<p className="tw-pt-2 tw-float-right tw-text-grey-muted">
Using headings in markdown is not allowed
</p>
</>
);
}

View File

@ -125,7 +125,7 @@ export const navLinkDevelop = [
];
export const navLinkSettings = [
// { name: 'Teams', to: '/teams', disabled: false },
{ name: 'Teams', to: '/teams', disabled: false },
{ name: 'Tags', to: '/tags', disabled: false },
// { name: 'Store', to: '/store', disabled: false },
{ name: 'Services', to: '/services', disabled: false },

View File

@ -36,5 +36,7 @@ export const useAuth = (pathname = '') => {
isEmpty(userDetails) &&
isEmpty(newUser),
isAuthenticatedRoute: isAuthenticatedRoute,
isAuthDisabled: authDisabled,
isAdminUser: userDetails?.isAdmin,
};
};

View File

@ -168,8 +168,8 @@ declare module 'Models' {
};
export type UserTeam = {
description?: string;
href?: string;
description: string;
href: string;
id: string;
name: string;
type: string;
@ -178,11 +178,13 @@ declare module 'Models' {
export type User = {
displayName: string;
isBot: boolean;
isAdmin: boolean;
id: string;
name?: string;
name: string;
profile: UserProfile;
teams: Array<UserTeam>;
timezone: string;
href: string;
};
export type FormatedTableData = {
@ -281,7 +283,15 @@ declare module 'Models' {
aggregations: Record<string, Sterm>;
};
};
export type Team = {
id: string;
name: string;
displayName: string;
description: string;
href: string;
users: Array<UserTeam>;
owns: Array<UserTeam>;
};
export type ServiceCollection = {
name: string;
value: string;

View File

@ -161,7 +161,7 @@ const TagsPage = () => {
const fetchLeftPanel = () => {
return (
<>
<div className="tw-flex tw-justify-between tw-items-baseline tw-mb-3">
<div className="tw-flex tw-justify-between tw-items-baseline tw-mb-3 tw-border-b">
<h6 className="tw-heading">Tag Categories</h6>
<Button
className="tw-h-7 tw-px-2"

View File

@ -0,0 +1,110 @@
import { UserTeam } from 'Models';
import React, { useState } from 'react';
import { Button } from '../../components/buttons/Button/Button';
import Searchbar from '../../components/common/searchbar/Searchbar';
import UserCard from './UserCard';
type Props = {
header: string;
list: Array<UserTeam>;
onCancel: () => void;
onSave: (data: Array<UserTeam>) => void;
};
const AddUsersModal = ({ header, list, onCancel, onSave }: Props) => {
const [selectedUsers, setSelectedusers] = useState<Array<string>>([]);
const [searchText, setSearchText] = useState('');
const selectionHandler = (id: string) => {
setSelectedusers((prevState) => {
if (prevState.includes(id)) {
const userArr = [...prevState];
const index = userArr.indexOf(id);
userArr.splice(index, 1);
return userArr;
} else {
return [...prevState, id];
}
});
};
const getUserCards = () => {
return list
.filter((user) => {
return (
user.description.includes(searchText) ||
user.name.includes(searchText)
);
})
.map((user, index) => {
const User = {
description: user.description,
name: user.name,
id: user.id,
};
return (
<UserCard
isActionVisible
isCheckBoxes
isIconVisible
item={User}
key={index}
onSelect={selectionHandler}
/>
);
});
};
const handleSave = () => {
const users = list.filter((user) => {
return selectedUsers.includes(user.id);
});
onSave(users);
};
const handleSearchAction = (searchValue: string) => {
setSearchText(searchValue);
};
return (
<dialog className="tw-modal ">
<div className="tw-modal-backdrop" />
<div className="tw-modal-container tw-max-h-90vh tw-max-w-3xl">
<div className="tw-modal-header">
<p className="tw-modal-title">{header}</p>
</div>
<div className="tw-modal-body">
<Searchbar
placeholder="Search for user..."
searchValue={searchText}
typingInterval={1500}
onSearch={handleSearchAction}
/>
<div className="tw-grid tw-grid-cols-3 tw-gap-4">
{getUserCards()}
</div>
</div>
<div className="tw-modal-footer tw-justify-end">
<Button
className="tw-mr-2"
size="regular"
theme="primary"
variant="text"
onClick={onCancel}>
Discard
</Button>
<Button
size="regular"
theme="primary"
type="submit"
variant="contained"
onClick={handleSave}>
Save
</Button>
</div>
</div>
</dialog>
);
};
export default AddUsersModal;

View File

@ -0,0 +1,118 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
* http://www.apache.org/licenses/LICENSE-2.0
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { Team } from 'Models';
import React, {
forwardRef,
useEffect,
useImperativeHandle,
useRef,
useState,
} from 'react';
import MarkdownWithPreview from '../../components/common/editor/MarkdownWithPreview';
type FormProp = {
saveData: (value: {}) => void;
initialData: Team;
};
type EditorContentRef = {
getEditorContent: () => string;
};
const Form: React.FC<FormProp> = forwardRef(
({ saveData, initialData }: FormProp, ref): JSX.Element => {
const [data, setData] = useState<Team>({
name: initialData.name,
description: initialData.description,
displayName: initialData.displayName,
id: initialData.id || '',
href: initialData.href || '',
owns: initialData.owns || [],
users: initialData.users || [],
});
const markdownRef = useRef<EditorContentRef>();
const onChangeHadler = (
e: React.ChangeEvent<HTMLInputElement | HTMLSelectElement>
) => {
e.persist();
setData((prevState) => {
return {
...prevState,
[e.target.name]: e.target.value,
};
});
};
useImperativeHandle(ref, () => ({
fetchMarkDownData() {
return markdownRef.current?.getEditorContent();
},
}));
useEffect(() => {
saveData({
...data,
});
}, [data]);
return (
<div className="tw-w-full tw-flex ">
<div className="tw-flex tw-w-full">
<div className="tw-w-full">
<div className="tw-mb-4">
<label className="tw-form-label required-field">Name</label>
<input
required
autoComplete="off"
className="tw-form-inputs tw-px-3 tw-py-1"
name="name"
placeholder="Name"
type="text"
value={data.name}
onChange={onChangeHadler}
/>
</div>
<div className="tw-mb-4">
<label className="tw-form-label required-field">
Display name
</label>
<input
required
autoComplete="off"
className="tw-form-inputs tw-px-3 tw-py-1"
name="displayName"
placeholder="Display name"
type="text"
value={data.displayName}
onChange={onChangeHadler}
/>
</div>
<div>
<label className="tw-form-label required-field">
Description
</label>
<MarkdownWithPreview ref={markdownRef} value={data.description} />
</div>
</div>
</div>
</div>
);
}
);
Form.displayName = 'TeamsForm';
export default Form;

View File

@ -0,0 +1,72 @@
import { capitalize } from 'lodash';
import React from 'react';
import { Link } from 'react-router-dom';
import Avatar from '../../components/common/avatar/Avatar';
import { getPartialNameFromFQN } from '../../utils/CommonUtils';
import SVGIcons from '../../utils/SvgUtils';
type Props = {
item: { description: string; name: string; id?: string };
isActionVisible?: boolean;
isIconVisible?: boolean;
isDataset?: boolean;
isCheckBoxes?: boolean;
onSelect?: (value: string) => void;
onRemove?: (value: string) => void;
};
const UserCard = ({
item,
isActionVisible = false,
isIconVisible = false,
isDataset = false,
isCheckBoxes = false,
onSelect,
onRemove,
}: Props) => {
return (
<div className="tw-card tw-flex tw-justify-between tw-py-2 tw-px-3 tw-group">
<div className={`tw-flex ${isCheckBoxes ? 'tw-mr-2' : 'tw-gap-1'}`}>
{isIconVisible ? <Avatar name={item.description} /> : null}
<div className="tw-flex tw-flex-col tw-pl-2">
{isDataset ? (
<Link to={`/dataset/${item.description}`}>
<button className="tw-font-normal tw-text-grey-body">
{getPartialNameFromFQN(item.description, ['database', 'table'])}
</button>
</Link>
) : (
<p className="tw-font-normal">{item.description}</p>
)}
<p>{isIconVisible ? item.name : capitalize(item.name)}</p>
</div>
</div>
{isActionVisible && (
<div className="tw-flex-none">
{isCheckBoxes ? (
<input
className="tw-px-2 custom-checkbox"
type="checkbox"
onChange={() => {
onSelect?.(item.id as string);
}}
/>
) : (
<span onClick={() => onRemove?.(item.id as string)}>
<SVGIcons
alt="delete"
className="tw-text-gray-500 tw-cursor-pointer tw-opacity-0 hover:tw-text-gray-700 group-hover:tw-opacity-100"
icon="icon-delete"
title="Remove"
/>
</span>
)}
</div>
)}
</div>
);
};
export default UserCard;

View File

@ -1,29 +0,0 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
* http://www.apache.org/licenses/LICENSE-2.0
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import React from 'react';
import PageContainer from '../../components/containers/PageContainer';
const TeamsPage = () => {
return (
<PageContainer>
<h1>Teams</h1>
</PageContainer>
);
};
export default TeamsPage;

View File

@ -0,0 +1,422 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
* http://www.apache.org/licenses/LICENSE-2.0
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { AxiosError, AxiosResponse } from 'axios';
import { compare } from 'fast-json-patch';
import { observer } from 'mobx-react';
import { Team, User, UserTeam } from 'Models';
import React, { useEffect, useState } from 'react';
import { Link } from 'react-router-dom';
import AppState from '../../AppState';
import {
createTeam,
getTeamByName,
getTeams,
patchTeamDetail,
} from '../../axiosAPIs/teamsAPI';
import { Button } from '../../components/buttons/Button/Button';
import ErrorPlaceHolder from '../../components/common/error-with-placeholder/ErrorPlaceHolder';
import RichTextEditorPreviewer from '../../components/common/rich-text-editor/RichTextEditorPreviewer';
import PageContainer from '../../components/containers/PageContainer';
import Loader from '../../components/Loader/Loader';
import FormModal from '../../components/Modals/FormModal';
import { ModalWithMarkdownEditor } from '../../components/Modals/ModalWithMarkdownEditor/ModalWithMarkdownEditor';
import { ERROR404 } from '../../constants/constants';
import SVGIcons from '../../utils/SvgUtils';
import AddUsersModal from './AddUsersModal';
import Form from './Form';
import UserCard from './UserCard';
const TeamsPage = () => {
const [teams, setTeams] = useState<Array<Team>>([]);
const [currentTeam, setCurrentTeam] = useState<Team>();
const [error, setError] = useState<string>('');
const [isLoading, setIsLoading] = useState<boolean>(false);
const [currentTab, setCurrentTab] = useState<number>(1);
const [isEditable, setIsEditable] = useState<boolean>(false);
const [isAddingTeam, setIsAddingTeam] = useState<boolean>(false);
const [isAddingUsers, setIsAddingUsers] = useState<boolean>(false);
const [userList, setUserList] = useState<Array<User>>([]);
const fetchTeams = () => {
setIsLoading(true);
getTeams(['users', 'owns'])
.then((res: AxiosResponse) => {
setTeams(res.data.data);
setCurrentTeam(res.data.data[0]);
setIsLoading(false);
})
.catch((err: AxiosError) => {
if (err?.response?.data.code) {
setError(ERROR404);
}
setIsLoading(false);
});
};
const fetchCurrentTeam = (name: string, update = false) => {
if (currentTeam?.name !== name || update) {
setIsLoading(true);
getTeamByName(name, ['users', 'owns'])
.then((res: AxiosResponse) => {
setCurrentTeam(res.data);
setIsLoading(false);
})
.catch((err: AxiosError) => {
if (err?.response?.data.code) {
setError(ERROR404);
}
setIsLoading(false);
});
}
};
const createNewTeam = (data: Team) => {
createTeam(data)
.then((res: AxiosResponse) => {
if (res.data) {
fetchTeams();
setIsAddingTeam(false);
} else {
setIsAddingTeam(false);
}
})
.catch(() => {
setIsAddingTeam(false);
});
};
const createUsers = (data: Array<UserTeam>) => {
const updatedTeam = {
...currentTeam,
users: [...(currentTeam?.users as Array<UserTeam>), ...data],
};
const jsonPatch = compare(currentTeam as Team, updatedTeam);
patchTeamDetail(currentTeam?.id, jsonPatch).then((res: AxiosResponse) => {
if (res.data) {
fetchCurrentTeam(res.data.name, true);
}
});
setIsAddingUsers(false);
};
const deleteUser = (id: string) => {
const users = [...(currentTeam?.users as Array<UserTeam>)];
const newUsers = users.filter((user) => {
return user.id !== id;
});
const updatedTeam = {
...currentTeam,
users: newUsers,
};
const jsonPatch = compare(currentTeam as Team, updatedTeam);
patchTeamDetail(currentTeam?.id, jsonPatch).then((res: AxiosResponse) => {
if (res.data) {
fetchCurrentTeam(res.data.name, true);
}
});
};
const getCurrentTeamClass = (name: string) => {
if (currentTeam?.name === name) {
return 'activeCategory';
} else {
return '';
}
};
const getActiveTabClass = (tab: number) => {
return tab === currentTab ? 'active' : '';
};
const getTabs = () => {
return (
<div className="tw-mb-3 ">
<nav className="tw-flex tw-flex-row tw-gh-tabs-container tw-px-4">
<button
className={`tw-pb-2 tw-px-4 tw-gh-tabs ${getActiveTabClass(1)}`}
onClick={() => {
setCurrentTab(1);
}}>
Users
</button>
<button
className={`tw-pb-2 tw-px-4 tw-gh-tabs ${getActiveTabClass(2)}`}
onClick={() => {
setCurrentTab(2);
}}>
Assets
</button>
</nav>
</div>
);
};
const getUserCards = () => {
if ((currentTeam?.users.length as number) <= 0) {
return (
<div className="tw-flex tw-flex-col tw-items-center tw-place-content-center tw-mt-40 tw-gap-1">
<p>there are not any users added yet.</p>
<p>would like to start adding some ?</p>
<Button
className="tw-h-8 tw-rounded tw-mb-2"
size="small"
theme="primary"
variant="contained"
onClick={() => setIsAddingUsers(true)}>
Add new user
</Button>
</div>
);
}
return (
<>
<div className="tw-grid xl:tw-grid-cols-4 md:tw-grid-cols-2 tw-gap-4">
{currentTeam?.users.map((user, index) => {
const User = {
description: user.description,
name: user.name,
id: user.id,
};
return (
<UserCard
isIconVisible
item={User}
key={index}
onRemove={deleteUser}
/>
);
})}
</div>
</>
);
};
const getDatasetCards = () => {
if ((currentTeam?.owns.length as number) <= 0) {
return (
<div className="tw-flex tw-flex-col tw-items-center tw-place-content-center tw-mt-40 tw-gap-1">
<p>Your team does not have any dataset</p>
<p>would like to start adding some ?</p>
<Link to="/explore">
<Button
className="tw-h-8 tw-rounded tw-mb-2 tw-text-white"
size="small"
theme="primary"
variant="contained">
Explore
</Button>
</Link>
</div>
);
}
return (
<>
<div className="tw-grid xl:tw-grid-cols-4 md:tw-grid-cols-2 tw-gap-4">
{' '}
{currentTeam?.owns.map((dataset, index) => {
const Dataset = { description: dataset.name, name: dataset.type };
return <UserCard isDataset item={Dataset} key={index} />;
})}
</div>
</>
);
};
const fetchLeftPanel = () => {
return (
<>
<div className="tw-flex tw-justify-between tw-items-baseline tw-mb-3 tw-border-b">
<h6 className="tw-heading">Teams</h6>
<Button
className="tw-h-7 tw-px-2"
size="small"
theme="primary"
variant="contained"
onClick={() => setIsAddingTeam(true)}>
<i aria-hidden="true" className="fa fa-plus" />
</Button>
</div>
{teams &&
teams.map((team: Team) => (
<div
className={`tw-group tw-text-grey-body tw-cursor-pointer tw-text-body tw-mb-3 tw-flex tw-justify-between ${getCurrentTeamClass(
team.name
)}`}
key={team.name}
onClick={() => {
fetchCurrentTeam(team.name);
setCurrentTab(1);
}}>
<p className="tw-text-center tag-category tw-self-center">
{team.displayName}
</p>
</div>
))}
</>
);
};
const onDescriptionUpdate = (updatedHTML: string) => {
if (currentTeam?.description !== updatedHTML) {
const updatedTeam = { ...currentTeam, description: updatedHTML };
const jsonPatch = compare(currentTeam as Team, updatedTeam);
patchTeamDetail(currentTeam?.id, jsonPatch).then((res: AxiosResponse) => {
if (res.data) {
fetchCurrentTeam(res.data.name, true);
}
});
setIsEditable(false);
} else {
setIsEditable(false);
}
};
const onDescriptionEdit = (): void => {
setIsEditable(true);
};
const onCancel = (): void => {
setIsEditable(false);
};
const getUniqueUserList = () => {
const uniqueList = userList
.filter((user) => {
const teamUser = currentTeam?.users.some(
(teamUser) => user.id === teamUser.id
);
return !teamUser && user;
})
.map((user) => {
return {
description: user.displayName,
id: user.id,
href: user.href,
name: user.name,
type: 'user',
};
});
return uniqueList;
};
useEffect(() => {
fetchTeams();
}, []);
useEffect(() => {
setUserList(AppState.users);
}, [AppState.users]);
return (
<>
{error ? (
<ErrorPlaceHolder />
) : (
<PageContainer leftPanelContent={fetchLeftPanel()}>
{isLoading ? (
<Loader />
) : (
<div className="container-fluid tw-pt-1 tw-pb-3">
<div className="tw-flex tw-justify-between tw-pl-1">
<div className="tw-heading tw-text-link tw-text-base">
{currentTeam?.displayName}
</div>
<Button
className="tw-h-8 tw-rounded tw-mb-2"
size="small"
theme="primary"
variant="contained"
onClick={() => setIsAddingUsers(true)}>
Add new user
</Button>
</div>
<div className="tw-flex tw-flex-col tw-border tw-rounded-md tw-mb-3 tw-min-h-32 tw-bg-white">
<div className="tw-flex tw-items-center tw-px-3 tw-py-1 tw-border-b">
<span className="tw-flex-1 tw-leading-8 tw-m-0 tw-font-normal">
Description
</span>
<div className="tw-flex-initial">
<button
className="focus:tw-outline-none"
onClick={onDescriptionEdit}>
<SVGIcons alt="edit" icon="icon-edit" title="Edit" />
</button>
</div>
</div>
<div className="tw-px-3 tw-pl-5 tw-py-2 tw-overflow-y-auto">
<div data-testid="description" id="description">
{currentTeam?.description.trim() ? (
<RichTextEditorPreviewer
markdown={currentTeam.description}
/>
) : (
<span className="tw-no-description">
No description added
</span>
)}
</div>
{isEditable && (
<ModalWithMarkdownEditor
header={`Edit description for ${currentTeam?.displayName}`}
placeholder="Enter Description"
value={currentTeam?.description || ''}
onCancel={onCancel}
onSave={onDescriptionUpdate}
/>
)}
</div>
</div>
{getTabs()}
{currentTab === 1 && getUserCards()}
{currentTab === 2 && getDatasetCards()}
{isAddingTeam && (
<FormModal
form={Form}
header="Adding new team"
initialData={{
name: '',
description: '',
displayName: '',
}}
onCancel={() => setIsAddingTeam(false)}
onSave={(data) => createNewTeam(data as Team)}
/>
)}
{isAddingUsers && (
<AddUsersModal
header={`Adding new users to ${currentTeam?.name}`}
list={getUniqueUserList()}
onCancel={() => setIsAddingUsers(false)}
onSave={(data) => createUsers(data)}
/>
)}
</div>
)}
</PageContainer>
)}
</>
);
};
export default observer(TeamsPage);

View File

@ -405,6 +405,13 @@
overflow-y: auto;
overflow-x: hidden;
}
.side-panel .activeCategory {
margin-right: -15px;
margin-left: -15px;
padding: 0px 15px;
border-right: 3px solid rgba(249, 130, 108);
font-weight: 500;
}
.search-wrapper + .page-container .side-panel {
top: 102px;

View File

@ -49,7 +49,7 @@ export const getPartialNameFromFQN = (
}
}
return arrPartialName.join('.');
return arrPartialName.join('/');
};
export const getCurrentUserId = (): string => {

View File

@ -98,6 +98,7 @@ module.exports = {
},
maxHeight: {
32: '8rem',
'90vh': '90vh',
},
minHeight: {
32: '8rem',