feat: add create and edit forms functionality
This commit is contained in:
@@ -1,7 +1,7 @@
|
|||||||
import React, { memo, ReactNode, useCallback, useRef, useState } from "react";
|
import React, { memo, ReactNode, useCallback, useRef, useState } from 'react';
|
||||||
|
|
||||||
import { areEqualShallow } from "Utils/equalityChecks";
|
import { areEqualShallow } from 'Utils/equalityChecks';
|
||||||
import { useDebounce } from "Hooks/useDebounce";
|
import { useDebounce } from 'Hooks/useDebounce';
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
id?: string;
|
id?: string;
|
||||||
@@ -38,16 +38,16 @@ const Form = ({ id, className, noValidate = true, submitButton, onSubmit, childr
|
|||||||
const onSubmitLocal = useDebounce((e: any) => {
|
const onSubmitLocal = useDebounce((e: any) => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
|
|
||||||
formRef.current.classList.add("was-validated");
|
formRef.current.classList.add('was-validated');
|
||||||
|
|
||||||
let value = "";
|
let value = '';
|
||||||
switch (e.target.type) {
|
switch (e.target.type) {
|
||||||
case "checkbox":
|
case 'checkbox':
|
||||||
value = e.target.checked;
|
value = e.target.checked;
|
||||||
break;
|
break;
|
||||||
case "text":
|
case 'text':
|
||||||
case "tel":
|
case 'tel':
|
||||||
case "email":
|
case 'email':
|
||||||
value = e.target.value.trim();
|
value = e.target.value.trim();
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
@@ -57,7 +57,7 @@ const Form = ({ id, className, noValidate = true, submitButton, onSubmit, childr
|
|||||||
// This will handle an empty input
|
// This will handle an empty input
|
||||||
if (value?.length === 0) {
|
if (value?.length === 0) {
|
||||||
validate({ [e.target.name]: e.target.value, isValid: true });
|
validate({ [e.target.name]: e.target.value, isValid: true });
|
||||||
formRef.current.classList.remove("was-validated");
|
formRef.current.classList.remove('was-validated');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -99,7 +99,7 @@ const Form = ({ id, className, noValidate = true, submitButton, onSubmit, childr
|
|||||||
}}
|
}}
|
||||||
onSubmit={(submitButton && onSubmitLocal) || submitWhenNoSubmitButton}
|
onSubmit={(submitButton && onSubmitLocal) || submitWhenNoSubmitButton}
|
||||||
id={id}
|
id={id}
|
||||||
className={`requires-validation ${className || ""}`}
|
className={`requires-validation ${className || ''}`}
|
||||||
noValidate={noValidate}
|
noValidate={noValidate}
|
||||||
>
|
>
|
||||||
{children}
|
{children}
|
||||||
|
|||||||
@@ -0,0 +1,144 @@
|
|||||||
|
import React, { memo, RefObject } from 'react';
|
||||||
|
import { Modal } from 'Components/Modal';
|
||||||
|
import { areEqual } from 'Utils/equalityChecks';
|
||||||
|
import { ValidateBackGround } from 'Components/Validation';
|
||||||
|
import { PurpleButton } from 'Components/Button';
|
||||||
|
import { Label } from 'Components/Label';
|
||||||
|
import { TextBox } from 'Components/TextBox';
|
||||||
|
import { CheckBox } from 'Components/CheckBox';
|
||||||
|
import { TextArea } from 'Components/TextArea';
|
||||||
|
import { ContractFormsSuccessToast } from '../ContractFormsSuccessToast';
|
||||||
|
|
||||||
|
import classes from './contractFormsModal.module.css';
|
||||||
|
|
||||||
|
const templateTags = [
|
||||||
|
'~~~name~~~',
|
||||||
|
'~~~project~~~',
|
||||||
|
'~~~job_no~~~',
|
||||||
|
'~~~company~~~',
|
||||||
|
'~~~current_date~~~',
|
||||||
|
'~~~date_of_loss~~~',
|
||||||
|
'~~~company_address~~~',
|
||||||
|
'~~~policy_holder_name~~~',
|
||||||
|
'~~~policy_number~~~',
|
||||||
|
'~~~claim_number~~~',
|
||||||
|
'~~~input~~~',
|
||||||
|
'~~~checkbox~~~',
|
||||||
|
'~~~company_logo~~~',
|
||||||
|
];
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
isOpen: boolean;
|
||||||
|
formData: any;
|
||||||
|
formErrors: any;
|
||||||
|
title: string;
|
||||||
|
submitText: string;
|
||||||
|
showToast: boolean;
|
||||||
|
toastMessage: string;
|
||||||
|
onFormSubmit: (e: any) => void;
|
||||||
|
onClickClose: (e: any) => void;
|
||||||
|
handleChange: (e: any) => void;
|
||||||
|
textAreaRef: RefObject<HTMLTextAreaElement>;
|
||||||
|
handleTagClick: (tag: string) => void;
|
||||||
|
submitting: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
const ContractFormsModal = ({
|
||||||
|
isOpen,
|
||||||
|
title,
|
||||||
|
submitText,
|
||||||
|
formData,
|
||||||
|
formErrors,
|
||||||
|
showToast,
|
||||||
|
toastMessage,
|
||||||
|
onFormSubmit,
|
||||||
|
onClickClose,
|
||||||
|
handleChange,
|
||||||
|
textAreaRef,
|
||||||
|
handleTagClick,
|
||||||
|
submitting,
|
||||||
|
}: Props) => (
|
||||||
|
<Modal
|
||||||
|
id="contractsFormModal"
|
||||||
|
classes={classes}
|
||||||
|
title={title}
|
||||||
|
isOpen={isOpen}
|
||||||
|
leftHeaderIcon="projects"
|
||||||
|
modalHeader
|
||||||
|
modalCloseClick={onClickClose}
|
||||||
|
toast={<ContractFormsSuccessToast showToast={showToast} message={toastMessage} />}
|
||||||
|
>
|
||||||
|
<form className={classes.form} onSubmit={onFormSubmit}>
|
||||||
|
<ValidateBackGround isValid={!formErrors?.name.length}>
|
||||||
|
<Label ariaLabel="Form Name" className={classes.formNameLabel} htmlFor="formName">
|
||||||
|
Form Name
|
||||||
|
</Label>
|
||||||
|
<TextBox
|
||||||
|
name="name"
|
||||||
|
id="name"
|
||||||
|
type="text"
|
||||||
|
className={`${classes.validateField} ${formErrors?.name.length ? classes.invalidField : classes.validField} ${
|
||||||
|
formErrors?.name.length ? 'is-invalid' : ''
|
||||||
|
}`}
|
||||||
|
placeholder="Authorization form"
|
||||||
|
ariaLabel="Form Name"
|
||||||
|
value={formData.name}
|
||||||
|
onChange={handleChange}
|
||||||
|
/>
|
||||||
|
<div className={`${classes.invalidFieldFeedback} invalid-feedback`}>{formErrors?.name?.[0]}</div>
|
||||||
|
</ValidateBackGround>
|
||||||
|
|
||||||
|
<div className={classes.requireSignatureContainer}>
|
||||||
|
<Label ariaLabel="Require signature" className={classes.requireSignatureLabel} htmlFor="requireSignature">
|
||||||
|
Require Signature
|
||||||
|
</Label>
|
||||||
|
<CheckBox
|
||||||
|
checked={formData.requireSignature}
|
||||||
|
name="requireSignature"
|
||||||
|
id="requireSignature"
|
||||||
|
onChange={handleChange}
|
||||||
|
className={classes.requireSignatureCheckbox}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className={classes.contractTemplateContainer}>
|
||||||
|
<Label ariaLabel="Contract Template" htmlFor="template" className="text-right pt-2">
|
||||||
|
Contract Template
|
||||||
|
</Label>
|
||||||
|
<div className={classes.contractTemplateFlex}>
|
||||||
|
<div className={classes.templateTags}>
|
||||||
|
{templateTags.map((tag) => (
|
||||||
|
<button key={tag} type="button" onKeyDown={() => {}} className="" onClick={() => handleTagClick(tag)}>
|
||||||
|
{tag}
|
||||||
|
</button>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
<div className={classes.textAreaWrapper}>
|
||||||
|
{formErrors?.template?.[0] && <p className={classes.textAreaWarning}>{formErrors?.template?.[0]}</p>}
|
||||||
|
<TextArea
|
||||||
|
id="template"
|
||||||
|
minRows={15}
|
||||||
|
name="template"
|
||||||
|
ariaLabel="contract template"
|
||||||
|
ref={textAreaRef}
|
||||||
|
value={formData.template}
|
||||||
|
onChange={handleChange}
|
||||||
|
className={classes.templateTextarea}
|
||||||
|
placeholder="Type your contract template here or click on tags from the left panel..."
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className={classes.footer}>
|
||||||
|
<PurpleButton type="submit" className={classes.submitButton} disabled={submitting} outlined>
|
||||||
|
{!submitting ? submitText : '...'}
|
||||||
|
</PurpleButton>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</Modal>
|
||||||
|
);
|
||||||
|
|
||||||
|
const ContractFormsModalMemo = memo(ContractFormsModal, areEqual);
|
||||||
|
|
||||||
|
export { ContractFormsModalMemo as ContractFormsModal };
|
||||||
@@ -0,0 +1,85 @@
|
|||||||
|
.modalDialog {
|
||||||
|
max-width: 1200px;
|
||||||
|
/* height: 80%; */
|
||||||
|
}
|
||||||
|
.form {
|
||||||
|
gap: 24px;
|
||||||
|
height: 100%;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
.requireSignatureContainer {
|
||||||
|
display: flex;
|
||||||
|
gap: 10px;
|
||||||
|
align-items: start;
|
||||||
|
}
|
||||||
|
.requireSignatureContainer label {
|
||||||
|
padding: 0px;
|
||||||
|
margin-top: -6px;
|
||||||
|
}
|
||||||
|
.contractTemplateContainer {
|
||||||
|
flex: 1 1 0px;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 8px;
|
||||||
|
margin-top: -10px;
|
||||||
|
}
|
||||||
|
.contractTemplateFlex {
|
||||||
|
display: flex;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
.templateTags {
|
||||||
|
border: 1px solid black;
|
||||||
|
border-radius: 8px;
|
||||||
|
padding: 10px 4px;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 4px;
|
||||||
|
width: fit-content;
|
||||||
|
}
|
||||||
|
.templateTags button {
|
||||||
|
background: none;
|
||||||
|
border: none;
|
||||||
|
cursor: pointer;
|
||||||
|
text-align: left;
|
||||||
|
padding: 2px 0;
|
||||||
|
}
|
||||||
|
.templateTags button:hover {
|
||||||
|
background-color: aliceblue;
|
||||||
|
}
|
||||||
|
.textAreaWrapper {
|
||||||
|
flex: 1 1 0px;
|
||||||
|
max-height: 400px;
|
||||||
|
height: fit-content;
|
||||||
|
overflow-y: auto;
|
||||||
|
border: 1px solid rgba(102, 98, 98, 0.695);
|
||||||
|
border-radius: 8px;
|
||||||
|
}
|
||||||
|
.textAreaWarning {
|
||||||
|
background: rgba(222, 84, 84, 0.288);
|
||||||
|
width: 100%;
|
||||||
|
padding: 3px;
|
||||||
|
font-size: 12px;
|
||||||
|
color: rgba(188, 14, 14, 0.911);
|
||||||
|
}
|
||||||
|
.textAreaWrapper:focus-within {
|
||||||
|
outline: 4px solid rgba(98, 172, 238, 0.866);
|
||||||
|
outline-offset: 2px;
|
||||||
|
transition: all;
|
||||||
|
}
|
||||||
|
.templateTextarea {
|
||||||
|
width: 100%;
|
||||||
|
resize: none;
|
||||||
|
border: none;
|
||||||
|
}
|
||||||
|
.footer {
|
||||||
|
margin-top: 10px;
|
||||||
|
padding-top: 10px;
|
||||||
|
}
|
||||||
|
.submitButton {
|
||||||
|
width: fit-content;
|
||||||
|
margin: 0 auto;
|
||||||
|
}
|
||||||
|
.submitButton:disabled {
|
||||||
|
opacity: 0.6;
|
||||||
|
}
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
export { ContractFormsModal } from './ContractFormsModal';
|
||||||
@@ -0,0 +1,35 @@
|
|||||||
|
import React, { memo } from 'react';
|
||||||
|
|
||||||
|
import { areEqual } from 'Utils/equalityChecks';
|
||||||
|
import { CheckedMarkSvg } from 'Components/Icons/CheckedMark';
|
||||||
|
|
||||||
|
import classes from './contractFormsSuccessToast.module.css';
|
||||||
|
|
||||||
|
export interface Props {
|
||||||
|
showToast: boolean;
|
||||||
|
message: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
const ContractFormsSuccessToast = ({ showToast = false, message }: Props) => (
|
||||||
|
<div
|
||||||
|
className={`toast fade d-flex align-items-center position-absolute border-0 bottom-0 ${
|
||||||
|
showToast ? 'show' : 'hide'
|
||||||
|
} ${classes.toastBase} ${classes.toastSuccess}`}
|
||||||
|
role="alert"
|
||||||
|
aria-live="assertive"
|
||||||
|
aria-atomic="true"
|
||||||
|
>
|
||||||
|
<div className={`toast-body ${classes['toast-body-override']} ${classes.toastText}`}>
|
||||||
|
{message}
|
||||||
|
<span className={`${classes.toastIcon}`}>
|
||||||
|
<CheckedMarkSvg />
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
|
||||||
|
ContractFormsSuccessToast.defaultProps = {};
|
||||||
|
|
||||||
|
const ContractFormsSuccessToastMemo = memo(ContractFormsSuccessToast, areEqual);
|
||||||
|
|
||||||
|
export { ContractFormsSuccessToastMemo as ContractFormsSuccessToast };
|
||||||
+78
@@ -0,0 +1,78 @@
|
|||||||
|
.toastBase {
|
||||||
|
height: 40px !important;
|
||||||
|
width: 100% !important;
|
||||||
|
z-index: 1000;
|
||||||
|
box-shadow: none;
|
||||||
|
border-radius: 0;
|
||||||
|
padding-left: 0.5rem !important;
|
||||||
|
padding-right: 0.5rem !important;
|
||||||
|
padding-top: 1rem !important;
|
||||||
|
padding-bottom: 1rem !important;
|
||||||
|
transition: opacity 0.15s linear;
|
||||||
|
position: absolute;
|
||||||
|
right: 0;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
.toast-body-override {
|
||||||
|
width: 95%;
|
||||||
|
padding: unset ip !important;
|
||||||
|
}
|
||||||
|
.toastCloseButtonContainer {
|
||||||
|
display: flex;
|
||||||
|
justify-content: flex-end;
|
||||||
|
width: 10%;
|
||||||
|
}
|
||||||
|
.toastCloseButton {
|
||||||
|
border-radius: 50% !important;
|
||||||
|
color: #5b476b !important;
|
||||||
|
background-color: #fff !important;
|
||||||
|
padding: 0.25rem !important;
|
||||||
|
opacity: unset !important;
|
||||||
|
/* This is the actual svg pulled from bootstrap. not the 0.5em beside center on the 2nd line. This is what has changed */
|
||||||
|
background: transparent
|
||||||
|
url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='%23000'%3e%3cpath d='M.293.293a1 1 0 011.414 0L8 6.586 14.293.293a1 1 0 111.414 1.414L9.414 8l6.293 6.293a1 1 0 01-1.414 1.414L8 9.414l-6.293 6.293a1 1 0 01-1.414-1.414L6.586 8 .293 1.707a1 1 0 010-1.414z'/%3e%3c/svg%3e")
|
||||||
|
center/0.5em auto no-repeat;
|
||||||
|
}
|
||||||
|
.toastIcon {
|
||||||
|
padding-left: 10px;
|
||||||
|
}
|
||||||
|
.toastCloseButton:hover {
|
||||||
|
color: #000;
|
||||||
|
text-decoration: none;
|
||||||
|
opacity: unset !important;
|
||||||
|
}
|
||||||
|
.toastText {
|
||||||
|
font-family: IBM Plex Sans !important;
|
||||||
|
font-style: normal !important;
|
||||||
|
font-weight: 600 !important;
|
||||||
|
font-size: 16px !important;
|
||||||
|
line-height: 24px !important;
|
||||||
|
color: #5b476b !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.toastSuccess {
|
||||||
|
background-color: #dcf5f0;
|
||||||
|
border-bottom: 1px solid #40c9ae !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.toastWarning {
|
||||||
|
background-color: #fff0f0;
|
||||||
|
border-bottom: 1px solid #e82828;
|
||||||
|
}
|
||||||
|
|
||||||
|
.toastCloseIcon {
|
||||||
|
position: absolute;
|
||||||
|
right: 22px;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 768px) {
|
||||||
|
.toastBase {
|
||||||
|
width: 100%;
|
||||||
|
/* height: auto; */
|
||||||
|
padding: 12px 66px 12px 22px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.toastText {
|
||||||
|
font-size: 14px;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
export { ContractFormsSuccessToast } from './ContractFormsSuccessToast';
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
import { FormTemplateResponse } from 'Containers/Forms/Models';
|
import { FormTemplate, FormTemplateResponse } from 'Containers/Forms/Models';
|
||||||
import React, { memo } from 'react';
|
import React, { memo } from 'react';
|
||||||
import { TabContent } from 'Components/Tabs';
|
import { TabContent } from 'Components/Tabs';
|
||||||
import { areEqual } from 'Utils/equalityChecks';
|
import { areEqual } from 'Utils/equalityChecks';
|
||||||
@@ -11,8 +11,8 @@ import classes from './contractForms.module.css';
|
|||||||
interface Props {
|
interface Props {
|
||||||
forms: FormTemplateResponse;
|
forms: FormTemplateResponse;
|
||||||
fetching: boolean;
|
fetching: boolean;
|
||||||
onClickRow?: (e: any) => void;
|
onClickRow?: (form: FormTemplate) => void;
|
||||||
onAdd: () => void;
|
onAdd: (e: any) => void;
|
||||||
onDelete: (id: number) => void;
|
onDelete: (id: number) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -26,7 +26,7 @@ const ContractForms = ({ forms, onClickRow, fetching, onAdd, onDelete }: Props)
|
|||||||
</PurpleButton>
|
</PurpleButton>
|
||||||
</div>
|
</div>
|
||||||
{fetching && <Spinner loading />}
|
{fetching && <Spinner loading />}
|
||||||
{!fetching && <FormsList forms={forms} onClickRow={onClickRow} onDelete={onDelete} />}
|
{!fetching && <FormsList forms={forms} onClickRow={onClickRow} onDelete={onDelete} onAdd={onAdd} />}
|
||||||
</div>
|
</div>
|
||||||
</TabContent>
|
</TabContent>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -16,6 +16,7 @@ const DeleteFormModal = ({ id, isOpen, modalCloseClick, onDelete }: Props) => (
|
|||||||
<div>
|
<div>
|
||||||
<Modal
|
<Modal
|
||||||
id={id && id.toString()}
|
id={id && id.toString()}
|
||||||
|
leftHeaderIcon="projects"
|
||||||
classes={classes}
|
classes={classes}
|
||||||
title="Delete Contract Form?"
|
title="Delete Contract Form?"
|
||||||
isOpen={isOpen}
|
isOpen={isOpen}
|
||||||
|
|||||||
@@ -12,10 +12,17 @@ import classes from './formsList.module.css';
|
|||||||
interface Props {
|
interface Props {
|
||||||
forms: FormTemplateResponse;
|
forms: FormTemplateResponse;
|
||||||
onDelete: (id: number) => void;
|
onDelete: (id: number) => void;
|
||||||
onClickRow?: (e: any) => void;
|
onAdd: (e: any) => void;
|
||||||
|
onClickRow?: (form: FormTemplate) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
const FormsList = ({ forms, onClickRow, onDelete }: Props) => (
|
const FormsList = ({ forms, onClickRow, onDelete, onAdd }: Props) => {
|
||||||
|
const onRowClick = (e: any, form: any) => {
|
||||||
|
e.preventDefault();
|
||||||
|
onClickRow(form);
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
<>
|
<>
|
||||||
{forms?.data?.length > 0 ? (
|
{forms?.data?.length > 0 ? (
|
||||||
<Table className={`table ${classes.formListWrapper}`}>
|
<Table className={`table ${classes.formListWrapper}`}>
|
||||||
@@ -27,20 +34,20 @@ const FormsList = ({ forms, onClickRow, onDelete }: Props) => (
|
|||||||
</TableRow>
|
</TableRow>
|
||||||
</TableHeader>
|
</TableHeader>
|
||||||
<TableBody>
|
<TableBody>
|
||||||
{forms.data.map(({ id, created_at: createdAt, name }: FormTemplate) => (
|
{forms.data.map((form: FormTemplate) => (
|
||||||
<TableRow key={id}>
|
<TableRow key={form.id}>
|
||||||
<TableColumn dataId={id} tdOnClick={onClickRow}>
|
<TableColumn dataId={form.id} tdOnClick={(e) => onRowClick(e, form)}>
|
||||||
<p>{name}</p>
|
<p>{form.name}</p>
|
||||||
</TableColumn>
|
</TableColumn>
|
||||||
<TableColumn dataId={id} tdOnClick={onClickRow} className={classes.columnContent}>
|
<TableColumn dataId={form.id} tdOnClick={(e) => onRowClick(e, form)} className={classes.columnContent}>
|
||||||
<p className={classes.numberAndDate}>{formatDate(createdAt, 'PP')}</p>
|
<p className={classes.numberAndDate}>{formatDate(form.created_at, 'PP')}</p>
|
||||||
</TableColumn>
|
</TableColumn>
|
||||||
<TableColumn>
|
<TableColumn>
|
||||||
<button
|
<button
|
||||||
className={classes.deleteBtn}
|
className={classes.deleteBtn}
|
||||||
onClick={(e) => {
|
onClick={(e) => {
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
onDelete(id);
|
onDelete(form.id);
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Icon type="trash" />
|
<Icon type="trash" />
|
||||||
@@ -51,10 +58,11 @@ const FormsList = ({ forms, onClickRow, onDelete }: Props) => (
|
|||||||
</TableBody>
|
</TableBody>
|
||||||
</Table>
|
</Table>
|
||||||
) : (
|
) : (
|
||||||
<NoFormsTable />
|
<NoFormsTable onAdd={onAdd} />
|
||||||
)}
|
)}
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
|
};
|
||||||
|
|
||||||
FormsList.defaultProps = {
|
FormsList.defaultProps = {
|
||||||
onClickRow: null,
|
onClickRow: null,
|
||||||
|
|||||||
@@ -1,11 +1,12 @@
|
|||||||
import React, { memo } from 'react';
|
import React, { memo } from 'react';
|
||||||
import { areEqual } from 'Utils/equalityChecks';
|
import { areEqual } from 'Utils/equalityChecks';
|
||||||
import { Table, TableHeader, TableRow, Th } from 'Components/Table';
|
import { Table, TableHeader, TableRow, Th } from 'Components/Table';
|
||||||
|
import { PurpleButton } from 'Components/Button';
|
||||||
import { Icon } from 'Components/Icons';
|
import { Icon } from 'Components/Icons';
|
||||||
|
|
||||||
import classes from './noFormsTable.module.css';
|
import classes from './noFormsTable.module.css';
|
||||||
|
|
||||||
const NoFormsTable = () => (
|
const NoFormsTable = ({ onAdd }: { onAdd: (e) => void }) => (
|
||||||
<div>
|
<div>
|
||||||
<Table className={`table ${classes.formListWrapper}`}>
|
<Table className={`table ${classes.formListWrapper}`}>
|
||||||
<TableHeader>
|
<TableHeader>
|
||||||
@@ -17,8 +18,11 @@ const NoFormsTable = () => (
|
|||||||
</TableHeader>
|
</TableHeader>
|
||||||
</Table>
|
</Table>
|
||||||
<div className={`d-flex justify-content-center align-items-center flex-column w-100 ${classes.noFormsContent}`}>
|
<div className={`d-flex justify-content-center align-items-center flex-column w-100 ${classes.noFormsContent}`}>
|
||||||
<p className={classes.noFormsText}>No forms yet. Create a new form.</p>
|
|
||||||
<Icon type="projects" />
|
<Icon type="projects" />
|
||||||
|
<p className={classes.noFormsText}>No forms yet. Create a new form.</p>
|
||||||
|
<PurpleButton className={classes.addButton} onClick={onAdd}>
|
||||||
|
Add +
|
||||||
|
</PurpleButton>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -48,3 +48,11 @@
|
|||||||
line-height: 30px;
|
line-height: 30px;
|
||||||
color: #b3abc6;
|
color: #b3abc6;
|
||||||
}
|
}
|
||||||
|
.addButton {
|
||||||
|
background: #9a00ff;
|
||||||
|
color: #fff;
|
||||||
|
font-weight: 500;
|
||||||
|
width: 160px;
|
||||||
|
border-radius: 25px;
|
||||||
|
margin-left: 2.4rem;
|
||||||
|
}
|
||||||
|
|||||||
@@ -1 +1,3 @@
|
|||||||
export { FormsList, ContractForms } from './FormTabs';
|
export { FormsList, ContractForms } from './FormTabs';
|
||||||
|
export { ContractFormsSuccessToast } from './ContractFormsSuccessToast';
|
||||||
|
export { ContractFormsModal } from './ContractFormsModal';
|
||||||
|
|||||||
@@ -0,0 +1,140 @@
|
|||||||
|
import React, { useEffect, useState, useRef, useCallback, memo } from 'react';
|
||||||
|
import { areEqual } from 'Utils/equalityChecks';
|
||||||
|
import { useDispatch, useSelector } from 'react-redux';
|
||||||
|
import { ContractFormsModal } from 'Components/Forms/ContractFormsModal';
|
||||||
|
import {
|
||||||
|
addingContractFormSelector,
|
||||||
|
companyIdSelector,
|
||||||
|
contractFormAddedSelector,
|
||||||
|
contractFormNameErrorSelector,
|
||||||
|
contractFormTemplateErrorSelector,
|
||||||
|
} from '../selectors';
|
||||||
|
import { addContractForm, listCompanyContractForms, setContractFormAdded, setContractFormErrors } from '../actions';
|
||||||
|
|
||||||
|
const initData = {
|
||||||
|
name: '',
|
||||||
|
template: '',
|
||||||
|
requireSignature: false,
|
||||||
|
};
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
isOpen: boolean;
|
||||||
|
onClose: () => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
const AddContractForms = ({ isOpen, onClose }: Props) => {
|
||||||
|
const dispatch = useDispatch();
|
||||||
|
const isAdding = useSelector(addingContractFormSelector, areEqual);
|
||||||
|
const isAdded = useSelector(contractFormAddedSelector, areEqual);
|
||||||
|
const companyId = useSelector(companyIdSelector, areEqual);
|
||||||
|
const nameErrors = useSelector(contractFormNameErrorSelector, areEqual);
|
||||||
|
const templateErrors = useSelector(contractFormTemplateErrorSelector, areEqual);
|
||||||
|
|
||||||
|
const [formData, setFormData] = useState({ ...initData });
|
||||||
|
const [toastMessage, setToastMessage] = useState('');
|
||||||
|
const [showToast, setShowToast] = useState(false);
|
||||||
|
const textAreaRef = useRef<HTMLTextAreaElement>(null);
|
||||||
|
|
||||||
|
const handleChange = useCallback((e: React.ChangeEvent<HTMLInputElement | HTMLTextAreaElement>) => {
|
||||||
|
const { name, value, type } = e.target;
|
||||||
|
const newValue = type === 'checkbox' ? (e.target as HTMLInputElement).checked : value;
|
||||||
|
setFormData((prev) => ({
|
||||||
|
...prev,
|
||||||
|
[name]: newValue,
|
||||||
|
}));
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const handleTagClick = useCallback(
|
||||||
|
(tag: string) => {
|
||||||
|
const textarea = textAreaRef.current;
|
||||||
|
if (textarea) {
|
||||||
|
const start = textarea.selectionStart;
|
||||||
|
const end = textarea.selectionEnd;
|
||||||
|
const newText = formData.template.substring(0, start) + tag + formData.template.substring(end);
|
||||||
|
setFormData((prev) => ({ ...prev, template: newText }));
|
||||||
|
setTimeout(() => {
|
||||||
|
textarea.selectionStart = start + tag.length;
|
||||||
|
textarea.selectionEnd = start + tag.length;
|
||||||
|
textarea.focus();
|
||||||
|
}, 0);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
[formData.template]
|
||||||
|
);
|
||||||
|
|
||||||
|
const onFormSubmit = useCallback(
|
||||||
|
(e) => {
|
||||||
|
e.preventDefault();
|
||||||
|
const errors: any = {};
|
||||||
|
if (!formData.name.trim()) errors.name = ['Form name is required'];
|
||||||
|
if (!formData.template.trim()) errors.template = ['Template is required'];
|
||||||
|
if (Object.keys(errors).length > 0) {
|
||||||
|
dispatch(setContractFormErrors(errors));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (companyId) {
|
||||||
|
const payload = {
|
||||||
|
company_id: companyId,
|
||||||
|
name: formData.name,
|
||||||
|
replacement_tags: Array.from(new Set(formData.template.match(/~~~.*?~~~/g) || [])).join(', '),
|
||||||
|
status: 'active',
|
||||||
|
template: formData.template,
|
||||||
|
has_signature: formData.requireSignature,
|
||||||
|
};
|
||||||
|
dispatch(addContractForm(payload));
|
||||||
|
}
|
||||||
|
},
|
||||||
|
[formData, companyId, dispatch]
|
||||||
|
);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (isAdded) {
|
||||||
|
setToastMessage('Contract Form Added');
|
||||||
|
setShowToast(true);
|
||||||
|
setFormData({ ...initData });
|
||||||
|
if (companyId) {
|
||||||
|
dispatch(listCompanyContractForms(companyId));
|
||||||
|
}
|
||||||
|
dispatch(setContractFormErrors({}));
|
||||||
|
setTimeout(() => {
|
||||||
|
setShowToast(false);
|
||||||
|
onClose();
|
||||||
|
dispatch(setContractFormAdded(false));
|
||||||
|
}, 1500);
|
||||||
|
}
|
||||||
|
}, [isAdded]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (!isOpen) {
|
||||||
|
setFormData({ ...initData });
|
||||||
|
dispatch(setContractFormErrors({}));
|
||||||
|
}
|
||||||
|
}, [isOpen, dispatch]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<ContractFormsModal
|
||||||
|
isOpen={isOpen}
|
||||||
|
title="Add Contract Form"
|
||||||
|
submitText="Add Contract"
|
||||||
|
formData={formData}
|
||||||
|
formErrors={{
|
||||||
|
name: nameErrors,
|
||||||
|
template: templateErrors,
|
||||||
|
}}
|
||||||
|
showToast={showToast}
|
||||||
|
toastMessage={toastMessage}
|
||||||
|
onFormSubmit={onFormSubmit}
|
||||||
|
onClickClose={(e) => {
|
||||||
|
e.preventDefault();
|
||||||
|
onClose();
|
||||||
|
}}
|
||||||
|
handleChange={handleChange}
|
||||||
|
textAreaRef={textAreaRef}
|
||||||
|
handleTagClick={handleTagClick}
|
||||||
|
submitting={isAdding}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const AddContractFormsMemo = memo(AddContractForms, areEqual);
|
||||||
|
export { AddContractFormsMemo as AddContractForms };
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
export { AddContractForms } from './AddContractForms';
|
||||||
@@ -0,0 +1,156 @@
|
|||||||
|
import React, { useEffect, useState, useRef, useCallback, memo } from 'react';
|
||||||
|
import { areEqual } from 'Utils/equalityChecks';
|
||||||
|
import { useDispatch, useSelector } from 'react-redux';
|
||||||
|
import { ContractFormsModal } from 'Components/Forms/ContractFormsModal';
|
||||||
|
import {
|
||||||
|
editingContractFormSelector,
|
||||||
|
contractFormEditedSelector,
|
||||||
|
contractFormNameErrorSelector,
|
||||||
|
contractFormTemplateErrorSelector,
|
||||||
|
companyIdSelector,
|
||||||
|
} from '../selectors';
|
||||||
|
import { editContractForm, setContractFormEdited, setContractFormErrors, listCompanyContractForms } from '../actions';
|
||||||
|
import { FormTemplate } from '../Models/FormsModel/FormModel';
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
isOpen: boolean;
|
||||||
|
onClose: () => void;
|
||||||
|
initData: FormTemplate;
|
||||||
|
}
|
||||||
|
|
||||||
|
const EditContractForms = ({ isOpen, onClose, initData }: Props) => {
|
||||||
|
const dispatch = useDispatch();
|
||||||
|
const isEditing = useSelector(editingContractFormSelector, areEqual);
|
||||||
|
const isEdited = useSelector(contractFormEditedSelector, areEqual);
|
||||||
|
const companyId = useSelector(companyIdSelector, areEqual);
|
||||||
|
const nameErrors = useSelector(contractFormNameErrorSelector, areEqual);
|
||||||
|
const templateErrors = useSelector(contractFormTemplateErrorSelector, areEqual);
|
||||||
|
|
||||||
|
const [formData, setFormData] = useState({
|
||||||
|
name: initData?.name ?? '',
|
||||||
|
template: initData?.template ?? '',
|
||||||
|
requireSignature: initData?.has_signature ?? false,
|
||||||
|
});
|
||||||
|
const [toastMessage, setToastMessage] = useState('');
|
||||||
|
const [showToast, setShowToast] = useState(false);
|
||||||
|
const textAreaRef = useRef<HTMLTextAreaElement>(null);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (initData) {
|
||||||
|
setFormData({
|
||||||
|
name: initData.name,
|
||||||
|
template: initData.template,
|
||||||
|
requireSignature: initData.has_signature,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}, [initData]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (isEdited) {
|
||||||
|
setToastMessage('Contract Form Updated');
|
||||||
|
setShowToast(true);
|
||||||
|
if (companyId) {
|
||||||
|
dispatch(listCompanyContractForms(companyId));
|
||||||
|
}
|
||||||
|
dispatch(setContractFormErrors({}));
|
||||||
|
setTimeout(() => {
|
||||||
|
setShowToast(false);
|
||||||
|
onClose();
|
||||||
|
dispatch(setContractFormEdited(false));
|
||||||
|
}, 1500);
|
||||||
|
}
|
||||||
|
// eslint-disable-next-line
|
||||||
|
}, [isEdited]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (!isOpen) {
|
||||||
|
setFormData({
|
||||||
|
name: '',
|
||||||
|
template: '',
|
||||||
|
requireSignature: false,
|
||||||
|
});
|
||||||
|
dispatch(setContractFormErrors({}));
|
||||||
|
}
|
||||||
|
}, [isOpen, dispatch, initData]);
|
||||||
|
|
||||||
|
const handleChange = useCallback((e: React.ChangeEvent<HTMLInputElement | HTMLTextAreaElement>) => {
|
||||||
|
const { name, value, type } = e.target;
|
||||||
|
const newValue = type === 'checkbox' ? (e.target as HTMLInputElement).checked : value;
|
||||||
|
setFormData((prev) => ({
|
||||||
|
...prev,
|
||||||
|
[name]: newValue,
|
||||||
|
}));
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const handleTagClick = useCallback(
|
||||||
|
(tag: string) => {
|
||||||
|
const textarea = textAreaRef.current;
|
||||||
|
if (textarea) {
|
||||||
|
const start = textarea.selectionStart;
|
||||||
|
const end = textarea.selectionEnd;
|
||||||
|
const newText = formData.template.substring(0, start) + tag + formData.template.substring(end);
|
||||||
|
setFormData((prev) => ({ ...prev, template: newText }));
|
||||||
|
setTimeout(() => {
|
||||||
|
textarea.selectionStart = start + tag.length;
|
||||||
|
textarea.selectionEnd = start + tag.length;
|
||||||
|
textarea.focus();
|
||||||
|
}, 0);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
[formData.template]
|
||||||
|
);
|
||||||
|
|
||||||
|
const onFormSubmit = useCallback(
|
||||||
|
(e) => {
|
||||||
|
e.preventDefault();
|
||||||
|
// Client-side validation
|
||||||
|
const errors: any = {};
|
||||||
|
if (!formData.name.trim()) errors.name = ['Form name is required'];
|
||||||
|
if (!formData.template.trim()) errors.template = ['Template is required'];
|
||||||
|
if (Object.keys(errors).length > 0) {
|
||||||
|
dispatch(setContractFormErrors(errors));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (companyId) {
|
||||||
|
// Prepare API payload
|
||||||
|
const payload = {
|
||||||
|
name: formData.name,
|
||||||
|
company_id: companyId,
|
||||||
|
replacement_tags: Array.from(new Set(formData.template.match(/~~~.*?~~~/g) || [])).join(', '),
|
||||||
|
status: 'active',
|
||||||
|
template: formData.template,
|
||||||
|
has_signature: formData.requireSignature,
|
||||||
|
};
|
||||||
|
dispatch(editContractForm(payload, companyId));
|
||||||
|
}
|
||||||
|
},
|
||||||
|
[formData, companyId, dispatch]
|
||||||
|
);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<ContractFormsModal
|
||||||
|
isOpen={isOpen}
|
||||||
|
title="Edit Contract Form"
|
||||||
|
submitText="Edit Contract"
|
||||||
|
formData={formData}
|
||||||
|
formErrors={{
|
||||||
|
name: nameErrors,
|
||||||
|
template: templateErrors,
|
||||||
|
}}
|
||||||
|
showToast={showToast}
|
||||||
|
toastMessage={toastMessage}
|
||||||
|
onFormSubmit={onFormSubmit}
|
||||||
|
onClickClose={(e) => {
|
||||||
|
e.preventDefault();
|
||||||
|
onClose();
|
||||||
|
}}
|
||||||
|
handleChange={handleChange}
|
||||||
|
textAreaRef={textAreaRef}
|
||||||
|
handleTagClick={handleTagClick}
|
||||||
|
submitting={isEditing}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const EditContractFormsMemo = memo(EditContractForms, areEqual);
|
||||||
|
export { EditContractFormsMemo as EditContractForms };
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
export { EditContractForms } from './EditContractForms';
|
||||||
@@ -5,11 +5,14 @@ import {
|
|||||||
contractFormsSelector,
|
contractFormsSelector,
|
||||||
fetchingContractFormsSelector,
|
fetchingContractFormsSelector,
|
||||||
companyIdSelector,
|
companyIdSelector,
|
||||||
deletingContractFormSelector,
|
// deletingContractFormSelector,
|
||||||
contractFormDeletedSelector,
|
contractFormDeletedSelector,
|
||||||
} from 'Containers/Forms/selectors';
|
} from 'Containers/Forms/selectors';
|
||||||
import { ContractForms } from 'Components/Forms';
|
import { ContractForms } from 'Components/Forms';
|
||||||
import { DeleteFormModal, DeleteToast } from 'Components/Forms/FormTabs';
|
import { DeleteFormModal, DeleteToast } from 'Components/Forms/FormTabs';
|
||||||
|
import { AddContractForms } from 'Containers/Forms/AddContractForms';
|
||||||
|
import { FormTemplate } from 'Containers/Forms/Models';
|
||||||
|
import { EditContractForms } from 'Containers/Forms/EditContractForms';
|
||||||
import { listCompanyContractForms, deleteContractForm, setDeletedFormId } from '../../actions';
|
import { listCompanyContractForms, deleteContractForm, setDeletedFormId } from '../../actions';
|
||||||
|
|
||||||
const ContractFormsContainer = () => {
|
const ContractFormsContainer = () => {
|
||||||
@@ -17,13 +20,21 @@ const ContractFormsContainer = () => {
|
|||||||
const contractForms = useSelector(contractFormsSelector, areEqual);
|
const contractForms = useSelector(contractFormsSelector, areEqual);
|
||||||
const fetching = useSelector(fetchingContractFormsSelector, areEqual);
|
const fetching = useSelector(fetchingContractFormsSelector, areEqual);
|
||||||
const companyId = useSelector(companyIdSelector, areEqual);
|
const companyId = useSelector(companyIdSelector, areEqual);
|
||||||
const deleting = useSelector(deletingContractFormSelector, areEqual);
|
// const deleting = useSelector(deletingContractFormSelector, areEqual);
|
||||||
const deletedId = useSelector(contractFormDeletedSelector, areEqual);
|
const deletedId = useSelector(contractFormDeletedSelector, areEqual);
|
||||||
const [deleteModal, setDeleteModal] = useState({
|
const [deleteModal, setDeleteModal] = useState({
|
||||||
isOpen: false,
|
isOpen: false,
|
||||||
id: null,
|
id: null,
|
||||||
});
|
});
|
||||||
const [showDeletedToast, setShowDeletedToast] = useState(false);
|
const [showDeletedToast, setShowDeletedToast] = useState(false);
|
||||||
|
const [showAddFormsModal, setShowAddFormsModal] = useState(false);
|
||||||
|
const [editModal, setEditModal] = useState<{
|
||||||
|
isOpen: boolean;
|
||||||
|
form: FormTemplate | null;
|
||||||
|
}>({
|
||||||
|
isOpen: false,
|
||||||
|
form: null,
|
||||||
|
});
|
||||||
|
|
||||||
const getContractForms = useCallback(() => {
|
const getContractForms = useCallback(() => {
|
||||||
if (companyId) {
|
if (companyId) {
|
||||||
@@ -49,11 +60,33 @@ const ContractFormsContainer = () => {
|
|||||||
[dispatch]
|
[dispatch]
|
||||||
);
|
);
|
||||||
|
|
||||||
const closeToast = useCallback((e: any) => {
|
const handleOpenEdit = useCallback((form: FormTemplate) => {
|
||||||
e.preventDefault();
|
setEditModal({
|
||||||
|
isOpen: true,
|
||||||
|
form,
|
||||||
|
});
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const closeEditModal = useCallback(() => {
|
||||||
|
setEditModal({
|
||||||
|
isOpen: false,
|
||||||
|
form: null,
|
||||||
|
});
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const closeToast = useCallback(() => {
|
||||||
setShowDeletedToast(false);
|
setShowDeletedToast(false);
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
|
const openAddForms = useCallback((e: any) => {
|
||||||
|
e.preventDefault();
|
||||||
|
setShowAddFormsModal(true);
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const closeAddForms = useCallback(() => {
|
||||||
|
setShowAddFormsModal(false);
|
||||||
|
}, []);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (deletedId) {
|
if (deletedId) {
|
||||||
dispatch(listCompanyContractForms(companyId));
|
dispatch(listCompanyContractForms(companyId));
|
||||||
@@ -75,13 +108,15 @@ const ContractFormsContainer = () => {
|
|||||||
<ContractForms
|
<ContractForms
|
||||||
forms={contractForms}
|
forms={contractForms}
|
||||||
fetching={fetching}
|
fetching={fetching}
|
||||||
onAdd={() => {}}
|
onAdd={openAddForms}
|
||||||
onDelete={deleteButtonClick}
|
onDelete={deleteButtonClick}
|
||||||
onClickRow={() => {}}
|
onClickRow={handleOpenEdit}
|
||||||
// deleting={deleting}
|
|
||||||
// deletedId={deletedId}
|
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
<AddContractForms isOpen={showAddFormsModal} onClose={closeAddForms} />
|
||||||
|
|
||||||
|
<EditContractForms isOpen={editModal.isOpen} initData={editModal.form as FormTemplate} onClose={closeEditModal} />
|
||||||
|
|
||||||
<DeleteFormModal
|
<DeleteFormModal
|
||||||
id={deleteModal.id as number}
|
id={deleteModal.id as number}
|
||||||
isOpen={deleteModal.isOpen}
|
isOpen={deleteModal.isOpen}
|
||||||
|
|||||||
@@ -16,3 +16,12 @@ export type FormTemplate = {
|
|||||||
export type FormTemplateResponse = {
|
export type FormTemplateResponse = {
|
||||||
data: FormTemplate[];
|
data: FormTemplate[];
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export type FormRequestBody = {
|
||||||
|
company_id: number;
|
||||||
|
name: string;
|
||||||
|
replacement_tags: string;
|
||||||
|
status: string;
|
||||||
|
template: string;
|
||||||
|
has_signature: boolean;
|
||||||
|
};
|
||||||
|
|||||||
@@ -1 +1 @@
|
|||||||
export type { FormTemplate, FormTemplateResponse } from './FormModel';
|
export type { FormTemplate, FormTemplateResponse, FormRequestBody } from './FormModel';
|
||||||
|
|||||||
@@ -1 +1 @@
|
|||||||
export type { FormTemplate, FormTemplateResponse } from './FormsModel';
|
export type { FormTemplate, FormTemplateResponse, FormRequestBody } from './FormsModel';
|
||||||
|
|||||||
@@ -1,17 +1,27 @@
|
|||||||
import { handleApiRequest } from 'Utils/handleApiRequest';
|
import { handleApiRequest } from 'Utils/handleApiRequest';
|
||||||
import { Api } from 'Utils/api';
|
import { Api } from 'Utils/api';
|
||||||
import { FormTemplateResponse } from 'Containers/Forms/Models';
|
import { FormRequestBody, FormTemplateResponse } from 'Containers/Forms/Models';
|
||||||
|
|
||||||
export const CONTRACT_FORMS = 'CONTRACT_FORMS';
|
export const CONTRACT_FORMS = 'CONTRACT_FORMS';
|
||||||
export const FETCHING_CONTRACT_FORMS = 'FETCHING_CONTRACT_FORMS';
|
export const FETCHING_CONTRACT_FORMS = 'FETCHING_CONTRACT_FORMS';
|
||||||
export const DELETING_CONTRACT_FORM = 'DELETING_CONTRACT_FORM';
|
export const DELETING_CONTRACT_FORM = 'DELETING_CONTRACT_FORM';
|
||||||
export const CONTRACT_FORM_DELETED = 'CONTRACT_FORM_DELETED';
|
export const CONTRACT_FORM_DELETED = 'CONTRACT_FORM_DELETED';
|
||||||
|
export const ADDING_CONTRACT_FORM = 'ADDING_CONTRACT_FORM';
|
||||||
|
export const CONTRACT_FORM_ADDED = 'CONTRACT_FORM_ADDED';
|
||||||
|
export const EDITING_CONTRACT_FORM = 'EDITING_CONTRACT_FORM';
|
||||||
|
export const CONTRACT_FORM_EDITED = 'CONTRACT_FORM_EDITED';
|
||||||
|
export const CONTRACT_FORM_ERRORS = 'CONTRACT_FORM_ERRORS';
|
||||||
|
|
||||||
interface FormsActionTypes {
|
interface FormsActionTypes {
|
||||||
CONTRACT_FORMS: FormTemplateResponse;
|
CONTRACT_FORMS: FormTemplateResponse;
|
||||||
FETCHING_CONTRACT_FORMS: boolean;
|
FETCHING_CONTRACT_FORMS: boolean;
|
||||||
DELETING_CONTRACT_FORM: boolean;
|
DELETING_CONTRACT_FORM: boolean;
|
||||||
CONTRACT_FORM_DELETED: number;
|
CONTRACT_FORM_DELETED: number;
|
||||||
|
ADDING_CONTRACT_FORM: boolean;
|
||||||
|
CONTRACT_FORM_ADDED: boolean;
|
||||||
|
EDITING_CONTRACT_FORM: boolean;
|
||||||
|
CONTRACT_FORM_EDITED: boolean;
|
||||||
|
CONTRACT_FORM_ERRORS: any;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface MessageAction {
|
interface MessageAction {
|
||||||
@@ -36,18 +46,74 @@ export const listCompanyContractForms = (companyId: number) => async (dispatch:
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const setDeletedFormId = (value: number | null) => async (dispatch: any) => {
|
||||||
|
dispatch({ type: CONTRACT_FORM_DELETED, payload: value });
|
||||||
|
};
|
||||||
|
|
||||||
// Thunk to delete a contract form by id
|
// Thunk to delete a contract form by id
|
||||||
export const deleteContractForm = (contractFormId: number) => async (dispatch: any) => {
|
export const deleteContractForm = (contractFormId: number) => async (dispatch: any) => {
|
||||||
dispatch({ type: DELETING_CONTRACT_FORM, payload: true });
|
dispatch({ type: DELETING_CONTRACT_FORM, payload: true });
|
||||||
try {
|
try {
|
||||||
await handleApiRequest(dispatch, Api.delete(`/contract-forms/${contractFormId}`), '', DELETING_CONTRACT_FORM);
|
await handleApiRequest(dispatch, Api.delete(`/contract-forms/${contractFormId}`), '', DELETING_CONTRACT_FORM);
|
||||||
dispatch({ type: CONTRACT_FORM_DELETED, payload: contractFormId });
|
dispatch(setDeletedFormId(contractFormId));
|
||||||
|
// dispatch({ type: CONTRACT_FORM_DELETED, payload: contractFormId });
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
// handle error if needed
|
// handle error if needed
|
||||||
dispatch({ type: DELETING_CONTRACT_FORM, payload: false });
|
dispatch({ type: DELETING_CONTRACT_FORM, payload: false });
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
export const setDeletedFormId = (value: number | null) => async (dispatch: any) => {
|
export const setContractFormAdded = (value: boolean) => (dispatch: any) => {
|
||||||
dispatch({ type: CONTRACT_FORM_DELETED, payload: value });
|
dispatch({
|
||||||
|
type: CONTRACT_FORM_ADDED,
|
||||||
|
payload: value,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
// Thunk to add a contract form
|
||||||
|
export const addContractForm = (formData: FormRequestBody) => async (dispatch: any) => {
|
||||||
|
dispatch({ type: ADDING_CONTRACT_FORM, payload: true });
|
||||||
|
try {
|
||||||
|
await handleApiRequest(
|
||||||
|
dispatch,
|
||||||
|
Api.post('/contract-forms', formData),
|
||||||
|
'CONTRACT_FORM_ERRORS',
|
||||||
|
ADDING_CONTRACT_FORM
|
||||||
|
);
|
||||||
|
dispatch(setContractFormAdded(true));
|
||||||
|
} catch (error) {
|
||||||
|
dispatch({ type: ADDING_CONTRACT_FORM, payload: false });
|
||||||
|
dispatch(setContractFormAdded(false));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export const setContractFormEdited = (value: boolean) => (dispatch: any) => {
|
||||||
|
dispatch({
|
||||||
|
type: CONTRACT_FORM_EDITED,
|
||||||
|
payload: value,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
// Thunk to edit a contract form
|
||||||
|
export const editContractForm = (formData: FormRequestBody, contractId: string) => async (dispatch: any) => {
|
||||||
|
dispatch({ type: EDITING_CONTRACT_FORM, payload: true });
|
||||||
|
try {
|
||||||
|
await handleApiRequest(
|
||||||
|
dispatch,
|
||||||
|
Api.put('/contract-forms', formData),
|
||||||
|
'CONTRACT_FORM_ERRORS',
|
||||||
|
EDITING_CONTRACT_FORM
|
||||||
|
);
|
||||||
|
dispatch(setContractFormEdited(true));
|
||||||
|
} catch (error) {
|
||||||
|
dispatch({ type: EDITING_CONTRACT_FORM, payload: false });
|
||||||
|
dispatch(setContractFormEdited(false));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export const setContractFormErrors = (errors: any) => (dispatch: any) => {
|
||||||
|
dispatch({
|
||||||
|
type: CONTRACT_FORM_ERRORS,
|
||||||
|
payload: errors,
|
||||||
|
});
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -4,6 +4,10 @@ import {
|
|||||||
SetFormsActionTypes,
|
SetFormsActionTypes,
|
||||||
DELETING_CONTRACT_FORM,
|
DELETING_CONTRACT_FORM,
|
||||||
CONTRACT_FORM_DELETED,
|
CONTRACT_FORM_DELETED,
|
||||||
|
ADDING_CONTRACT_FORM,
|
||||||
|
CONTRACT_FORM_ADDED,
|
||||||
|
EDITING_CONTRACT_FORM,
|
||||||
|
CONTRACT_FORM_EDITED,
|
||||||
} from './actions';
|
} from './actions';
|
||||||
|
|
||||||
const initialState = {
|
const initialState = {
|
||||||
@@ -11,6 +15,11 @@ const initialState = {
|
|||||||
fetchingContractForms: false,
|
fetchingContractForms: false,
|
||||||
deletingContractForm: false,
|
deletingContractForm: false,
|
||||||
contractFormDeleted: null,
|
contractFormDeleted: null,
|
||||||
|
addingContractForm: false,
|
||||||
|
contractFormAdded: false,
|
||||||
|
editingContractForm: false,
|
||||||
|
contractFormEdited: false,
|
||||||
|
formErrors: {},
|
||||||
};
|
};
|
||||||
|
|
||||||
export const formsReducer = (state = initialState, action: SetFormsActionTypes) => {
|
export const formsReducer = (state = initialState, action: SetFormsActionTypes) => {
|
||||||
@@ -39,6 +48,36 @@ export const formsReducer = (state = initialState, action: SetFormsActionTypes)
|
|||||||
contractFormDeleted: action.payload,
|
contractFormDeleted: action.payload,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
case ADDING_CONTRACT_FORM: {
|
||||||
|
return {
|
||||||
|
...state,
|
||||||
|
addingContractForm: action.payload,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
case CONTRACT_FORM_ADDED: {
|
||||||
|
return {
|
||||||
|
...state,
|
||||||
|
contractFormAdded: action.payload,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
case EDITING_CONTRACT_FORM: {
|
||||||
|
return {
|
||||||
|
...state,
|
||||||
|
editingContractForm: action.payload,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
case CONTRACT_FORM_EDITED: {
|
||||||
|
return {
|
||||||
|
...state,
|
||||||
|
contractFormEdited: action.payload,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
case 'CONTRACT_FORM_ERRORS': {
|
||||||
|
return {
|
||||||
|
...state,
|
||||||
|
formErrors: action.payload || {},
|
||||||
|
};
|
||||||
|
}
|
||||||
default:
|
default:
|
||||||
return state;
|
return state;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,3 +4,10 @@ export const fetchingContractFormsSelector = ({ forms }: any) => forms?.fetching
|
|||||||
export { firstCompanyIdSelector as companyIdSelector } from '../Projects/selectors';
|
export { firstCompanyIdSelector as companyIdSelector } from '../Projects/selectors';
|
||||||
export const deletingContractFormSelector = ({ forms }: any) => forms?.deletingContractForm || false;
|
export const deletingContractFormSelector = ({ forms }: any) => forms?.deletingContractForm || false;
|
||||||
export const contractFormDeletedSelector = ({ forms }: any) => forms?.contractFormDeleted || null;
|
export const contractFormDeletedSelector = ({ forms }: any) => forms?.contractFormDeleted || null;
|
||||||
|
export const addingContractFormSelector = ({ forms }: any) => forms?.addingContractForm || false;
|
||||||
|
export const contractFormAddedSelector = ({ forms }: any) => forms?.contractFormAdded || false;
|
||||||
|
export const editingContractFormSelector = ({ forms }: any) => forms?.editingContractForm || false;
|
||||||
|
export const contractFormEditedSelector = ({ forms }: any) => forms?.contractFormEdited || false;
|
||||||
|
export const contractFormErrorsSelector = ({ forms }: any) => forms?.formErrors || {};
|
||||||
|
export const contractFormNameErrorSelector = ({ forms }: any) => forms?.formErrors?.name || [];
|
||||||
|
export const contractFormTemplateErrorSelector = ({ forms }: any) => forms?.formErrors?.template || [];
|
||||||
|
|||||||
Reference in New Issue
Block a user