Refactor; add, edit and delete contract forms logic and api calls
This commit is contained in:
@@ -3,9 +3,6 @@
|
||||
"version": "1.0.0",
|
||||
"main": "index.js",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": "18.x"
|
||||
},
|
||||
"scripts": {
|
||||
"start": "cross-env NODE_ENV=development webpack serve --config config/webpack.dev.js",
|
||||
"prep": "yarn && yarn prepare-husky && yarn start",
|
||||
|
||||
@@ -7,7 +7,7 @@ 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 { ContractFormsToast } from '../ContractFormsToast';
|
||||
|
||||
import classes from './contractFormsModal.module.css';
|
||||
|
||||
@@ -35,6 +35,7 @@ interface Props {
|
||||
submitText: string;
|
||||
showToast: boolean;
|
||||
toastMessage: string;
|
||||
toastType?: string;
|
||||
onFormSubmit: (e: any) => void;
|
||||
onClickClose: (e: any) => void;
|
||||
handleChange: (e: any) => void;
|
||||
@@ -51,6 +52,7 @@ const ContractFormsModal = ({
|
||||
formErrors,
|
||||
showToast,
|
||||
toastMessage,
|
||||
toastType = 'success',
|
||||
onFormSubmit,
|
||||
onClickClose,
|
||||
handleChange,
|
||||
@@ -66,7 +68,7 @@ const ContractFormsModal = ({
|
||||
leftHeaderIcon="projects"
|
||||
modalHeader
|
||||
modalCloseClick={onClickClose}
|
||||
toast={<ContractFormsSuccessToast showToast={showToast} message={toastMessage} />}
|
||||
toast={<ContractFormsToast type={toastType as 'success' | 'error'} showToast={showToast} message={toastMessage} />}
|
||||
>
|
||||
<form className={classes.form} onSubmit={onFormSubmit}>
|
||||
<ValidateBackGround isValid={!formErrors?.name.length}>
|
||||
@@ -139,6 +141,10 @@ const ContractFormsModal = ({
|
||||
</Modal>
|
||||
);
|
||||
|
||||
ContractFormsModal.defaultProps = {
|
||||
toastType: 'success',
|
||||
};
|
||||
|
||||
const ContractFormsModalMemo = memo(ContractFormsModal, areEqual);
|
||||
|
||||
export { ContractFormsModalMemo as ContractFormsModal };
|
||||
|
||||
@@ -1,35 +0,0 @@
|
||||
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 };
|
||||
@@ -1 +0,0 @@
|
||||
export { ContractFormsSuccessToast } from './ContractFormsSuccessToast';
|
||||
@@ -0,0 +1,44 @@
|
||||
import React, { memo } from 'react';
|
||||
|
||||
import { areEqual } from 'Utils/equalityChecks';
|
||||
import { CheckedMarkSvg } from 'Components/Icons/CheckedMark';
|
||||
|
||||
import classes from './contractFormsToast.module.css';
|
||||
|
||||
export interface Props {
|
||||
showToast: boolean;
|
||||
message: string;
|
||||
type?: 'success' | 'error';
|
||||
}
|
||||
|
||||
const ContractFormsToast = ({ showToast = false, message, type = 'success' }: Props) => {
|
||||
const getToastClass = () => (type === 'success' ? classes.toastSuccess : classes.toastWarning);
|
||||
|
||||
return (
|
||||
<div
|
||||
className={`toast fade d-flex align-items-center position-absolute border-0 bottom-0 ${
|
||||
showToast ? 'show' : 'hide'
|
||||
} ${classes.toastBase} ${getToastClass()}`}
|
||||
role="alert"
|
||||
aria-live="assertive"
|
||||
aria-atomic="true"
|
||||
>
|
||||
<div className={`toast-body ${classes['toast-body-override']} ${classes.toastText}`}>
|
||||
{message}
|
||||
{type === 'success' && (
|
||||
<span className={`${classes.toastIcon}`}>
|
||||
<CheckedMarkSvg />
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
ContractFormsToast.defaultProps = {
|
||||
type: 'success',
|
||||
};
|
||||
|
||||
const ContractFormsToastMemo = memo(ContractFormsToast, areEqual);
|
||||
|
||||
export { ContractFormsToastMemo as ContractFormsToast };
|
||||
+5
-1
@@ -57,7 +57,11 @@
|
||||
|
||||
.toastWarning {
|
||||
background-color: #fff0f0;
|
||||
border-bottom: 1px solid #e82828;
|
||||
border-bottom: 1px solid #e82828 !important;
|
||||
}
|
||||
|
||||
.toastWarning .toastText {
|
||||
color: #d32f2f !important;
|
||||
}
|
||||
|
||||
.toastCloseIcon {
|
||||
@@ -0,0 +1 @@
|
||||
export { ContractFormsToast } from './ContractFormsToast';
|
||||
@@ -10,36 +10,45 @@ export interface Props {
|
||||
isDisplayed: boolean;
|
||||
message: string;
|
||||
closeToast: (e: any) => void;
|
||||
type?: 'success' | 'error';
|
||||
}
|
||||
|
||||
const DeleteToast = ({ isDisplayed = false, message, closeToast }: Props) => (
|
||||
<div
|
||||
className={`toast fade d-flex align-items-center position-fixed border-0 bottom-0 ${
|
||||
isDisplayed ? '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 className={classes.toastCloseButtonContainer}>
|
||||
<Button
|
||||
type="button"
|
||||
className={`btn-close ${classes.toastCloseButton}`}
|
||||
data-bs-dismiss="toast"
|
||||
aria-label="Close"
|
||||
onClick={closeToast}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
const DeleteToast = ({ isDisplayed = false, message, closeToast, type = 'success' }: Props) => {
|
||||
const getToastClass = () => (type === 'success' ? classes.toastSuccess : classes.toastWarning);
|
||||
|
||||
DeleteToast.defaultProps = {};
|
||||
return (
|
||||
<div
|
||||
className={`toast fade d-flex align-items-center position-fixed border-0 bottom-0 ${
|
||||
isDisplayed ? 'show' : 'hide'
|
||||
} ${classes.toastBase} ${getToastClass()}`}
|
||||
role="alert"
|
||||
aria-live="assertive"
|
||||
aria-atomic="true"
|
||||
>
|
||||
<div className={`toast-body ${classes['toast-body-override']} ${classes.toastText}`}>
|
||||
{message}
|
||||
{type === 'success' && (
|
||||
<span className={`${classes.toastIcon}`}>
|
||||
<CheckedMarkSvg />
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
<div className={classes.toastCloseButtonContainer}>
|
||||
<Button
|
||||
type="button"
|
||||
className={`btn-close ${classes.toastCloseButton}`}
|
||||
data-bs-dismiss="toast"
|
||||
aria-label="Close"
|
||||
onClick={closeToast}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
DeleteToast.defaultProps = {
|
||||
type: 'success',
|
||||
};
|
||||
|
||||
const DeleteToastMemo = memo(DeleteToast, areEqual);
|
||||
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
export { FormsList, ContractForms } from './FormTabs';
|
||||
export { ContractFormsSuccessToast } from './ContractFormsSuccessToast';
|
||||
export { ContractFormsToast } from './ContractFormsToast';
|
||||
export { ContractFormsModal } from './ContractFormsModal';
|
||||
|
||||
@@ -33,6 +33,8 @@ const AddContractForms = ({ isOpen, onClose }: Props) => {
|
||||
const [formData, setFormData] = useState({ ...initData });
|
||||
const [toastMessage, setToastMessage] = useState('');
|
||||
const [showToast, setShowToast] = useState(false);
|
||||
const [toastType, setToastType] = useState<'success' | 'error'>('success');
|
||||
const [wasAdding, setWasAdding] = useState(false);
|
||||
const textAreaRef = useRef<HTMLTextAreaElement>(null);
|
||||
|
||||
const handleChange = useCallback((e: React.ChangeEvent<HTMLInputElement | HTMLTextAreaElement>) => {
|
||||
@@ -89,6 +91,7 @@ const AddContractForms = ({ isOpen, onClose }: Props) => {
|
||||
|
||||
useEffect(() => {
|
||||
if (isAdded) {
|
||||
setToastType('success');
|
||||
setToastMessage('Contract Form Added');
|
||||
setShowToast(true);
|
||||
setFormData({ ...initData });
|
||||
@@ -102,7 +105,22 @@ const AddContractForms = ({ isOpen, onClose }: Props) => {
|
||||
dispatch(setContractFormAdded(false));
|
||||
}, 1500);
|
||||
}
|
||||
}, [isAdded]);
|
||||
}, [isAdded, companyId, dispatch, onClose]);
|
||||
|
||||
useEffect(() => {
|
||||
if (isAdding) {
|
||||
setWasAdding(true);
|
||||
} else if (wasAdding && !isAdded) {
|
||||
// API call finished but form wasn't added (error occurred)
|
||||
setToastType('error');
|
||||
setToastMessage('Failed to add contract form. Please try again.');
|
||||
setShowToast(true);
|
||||
setWasAdding(false);
|
||||
setTimeout(() => {
|
||||
setShowToast(false);
|
||||
}, 3000);
|
||||
}
|
||||
}, [isAdding, isAdded, wasAdding]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!isOpen) {
|
||||
@@ -123,6 +141,7 @@ const AddContractForms = ({ isOpen, onClose }: Props) => {
|
||||
}}
|
||||
showToast={showToast}
|
||||
toastMessage={toastMessage}
|
||||
toastType={toastType}
|
||||
onFormSubmit={onFormSubmit}
|
||||
onClickClose={(e) => {
|
||||
e.preventDefault();
|
||||
|
||||
@@ -33,6 +33,8 @@ const EditContractForms = ({ isOpen, onClose, initData }: Props) => {
|
||||
});
|
||||
const [toastMessage, setToastMessage] = useState('');
|
||||
const [showToast, setShowToast] = useState(false);
|
||||
const [toastType, setToastType] = useState<'success' | 'error'>('success');
|
||||
const [wasEditing, setWasEditing] = useState(false);
|
||||
const textAreaRef = useRef<HTMLTextAreaElement>(null);
|
||||
|
||||
useEffect(() => {
|
||||
@@ -47,6 +49,7 @@ const EditContractForms = ({ isOpen, onClose, initData }: Props) => {
|
||||
|
||||
useEffect(() => {
|
||||
if (isEdited) {
|
||||
setToastType('success');
|
||||
setToastMessage('Contract Form Updated');
|
||||
setShowToast(true);
|
||||
if (companyId) {
|
||||
@@ -59,8 +62,22 @@ const EditContractForms = ({ isOpen, onClose, initData }: Props) => {
|
||||
dispatch(setContractFormEdited(false));
|
||||
}, 1500);
|
||||
}
|
||||
// eslint-disable-next-line
|
||||
}, [isEdited]);
|
||||
}, [isEdited, companyId, dispatch, onClose]);
|
||||
|
||||
useEffect(() => {
|
||||
if (isEditing) {
|
||||
setWasEditing(true);
|
||||
} else if (wasEditing && !isEdited) {
|
||||
// API call finished but form wasn't edited (error occurred)
|
||||
setToastType('error');
|
||||
setToastMessage('Failed to update contract form. Please try again.');
|
||||
setShowToast(true);
|
||||
setWasEditing(false);
|
||||
setTimeout(() => {
|
||||
setShowToast(false);
|
||||
}, 3000);
|
||||
}
|
||||
}, [isEditing, isEdited, wasEditing]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!isOpen) {
|
||||
@@ -139,6 +156,7 @@ const EditContractForms = ({ isOpen, onClose, initData }: Props) => {
|
||||
}}
|
||||
showToast={showToast}
|
||||
toastMessage={toastMessage}
|
||||
toastType={toastType}
|
||||
onFormSubmit={onFormSubmit}
|
||||
onClickClose={(e) => {
|
||||
e.preventDefault();
|
||||
|
||||
@@ -5,7 +5,7 @@ import {
|
||||
contractFormsSelector,
|
||||
fetchingContractFormsSelector,
|
||||
companyIdSelector,
|
||||
// deletingContractFormSelector,
|
||||
deletingContractFormSelector,
|
||||
contractFormDeletedSelector,
|
||||
} from 'Containers/Forms/selectors';
|
||||
import { ContractForms } from 'Components/Forms';
|
||||
@@ -20,13 +20,16 @@ const ContractFormsContainer = () => {
|
||||
const contractForms = useSelector(contractFormsSelector, areEqual);
|
||||
const fetching = useSelector(fetchingContractFormsSelector, areEqual);
|
||||
const companyId = useSelector(companyIdSelector, areEqual);
|
||||
// const deleting = useSelector(deletingContractFormSelector, areEqual);
|
||||
const deleting = useSelector(deletingContractFormSelector, areEqual);
|
||||
const deletedId = useSelector(contractFormDeletedSelector, areEqual);
|
||||
const [deleteModal, setDeleteModal] = useState({
|
||||
isOpen: false,
|
||||
id: null,
|
||||
});
|
||||
const [showDeletedToast, setShowDeletedToast] = useState(false);
|
||||
const [deleteToastMessage, setDeleteToastMessage] = useState('Form Deleted');
|
||||
const [deleteToastType, setDeleteToastType] = useState<'success' | 'error'>('success');
|
||||
const [wasDeleting, setWasDeleting] = useState(false);
|
||||
const [showAddFormsModal, setShowAddFormsModal] = useState(false);
|
||||
const [editModal, setEditModal] = useState<{
|
||||
isOpen: boolean;
|
||||
@@ -89,15 +92,34 @@ const ContractFormsContainer = () => {
|
||||
|
||||
useEffect(() => {
|
||||
if (deletedId) {
|
||||
// Success case
|
||||
setDeleteToastType('success');
|
||||
setDeleteToastMessage('Form Deleted');
|
||||
setShowDeletedToast(true);
|
||||
dispatch(listCompanyContractForms(companyId));
|
||||
|
||||
setShowDeletedToast(true);
|
||||
|
||||
setTimeout(() => setShowDeletedToast(false), 1500);
|
||||
|
||||
dispatch(setDeletedFormId(null));
|
||||
setTimeout(() => {
|
||||
setShowDeletedToast(false);
|
||||
dispatch(setDeletedFormId(null));
|
||||
}, 1500);
|
||||
}
|
||||
}, [deletedId]);
|
||||
}, [deletedId, companyId, dispatch]);
|
||||
|
||||
useEffect(() => {
|
||||
if (deleting) {
|
||||
setWasDeleting(true);
|
||||
} else if (wasDeleting && !deletedId) {
|
||||
// API call finished but form wasn't deleted (error occurred)
|
||||
setDeleteToastType('error');
|
||||
setDeleteToastMessage('Failed to delete form. Please try again.');
|
||||
setShowDeletedToast(true);
|
||||
setWasDeleting(false);
|
||||
|
||||
setTimeout(() => {
|
||||
setShowDeletedToast(false);
|
||||
}, 3000);
|
||||
}
|
||||
}, [deleting, deletedId, wasDeleting]);
|
||||
|
||||
useEffect(() => {
|
||||
getContractForms();
|
||||
@@ -128,7 +150,12 @@ const ContractFormsContainer = () => {
|
||||
}
|
||||
onDelete={handleDelete}
|
||||
/>
|
||||
{deletedId && <DeleteToast isDisplayed={showDeletedToast} closeToast={closeToast} message="Form Deleted" />}
|
||||
<DeleteToast
|
||||
isDisplayed={showDeletedToast}
|
||||
closeToast={closeToast}
|
||||
message={deleteToastMessage}
|
||||
type={deleteToastType}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -53,12 +53,12 @@ export const setDeletedFormId = (value: number | null) => async (dispatch: any)
|
||||
// Thunk to delete a contract form by id
|
||||
export const deleteContractForm = (contractFormId: number) => async (dispatch: any) => {
|
||||
dispatch({ type: DELETING_CONTRACT_FORM, payload: true });
|
||||
|
||||
try {
|
||||
await handleApiRequest(dispatch, Api.delete(`/contract-forms/${contractFormId}`), '', DELETING_CONTRACT_FORM);
|
||||
|
||||
dispatch(setDeletedFormId(contractFormId));
|
||||
// dispatch({ type: CONTRACT_FORM_DELETED, payload: contractFormId });
|
||||
} catch (error) {
|
||||
// handle error if needed
|
||||
dispatch({ type: DELETING_CONTRACT_FORM, payload: false });
|
||||
}
|
||||
};
|
||||
@@ -73,16 +73,19 @@ export const setContractFormAdded = (value: boolean) => (dispatch: any) => {
|
||||
// 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
|
||||
);
|
||||
|
||||
const response = await handleApiRequest(
|
||||
dispatch,
|
||||
Api.post('/contract-forms', formData),
|
||||
'CONTRACT_FORM_ERRORS',
|
||||
ADDING_CONTRACT_FORM
|
||||
);
|
||||
|
||||
if (response) {
|
||||
// API call succeeded
|
||||
dispatch(setContractFormAdded(true));
|
||||
} catch (error) {
|
||||
dispatch({ type: ADDING_CONTRACT_FORM, payload: false });
|
||||
} else {
|
||||
// API call failed
|
||||
dispatch(setContractFormAdded(false));
|
||||
}
|
||||
};
|
||||
@@ -97,16 +100,19 @@ export const setContractFormEdited = (value: boolean) => (dispatch: any) => {
|
||||
// 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
|
||||
);
|
||||
|
||||
const response = await handleApiRequest(
|
||||
dispatch,
|
||||
Api.put('/contract-forms', formData),
|
||||
'CONTRACT_FORM_ERRORS',
|
||||
EDITING_CONTRACT_FORM
|
||||
);
|
||||
|
||||
if (response) {
|
||||
// API call succeeded
|
||||
dispatch(setContractFormEdited(true));
|
||||
} catch (error) {
|
||||
dispatch({ type: EDITING_CONTRACT_FORM, payload: false });
|
||||
} else {
|
||||
// API call failed
|
||||
dispatch(setContractFormEdited(false));
|
||||
}
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user