Skip to content

Commit

Permalink
Merge pull request #339 from aehrc/feature/collapsible-form
Browse files Browse the repository at this point in the history
Feature/collapsible form
  • Loading branch information
fongsean authored Jul 25, 2023
2 parents 174dbb7 + c9a4bd0 commit d7f4acd
Show file tree
Hide file tree
Showing 25 changed files with 781 additions and 97 deletions.
1 change: 0 additions & 1 deletion apps/smart-forms-app/cypress/e2e/viewer.cy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,6 @@ describe('response viewer', () => {

it('edit response', () => {
cy.clickOnViewerOperation('Edit Response');
cy.getByData('form-heading').should('be.visible');

cy.goToPatientDetailsTab();
cy.initAgeValue(60);
Expand Down
1 change: 0 additions & 1 deletion apps/smart-forms-app/cypress/support/commands.ts
Original file line number Diff line number Diff line change
Expand Up @@ -95,7 +95,6 @@ Cypress.Commands.add('waitForPopulation', () => {
cy.intercept(populateRegex).as('populating');

cy.wait('@populating', { timeout: 10000 }).its('response.statusCode').should('eq', 200);
cy.getByData('form-heading').should('be.visible');
});

Cypress.Commands.add('launchFromSMARTHealthIT', () => {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import { Button } from '@mui/material';
import { useNavigate } from 'react-router-dom';

function ReauthenticateButton() {
const navigate = useNavigate();

return (
<Button
variant="contained"
sx={{ mt: 4 }}
onClick={() => {
navigate('/');
}}
data-test="button-unlaunched-state">
Re-authenticate
</Button>
);
}

export default ReauthenticateButton;
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ function UnlaunchedButton() {
<Button
variant="contained"
color="warning"
sx={{ mt: 6 }}
sx={{ mt: 4 }}
onClick={() => {
navigate('/dashboard/questionnaires');
}}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ interface DashboardHeaderProps {
onOpenNav: () => void;
}

const GenericHeader = memo(function DashboardHeader(props: DashboardHeaderProps) {
const GenericHeader = memo(function GenericHeader(props: DashboardHeaderProps) {
const { onOpenNav } = props;

const isDesktop = useResponsive('up', 'lg');
Expand Down
8 changes: 6 additions & 2 deletions apps/smart-forms-app/src/components/Header/HeaderIcons.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -21,13 +21,17 @@ import { Stack } from '@mui/material';
import { useTheme } from '@mui/material/styles';
import useResponsive from '../../hooks/useResponsive.ts';

function HeaderIcons() {
interface HeaderIconsProps {
isEmbeddedView?: boolean;
}
function HeaderIcons(props: HeaderIconsProps) {
const { isEmbeddedView } = props;
const theme = useTheme();
const isDesktop = useResponsive('up', 'lg');

return (
<Stack direction="row" alignItems="center" sx={{ color: theme.palette.grey['700'] }}>
{isDesktop ? <DesktopHeaderIcons /> : <MobileHeaderIcons />}
{isDesktop && !isEmbeddedView ? <DesktopHeaderIcons /> : <MobileHeaderIcons />}
</Stack>
);
}
Expand Down
7 changes: 5 additions & 2 deletions apps/smart-forms-app/src/components/Logos/Logo.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -22,17 +22,20 @@ import useResponsive from '../../hooks/useResponsive.ts';

interface LogoProps {
isNav?: boolean;
isEmbeddedView?: boolean;
}

const Logo = memo(function Logo(props: LogoProps) {
const { isNav } = props;
const { isNav, isEmbeddedView } = props;

const isDesktop = useResponsive('up', 'lg');

return (
<Box display="flex" alignItems="center" columnGap={1.5}>
<Box component="img" src={AppLogo} display="inline-flex" width={36} height={36} />
{isDesktop || isNav ? <Typography variant="h6">Smart Forms</Typography> : null}
{(isDesktop || isNav) && !isEmbeddedView ? (
<Typography variant="h6">Smart Forms</Typography>
) : null}
</Box>
);
});
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ import EditNoteIcon from '@mui/icons-material/EditNote';

function CreateNewResponseButton() {
const smartClient = useConfigStore((state) => state.smartClient);
const setLaunchIntent = useConfigStore((state) => state.setLaunchIntent);
const buildSourceQuestionnaire = useQuestionnaireStore((state) => state.buildSourceQuestionnaire);
const buildSourceResponse = useQuestionnaireResponseStore((state) => state.buildSourceResponse);

Expand Down Expand Up @@ -57,6 +58,13 @@ function CreateNewResponseButton() {
const questionnaireResponse = createQuestionnaireResponse(questionnaire);
buildSourceResponse(questionnaireResponse);

// FIXME this is a hack to test tabs rendering in an embedded browser
if (questionnaire.id === 'TestIntentCollapsible') {
setLaunchIntent('embedded-browser');
} else {
setLaunchIntent(null);
}

navigate('/renderer');
setIsLoading(false);
}
Expand Down
46 changes: 46 additions & 0 deletions apps/smart-forms-app/src/features/notfound/NotFound.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
/*
* 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 CenteredWrapper from '../../components/Wrapper/CenteredWrapper.tsx';
import { Stack, Typography } from '@mui/material';
import UnlaunchedButton from '../../components/Button/UnlaunchedButton.tsx';
import useConfigStore from '../../stores/useConfigStore.ts';
import ReauthenticateButton from '../../components/Button/ReauthenticateButton.tsx';

function NotFound() {
const smartClient = useConfigStore((state) => state.smartClient);

const isNotLaunched = !smartClient;

const authSessionFound = isNotLaunched && sessionStorage.getItem('authorised') === 'true';

return (
<CenteredWrapper>
<Stack rowGap={2}>
<Typography variant="h3">Error 404</Typography>
<Typography fontSize={13}>
{authSessionFound
? "We couldn't find the page you were looking for, but we detected a recent auth session. You would need to be re-authenticated. Do you want to try re-authenticating?"
: "We couldn't find the page you were looking for. Do you want to go back to the home page?"}
</Typography>
</Stack>
{authSessionFound ? <ReauthenticateButton /> : <UnlaunchedButton />}
</CenteredWrapper>
);
}

export default NotFound;
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,6 @@ import { createQuestionnaireResponse } from '../../renderer/utils/qrItem.ts';
import useQuestionnaireStore from '../../../stores/useQuestionnaireStore.ts';
import useQuestionnaireResponseStore from '../../../stores/useQuestionnaireResponseStore.ts';

// TODO this cant be used yet

function PlaygroundRenderer() {
const sourceQuestionnaire = useQuestionnaireStore((state) => state.sourceQuestionnaire);

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
/*
* 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 { ReactNode } from 'react';
import { memo } from 'react';
import {
Accordion,
AccordionDetails,
AccordionSummary,
Box,
Tooltip,
Typography
} from '@mui/material';
import ExpandMoreIcon from '@mui/icons-material/ExpandMore';
import { getContextDisplays } from '../../../utils/tabs.ts';
import type { QuestionnaireItem } from 'fhir/r4';
import { getShortText } from '../../../utils/itemControl.ts';
import GroupHeadingIcon from '../QFormComponents/GroupItem/GroupHeadingIcon.tsx';

interface FormBodySingleCollapsibleProps {
qItem: QuestionnaireItem;
index: number;
selectedIndex: number;
onToggleExpand: (index: number) => void;
children: ReactNode;
}

const FormBodySingleCollapsible = memo(function FormBodySingleCollapsible(
props: FormBodySingleCollapsibleProps
) {
const { qItem, index, selectedIndex, onToggleExpand, children } = props;

const contextDisplayItems = getContextDisplays(qItem);

const collapsibleLabel = getShortText(qItem) ?? qItem.text ?? '';

return (
<Accordion
expanded={selectedIndex === index}
TransitionProps={{ unmountOnExit: true }}
onChange={() => onToggleExpand(index)}>
<AccordionSummary
expandIcon={
<Tooltip title={'Expand'}>
<ExpandMoreIcon />
</Tooltip>
}>
<Box display="flex" alignItems="center" justifyContent="space-between" width="100%" mr={3}>
<Typography variant="subtitle2">{collapsibleLabel}</Typography>
<Box display="flex" columnGap={0.5}>
{contextDisplayItems.map((item) => {
return <GroupHeadingIcon key={item.linkId} displayItem={item} />;
})}
</Box>
</Box>
</AccordionSummary>
<AccordionDetails>{children}</AccordionDetails>
</Accordion>
);
});

export default FormBodySingleCollapsible;
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
/*
* 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 { QuestionnaireItem, QuestionnaireResponseItem } from 'fhir/r4';
import GroupItem from '../QFormComponents/GroupItem/GroupItem.tsx';
import FormBodySingleCollapsible from './FormBodySingleCollapsible.tsx';
import type { PropsWithQrItemChangeHandler } from '../../../types/renderProps.interface.ts';
import useHidden from '../../../hooks/useHidden.ts';

interface FormBodySingleCollapsibleProps
extends PropsWithQrItemChangeHandler<QuestionnaireResponseItem> {
qItem: QuestionnaireItem;
qrItem: QuestionnaireResponseItem;
index: number;
selectedIndex: number;
onToggleExpand: (index: number) => void;
}

function FormBodySingleCollapsibleWrapper(props: FormBodySingleCollapsibleProps) {
const { qItem, qrItem, index, selectedIndex, onToggleExpand, onQrItemChange } = props;

const itemIsHidden = useHidden(qItem);
if (itemIsHidden) {
return null;
}

return (
<FormBodySingleCollapsible
key={qItem.linkId}
qItem={qItem}
index={index}
selectedIndex={selectedIndex}
onToggleExpand={onToggleExpand}>
<GroupItem
qItem={qItem}
qrItem={qrItem}
isRepeated={true}
groupCardElevation={1}
onQrItemChange={onQrItemChange}
/>
</FormBodySingleCollapsible>
);
}

export default FormBodySingleCollapsibleWrapper;
Loading

0 comments on commit d7f4acd

Please sign in to comment.