Improved code styling

This commit is contained in:
ireic
2019-12-24 16:39:03 +01:00
parent 4941261251
commit 3c705a6084
81 changed files with 671 additions and 583 deletions

View File

@@ -1,7 +1,7 @@
import styled from 'styled-components';
import { InputDebounced, Avatar, Button } from 'shared/components';
import { color, font, mixin } from 'shared/utils/styles';
import { InputDebounced, Avatar, Button } from 'shared/components';
export const Filters = styled.div`
display: flex;

View File

@@ -16,14 +16,12 @@ const propTypes = {
projectUsers: PropTypes.array.isRequired,
defaultFilters: PropTypes.object.isRequired,
filters: PropTypes.object.isRequired,
setFilters: PropTypes.func.isRequired,
mergeFilters: PropTypes.func.isRequired,
};
const ProjectBoardFilters = ({ projectUsers, defaultFilters, filters, setFilters }) => {
const ProjectBoardFilters = ({ projectUsers, defaultFilters, filters, mergeFilters }) => {
const { searchQuery, userIds, myOnly, recent } = filters;
const setFiltersMerge = newFilters => setFilters({ ...filters, ...newFilters });
const areFiltersCleared = !searchQuery && userIds.length === 0 && !myOnly && !recent;
return (
@@ -31,7 +29,7 @@ const ProjectBoardFilters = ({ projectUsers, defaultFilters, filters, setFilters
<SearchInput
icon="search"
value={searchQuery}
onChange={value => setFiltersMerge({ searchQuery: value })}
onChange={value => mergeFilters({ searchQuery: value })}
/>
<Avatars>
{projectUsers.map(user => (
@@ -39,27 +37,27 @@ const ProjectBoardFilters = ({ projectUsers, defaultFilters, filters, setFilters
<StyledAvatar
avatarUrl={user.avatarUrl}
name={user.name}
onClick={() => setFiltersMerge({ userIds: xor(userIds, [user.id]) })}
onClick={() => mergeFilters({ userIds: xor(userIds, [user.id]) })}
/>
</AvatarIsActiveBorder>
))}
</Avatars>
<StyledButton
color="empty"
variant="empty"
isActive={myOnly}
onClick={() => setFiltersMerge({ myOnly: !myOnly })}
onClick={() => mergeFilters({ myOnly: !myOnly })}
>
Only My Issues
</StyledButton>
<StyledButton
color="empty"
variant="empty"
isActive={recent}
onClick={() => setFiltersMerge({ recent: !recent })}
onClick={() => mergeFilters({ recent: !recent })}
>
Recently Updated
</StyledButton>
{!areFiltersCleared && (
<ClearAll onClick={() => setFilters(defaultFilters)}>Clear all</ClearAll>
<ClearAll onClick={() => mergeFilters(defaultFilters)}>Clear all</ClearAll>
)}
</Filters>
);

View File

@@ -2,6 +2,7 @@ import React from 'react';
import PropTypes from 'prop-types';
import { CopyLinkButton } from 'shared/components';
import { Breadcrumbs, Divider, Header, BoardName } from './Styles';
const propTypes = {
@@ -17,6 +18,7 @@ const ProjectBoardHeader = ({ projectName }) => (
<Divider>/</Divider>
Kanban Board
</Breadcrumbs>
<Header>
<BoardName>Kanban board</BoardName>
<CopyLinkButton />

View File

@@ -1,8 +1,8 @@
import styled, { css } from 'styled-components';
import { Link } from 'react-router-dom';
import { Avatar } from 'shared/components';
import { color, font, mixin } from 'shared/utils/styles';
import { Avatar } from 'shared/components';
export const IssueLink = styled(Link)`
display: block;

View File

@@ -4,6 +4,7 @@ import { useRouteMatch } from 'react-router-dom';
import { Draggable } from 'react-beautiful-dnd';
import { IssueTypeIcon, IssuePriorityIcon } from 'shared/components';
import { IssueLink, Issue, Title, Bottom, Assignees, AssigneeAvatar } from './Styles';
const propTypes = {

View File

@@ -8,6 +8,7 @@ import api from 'shared/utils/api';
import useApi from 'shared/hooks/api';
import { moveItemWithinArray, insertItemIntoArray } from 'shared/utils/javascript';
import { IssueStatus, IssueStatusCopy } from 'shared/constants/issues';
import Issue from './Issue';
import { Lists, List, Title, IssuesCount, Issues } from './Styles';
@@ -21,13 +22,8 @@ const ProjectBoardLists = ({ project, filters, updateLocalIssuesArray }) => {
const [{ data: currentUserData }] = useApi.get('/currentUser');
const currentUserId = get(currentUserData, 'currentUser.id');
const filteredIssues = filterIssues(project.issues, filters, currentUserId);
const handleIssueDrop = async ({ draggableId, destination, source }) => {
if (!destination) return;
const isSameList = destination.droppableId === source.droppableId;
const isSamePosition = destination.index === source.index;
if (isSameList && isSamePosition) return;
if (!isPositionChanged(source, destination)) return;
const issueId = Number(draggableId);
@@ -35,7 +31,7 @@ const ProjectBoardLists = ({ project, filters, updateLocalIssuesArray }) => {
url: `/issues/${issueId}`,
updatedFields: {
status: destination.droppableId,
listPosition: calculateListPosition(project.issues, destination, isSameList, issueId),
listPosition: calculateListPosition(project.issues, destination, source, issueId),
},
currentFields: project.issues.find(({ id }) => id === issueId),
setLocalData: fields => updateLocalIssuesArray(issueId, fields),
@@ -43,21 +39,17 @@ const ProjectBoardLists = ({ project, filters, updateLocalIssuesArray }) => {
};
const renderList = status => {
const filteredIssues = filterIssues(project.issues, filters, currentUserId);
const filteredListIssues = getSortedListIssues(filteredIssues, status);
const allListIssues = getSortedListIssues(project.issues, status);
const issuesCount =
allListIssues.length !== filteredListIssues.length
? `${filteredListIssues.length} of ${allListIssues.length}`
: allListIssues.length;
return (
<Droppable key={status} droppableId={status}>
{provided => (
<List>
<Title>
{`${IssueStatusCopy[status]} `}
<IssuesCount>{issuesCount}</IssuesCount>
<IssuesCount>{formatIssuesCount(allListIssues, filteredListIssues)}</IssuesCount>
</Title>
<Issues {...provided.droppableProps} ref={provided.innerRef}>
{filteredListIssues.map((issue, index) => (
@@ -102,6 +94,20 @@ const filterIssues = (projectIssues, filters, currentUserId) => {
const getSortedListIssues = (issues, status) =>
issues.filter(issue => issue.status === status).sort((a, b) => a.listPosition - b.listPosition);
const formatIssuesCount = (allListIssues, filteredListIssues) => {
if (allListIssues.length !== filteredListIssues.length) {
return `${filteredListIssues.length} of ${allListIssues.length}`;
}
return allListIssues.length;
};
const isPositionChanged = (destination, source) => {
if (!destination) return false;
const isSameList = destination.droppableId === source.droppableId;
const isSamePosition = destination.index === source.index;
return !isSameList || !isSamePosition;
};
const calculateListPosition = (...args) => {
const { prevIssue, nextIssue } = getAfterDropPrevNextIssue(...args);
let position;
@@ -118,9 +124,10 @@ const calculateListPosition = (...args) => {
return position;
};
const getAfterDropPrevNextIssue = (allIssues, destination, isSameList, droppedIssueId) => {
const getAfterDropPrevNextIssue = (allIssues, destination, source, droppedIssueId) => {
const destinationIssues = getSortedListIssues(allIssues, destination.droppableId);
const droppedIssue = allIssues.find(issue => issue.id === droppedIssueId);
const isSameList = destination.droppableId === source.droppableId;
const afterDropDestinationIssues = isSameList
? moveItemWithinArray(destinationIssues, droppedIssue, destination.index)

View File

@@ -1,6 +1,8 @@
import React, { useState } from 'react';
import React from 'react';
import PropTypes from 'prop-types';
import useMergeState from 'shared/hooks/mergeState';
import Header from './Header';
import Filters from './Filters';
import Lists from './Lists';
@@ -18,7 +20,7 @@ const defaultFilters = {
};
const ProjectBoard = ({ project, updateLocalIssuesArray }) => {
const [filters, setFilters] = useState(defaultFilters);
const [filters, mergeFilters] = useMergeState(defaultFilters);
return (
<>
<Header projectName={project.name} />
@@ -26,7 +28,7 @@ const ProjectBoard = ({ project, updateLocalIssuesArray }) => {
projectUsers={project.users}
defaultFilters={defaultFilters}
filters={filters}
setFilters={setFilters}
mergeFilters={mergeFilters}
/>
<Lists project={project} filters={filters} updateLocalIssuesArray={updateLocalIssuesArray} />
</>

View File

@@ -11,6 +11,7 @@ import {
import toast from 'shared/utils/toast';
import useApi from 'shared/hooks/api';
import { Form, IssueTypeIcon, Icon, Avatar, IssuePriorityIcon } from 'shared/components';
import {
FormHeading,
FormElement,
@@ -152,10 +153,10 @@ const ProjectIssueCreateForm = ({ project, fetchProject, modalClose }) => {
renderValue={renderPriority}
/>
<Actions>
<ActionButton type="submit" color="primary" working={isCreating}>
<ActionButton type="submit" variant="primary" working={isCreating}>
Create Issue
</ActionButton>
<ActionButton color="empty" onClick={modalClose}>
<ActionButton variant="empty" onClick={modalClose}>
Cancel
</ActionButton>
</Actions>

View File

@@ -2,6 +2,7 @@ import React, { useRef } from 'react';
import PropTypes from 'prop-types';
import { Textarea } from 'shared/components';
import { Actions, FormButton } from './Styles';
const propTypes = {
@@ -20,6 +21,7 @@ const ProjectBoardIssueDetailsCommentsBodyForm = ({
onCancel,
}) => {
const $textareaRef = useRef();
return (
<>
<Textarea
@@ -31,8 +33,8 @@ const ProjectBoardIssueDetailsCommentsBodyForm = ({
/>
<Actions>
<FormButton
color="primary"
working={isWorking}
variant="primary"
isWorking={isWorking}
onClick={() => {
if ($textareaRef.current.value.trim()) {
onSubmit();
@@ -41,7 +43,7 @@ const ProjectBoardIssueDetailsCommentsBodyForm = ({
>
Save
</FormButton>
<FormButton color="empty" onClick={onCancel}>
<FormButton variant="empty" onClick={onCancel}>
Cancel
</FormButton>
</Actions>

View File

@@ -5,6 +5,7 @@ import api from 'shared/utils/api';
import toast from 'shared/utils/toast';
import { formatDateTimeConversational } from 'shared/utils/dateTime';
import { ConfirmModal } from 'shared/components';
import BodyForm from '../BodyForm';
import {
Comment,
@@ -35,6 +36,7 @@ const ProjectBoardIssueDetailsComment = ({ comment, fetchIssue }) => {
toast.error(error);
}
};
const handleCommentUpdate = async () => {
try {
setUpdating(true);
@@ -46,12 +48,14 @@ const ProjectBoardIssueDetailsComment = ({ comment, fetchIssue }) => {
toast.error(error);
}
};
return (
<Comment>
<UserAvatar name={comment.user.name} avatarUrl={comment.user.avatarUrl} />
<Content>
<Username>{comment.user.name}</Username>
<CreatedAt>{formatDateTimeConversational(comment.createdAt)}</CreatedAt>
{isFormOpen ? (
<BodyForm
value={body}

View File

@@ -2,7 +2,8 @@ import React, { useEffect } from 'react';
import PropTypes from 'prop-types';
import { KeyCodes } from 'shared/constants/keyCodes';
import { isFocusedElementEditable } from 'shared/utils/dom';
import { isFocusedElementEditable } from 'shared/utils/browser';
import { Tip, TipLetter } from './Styles';
const propTypes = {
@@ -17,7 +18,9 @@ const ProjectBoardIssueDetailsCommentsCreateProTip = ({ setFormOpen }) => {
setFormOpen(true);
}
};
document.addEventListener('keydown', handleKeyDown);
return () => {
document.removeEventListener('keydown', handleKeyDown);
};

View File

@@ -4,6 +4,7 @@ import PropTypes from 'prop-types';
import api from 'shared/utils/api';
import useApi from 'shared/hooks/api';
import toast from 'shared/utils/toast';
import BodyForm from '../BodyForm';
import ProTip from './ProTip';
import { Create, UserAvatar, Right, FakeTextarea } from './Styles';

View File

@@ -14,13 +14,14 @@ const ProjectBoardIssueDetailsComments = ({ issue, fetchIssue }) => (
<Comments>
<Title>Comments</Title>
<Create issueId={issue.id} fetchIssue={fetchIssue} />
{sortByNewestFirst(issue.comments).map(comment => (
{sortByNewest(issue.comments).map(comment => (
<Comment key={comment.id} comment={comment} fetchIssue={fetchIssue} />
))}
</Comments>
);
const sortByNewestFirst = items => items.sort((a, b) => -a.createdAt.localeCompare(b.createdAt));
const sortByNewest = items => items.sort((a, b) => -a.createdAt.localeCompare(b.createdAt));
ProjectBoardIssueDetailsComments.propTypes = propTypes;

View File

@@ -2,6 +2,7 @@ import React from 'react';
import PropTypes from 'prop-types';
import { formatDateTimeConversational } from 'shared/utils/dateTime';
import { Dates } from './Styles';
const propTypes = {

View File

@@ -21,13 +21,16 @@ const ProjectBoardIssueDetailsDelete = ({ issue, fetchProject, modalClose }) =>
toast.error(error);
}
};
return (
<ConfirmModal
title="Are you sure you want to delete this issue?"
message="Once you delete, it's gone for good."
confirmText="Delete issue"
onConfirm={handleIssueDelete}
renderLink={modal => <Button icon="trash" iconSize={19} color="empty" onClick={modal.open} />}
renderLink={modal => (
<Button icon="trash" iconSize={19} variant="empty" onClick={modal.open} />
)}
/>
);
};

View File

@@ -1,8 +1,9 @@
import React, { useState } from 'react';
import PropTypes from 'prop-types';
import { getTextContentsFromHtmlString } from 'shared/utils/html';
import { getTextContentsFromHtmlString } from 'shared/utils/browser';
import { TextEditor, TextEditedContent, Button } from 'shared/components';
import { Title, EmptyLabel, Actions } from './Styles';
const propTypes = {
@@ -14,8 +15,15 @@ const ProjectBoardIssueDetailsDescription = ({ issue, updateIssue }) => {
const [value, setValue] = useState(issue.description);
const [isEditing, setEditing] = useState(false);
const handleUpdate = () => {
setEditing(false);
updateIssue({ description: value });
};
const isDescriptionEmpty = getTextContentsFromHtmlString(issue.description).trim().length === 0;
const renderPresentingMode = () =>
isDescriptionEmpty(issue.description) ? (
isDescriptionEmpty ? (
<EmptyLabel onClick={() => setEditing(true)}>Add a description...</EmptyLabel>
) : (
<TextEditedContent content={issue.description} onClick={() => setEditing(true)} />
@@ -25,21 +33,16 @@ const ProjectBoardIssueDetailsDescription = ({ issue, updateIssue }) => {
<>
<TextEditor placeholder="Describe the issue" defaultValue={value} onChange={setValue} />
<Actions>
<Button
color="primary"
onClick={() => {
setEditing(false);
updateIssue({ description: value });
}}
>
<Button variant="primary" onClick={handleUpdate}>
Save
</Button>
<Button color="empty" onClick={() => setEditing(false)}>
<Button variant="empty" onClick={() => setEditing(false)}>
Cancel
</Button>
</Actions>
</>
);
return (
<>
<Title>Description</Title>
@@ -48,9 +51,6 @@ const ProjectBoardIssueDetailsDescription = ({ issue, updateIssue }) => {
);
};
const isDescriptionEmpty = description =>
getTextContentsFromHtmlString(description).trim().length === 0;
ProjectBoardIssueDetailsDescription.propTypes = propTypes;
export default ProjectBoardIssueDetailsDescription;

View File

@@ -1,6 +1,7 @@
import React from 'react';
import { Button, Tooltip } from 'shared/components';
import feedbackImage from './assets/feedback.png';
import { FeedbackDropdown, FeedbackImageCont, FeedbackImage, FeedbackParagraph } from './Styles';
@@ -9,7 +10,7 @@ const ProjectBoardIssueDetailsFeedback = () => (
width={300}
offset={{ top: -15 }}
renderLink={linkProps => (
<Button icon="feedback" color="empty" {...linkProps}>
<Button icon="feedback" variant="empty" {...linkProps}>
Give feedback
</Button>
)}
@@ -18,19 +19,23 @@ const ProjectBoardIssueDetailsFeedback = () => (
<FeedbackImageCont>
<FeedbackImage src={feedbackImage} alt="Give feedback" />
</FeedbackImageCont>
<FeedbackParagraph>
This simplified Jira clone is built with React on the front-end and Node/TypeScript on the
back-end.
</FeedbackParagraph>
<FeedbackParagraph>
{'Read more on our website or reach out via '}
<a href="mailto:ivor@codetree.co">
<strong>ivor@codetree.co</strong>
</a>
</FeedbackParagraph>
<a href="https://codetree.co/" target="_blank" rel="noreferrer noopener">
<Button color="primary">Visit Website</Button>
<Button variant="primary">Visit Website</Button>
</a>
<a href="https://github.com/oldboyxx/jira_clone" target="_blank" rel="noreferrer noopener">
<Button style={{ marginLeft: 10 }} icon="github">
Github Repo

View File

@@ -3,8 +3,9 @@ import PropTypes from 'prop-types';
import { IssuePriority, IssuePriorityCopy } from 'shared/constants/issues';
import { Select, IssuePriorityIcon } from 'shared/components';
import { Priority, Label } from './Styles';
import { SectionTitle } from '../Styles';
import { Priority, Label } from './Styles';
const propTypes = {
issue: PropTypes.object.isRequired,
@@ -18,6 +19,7 @@ const ProjectBoardIssueDetailsPriority = ({ issue, updateIssue }) => {
<Label>{IssuePriorityCopy[priority]}</Label>
</Priority>
);
return (
<>
<SectionTitle>Priority</SectionTitle>

View File

@@ -3,8 +3,9 @@ import PropTypes from 'prop-types';
import { IssueStatus, IssueStatusCopy } from 'shared/constants/issues';
import { Select, Icon } from 'shared/components';
import { Status } from './Styles';
import { SectionTitle } from '../Styles';
import { Status } from './Styles';
const propTypes = {
issue: PropTypes.object.isRequired,

View File

@@ -3,6 +3,7 @@ import PropTypes from 'prop-types';
import { KeyCodes } from 'shared/constants/keyCodes';
import { is, generateErrors } from 'shared/utils/validation';
import { TitleTextarea, ErrorText } from './Styles';
const propTypes = {

View File

@@ -3,6 +3,8 @@ import PropTypes from 'prop-types';
import { isNil } from 'lodash';
import { InputDebounced, Modal, Button } from 'shared/components';
import { SectionTitle } from '../Styles';
import {
TrackingLink,
Tracking,
@@ -18,7 +20,6 @@ import {
InputLabel,
Actions,
} from './Styles';
import { SectionTitle } from '../Styles';
const propTypes = {
issue: PropTypes.object.isRequired,
@@ -38,52 +39,6 @@ const ProjectBoardIssueDetailsTracking = ({ issue, updateIssue }) => {
/>
);
const calculateTrackingBarWidth = () => {
const { timeSpent, timeRemaining, estimate } = issue;
if (!timeSpent) {
return 0;
}
if (isNil(timeRemaining) && isNil(estimate)) {
return 100;
}
if (!isNil(timeRemaining)) {
return (timeSpent / (timeSpent + timeRemaining)) * 100;
}
if (!isNil(estimate)) {
return Math.min((timeSpent / estimate) * 100, 100);
}
};
const renderRemainingOrEstimate = () => {
const { timeRemaining, estimate } = issue;
if (isNil(timeRemaining) && isNil(estimate)) {
return null;
}
if (!isNil(timeRemaining)) {
return <div>{`${timeRemaining}h remaining`}</div>;
}
if (!isNil(estimate)) {
return <div>{`${estimate}h estimated`}</div>;
}
};
const renderTrackingPreview = (onClick = () => {}) => (
<Tracking onClick={onClick}>
<WatchIcon type="stopwatch" size={26} top={-1} />
<Right>
<BarCont>
<Bar width={calculateTrackingBarWidth()} />
</BarCont>
<Values>
<div>{issue.timeSpent ? `${issue.timeSpent}h logged` : 'No time logged'}</div>
{renderRemainingOrEstimate()}
</Values>
</Right>
</Tracking>
);
const renderEstimate = () => (
<>
<SectionTitle>Original Estimate (hours)</SectionTitle>
@@ -96,11 +51,11 @@ const ProjectBoardIssueDetailsTracking = ({ issue, updateIssue }) => {
<SectionTitle>Time Tracking</SectionTitle>
<Modal
width={400}
renderLink={modal => <TrackingLink>{renderTrackingPreview(modal.open)}</TrackingLink>}
renderLink={modal => <TrackingLink>{renderTrackingWidget(modal.open)}</TrackingLink>}
renderContent={modal => (
<ModalContents>
<ModalTitle>Time tracking</ModalTitle>
{renderTrackingPreview()}
{renderTrackingWidget()}
<Inputs>
<InputCont>
<InputLabel>Time spent (hours)</InputLabel>
@@ -112,7 +67,7 @@ const ProjectBoardIssueDetailsTracking = ({ issue, updateIssue }) => {
</InputCont>
</Inputs>
<Actions>
<Button color="primary" onClick={modal.close}>
<Button variant="primary" onClick={modal.close}>
Done
</Button>
</Actions>
@@ -122,6 +77,21 @@ const ProjectBoardIssueDetailsTracking = ({ issue, updateIssue }) => {
</>
);
const renderTrackingWidget = (onClick = () => {}) => (
<Tracking onClick={onClick}>
<WatchIcon type="stopwatch" size={26} top={-1} />
<Right>
<BarCont>
<Bar width={calculateTrackingBarWidth(issue)} />
</BarCont>
<Values>
<div>{issue.timeSpent ? `${issue.timeSpent}h logged` : 'No time logged'}</div>
{renderRemainingOrEstimate(issue)}
</Values>
</Right>
</Tracking>
);
return (
<>
{renderEstimate()}
@@ -130,6 +100,33 @@ const ProjectBoardIssueDetailsTracking = ({ issue, updateIssue }) => {
);
};
const calculateTrackingBarWidth = ({ timeSpent, timeRemaining, estimate }) => {
if (!timeSpent) {
return 0;
}
if (isNil(timeRemaining) && isNil(estimate)) {
return 100;
}
if (!isNil(timeRemaining)) {
return (timeSpent / (timeSpent + timeRemaining)) * 100;
}
if (!isNil(estimate)) {
return Math.min((timeSpent / estimate) * 100, 100);
}
};
const renderRemainingOrEstimate = ({ timeRemaining, estimate }) => {
if (isNil(timeRemaining) && isNil(estimate)) {
return null;
}
if (!isNil(timeRemaining)) {
return <div>{`${timeRemaining}h remaining`}</div>;
}
if (!isNil(estimate)) {
return <div>{`${estimate}h estimated`}</div>;
}
};
ProjectBoardIssueDetailsTracking.propTypes = propTypes;
export default ProjectBoardIssueDetailsTracking;

View File

@@ -3,6 +3,7 @@ import PropTypes from 'prop-types';
import { IssueType, IssueTypeCopy } from 'shared/constants/issues';
import { IssueTypeIcon, Select } from 'shared/components';
import { TypeButton, Type, TypeLabel } from './Styles';
const propTypes = {
@@ -21,7 +22,7 @@ const ProjectBoardIssueDetailsType = ({ issue, updateIssue }) => (
}))}
onChange={type => updateIssue({ type })}
renderValue={({ value: type }) => (
<TypeButton color="empty" icon={<IssueTypeIcon type={type} />}>
<TypeButton variant="empty" icon={<IssueTypeIcon type={type} />}>
{`${type}-${issue.id}`}
</TypeButton>
)}

View File

@@ -2,8 +2,9 @@ import React from 'react';
import PropTypes from 'prop-types';
import { Avatar, Select, Icon } from 'shared/components';
import { User, Username } from './Styles';
import { SectionTitle } from '../Styles';
import { User, Username } from './Styles';
const propTypes = {
issue: PropTypes.object.isRequired,

View File

@@ -4,6 +4,7 @@ import PropTypes from 'prop-types';
import api from 'shared/utils/api';
import useApi from 'shared/hooks/api';
import { PageError, CopyLinkButton, Button } from 'shared/components';
import Loader from './Loader';
import Type from './Type';
import Feedback from './Feedback';
@@ -61,9 +62,9 @@ const ProjectBoardIssueDetails = ({
<Type issue={issue} updateIssue={updateIssue} />
<TopActionsRight>
<Feedback />
<CopyLinkButton color="empty" />
<CopyLinkButton variant="empty" />
<Delete issue={issue} fetchProject={fetchProject} modalClose={modalClose} />
<Button icon="close" iconSize={24} color="empty" onClick={modalClose} />
<Button icon="close" iconSize={24} variant="empty" onClick={modalClose} />
</TopActionsRight>
</TopActions>
<Content>

View File

@@ -2,7 +2,7 @@ import styled from 'styled-components';
import { NavLink } from 'react-router-dom';
import { font, sizes, color, mixin, zIndexValues } from 'shared/utils/styles';
import Logo from 'shared/components/Logo';
import { Logo } from 'shared/components';
export const NavLeft = styled.aside`
z-index: ${zIndexValues.navLeft};

View File

@@ -2,6 +2,7 @@ import React from 'react';
import { Link, useRouteMatch } from 'react-router-dom';
import { Icon } from 'shared/components';
import { NavLeft, LogoLink, StyledLogo, Bottom, Item, ItemText } from './Styles';
const ProjectNavbarLeft = () => {

View File

@@ -1,4 +1,4 @@
import styled, { css } from 'styled-components';
import styled from 'styled-components';
import { NavLink } from 'react-router-dom';
import { color, sizes, font, mixin, zIndexValues } from 'shared/utils/styles';
@@ -51,11 +51,7 @@ export const LinkItem = styled(NavLink)`
${props =>
!props.implemented
? `cursor: not-allowed;`
: css`
&:hover {
background: ${color.backgroundLight};
}
`}
: `&:hover { background: ${color.backgroundLight}; }`}
i {
margin-right: 15px;
font-size: 20px;

View File

@@ -1,7 +1,9 @@
import React from 'react';
import PropTypes from 'prop-types';
import { useRouteMatch } from 'react-router-dom';
import { Icon, ProjectAvatar } from 'shared/components';
import {
Sidebar,
ProjectInfo,
@@ -16,17 +18,19 @@ import {
const propTypes = {
projectName: PropTypes.string.isRequired,
matchPath: PropTypes.string.isRequired,
};
const ProjectSidebar = ({ projectName, matchPath }) => {
const ProjectSidebar = ({ projectName }) => {
const match = useRouteMatch();
const renderLinkItem = (text, iconType, path = '') => (
<LinkItem exact to={`${matchPath}${path}`} implemented={path}>
<LinkItem exact to={`${match.path}${path}`} implemented={path}>
<Icon type={iconType} />
<LinkText>{text}</LinkText>
{!path && <NotImplemented>Not implemented</NotImplemented>}
</LinkItem>
);
return (
<Sidebar>
<ProjectInfo>
@@ -36,6 +40,7 @@ const ProjectSidebar = ({ projectName, matchPath }) => {
<ProjectCategory>Software project</ProjectCategory>
</ProjectTexts>
</ProjectInfo>
{renderLinkItem('Kanban Board', 'board', '/board')}
{renderLinkItem('Reports', 'reports')}
<Divider />

View File

@@ -4,6 +4,7 @@ import { Route, Redirect, useRouteMatch, useHistory } from 'react-router-dom';
import useApi from 'shared/hooks/api';
import { updateArrayItemById } from 'shared/utils/javascript';
import { PageLoader, PageError, Modal } from 'shared/components';
import NavbarLeft from './NavbarLeft';
import Sidebar from './Sidebar';
import Board from './Board';
@@ -38,12 +39,24 @@ const Project = () => {
updateLocalIssuesArray={updateLocalIssuesArray}
/>
);
const renderIssueCreateModal = () => (
<Modal
isOpen
width={800}
onClose={() => history.push(`${match.url}/board`)}
renderContent={modal => (
<IssueCreateForm project={project} fetchProject={fetchProject} modalClose={modal.close} />
)}
/>
);
const renderIssueDetailsModal = routeProps => (
<Modal
isOpen
width={1040}
withCloseIcon={false}
onClose={() => history.push(match.url)}
onClose={() => history.push(`${match.url}/board`)}
renderContent={modal => (
<IssueDetails
issueId={routeProps.match.params.issueId}
@@ -55,20 +68,11 @@ const Project = () => {
)}
/>
);
const renderIssueCreateModal = () => (
<Modal
isOpen
width={800}
onClose={() => history.push(match.url)}
renderContent={modal => (
<IssueCreateForm project={project} fetchProject={fetchProject} modalClose={modal.close} />
)}
/>
);
return (
<ProjectPage>
<NavbarLeft />
<Sidebar projectName={project.name} matchPath={match.path} />
<Sidebar projectName={project.name} />
<Route path={`${match.path}/board`} render={renderBoard} />
<Route path={`${match.path}/board/create-issue`} render={renderIssueCreateModal} />
<Route path={`${match.path}/board/issue/:issueId`} render={renderIssueDetailsModal} />