Skip to content

Commit

Permalink
Merge pull request #748 from MuhammadUmer44/feature-workspace-codegra…
Browse files Browse the repository at this point in the history
…ph-integration

feat(workspace): Add Code Graph Management to Workspace View
  • Loading branch information
humansinstitute authored Dec 11, 2024
2 parents 91453db + 619a1ff commit 2897787
Show file tree
Hide file tree
Showing 4 changed files with 444 additions and 1 deletion.
143 changes: 143 additions & 0 deletions src/people/widgetViews/WorkspaceMission.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -57,9 +57,11 @@ import { useIsMobile } from 'hooks';
import styled from 'styled-components';
import { AvatarGroup } from 'components/common/AvatarGroup';
import { userHasRole } from 'helpers/helpers-extended';
import { CodeGraph } from 'store/interface';
import avatarIcon from '../../public/static/profile_avatar.svg';
import { colors } from '../../config/colors';
import dragIcon from '../../pages/superadmin/header/icons/drag_indicator.svg';
import AddCodeGraph from './workspace/AddCodeGraphModal';
import AddFeature from './workspace/AddFeatureModal';
import {
ActionButton,
Expand Down Expand Up @@ -252,6 +254,47 @@ const WorkspaceMission = () => {
const [isOpenUserManage, setIsOpenUserManage] = useState<boolean>(false);
const [users, setUsers] = useState<Person[]>([]);
const [displayUserRepoOptions, setDisplayUserRepoOptions] = useState<Record<number, boolean>>({});
const [codeGraphModal, setCodeGraphModal] = useState(false);
const [codeGraph, setCodeGraph] = useState<CodeGraph | null>(null);
const [codeGraphModalType, setCodeGraphModalType] = useState<'add' | 'edit'>('add');
const [currentCodeGraphUuid, setCurrentCodeGraphUuid] = useState('');

const fetchCodeGraph = useCallback(async () => {
try {
const data = await main.getWorkspaceCodeGraph(uuid);
setCodeGraph(data);
} catch (error) {
console.error(error);
}
}, [main, uuid]);

useEffect(() => {
fetchCodeGraph();
}, [fetchCodeGraph]);

const openCodeGraphModal = (type: 'add' | 'edit', graph?: CodeGraph) => {
if (type === 'edit' && graph) {
setCurrentCodeGraphUuid(graph.uuid);
} else {
setCurrentCodeGraphUuid('');
}
setCodeGraphModalType(type);
setCodeGraphModal(true);
};

const closeCodeGraphModal = () => {
setCodeGraphModal(false);
};

const handleDeleteCodeGraph = async () => {
try {
await main.deleteCodeGraph(uuid, currentCodeGraphUuid);
closeCodeGraphModal();
fetchCodeGraph();
} catch (error) {
console.error('Error deleteCodeGraph', error);
}
};

const handleUserRepoOptionClick = (repositoryId: number) => {
setDisplayUserRepoOptions((prev: Record<number, boolean>) => ({
Expand Down Expand Up @@ -763,6 +806,71 @@ const WorkspaceMission = () => {
</StyledList>
</DataWrap2>
</FieldWrap>

<FieldWrap style={{ marginTop: '20px' }}>
<DataWrap2>
<RowFlex>
<Label>Code Graph</Label>
{!codeGraph && (
<Button
onClick={() => openCodeGraphModal('add')}
style={{
borderRadius: '5px',
margin: 0,
marginLeft: 'auto'
}}
dataTestId="new-codegraph-btn"
text="Add Code Graph"
/>
)}
</RowFlex>
{codeGraph && (
<StyledList>
<StyledListElement>
<OptionsWrap style={{ position: 'unset', display: 'contents' }}>
<MaterialIcon
icon={'more_horiz'}
onClick={() => handleUserRepoOptionClick(codeGraph.id as number)}
className="MaterialIcon"
data-testid="codegraph-option-btn"
style={{ transform: 'rotate(90deg)' }}
/>
{displayUserRepoOptions[codeGraph.id as number] && (
<EditPopover>
<EditPopoverTail bottom="-30px" left="-27px" />
<EditPopoverContent
onClick={() => {
openCodeGraphModal('edit', codeGraph);
setDisplayUserRepoOptions((prev: Record<number, boolean>) => ({
...prev,
[codeGraph.id as number]: !prev[codeGraph.id as number]
}));
}}
bottom="-60px"
transform="translateX(-90%)"
>
<MaterialIcon
icon="edit"
style={{ fontSize: '20px', marginTop: '2px' }}
/>
<EditPopoverText data-testid="codegraph-edit-btn">
Edit
</EditPopoverText>
</EditPopoverContent>
</EditPopover>
)}
</OptionsWrap>
<RepoName>{codeGraph.name} : </RepoName>
<EuiToolTip position="top" content={codeGraph.url}>
<a href={codeGraph.url} target="_blank" rel="noreferrer">
{codeGraph.url}
</a>
</EuiToolTip>
</StyledListElement>
</StyledList>
)}
</DataWrap2>
</FieldWrap>
</LeftSection>
<VerticalGrayLine />
<RightSection>
Expand Down Expand Up @@ -1047,6 +1155,41 @@ const WorkspaceMission = () => {
schematic_img={workspaceData?.schematic_img ?? ''}
/>
</Modal>
<Modal
visible={codeGraphModal}
style={{
height: '100%',
flexDirection: 'column'
}}
envStyle={{
marginTop: isMobile ? 64 : 0,
background: color.pureWhite,
zIndex: 20,
maxHeight: '100%',
borderRadius: '10px',
minWidth: isMobile ? '100%' : '25%',
minHeight: isMobile ? '100%' : '20%'
}}
overlayClick={closeCodeGraphModal}
bigCloseImage={closeCodeGraphModal}
bigCloseImageStyle={{
top: '-18px',
right: '-18px',
background: '#000',
borderRadius: '50%'
}}
>
<AddCodeGraph
closeHandler={closeCodeGraphModal}
getCodeGraph={fetchCodeGraph}
workspace_uuid={uuid}
modalType={codeGraphModalType}
currentUuid={currentCodeGraphUuid}
handleDelete={handleDeleteCodeGraph}
name={codeGraph?.name}
url={codeGraph?.url}
/>
</Modal>
{isOpenUserManage && (
<ManageWorkspaceUsersModal
isOpen={isOpenUserManage}
Expand Down
213 changes: 213 additions & 0 deletions src/people/widgetViews/workspace/AddCodeGraphModal.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,213 @@
import React, { useState } from 'react';
import styled from 'styled-components';
import { useStores } from 'store';
import { EuiGlobalToastList, EuiLoadingSpinner } from '@elastic/eui';
import { normalizeInput } from '../../../helpers';
import { Toast } from './interface';
import {
TextInput,
WorkspaceInputContainer,
WorkspaceLabel,
ActionButton,
ButtonWrap
} from './style';

const AddWorkspaceWrapper = styled.div`
min-width: 100%;
padding: 3rem 2rem;
display: flex;
flex-direction: column;
@media only screen and (max-width: 500px) {
padding: 1rem;
width: 100%;
}
`;

const AddWorkspaceHeader = styled.h2`
color: #3c3f41;
font-family: 'Barlow';
font-size: 1.875rem;
font-style: normal;
font-weight: 800;
line-height: 1.875rem;
margin-bottom: 0;
min-width: 100%;
@media only screen and (max-width: 500px) {
text-align: center;
font-size: 1.4rem;
}
`;

const WorkspaceDetailsContainer = styled.div`
margin-top: 3rem;
min-width: 100%;
display: flex;
flex-direction: column;
gap: 1rem;
`;

const FooterContainer = styled.div`
display: flex;
gap: 2rem;
align-items: end;
justify-content: space-between;
margin-top: 15px;
`;

const errcolor = '#FF8F80';
const MAX_NAME_LENGTH = 50;

interface AddCodeGraphProps {
closeHandler: () => void;
getCodeGraph: () => void;
handleDelete?: () => void;
workspace_uuid: string;
modalType: 'add' | 'edit';
currentUuid?: string;
name?: string;
url?: string;
}

const AddCodeGraph: React.FC<AddCodeGraphProps> = ({
closeHandler,
getCodeGraph,
workspace_uuid,
modalType,
currentUuid,
handleDelete,
name = '',
url = ''
}: AddCodeGraphProps) => {
const [graphName, setGraphName] = useState(name);
const [graphNameError, setGraphNameError] = useState(false);
const [graphUrl, setGraphUrl] = useState(url);
const [graphUrlError, setGraphUrlError] = useState(false);
const [isLoading, setIsLoading] = useState(false);
const { main } = useStores();
const [toasts, setToasts] = useState<Toast[]>([]);

const handleGraphNameChange = (e: React.ChangeEvent<HTMLInputElement>) => {
const newValue = e.target.value;
if (newValue.length <= MAX_NAME_LENGTH) {
setGraphName(newValue);
setGraphNameError(false);
} else {
setGraphNameError(true);
}
};

const handleGraphUrlChange = (e: React.ChangeEvent<HTMLInputElement>) => {
const newValue = e.target.value;
setGraphUrl(newValue);
setGraphUrlError(false);
};

const addSuccessToast = () => {
setToasts([
{
id: '1',
title: 'Code Graph',
color: 'success',
text: 'Code Graph added successfully'
}
]);
};

const addErrorToast = (text: string) => {
setToasts([
{
id: '2',
title: 'Code Graph',
color: 'danger',
text
}
]);
};

const removeToast = () => {
setToasts([]);
};

const handleSave = async () => {
try {
setIsLoading(true);
const codeGraph = {
workspace_uuid,
uuid: currentUuid || '',
name: normalizeInput(graphName),
url: graphUrl
};

await main.createOrUpdateCodeGraph(codeGraph);
addSuccessToast();
getCodeGraph();
closeHandler();
} catch (error) {
console.error(error);
addErrorToast(String(error));
} finally {
setIsLoading(false);
}
};

return (
<AddWorkspaceWrapper>
<AddWorkspaceHeader>
{modalType === 'add' ? 'Add New Code Graph' : 'Edit Code Graph'}
</AddWorkspaceHeader>
<WorkspaceDetailsContainer>
<WorkspaceInputContainer feature={true} style={{ color: graphNameError ? errcolor : '' }}>
<WorkspaceLabel style={{ color: graphNameError ? errcolor : '' }}>
Code Graph name *
</WorkspaceLabel>
<TextInput
placeholder="Code Graph name"
value={graphName}
feature={true}
data-testid="graph-name-input"
onChange={handleGraphNameChange}
style={{ borderColor: graphNameError ? errcolor : '' }}
/>
</WorkspaceInputContainer>
<WorkspaceInputContainer feature={true} style={{ color: graphUrlError ? errcolor : '' }}>
<WorkspaceLabel style={{ color: graphUrlError ? errcolor : '' }}>
Code Graph url *
</WorkspaceLabel>
<TextInput
placeholder="Code Graph url"
value={graphUrl}
feature={true}
data-testid="graph-url-input"
onChange={handleGraphUrlChange}
style={{ borderColor: graphUrlError ? errcolor : '' }}
/>
</WorkspaceInputContainer>
</WorkspaceDetailsContainer>
<FooterContainer>
<ButtonWrap>
<ActionButton data-testid="codegraph-cancel-btn" onClick={closeHandler} color="cancel">
Cancel
</ActionButton>
<ActionButton
disabled={graphNameError || !graphName || !graphUrl}
data-testid="add-codegraph-btn"
color="primary"
onClick={handleSave}
>
{isLoading ? <EuiLoadingSpinner size="m" /> : 'Save'}
</ActionButton>
{modalType === 'edit' && handleDelete && (
<ActionButton data-testid="delete-codegraph-btn" color="danger" onClick={handleDelete}>
{isLoading ? <EuiLoadingSpinner size="m" /> : 'Delete'}
</ActionButton>
)}
</ButtonWrap>
</FooterContainer>
<EuiGlobalToastList toasts={toasts} dismissToast={removeToast} toastLifeTimeMs={3000} />
</AddWorkspaceWrapper>
);
};

export default AddCodeGraph;
Loading

0 comments on commit 2897787

Please sign in to comment.