Skip to content

Commit

Permalink
#163: Collaborator Validation For Add Modal (#182)
Browse files Browse the repository at this point in the history
* feat(add-collab): add-collab modal init

* fix(i18n): add translation for button

* feat(i18n): AddCollaboratorModal temp schema and i18n translations

* feat(validation): add collab validation

* fix(form): reset form when modal closed

- rename fields to schema from package

* chore(organize): remove comments and organize code
  • Loading branch information
JamesTLopez authored Feb 5, 2025
1 parent b170d3a commit 10cdbd9
Show file tree
Hide file tree
Showing 4 changed files with 177 additions and 3 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,143 @@
/*
* Copyright (c) 2025 The Ontario Institute for Cancer Research. All rights reserved
*
* This program and the accompanying materials are made available under the terms of
* the GNU Affero General Public License v3.0. You should have received a copy of the
* GNU Affero General Public License along with this program.
* If not, see <http://www.gnu.org/licenses/>.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY
* EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT
* SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
* TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
* OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER
* IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
* ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/

import { ApplicationOutletContext } from '@/global/types';
import { zodResolver } from '@hookform/resolvers/zod';
import { type CollaboratorsSchemaType, collaboratorsSchema } from '@pcgl-daco/validation';
import { Button, Col, Flex, Form, Modal, Row, Typography } from 'antd';
import { createSchemaFieldRule } from 'antd-zod';
import { useForm } from 'react-hook-form';
import { useTranslation } from 'react-i18next';
import { useOutletContext } from 'react-router';

import InputBox from '@/components/pages/application/form-components/InputBox';

const { Text } = Typography;

type AddCollaboratorModalProps = {
isOpen: boolean;
setIsOpen: (val: boolean) => void;
};

const rule = createSchemaFieldRule(collaboratorsSchema);

const AddCollaboratorModal = ({ isOpen, setIsOpen }: AddCollaboratorModalProps) => {
const { t: translate } = useTranslation();
const { isEditMode } = useOutletContext<ApplicationOutletContext>();

const { control } = useForm<CollaboratorsSchemaType>({
resolver: zodResolver(collaboratorsSchema),
});

return (
<Modal
title={translate('collab-section.addModalTitle')}
okText={translate('button.addCollab')}
cancelText={translate('button.cancel')}
width={'100%'}
style={{ top: '20%', maxWidth: '800px', paddingInline: 10 }}
open={isOpen}
onCancel={() => setIsOpen(false)}
footer={[]}
destroyOnClose
>
<Flex style={{ height: '100%', marginTop: 20 }} vertical gap={'middle'}>
<Text>{translate('collab-section.addModalDescription')}</Text>
<Form layout="vertical" clearOnDestroy>
<Flex vertical>
<Row gutter={26}>
<Col xs={{ flex: '100%' }} md={{ flex: '100%' }} lg={{ flex: '50%' }}>
<InputBox
label={translate('collab-section.form.firstName')}
name="collabFirstName"
control={control}
rule={rule}
required
disabled={!isEditMode}
/>
</Col>
<Col xs={{ flex: '100%' }} md={{ flex: '100%' }} lg={{ flex: '50%' }}>
<InputBox
label={translate('collab-section.form.middleName')}
name="collabMiddleName"
control={control}
rule={rule}
disabled={!isEditMode}
/>
</Col>
</Row>
<Row gutter={26}>
<Col xs={{ flex: '100%' }} md={{ flex: '100%' }} lg={{ flex: '50%' }}>
<InputBox
label={translate('collab-section.form.lastName')}
name="collabLastName"
control={control}
rule={rule}
required
disabled={!isEditMode}
/>
</Col>
<Col xs={{ flex: '100%' }} md={{ flex: '100%' }} lg={{ flex: '50%' }}>
<InputBox
label={translate('collab-section.form.suffix')}
name="collabSuffix"
control={control}
rule={rule}
disabled={!isEditMode}
/>
</Col>
</Row>
<Row gutter={26}>
<Col xs={{ flex: '100%' }} md={{ flex: '100%' }} lg={{ flex: '50%' }}>
<InputBox
label={translate('collab-section.form.primaryEmail')}
name="collabPrimaryEmail"
control={control}
rule={rule}
required
disabled={!isEditMode}
/>
</Col>
<Col xs={{ flex: '100%' }} md={{ flex: '100%' }} lg={{ flex: '50%' }}>
<InputBox
label={translate('collab-section.form.positionTitle')}
name="collabPositionTitle"
control={control}
rule={rule}
required
disabled={!isEditMode}
/>
</Col>
</Row>
</Flex>
<Flex align="center" justify="flex-end" gap={'middle'}>
<Button htmlType="button" onClick={() => setIsOpen(false)}>
{translate('button.cancel')}
</Button>
<Button type="primary" htmlType="submit">
{translate('button.addCollab')}
</Button>
</Flex>
</Form>
</Flex>
</Modal>
);
};

export default AddCollaboratorModal;
12 changes: 11 additions & 1 deletion apps/ui/src/i18n/locale/en/enSection.json
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,16 @@
"collab-section": {
"title": "Collaborators",
"description1": "Please include the names of all investigators, collaborators, research staff (including post-docs) and students (including graduate students), who will have access to the PCGL Controlled Data in order to work on the Research Summary as outlined in Section D of this application",
"note": "* Please note: co-investigators, collaborators or students at other institutions should not be included in this list. They will have to submit a separate application for access to controlled data."
"note": "* Please note: co-investigators, collaborators or students at other institutions should not be included in this list. They will have to submit a separate application for access to controlled data.",
"addModalTitle": "Add A Collaborator",
"addModalDescription": "Please fill out the following information for the collaborator, including a valid institutional email address that they will use to log in to PCGL and will be the email address associated with PCGL Controlled Data access.",
"form": {
"firstName": "First Name:",
"middleName": "Middle Name:",
"lastName": "Last Name:",
"suffix": "Suffix:",
"primaryEmail": "Institutional Email:",
"positionTitle": "Position Title:"
}
}
}
14 changes: 13 additions & 1 deletion apps/ui/src/pages/applications/sections/collaborators.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -19,10 +19,12 @@

import { PlusCircleOutlined } from '@ant-design/icons';
import { Button, Col, Flex, Row, Space, Table, TableProps, theme } from 'antd';
import { useState } from 'react';
import { useTranslation } from 'react-i18next';
import { useOutletContext } from 'react-router';

import SectionWrapper from '@/components/layouts/SectionWrapper';
import AddCollaboratorModal from '@/components/pages/application/modals/AddCollaboratorModal';
import SectionContent from '@/components/pages/application/SectionContent';
import SectionFooter from '@/components/pages/application/SectionFooter';
import SectionTitle from '@/components/pages/application/SectionTitle';
Expand All @@ -37,11 +39,15 @@ interface CollabTableData {
institutionalEmail: string;
title: string;
}

const Collaborators = () => {
const { t: translate } = useTranslation();
const { isEditMode } = useOutletContext<ApplicationOutletContext>();
const { token } = useToken();

// MODAL STATES
const [openAddCollaboratorModal, setOpenAddCollaboratorModal] = useState(false);

const columns: TableProps<CollabTableData>['columns'] = [
{
key: 'firstName',
Expand Down Expand Up @@ -104,7 +110,12 @@ const Collaborators = () => {
/>
<Row justify={'end'}>
<Col style={{ paddingTop: token.paddingLG }}>
<Button style={{ borderRadius: 100 }} type="primary" disabled={!isEditMode}>
<Button
onClick={() => setOpenAddCollaboratorModal(true)}
style={{ borderRadius: 100 }}
type="primary"
disabled={!isEditMode}
>
<Flex align="center" justify="center" gap={'small'}>
<PlusCircleOutlined />
{translate('button.addCollab')}
Expand All @@ -113,6 +124,7 @@ const Collaborators = () => {
</Col>
</Row>
</SectionContent>
<AddCollaboratorModal isOpen={openAddCollaboratorModal} setIsOpen={setOpenAddCollaboratorModal} />
<SectionFooter currentRoute="collaborators" isEditMode={isEditMode} />
</>
</SectionWrapper>
Expand Down
11 changes: 10 additions & 1 deletion packages/validation/src/schemas.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,10 +21,10 @@ import { z } from 'zod';
import { ConciseWordCountString, EmptyOrOptionalString, NonEmptyString, OptionalURLString } from './common/strings.js';
import { ONLY_ALPHANUMERIC } from './utils/regex.js';

// Applicant Information Form Section
export type ApplicantInformationSchemaType = z.infer<typeof applicantInformationSchema>;
export type InstitutionalRepSchemaType = z.infer<typeof institutionalRepSchema>;
export type ProjectInformationSchemaType = z.infer<typeof projectInformationSchema>;
export type CollaboratorsSchemaType = z.infer<typeof collaboratorsSchema>;

export const applicantInformationSchema = z.object({
applicantTitle: NonEmptyString,
Expand All @@ -44,6 +44,15 @@ export const applicantInformationSchema = z.object({
applicantInstituteBuilding: EmptyOrOptionalString,
});

export const collaboratorsSchema = z.object({
collabFirstName: NonEmptyString,
collabMiddleName: NonEmptyString,
collabLastName: NonEmptyString,
collabSuffix: NonEmptyString,
collabPrimaryEmail: NonEmptyString.email(),
collabPositionTitle: NonEmptyString,
});

export const institutionalRepSchema = z.object({
institutionalTitle: NonEmptyString,
institutionalFirstName: NonEmptyString,
Expand Down

0 comments on commit 10cdbd9

Please sign in to comment.