Skip to content

Commit

Permalink
feat(form-app): support multiple user forms per definition
Browse files Browse the repository at this point in the history
  • Loading branch information
tzuge committed Jan 31, 2025
1 parent b4897ba commit 9d704bd
Show file tree
Hide file tree
Showing 14 changed files with 293 additions and 128 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import {
GoASpacer,
GoATable,
} from '@abgov/react-components-new';
import { RowSkeleton } from '@core-services/app-common';
import { FunctionComponent, useEffect, useState } from 'react';
import { useDispatch, useSelector } from 'react-redux';
import styled from 'styled-components';
Expand All @@ -29,7 +30,6 @@ import {
import { ContentContainer } from '../components/ContentContainer';
import { PropertiesContainer } from '../components/PropertiesContainer';
import { ScheduleIntakeModal } from '../components/ScheduleIntakeModal';
import { RowSkeleton } from '../components/RowSkeleton';

const OverviewLayout = styled.div`
position: absolute;
Expand Down
23 changes: 7 additions & 16 deletions apps/form-admin-app/src/app/containers/FormDefinitions.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { GoABadge, GoAButton, GoAButtonGroup, GoACallout, GoATable } from '@abgov/react-components-new';
import { RowLoadMore, RowSkeleton } from '@core-services/app-common';
import { FunctionComponent, useEffect, useState } from 'react';
import { useDispatch, useSelector } from 'react-redux';
import {
Expand All @@ -17,7 +18,6 @@ import { NavigateFunction, useNavigate } from 'react-router-dom';
import { AddTagModal } from '../components/AddTagModal';
import { SearchLayout } from '../components/SearchLayout';
import { ContentContainer } from '../components/ContentContainer';
import { RowSkeleton } from '../components/RowSkeleton';
import { Tags } from './Tags';
import { TagSearchFilter } from './TagSearchFilter';

Expand Down Expand Up @@ -126,21 +126,12 @@ export const FormsDefinitions = () => {
/>
))}
<RowSkeleton columns={4} show={busy.loading} />
{next && (
<tr>
<td colSpan={4}>
<GoAButtonGroup alignment="center">
<GoAButton
type="tertiary"
disabled={busy.loading}
onClick={() => dispatch(loadDefinitions({ after: next }))}
>
Load more
</GoAButton>
</GoAButtonGroup>
</td>
</tr>
)}
<RowLoadMore
columns={4}
next={next}
loading={busy.loading}
onLoadMore={(after) => dispatch(loadDefinitions({ after }))}
/>
</tbody>
</GoATable>
</ContentContainer>
Expand Down
23 changes: 7 additions & 16 deletions apps/form-admin-app/src/app/containers/FormSubmissions.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import {
GoAFormItem,
GoATable,
} from '@abgov/react-components-new';
import { RowLoadMore, RowSkeleton } from '@core-services/app-common';
import { useDispatch, useSelector } from 'react-redux';
import { FunctionComponent, useEffect, useState } from 'react';
import { useNavigate } from 'react-router-dom';
Expand All @@ -32,7 +33,6 @@ import { DataValueCell } from '../components/DataValueCell';
import { ExportModal } from '../components/ExportModal';
import { SearchFormItemsContainer } from '../components/SearchFormItemsContainer';
import { DataValueCriteriaItem } from '../components/DataValueCriteriaItem';
import { RowSkeleton } from '../components/RowSkeleton';
import { AddTagModal } from '../components/AddTagModal';
import { Tags } from './Tags';
import { TagSearchFilter } from './TagSearchFilter';
Expand Down Expand Up @@ -186,21 +186,12 @@ export const FormSubmissions: FunctionComponent<FormSubmissionsProps> = ({ defin
</tr>
))}
<RowSkeleton columns={4 + dataValues.length} show={busy.loading} />
{next && (
<tr>
<td colSpan={4 + dataValues.length}>
<GoAButtonGroup alignment="center">
<GoAButton
type="tertiary"
disabled={busy.loading}
onClick={() => dispatch(findSubmissions({ definitionId, criteria, after: next }))}
>
Load more
</GoAButton>
</GoAButtonGroup>
</td>
</tr>
)}
<RowLoadMore
columns={4 + dataValues.length}
next={next}
loading={busy.loading}
onLoadMore={(after) => dispatch(findSubmissions({ definitionId, criteria, after }))}
/>
</tbody>
</GoATable>
</ContentContainer>
Expand Down
23 changes: 7 additions & 16 deletions apps/form-admin-app/src/app/containers/Forms.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import {
GoAIcon,
GoATable,
} from '@abgov/react-components-new';
import { RowLoadMore, RowSkeleton } from '@core-services/app-common';
import { FunctionComponent, useEffect, useState } from 'react';
import { useDispatch, useSelector } from 'react-redux';
import { NavigateFunction, useNavigate } from 'react-router-dom';
Expand Down Expand Up @@ -37,7 +38,6 @@ import { DataValueCell } from '../components/DataValueCell';
import { ExportModal } from '../components/ExportModal';
import { SearchFormItemsContainer } from '../components/SearchFormItemsContainer';
import { DataValueCriteriaItem } from '../components/DataValueCriteriaItem';
import { RowSkeleton } from '../components/RowSkeleton';
import { AddTagModal } from '../components/AddTagModal';
import { Tags } from './Tags';
import { TagSearchFilter } from './TagSearchFilter';
Expand Down Expand Up @@ -226,21 +226,12 @@ export const Forms: FunctionComponent<FormsProps> = ({ definitionId }) => {
/>
))}
<RowSkeleton columns={5 + dataValues.length} show={busy.loading} />
{next && (
<tr>
<td colSpan={4 + dataValues.length}>
<GoAButtonGroup alignment="center">
<GoAButton
type="tertiary"
disabled={busy.loading}
onClick={() => dispatch(findForms({ definitionId, criteria, after: next }))}
>
Load more
</GoAButton>
</GoAButtonGroup>
</td>
</tr>
)}
<RowLoadMore
columns={4 + dataValues.length}
next={next}
loading={busy.loading}
onLoadMore={(after) => dispatch(findForms({ definitionId, criteria, after }))}
/>
</tbody>
</GoATable>
</ContentContainer>
Expand Down
4 changes: 2 additions & 2 deletions apps/form-app/src/app/containers/Form.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -62,8 +62,8 @@ const FormComponent: FunctionComponent<FormProps> = ({ className }) => {
<Container vs={1} hs={1} key={formId}>
{form && !fileBusy.loading && (
<>
{form.status === 'submitted' && <SubmittedForm definition={definition} form={form} data={data} />}
{form.status === 'draft' && (
{form.status === 'Submitted' && <SubmittedForm definition={definition} form={form} data={data} />}
{form.status === 'Draft' && (
<DraftFormWrapper
definition={definition}
form={form}
Expand Down
22 changes: 14 additions & 8 deletions apps/form-app/src/app/containers/FormDefinition.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,11 @@ import { Navigate, Route, Routes, useLocation, useNavigate, useParams } from 're
import {
AppDispatch,
definitionSelector,
findUserForm,
findUserForms,
Form as FormObject,
FormDefinition as FormDefinitionObject,
selectedDefinition,
userFormSelector,
defaultUserFormSelector,
tenantSelector,
busySelector,
createForm,
Expand All @@ -22,6 +22,7 @@ import { Form } from './Form';
import { ContinueApplication } from '../components/ContinueApplication';
import { StartApplication } from '../components/StartApplication';
import { FormNotAvailable } from '../components/FormNoAvailable';
import { Forms } from './Forms';

interface FormDefinitionStart {
definition: FormDefinitionObject;
Expand All @@ -35,20 +36,16 @@ export const FormDefinitionStart: FunctionComponent<FormDefinitionStart> = ({ de
const urlParams = new URLSearchParams(location.search);
const AUTO_CREATE_PARAM = 'autoCreate';

const { initialized, form } = useSelector(userFormSelector);
const { initialized, form, empty } = useSelector(defaultUserFormSelector);
const busy = useSelector(busySelector);

useEffect(() => {
dispatch(findUserForm(definition.id));
}, [dispatch, definition]);

return definition.anonymousApply ? (
<Navigate to="draft" />
) : (
initialized &&
(form?.id ? (
<ContinueApplication definition={definition} form={form} onContinue={() => navigate(`${form.id}`)} />
) : (
) : empty ? (
<StartApplication
definition={definition}
autoCreate={urlParams.has(AUTO_CREATE_PARAM, 'true')}
Expand All @@ -61,6 +58,8 @@ export const FormDefinitionStart: FunctionComponent<FormDefinitionStart> = ({ de
}
}}
/>
) : (
<Navigate to="forms" />
))
);
};
Expand All @@ -81,6 +80,12 @@ export const FormDefinition: FunctionComponent = () => {
}
}, [dispatch, definitionId, tenant]);

useEffect(() => {
if (definition && user) {
dispatch(findUserForms({ definitionId: definition.id }));
}
}, [dispatch, definition, user]);

// Definition can be available even if there is no signed in user.
// If definition is not available, then show the sign-in option as user might have access if they sign in.
return (
Expand All @@ -91,6 +96,7 @@ export const FormDefinition: FunctionComponent = () => {
<ScheduledIntake definition={definition}>
<Routes>
<Route path="/draft" element={<AnonymousForm />} />
<Route path="/forms" element={<Forms definition={definition} />} />{' '}
<Route path="/:formId" element={<Form />} />
<Route path="/" element={<FormDefinitionStart definition={definition} />} />
<Route path="*" element={<Navigate to="/" />} />
Expand Down
97 changes: 97 additions & 0 deletions apps/form-app/src/app/containers/Forms.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
import { GoAButton, GoAButtonGroup, GoASkeleton, GoATable } from '@abgov/react-components-new';
import { Band, Container, RowLoadMore, RowSkeleton } from '@core-services/app-common';
import { useDispatch, useSelector } from 'react-redux';
import { FunctionComponent } from 'react';
import { useNavigate } from 'react-router-dom';
import {
AppDispatch,
busySelector,
canCreateDraftSelector,
createForm,
defaultUserFormSelector,
findUserForms,
FormDefinition,
formsSelector,
FormStatus,
} from '../state';

interface FormsProps {
definition: FormDefinition;
}

export const Forms: FunctionComponent<FormsProps> = ({ definition }) => {
const dispatch = useDispatch<AppDispatch>();
const navigate = useNavigate();

const busy = useSelector(busySelector);
const { forms, next } = useSelector(formsSelector);
const { form: defaultForm } = useSelector(defaultUserFormSelector);
const canCreateDraft = useSelector(canCreateDraftSelector);

return (
<div>
<Band title={`Your ${definition.name} forms`}>
Continue working on existing draft forms, or view forms you submitted in the past.
</Band>
<Container vs={3} hs={1}>
<GoAButtonGroup alignment="end">
{canCreateDraft && (
<GoAButton
type={defaultForm?.status === FormStatus.draft ? 'secondary' : 'primary'}
onClick={async () => {
const form = await dispatch(createForm(definition.id)).unwrap();
if (form?.id) {
navigate(`../${form.id}`);
}
}}
>
Start new draft
</GoAButton>
)}
</GoAButtonGroup>
<GoATable width="100%">
<thead>
<tr>
<th>Created on</th>
<th>Submitted on</th>
<th>Status</th>
<th>Actions</th>
</tr>
</thead>
<tbody>
{forms.map((form) => (
<tr key={form.id}>
<td>{form.created.toFormat('LLLL dd, yyyy')}</td>
<td>{form.submitted?.toFormat('LLLL dd, yyyy')}</td>
<td>{form.status}</td>
<td>
<GoAButtonGroup alignment="end">
{form.status === FormStatus.draft ? (
<GoAButton
type={form?.id === defaultForm?.id ? 'primary' : 'secondary'}
onClick={() => navigate(`../${form.id}`)}
>
Continue draft
</GoAButton>
) : (
<GoAButton type="secondary" onClick={() => navigate(`../${form.id}`)}>
View submitted
</GoAButton>
)}
</GoAButtonGroup>
</td>
</tr>
))}
<RowSkeleton columns={4} show={busy.loading} />
<RowLoadMore
columns={4}
next={next}
loading={busy.loading}
onLoadMore={(after) => dispatch(findUserForms({ definitionId: definition.id, after }))}
/>
</tbody>
</GoATable>
</Container>
</div>
);
};
Loading

0 comments on commit 9d704bd

Please sign in to comment.