diff --git a/packages/central-server/src/apiV2/tasks/GETTasks.js b/packages/central-server/src/apiV2/tasks/GETTasks.js index 457c72c27e..65fa0fd7a4 100644 --- a/packages/central-server/src/apiV2/tasks/GETTasks.js +++ b/packages/central-server/src/apiV2/tasks/GETTasks.js @@ -26,20 +26,23 @@ export class GETTasks extends GETHandler { } async getDbQueryOptions() { - const { multiJoin, sort, ...restOfOptions } = await super.getDbQueryOptions(); + const { multiJoin, sort, rawSort, ...restOfOptions } = await super.getDbQueryOptions(); - return { + const options = { ...restOfOptions, - // Strip table prefix from `task_status` and `assignee_name` as these are customColumns - sort: - !restOfOptions.rawSort && - sort?.map(s => - s - .replace('task.task_status', 'task_status') - .replace('task.assignee_name', 'assignee_name'), - ), // Appending the multi-join from the Record class so that we can fetch the `task_status` and `assignee_name` multiJoin: mergeMultiJoin(multiJoin, this.models.task.DatabaseRecordClass.joins), }; + + if (rawSort) { + options.rawSort = rawSort; + } else { + // Strip table prefix from `task_status` and `assignee_name` as these are customColumns + options.sort = sort?.map(s => + s.replace('task.task_status', 'task_status').replace('task.assignee_name', 'assignee_name'), + ); + } + + return options; } } diff --git a/packages/datatrak-web-server/src/routes/TasksRoute.ts b/packages/datatrak-web-server/src/routes/TasksRoute.ts index 5d3bbcbb73..bacddce998 100644 --- a/packages/datatrak-web-server/src/routes/TasksRoute.ts +++ b/packages/datatrak-web-server/src/routes/TasksRoute.ts @@ -147,7 +147,6 @@ export class TasksRoute extends Route { this.formatFilters(); await this.processFilterSettings(); - // If no sort is provided, default to sorting completed and cancelled tasks to the bottom and by due date const rawSort = !sort && diff --git a/packages/datatrak-web/src/api/queries/useTasks.ts b/packages/datatrak-web/src/api/queries/useTasks.ts index 816b5b28fa..440121fead 100644 --- a/packages/datatrak-web/src/api/queries/useTasks.ts +++ b/packages/datatrak-web/src/api/queries/useTasks.ts @@ -9,7 +9,7 @@ import { get } from '../api'; type Filter = { id: string; - value: string; + value: string | object; }; type SortBy = { @@ -17,13 +17,15 @@ type SortBy = { desc: boolean; }; -export const useTasks = ( - projectId?: string, - pageSize?: number, - page?: number, - filters: Filter[] = [], - sortBy?: SortBy[], -) => { +interface UseTasksOptions { + projectId?: string; + pageSize?: number; + page?: number; + filters?: Filter[]; + sortBy?: SortBy[]; +} + +export const useTasks = ({ projectId, pageSize, page, filters = [], sortBy }: UseTasksOptions) => { return useQuery( ['tasks', projectId, pageSize, page, filters, sortBy], (): Promise => diff --git a/packages/datatrak-web/src/features/Tasks/NoTasksSection.tsx b/packages/datatrak-web/src/features/Tasks/NoTasksSection.tsx new file mode 100644 index 0000000000..d8b1455c0b --- /dev/null +++ b/packages/datatrak-web/src/features/Tasks/NoTasksSection.tsx @@ -0,0 +1,57 @@ +/* + * Tupaia + * Copyright (c) 2017 - 2023 Beyond Essential Systems Pty Ltd + */ +import React from 'react'; +import styled from 'styled-components'; +import { Typography } from '@material-ui/core'; +import { Button as UIButton } from '@tupaia/ui-components'; +import { Link } from 'react-router-dom'; +import { ROUTES } from '../../constants'; + +const Container = styled.div` + display: flex; + flex-direction: column; + align-items: center; + height: 100%; + text-align: center; +`; + +const Image = styled.img.attrs({ + src: '/tupaia-high-five.svg', + alt: 'Illustration of two hands giving a high five', +})` + flex: 1; + height: auto; + min-height: 5rem; + width: auto; + margin: 0 auto 1rem; +`; + +const Text = styled(Typography)` + text-align: center; + font-size: 0.9rem; + line-height: 1.5; + margin-block-end: 0.5rem; +`; + +const Button = styled(UIButton)` + padding: 0.25rem 1rem; + margin-block-end: 0.5rem; + + .MuiButton-label { + font-size: 0.75rem; + } +`; +export const NoTasksSection = () => ( + + + + Congratulations, you have no tasks to complete! You can view all other tasks for your project + using the button below. + + + +); diff --git a/packages/datatrak-web/src/features/Tasks/StatusPill.tsx b/packages/datatrak-web/src/features/Tasks/StatusPill.tsx index cc962c227b..c2044f51af 100644 --- a/packages/datatrak-web/src/features/Tasks/StatusPill.tsx +++ b/packages/datatrak-web/src/features/Tasks/StatusPill.tsx @@ -14,9 +14,9 @@ const Pill = styled.span<{ }>` background-color: ${({ $color }) => `${$color}22`}; color: ${({ $color }) => $color}; - font-size: 0.7rem; + font-size: 0.625rem; padding-inline: 0.7rem; - padding-block: 0.3rem; + padding-block: 0.2rem; border-radius: 20px; .cell-content > div:has(&) { overflow: visible; diff --git a/packages/datatrak-web/src/features/Tasks/TaskTile.tsx b/packages/datatrak-web/src/features/Tasks/TaskTile.tsx new file mode 100644 index 0000000000..d53f41e9ff --- /dev/null +++ b/packages/datatrak-web/src/features/Tasks/TaskTile.tsx @@ -0,0 +1,111 @@ +/* + * Tupaia + * Copyright (c) 2017 - 2023 Beyond Essential Systems Pty Ltd + */ + +import React from 'react'; +import styled from 'styled-components'; +import { generatePath, Link } from 'react-router-dom'; +import ChatIcon from '@material-ui/icons/ChatBubbleOutline'; +import { ROUTES } from '../../constants'; +import { StatusPill } from './StatusPill'; +import { displayDate } from '../../utils'; +import { ButtonLink } from '../../components'; + +const TileContainer = styled.div` + display: flex; + text-align: left; + justify-content: space-between; + border-radius: 10px; + border: 1px solid ${({ theme }) => theme.palette.divider}; + width: 100%; + padding: 0.4rem 0.7rem; + margin-block-end: 0.5rem; + + .MuiButton-root { + padding: 0.2rem 1.2rem; + } + + .MuiButton-label { + font-size: 0.75rem; + } +`; + +const TileTitle = styled.div` + font-weight: 500; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; + margin-block-end: 0.25rem; + color: ${({ theme }) => theme.palette.text.primary}; +`; + +const TileLeft = styled.div` + flex: 1; + padding-inline-end: 1rem; + min-width: 0; +`; + +const TileContent = styled.div` + display: flex; + font-size: 0.75rem; + color: ${({ theme }) => theme.palette.text.secondary}; + align-items: center; + + > span { + margin-inline-end: 0.6rem; + } +`; + +const TileRight = styled.div` + display: flex; + flex-direction: column; + justify-content: center; +`; + +const CommentsContainer = styled.div` + display: flex; + align-items: center; + + .MuiSvgIcon-root { + font-size: 1rem; + margin-inline-end: 0.2rem; + } +`; + +const Comments = ({ commentsCount = 0 }) => { + if (!commentsCount) { + return null; + } + return ( + + + {commentsCount} + + ); +}; + +export const TaskTile = ({ task }) => { + const { survey, entity, taskStatus, dueDate } = task; + const surveyLink = generatePath(ROUTES.SURVEY, { + surveyCode: survey.code, + countryCode: entity.countryCode, + }); + return ( + + + {survey.name} + + + {displayDate(dueDate)} + + + + + + Complete task + + + + ); +}; diff --git a/packages/datatrak-web/src/features/Tasks/TasksTable/TasksTable.tsx b/packages/datatrak-web/src/features/Tasks/TasksTable/TasksTable.tsx index 30c13878bd..f8b0b13746 100644 --- a/packages/datatrak-web/src/features/Tasks/TasksTable/TasksTable.tsx +++ b/packages/datatrak-web/src/features/Tasks/TasksTable/TasksTable.tsx @@ -50,7 +50,7 @@ const useTasksTable = () => { const urlFilters = searchParams.get('filters'); const filters = urlFilters ? JSON.parse(urlFilters) : []; - const { data, isLoading } = useTasks(projectId, pageSize, page, filters, sortBy); + const { data, isLoading } = useTasks({ projectId, pageSize, page, filters, sortBy }); const updateSorting = newSorting => { searchParams.set('sortBy', JSON.stringify(newSorting)); diff --git a/packages/datatrak-web/src/features/Tasks/index.ts b/packages/datatrak-web/src/features/Tasks/index.ts index c8ffe1d4c4..fba3813d86 100644 --- a/packages/datatrak-web/src/features/Tasks/index.ts +++ b/packages/datatrak-web/src/features/Tasks/index.ts @@ -6,5 +6,7 @@ export { TaskPageHeader } from './TaskPageHeader'; export { TasksTable } from './TasksTable'; export { TaskDetails } from './TaskDetails'; +export { NoTasksSection } from './NoTasksSection'; +export { TaskTile } from './TaskTile'; export { CreateTaskModal } from './CreateTaskModal'; export { TaskActionsMenu } from './TaskActionsMenu'; diff --git a/packages/datatrak-web/src/views/LandingPage/LandingPage.tsx b/packages/datatrak-web/src/views/LandingPage/LandingPage.tsx index 177985a749..304cf976fe 100644 --- a/packages/datatrak-web/src/views/LandingPage/LandingPage.tsx +++ b/packages/datatrak-web/src/views/LandingPage/LandingPage.tsx @@ -11,6 +11,7 @@ import { SurveyResponsesSection } from './SurveyResponsesSection'; import { LeaderboardSection } from './LeaderboardSection'; import { ActivityFeedSection } from './ActivityFeedSection'; import { RecentSurveysSection } from './RecentSurveysSection'; +import { TasksSection } from './TasksSection'; import { DESKTOP_MEDIA_QUERY, HEADER_HEIGHT } from '../../constants'; const PageContainer = styled(BasePageContainer)` @@ -21,7 +22,7 @@ const PageContainer = styled(BasePageContainer)` const PageBody = styled.div` display: flex; flex-direction: column; - padding: 1.5rem 0; + padding: 0.5rem 0 1.5rem; width: 100%; max-width: 85rem; margin: 0 auto; @@ -29,12 +30,7 @@ const PageBody = styled.div` ${({ theme }) => theme.breakpoints.up('md')} { height: calc(100vh - ${HEADER_HEIGHT}); - padding: 2rem 2.75rem 0.8rem 2.75rem; - } - - ${DESKTOP_MEDIA_QUERY} { - padding-top: 4rem; - padding-bottom: 2.5rem; + padding: 0.2rem 1rem 0.8rem; } `; @@ -44,6 +40,8 @@ const Grid = styled.div` flex-direction: column; margin-top: 1rem; min-height: 0; // This is needed to stop the grid overflowing the flex container + max-width: 38rem; + margin-inline: auto; .MuiButtonBase-root { margin-left: 0; // clear spacing of adjacent buttons @@ -57,11 +55,13 @@ const Grid = styled.div` ${({ theme }) => theme.breakpoints.up('md')} { display: grid; gap: 1.25rem; - grid-template-rows: auto auto; - grid-template-columns: 1fr 1fr; + grid-template-rows: 20rem auto auto; + grid-template-columns: 2fr 1fr; grid-template-areas: - 'recentSurveys leaderboard' - 'recentResponses activityFeed'; + 'surveySelect tasks' + 'recentSurveys recentResponses' + 'activityFeed leaderboard'; + max-width: none; > section { margin: 0; @@ -70,9 +70,11 @@ const Grid = styled.div` } ${({ theme }) => theme.breakpoints.up('lg')} { - grid-template-columns: 23% 1fr 1fr 28%; + grid-template-rows: 10.5rem auto auto; + grid-template-columns: 23% 1fr 1fr 30%; grid-template-areas: - 'recentSurveys recentSurveys recentSurveys leaderboard' + 'surveySelect surveySelect surveySelect tasks' + 'recentSurveys recentSurveys recentSurveys tasks' 'recentResponses activityFeed activityFeed leaderboard'; > div { min-height: auto; @@ -80,17 +82,20 @@ const Grid = styled.div` } ${DESKTOP_MEDIA_QUERY} { - margin-top: 2.5rem; gap: 1.81rem; } `; export const LandingPage = () => { + // Todo: Remove this once the feature is complete + const showTasks = process.env.REACT_APP_TUPAIA_TASKS; + return ( - + + {showTasks && } diff --git a/packages/datatrak-web/src/views/LandingPage/SectionHeading.tsx b/packages/datatrak-web/src/views/LandingPage/SectionHeading.tsx index bd8236837b..d1bc45fe2b 100644 --- a/packages/datatrak-web/src/views/LandingPage/SectionHeading.tsx +++ b/packages/datatrak-web/src/views/LandingPage/SectionHeading.tsx @@ -10,6 +10,7 @@ export const SectionHeading = styled(Typography).attrs({ variant: 'h2', })` font-size: 1rem; + line-height: 1.2; font-weight: 500; margin-bottom: 0.75rem; `; diff --git a/packages/datatrak-web/src/views/LandingPage/SurveySelectSection.tsx b/packages/datatrak-web/src/views/LandingPage/SurveySelectSection.tsx index 4b631a23bc..3505e838cf 100644 --- a/packages/datatrak-web/src/views/LandingPage/SurveySelectSection.tsx +++ b/packages/datatrak-web/src/views/LandingPage/SurveySelectSection.tsx @@ -6,12 +6,13 @@ import React from 'react'; import { Typography } from '@material-ui/core'; import styled from 'styled-components'; -import { DESKTOP_MEDIA_QUERY, ROUTES } from '../../constants'; +import { ROUTES } from '../../constants'; import { Button, ButtonLink as BaseButtonLink } from '../../components'; const TUPAIA_REDIRECT_URL = process.env.REACT_APP_TUPAIA_REDIRECT_URL || 'https://tupaia.org'; -const SurveyAlert = styled.div` +const SectionContainer = styled.section` + grid-area: surveySelect; background-color: ${({ theme }) => theme.palette.background.paper}; border-radius: 0.625rem; padding: 1rem; @@ -19,20 +20,35 @@ const SurveyAlert = styled.div` position: relative; align-items: flex-start; justify-content: space-between; + overflow: visible !important; + height: max-content; + ${({ theme }) => theme.breakpoints.up('sm')} { - padding: 1rem 2.3rem; + padding: 1rem 3rem 1rem 2.2rem; + margin-block-start: 2.1rem !important; + } +`; + +const SectionContent = styled.div` + display: flex; + flex-direction: column-reverse; + width: 70%; + padding-inline-end: 2rem; + ${({ theme }) => theme.breakpoints.up('md')} { + flex-direction: row; + width: 100%; + align-items: center; } `; const ButtonLink = styled(BaseButtonLink)` font-size: 1rem; - padding-left: 0.5rem; - padding-right: 0.5rem; + padding-inline: 0.5rem; & ~ .MuiButtonBase-root { - margin-left: 0; // override default margin from ui-components + margin-inline-start: 0; // override default margin from ui-components } &:last-child { - margin-top: 1rem; + margin-block-start: 1rem; } `; @@ -50,76 +66,63 @@ const ButtonWrapper = styled.div` line-height: 1.1; padding: 0.75rem; &:last-child { - margin-top: 0.625rem; + margin-block-start: 0.625rem; } } `; const TextWrapper = styled.div` - margin: 0; + margin-block-end: 1rem; display: flex; flex-direction: column; ${({ theme }) => theme.breakpoints.up('md')} { - padding-right: 4rem; + margin-block-end: 0; max-width: 75%; - padding-left: 2rem; + padding-inline: 2rem 4rem; } ${({ theme }) => theme.breakpoints.up('lg')} { - padding-right: 1rem; + padding-inline-end: 1rem; max-width: 80%; } `; const Text = styled(Typography)` ${({ theme }) => theme.breakpoints.up('sm')} { - font-size: 1rem; + font-size: 0.9rem; line-height: 1.5; } `; const DesktopText = styled.span` - ${({ theme }) => theme.breakpoints.down('sm')} { + ${({ theme }) => theme.breakpoints.down('xs')} { display: none; } `; const SurveysImage = styled.img` - width: auto; - height: calc(100% + 3rem); position: absolute; + width: auto; display: flex; align-items: center; - right: 0; - top: -1.5rem; - ${({ theme }) => theme.breakpoints.up('lg')} { - top: -20%; - right: 2rem; - height: 150%; - } + top: 50%; + transform: translateY(-50%); + right: 1rem; + height: 100%; - ${DESKTOP_MEDIA_QUERY} { - top: -2rem; - height: calc(100% + 6rem); + ${({ theme }) => theme.breakpoints.up('md')} { + right: -1rem; } -`; -const SurveyAlertContent = styled.div` - display: flex; - flex-direction: column-reverse; - width: 70%; - padding-right: 2rem; - ${({ theme }) => theme.breakpoints.up('md')} { - flex-direction: row; - width: 100%; - align-items: center; + ${({ theme }) => theme.breakpoints.up('lg')} { + height: 160%; } `; export const SurveySelectSection = () => { return ( - - + + Select survey + )} + + ); + } else { + Contents = ; + } + + return ( + + + My tasks + {hasTasks && ( + + View more... + + )} + + {Contents} + + ); +};