diff --git a/apps/smart-forms-app/cypress/e2e/auth.cy.ts b/apps/smart-forms-app/cypress/e2e/auth.cy.ts index 75b6e0c26..54676d608 100644 --- a/apps/smart-forms-app/cypress/e2e/auth.cy.ts +++ b/apps/smart-forms-app/cypress/e2e/auth.cy.ts @@ -22,11 +22,8 @@ describe('launch app in SMART Launcher', () => { const launchPage = 'http://localhost:5173/launch'; it('from launch page, redirect to picker', () => { - cy.intercept('/').as('homepage'); - cy.visit(launchPage); - cy.wait('@homepage').its('response.statusCode').should('eq', 200); - cy.location('pathname').should('eq', '/'); + cy.getByData('button-unlaunched-state'); }); }); diff --git a/apps/smart-forms-app/cypress/e2e/populate.cy.ts b/apps/smart-forms-app/cypress/e2e/populate.cy.ts index 133da8818..4db624f55 100644 --- a/apps/smart-forms-app/cypress/e2e/populate.cy.ts +++ b/apps/smart-forms-app/cypress/e2e/populate.cy.ts @@ -50,7 +50,7 @@ describe('populate form', () => { it('form items in Patient Details tab have expected populated answers', () => { cy.goToPatientDetailsTab(); - cy.getByData('q-item-string-box').eq(0).find('input').should('have.value', 'Benito Lucio'); + cy.getByData('q-item-string-box').eq(0).find('input').should('have.value', 'Lucio, Benito Mr.'); cy.getByData('q-item-date-box').eq(0).find('input').should('have.value', '18/08/1936'); cy.getByData('q-item-choice-radio-answer-value-set-box') @@ -97,7 +97,7 @@ describe('populate form', () => { cy.previewForm(); cy.getByData('response-item-text').contains('Name'); - cy.getByData('response-item-answer').contains('Benito Lucio'); + cy.getByData('response-item-answer').contains('Lucio, Benito Mr.'); cy.getByData('response-item-text').contains('Date of birth'); cy.getByData('response-item-answer').contains('18/08/1936'); diff --git a/apps/smart-forms-app/package.json b/apps/smart-forms-app/package.json index 6844d32c5..45759b2e2 100644 --- a/apps/smart-forms-app/package.json +++ b/apps/smart-forms-app/package.json @@ -23,6 +23,7 @@ "@sentry/react": "^7.58.1", "@sentry/tracing": "^7.57.0", "@tanstack/react-query": "^4.29.5", + "@tanstack/react-table": "^8.9.3", "allotment": "^1.19.0", "dayjs": "^1.11.7", "fhirclient": "^2.5.2", diff --git a/apps/smart-forms-app/src/components/Button/UnlaunchedButton.tsx b/apps/smart-forms-app/src/components/Button/UnlaunchedButton.tsx new file mode 100644 index 000000000..8768c139f --- /dev/null +++ b/apps/smart-forms-app/src/components/Button/UnlaunchedButton.tsx @@ -0,0 +1,21 @@ +import { Button } from '@mui/material'; +import { useNavigate } from 'react-router-dom'; + +function UnlaunchedButton() { + const navigate = useNavigate(); + + return ( + + ); +} + +export default UnlaunchedButton; diff --git a/apps/smart-forms-app/src/components/Header/DesktopHeader.tsx b/apps/smart-forms-app/src/components/Header/DesktopHeaderIcons.tsx similarity index 78% rename from apps/smart-forms-app/src/components/Header/DesktopHeader.tsx rename to apps/smart-forms-app/src/components/Header/DesktopHeaderIcons.tsx index aab78e8fc..14d72e432 100644 --- a/apps/smart-forms-app/src/components/Header/DesktopHeader.tsx +++ b/apps/smart-forms-app/src/components/Header/DesktopHeaderIcons.tsx @@ -15,23 +15,26 @@ * limitations under the License. */ -import { Avatar, Box, useTheme } from '@mui/material'; +import { Avatar, Box, Typography, useTheme } from '@mui/material'; import { AccountDetailsTypography, AccountNameTypography } from '../Typography/Typography.tsx'; import { constructName } from '../../features/smartAppLaunch/utils/launchContext.ts'; import MedicalServicesIcon from '@mui/icons-material/MedicalServices'; import useConfigStore from '../../stores/useConfigStore.ts'; -function DesktopHeader() { +function DesktopHeaderIcons() { const user = useConfigStore((state) => state.user); const theme = useTheme(); return ( - - + + + + User + {user && user.gender ? : null} @@ -39,4 +42,4 @@ function DesktopHeader() { ); } -export default DesktopHeader; +export default DesktopHeaderIcons; diff --git a/apps/smart-forms-app/src/features/dashboard/components/DashboardHeader/DashboardHeader.tsx b/apps/smart-forms-app/src/components/Header/GenericHeader.tsx similarity index 52% rename from apps/smart-forms-app/src/features/dashboard/components/DashboardHeader/DashboardHeader.tsx rename to apps/smart-forms-app/src/components/Header/GenericHeader.tsx index 5eb2fa1f4..e1a7ebfc5 100644 --- a/apps/smart-forms-app/src/features/dashboard/components/DashboardHeader/DashboardHeader.tsx +++ b/apps/smart-forms-app/src/components/Header/GenericHeader.tsx @@ -15,30 +15,22 @@ * limitations under the License. */ -import { Box, Stack } from '@mui/material'; -import Iconify from '../../../../components/Iconify/Iconify.tsx'; -import { useTheme } from '@mui/material/styles'; -import DesktopHeader from '../../../../components/Header/DesktopHeader.tsx'; -import useResponsive from '../../../../hooks/useResponsive.ts'; -import Logo from '../../../../components/Logos/Logo.tsx'; -import { LogoWrapper } from '../../../../components/Logos/Logo.styles.ts'; -import { - MenuIconButton, - StyledRoot, - StyledToolbar -} from '../../../../components/Header/Header.styles.ts'; -import MobileHeaderWithQuestionnaire from '../../../../components/Header/MobileHeaderWithoutQuestionnaire.tsx'; +import { Box } from '@mui/material'; +import Iconify from '../Iconify/Iconify.tsx'; +import useResponsive from '../../hooks/useResponsive.ts'; +import Logo from '../Logos/Logo.tsx'; +import { LogoWrapper } from '../Logos/Logo.styles.ts'; +import { MenuIconButton, StyledRoot, StyledToolbar } from './Header.styles.ts'; import { memo } from 'react'; +import HeaderIcons from './HeaderIcons.tsx'; interface DashboardHeaderProps { onOpenNav: () => void; } -const DashboardHeader = memo(function DashboardHeader(props: DashboardHeaderProps) { +const GenericHeader = memo(function DashboardHeader(props: DashboardHeaderProps) { const { onOpenNav } = props; - const theme = useTheme(); - const isDesktop = useResponsive('up', 'lg'); return ( @@ -53,21 +45,13 @@ const DashboardHeader = memo(function DashboardHeader(props: DashboardHeaderProp ) : null} + - - {isDesktop ? : } - + ); }); -export default DashboardHeader; +export default GenericHeader; diff --git a/apps/smart-forms-app/src/components/Header/Header.styles.ts b/apps/smart-forms-app/src/components/Header/Header.styles.ts index fc4592dc5..aee077fe5 100644 --- a/apps/smart-forms-app/src/components/Header/Header.styles.ts +++ b/apps/smart-forms-app/src/components/Header/Header.styles.ts @@ -18,9 +18,9 @@ import { alpha, styled } from '@mui/material/styles'; import { AppBar, IconButton, Toolbar } from '@mui/material'; -const NAV_WIDTH = 240; -const HEADER_MOBILE = 64; -const HEADER_DESKTOP = 72; +export const NAV_WIDTH = 240; +export const HEADER_MOBILE_HEIGHT = 52; +export const HEADER_DESKTOP_HEIGHT = 60; export const MenuIconButton = styled(IconButton)(({ theme }) => ({ paddingRight: '8px', @@ -43,9 +43,13 @@ export const StyledRoot = styled(AppBar, { })); export const StyledToolbar = styled(Toolbar)(({ theme }) => ({ - minHeight: HEADER_MOBILE, + height: HEADER_MOBILE_HEIGHT, + padding: theme.spacing(0, 1), + [theme.breakpoints.up('sm')]: { + padding: theme.spacing(0, 2) + }, [theme.breakpoints.up('lg')]: { - minHeight: HEADER_DESKTOP, + minHeight: HEADER_DESKTOP_HEIGHT, padding: theme.spacing(0, 4) } })); diff --git a/apps/smart-forms-app/src/components/Header/HeaderIcons.tsx b/apps/smart-forms-app/src/components/Header/HeaderIcons.tsx new file mode 100644 index 000000000..044369956 --- /dev/null +++ b/apps/smart-forms-app/src/components/Header/HeaderIcons.tsx @@ -0,0 +1,35 @@ +/* + * Copyright 2023 Commonwealth Scientific and Industrial Research + * Organisation (CSIRO) ABN 41 687 119 230. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import DesktopHeaderIcons from './DesktopHeaderIcons.tsx'; +import MobileHeaderIcons from './MobileHeaderIcons.tsx'; +import { Stack } from '@mui/material'; +import { useTheme } from '@mui/material/styles'; +import useResponsive from '../../hooks/useResponsive.ts'; + +function HeaderIcons() { + const theme = useTheme(); + const isDesktop = useResponsive('up', 'lg'); + + return ( + + {isDesktop ? : } + + ); +} + +export default HeaderIcons; diff --git a/apps/smart-forms-app/src/components/Header/MobileHeaderWithoutQuestionnaire.tsx b/apps/smart-forms-app/src/components/Header/MobileHeaderIcons.tsx similarity index 70% rename from apps/smart-forms-app/src/components/Header/MobileHeaderWithoutQuestionnaire.tsx rename to apps/smart-forms-app/src/components/Header/MobileHeaderIcons.tsx index 20f3aac5c..0000adb3a 100644 --- a/apps/smart-forms-app/src/components/Header/MobileHeaderWithoutQuestionnaire.tsx +++ b/apps/smart-forms-app/src/components/Header/MobileHeaderIcons.tsx @@ -15,42 +15,45 @@ * limitations under the License. */ -import { Box } from '@mui/material'; -import NavErrorAlert from '../Nav/NavErrorAlert.tsx'; -import PersonPopover from './Popovers/PersonPopover.tsx'; +import HeaderPopover from './Popovers/HeaderPopover.tsx'; import FaceIcon from '@mui/icons-material/Face'; import PatientPopoverMenu from './Popovers/PatientPopoverMenu.tsx'; import MedicalServicesIcon from '@mui/icons-material/MedicalServices'; import UserPopoverMenu from './Popovers/UserPopoverMenu.tsx'; import { useTheme } from '@mui/material/styles'; +import { Box } from '@mui/material'; +import NotLaunchedPopover from './Popovers/NotLaunchedPopover.tsx'; import useConfigStore from '../../stores/useConfigStore.ts'; -function MobileHeaderWithQuestionnaire() { - const smartClient = useConfigStore((state) => state.smartClient); - +function MobileHeaderIcons() { const theme = useTheme(); + const smartClient = useConfigStore((state) => state.smartClient); const isNotLaunched = !smartClient; return ( <> {isNotLaunched ? ( - - + + ) : null} - } + } menuContent={} /> - } + displayIcon={ + + } menuContent={} /> ); } -export default MobileHeaderWithQuestionnaire; +export default MobileHeaderIcons; diff --git a/apps/smart-forms-app/src/components/Header/MobileHeaderWithQuestionnaire.tsx b/apps/smart-forms-app/src/components/Header/MobileHeaderWithQuestionnaire.tsx deleted file mode 100644 index 971c08138..000000000 --- a/apps/smart-forms-app/src/components/Header/MobileHeaderWithQuestionnaire.tsx +++ /dev/null @@ -1,63 +0,0 @@ -/* - * Copyright 2023 Commonwealth Scientific and Industrial Research - * Organisation (CSIRO) ABN 41 687 119 230. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import { Box } from '@mui/material'; -import NavErrorAlert from '../Nav/NavErrorAlert.tsx'; -import PersonPopover from './Popovers/PersonPopover.tsx'; -import AssignmentIcon from '@mui/icons-material/Assignment'; -import QuestionnairePopoverMenu from './Popovers/QuestionnairePopoverMenu.tsx'; -import FaceIcon from '@mui/icons-material/Face'; -import PatientPopoverMenu from './Popovers/PatientPopoverMenu.tsx'; -import MedicalServicesIcon from '@mui/icons-material/MedicalServices'; -import UserPopoverMenu from './Popovers/UserPopoverMenu.tsx'; -import { useTheme } from '@mui/material/styles'; -import useConfigStore from '../../stores/useConfigStore.ts'; - -function MobileHeaderWithQuestionnaire() { - const smartClient = useConfigStore((state) => state.smartClient); - - const theme = useTheme(); - - const isNotLaunched = !smartClient; - - return ( - <> - {isNotLaunched ? ( - - - - ) : null} - } - menuContent={} - /> - } - menuContent={} - /> - } - menuContent={} - /> - - ); -} - -export default MobileHeaderWithQuestionnaire; diff --git a/apps/smart-forms-app/src/components/Header/Popovers/HeaderPopover.tsx b/apps/smart-forms-app/src/components/Header/Popovers/HeaderPopover.tsx new file mode 100644 index 000000000..9089868da --- /dev/null +++ b/apps/smart-forms-app/src/components/Header/Popovers/HeaderPopover.tsx @@ -0,0 +1,62 @@ +/* + * Copyright 2023 Commonwealth Scientific and Industrial Research + * Organisation (CSIRO) ABN 41 687 119 230. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import PopupState, { bindPopover, bindTrigger } from 'material-ui-popup-state'; +import { Avatar, IconButton, Popover, Stack, Typography } from '@mui/material'; +import type { ReactElement } from 'react'; + +interface HeaderPopoverProps { + entity: 'Patient' | 'User' | 'Form'; + bgColor: string; + displayIcon: ReactElement; + menuContent: ReactElement; +} + +function HeaderPopover(props: HeaderPopoverProps) { + const { entity, bgColor, displayIcon, menuContent } = props; + + return ( + <> + + {(popupState) => ( +
+ + + {displayIcon} + + + {entity} + + + + {menuContent} + +
+ )} +
+ + ); +} + +export default HeaderPopover; diff --git a/apps/smart-forms-app/src/components/Header/Popovers/PersonPopover.tsx b/apps/smart-forms-app/src/components/Header/Popovers/NotLaunchedPopover.tsx similarity index 63% rename from apps/smart-forms-app/src/components/Header/Popovers/PersonPopover.tsx rename to apps/smart-forms-app/src/components/Header/Popovers/NotLaunchedPopover.tsx index 4f1f326ca..a6ae7e08e 100644 --- a/apps/smart-forms-app/src/components/Header/Popovers/PersonPopover.tsx +++ b/apps/smart-forms-app/src/components/Header/Popovers/NotLaunchedPopover.tsx @@ -16,29 +16,25 @@ */ import PopupState, { bindPopover, bindTrigger } from 'material-ui-popup-state'; -import { Avatar, IconButton, Popover } from '@mui/material'; - -interface PersonPopoverProps { - bgColor: string; - displayIcon: JSX.Element; - menuContent: JSX.Element; -} - -function PersonPopover(props: PersonPopoverProps) { - const { bgColor, displayIcon, menuContent } = props; +import { IconButton, Popover, Stack, Typography } from '@mui/material'; +import ErrorIcon from '@mui/icons-material/Error'; +import { PopoverMenuWrapper } from './Popover.styles.ts'; +function NotLaunchedPopover() { return ( <> {(popupState) => (
- - {displayIcon} - + + + + + - {menuContent} + + + Save operations disabled, app not launched via SMART + +
)} @@ -65,4 +59,4 @@ function PersonPopover(props: PersonPopoverProps) { ); } -export default PersonPopover; +export default NotLaunchedPopover; diff --git a/apps/smart-forms-app/src/components/Layout/Layout.styles.ts b/apps/smart-forms-app/src/components/Layout/Layout.styles.ts index 2f9948f06..84b63b84b 100644 --- a/apps/smart-forms-app/src/components/Layout/Layout.styles.ts +++ b/apps/smart-forms-app/src/components/Layout/Layout.styles.ts @@ -16,9 +16,7 @@ */ import { Box, styled } from '@mui/material'; - -const APP_BAR_MOBILE = 64; -const APP_BAR_DESKTOP = 72; +import { HEADER_DESKTOP_HEIGHT, HEADER_MOBILE_HEIGHT } from '../Header/Header.styles.ts'; export const StyledRoot = styled(Box)({ display: 'flex', @@ -30,15 +28,15 @@ export const Main = styled(Box)(({ theme }) => ({ flexGrow: 1, overflow: 'auto', minHeight: '100%', - paddingTop: APP_BAR_MOBILE + 12, - paddingBottom: theme.spacing(4), + paddingTop: HEADER_MOBILE_HEIGHT + 8, + paddingBottom: theme.spacing(3), [theme.breakpoints.up('md')]: { - paddingTop: APP_BAR_DESKTOP + 16, + paddingTop: HEADER_DESKTOP_HEIGHT + 12, paddingLeft: theme.spacing(1), paddingRight: theme.spacing(1) }, [theme.breakpoints.up('lg')]: { - paddingTop: APP_BAR_DESKTOP + 16, + paddingTop: HEADER_DESKTOP_HEIGHT + 16, paddingLeft: theme.spacing(2), paddingRight: theme.spacing(2) } diff --git a/apps/smart-forms-app/src/components/Logos/Logo.tsx b/apps/smart-forms-app/src/components/Logos/Logo.tsx index 05b14d045..36680a05b 100644 --- a/apps/smart-forms-app/src/components/Logos/Logo.tsx +++ b/apps/smart-forms-app/src/components/Logos/Logo.tsx @@ -18,12 +18,21 @@ import { memo } from 'react'; import { Box, Typography } from '@mui/material'; import AppLogo from '../../data/images/logo.svg'; +import useResponsive from '../../hooks/useResponsive.ts'; + +interface LogoProps { + isNav?: boolean; +} + +const Logo = memo(function Logo(props: LogoProps) { + const { isNav } = props; + + const isDesktop = useResponsive('up', 'lg'); -const Logo = memo(function Logo() { return ( - - Smart Forms + + {isDesktop || isNav ? Smart Forms : null} ); }); diff --git a/apps/smart-forms-app/src/components/Nav/Nav.styles.ts b/apps/smart-forms-app/src/components/Nav/Nav.styles.ts index 978490285..bb8aa2ff1 100644 --- a/apps/smart-forms-app/src/components/Nav/Nav.styles.ts +++ b/apps/smart-forms-app/src/components/Nav/Nav.styles.ts @@ -64,12 +64,15 @@ export const StyledAlert = styled(Box, { })<{ color: 'info' | 'error' }>(({ theme, color }) => ({ display: 'flex', alignItems: 'center', - padding: theme.spacing(1.5), + padding: theme.spacing(1.25), borderRadius: Number(theme.shape.borderRadius) * 1.5, backgroundColor: alpha( color === 'error' ? theme.palette.error.light : theme.palette.info.light, 0.12 ), + [theme.breakpoints.up('sm')]: { + padding: theme.spacing(1.5) + }, [theme.breakpoints.up('md')]: { padding: theme.spacing(2) } diff --git a/apps/smart-forms-app/src/components/Nav/NavPatientDetails.tsx b/apps/smart-forms-app/src/components/Nav/NavPatientDetails.tsx index 99d6ae78b..ead43d796 100644 --- a/apps/smart-forms-app/src/components/Nav/NavPatientDetails.tsx +++ b/apps/smart-forms-app/src/components/Nav/NavPatientDetails.tsx @@ -15,7 +15,7 @@ * limitations under the License. */ -import { Box } from '@mui/material'; +import { Box, Typography } from '@mui/material'; import FaceIcon from '@mui/icons-material/Face'; import { constructName } from '../../features/smartAppLaunch/utils/launchContext.ts'; import dayjs from 'dayjs'; @@ -37,6 +37,9 @@ function NavPatientDetails() { + + Patient + {patient ? ( <> diff --git a/apps/smart-forms-app/src/components/Toggles/QuestionnaireSourceToggle.tsx b/apps/smart-forms-app/src/components/Toggles/QuestionnaireSourceToggle.tsx deleted file mode 100644 index c17e72598..000000000 --- a/apps/smart-forms-app/src/components/Toggles/QuestionnaireSourceToggle.tsx +++ /dev/null @@ -1,55 +0,0 @@ -/* - * Copyright 2023 Commonwealth Scientific and Industrial Research - * Organisation (CSIRO) ABN 41 687 119 230. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import { FormControlLabel, Switch, Typography } from '@mui/material'; -import { useContext } from 'react'; -import { SelectedQuestionnaireContext } from '../../features/dashboard/contexts/SelectedQuestionnaireContext.tsx'; -import useConfigStore from '../../stores/useConfigStore.ts'; - -interface Props { - setPage: (page: number) => void; -} -function QuestionnaireSourceToggle(props: Props) { - const { setPage } = props; - - const questionnaireSource = useConfigStore((state) => state.questionnaireSource); - const updateQuestionnaireSource = useConfigStore((state) => state.updateQuestionnaireSource); - - const { clearSelectedQuestionnaire } = useContext(SelectedQuestionnaireContext); - - return ( - { - updateQuestionnaireSource(questionnaireSource === 'local' ? 'remote' : 'local'); - clearSelectedQuestionnaire(); - setPage(0); - }} - /> - } - label={ - - {questionnaireSource} - - } - /> - ); -} - -export default QuestionnaireSourceToggle; diff --git a/apps/smart-forms-app/src/features/dashboard/components/DashboardNav/DashboardNav.tsx b/apps/smart-forms-app/src/features/dashboard/components/DashboardNav/DashboardNav.tsx index d930e1ae8..711f17984 100644 --- a/apps/smart-forms-app/src/features/dashboard/components/DashboardNav/DashboardNav.tsx +++ b/apps/smart-forms-app/src/features/dashboard/components/DashboardNav/DashboardNav.tsx @@ -27,15 +27,14 @@ import GoToPlaygroundButton from '../DashboardPages/QuestionnairePage/Buttons/Go import { CsiroLogoWrapper, NavLogoWrapper } from '../../../../components/Logos/Logo.styles.ts'; import { NavMiddleWrapper } from '../../../../components/Nav/Nav.styles.ts'; import useConfigStore from '../../../../stores/useConfigStore.ts'; +import { NAV_WIDTH } from '../../../../components/Header/Header.styles.ts'; -const NAV_WIDTH = 240; - -interface Props { +interface DashboardNavProps { openNav: boolean; onCloseNav: () => void; } -export default function DashboardNav(props: Props) { +export default function DashboardNav(props: DashboardNavProps) { const { openNav, onCloseNav } = props; const smartClient = useConfigStore((state) => state.smartClient); @@ -52,12 +51,12 @@ export default function DashboardNav(props: Props) { '& .simplebar-content': { height: 1, display: 'flex', flexDirection: 'column' } }}> - + - + diff --git a/apps/smart-forms-app/src/features/dashboard/components/DashboardNav/DashboardNavItem.tsx b/apps/smart-forms-app/src/features/dashboard/components/DashboardNav/DashboardNavItem.tsx index 22227ebeb..d670e97f2 100644 --- a/apps/smart-forms-app/src/features/dashboard/components/DashboardNav/DashboardNavItem.tsx +++ b/apps/smart-forms-app/src/features/dashboard/components/DashboardNav/DashboardNavItem.tsx @@ -17,13 +17,21 @@ import { ListItemButton, ListItemText, useTheme } from '@mui/material'; import { NavLink } from 'react-router-dom'; -import type { NavItem } from '../../../../types/Nav.interface.ts'; import { StyledNavItemIcon } from '../../../../components/Nav/Nav.styles.ts'; +import type { ReactNode } from 'react'; import { useContext } from 'react'; import { SelectedQuestionnaireContext } from '../../contexts/SelectedQuestionnaireContext.tsx'; -function DashboardNavItem(props: NavItem) { - const { title, path, icon, disabled } = props; +export interface DashboardNavItemProps { + title: string; + path: string; + icon: ReactNode; + disabled?: boolean; + onCloseNav: () => void; +} + +function DashboardNavItem(props: DashboardNavItemProps) { + const { title, path, icon, disabled, onCloseNav } = props; const { setSelectedQuestionnaire } = useContext(SelectedQuestionnaireContext); const theme = useTheme(); @@ -35,7 +43,10 @@ function DashboardNavItem(props: NavItem) { disableGutters disabled={disabled} data-test="list-button-dashboard-nav-page" - onClick={() => setSelectedQuestionnaire(null)} + onClick={() => { + setSelectedQuestionnaire(null); + onCloseNav(); + }} sx={{ ...theme.typography.subtitle2, height: 48, diff --git a/apps/smart-forms-app/src/features/dashboard/components/DashboardNav/DashboardNavSection.tsx b/apps/smart-forms-app/src/features/dashboard/components/DashboardNav/DashboardNavSection.tsx index 1992c2122..b115db3b0 100644 --- a/apps/smart-forms-app/src/features/dashboard/components/DashboardNav/DashboardNavSection.tsx +++ b/apps/smart-forms-app/src/features/dashboard/components/DashboardNav/DashboardNavSection.tsx @@ -27,7 +27,13 @@ import { } from '../../../../components/Nav/Nav.styles.ts'; import useConfigStore from '../../../../stores/useConfigStore.ts'; -const DashboardNavSection = memo(function DashboardNavSection() { +interface DashboardNavSectionProps { + onCloseNav: () => void; +} + +const DashboardNavSection = memo(function DashboardNavSection(props: DashboardNavSectionProps) { + const { onCloseNav } = props; + const smartClient = useConfigStore((state) => state.smartClient); return ( @@ -40,12 +46,14 @@ const DashboardNavSection = memo(function DashboardNavSection() { title={'Questionnaires'} path={'/dashboard/questionnaires'} icon={} + onCloseNav={onCloseNav} /> } disabled={!smartClient} + onCloseNav={onCloseNav} /> diff --git a/apps/smart-forms-app/src/features/dashboard/components/DashboardPages/DashboardHeading.tsx b/apps/smart-forms-app/src/features/dashboard/components/DashboardPages/DashboardHeading.tsx deleted file mode 100644 index 4e50cbbf9..000000000 --- a/apps/smart-forms-app/src/features/dashboard/components/DashboardPages/DashboardHeading.tsx +++ /dev/null @@ -1,40 +0,0 @@ -/* - * Copyright 2023 Commonwealth Scientific and Industrial Research - * Organisation (CSIRO) ABN 41 687 119 230. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import { Box, Stack, Typography } from '@mui/material'; -import QuestionnaireSourceToggle from '../../../../components/Toggles/QuestionnaireSourceToggle.tsx'; -import useConfigStore from '../../../../stores/useConfigStore.ts'; - -interface DashboardHeadingProps { - headingText: string; - setPage: (page: number) => void; -} -function DashboardHeading(props: DashboardHeadingProps) { - const { headingText, setPage } = props; - - const debugMode = useConfigStore((state) => state.debugMode); - - return ( - - {headingText} - - {debugMode ? : null} - - ); -} - -export default DashboardHeading; diff --git a/apps/smart-forms-app/src/features/dashboard/components/DashboardPages/DashboardTableHead.tsx b/apps/smart-forms-app/src/features/dashboard/components/DashboardPages/DashboardTableHead.tsx index 38413c1c1..7d1951e5b 100644 --- a/apps/smart-forms-app/src/features/dashboard/components/DashboardPages/DashboardTableHead.tsx +++ b/apps/smart-forms-app/src/features/dashboard/components/DashboardPages/DashboardTableHead.tsx @@ -15,36 +15,35 @@ * limitations under the License. */ -import type { TableAttributes } from '../../../renderer/types/table.interface.ts'; import { TableCell, TableHead, TableRow, TableSortLabel } from '@mui/material'; +import type { Header, SortDirection } from '@tanstack/react-table'; interface DashboardTableHeadProps { - order: 'asc' | 'desc'; - orderBy: string; - headLabel: TableAttributes[]; - onSort: (event: MouseEvent, property: keyof T) => void; + headers: Header[]; } function DashboardTableHead(props: DashboardTableHeadProps) { - const { order, orderBy, headLabel, onSort } = props; + const { headers } = props; return ( - {headLabel.map((headCell) => ( - - onSort(event as unknown as MouseEvent, headCell.id as keyof T)}> - {headCell.label} - - - ))} + {headers.map((header) => { + const label = (header.column.columnDef.header ?? '') as string; + const sortValue = header.column.getIsSorted() as SortDirection | false; + + return ( + + + {label} + + + ); + })} ); diff --git a/apps/smart-forms-app/src/features/dashboard/components/DashboardPages/DashboardTablePagination.tsx b/apps/smart-forms-app/src/features/dashboard/components/DashboardPages/DashboardTablePagination.tsx index f12342771..641cd7cb2 100644 --- a/apps/smart-forms-app/src/features/dashboard/components/DashboardPages/DashboardTablePagination.tsx +++ b/apps/smart-forms-app/src/features/dashboard/components/DashboardPages/DashboardTablePagination.tsx @@ -15,37 +15,34 @@ * limitations under the License. */ -import { Box, Fade, TablePagination, Typography } from '@mui/material'; +import { Box, TablePagination } from '@mui/material'; +import type { Table } from '@tanstack/react-table'; +import type { ReactNode } from 'react'; -interface TablePaginationProps { - isFetching: boolean; - numOfItems: number; - page: number; - rowsPerPage: number; - setPage: (page: number) => void; - setRowsPerPage: (rowsPerPage: number) => void; +interface DashboardTablePaginationProps { + table: Table; + children: ReactNode; } -function DashboardTablePagination(props: TablePaginationProps) { - const { isFetching, numOfItems, page, rowsPerPage, setPage, setRowsPerPage } = props; +function DashboardTablePagination(props: DashboardTablePaginationProps) { + const { table, children } = props; + + const { pageSize, pageIndex } = table.getState().pagination; + const totalNumberOfItems = table.getRowModel().rows.length; return ( - - - Updating... - - + {children} setPage(newPage)} + count={table.getFilteredRowModel().rows.length} + rowsPerPage={pageSize} + page={pageIndex} + onPageChange={(_, page) => table.setPageIndex(page)} onRowsPerPageChange={(event) => { - setRowsPerPage(parseInt(event.target.value)); - setPage(0); + const size = event.target.value ? Number(event.target.value) : 10; + table.setPageSize(size); }} /> diff --git a/apps/smart-forms-app/src/features/dashboard/components/DashboardPages/PageHeading.tsx b/apps/smart-forms-app/src/features/dashboard/components/DashboardPages/PageHeading.tsx new file mode 100644 index 000000000..7896c3f4c --- /dev/null +++ b/apps/smart-forms-app/src/features/dashboard/components/DashboardPages/PageHeading.tsx @@ -0,0 +1,35 @@ +/* + * Copyright 2023 Commonwealth Scientific and Industrial Research + * Organisation (CSIRO) ABN 41 687 119 230. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { Box, Typography } from '@mui/material'; +import type { ReactNode } from 'react'; + +interface PageHeadingProps { + children: ReactNode; +} + +function PageHeading(props: PageHeadingProps) { + const { children } = props; + + return ( + + {children} + + ); +} + +export default PageHeading; diff --git a/apps/smart-forms-app/src/features/dashboard/components/DashboardPages/QuestionnairePage/Buttons/CreateNewResponseButton.tsx b/apps/smart-forms-app/src/features/dashboard/components/DashboardPages/QuestionnairePage/Buttons/CreateNewResponseButton.tsx index b175519c9..44fd5dedd 100644 --- a/apps/smart-forms-app/src/features/dashboard/components/DashboardPages/QuestionnairePage/Buttons/CreateNewResponseButton.tsx +++ b/apps/smart-forms-app/src/features/dashboard/components/DashboardPages/QuestionnairePage/Buttons/CreateNewResponseButton.tsx @@ -15,28 +15,24 @@ * limitations under the License. */ -import { useState } from 'react'; -import Iconify from '../../../../../../components/Iconify/Iconify.tsx'; +import { useContext, useState } from 'react'; import { createQuestionnaireResponse } from '../../../../../renderer/utils/qrItem.ts'; import { useNavigate } from 'react-router-dom'; import { postQuestionnaireToSMARTHealthIT } from '../../../../../save/api/saveQr.ts'; -import type { SelectedQuestionnaire } from '../../../../types/list.interface.ts'; import useQuestionnaireStore from '../../../../../../stores/useQuestionnaireStore.ts'; import useQuestionnaireResponseStore from '../../../../../../stores/useQuestionnaireResponseStore.ts'; import useConfigStore from '../../../../../../stores/useConfigStore.ts'; -import { LoadingButton } from '@mui/lab'; - -interface Props { - selectedQuestionnaire: SelectedQuestionnaire | null; -} - -function CreateNewResponseButton(props: Props) { - const { selectedQuestionnaire } = props; +import { CircularProgress, IconButton, Stack, Typography } from '@mui/material'; +import { SelectedQuestionnaireContext } from '../../../../contexts/SelectedQuestionnaireContext.tsx'; +import EditNoteIcon from '@mui/icons-material/EditNote'; +function CreateNewResponseButton() { const smartClient = useConfigStore((state) => state.smartClient); const buildSourceQuestionnaire = useQuestionnaireStore((state) => state.buildSourceQuestionnaire); const buildSourceResponse = useQuestionnaireResponseStore((state) => state.buildSourceResponse); + const { selectedQuestionnaire } = useContext(SelectedQuestionnaireContext); + const navigate = useNavigate(); const [isLoading, setIsLoading] = useState(false); @@ -65,24 +61,30 @@ function CreateNewResponseButton(props: Props) { setIsLoading(false); } + const buttonIsDisabled = !selectedQuestionnaire?.listItem || isLoading; + return ( - } - sx={{ - px: 2.5, - backgroundColor: 'secondary.main', - '&:hover': { - backgroundColor: 'secondary.dark' - } - }} - data-test="button-create-response" - onClick={handleClick}> - Create response - + + + {isLoading ? ( + + ) : ( + + )} + + + + Create response + + ); } diff --git a/apps/smart-forms-app/src/features/dashboard/components/DashboardPages/QuestionnairePage/Buttons/GoToPlaygroundButton.tsx b/apps/smart-forms-app/src/features/dashboard/components/DashboardPages/QuestionnairePage/Buttons/GoToPlaygroundButton.tsx index 39097ee73..99a3bdf07 100644 --- a/apps/smart-forms-app/src/features/dashboard/components/DashboardPages/QuestionnairePage/Buttons/GoToPlaygroundButton.tsx +++ b/apps/smart-forms-app/src/features/dashboard/components/DashboardPages/QuestionnairePage/Buttons/GoToPlaygroundButton.tsx @@ -16,7 +16,6 @@ */ import { Button } from '@mui/material'; -import Iconify from '../../../../../../components/Iconify/Iconify.tsx'; import { useNavigate } from 'react-router-dom'; function GoToPlaygroundButton() { @@ -26,7 +25,6 @@ function GoToPlaygroundButton() { + + { + navigate('/dashboard/questionnaires'); + }} + sx={{ ml: -1, mr: 0.75 }}> + + + ) : null; } diff --git a/apps/smart-forms-app/src/features/dashboard/components/DashboardPages/ResponsesPage/Buttons/OpenResponseButton.tsx b/apps/smart-forms-app/src/features/dashboard/components/DashboardPages/ResponsesPage/Buttons/OpenResponseButton.tsx index 4e4eafe5d..2216c2925 100644 --- a/apps/smart-forms-app/src/features/dashboard/components/DashboardPages/ResponsesPage/Buttons/OpenResponseButton.tsx +++ b/apps/smart-forms-app/src/features/dashboard/components/DashboardPages/ResponsesPage/Buttons/OpenResponseButton.tsx @@ -16,7 +16,6 @@ */ import { useMemo, useState } from 'react'; -import Iconify from '../../../../../../components/Iconify/Iconify.tsx'; import { useQuery } from '@tanstack/react-query'; import type { Bundle, Questionnaire } from 'fhir/r4'; import { @@ -31,7 +30,8 @@ import { assembleIfRequired } from '../../../../../assemble/utils/assemble.ts'; import useConfigStore from '../../../../../../stores/useConfigStore.ts'; import useQuestionnaireStore from '../../../../../../stores/useQuestionnaireStore.ts'; import useQuestionnaireResponseStore from '../../../../../../stores/useQuestionnaireResponseStore.ts'; -import { LoadingButton } from '@mui/lab'; +import { CircularProgress, IconButton, Stack, Typography } from '@mui/material'; +import OpenInNewIcon from '@mui/icons-material/OpenInNew'; interface Props { selectedResponse: SelectedResponse | null; @@ -125,24 +125,30 @@ function OpenResponseButton(props: Props) { setIsLoading(false); } + const buttonIsDisabled = !selectedResponse || !referencedQuestionnaire || isLoading; + return ( - } - sx={{ - px: 2.25, - backgroundColor: 'secondary.main', - '&:hover': { - backgroundColor: 'secondary.dark' - } - }} - data-test="button-open-response" - onClick={handleClick}> - Open Response - + + + {isLoading ? ( + + ) : ( + + )} + + + + Open Response + + ); } diff --git a/apps/smart-forms-app/src/features/dashboard/components/DashboardPages/ResponsesPage/ResponsesPage.tsx b/apps/smart-forms-app/src/features/dashboard/components/DashboardPages/ResponsesPage/ResponsesPage.tsx index ab6f5a699..49bd4a220 100644 --- a/apps/smart-forms-app/src/features/dashboard/components/DashboardPages/ResponsesPage/ResponsesPage.tsx +++ b/apps/smart-forms-app/src/features/dashboard/components/DashboardPages/ResponsesPage/ResponsesPage.tsx @@ -15,101 +15,12 @@ * limitations under the License. */ -import { useMemo, useRef, useState } from 'react'; -import { Box, Card, Container, Fade, Stack, Table, TableBody, TableContainer } from '@mui/material'; -import { applySortFilter, getComparator } from '../../../utils/dashboard.ts'; -import type { QuestionnaireResponse } from 'fhir/r4'; -import ResponseListToolbar from './TableComponents/ResponseListToolbar.tsx'; -import ResponseListFeedback from './TableComponents/ResponseListFeedback.tsx'; -import BackToQuestionnairesButton from './Buttons/BackToQuestionnairesButton.tsx'; -import OpenResponseButton from './Buttons/OpenResponseButton.tsx'; -import useDebounce from '../../../../renderer/hooks/useDebounce.ts'; +import { Card, Container, Fade } from '@mui/material'; import { Helmet } from 'react-helmet'; -import type { TableAttributes } from '../../../../renderer/types/table.interface.ts'; -import type { ResponseListItem, SelectedResponse } from '../../../types/list.interface.ts'; -import useConfigStore from '../../../../../stores/useConfigStore.ts'; -import DashboardHeading from '../DashboardHeading.tsx'; -import ResponseTableRow from './TableComponents/ResponseTableRow.tsx'; -import DashboardTablePagination from '../DashboardTablePagination.tsx'; -import useFetchResponses from '../../../hooks/useFetchResponses.ts'; -import DashboardTableHead from '../DashboardTableHead.tsx'; - -const tableHeaders: TableAttributes[] = [ - { id: 'title', label: 'Questionnaire Title', alignRight: false }, - { id: 'author', label: 'Author', alignRight: false }, - { id: 'authored', label: 'Authored On', alignRight: false }, - { id: 'status', label: 'Status', alignRight: false } -]; +import PageHeading from '../PageHeading.tsx'; +import ResponsesTable from './ResponsesTable.tsx'; function ResponsesPage() { - const questionnaireSource = useConfigStore((state) => state.questionnaireSource); - - // Scroll to buttons row when response is selected - for screens with small height - const buttonsRef = useRef(null); - function executeScroll() { - if (buttonsRef.current) { - buttonsRef.current.scrollIntoView({ behavior: 'smooth' }); - } - } - - const [page, setPage] = useState(0); - const [rowsPerPage, setRowsPerPage] = useState(10); - - const [order, setOrder] = useState<'asc' | 'desc'>('desc'); - const [selectedResponse, setSelectedResponse] = useState(null); - const [orderBy, setOrderBy] = useState('authored'); - const [searchInput, setSearchInput] = useState(''); - - const debouncedInput = useDebounce(searchInput, 300); - - const { responses, responseListItems, fetchStatus, fetchError, isFetching } = useFetchResponses( - searchInput, - debouncedInput, - questionnaireSource - ); - - // sort or perform client-side filtering or items - const filteredListItems: ResponseListItem[] = useMemo( - () => - applySortFilter( - responseListItems, - getComparator(order, orderBy, 'response'), - questionnaireSource - ) as ResponseListItem[], - [order, orderBy, responseListItems, questionnaireSource] - ); - - const isEmpty = filteredListItems.length === 0 && fetchStatus !== 'loading'; - - // Event handlers - const handleSort = (_: MouseEvent, property: keyof ResponseListItem) => { - const isAsc = orderBy === property && order === 'asc'; - setOrder(isAsc ? 'desc' : 'asc'); - setOrderBy(property); - }; - - const handleRowClick = (id: string) => { - const selectedItem = filteredListItems.find((item) => item.id === id); - - if (selectedItem) { - executeScroll(); - if (selectedItem.id === selectedResponse?.listItem.id) { - setSelectedResponse(null); - } else { - const resource = responses?.entry?.find((entry) => entry.resource?.id === id)?.resource; - - if (resource) { - setSelectedResponse({ - listItem: selectedItem, - resource: resource as QuestionnaireResponse - }); - } else { - setSelectedResponse(null); - } - } - } - }; - return ( <> @@ -117,73 +28,11 @@ function ResponsesPage() { - + Responses - setSelectedResponse(null)} - onSearch={(input) => { - setPage(0); - setSearchInput(input); - }} - /> - - - - - - {filteredListItems - .slice(page * rowsPerPage, page * rowsPerPage + rowsPerPage) - .map((row) => { - const { id } = row; - const isSelected = selectedResponse?.listItem.id === id; - - return ( - handleRowClick(id)} - /> - ); - })} - - - {(isEmpty || fetchStatus === 'error' || fetchStatus === 'loading') && - filteredListItems.length === 0 ? ( - - ) : null} -
-
- - +
- - - - - -
diff --git a/apps/smart-forms-app/src/features/dashboard/components/DashboardPages/ResponsesPage/ResponsesTable.tsx b/apps/smart-forms-app/src/features/dashboard/components/DashboardPages/ResponsesPage/ResponsesTable.tsx new file mode 100644 index 000000000..3d7d3c218 --- /dev/null +++ b/apps/smart-forms-app/src/features/dashboard/components/DashboardPages/ResponsesPage/ResponsesTable.tsx @@ -0,0 +1,105 @@ +/* + * Copyright 2023 Commonwealth Scientific and Industrial Research + * Organisation (CSIRO) ABN 41 687 119 230. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { useMemo, useState } from 'react'; +import type { SelectedResponse } from '../../../types/list.interface.ts'; +import useDebounce from '../../../../renderer/hooks/useDebounce.ts'; +import useFetchResponses from '../../../hooks/useFetchResponses.ts'; +import { createResponseTableColumns } from '../../../utils/tableColumns.ts'; +import type { SortingState } from '@tanstack/react-table'; +import { + getCoreRowModel, + getPaginationRowModel, + getSortedRowModel, + useReactTable +} from '@tanstack/react-table'; +import type { QuestionnaireResponse } from 'fhir/r4'; +import ResponsesTableView from './ResponsesTableView.tsx'; + +function ResponsesTable() { + const [selectedResponse, setSelectedResponse] = useState(null); + const [searchInput, setSearchInput] = useState(''); + + const debouncedInput = useDebounce(searchInput, 300); + + const { responses, responseListItems, fetchStatus, fetchError, isFetching } = useFetchResponses( + searchInput, + debouncedInput + ); + + const columns = useMemo(() => createResponseTableColumns(), []); + + const [sorting, setSorting] = useState([ + { + id: 'authored', + desc: true + } + ]); + + const table = useReactTable({ + data: responseListItems, + columns: columns, + onSortingChange: setSorting, + getCoreRowModel: getCoreRowModel(), + getSortedRowModel: getSortedRowModel(), + getPaginationRowModel: getPaginationRowModel(), + state: { + sorting + } + }); + + function handleRowClick(id: string) { + const selectedItem = responseListItems.find((item) => item.id === id); + + if (selectedItem) { + if (selectedItem.id === selectedResponse?.listItem.id) { + setSelectedResponse(null); + } else { + const resource = responses?.entry?.find((entry) => entry.resource?.id === id)?.resource; + + if (resource) { + setSelectedResponse({ + listItem: selectedItem, + resource: resource as QuestionnaireResponse + }); + } else { + setSelectedResponse(null); + } + } + } + } + + return ( + { + table.setPageIndex(0); + setSearchInput(input); + }} + onRowClick={handleRowClick} + onSelectResponse={setSelectedResponse} + /> + ); +} + +export default ResponsesTable; diff --git a/apps/smart-forms-app/src/features/dashboard/components/DashboardPages/ResponsesPage/ResponsesTableView.tsx b/apps/smart-forms-app/src/features/dashboard/components/DashboardPages/ResponsesPage/ResponsesTableView.tsx new file mode 100644 index 000000000..fd736be6c --- /dev/null +++ b/apps/smart-forms-app/src/features/dashboard/components/DashboardPages/ResponsesPage/ResponsesTableView.tsx @@ -0,0 +1,111 @@ +/* + * Copyright 2023 Commonwealth Scientific and Industrial Research + * Organisation (CSIRO) ABN 41 687 119 230. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import ResponseListToolbar from './TableComponents/ResponseListToolbar.tsx'; +import { Fade, Table as MuiTable, TableBody, TableContainer, Typography } from '@mui/material'; +import DashboardTableHead from '../DashboardTableHead.tsx'; +import ResponseTableRow from './TableComponents/ResponseTableRow.tsx'; +import ResponseListFeedback from './TableComponents/ResponseListFeedback.tsx'; +import DashboardTablePagination from '../DashboardTablePagination.tsx'; +import type { ResponseListItem, SelectedResponse } from '../../../types/list.interface.ts'; +import type { Table } from '@tanstack/react-table'; + +interface ResponsesTableViewProps { + table: Table; + searchInput: string; + debouncedInput: string; + fetchStatus: 'error' | 'success' | 'loading'; + isFetching: boolean; + fetchError: unknown; + selectedResponse: SelectedResponse | null; + onSearch: (input: string) => void; + onRowClick: (id: string) => void; + onSelectResponse: (selected: SelectedResponse | null) => void; +} + +function ResponsesTableView(props: ResponsesTableViewProps) { + const { + table, + searchInput, + debouncedInput, + fetchStatus, + isFetching, + fetchError, + selectedResponse, + onSearch, + onRowClick, + onSelectResponse + } = props; + + const headers = table.getHeaderGroups()[0].headers; + + const isEmpty = + table.getRowModel().rows.length === 0 && !!debouncedInput && fetchStatus !== 'loading'; + + return ( + <> + onSelectResponse(null)} + onSearch={onSearch} + /> + + + + + + {table.getRowModel().rows.map((row) => { + const rowData = row.original; + const isSelected = selectedResponse?.listItem.id === rowData.id; + + return ( + onRowClick(rowData.id)} + /> + ); + })} + + + {(isEmpty || fetchStatus === 'error' || fetchStatus === 'loading') && + table.getRowModel().rows.length === 0 ? ( + + ) : null} + + + + + + + Updating... + + + + + ); +} + +export default ResponsesTableView; diff --git a/apps/smart-forms-app/src/features/dashboard/components/DashboardPages/ResponsesPage/TableComponents/ResponseListFeedback.tsx b/apps/smart-forms-app/src/features/dashboard/components/DashboardPages/ResponsesPage/TableComponents/ResponseListFeedback.tsx index 5f3632321..cf8644142 100644 --- a/apps/smart-forms-app/src/features/dashboard/components/DashboardPages/ResponsesPage/TableComponents/ResponseListFeedback.tsx +++ b/apps/smart-forms-app/src/features/dashboard/components/DashboardPages/ResponsesPage/TableComponents/ResponseListFeedback.tsx @@ -16,7 +16,6 @@ */ import { Stack, TableBody, TableCell, TableRow } from '@mui/material'; -import useConfigStore from '../../../../../../stores/useConfigStore.ts'; import DashboardFeedbackMessage from '../../DashboardFeedbackMessage.tsx'; import { useSnackbar } from 'notistack'; @@ -30,14 +29,12 @@ interface Props { function ResponseListFeedback(props: Props) { const { isEmpty, status, searchInput, error } = props; - const questionnaireSource = useConfigStore((state) => state.questionnaireSource); - const { enqueueSnackbar } = useSnackbar(); let feedbackType: 'error' | 'empty' | 'loading' | null = null; if (status === 'error') { feedbackType = 'error'; - } else if (status === 'loading' && questionnaireSource === 'remote') { + } else if (status === 'loading') { feedbackType = 'loading'; } else if (isEmpty) { feedbackType = 'empty'; diff --git a/apps/smart-forms-app/src/features/dashboard/components/DashboardPages/ResponsesPage/TableComponents/ResponseListToolbar.tsx b/apps/smart-forms-app/src/features/dashboard/components/DashboardPages/ResponsesPage/TableComponents/ResponseListToolbar.tsx index 26b352e44..de5d3ba58 100644 --- a/apps/smart-forms-app/src/features/dashboard/components/DashboardPages/ResponsesPage/TableComponents/ResponseListToolbar.tsx +++ b/apps/smart-forms-app/src/features/dashboard/components/DashboardPages/ResponsesPage/TableComponents/ResponseListToolbar.tsx @@ -15,18 +15,10 @@ * limitations under the License. */ -import { - Box, - IconButton, - InputAdornment, - LinearProgress, - Tooltip, - Typography, - useTheme -} from '@mui/material'; +import { Box, InputAdornment, LinearProgress, Typography } from '@mui/material'; import Iconify from '../../../../../../components/Iconify/Iconify.tsx'; import { - getToolBarColors, + getResponseToolBarColors, StyledRoot, StyledSearch } from '../../QuestionnairePage/TableComponents/QuestionnaireListToolbar.styles.ts'; @@ -34,31 +26,36 @@ import dayjs from 'dayjs'; import type { ChangeEvent } from 'react'; import { useContext } from 'react'; import { SelectedQuestionnaireContext } from '../../../../contexts/SelectedQuestionnaireContext.tsx'; -import { constructName } from '../../../../../smartAppLaunch/utils/launchContext.ts'; -import type { ResponseListItem } from '../../../../types/list.interface.ts'; -import useConfigStore from '../../../../../../stores/useConfigStore.ts'; +import type { SelectedResponse } from '../../../../types/list.interface.ts'; +import BackToQuestionnairesButton from '../Buttons/BackToQuestionnairesButton.tsx'; +import ResponseListToolbarButtons from './ResponseListToolbarButtons.tsx'; +import useResponsive from '../../../../../../hooks/useResponsive.ts'; -interface Props { - selected: ResponseListItem | undefined; +interface ResponseListToolbarProps { + selectedResponse: SelectedResponse | null; searchInput: string; isFetching: boolean; - clearSelection: () => void; + onClearSelection: () => void; onSearch: (searchInput: string) => void; } -function ResponseListToolbar(props: Props) { - const { selected, searchInput, isFetching, clearSelection, onSearch } = props; +function ResponseListToolbar(props: ResponseListToolbarProps) { + const { selectedResponse, searchInput, isFetching, onClearSelection, onSearch } = props; - const { selectedQuestionnaire, existingResponses, clearSelectedQuestionnaire } = useContext( - SelectedQuestionnaireContext - ); - const patient = useConfigStore((state) => state.patient); - const theme = useTheme(); + const { selectedQuestionnaire, existingResponses } = useContext(SelectedQuestionnaireContext); + + const isTabletAndUp = useResponsive('up', 'md'); const selectedQuestionnaireTitle = selectedQuestionnaire?.listItem.title ?? 'selected questionnaire'; - const toolBarColors = getToolBarColors(selected, selectedQuestionnaire, existingResponses); + const selected = selectedResponse?.listItem; + + const toolBarColors = getResponseToolBarColors( + selected, + selectedQuestionnaire, + existingResponses + ); return ( <> @@ -68,9 +65,12 @@ function ResponseListToolbar(props: Props) { {selected.title} — {dayjs(selected.authored).format('LL')} selected ) : selectedQuestionnaire && existingResponses.length > 0 ? ( - - Displaying responses from the {selectedQuestionnaireTitle} questionnaire - + + + + Displaying responses from the {selectedQuestionnaireTitle} questionnaire + + ) : ( )} - {selected ? ( - - - - - - ) : selectedQuestionnaire && existingResponses.length > 0 ? ( - - clearSelectedQuestionnaire()} - data-test="button-remove-questionnaire-filter"> - - - - ) : ( - - Showing responses for {constructName(patient?.name)} - - )} + {isFetching ? : } diff --git a/apps/smart-forms-app/src/features/dashboard/components/DashboardPages/ResponsesPage/TableComponents/ResponseListToolbarButtons.tsx b/apps/smart-forms-app/src/features/dashboard/components/DashboardPages/ResponsesPage/TableComponents/ResponseListToolbarButtons.tsx new file mode 100644 index 000000000..3f8226c74 --- /dev/null +++ b/apps/smart-forms-app/src/features/dashboard/components/DashboardPages/ResponsesPage/TableComponents/ResponseListToolbarButtons.tsx @@ -0,0 +1,93 @@ +/* + * Copyright 2023 Commonwealth Scientific and Industrial Research + * Organisation (CSIRO) ABN 41 687 119 230. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { useContext } from 'react'; +import { Box, IconButton, Stack, Typography } from '@mui/material'; +import ClearIcon from '@mui/icons-material/Clear'; +import Iconify from '../../../../../../components/Iconify/Iconify.tsx'; +import { constructName } from '../../../../../smartAppLaunch/utils/launchContext.ts'; +import { SelectedQuestionnaireContext } from '../../../../contexts/SelectedQuestionnaireContext.tsx'; +import type { SelectedResponse } from '../../../../types/list.interface.ts'; +import useConfigStore from '../../../../../../stores/useConfigStore.ts'; +import OpenResponseButton from '../Buttons/OpenResponseButton.tsx'; +import useResponsive from '../../../../../../hooks/useResponsive.ts'; + +interface ResponseListToolbarButtonsProps { + selectedResponse: SelectedResponse | null; + onClearSelection: () => void; +} + +function ResponseListToolbarButtons(props: ResponseListToolbarButtonsProps) { + const { selectedResponse, onClearSelection } = props; + + const { selectedQuestionnaire, existingResponses, clearSelectedQuestionnaire } = useContext( + SelectedQuestionnaireContext + ); + + const patient = useConfigStore((state) => state.patient); + + const isDesktop = useResponsive('up', 'lg'); + + const selected = selectedResponse?.listItem; + + if (selected) { + return ( + + + + + + + + Unselect + + + + ); + } + + if (selectedQuestionnaire && existingResponses.length > 0) { + return ( + + clearSelectedQuestionnaire()} + data-test="button-remove-questionnaire-filter"> + + + + Remove filter + + + ); + } + + return ( + + Showing responses for {constructName(patient?.name)} + + ); +} + +export default ResponseListToolbarButtons; diff --git a/apps/smart-forms-app/src/features/dashboard/components/DashboardPages/ResponsesPage/TableComponents/ResponseTableRow.tsx b/apps/smart-forms-app/src/features/dashboard/components/DashboardPages/ResponsesPage/TableComponents/ResponseTableRow.tsx index d6b8534c0..0e44f797f 100644 --- a/apps/smart-forms-app/src/features/dashboard/components/DashboardPages/ResponsesPage/TableComponents/ResponseTableRow.tsx +++ b/apps/smart-forms-app/src/features/dashboard/components/DashboardPages/ResponsesPage/TableComponents/ResponseTableRow.tsx @@ -41,7 +41,7 @@ function ResponseTableRow(props: ResponseTableRowProps) { tabIndex={-1} selected={isSelected} sx={{ cursor: 'pointer' }} - data-test="questionnaire-list-row" + data-test="response-list-row" onClick={onRowClick}> diff --git a/apps/smart-forms-app/src/features/dashboard/hooks/useFetchQuestionnaires.ts b/apps/smart-forms-app/src/features/dashboard/hooks/useFetchQuestionnaires.ts index 92ae251ff..762cb55da 100644 --- a/apps/smart-forms-app/src/features/dashboard/hooks/useFetchQuestionnaires.ts +++ b/apps/smart-forms-app/src/features/dashboard/hooks/useFetchQuestionnaires.ts @@ -17,19 +17,12 @@ import { useQuery } from '@tanstack/react-query'; import type { Bundle } from 'fhir/r4'; -import { - constructBundle, - getFormsServerBundlePromise, - getQuestionnaireListItems -} from '../utils/dashboard.ts'; +import { getFormsServerBundlePromise, getQuestionnaireListItems } from '../utils/dashboard.ts'; import { useMemo } from 'react'; -import { loadQuestionnairesFromLocal } from '../../../api/local.ts'; import type { QuestionnaireListItem } from '../types/list.interface.ts'; -import type { Source } from '../../../types/source.interface.ts'; interface useFetchQuestionnairesReturnParams { remoteQuestionnaires: Bundle | undefined; - localQuestionnaires: Bundle; questionnaireListItems: QuestionnaireListItem[]; fetchStatus: 'error' | 'success' | 'loading'; fetchError: unknown; @@ -39,8 +32,7 @@ interface useFetchQuestionnairesReturnParams { function useFetchQuestionnaires( searchInput: string, - debouncedInput: string, - questionnaireSource: Source + debouncedInput: string ): useFetchQuestionnairesReturnParams { const numOfSearchEntries = 100; @@ -59,24 +51,14 @@ function useFetchQuestionnaires( enabled: debouncedInput === searchInput }); - // load local questionnaires to be used if source is local - const localQuestionnaires: Bundle = useMemo( - () => constructBundle(loadQuestionnairesFromLocal()), - [] - ); - // construct questionnaire list items for data display const questionnaireListItems: QuestionnaireListItem[] = useMemo( - () => - getQuestionnaireListItems( - questionnaireSource === 'remote' ? remoteQuestionnaires : localQuestionnaires - ), - [remoteQuestionnaires, localQuestionnaires, questionnaireSource] + () => getQuestionnaireListItems(remoteQuestionnaires), + [remoteQuestionnaires] ); return { remoteQuestionnaires, - localQuestionnaires, questionnaireListItems, fetchStatus: status, fetchError: error, diff --git a/apps/smart-forms-app/src/features/dashboard/hooks/useFetchResponses.ts b/apps/smart-forms-app/src/features/dashboard/hooks/useFetchResponses.ts index 7e5c4830f..6edc009b8 100644 --- a/apps/smart-forms-app/src/features/dashboard/hooks/useFetchResponses.ts +++ b/apps/smart-forms-app/src/features/dashboard/hooks/useFetchResponses.ts @@ -25,7 +25,6 @@ import { import { useContext, useMemo } from 'react'; import type { ResponseListItem } from '../types/list.interface.ts'; import useConfigStore from '../../../stores/useConfigStore.ts'; -import type { Source } from '../../../types/source.interface.ts'; import { SelectedQuestionnaireContext } from '../contexts/SelectedQuestionnaireContext.tsx'; interface useFetchResponsesReturnParams { @@ -38,8 +37,7 @@ interface useFetchResponsesReturnParams { function useFetchResponses( searchInput: string, - debouncedInput: string, - questionnaireSource: Source + debouncedInput: string ): useFetchResponsesReturnParams { const smartClient = useConfigStore((state) => state.smartClient); const patient = useConfigStore((state) => state.patient); @@ -63,7 +61,7 @@ function useFetchResponses( // eslint-disable-next-line @typescript-eslint/no-non-null-assertion () => getClientBundlePromise(smartClient!, queryUrl), { - enabled: questionnaireSource === 'remote' && !!smartClient && debouncedInput === searchInput + enabled: !!smartClient && debouncedInput === searchInput } ); diff --git a/apps/smart-forms-app/src/features/dashboard/layout/DashboardLayout.tsx b/apps/smart-forms-app/src/features/dashboard/layout/DashboardLayout.tsx index e2ccce6ce..1a4c3e429 100644 --- a/apps/smart-forms-app/src/features/dashboard/layout/DashboardLayout.tsx +++ b/apps/smart-forms-app/src/features/dashboard/layout/DashboardLayout.tsx @@ -16,7 +16,7 @@ */ import { useEffect, useState } from 'react'; -import DashboardHeader from '../components/DashboardHeader/DashboardHeader.tsx'; +import GenericHeader from '../../../components/Header/GenericHeader.tsx'; import DashboardNav from '../components/DashboardNav/DashboardNav.tsx'; import { Main, StyledRoot } from '../../../components/Layout/Layout.styles.ts'; import { Outlet, useNavigate } from 'react-router-dom'; @@ -59,7 +59,7 @@ function DashboardLayout() { return ( - setOpen(true)} /> + setOpen(true)} /> setOpen(false)} />
diff --git a/apps/smart-forms-app/src/features/dashboard/types/list.interface.ts b/apps/smart-forms-app/src/features/dashboard/types/list.interface.ts index 4510466cb..6323f33bd 100644 --- a/apps/smart-forms-app/src/features/dashboard/types/list.interface.ts +++ b/apps/smart-forms-app/src/features/dashboard/types/list.interface.ts @@ -5,7 +5,7 @@ export interface QuestionnaireListItem { title: string; avatarColor: string; publisher: string; - date: string; + date: Date | null; status: Questionnaire['status']; } @@ -14,13 +14,10 @@ export interface ResponseListItem { title: string; avatarColor: string; author: string; - authored: string; + authored: Date | null; status: QuestionnaireResponse['status']; } -export type ListItem = QuestionnaireListItem | ResponseListItem; -export type ListItemWithIndex = [number, QuestionnaireListItem | ResponseListItem]; - export interface SelectedQuestionnaire { listItem: QuestionnaireListItem; resource: Questionnaire; diff --git a/apps/smart-forms-app/src/features/dashboard/utils/dashboard.ts b/apps/smart-forms-app/src/features/dashboard/utils/dashboard.ts index 79abed5fd..17b9e1887 100644 --- a/apps/smart-forms-app/src/features/dashboard/utils/dashboard.ts +++ b/apps/smart-forms-app/src/features/dashboard/utils/dashboard.ts @@ -15,7 +15,6 @@ * limitations under the License. */ -import filter from 'lodash.filter'; import * as FHIR from 'fhirclient'; import type { Bundle, @@ -28,82 +27,11 @@ import randomColor from 'randomcolor'; import dayjs from 'dayjs'; import { getQuestionnaireNameFromResponse } from '../../renderer/utils/itemControl.ts'; import type Client from 'fhirclient/lib/Client'; -import type { - ListItem, - ListItemWithIndex, - QuestionnaireListItem, - ResponseListItem -} from '../types/list.interface.ts'; +import type { QuestionnaireListItem, ResponseListItem } from '../types/list.interface.ts'; import { HEADERS } from '../../../api/headers.ts'; const endpointUrl = import.meta.env.VITE_FORMS_SERVER_URL ?? 'https://api.smartforms.io/fhir'; -export function descendingComparator( - a: ListItem, - b: ListItem, - orderBy: keyof QuestionnaireListItem | keyof ResponseListItem, - listType: 'questionnaire' | 'response' -): number { - if (listType === 'questionnaire') { - orderBy = orderBy as keyof QuestionnaireListItem; - a = a as QuestionnaireListItem; - b = b as QuestionnaireListItem; - if (b[orderBy] < a[orderBy]) { - return -1; - } - if (b[orderBy] > a[orderBy]) { - return 1; - } - } else { - orderBy = orderBy as keyof ResponseListItem; - a = a as ResponseListItem; - b = b as ResponseListItem; - if (b[orderBy] < a[orderBy]) { - return -1; - } - if (b[orderBy] > a[orderBy]) { - return 1; - } - } - - return 0; -} - -export function getComparator( - order: 'asc' | 'desc', - orderBy: keyof QuestionnaireListItem | keyof ResponseListItem, - listType: 'questionnaire' | 'response' -): (a: ListItem, b: ListItem) => number { - return order === 'desc' - ? (a: ListItem, b: ListItem) => descendingComparator(a, b, orderBy, listType) - : (a: ListItem, b: ListItem) => -descendingComparator(a, b, orderBy, listType); -} - -export function applySortFilter( - array: ListItem[], - comparator: (a: ListItem, b: ListItem) => number, - source: 'local' | 'remote', - query?: string -): ListItem[] { - const stabilizedThis: ListItemWithIndex[] = array.map((el, index) => [index, el]); - - stabilizedThis.sort((a: ListItemWithIndex, b: ListItemWithIndex) => { - const order = comparator(a[1], b[1]); - if (order !== 0) { - return order; - } - - return a[0] - b[0]; - }); - - // Perform client-side filtering only when source is local - if (query && source === 'local') { - return filter(array, (_item) => _item.title.toLowerCase().indexOf(query.toLowerCase()) !== -1); - } - - return stabilizedThis.map((el) => el[1]); -} - export function getFormsServerBundlePromise(queryUrl: string): Promise { queryUrl = queryUrl.replace('|', '&version='); @@ -163,14 +91,20 @@ export function getQuestionnaireListItems(bundle: Bundle | undefined): Questionn }) .map((entry, i) => { const questionnaire = entry.resource as Questionnaire; // non-questionnaire resources are filtered - const questionnaireTitle = questionnaire.title ?? 'Untitled'; + const questionnaireTitle = questionnaire.title + ? questionnaire.title.charAt(0).toUpperCase() + questionnaire.title.slice(1) + : 'Untitled'; + + const questionnairePublisher = questionnaire.publisher + ? questionnaire.publisher.charAt(0).toUpperCase() + questionnaire.publisher.slice(1) + : '-'; const questionnaireListItem: QuestionnaireListItem = { id: questionnaire.id ?? i.toString(), title: questionnaireTitle, avatarColor: randomColor({ luminosity: 'dark', seed: questionnaireTitle + i.toString() }), - publisher: questionnaire.publisher ?? '—', - date: questionnaire.date ? dayjs(questionnaire.date).format() : '—', + publisher: questionnairePublisher, + date: questionnaire.date ? dayjs(questionnaire.date).toDate() : null, status: questionnaire.status }; return questionnaireListItem; @@ -191,7 +125,7 @@ export function getResponseListItems(bundle: Bundle | undefined): ResponseListIt title: responseTitle, avatarColor: randomColor({ luminosity: 'dark', seed: responseTitle + i.toString() }), author: response.author?.display ?? '—', - authored: response.authored ?? dayjs(response.authored).format(), + authored: response.authored ? dayjs(response.authored).toDate() : null, status: response.status }; return responseListItem; diff --git a/apps/smart-forms-app/src/features/dashboard/utils/tableColumns.ts b/apps/smart-forms-app/src/features/dashboard/utils/tableColumns.ts new file mode 100644 index 000000000..6d04be450 --- /dev/null +++ b/apps/smart-forms-app/src/features/dashboard/utils/tableColumns.ts @@ -0,0 +1,61 @@ +/* + * Copyright 2023 Commonwealth Scientific and Industrial Research + * Organisation (CSIRO) ABN 41 687 119 230. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import type { ColumnDef } from '@tanstack/react-table'; +import type { QuestionnaireListItem, ResponseListItem } from '../types/list.interface.ts'; + +export function createQuestionnaireTableColumns(): ColumnDef[] { + return [ + { + accessorKey: 'title', + header: 'Title' + }, + { + accessorKey: 'publisher', + header: 'Publisher' + }, + { + accessorKey: 'date', + header: 'Date' + }, + { + accessorKey: 'status', + header: 'Status' + } + ]; +} + +export function createResponseTableColumns(): ColumnDef[] { + return [ + { + accessorKey: 'title', + header: 'Questionnaire Title' + }, + { + accessorKey: 'author', + header: 'Author' + }, + { + accessorKey: 'authored', + header: 'Authored On' + }, + { + accessorKey: 'status', + header: 'Status' + } + ]; +} diff --git a/apps/smart-forms-app/src/features/playground/components/PlaygroundHeader.tsx b/apps/smart-forms-app/src/features/playground/components/PlaygroundHeader.tsx index 48db086fa..b2cf8a4a5 100644 --- a/apps/smart-forms-app/src/features/playground/components/PlaygroundHeader.tsx +++ b/apps/smart-forms-app/src/features/playground/components/PlaygroundHeader.tsx @@ -15,17 +15,14 @@ * limitations under the License. */ -import Iconify from '../../../components/Iconify/Iconify.tsx'; - import { useTheme } from '@mui/material/styles'; import Logo from '../../../components/Logos/Logo.tsx'; -import { Box, IconButton, Toolbar, Tooltip, Typography } from '@mui/material'; +import { Box, IconButton, Tooltip, Typography } from '@mui/material'; import { useNavigate } from 'react-router-dom'; import { LogoWrapper } from '../../../components/Logos/Logo.styles.ts'; -import { StyledRoot } from '../../../components/Header/Header.styles.ts'; +import { StyledRoot, StyledToolbar } from '../../../components/Header/Header.styles.ts'; import { memo } from 'react'; - -const HEADER_PLAYGROUND = 64; +import ArrowBackIcon from '@mui/icons-material/ArrowBack'; const PlaygroundHeader = memo(function PlaygroundHeader() { const theme = useTheme(); @@ -34,10 +31,7 @@ const PlaygroundHeader = memo(function PlaygroundHeader() { return ( - + { @@ -47,7 +41,7 @@ const PlaygroundHeader = memo(function PlaygroundHeader() { mr: 1, color: 'text.primary' }}> - + @@ -56,8 +50,11 @@ const PlaygroundHeader = memo(function PlaygroundHeader() { - Playground - + + + Playground + + ); }); diff --git a/apps/smart-forms-app/src/features/preview/utils/preview.ts b/apps/smart-forms-app/src/features/preview/utils/preview.ts index 3a7d1f936..06e8189d3 100644 --- a/apps/smart-forms-app/src/features/preview/utils/preview.ts +++ b/apps/smart-forms-app/src/features/preview/utils/preview.ts @@ -30,7 +30,7 @@ export function qrToHTML( ): string { if (!questionnaireResponse.item || questionnaireResponse.item.length === 0) return ''; - let QrHtml = `
${questionnaire.title}

`; + let QrHtml = `
${questionnaire.title}

`; for (const topLevelQRItem of questionnaireResponse.item) { const topLevelQRItemHTML = qrItemToHTML(topLevelQRItem); diff --git a/apps/smart-forms-app/src/features/renderer/components/FormPage/QFormComponents/DateTimeItem/DateTimeField.tsx b/apps/smart-forms-app/src/features/renderer/components/FormPage/QFormComponents/DateTimeItem/DateTimeField.tsx index 024ce1e49..26d7ff049 100644 --- a/apps/smart-forms-app/src/features/renderer/components/FormPage/QFormComponents/DateTimeItem/DateTimeField.tsx +++ b/apps/smart-forms-app/src/features/renderer/components/FormPage/QFormComponents/DateTimeItem/DateTimeField.tsx @@ -19,6 +19,7 @@ import type { PropsWithIsTabledAttribute } from '../../../../types/renderProps.i import type { Dayjs } from 'dayjs'; import { DateTimePicker as MuiDateTimePicker, LocalizationProvider } from '@mui/x-date-pickers'; import { AdapterDayjs } from '@mui/x-date-pickers/AdapterDayjs'; +import { Box } from '@mui/material'; interface DateTimeFieldProps extends PropsWithIsTabledAttribute { value: Dayjs | null; @@ -33,20 +34,21 @@ function DateTimeField(props: DateTimeFieldProps) { return ( - + + + ); } diff --git a/apps/smart-forms-app/src/features/renderer/components/FormPage/QFormComponents/QItemChoice/QItemChoiceSelectAnswerOption.tsx b/apps/smart-forms-app/src/features/renderer/components/FormPage/QFormComponents/QItemChoice/QItemChoiceSelectAnswerOption.tsx index 70bbfccef..44d9ce514 100644 --- a/apps/smart-forms-app/src/features/renderer/components/FormPage/QFormComponents/QItemChoice/QItemChoiceSelectAnswerOption.tsx +++ b/apps/smart-forms-app/src/features/renderer/components/FormPage/QFormComponents/QItemChoice/QItemChoiceSelectAnswerOption.tsx @@ -30,6 +30,7 @@ import type { PropsWithQrItemChangeHandler } from '../../../../types/renderProps.interface.ts'; import DisplayInstructions from '../DisplayItem/DisplayInstructions.tsx'; +import { Fragment } from 'react'; interface Props extends PropsWithQrItemChangeHandler, @@ -77,28 +78,32 @@ function QItemChoiceSelectAnswerOption(props: Props) { endAdornment={{displayUnit}} sx={{ maxWidth: !isTabled ? 280 : 3000 }} onChange={handleChange}> - {qItem.answerOption?.map((option) => { + {qItem.answerOption?.map((option, index) => { if (option['valueCoding']) { return ( {option.valueCoding.display ?? option.valueCoding.code} ); - } else if (option['valueString']) { + } + + if (option['valueString']) { return ( {option.valueString} ); - } else if (option['valueInteger']) { + } + + if (option['valueInteger']) { return ( {option.valueInteger} ); - } else { - return null; } + + return ; })} ); diff --git a/apps/smart-forms-app/src/features/renderer/components/FormPreviewPage/FormPreview.tsx b/apps/smart-forms-app/src/features/renderer/components/FormPreviewPage/FormPreview.tsx index b3c269421..18a33b8e9 100644 --- a/apps/smart-forms-app/src/features/renderer/components/FormPreviewPage/FormPreview.tsx +++ b/apps/smart-forms-app/src/features/renderer/components/FormPreviewPage/FormPreview.tsx @@ -15,7 +15,7 @@ * limitations under the License. */ -import { Box, Card, Container, Fade, Typography } from '@mui/material'; +import { Card, Container, Fade } from '@mui/material'; import FormInvalid from '../FormPage/FormInvalid.tsx'; import parse from 'html-react-parser'; import { qrToHTML } from '../../../preview/utils/preview.ts'; @@ -23,6 +23,7 @@ import { removeHiddenAnswers } from '../../../save/api/saveQr.ts'; import { Helmet } from 'react-helmet'; import useQuestionnaireStore from '../../../../stores/useQuestionnaireStore.ts'; import useQuestionnaireResponseStore from '../../../../stores/useQuestionnaireResponseStore.ts'; +import PageHeading from '../../../dashboard/components/DashboardPages/PageHeading.tsx'; function FormPreview() { const sourceQuestionnaire = useQuestionnaireStore((state) => state.sourceQuestionnaire); @@ -55,10 +56,8 @@ function FormPreview() { {sourceQuestionnaire.title ?? 'Form Preview'} - - - Preview - + + Preview {parsedHTML} diff --git a/apps/smart-forms-app/src/features/renderer/components/RendererHeader/RendererHeader.tsx b/apps/smart-forms-app/src/features/renderer/components/RendererHeader/RendererHeader.tsx index 7c6556b8c..b34476d21 100644 --- a/apps/smart-forms-app/src/features/renderer/components/RendererHeader/RendererHeader.tsx +++ b/apps/smart-forms-app/src/features/renderer/components/RendererHeader/RendererHeader.tsx @@ -17,16 +17,15 @@ import Iconify from '../../../../components/Iconify/Iconify.tsx'; import { useTheme } from '@mui/material/styles'; -import DesktopHeader from '../../../../components/Header/DesktopHeader.tsx'; import useResponsive from '../../../../hooks/useResponsive.ts'; import Logo from '../../../../components/Logos/Logo.tsx'; -import { Box, IconButton, Stack, Typography } from '@mui/material'; +import { Box, IconButton, Typography } from '@mui/material'; import UpdatingIndicator from './UpdatingIndicator.tsx'; -import MobileHeaderWithQuestionnaire from '../../../../components/Header/MobileHeaderWithQuestionnaire.tsx'; import { LogoWrapper } from '../../../../components/Logos/Logo.styles.ts'; import { StyledRoot, StyledToolbar } from '../../../../components/Header/Header.styles.ts'; import useQuestionnaireStore from '../../../../stores/useQuestionnaireStore.ts'; import { memo } from 'react'; +import HeaderIcons from '../../../../components/Header/HeaderIcons.tsx'; interface RendererHeaderProps { navIsCollapsed: boolean; @@ -49,7 +48,6 @@ const RendererHeader = memo(function RendererHeader(props: RendererHeaderProps) )} - {isDesktop && navIsExpanded ? ( - - - {sourceQuestionnaire.title} - - - ) : null} + + + {sourceQuestionnaire.title} + + + - - {isDesktop && navIsExpanded ? : } - + ); diff --git a/apps/smart-forms-app/src/features/renderer/components/RendererHeader/UpdatingIndicator.tsx b/apps/smart-forms-app/src/features/renderer/components/RendererHeader/UpdatingIndicator.tsx index f709d9718..40252b0b6 100644 --- a/apps/smart-forms-app/src/features/renderer/components/RendererHeader/UpdatingIndicator.tsx +++ b/apps/smart-forms-app/src/features/renderer/components/RendererHeader/UpdatingIndicator.tsx @@ -18,12 +18,15 @@ import { useEffect, useState } from 'react'; import { Fade, Typography } from '@mui/material'; import useQuestionnaireResponseStore from '../../../../stores/useQuestionnaireResponseStore.ts'; +import useResponsive from '../../../../hooks/useResponsive.ts'; function UpdatingIndicator() { const updatableResponse = useQuestionnaireResponseStore((state) => state.updatableResponse); const [isUpdating, setIsUpdating] = useState(false); + const isDesktop = useResponsive('up', 'lg'); + useEffect(() => { setIsUpdating(true); setTimeout(() => { @@ -33,7 +36,11 @@ function UpdatingIndicator() { return ( - + Updating... diff --git a/apps/smart-forms-app/src/features/renderer/components/RendererNav/RendererNav.tsx b/apps/smart-forms-app/src/features/renderer/components/RendererNav/RendererNav.tsx index b4348d41a..d1279cc51 100644 --- a/apps/smart-forms-app/src/features/renderer/components/RendererNav/RendererNav.tsx +++ b/apps/smart-forms-app/src/features/renderer/components/RendererNav/RendererNav.tsx @@ -28,8 +28,7 @@ import CsiroLogo from '../../../../components/Logos/CsiroLogo.tsx'; import { NavLogoWrapper } from '../../../../components/Logos/Logo.styles.ts'; import { NavErrorAlertWrapper } from '../../../../components/Nav/Nav.styles.ts'; import useConfigStore from '../../../../stores/useConfigStore.ts'; - -const NAV_WIDTH = 240; +import { NAV_WIDTH } from '../../../../components/Header/Header.styles.ts'; interface Props { openNav: boolean; @@ -54,7 +53,7 @@ function RendererNav(props: Props) { '& .simplebar-content': { height: 1, display: 'flex', flexDirection: 'column' } }}> - + diff --git a/apps/smart-forms-app/src/features/smartAppLaunch/components/Authorisation.tsx b/apps/smart-forms-app/src/features/smartAppLaunch/components/Authorisation.tsx index c7801db5e..3600ac868 100644 --- a/apps/smart-forms-app/src/features/smartAppLaunch/components/Authorisation.tsx +++ b/apps/smart-forms-app/src/features/smartAppLaunch/components/Authorisation.tsx @@ -75,7 +75,6 @@ function Authorisation() { const setPatient = useConfigStore((state) => state.setPatient); const setUser = useConfigStore((state) => state.setUser); const setEncounter = useConfigStore((state) => state.setEncounter); - const updateQuestionnaireSource = useConfigStore((state) => state.updateQuestionnaireSource); const buildSourceQuestionnaire = useQuestionnaireStore((state) => state.buildSourceQuestionnaire); @@ -89,7 +88,6 @@ function Authorisation() { .then((client) => { // Set SMART client setSmartClient(client); - updateQuestionnaireSource('remote'); sessionStorage.setItem('authorised', 'true'); dispatch({ type: 'UPDATE_HAS_CLIENT', payload: true }); diff --git a/apps/smart-forms-app/src/features/smartAppLaunch/components/LaunchView.tsx b/apps/smart-forms-app/src/features/smartAppLaunch/components/LaunchView.tsx index 2bd284045..86c5b345b 100644 --- a/apps/smart-forms-app/src/features/smartAppLaunch/components/LaunchView.tsx +++ b/apps/smart-forms-app/src/features/smartAppLaunch/components/LaunchView.tsx @@ -17,9 +17,9 @@ import ProgressSpinner from '../../../components/Spinners/ProgressSpinner.tsx'; import type { LaunchState } from './Launch.tsx'; -import { useNavigate } from 'react-router-dom'; -import { Button, Stack, Typography } from '@mui/material'; +import { Stack, Typography } from '@mui/material'; import CenteredWrapper from '../../../components/Wrapper/CenteredWrapper.tsx'; +import UnlaunchedButton from '../../../components/Button/UnlaunchedButton.tsx'; interface LaunchViewProps { launchState: LaunchState; @@ -28,8 +28,6 @@ interface LaunchViewProps { function LaunchView(props: LaunchViewProps) { const { launchState } = props; - const navigate = useNavigate(); - if (launchState === 'error') { return ( @@ -41,15 +39,8 @@ function LaunchView(props: LaunchViewProps) { {'Please contact your administrator for assistance.'} - + + ); } diff --git a/apps/smart-forms-app/src/features/smartAppLaunch/components/RenderAuthStatus.tsx b/apps/smart-forms-app/src/features/smartAppLaunch/components/RenderAuthStatus.tsx index 4a9c509e4..fe98bdc51 100644 --- a/apps/smart-forms-app/src/features/smartAppLaunch/components/RenderAuthStatus.tsx +++ b/apps/smart-forms-app/src/features/smartAppLaunch/components/RenderAuthStatus.tsx @@ -16,20 +16,12 @@ */ import { useState } from 'react'; -import { - Accordion, - AccordionDetails, - AccordionSummary, - Box, - Button, - Typography -} from '@mui/material'; -import ErrorIcon from '@mui/icons-material/Error'; +import { Accordion, AccordionDetails, AccordionSummary, Stack, Typography } from '@mui/material'; import ExpandMoreIcon from '@mui/icons-material/ExpandMore'; -import Iconify from '../../../components/Iconify/Iconify.tsx'; import ProgressSpinner from '../../../components/Spinners/ProgressSpinner.tsx'; -import { useNavigate } from 'react-router-dom'; import type { AuthState } from '../types/authorisation.interface.ts'; +import CenteredWrapper from '../../../components/Wrapper/CenteredWrapper.tsx'; +import UnlaunchedButton from '../../../components/Button/UnlaunchedButton.tsx'; interface RenderAuthStatusProps { authState: AuthState; @@ -40,19 +32,18 @@ function RenderAuthStatus(props: RenderAuthStatusProps) { const [isExpanded, setIsExpanded] = useState(false); - const navigate = useNavigate(); + const launchFailed = + authState.hasClient === false || authState.hasUser === false || authState.hasPatient === false; - if ( - authState.hasClient === false || - authState.hasUser === false || - authState.hasPatient === false - ) { + if (launchFailed) { return ( - - - - Launch failed. - + + + An error occurred while authorising the launch. + + {'Try relaunching the app or contact your administrator for assistance.'} + + {authState.errorMessage ? ( setIsExpanded(expanded)}> @@ -64,19 +55,8 @@ function RenderAuthStatus(props: RenderAuthStatusProps) { ) : null} - - - - - + + ); } diff --git a/apps/smart-forms-app/src/features/viewer/ResponsePreview.tsx b/apps/smart-forms-app/src/features/viewer/ResponsePreview.tsx index d9cf1fa12..c43cae978 100644 --- a/apps/smart-forms-app/src/features/viewer/ResponsePreview.tsx +++ b/apps/smart-forms-app/src/features/viewer/ResponsePreview.tsx @@ -16,7 +16,7 @@ */ import { useContext, useEffect, useRef } from 'react'; -import { Box, Card, Container, Fade, Typography } from '@mui/material'; +import { Box, Card, Container, Fade } from '@mui/material'; import ViewerInvalid from '../renderer/components/FormPage/ViewerInvalid.tsx'; import { PrintComponentRefContext } from './ViewerLayout.tsx'; import { removeHiddenAnswers } from '../save/api/saveQr.ts'; @@ -25,6 +25,7 @@ import { qrToHTML } from '../preview/utils/preview.ts'; import { Helmet } from 'react-helmet'; import useQuestionnaireStore from '../../stores/useQuestionnaireStore.ts'; import useQuestionnaireResponseStore from '../../stores/useQuestionnaireResponseStore.ts'; +import PageHeading from '../dashboard/components/DashboardPages/PageHeading.tsx'; function ResponsePreview() { const sourceQuestionnaire = useQuestionnaireStore((state) => state.sourceQuestionnaire); @@ -59,7 +60,6 @@ function ResponsePreview() { enableWhenExpressions }); - // TODO fix the additional group title "Emergency contact" const parsedHTML = parse(qrToHTML(questionnaire, responseCleaned)); return ( @@ -68,10 +68,8 @@ function ResponsePreview() { {questionnaire.title ? questionnaire.title : 'Response Preview'} - - - Response Preview - + + Response Preview <>{parsedHTML} diff --git a/apps/smart-forms-app/src/features/viewer/ViewerHeader/ViewerHeader.tsx b/apps/smart-forms-app/src/features/viewer/ViewerHeader/ViewerHeader.tsx deleted file mode 100644 index ad229133a..000000000 --- a/apps/smart-forms-app/src/features/viewer/ViewerHeader/ViewerHeader.tsx +++ /dev/null @@ -1,75 +0,0 @@ -/* - * Copyright 2023 Commonwealth Scientific and Industrial Research - * Organisation (CSIRO) ABN 41 687 119 230. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import Iconify from '../../../components/Iconify/Iconify.tsx'; - -import { useTheme } from '@mui/material/styles'; -import DesktopHeader from '../../../components/Header/DesktopHeader.tsx'; -import useResponsive from '../../../hooks/useResponsive.ts'; -import Logo from '../../../components/Logos/Logo.tsx'; -import { Box, Stack } from '@mui/material'; -import { - MenuIconButton, - StyledRoot, - StyledToolbar -} from '../../../components/Header/Header.styles.ts'; -import { LogoWrapper } from '../../../components/Logos/Logo.styles.ts'; -import MobileHeaderWithQuestionnaire from '../../../components/Header/MobileHeaderWithQuestionnaire.tsx'; -import { memo } from 'react'; - -interface ViewerHeaderProps { - onOpenNav: () => void; -} - -const ViewerHeader = memo(function ViewerHeader(props: ViewerHeaderProps) { - const { onOpenNav } = props; - - const theme = useTheme(); - - const isDesktop = useResponsive('up', 'lg'); - - return ( - - - - - - - {!isDesktop ? ( - - - - ) : null} - - - - - {isDesktop ? : } - - - - ); -}); - -export default ViewerHeader; diff --git a/apps/smart-forms-app/src/features/viewer/ViewerLayout.tsx b/apps/smart-forms-app/src/features/viewer/ViewerLayout.tsx index 43036556d..69f48eff5 100644 --- a/apps/smart-forms-app/src/features/viewer/ViewerLayout.tsx +++ b/apps/smart-forms-app/src/features/viewer/ViewerLayout.tsx @@ -22,10 +22,10 @@ import { Outlet } from 'react-router-dom'; import BackToTopButton from '../backToTop/components/BackToTopButton.tsx'; import { Fab } from '@mui/material'; import KeyboardArrowUpIcon from '@mui/icons-material/KeyboardArrowUp'; -import ViewerHeader from './ViewerHeader/ViewerHeader.tsx'; import ViewerNav from './ViewerNav/ViewerNav.tsx'; import type { PrintComponentRefContextType } from '../print/types/printComponentRefContext.type.ts'; +import GenericHeader from '../../components/Header/GenericHeader.tsx'; export const PrintComponentRefContext = createContext({ componentRef: null, @@ -40,7 +40,7 @@ function ViewerLayout() { return ( - setOpen(true)} /> + setOpen(true)} /> setOpen(false)} />
diff --git a/apps/smart-forms-app/src/features/viewer/ViewerNav/ViewerNav.tsx b/apps/smart-forms-app/src/features/viewer/ViewerNav/ViewerNav.tsx index f158c44b1..03ae39602 100644 --- a/apps/smart-forms-app/src/features/viewer/ViewerNav/ViewerNav.tsx +++ b/apps/smart-forms-app/src/features/viewer/ViewerNav/ViewerNav.tsx @@ -29,8 +29,7 @@ import { NavErrorAlertWrapper } from '../../../components/Nav/Nav.styles.ts'; import useQuestionnaireStore from '../../../stores/useQuestionnaireStore.ts'; import useConfigStore from '../../../stores/useConfigStore.ts'; import useQuestionnaireResponseStore from '../../../stores/useQuestionnaireResponseStore.ts'; - -const NAV_WIDTH = 240; +import { NAV_WIDTH } from '../../../components/Header/Header.styles.ts'; interface Props { openNav: boolean; @@ -56,7 +55,7 @@ function ViewerNav(props: Props) { '& .simplebar-content': { height: 1, display: 'flex', flexDirection: 'column' } }}> - + diff --git a/apps/smart-forms-app/src/router/Router.tsx b/apps/smart-forms-app/src/router/Router.tsx index a46a8392d..37f1b251b 100644 --- a/apps/smart-forms-app/src/router/Router.tsx +++ b/apps/smart-forms-app/src/router/Router.tsx @@ -19,7 +19,6 @@ import { createBrowserRouter, Navigate, RouterProvider } from 'react-router-dom' import DashboardLayout from '../features/dashboard/layout/DashboardLayout.tsx'; import Launch from '../features/smartAppLaunch/components/Launch.tsx'; import QuestionnairesPage from '../features/dashboard/components/DashboardPages/QuestionnairePage/QuestionnairesPage.tsx'; -import ResponsesPage from '../features/dashboard/components/DashboardPages/ResponsesPage/ResponsesPage.tsx'; import RendererLayout from '../features/renderer/components/RendererLayout.tsx'; import FormRenderer from '../features/renderer/components/FormPage/FormRenderer/FormRenderer.tsx'; import FormPreview from '../features/renderer/components/FormPreviewPage/FormPreview.tsx'; @@ -28,6 +27,7 @@ import ResponsePreview from '../features/viewer/ResponsePreview.tsx'; import Authorisation from '../features/smartAppLaunch/components/Authorisation.tsx'; import PlaygroundLayout from '../features/playground/components/PlaygroundLayout.tsx'; import Playground from '../features/playground/components/Playground.tsx'; +import ResponsesPage from '../features/dashboard/components/DashboardPages/ResponsesPage/ResponsesPage.tsx'; export default function Router() { const router = createBrowserRouter([ diff --git a/apps/smart-forms-app/src/stores/useConfigStore.ts b/apps/smart-forms-app/src/stores/useConfigStore.ts index 6bbe0118f..a7837b12f 100644 --- a/apps/smart-forms-app/src/stores/useConfigStore.ts +++ b/apps/smart-forms-app/src/stores/useConfigStore.ts @@ -1,7 +1,6 @@ import { create } from 'zustand'; import type { Encounter, Patient, Practitioner } from 'fhir/r4'; import type Client from 'fhirclient/lib/Client'; -import type { Source } from '../types/source.interface.ts'; export interface ConfigState { smartClient: Client | null; @@ -9,12 +8,10 @@ export interface ConfigState { user: Practitioner | null; encounter: Encounter | null; debugMode: boolean; - questionnaireSource: Source; setSmartClient: (client: Client) => void; setPatient: (patient: Patient) => void; setUser: (user: Practitioner) => void; setEncounter: (encounter: Encounter) => void; - updateQuestionnaireSource: (newSource: Source) => void; activateDebugMode: () => void; } @@ -24,12 +21,10 @@ const useConfigStore = create()((set) => ({ user: null, encounter: null, debugMode: false, - questionnaireSource: 'remote', setSmartClient: (client: Client) => set(() => ({ smartClient: client })), setPatient: (patient: Patient) => set(() => ({ patient: patient })), setUser: (user: Practitioner) => set(() => ({ user: user })), setEncounter: (encounter: Encounter) => set(() => ({ encounter: encounter })), - updateQuestionnaireSource: (newSource: Source) => set(() => ({ questionnaireSource: newSource })), activateDebugMode: () => set(() => ({ debugMode: true })) })); diff --git a/apps/smart-forms-app/src/theme/palette.ts b/apps/smart-forms-app/src/theme/palette.ts index 84bde3112..0fc33dabc 100644 --- a/apps/smart-forms-app/src/theme/palette.ts +++ b/apps/smart-forms-app/src/theme/palette.ts @@ -69,7 +69,7 @@ const palette: PaletteOptions = { dark: '#abebc6' }, pale: { - primary: '#D1E9FC', + primary: '#D6EBFC', secondary: '#D3EBDD' }, divider: alpha(grey['500'], 0.24), diff --git a/apps/smart-forms-app/src/types/Nav.interface.ts b/apps/smart-forms-app/src/types/Nav.interface.ts index ffca2d9fc..3d8c2429c 100644 --- a/apps/smart-forms-app/src/types/Nav.interface.ts +++ b/apps/smart-forms-app/src/types/Nav.interface.ts @@ -15,16 +15,11 @@ * limitations under the License. */ +import type { ReactNode } from 'react'; + export interface OperationItem { title: string; - icon: JSX.Element; + icon: ReactNode; disabled?: boolean; onClick: () => unknown; } - -export interface NavItem { - title: string; - path: string; - icon: JSX.Element; - disabled?: boolean; -} diff --git a/package-lock.json b/package-lock.json index 2a9351242..88831ce0e 100644 --- a/package-lock.json +++ b/package-lock.json @@ -25,6 +25,7 @@ "@sentry/react": "^7.58.1", "@sentry/tracing": "^7.57.0", "@tanstack/react-query": "^4.29.5", + "@tanstack/react-table": "^8.9.3", "allotment": "^1.19.0", "dayjs": "^1.11.7", "fhirclient": "^2.5.2", @@ -7091,6 +7092,37 @@ "react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0" } }, + "node_modules/@tanstack/react-table": { + "version": "8.9.3", + "resolved": "https://registry.npmjs.org/@tanstack/react-table/-/react-table-8.9.3.tgz", + "integrity": "sha512-Ng9rdm3JPoSCi6cVZvANsYnF+UoGVRxflMb270tVj0+LjeT/ZtZ9ckxF6oLPLcKesza6VKBqtdF9mQ+vaz24Aw==", + "dependencies": { + "@tanstack/table-core": "8.9.3" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/tannerlinsley" + }, + "peerDependencies": { + "react": ">=16", + "react-dom": ">=16" + } + }, + "node_modules/@tanstack/table-core": { + "version": "8.9.3", + "resolved": "https://registry.npmjs.org/@tanstack/table-core/-/table-core-8.9.3.tgz", + "integrity": "sha512-NpHZBoHTfqyJk0m/s/+CSuAiwtebhYK90mDuf5eylTvgViNOujiaOaxNDxJkQQAsVvHWZftUGAx1EfO1rkKtLg==", + "engines": { + "node": ">=12" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/tannerlinsley" + } + }, "node_modules/@tsconfig/node10": { "version": "1.0.9", "resolved": "https://registry.npmjs.org/@tsconfig/node10/-/node10-1.0.9.tgz", diff --git a/package.json b/package.json index cd36a2f0a..2eff6a66b 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,7 @@ { "scripts": { "lint": "eslint -c .eslintrc.json .", + "lint-fix": "eslint -c .eslintrc.json --fix .", "check-formatting": "prettier --check ." }, "workspaces": [