diff --git a/packages/datatrak-web/src/components/Icons/Dot.tsx b/packages/datatrak-web/src/components/Icons/Dot.tsx new file mode 100644 index 0000000000..7b1ed18341 --- /dev/null +++ b/packages/datatrak-web/src/components/Icons/Dot.tsx @@ -0,0 +1,45 @@ +import { Property } from 'csstype'; +import React, { SVGProps } from 'react'; + +export interface DotIconProps extends SVGProps { + /** + * A human-readable title for this SVG, like alt text on an ``. + * @see https://www.w3.org/TR/SVG-access/#Equivalent + */ + titleAccess?: string; + htmlColor?: Property.Color; + variant?: 'filled' | 'outlined'; +} + +export const DotIcon = ({ + titleAccess, + htmlColor = 'currentcolor', + variant = 'filled', + ...props +}: DotIconProps) => { + const circleProps = + variant === 'filled' + ? { + fill: htmlColor, + r: 4, + } + : { + stroke: htmlColor, + strokeWidth: 2, + r: 3, + }; + + return ( + + {titleAccess && {titleAccess}} + + + ); +}; diff --git a/packages/datatrak-web/src/components/Icons/index.ts b/packages/datatrak-web/src/components/Icons/index.ts index 2b65c85b0b..398586695f 100644 --- a/packages/datatrak-web/src/components/Icons/index.ts +++ b/packages/datatrak-web/src/components/Icons/index.ts @@ -1,14 +1,16 @@ +export { ArrowLeftIcon } from './ArrowLeftIcon'; export { Coconut } from './Coconut'; -export { Pig } from './Pig'; -export { SurveyIcon } from './SurveyIcon'; -export { SurveyFolderIcon } from './SurveyFolderIcon'; -export { SurveyTickIcon } from './SurveyTickIcon'; +export { CommentIcon } from './CommentIcon'; +export { CopyIcon } from './CopyIcon'; +export { DotIcon } from './Dot'; +export type { DotIconProps } from './Dot'; export { DownloadIcon } from './DownloadIcon'; -export { RadioIcon } from './RadioIcon'; +export { Pig } from './Pig'; export { PinIcon } from './PinIcon'; +export { RadioIcon } from './RadioIcon'; export { ReportsIcon } from './ReportsIcon'; -export { CopyIcon } from './CopyIcon'; -export { TaskIcon } from './TaskIcon'; -export { CommentIcon } from './CommentIcon'; -export { ArrowLeftIcon } from './ArrowLeftIcon'; export { ShareIcon } from './ShareIcon'; +export { SurveyFolderIcon } from './SurveyFolderIcon'; +export { SurveyIcon } from './SurveyIcon'; +export { SurveyTickIcon } from './SurveyTickIcon'; +export { TaskIcon } from './TaskIcon'; diff --git a/packages/datatrak-web/src/components/SyncIndicator.tsx b/packages/datatrak-web/src/components/SyncIndicator.tsx new file mode 100644 index 0000000000..5639d95b25 --- /dev/null +++ b/packages/datatrak-web/src/components/SyncIndicator.tsx @@ -0,0 +1,24 @@ +import React from 'react'; +import { useTheme } from '@material-ui/core/styles'; + +import { DotIcon, DotIconProps } from './Icons/Dot'; + +export const SyncSuccessIcon = (props: DotIconProps) => { + const { palette } = useTheme(); + return ; +}; + +export const SyncNeutralIcon = (props: DotIconProps) => { + const { palette } = useTheme(); + return ; +}; + +interface SyncIndicatorProps { + syncStatus: 'onlineOnly' | 'availableOffline'; +} +export const SyncIndicator = ({ syncStatus }: SyncIndicatorProps) => + syncStatus === 'onlineOnly' ? ( + + ) : ( + + ); diff --git a/packages/datatrak-web/src/components/Tile.tsx b/packages/datatrak-web/src/components/Tile.tsx index 9f3682f44f..14daad5fc2 100644 --- a/packages/datatrak-web/src/components/Tile.tsx +++ b/packages/datatrak-web/src/components/Tile.tsx @@ -1,164 +1,160 @@ -import React, { ComponentType, ReactNode } from 'react'; -import styled from 'styled-components'; -import { Typography, Box, Paper } from '@material-ui/core'; +import { ButtonProps, Paper, Typography } from '@material-ui/core'; import { Skeleton } from '@material-ui/lab'; +import React, { Fragment, ReactNode } from 'react'; +import styled, { css } from 'styled-components'; + +import { useIsMobile } from '../utils'; import { Button } from './Button'; const Wrapper = styled(Paper).attrs({ elevation: 0, + component: Button, })` - display: flex; - flex-direction: column; - justify-content: space-between; - height: 100%; - padding: 0.8rem 1rem; - background: ${({ theme }) => theme.palette.background.paper}; + align-items: stretch; border-radius: 0.625rem; + display: block flex; + flex-direction: column; + font-size: 0.875rem; font-weight: 400; - font-size: 0.75rem; - color: ${({ theme }) => theme.palette.text.secondary}; + gap: 0.25rem 0.5rem; + inline-size: 14.75rem; + justify-content: flex-start; + line-height: 1.45; + min-block-size: fit-content; overflow: hidden; -`; + padding: 1rem; -const ButtonWrapper = styled(Wrapper).attrs({ - component: Button, -})` - flex-direction: row; - position: relative; - justify-content: flex-start; - align-items: flex-start; - padding-block-start: 0.8rem; - padding-block-end: 0; - padding-inline: 0; - - svg { - margin-right: 0.4rem; - margin-top: 0.2rem; + .MuiButton-label :where(p, h1, h2, h3, h4, h5, h6) { + margin-block: 0; } - &:hover { - background-color: ${({ theme }) => theme.palette.primaryHover}; - } - ${({ theme }) => theme.breakpoints.up('md')} { - padding-inline: 1rem; - padding-block: 0.8rem; - } -` as typeof Button; + ${({ theme }) => css` + color: ${theme.palette.text.secondary}; -const Text = styled(Typography)` - font-size: 0.75rem; - color: ${({ theme }) => theme.palette.text.secondary}; - text-align: left; - display: block; - white-space: nowrap; - overflow: hidden; - text-overflow: ellipsis; - padding-inline: 0.8rem; - &:not(:last-child) { - margin-bottom: 0.2rem; - } - &:last-child { - border-top: 1px solid ${({ theme }) => theme.palette.divider}; - padding-block: 0.5rem; - margin-block-start: 0.4rem; - } - ${({ theme }) => theme.breakpoints.up('md')} { - padding-inline: 0; - &:last-child { - border-top: none; - padding-block: 0; - margin-block-start: 0; + &, + &.Mui-disabled.MuiButton-containedPrimary { + background-color: ${theme.palette.background.paper}; + opacity: initial; } - } -`; -const Heading = styled(Text)` - font-size: 0.875rem; - font-weight: 500; - color: ${({ theme }) => theme.palette.text.primary}; - margin-bottom: 0.2rem; -`; + &:hover { + background-color: ${theme.palette.primaryHover}; + } -const LoadingContainer = styled.div` - overflow: hidden; - max-height: 100%; - > div { - &:not(:last-child) { - margin-bottom: 0.6rem; + ${theme.breakpoints.up('lg')} { + flex-direction: row; + inline-size: 100%; } + `} + + .MuiButton-label { + display: contents; } -`; +` as typeof Button; -const ButtonContent = styled.div` +const Header = styled.header` + align-items: start; display: flex; - flex-direction: column; - width: 100%; - ${({ theme }) => theme.breakpoints.up('md')} { - flex-direction: row; - // To make ellipsis work on the text, we need to set a max-width, and by adding calc(90%) we can make it responsive as well because calc converts the percentage to pixels - max-width: calc(90%); - } + gap: 0.5rem; + justify-content: space-between; + + ${({ theme }) => { + const { down, up } = theme.breakpoints; + return css` + ${down('lg')} { + margin-block-end: 0.5rem; + block-size: 1.5rem; + } + ${up('lg')} { + flex-direction: column; + } + `; + }} `; -const TextWrapper = styled(Box)` - margin-block-start: 0.2rem; - width: 100%; - ${({ theme }) => theme.breakpoints.up('md')} { - margin-block-start: 0; - } +const IconGroup = styled.div` + block-size: 100%; + display: flex; + gap: 0.25rem; +`; +const LeadingIconGroup = styled(IconGroup)` + align-self: flex-start; +`; +const TrailingIconGroup = styled(IconGroup)` + align-self: flex-end; `; -const ContentItem = styled.div` - width: 100%; - padding-inline: 1rem; +const BodyWrapper = styled.div` + flex-grow: 1; +`; - ${({ theme }) => theme.breakpoints.up('md')} { - width: auto; - padding-inline: 0; - } +const Heading = styled(Typography).attrs({ variant: 'h3' })` + color: ${({ theme }) => theme.palette.text.primary}; + font-size: 1rem; + font-weight: 500; + line-height: inherit; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; `; -interface TileProps { - title?: string; - text?: string; +export interface TileProps extends ButtonProps { + heading?: ReactNode; + description?: ReactNode; + leadingIcons?: ReactNode; + trailingIcons?: ReactNode; to?: string; tooltip?: ReactNode; - children?: ReactNode; - Icon?: ComponentType; - onClick?: () => void; } -export const Tile = ({ title, text, children, to, tooltip, Icon, onClick }: TileProps) => { - const content = [text, children].filter(Boolean); +export const Tile = ({ + children, + description, + heading, + leadingIcons, + trailingIcons, + ...props +}: TileProps) => { + const Body = useIsMobile() ? Fragment : BodyWrapper; return ( - - - {Icon && ( - - - - )} - - {title && {title}} - {content.map((content, index) => ( - {content} - ))} - - - + +
+ {leadingIcons && {leadingIcons}} + {trailingIcons && {trailingIcons}} +
+ + {heading && {heading}} + {children} + +
); }; -export const LoadingTile = ({ count = 1 }) => { - return ( - - {Array.from({ length: count }).map((_, index) => ( - - - - - - ))} - - ); -}; +interface TileSkeletonProps { + lineCount?: number; +} +export const TileSkeleton = ({ lineCount = 2 }: TileSkeletonProps) => ( + } + heading={} + > + {Array.from({ length: lineCount }).map((_, i) => ( + + ))} + +); + +export const TileSkeletons = ({ + count = 3, + tileSkeletonProps, +}: { + count?: number; + tileSkeletonProps?: TileSkeletonProps; +}) => ( + <> + {Array.from({ length: count }).map((_, i) => ( + + ))} + +); diff --git a/packages/datatrak-web/src/components/index.ts b/packages/datatrak-web/src/components/index.ts index d297d62d2c..24ecd75328 100644 --- a/packages/datatrak-web/src/components/index.ts +++ b/packages/datatrak-web/src/components/index.ts @@ -5,6 +5,7 @@ export { Button } from './Button'; export { ButtonLink } from './ButtonLink'; export { CancelConfirmModal } from './CancelConfirmModal'; export { ChangeProjectButton } from './ChangeProjectButton'; +export { DateTimeDisplay } from './DateTimeDisplay'; export { ErrorDisplay } from './ErrorDisplay'; export { InputHelperText } from './InputHelperText'; export { Modal } from './Modal'; @@ -12,6 +13,6 @@ export { PageTitleBar } from './PageTitleBar'; export { BlockScrollView, InlineScrollView } from './ScrollView'; export { SmallModal } from './SmallModal'; export { TextInput } from './TextInput'; -export { Tile, LoadingTile } from './Tile'; +export { Tile, TileSkeleton, TileSkeletons } from './Tile'; export { Toast } from './Toast'; export { TopProgressBar } from './TopProgressBar'; diff --git a/packages/datatrak-web/src/features/Tasks/NoTasksSection.tsx b/packages/datatrak-web/src/features/Tasks/NoTasksSection.tsx index 82261dea66..192cf4bcbe 100644 --- a/packages/datatrak-web/src/features/Tasks/NoTasksSection.tsx +++ b/packages/datatrak-web/src/features/Tasks/NoTasksSection.tsx @@ -1,19 +1,22 @@ +import { Typography } from '@material-ui/core'; import React from 'react'; +import { Link } from 'react-router-dom'; 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'; +import { useIsMobile } from '../../utils'; -const DesktopContainer = styled.div` - display: flex; - flex-direction: column; +const Section = styled.section` align-items: center; - height: 100%; + display: flex; + text-wrap: balance; +`; - ${({ theme }) => theme.breakpoints.down('sm')} { - display: none; - } +const DesktopWrapper = styled(Section)` + block-size: 100%; + flex-direction: column; `; const Image = styled.img.attrs({ @@ -44,7 +47,7 @@ const Button = styled(UIButton)` `; const Desktop = () => ( - + Congratulations, you have no tasks to complete! You can view all other tasks for your project @@ -53,17 +56,15 @@ const Desktop = () => ( - + ); -const MobileContainer = styled.div` - display: flex; +const MobileWrapper = styled(Section)` justify-content: space-between; - align-items: center; p { flex: 1; - text-align: left; + text-align: start; margin-inline-end: 1rem; margin-block-end: 0; font-size: 0.75rem; @@ -73,25 +74,16 @@ const MobileContainer = styled.div` display: inline-block; margin-block-end: 0; } - - ${({ theme }) => theme.breakpoints.up('md')} { - display: none; - } `; const Mobile = () => ( - - You have no tasks to complete. + + You have no tasks to complete {/* Todo: Add button back when mobile tasks are ready */} {/**/} - + ); -export const NoTasksSection = () => ( - <> - - - -); +export const NoTasksSection = () => (useIsMobile() ? : ); diff --git a/packages/datatrak-web/src/features/Tasks/TaskDetails/TaskDetails.tsx b/packages/datatrak-web/src/features/Tasks/TaskDetails/TaskDetails.tsx index 3a8b2f6889..b7402c6abb 100644 --- a/packages/datatrak-web/src/features/Tasks/TaskDetails/TaskDetails.tsx +++ b/packages/datatrak-web/src/features/Tasks/TaskDetails/TaskDetails.tsx @@ -1,14 +1,15 @@ import { Paper, Typography } from '@material-ui/core'; -import { TaskStatus } from '@tupaia/types'; -import { LoadingContainer } from '@tupaia/ui-components'; import { parseISO } from 'date-fns'; import React, { useEffect, useState } from 'react'; import { Controller, useForm } from 'react-hook-form'; import styled from 'styled-components'; +import { TaskStatus } from '@tupaia/types'; +import { LoadingContainer } from '@tupaia/ui-components'; + import { useEditTask, useSurveyResponse } from '../../../api'; import { Button as BaseButton, SurveyTickIcon, Tile } from '../../../components'; -import { DateTimeDisplay } from '../../../components/DateTimeDisplay'; +import { DateTimeDisplay } from '../../../components'; import { SingleTaskResponse } from '../../../types'; import { AssigneeInput } from '../AssigneeInput'; import { DueDatePicker } from '../DueDatePicker'; @@ -120,8 +121,7 @@ const InitialRequest = ({ initialRequestId }) => { const { id, countryName, dataTime, surveyName, entityName } = surveyResponse; return ( @@ -130,9 +130,12 @@ const InitialRequest = ({ initialRequestId }) => { {entityName} } - Icon={SurveyTickIcon} + leadingIcons={} > - {countryName}, +

{entityName}

+

+ {countryName}, +

); }; diff --git a/packages/datatrak-web/src/theme/theme.ts b/packages/datatrak-web/src/theme/theme.ts index f390acfb59..06331985bc 100755 --- a/packages/datatrak-web/src/theme/theme.ts +++ b/packages/datatrak-web/src/theme/theme.ts @@ -82,6 +82,11 @@ export const theme = createMuiTheme({ minWidth: 0, padding: 0, }, + ":is(ol, ul)[role='list']": { + listStyleType: 'none', + marginBlock: 0, + paddingInlineStart: 0, + }, }, }, MuiDialogActions: { diff --git a/packages/datatrak-web/src/views/LandingPage/ActivityFeedSection/ActivityFeedSection.tsx b/packages/datatrak-web/src/views/LandingPage/ActivityFeedSection/ActivityFeedSection.tsx index af01f3ca0f..c1d1c537e1 100644 --- a/packages/datatrak-web/src/views/LandingPage/ActivityFeedSection/ActivityFeedSection.tsx +++ b/packages/datatrak-web/src/views/LandingPage/ActivityFeedSection/ActivityFeedSection.tsx @@ -5,7 +5,7 @@ import { DesktopActivityFeed } from './DesktopActivityFeed'; import { MobileActivityFeed } from './MobileActivityFeed'; const ActivityFeed = styled.section` - grid-area: activityFeed; + grid-area: --activityFeed; display: flex; flex-direction: column; height: auto; diff --git a/packages/datatrak-web/src/views/LandingPage/LandingPage.tsx b/packages/datatrak-web/src/views/LandingPage/LandingPage.tsx index 5622d6d042..dbc9f2d06b 100644 --- a/packages/datatrak-web/src/views/LandingPage/LandingPage.tsx +++ b/packages/datatrak-web/src/views/LandingPage/LandingPage.tsx @@ -1,14 +1,15 @@ import React from 'react'; -import styled from 'styled-components'; +import styled, { css } from 'styled-components'; + +import { useCurrentUserRecentSurveys } from '../../api'; import { PageContainer as BasePageContainer } from '../../components'; -import { SurveySelectSection } from './SurveySelectSection'; -import { SurveyResponsesSection } from './SurveyResponsesSection'; -import { LeaderboardSection } from './LeaderboardSection'; +import { HEADER_HEIGHT } from '../../constants'; import { ActivityFeedSection } from './ActivityFeedSection'; +import { LeaderboardSection } from './LeaderboardSection'; import { RecentSurveysSection } from './RecentSurveysSection'; +import { SurveyResponsesSection } from './SurveyResponsesSection'; +import { SurveySelectSection } from './SurveySelectSection'; import { TasksSection } from './TasksSection'; -import { HEADER_HEIGHT } from '../../constants'; -import { useCurrentUserRecentSurveys } from '../../api'; const PageContainer = styled(BasePageContainer)` display: flex; @@ -41,16 +42,14 @@ const PageBody = styled.div` } `; -const Grid = styled.div<{ - $hasMoreThanOneSurvey: boolean; -}>` - flex: 1; +const Grid = styled.div<{ $hasMultiple?: boolean }>` display: flex; flex-direction: column; - min-height: 0; // This is needed to stop the grid overflowing the flex container - max-width: 100%; - margin-inline: auto; + gap: 1.5rem; margin-block: 1.3rem; + margin-inline: auto; + max-inline-size: 100%; + min-block-size: 0; // This is needed to stop the grid overflowing the flex container .MuiButtonBase-root { margin-left: 0; // clear spacing of adjacent buttons @@ -58,46 +57,39 @@ const Grid = styled.div<{ > section { overflow: hidden; - &:not(:last-child) { - margin-bottom: 1rem; - } } - ${({ theme }) => theme.breakpoints.up('md')} { - gap: 1.5rem; - display: grid; - margin-block: 0.5rem; - grid-template-rows: ${({ $hasMoreThanOneSurvey }) => - $hasMoreThanOneSurvey ? 'auto auto auto' : 'auto 7rem auto'}; - grid-template-columns: 23% 1fr 1fr 30%; - grid-template-areas: ${({ $hasMoreThanOneSurvey }) => { - //If there is < 2 surveys, the recentSurveys section will be smaller and the activity feed will shift upwards on larger screens - if ($hasMoreThanOneSurvey) { - return ` - 'surveySelect surveySelect surveySelect tasks' - 'recentSurveys recentSurveys recentSurveys tasks' - 'recentResponses activityFeed activityFeed leaderboard' - `; + ${({ $hasMultiple, theme }) => { + const { up } = theme.breakpoints; + return css` + ${up('md')} { + display: grid; + grid-template-columns: repeat(3, 1fr) 1.4fr; + margin-block: 0.5rem; } - return `'surveySelect surveySelect surveySelect tasks' - 'recentSurveys activityFeed activityFeed tasks' - 'recentResponses activityFeed activityFeed leaderboard' - `; - }}; - > section { - &:not(:last-child) { - margin-bottom: 0; - } - } - > div { - min-height: auto; - } - } + ${up('lg')} { + gap: 1.81rem; + } - ${({ theme }) => theme.breakpoints.up('lg')} { - gap: 1.81rem; - } + // If there is only one survey, Recent Surveys section collapses and Activity Feed shifts up + ${$hasMultiple + ? css` + grid-template-areas: + '--surveySelect --surveySelect --surveySelect --tasks' + '--recentSurveys --recentSurveys --recentSurveys --tasks' + '--recentResponses --activityFeed --activityFeed --leaderboard'; + grid-template-rows: repeat(3, auto); + ` + : css` + grid-template-areas: + '--surveySelect --surveySelect --surveySelect --tasks' + '--recentSurveys --activityFeed --activityFeed --tasks' + '--recentResponses --activityFeed --activityFeed --leaderboard'; + grid-template-rows: auto auto 1fr; + `} + `; + }} `; export const LandingPage = () => { @@ -107,7 +99,7 @@ export const LandingPage = () => { return ( - + diff --git a/packages/datatrak-web/src/views/LandingPage/LeaderboardSection.tsx b/packages/datatrak-web/src/views/LandingPage/LeaderboardSection.tsx index 21a020f98c..9052fed06c 100644 --- a/packages/datatrak-web/src/views/LandingPage/LeaderboardSection.tsx +++ b/packages/datatrak-web/src/views/LandingPage/LeaderboardSection.tsx @@ -4,7 +4,7 @@ import { SectionHeading } from './SectionHeading'; import { Leaderboard } from '../../features'; const Wrapper = styled.section` - grid-area: leaderboard; + grid-area: --leaderboard; display: flex; flex-direction: column; `; diff --git a/packages/datatrak-web/src/views/LandingPage/RecentSurveysSection.tsx b/packages/datatrak-web/src/views/LandingPage/RecentSurveysSection.tsx index 0f5124ca24..00d51e2307 100644 --- a/packages/datatrak-web/src/views/LandingPage/RecentSurveysSection.tsx +++ b/packages/datatrak-web/src/views/LandingPage/RecentSurveysSection.tsx @@ -1,81 +1,105 @@ +import { Typography } from '@material-ui/core'; import React from 'react'; import styled from 'styled-components'; -import { Typography } from '@material-ui/core'; -import { SurveyIcon, Tile, LoadingTile } from '../../components'; + +import { DatatrakWebSurveyResponsesRequest } from '@tupaia/types'; + import { useCurrentUserRecentSurveys } from '../../api'; -import { SectionHeading } from './SectionHeading'; +import { InlineScrollView, SurveyIcon, Tile, TileSkeleton } from '../../components'; +import { TileProps } from '../../components/Tile'; import { useIsMobile } from '../../utils'; +import { SectionHeading } from './SectionHeading'; const RecentSurveys = styled.section` - grid-area: recentSurveys; - display: flex; - flex-direction: column; + display: grid; + grid-area: --recentSurveys; + grid-template-columns: subgrid; + grid-template-rows: auto 1fr; `; -const ScrollBody = styled.div<{ - $hasMoreThanOneSurvey: boolean; -}>` - display: flex; - overflow-x: auto; +const InlineScroll = styled(InlineScrollView).attrs({ + $gap: '1rem', + as: 'ul', + role: 'list', +})``; +const GridScroll = styled.div.attrs({ + as: 'ul', + role: 'list', +})` column-gap: 1rem; + display: grid; + grid-auto-flow: row; + grid-template-columns: subgrid; row-gap: 0.6rem; + grid-column: 1 / -1; +`; - > span, - > a { - width: 18rem; - max-width: 100%; - //Reset flex grow and shrink - flex: 0 0 auto; - } - // make the 2 row grid on desktop - ${({ theme }) => theme.breakpoints.up('md')} { - display: grid; - grid-template-rows: 1fr; - grid-auto-flow: row; - grid-template-columns: ${({ $hasMoreThanOneSurvey }) => - $hasMoreThanOneSurvey ? ' repeat(auto-fill, minmax(calc(33.3% - 1rem), 1fr))' : '1fr'}; - } +const TooltipText = styled.p` + font-weight: normal; + margin-block: 0; + text-align: center; + text-wrap: balance; `; +type RecentSurveyTileProps = TileProps & + Pick< + DatatrakWebSurveyResponsesRequest.SurveyResponse, + 'surveyName' | 'surveyCode' | 'countryName' | 'countryCode' + >; + +const RecentSurveyTile = ({ + surveyName, + surveyCode, + countryName, + countryCode, + ...props +}: RecentSurveyTileProps) => { + const tooltip = ( + <> + {surveyName} + {countryName} + + ); + + return ( + } + to={`/survey/${countryCode}/${surveyCode}/1`} + tooltip={tooltip} + {...props} + > + {countryName} + + ); +}; + export const RecentSurveysSection = () => { - const isMobile = useIsMobile(); - const { data: recentSurveys = [], isSuccess, isLoading } = useCurrentUserRecentSurveys(); - const hasMoreThanOneSurvey = recentSurveys.length > 1; + const { data: recentSurveys = [], isLoading } = useCurrentUserRecentSurveys(); + + const ScrollableList = useIsMobile() ? InlineScroll : GridScroll; + + const renderContents = () => { + if (isLoading) return ; + + if (recentSurveys.length > 0) + return recentSurveys.map(props => ( +
  • + +
  • + )); + + return ( + + No recent surveys to display + + ); + }; return ( Top surveys - - {isLoading && } - {isSuccess && ( - <> - {recentSurveys?.length ? ( - recentSurveys.map(({ surveyName, surveyCode, countryName, countryCode }) => ( - - {surveyName} -
    - {countryName} - - ) : null - } - Icon={SurveyIcon} - to={`/survey/${countryCode}/${surveyCode}/1`} - /> - )) - ) : ( - - No recent surveys to display - - )} - - )} -
    + {renderContents()}
    ); }; diff --git a/packages/datatrak-web/src/views/LandingPage/SectionHeading.tsx b/packages/datatrak-web/src/views/LandingPage/SectionHeading.tsx index 8986527bad..937fadf313 100644 --- a/packages/datatrak-web/src/views/LandingPage/SectionHeading.tsx +++ b/packages/datatrak-web/src/views/LandingPage/SectionHeading.tsx @@ -5,7 +5,8 @@ export const SectionHeading = styled(Typography).attrs({ variant: 'h2', })` font-size: 1rem; - line-height: 1.2; font-weight: 500; - margin-bottom: 0.75rem; + grid-column: 1 / -1; + line-height: 1.2; + margin-block-end: 0.75rem; `; diff --git a/packages/datatrak-web/src/views/LandingPage/SurveyResponsesSection.tsx b/packages/datatrak-web/src/views/LandingPage/SurveyResponsesSection.tsx index f1b4f40b47..00390a1975 100644 --- a/packages/datatrak-web/src/views/LandingPage/SurveyResponsesSection.tsx +++ b/packages/datatrak-web/src/views/LandingPage/SurveyResponsesSection.tsx @@ -1,79 +1,112 @@ -import React from 'react'; import { Typography } from '@material-ui/core'; +import { parseISO } from 'date-fns'; +import React from 'react'; import styled from 'styled-components'; + import { useCurrentUserContext, useCurrentUserSurveyResponses } from '../../api'; -import { displayDate, useIsMobile } from '../../utils'; -import { LoadingTile, SurveyTickIcon, Tile } from '../../components'; +import { + BlockScrollView, + DateTimeDisplay, + InlineScrollView, + SurveyTickIcon, + Tile, +} from '../../components'; +import { TileProps, TileSkeletons } from '../../components/Tile'; +import { useIsMobile } from '../../utils'; import { SectionHeading } from './SectionHeading'; const Container = styled.section` - grid-area: recentResponses; - display: flex; - flex-direction: column; + display: grid; + grid-area: --recentResponses; + grid-template-columns: subgrid; + grid-template-rows: auto 1fr; `; -const ScrollBody = styled.div` - display: flex; - flex-direction: row; - overflow-x: auto; - column-gap: 1rem; - row-gap: 0.6rem; - - > span, - > a { - width: 18rem; - max-width: 100%; - //Reset flex grow and shrink - flex: 0 0 auto; - } +const InlineScroll = styled(InlineScrollView).attrs({ + $gap: '1rem', + as: 'ul', + role: 'list', + size: '100%', +})``; +const BlockScroll = styled(BlockScrollView).attrs({ + $gap: '0.6rem', + as: 'ul', + role: 'list', + size: '100%', +})``; - ${({ theme }) => theme.breakpoints.up('md')} { - flex-direction: column; - overflow: auto; - } +const TooltipText = styled.p` + font-weight: normal; + margin-block: 0; + text-align: center; + text-wrap: balance; `; +interface SurveyResponseTileProps extends TileProps { + id: string; + surveyName: string; + dataTime: string; + entityName: string; + countryName: string; +} +const SurveyResponseTile = ({ + id, + surveyName, + dataTime, + entityName, + countryName, +}: SurveyResponseTileProps) => { + const tooltip = ( + <> + {surveyName} + {entityName} + + ); + + return ( + } + // TODO: Uncomment and de-hard-code when sync is implemented + // trailingIcons={isWebApp() ? : null} + to={`?responseId=${id}`} + tooltip={tooltip} + > +

    {entityName}

    +

    + {countryName}, +

    +
    + ); +}; + export const SurveyResponsesSection = () => { - const { data: recentSurveyResponses, isSuccess, isLoading } = useCurrentUserSurveyResponses(); - const isMobile = useIsMobile(); + const { data: recentSurveyResponses = [], isLoading } = useCurrentUserSurveyResponses(); const { project } = useCurrentUserContext(); + const ScrollableList = useIsMobile() ? InlineScroll : BlockScroll; + + const renderContents = () => { + if (isLoading) return ; + + if (recentSurveyResponses.length > 0) + return recentSurveyResponses.map(props => ( +
  • + +
  • + )); + + return ( + + No recent surveys responses to display for {project?.name || 'project'} + + ); + }; + return ( Submission history - - {isLoading && } - {isSuccess && ( - <> - {recentSurveyResponses?.length > 0 ? ( - recentSurveyResponses.map(({ id, surveyName, dataTime, entityName, countryName }) => ( - - {surveyName} -
    - {entityName} - - ) : null - } - Icon={SurveyTickIcon} - > - {countryName}, {displayDate(dataTime)} -
    - )) - ) : ( - - No recent surveys responses to display for {project?.name || 'project'} - - )}{' '} - - )} -
    + {renderContents()}
    ); }; diff --git a/packages/datatrak-web/src/views/LandingPage/SurveySelectSection.tsx b/packages/datatrak-web/src/views/LandingPage/SurveySelectSection.tsx index 6eb20ecd9d..1eb03708d8 100644 --- a/packages/datatrak-web/src/views/LandingPage/SurveySelectSection.tsx +++ b/packages/datatrak-web/src/views/LandingPage/SurveySelectSection.tsx @@ -1,13 +1,14 @@ import React from 'react'; import { Typography } from '@material-ui/core'; -import styled from 'styled-components'; +import styled, { css } from 'styled-components'; + 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 SectionContainer = styled.section` - grid-area: surveySelect; + grid-area: --surveySelect; background-color: ${({ theme }) => theme.palette.background.paper}; padding: 1rem; display: flex; @@ -17,10 +18,11 @@ const SectionContainer = styled.section` overflow: visible !important; border-radius: 0.625rem; ${({ theme }) => theme.breakpoints.up('md')} { - margin-block-start: 1.3rem; + margin-block-start: 1.9375rem; } ${({ theme }) => theme.breakpoints.up('lg')} { - padding: 1rem 3rem 1rem 2.2rem; + padding-block: 1rem; + inline: 2.2rem 3rem; } `; @@ -29,14 +31,21 @@ const SectionContent = styled.div` flex-direction: column-reverse; width: 70%; padding-inline-end: 2rem; - ${({ theme }) => theme.breakpoints.up('sm')} { - margin-inline-start: 10%; - } - ${({ theme }) => theme.breakpoints.up('md')} { - margin-inline-start: 0; - flex-direction: row; - width: 100%; - align-items: center; + + ${({ theme }) => { + const { up } = theme.breakpoints; + return css` + ${up('sm')} { + margin-inline-start: 10%; + } + ${up('md')} { + margin-inline-start: 0; + flex-direction: row; + width: 100%; + align-items: center; + } + `; + }} { } `; @@ -74,29 +83,39 @@ const TextWrapper = styled.div` margin-block-end: 0.7rem; display: flex; flex-direction: column; - ${({ theme }) => theme.breakpoints.up('md')} { - margin-block-end: 0; - max-width: 75%; - padding-inline: 1rem 4rem; - } - ${({ theme }) => theme.breakpoints.up('lg')} { - padding-inline: 2rem 1rem; - max-width: 80%; - } + ${({ theme }) => { + const { up } = theme.breakpoints; + return css` + ${up('md')} { + margin-block-end: 0; + max-width: 75%; + padding-inline: 1rem 4rem; + } + ${up('lg')} { + padding-inline: 2rem 1rem; + max-width: 80%; + } + `; + }} `; const Text = styled(Typography)` - ${({ theme }) => theme.breakpoints.up('xs')} { - line-height: 1.5; - font-size: 0.9rem; - } - ${({ theme }) => theme.breakpoints.up('sm')} { - font-size: 1rem; - } - ${({ theme }) => theme.breakpoints.up('md')} { - font-size: 0.9rem; - } + ${({ theme }) => { + const { up } = theme.breakpoints; + return css` + ${up('xs')} { + line-height: 1.5; + font-size: 0.9rem; + } + ${up('sm')} { + font-size: 1rem; + } + ${up('md')} { + font-size: 0.9rem; + } + `; + }} `; const DesktopText = styled.span` @@ -105,7 +124,10 @@ const DesktopText = styled.span` } `; -const SurveysImage = styled.img` +const SurveysImage = styled.img.attrs({ + 'aria-hidden': true, + src: '/surveys.svg', +})` position: absolute; width: auto; display: flex; @@ -114,17 +136,22 @@ const SurveysImage = styled.img` transform: translateY(-50%); right: 0; height: 130%; - ${({ theme }) => theme.breakpoints.up('sm')} { - right: 10%; - height: 125%; - } - ${({ theme }) => theme.breakpoints.up('md')} { - right: -1rem; - height: 130%; - } - ${({ theme }) => theme.breakpoints.up('lg')} { - height: 150%; - } + ${({ theme }) => { + const { up } = theme.breakpoints; + return css` + ${up('sm')} { + right: 10%; + height: 125%; + } + ${up('md')} { + right: -1rem; + height: 130%; + } + ${up('lg')} { + height: 150%; + } + `; + }} `; export const SurveySelectSection = () => { @@ -149,7 +176,7 @@ export const SurveySelectSection = () => { - + ); }; diff --git a/packages/datatrak-web/src/views/LandingPage/TasksSection.tsx b/packages/datatrak-web/src/views/LandingPage/TasksSection.tsx index 1e11ae14d1..366308eea1 100644 --- a/packages/datatrak-web/src/views/LandingPage/TasksSection.tsx +++ b/packages/datatrak-web/src/views/LandingPage/TasksSection.tsx @@ -1,16 +1,18 @@ import React from 'react'; -import styled, { css } from 'styled-components'; import { Link } from 'react-router-dom'; -import { useMediaQuery, useTheme } from '@material-ui/core'; +import styled, { css } from 'styled-components'; + import { FlexSpaceBetween, Button as UIButton } from '@tupaia/ui-components'; + import { useCurrentUserContext, useTasks } from '../../api'; -import { NoTasksSection, TaskTile } from '../../features/Tasks'; import { ROUTES } from '../../constants'; -import { LoadingTile } from '../../components'; +import { NoTasksSection, TaskTile } from '../../features/Tasks'; +import { useIsMobile } from '../../utils'; import { SectionHeading } from './SectionHeading'; +import { TileSkeletons } from '../../components'; const SectionContainer = styled.section` - grid-area: tasks; + grid-area: --tasks; display: flex; flex-direction: column; ${({ theme }) => theme.breakpoints.up('lg')} { @@ -23,7 +25,8 @@ const Paper = styled.div<{ $hasTasks?: boolean }>` text-align: center; overflow: auto; background: ${({ theme }) => theme.palette.background.paper}; - padding: 1rem 1.25rem; + padding-block: 1rem; + padding-inline: 1.25rem; border-radius: 10px; ${({ theme, $hasTasks }) => @@ -34,13 +37,13 @@ const Paper = styled.div<{ $hasTasks?: boolean }>` padding: 0; } `} - } `; const Button = styled(UIButton)` margin: 0.3rem auto 0; display: inline-block; - padding: 0.1rem 1rem 0.2rem; + padding-block: 0.1rem 0.2rem; + padding-inline: 1rem; .MuiButton-label { font-size: 0.75rem; } @@ -75,31 +78,18 @@ const TopViewMoreButton = styled(ViewMoreButton)` } `; -const DesktopButton = styled(Button)` - ${({ theme }) => theme.breakpoints.down('sm')} { - display: none; - } -`; - +const DesktopButton = Button; const MobileButton = styled(ViewMoreButton)` float: right; - - ${({ theme }) => theme.breakpoints.up('md')} { - display: none; - } `; const ViewMoreTasksButton = ({ numberOfPages }) => { if (numberOfPages <= 1) return null; + const Button = useIsMobile() ? MobileButton : DesktopButton; return ( - <> - - View more - - - View more... - - + ); }; @@ -115,8 +105,8 @@ export const TasksSection = () => { }, }, ]; - const theme = useTheme(); - const isMobile = useMediaQuery(theme.breakpoints.down('sm')); + + const isMobile = useIsMobile(); const { data = { tasks: [], numberOfPages: 0 }, isLoading, @@ -127,7 +117,7 @@ export const TasksSection = () => { const renderContents = () => { if (isLoading) { - return ; + return ; } if (!hasTasks) { return ; @@ -145,11 +135,11 @@ export const TasksSection = () => { return ( - + My tasks {hasTasks && ( - View more... + View more )} diff --git a/packages/types/src/types/requests/datatrak-web-server/SurveyResponsesRequest.ts b/packages/types/src/types/requests/datatrak-web-server/SurveyResponsesRequest.ts index f1b7ce042a..d08fb74c3b 100644 --- a/packages/types/src/types/requests/datatrak-web-server/SurveyResponsesRequest.ts +++ b/packages/types/src/types/requests/datatrak-web-server/SurveyResponsesRequest.ts @@ -2,11 +2,12 @@ import { SurveyResponse as SurveyResponseT, Country, Entity, Survey } from '../. export type Params = Record; -type SurveyResponse = { +export type SurveyResponse = { assessorName: SurveyResponseT['assessor_name']; countryName: Country['name']; countryCode: Country['code']; - dataTime: Date; + /** ISO9075 format */ + dataTime: string; entityName: Entity['name']; id: SurveyResponseT['id']; surveyName: Survey['name'];