Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(form-app): support multiple user forms per definition #4050

Merged
merged 1 commit into from
Jan 31, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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