From 8431efd13ec7ee4eef2f15c60cb4b8393d738c84 Mon Sep 17 00:00:00 2001 From: Roland Bernard <rolbernard@unibz.it> Date: Mon, 31 May 2021 23:29:29 +0200 Subject: [PATCH] Fixed the display of long task descriptions --- .../layout/{MemberList => UserList}/index.tsx | 24 ++- .../user-list.scss} | 0 client/src/components/ui/Task/task.scss | 2 + client/src/components/ui/TeamMember/index.tsx | 36 ----- client/src/components/ui/User/index.tsx | 36 +++++ .../team-member.scss => User/user.scss} | 0 .../Tasks/TaskDetail/TaskAssignees/index.tsx | 14 +- client/src/pages/Tasks/TaskDetail/index.tsx | 139 +++++++++--------- client/src/pages/Tasks/tasks.scss | 1 + client/src/pages/Teams/TeamsMembers/index.tsx | 32 ++-- 10 files changed, 153 insertions(+), 131 deletions(-) rename client/src/components/layout/{MemberList => UserList}/index.tsx (53%) rename client/src/components/layout/{MemberList/member-list.scss => UserList/user-list.scss} (100%) delete mode 100644 client/src/components/ui/TeamMember/index.tsx create mode 100644 client/src/components/ui/User/index.tsx rename client/src/components/ui/{TeamMember/team-member.scss => User/user.scss} (100%) diff --git a/client/src/components/layout/MemberList/index.tsx b/client/src/components/layout/UserList/index.tsx similarity index 53% rename from client/src/components/layout/MemberList/index.tsx rename to client/src/components/layout/UserList/index.tsx index 620032b..ca288e2 100644 --- a/client/src/components/layout/MemberList/index.tsx +++ b/client/src/components/layout/UserList/index.tsx @@ -1,17 +1,22 @@ import { ReactNode, useState } from 'react'; +import { User } from 'adapters/user'; + import Popup from 'components/ui/Popup'; -import TeamMember, { TeamMemberProps } from 'components/ui/TeamMember'; +import UserComponent from 'components/ui/User'; +import { DropDownItem } from 'components/navigation/Dropdown'; -import './member-list.scss'; +import './user-list.scss'; -interface Props { - members: TeamMemberProps[]; +interface Props<T extends User> { + users: T[]; addContent?: ReactNode + info?: (user: T) => string; + settings?: (user: T) => DropDownItem[]; } -export default function MemberList({ members, addContent }: Props) { +export default function UserList<T extends User>({ users, addContent, info, settings }: Props<T>) { const [showAdd, setShowAdd] = useState(false); return ( <> @@ -29,8 +34,13 @@ export default function MemberList({ members, addContent }: Props) { </div> } - {members.length > 0 ? members.map((member) => ( - <TeamMember key={member.user.id} {...member} /> + {users.length > 0 ? users.map((user) => ( + <UserComponent + key={user.id} + user={user} + info={info} + settings={settings} + /> )) : ( <div>No user found.</div> )} diff --git a/client/src/components/layout/MemberList/member-list.scss b/client/src/components/layout/UserList/user-list.scss similarity index 100% rename from client/src/components/layout/MemberList/member-list.scss rename to client/src/components/layout/UserList/user-list.scss diff --git a/client/src/components/ui/Task/task.scss b/client/src/components/ui/Task/task.scss index 58e8565..7b03b54 100644 --- a/client/src/components/ui/Task/task.scss +++ b/client/src/components/ui/Task/task.scss @@ -64,6 +64,8 @@ .description-container { margin-top: 20px; + max-height: 3rem; + overflow: hidden; } .assignee-list { diff --git a/client/src/components/ui/TeamMember/index.tsx b/client/src/components/ui/TeamMember/index.tsx deleted file mode 100644 index ec69f64..0000000 --- a/client/src/components/ui/TeamMember/index.tsx +++ /dev/null @@ -1,36 +0,0 @@ - -import { User } from 'adapters/user'; - -import Avatar from 'components/ui/Avatar'; -import Dropdown, { DropDownItem } from 'components/navigation/Dropdown'; - -import './team-member.scss'; - -export interface TeamMemberProps { - user: User; - info: string; - settings?: DropDownItem[] -} - -export default function TeamMember({ user, info, settings }: TeamMemberProps) { - return ( - <div className="team-member-item"> - <Avatar user={user} /> - <div className="details"> - <div className="name">{user.realname ?? user.username}</div> - <div className="info">{info}</div> - </div> - { - settings && - <Dropdown items={settings} position="right"> - <div className="settings"> - <span className="material-icons icon"> - expand_more - </span> - </div> - </Dropdown> - } - </div> - ); -} - diff --git a/client/src/components/ui/User/index.tsx b/client/src/components/ui/User/index.tsx new file mode 100644 index 0000000..83dd4cd --- /dev/null +++ b/client/src/components/ui/User/index.tsx @@ -0,0 +1,36 @@ + +import { User } from 'adapters/user'; + +import Avatar from 'components/ui/Avatar'; +import Dropdown, { DropDownItem } from 'components/navigation/Dropdown'; + +import './user.scss'; + +export interface UserProps<T extends User> { + user: T; + info?: (user: T) => string; + settings?: (user: T) => DropDownItem[]; +} + +export default function UserComponent<T extends User>({ user, info, settings }: UserProps<T>) { + return ( + <div className="team-member-item"> + <Avatar user={user} /> + <div className="details"> + <div className="name">{user.realname ?? user.username}</div> + <div className="info">{info?.(user)}</div> + </div> + { + settings && + <Dropdown items={settings(user)} position="right"> + <div className="settings"> + <span className="material-icons icon"> + expand_more + </span> + </div> + </Dropdown> + } + </div> + ); +} + diff --git a/client/src/components/ui/TeamMember/team-member.scss b/client/src/components/ui/User/user.scss similarity index 100% rename from client/src/components/ui/TeamMember/team-member.scss rename to client/src/components/ui/User/user.scss diff --git a/client/src/pages/Tasks/TaskDetail/TaskAssignees/index.tsx b/client/src/pages/Tasks/TaskDetail/TaskAssignees/index.tsx index 201dce2..ade815a 100644 --- a/client/src/pages/Tasks/TaskDetail/TaskAssignees/index.tsx +++ b/client/src/pages/Tasks/TaskDetail/TaskAssignees/index.tsx @@ -1,10 +1,11 @@ -import MemberList from 'components/layout/MemberList'; +import { AssignedUser } from 'adapters/user'; + +import UserList from 'components/layout/UserList'; import LoadingScreen from 'components/ui/LoadingScreen'; -import { TeamMemberProps } from 'components/ui/TeamMember'; interface Props { - assignees: TeamMemberProps[] + assignees: AssignedUser[] } export default function TaskAssignees({ assignees }: Props) { @@ -12,7 +13,12 @@ export default function TaskAssignees({ assignees }: Props) { <section className="task-assignees-section"> { assignees - ? <MemberList members={assignees} /> + ? ( + <UserList + users={assignees} + info={user => user.time + " min"} + /> + ) : <LoadingScreen /> } </section> diff --git a/client/src/pages/Tasks/TaskDetail/index.tsx b/client/src/pages/Tasks/TaskDetail/index.tsx index 020d380..3f157be 100644 --- a/client/src/pages/Tasks/TaskDetail/index.tsx +++ b/client/src/pages/Tasks/TaskDetail/index.tsx @@ -4,7 +4,7 @@ import { useHistory, useParams } from 'react-router'; import { getTeam } from 'adapters/team'; import { StatusColors } from 'adapters/common'; -import { getCurrentUser } from 'adapters/user'; +import { AssignedUser } from 'adapters/user'; import { getProject, Project } from 'adapters/project'; import { getTask, getTaskAssignees, Task, TaskAssignment } from 'adapters/task'; @@ -13,102 +13,107 @@ import Tabs from 'components/navigation/Tabs'; import DetailGrid from 'components/layout/DetailGrid'; import LoadingScreen from 'components/ui/LoadingScreen'; import ButtonLink from 'components/navigation/ButtonLink'; -import { TeamMemberProps } from 'components/ui/TeamMember'; import TaskAssignees from './TaskAssignees'; import TaskComments from './TaskComments'; import './task-detail.scss'; +import {getLoggedInUser} from 'adapters/auth'; export interface Params { taskId: string; } export default function TaskDetail() { + const [more, setMore] = useState(false); const [task, setTask] = useState<Task>(); const [project, setProject] = useState<Project>(); const [teamNames, setTeamNames] = useState<string[]>([]); - const [assignees, setAssignees] = useState<TeamMemberProps[]>([]); + const [assignees, setAssignees] = useState<AssignedUser[]>([]); const [assignment, setAssignment] = useState<TaskAssignment>(); const history = useHistory(); const { taskId } = useParams<Params>(); + const userId = getLoggedInUser(); useEffect(() => { getTask(taskId).then((task) => { setTask(task); - getProject(task.project).then((project) => { + getProject(task.project).then(async (project) => { setProject(project); - project.teams.forEach((teamId) => - getTeam(teamId).then((team) => { - setTeamNames(state => [...state, team.name]) - } - )); + setTeamNames( + (await Promise.all(project.teams.map(getTeam))) + .map(team => team.name) + ); }); - getTaskAssignees(taskId).then(assignees => - setAssignees(assignees.map(assignee => ({ - user: assignee, - info: assignee.time.toString() + ' min' - }))) - ); - getCurrentUser().then((user) => - setAssignment(task.assigned.find(a => a.user === user.id)) - ); + getTaskAssignees(taskId).then(setAssignees); + setAssignment(task.assigned.find(a => a.user === userId)) }).catch(() => history.goBack()); }, [taskId, history]); - return ( - task - ? ( - <div className={'tasks-detail-page theme-' + StatusColors.get(task.status)}> - <span className="material-icons back-btn" onClick={history.goBack} > - arrow_back - </span> - <div className="content-container"> - <Tag label={task.status} color={StatusColors.get(task.status)} /> - <h1>{task.name}</h1> - <div className="description-container"> - <p> - {task.text} - </p> - </div> - <h2> - Details - </h2> - <DetailGrid - details={[ - { icon: 'folder', title: 'Project', label: project?.name ?? 'Loading...' }, - { icon: 'group', title: 'Teams', label: teamNames.join(', ') } - ]} - /> + if (task) { + return ( + <div className={'tasks-detail-page theme-' + StatusColors.get(task.status)}> + <span className="material-icons back-btn" onClick={history.goBack} > + arrow_back + </span> + <div className="content-container"> + <Tag label={task.status} color={StatusColors.get(task.status)} /> + <h1>{task.name}</h1> + <div className="description-container"> { - assignment && !assignment.finished && ( - <ButtonLink href={'/tasks/' + taskId + '/start'} className="expanded"> - Start working - </ButtonLink> - ) + (task.text.length < 300) + ? <p>{task.text}</p> + : (more + ? <> + <p>{task.text}</p> + <a onClick={() => setMore(false)}>less</a> + </> + : <> + <p>{task.text.substr(0, 300) + '... '}</p> + <a onClick={() => setMore(true)}>more</a> + </> + ) } - <ButtonLink href={'/tasks/' + taskId + '/edit'} className="dark expanded"> - Edit - </ButtonLink> - <Tabs - tabs={[ - { - label: 'Assignees', - route: '/tasks/' + taskId, - component: <TaskAssignees assignees={assignees} /> - }, - { - label: 'Comments', - route: '/tasks/' + taskId + '/comments', - component: <TaskComments taskId={taskId} /> - } - ]} - /> </div> + <h2> + Details + </h2> + <DetailGrid + details={[ + { icon: 'folder', title: 'Project', label: project?.name ?? 'Loading...' }, + { icon: 'group', title: 'Teams', label: teamNames.join(', ') } + ]} + /> + { + assignment && !assignment.finished && ( + <ButtonLink href={'/tasks/' + taskId + '/start'} className="expanded"> + Start working + </ButtonLink> + ) + } + <ButtonLink href={'/tasks/' + taskId + '/edit'} className="dark expanded"> + Edit + </ButtonLink> + <Tabs + tabs={[ + { + label: 'Assignees', + route: '/tasks/' + taskId, + component: <TaskAssignees assignees={assignees} /> + }, + { + label: 'Comments', + route: '/tasks/' + taskId + '/comments', + component: <TaskComments taskId={taskId} /> + } + ]} + /> </div> - ) - : <LoadingScreen /> - ); + </div> + ) + } else { + return <LoadingScreen /> + } } diff --git a/client/src/pages/Tasks/tasks.scss b/client/src/pages/Tasks/tasks.scss index bc1a6c5..59515a9 100644 --- a/client/src/pages/Tasks/tasks.scss +++ b/client/src/pages/Tasks/tasks.scss @@ -17,6 +17,7 @@ } .tasks-container { + margin-top: 20px; margin-bottom: 60px; .task { diff --git a/client/src/pages/Teams/TeamsMembers/index.tsx b/client/src/pages/Teams/TeamsMembers/index.tsx index f8ce078..87a2399 100644 --- a/client/src/pages/Teams/TeamsMembers/index.tsx +++ b/client/src/pages/Teams/TeamsMembers/index.tsx @@ -5,7 +5,7 @@ import { getTeamRoles, Team, TeamMember, TeamRole } from 'adapters/team'; import RoleForm from 'components/forms/RoleForm'; import MemberForm from 'components/forms/MemberForm'; -import MemberList from 'components/layout/MemberList'; +import UserList from 'components/layout/UserList'; import LoadingScreen from 'components/ui/LoadingScreen'; import './teams-members.scss'; @@ -27,22 +27,9 @@ export default function TeamsMembers({ members, team }: Props) { { roles ? ( - <MemberList - members={members.map(member => ({ - user: member, - info: member.role.name, - settings: [{ - label: 'Edit role', - popupContent: ( - <RoleForm - setRoles={setRoles} - roles={roles} - team={team} - member={member} - /> - ) - }] - }))} + <UserList + users={members} + info={member => member.role.name} addContent={ <MemberForm setRoles={setRoles} @@ -50,6 +37,17 @@ export default function TeamsMembers({ members, team }: Props) { team={team} /> } + settings={member => [{ + label: 'Edit role', + popupContent: ( + <RoleForm + setRoles={setRoles} + roles={roles} + team={team} + member={member} + /> + ) + }]} /> ) : <LoadingScreen /> -- GitLab