src/Components/BiddingFunctionsPage/BiddingTool/BiddingToolCard/BiddingToolCard.jsx
import { useEffect, useState } from 'react';
import { useDispatch, useSelector } from 'react-redux';
import { Link } from 'react-router-dom';
import Linkify from 'react-linkify';
import FA from 'react-fontawesome';
import TextareaAutosize from 'react-textarea-autosize';
import swal from '@sweetalert/with-react';
import PropTypes from 'prop-types';
import shortid from 'shortid';
import { isEqual } from 'lodash';
import { userHasPermissions } from 'utilities';
import TabbedCard from 'Components/TabbedCard';
import { Row } from 'Components/Layout';
import { Definition } from '../../../DefinitionList';
import { biddingTool, biddingToolCreate, biddingToolCreateData, biddingToolDelete, biddingToolEdit } from '../../../../actions/biddingTool';
import Spinner from '../../../Spinner/Spinner';
import CheckBox from '../../../CheckBox/CheckBox';
import { history } from '../../../../store';
// eslint-disable-next-line
const BiddingToolCard = (props) => {
const { id, location: routeLocation } = props;
const isCreate = id === 'new';
const rootLocation = () => {
const routeHistory = routeLocation.split('/');
routeHistory.pop();
return routeHistory.join('/');
};
const dispatch = useDispatch();
const userProfile = useSelector(state => state.userProfile);
const isSuperUser = userHasPermissions(['superuser'], userProfile.permission_groups);
// ========================== DATA ==========================
const result = (isCreate ?
useSelector(state => state.biddingToolCreateDataSuccess) :
useSelector(state => state.biddingTool)) || {};
const resultIsLoading = (isCreate ?
useSelector(state => state.biddingToolCreateDataLoading) :
useSelector(state => state.biddingToolFetchDataLoading)) || false;
const locations = result?.locations || [];
const statuses = result?.statuses || [];
const tods = result?.tods || [];
const unaccompaniedStatuses = result?.unaccompanied_statuses || [];
const housingTypes = result?.housing_types || [];
const quartersTypes = result?.quarters_types || [];
const ehcps = result?.ehcps || [];
const location$ = locations.find(o => o.code === result?.location);
useEffect(() => {
if (isCreate) {
dispatch(biddingToolCreateData());
} else {
dispatch(biddingTool(id));
}
}, []);
const initialValues = isCreate ? {
location: locations?.[0]?.code || '',
status: statuses?.[0]?.code || '',
tod: tods?.[0]?.code || '',
unaccompanied_status: unaccompaniedStatuses?.[0]?.code || '',
housing: housingTypes?.[0]?.code || '',
quarters: quartersTypes?.[0]?.code || '',
efm_issues: ehcps?.[0]?.code || '',
snd: 'N',
hds: 'N',
apo_fpo_dpo: 'N',
consumable_allowance: 'N',
fm_fp: 'N',
inside_efm_employment: 'N',
outside_efm_employment: 'N',
cola: '0',
differential_rate: '0',
danger_pay: '0',
climate_zone: '0',
rr_point: '',
medical: '',
remarks: '',
quarters_remark: '',
special_ship_allowance: '',
school_year: '',
grade_education: '',
efm_employment: '',
} : {
location: result?.location,
status: result?.status,
tod: result?.tod,
unaccompanied_status: result?.unaccompanied_status,
housing: result?.housing,
quarters: result?.quarters,
efm_issues: result?.efm_issues,
snd: result?.snd ?? 'N',
hds: result?.hds ?? 'N',
apo_fpo_dpo: result?.apo_fpo_dpo ?? 'N',
consumable_allowance: result?.consumable_allowance ?? 'N',
fm_fp: result?.fm_fp ?? 'N',
inside_efm_employment: result?.inside_efm_employment ?? 'N',
outside_efm_employment: result?.outside_efm_employment ?? 'N',
cola: result?.cola,
differential_rate: result?.differential_rate,
danger_pay: result?.danger_pay,
climate_zone: result?.climate_zone,
rr_point: result?.rr_point ?? '',
medical: result?.medical ?? '',
remarks: result?.remarks ?? '',
quarters_remark: result?.quarters_remark ?? '',
special_ship_allowance: result?.special_ship_allowance ?? '',
school_year: result?.school_year ?? '',
grade_education: result?.grade_education ?? '',
efm_employment: result?.efm_employment ?? '',
};
const [editMode, setEditMode] = useState(false);
const [values, setValues] = useState(initialValues);
useEffect(() => {
if (editMode) {
setValues({
location: result?.location,
status: result?.status,
tod: result?.tod,
unaccompanied_status: result?.unaccompanied_status,
housing: result?.housing,
quarters: result?.quarters,
efm_issues: result?.efm_issues,
snd: result?.snd ?? 'N',
hds: result?.hds ?? 'N',
apo_fpo_dpo: result?.apo_fpo_dpo ?? 'N',
consumable_allowance: result?.consumable_allowance ?? 'N',
fm_fp: result?.fm_fp ?? 'N',
inside_efm_employment: result?.inside_efm_employment ?? 'N',
outside_efm_employment: result?.outside_efm_employment ?? 'N',
cola: result?.cola,
differential_rate: result?.differential_rate,
danger_pay: result?.danger_pay,
climate_zone: result?.climate_zone,
rr_point: result?.rr_point ?? '',
medical: result?.medical ?? '',
remarks: result?.remarks ?? '',
quarters_remark: result?.quarters_remark ?? '',
special_ship_allowance: result?.special_ship_allowance ?? '',
school_year: result?.school_year ?? '',
grade_education: result?.grade_education ?? '',
efm_employment: result?.efm_employment ?? '',
});
} else {
setValues({
location: locations?.[0]?.code || '',
status: statuses?.[0]?.code || '',
tod: tods?.[0]?.code || '',
unaccompanied_status: unaccompaniedStatuses?.[0]?.code || '',
housing: housingTypes?.[0]?.code || '',
quarters: quartersTypes?.[0]?.code || '',
efm_issues: ehcps?.[0]?.code || '',
snd: 'N',
hds: 'N',
apo_fpo_dpo: 'N',
consumable_allowance: 'N',
fm_fp: 'N',
inside_efm_employment: 'N',
outside_efm_employment: 'N',
cola: '0',
differential_rate: '0',
danger_pay: '0',
climate_zone: '0',
rr_point: '',
medical: '',
remarks: '',
quarters_remark: '',
special_ship_allowance: '',
school_year: '',
grade_education: '',
efm_employment: '',
});
}
}, [editMode, result]);
// ========================== VIEW MODE ==========================
/* eslint-disable quote-props */
const sections = [
{ 'TOD': tods.find(o => o.code === result?.tod)?.description || 'None Listed' },
{ 'R & R Point': result?.rr_point || 'None Listed' },
{ 'COLA': result?.cola?.toString() || 'None Listed' },
{ 'Differential Rate': result?.differential_rate?.toString() || 'None Listed' },
{ 'Consumable Allowance': result?.consumable_allowance === 'Y' ? 'Yes' : 'No' },
{ 'APO/FPO/DPO': result?.apo_fpo_dpo === 'Y' ? 'Yes' : 'No' },
{ 'Danger Pay': result?.danger_pay?.toString() || 'None Listed' },
{ 'SND': result?.snd === 'Y' ? 'Yes' : 'No' },
{ 'HDS': result?.hds === 'Y' ? 'Yes' : 'No' },
{ 'Unaccompanied Status': unaccompaniedStatuses.find(o => o.code === result?.unaccompanied_status)?.description || 'None Listed' },
{ 'Housing Type': housingTypes.find(o => o.code === result?.housing_type)?.description || 'None Listed' },
{ 'Quarters': quartersTypes.find(o => o.code === result?.quarters_type)?.description || 'None Listed' },
{ 'EFM Issues': ehcps.find(o => o.code === result?.efm_issues)?.description || 'None Listed' },
];
const textAreas = [{
label: 'School Year',
name: 'school-year',
value: result?.school_year ?? '',
}, {
label: 'Grade, Adequater Education at Post',
name: 'grade-education',
value: result?.grade_education ?? '',
}, {
label: 'EFM Employment Opportunities',
name: 'efm-employment',
value: result?.efm_employment ?? '',
}, {
label: 'Medical',
name: 'medical',
value: result?.medical ?? '',
}, {
label: 'Remarks',
name: 'remarks',
value: result?.remarks ?? '',
}];
/* eslint-enable quote-props */
const readView = (
<div>
<Row fluid className="position-content--section position-content--subheader">
<div className="line-separated-fields">
<div>
<span className="span-label">Post/Country/Code:</span>
<span className="span-text">
{location$?.state_country || 'None Listed'}, {location$?.code || 'None Listed'}
</span>
</div>
</div>
{(isSuperUser && !editMode) &&
<button className="toggle-edit-mode" onClick={() => setEditMode(!editMode)}>
<FA name="pencil" />
<div>Edit</div>
</button>
}
</Row>
<Row fluid className="position-content--section position-content--details">
<dl className="definitions">
{sections.map(item => {
const key = Object.keys(item)[0];
return (
<Definition
key={shortid.generate()}
term={key}
definition={item[key]}
excludeColon
/>
);
})}
</dl>
</Row>
<div>
{textAreas.map(t => (
<Row key={t.name} fluid className="position-content--description">
<span className="definition-title">{t.label}</span>
<Linkify properties={{ target: '_blank' }}>
<TextareaAutosize
maxRows={6}
minRows={6}
maxLength="4000"
name={t.name}
placeholder="No Description"
defaultValue={t.value}
disabled
className="disabled-input"
draggable={false}
/>
</Linkify>
<div className="word-count">
{t.value?.length} / 4,000
</div>
</Row>
))}
</div>
<div className="position-form--actions">
<Link to={rootLocation()}>Back</Link>
</div>
</div>
);
// ========================== EDIT MODE ==========================
const onSubmit = () => {
if (isCreate) {
dispatch(biddingToolCreate(values));
} else {
dispatch(biddingToolEdit(
{
...values,
updater_id: result?.updater_id,
updated_date: result?.updated_date,
},
() => { setEditMode(false); history.go(0); },
));
}
};
const onCancel = () => {
if (isCreate) history.push(rootLocation());
else if (setEditMode) setEditMode(false);
};
const showCancelModal = () => {
if (isEqual(initialValues, values)) {
onCancel();
} else {
swal({
title: 'Confirm Discard Changes',
button: false,
closeOnEsc: true,
content: (
<div className="simple-action-modal">
<div className="help-text">
{isCreate ?
<span>Are you sure you want to discard this Bidding Tool draft?</span> :
<span>Are you sure you want to discard all changes made to this Bidding Tool?</span>
}
</div>
<div className="modal-controls">
<button onClick={() => { onCancel(); swal.close(); }}>Yes</button>
<button className="usa-button-secondary" onClick={() => swal.close()}>No</button>
</div>
</div>
),
});
}
};
const onDelete = () => {
dispatch(biddingToolDelete(
{
location: id,
updater_id: result?.updater_id,
updated_date: result?.updated_date,
},
() => swal.close(),
));
};
const showDeleteModal = () => {
swal({
title: 'Confirm Delete Bidding Tool',
button: false,
closeOnEsc: true,
content: (
<div className="simple-action-modal">
<div className="help-text">
<span>
Are you sure you want to delete this Bidding Tool? This action cannot but undone.
</span>
</div>
<div className="modal-controls">
<button onClick={onDelete}>Yes</button>
<button className="usa-button-secondary" onClick={() => swal.close()}>No</button>
</div>
</div>
),
});
};
const editView = (
<div>
<div className="position-form">
<div className="position-form--inputs">
<div className="position-form--label-input-container">
<label htmlFor="gsa-location">GSA Location</label>
<select
id="location"
value={values.location}
onChange={(e) => setValues({ ...values, location: e.target.value })}
>
<option value="" disabled>Select Location</option>
{locations?.map(b => (
<option key={b.code} value={b.code}>{b.state_country}</option>
))}
</select>
</div>
<div className="position-form--label-input-container">
<label htmlFor="status">Status</label>
<select
id="status"
value={values.status}
onChange={(e) => setValues({ ...values, status: e.target.value })}
>
<option value="" disabled>Select Status</option>
{statuses?.map(b => (
<option key={b.code} value={b.code}>{b.description}</option>
))}
</select>
</div>
<div className="position-form--label-input-container mt-30">
<CheckBox
label="SND"
id="snd"
onCheckBoxClick={e => setValues({ ...values, snd: e ? 'Y' : 'N' })}
value={values.snd === 'Y'}
/>
</div>
<div className="position-form--label-input-container mt-30">
<CheckBox
label="HDS"
id="hds"
onCheckBoxClick={e => setValues({ ...values, hds: e ? 'Y' : 'N' })}
value={values.hds === 'Y'}
/>
</div>
<div className="position-form--label-input-container">
<label htmlFor="tod">TOD</label>
<select
id="tod"
value={values.tod}
onChange={(e) => setValues({ ...values, tod: e.target.value })}
>
<option value="" disabled>Select TOD</option>
{tods?.map(b => (
<option key={b.code} value={b.code}>{b.description}</option>
))}
</select>
</div>
<div className="position-form--label-input-container">
<label htmlFor="rr-point">R & R Point</label>
<input
id="rr-point"
defaultValue={values.rr_point}
onChange={(e) => setValues({ ...values, rr_point: e.target.value })}
/>
</div>
<div className="position-form--label-input-container">
<label htmlFor="unaccompanied-status">Unaccompanied Status</label>
<select
id="unaccompanied-status"
value={values.unaccompanied_status}
onChange={(e) => setValues({ ...values, unaccompanied_status: e.target.value })}
>
<option value="" disabled>Select Unaccompanied Status</option>
{unaccompaniedStatuses?.map(b => (
<option key={b.code} value={b.code}>{b.description}</option>
))}
</select>
</div>
<div className="position-form--label-input-container mt-30">
<CheckBox
label="APO/FPO/DPO"
id="apo"
onCheckBoxClick={e => setValues({ ...values, apo_fpo_dpo: e ? 'Y' : 'N' })}
value={values.apo_fpo_dpo === 'Y'}
/>
</div>
<div className="position-form--label-input-container">
<label htmlFor="cola">COLA (0-160)</label>
<input
id="cola"
type="number"
min="0"
max="160"
value={values.cola}
onChange={(e) => setValues({ ...values, cola: e.target.value })}
/>
</div>
<div className="position-form--label-input-container">
<label htmlFor="differential-rate">Differential Rate</label>
<input
id="differential-rate"
type="number"
min="0"
value={values.differential_rate}
onChange={(e) => setValues({ ...values, differential_rate: e.target.value })}
/>
</div>
<div className="position-form--label-input-container">
<label htmlFor="danger-pay">Danger Pay (0-35)</label>
<input
id="danger-pay"
type="number"
min="0"
max="35"
value={values.danger_pay}
onChange={(e) => setValues({ ...values, danger_pay: e.target.value })}
/>
</div>
</div>
<div className="position-form--label-input-container">
<Row fluid className="position-form--description">
<span className="definition-title">Medical</span>
<Linkify properties={{ target: '_blank' }}>
<TextareaAutosize
maxRows={6}
minRows={6}
maxLength="4000"
name="medical"
placeholder="No Description"
defaultValue={values.medical}
onChange={(e) => setValues({ ...values, medical: e.target.value })}
draggable={false}
/>
</Linkify>
<div className="word-count">
{values?.medical?.length} / 4,000
</div>
</Row>
</div>
<div className="position-form--label-input-container">
<Row fluid className="position-form--description">
<span className="definition-title">Remarks</span>
<Linkify properties={{ target: '_blank' }}>
<TextareaAutosize
maxRows={6}
minRows={6}
maxLength="4000"
name="remarks"
placeholder="No Description"
defaultValue={values.remarks}
onChange={(e) => setValues({ ...values, remarks: e.target.value })}
draggable={false}
/>
</Linkify>
<div className="word-count">
{values?.remarks?.length} / 4,000
</div>
</Row>
</div>
<div className="position-form--inputs">
<div className="position-form--label-input-container">
<label htmlFor="climate-zone">Climate Zone</label>
<input
id="climate-zone"
type="number"
min="0"
value={values.climate_zone}
onChange={(e) => setValues({ ...values, climate_zone: e.target.value })}
/>
</div>
<div className="position-form--label-input-container mt-30">
<CheckBox
label="Consumable Allowance"
id="consumable-allowance"
onCheckBoxClick={e => setValues({ ...values, consumable_allowance: e ? 'Y' : 'N' })}
value={values.consumable_allowance === 'Y'}
/>
</div>
<div className="position-form--label-input-container mt-30">
<CheckBox
label="FM/FP Accepted"
id="fm-fp"
onCheckBoxClick={e => setValues({ ...values, fm_fp: e ? 'Y' : 'N' })}
value={values.fm_fp === 'Y'}
/>
</div>
<div className="position-form--label-input-container">
<label htmlFor="housing-type">Housing Type</label>
<select
id="housing"
value={values.housing}
onChange={(e) => setValues({ ...values, housing: e.target.value })}
>
<option value="" disabled>Select Housing Type</option>
{housingTypes?.map(b => (
<option key={b.code} value={b.code}>{b.description}</option>
))}
</select>
</div>
<div className="position-form--label-input-container">
<label htmlFor="qtrs-type">Qtrs. Type</label>
<select
id="quarters"
value={values.quarters}
onChange={(e) => setValues({ ...values, quarters: e.target.value })}
>
<option value="" disabled>Select Quarters Type</option>
{quartersTypes?.map(b => (
<option key={b.code} value={b.code}>{b.description}</option>
))}
</select>
</div>
</div>
<div className="position-form--label-input-container">
<Row fluid className="position-form--description">
<span className="definition-title">Quarters Remark</span>
<Linkify properties={{ target: '_blank' }}>
<TextareaAutosize
maxRows={6}
minRows={6}
maxLength="4000"
name="quarters-remark"
placeholder="No Description"
defaultValue={values.quarters_remark}
onChange={(e) => setValues({ ...values, quarters_remark: e.target.value })}
draggable={false}
/>
</Linkify>
<div className="word-count">
{values?.quarters_remark?.length} / 4,000
</div>
</Row>
</div>
<div className="position-form--label-input-container">
<Row fluid className="position-form--description">
<span className="definition-title">Special Ship. Allow.</span>
<Linkify properties={{ target: '_blank' }}>
<TextareaAutosize
maxRows={6}
minRows={6}
maxLength="4000"
name="special-ship-allowance"
placeholder="No Description"
defaultValue={values.special_ship_allowance}
onChange={(e) => setValues({ ...values, special_ship_allowance: e.target.value })}
draggable={false}
/>
</Linkify>
<div className="word-count">
{values?.special_ship_allowance?.length} / 4,000
</div>
</Row>
</div>
<div className="position-form--label-input-container">
<Row fluid className="position-form--description">
<span className="definition-title">School Year Text</span>
<Linkify properties={{ target: '_blank' }}>
<TextareaAutosize
maxRows={6}
minRows={6}
maxLength="4000"
name="school-year"
placeholder="No Description"
defaultValue={values.school_year}
onChange={(e) => setValues({ ...values, school_year: e.target.value })}
draggable={false}
/>
</Linkify>
<div className="word-count">
{values?.school_year?.length} / 4,000
</div>
</Row>
</div>
<div className="position-form--label-input-container">
<Row fluid className="position-form--description">
<span className="definition-title">Ed. Grades At Post</span>
<Linkify properties={{ target: '_blank' }}>
<TextareaAutosize
maxRows={6}
minRows={6}
maxLength="4000"
name="grade-education"
placeholder="No Description"
defaultValue={values.grade_education}
onChange={(e) => setValues({ ...values, grade_education: e.target.value })}
draggable={false}
/>
</Linkify>
<div className="word-count">
{values?.grade_education?.length} / 4,000
</div>
</Row>
</div>
<div className="position-form--label-input-container">
<Row fluid className="position-form--description">
<span className="definition-title">Employment Opportunities</span>
<Linkify properties={{ target: '_blank' }}>
<TextareaAutosize
maxRows={6}
minRows={6}
maxLength="4000"
name="efm-employment"
placeholder="No Description"
defaultValue={values.efm_employment}
onChange={(e) => setValues({ ...values, efm_employment: e.target.value })}
draggable={false}
/>
</Linkify>
<div className="word-count">
{values?.efm_employment?.length} / 4,000
</div>
</Row>
</div>
<div className="position-form--inputs">
<div className="position-form--label-input-container">
<label htmlFor="qtrs-type">EFM Issues</label>
<select
id="efm-issues"
value={values.efm_issues}
onChange={(e) => setValues({ ...values, efm_issues: e.target.value })}
>
<option value="" disabled>Select EFM Issues</option>
{ehcps.map(b => (
<option key={b.code} value={b.code}>{b.description}</option>
))}
</select>
</div>
<div className="position-form--label-input-container mt-30">
<CheckBox
label="Mission EFM EMP"
id="mission-efm-emp"
onCheckBoxClick={e => setValues({ ...values, inside_efm_employment: e ? 'Y' : 'N' })}
value={values.inside_efm_employment === 'Y'}
/>
</div>
<div className="position-form--label-input-container mt-30">
<CheckBox
label="Outside EFM EMP"
id="outside-efm-emp"
onCheckBoxClick={e => setValues({ ...values, outside_efm_employment: e ? 'Y' : 'N' })}
value={values.outside_efm_employment === 'Y'}
/>
</div>
</div>
</div>
{isCreate ?
<div className="position-form--actions">
<button onClick={showCancelModal}>Cancel</button>
<button onClick={onSubmit}>Create</button>
</div> :
<div className="position-form--actions split-actions">
<button onClick={showDeleteModal}>
<FA name="trash" />
Remove from Bid List
</button>
<div>
<button onClick={showCancelModal}>Cancel</button>
<button onClick={onSubmit}>Save Bidding Tool</button>
</div>
</div>
}
</div>
);
return ((resultIsLoading && !isCreate) ?
<Spinner type="bidding-tool" size="small" /> :
<TabbedCard
tabs={[{
text: 'Bidding Tool Overview',
value: 'OVERVIEW',
content: (
<div className="position-content--container">
<div className="position-content">
{(editMode || isCreate) ? editView : readView}
</div>
</div >
),
}]}
/>
);
};
BiddingToolCard.propTypes = {
id: PropTypes.string.isRequired,
location: PropTypes.string.isRequired,
};
BiddingToolCard.defaultProps = {
id: 'new',
location: '/profile/',
};
export default BiddingToolCard;