Skip to content

Commit

Permalink
feat(form-admin-app): implement form support topics
Browse files Browse the repository at this point in the history
  • Loading branch information
tzuge committed Jan 15, 2025
1 parent 0cc3467 commit 3bb1ba5
Show file tree
Hide file tree
Showing 9 changed files with 255 additions and 131 deletions.
11 changes: 11 additions & 0 deletions apps/form-admin-app/src/app/components/ActionsForm.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import styled from 'styled-components';

export const ActionsForm = styled.form`
background: var(--goa-color-greyscale-100);
border-top: 1px solid var(--goa-color-greyscale-200);
flex: 0;
padding-top: var(--goa-space-m);
padding-bottom: var(--goa-space-s);
padding-left: var(--goa-space-s);
padding-right: var(--goa-space-s);
`;
14 changes: 4 additions & 10 deletions apps/form-admin-app/src/app/components/DetailsLayout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,28 +12,22 @@ const DetailsLayoutContainer = styled.div`
right: 0;
display: flex;
flex-direction: column;
& > * {
flex: 1
}
& > :first-child {
background: var(--goa-color-greyscale-100);
border-bottom: 1px solid var(--goa-color-greyscale-200);
flex: 0;
}
& > form:last-child {
background: var(--goa-color-greyscale-100);
border-top: 1px solid var(--goa-color-greyscale-200);
flex: 0;
padding-top: var(--goa-space-m);
padding-bottom: var(--goa-space-s);
padding-left: var(--goa-space-s);
padding-right: var(--goa-space-s);
}
`;

interface DetailsLayoutProps {
initialized: boolean;
navButtons?: ReactNode;
header: ReactNode;
children: ReactNode;
actionsForm: ReactNode;
actionsForm?: ReactNode;
}

export const DetailsLayout: FunctionComponent<DetailsLayoutProps> = ({
Expand Down
73 changes: 73 additions & 0 deletions apps/form-admin-app/src/app/components/Tabs.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
import { FunctionComponent, ReactNode, useState } from 'react';
import styled from 'styled-components';

interface TabProps {
heading: string;
hide?: boolean;
children: ReactNode;
}
export const Tab: FunctionComponent<TabProps> = ({ children }) => {
return children;
};

const TabContainer = styled.div`
display: flex;
flex-direction: column;
min-height: 0;
& > .heading {
flex: 0;
border-bottom: 1px solid var(--goa-color-greyscale-100);
> button {
border: none;
padding: var(--goa-space-s);
margin-right: var(--goa-space-xs);
cursor: pointer;
font: var(--goa-typography-body-m);
}
> button:hover,
button[data-selected='true'] {
border-bottom: 3px solid var(--goa-color-interactive-default);
font: var(--goa-typography-heading-s);
}
}
& > .content {
flex: 1;
min-height: 0;
position: relative;
> * {
position: absolute;
height: 100%;
width: 100%;
}
}
> .content[data-selected='false'] {
display: none;
}
`;

interface TabsProps {
children: React.ReactElement<TabProps>[];
}
export const Tabs: FunctionComponent<TabsProps> = ({ children }) => {
const [selected, setSelected] = useState(0);

const available = children.filter((child) => !child.props.hide);

return (
<TabContainer>
<div className="heading">
{available.length > 1 &&
available.map((child, idx) => (
<button key={child.props.heading} data-selected={idx === selected} onClick={() => setSelected(idx)}>
{child.props.heading}
</button>
))}
</div>
{available.map((child, idx) => (
<div className="content" data-selected={idx === selected}>
{child}
</div>
))}
</TabContainer>
);
};
131 changes: 80 additions & 51 deletions apps/form-admin-app/src/app/containers/Form.tsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
import { GoAButton, GoAButtonGroup, GoACheckbox, GoAFormItem, GoAInput, GoAModal } from '@abgov/react-components-new';
import { GoAButton, GoAButtonGroup, GoAFormItem, GoAModal } from '@abgov/react-components-new';
import { DateTime } from 'luxon';
import { useEffect, useState } from 'react';
import { useDispatch, useSelector } from 'react-redux';
import { useParams } from 'react-router-dom';
import styled from 'styled-components';
import {
AppDispatch,
formBusySelector,
Expand All @@ -14,13 +15,23 @@ import {
FormStatus,
runFormOperation,
selectForm,
loadTopic,
selectTopic,
} from '../state';
import { FormViewer } from './FormViewer';
import { DetailsLayout } from '../components/DetailsLayout';
import { ContentContainer } from '../components/ContentContainer';
import { PropertiesContainer } from '../components/PropertiesContainer';
import { PdfDownload } from './PdfDownload';
import { AdspId } from '../../lib/adspId';
import CommentsViewer from './CommentsViewer';
import { ActionsForm } from '../components/ActionsForm';
import { Tab, Tabs } from '../components/Tabs';

const FormContainer = styled.div`
display: flex;
flex-direction: column;
`;

export const Form = () => {
const dispatch = useDispatch<AppDispatch>();
Expand All @@ -40,6 +51,15 @@ export const Form = () => {
dispatch(selectForm(formId));
}, [dispatch, formId]);

useEffect(() => {
if (form) {
(async () => {
await dispatch(loadTopic({ resourceId: form.urn, typeId: 'form-questions' }));
await dispatch(selectTopic({ resourceId: form.urn }));
})();
}
}, [dispatch, definition, form]);

return (
<DetailsLayout
initialized={!!(definition && form)}
Expand All @@ -65,57 +85,66 @@ export const Form = () => {
</PropertiesContainer>
)
}
actionsForm={
<form>
<GoAButtonGroup alignment="end">
{form?.status === FormStatus.submitted && canSetToDraft && (
<GoAButton
type="secondary"
disabled={busy.executing}
onClick={() => dispatch(runFormOperation({ urn: AdspId.parse(form.urn), operation: 'to-draft' }))}
>
Set to draft
</GoAButton>
)}
{form?.status !== FormStatus.archived && canArchive && (
<GoAButton type="primary" disabled={busy.executing} onClick={() => setShowArchiveConfirm(true)}>
Archive form
</GoAButton>
)}
</GoAButtonGroup>
</form>
}
>
<>
<ContentContainer>
<FormViewer
dataSchema={definition?.dataSchema}
uiSchema={definition?.uiSchema}
data={form?.data}
files={files}
/>
</ContentContainer>
<GoAModal heading="Archive this form?" open={showArchiveConfirm}>
<div>
Archiving the form will change its status to "Archived" so it can be separated from forms that are still
being actively worked on. The applicant will no longer be able to update the form.
</div>
<GoAButtonGroup alignment="end" mt="xl">
<GoAButton type="secondary" onClick={() => setShowArchiveConfirm(false)}>
Cancel
</GoAButton>
<GoAButton
type={form?.status === FormStatus.submitted ? 'primary' : 'secondary'}
onClick={() => {
dispatch(runFormOperation({ urn: AdspId.parse(form.urn), operation: 'archive' }));
setShowArchiveConfirm(false);
}}
>
Archive form
</GoAButton>
</GoAButtonGroup>
</GoAModal>
</>
<Tabs>
<Tab heading="Form">
<FormContainer>
<ContentContainer>
<FormViewer
dataSchema={definition?.dataSchema}
uiSchema={definition?.uiSchema}
data={form?.data}
files={files}
/>
</ContentContainer>
<ActionsForm>
<GoAButtonGroup alignment="end">
{form?.status === FormStatus.submitted && canSetToDraft && (
<GoAButton
type="secondary"
disabled={busy.executing}
onClick={() => dispatch(runFormOperation({ urn: AdspId.parse(form.urn), operation: 'to-draft' }))}
>
Set to draft
</GoAButton>
)}
{form?.status !== FormStatus.archived && canArchive && (
<GoAButton
type={form?.status === FormStatus.submitted ? 'primary' : 'secondary'}
disabled={busy.executing}
onClick={() => setShowArchiveConfirm(true)}
>
Archive form
</GoAButton>
)}
</GoAButtonGroup>
</ActionsForm>
<GoAModal heading="Archive this form?" open={showArchiveConfirm}>
<div>
Archiving the form will change its status to "Archived" so it can be separated from forms that are still
being actively worked on. The applicant will no longer be able to update the form.
</div>
<GoAButtonGroup alignment="end" mt="xl">
<GoAButton type="secondary" onClick={() => setShowArchiveConfirm(false)}>
Cancel
</GoAButton>
<GoAButton
type="primary"
onClick={() => {
dispatch(runFormOperation({ urn: AdspId.parse(form.urn), operation: 'archive' }));
setShowArchiveConfirm(false);
}}
>
Archive form
</GoAButton>
</GoAButtonGroup>
</GoAModal>
</FormContainer>
</Tab>
<Tab heading="Applicant questions" hide={!definition.supportTopic}>
<CommentsViewer />
</Tab>
</Tabs>
</DetailsLayout>
);
};
10 changes: 10 additions & 0 deletions apps/form-admin-app/src/app/containers/FormDefinitionOverview.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,16 @@ export const FormDefinitionOverview: FunctionComponent<FormDefinitionOverviewPro
the form, then submit once ready.
</GoADetails>
)}
{definition.supportTopic ? (
<GoADetails heading="Applicant questions">
Applicants can send questions regarding their form, which staff can review and respond to. Anonymous
applicants are not able to send questions.
</GoADetails>
) : (
<GoADetails heading="No applicant questions">
Applicants are not able to send questions through the form system.
</GoADetails>
)}
{definition.generatesPdf ? (
<GoADetails heading="Creates PDF when submitted">
PDF copy of the submitted information is created when forms are submitted.
Expand Down
33 changes: 18 additions & 15 deletions apps/form-admin-app/src/app/containers/FormDefinitions.tsx
Original file line number Diff line number Diff line change
@@ -1,3 +1,16 @@
import {
GoABadge,
GoAButton,
GoAButtonGroup,
GoACallout,
GoAChip,
GoADropdown,
GoADropdownItem,
GoAFormItem,
GoAIconButton,
GoASkeleton,
GoATable,
} from '@abgov/react-components-new';
import { FunctionComponent, useEffect, useState } from 'react';
import { useDispatch, useSelector } from 'react-redux';
import {
Expand All @@ -19,19 +32,6 @@ import {
tagsSelector,
Resource,
} from '../state';
import {
GoABadge,
GoAButton,
GoAButtonGroup,
GoACallout,
GoAChip,
GoADropdown,
GoADropdownItem,
GoAFormItem,
GoAIconButton,
GoASkeleton,
GoATable,
} from '@abgov/react-components-new';
import { useNavigate } from 'react-router-dom';
import { AddTagModal } from '../components/AddTagModal';
import { SearchLayout } from '../components/SearchLayout';
Expand Down Expand Up @@ -73,7 +73,9 @@ export const FormDefinitionRow: FunctionComponent<FormDefinitionRowProps> = ({
<tr key={definition.id}>
<td>{definition.name}</td>
{loadingTags ? (
<td><GoASkeleton type="text-small" /></td>
<td>
<GoASkeleton type="text-small" />
</td>
) : (
<td>
{tags?.map((tag) => (
Expand All @@ -84,9 +86,10 @@ export const FormDefinitionRow: FunctionComponent<FormDefinitionRowProps> = ({
)}
<td>
<FeatureBadge feature="Anonymous applicant" hasFeature={definition.anonymousApply} />
<FeatureBadge feature="Scheduled intakes" hasFeature={definition.scheduledIntakes} />
<FeatureBadge feature="Applicant questions" hasFeature={definition.supportTopic} />
<FeatureBadge feature="Creates submissions" hasFeature={definition.submissionRecords} />
<FeatureBadge feature="Creates PDF" hasFeature={definition.generatesPdf} />
<FeatureBadge feature="Scheduled intakes" hasFeature={definition.scheduledIntakes} />
</td>
<td>
<GoAButtonGroup alignment="end">
Expand Down
Loading

0 comments on commit 3bb1ba5

Please sign in to comment.