diff --git a/src/components/BMDashboard/AddMaterial/AddMaterial.css b/src/components/BMDashboard/AddMaterial/AddMaterial.css index eede7869c7..1b2637dc73 100644 --- a/src/components/BMDashboard/AddMaterial/AddMaterial.css +++ b/src/components/BMDashboard/AddMaterial/AddMaterial.css @@ -1,99 +1,110 @@ -.materialContainer { - padding: 0% !important; - min-height: 100vh; -} - -.materialPage { - background-color: #E8F4F9; +.add-material-form{ width: 100%; - min-height: 100%; - margin: 0px; - padding: 1rem 2rem; - font-size: 15px; } -.material { - background-color: white; - border-radius: 1rem; - padding: 1.5rem; - height: auto; +.add-material-form Label{ +font-size: .8em; } -.materialTitle { - font-weight: bold; - margin-bottom: 20px; +.add-material-flex-group{ + display: flex; + gap: 2rem; } -.materialImage { - height: 10rem; - width: 10rem; - margin-bottom: 1rem; +.add-material-buttons{ + display: flex; + gap: 1rem; + margin: 1rem auto 2rem; } -.materialFormField { - margin-bottom: .5rem; +.add-material-buttons button{ + width: 50%; } -.materialFormLabel { - font-weight: 500; - font-size: 15px; - color: #1C8BCC; - display: flex; - justify-content: flex-start; +.add-material-total-price{ + display: flex; + border-radius: 10px; + line-height: 3rem; + font-weight: bolder; + background-color: #f5e6e6; + padding-left: 1em; } -.materialFormError { - font-weight: 500; - font-size: 11px !important; - color: red; - display: flex; - justify-content: flex-start; +.add-material-total-price div{ + width: 50%; } -.materialFormTableError { - font-weight: 500; - font-size: 11px !important; - color: red; +.total-price-calculated{ + text-align: end; + padding-right: 1em; } -.materialFormErrorClr { - color: red; +.add-material-createdby{ + display: flex; + border-radius: 10px; + line-height: 3rem; + font-weight: bolder; + background-color: #add3ef; + padding-left: 1em; + margin-top: 1em; } -.materialFormSmallText { - font-size: xx-small; - color: gray; +.add-material-createdby div{ + width: 50%; } -.materialFormText { - color: darkslategrey; +.createdby{ + text-align: end; + padding-right: 1em; } -.materialMargin { - margin-top: 10px; +.file-preview-container{ + display: flex; + flex-direction: row; + gap: 0.5rem; + width: 100%; + margin-top: 1rem; } -.materialFormValue { - font-weight: 400; - font-size: 15px; +.materialFormError { + font-weight: 500; + font-size: 11px !important; + color: red; display: flex; justify-content: flex-start; } -.materialButtonOutline { - color: #2E5061 !important; +.field-required{ + color: red; } -.materialButtonOutline:hover { - background-color: #2E5061 !important; - color: white !important +@media screen and (max-width: 640px) { + .add-material-flex-group { + display: block; + } + + .file-preview-container{ + display: block; + } } -.materialButtonBg { - background-color: #2E5061 !important +.add-material-container{ + width: 100%; + max-width: 800px; + margin: 1rem auto; + padding: 1rem 2rem; + border: 1px solid #ccc; + border-radius: 20px; } -.materialButtonBg:hover { - background-color: white !important; - color: #2E5061 !important; -} \ No newline at end of file +.add-material-header h2{ + font-size: clamp(1.5rem, 2.5vw, 2.5rem); + margin-left: 1rem; +} + + +@media screen and (max-width: 800px) { + .add-material-container { + width: 95%; + } +} diff --git a/src/components/BMDashboard/AddMaterial/AddMaterial.jsx b/src/components/BMDashboard/AddMaterial/AddMaterial.jsx index b0a23b5241..14dd802a62 100644 --- a/src/components/BMDashboard/AddMaterial/AddMaterial.jsx +++ b/src/components/BMDashboard/AddMaterial/AddMaterial.jsx @@ -1,66 +1,60 @@ -import { - Col, - Container, - Form, - FormGroup, - Input, - Label, - Button, - CardBody, - Card, - Table, -} from 'reactstrap'; -import './AddMaterial.css'; +import { useState, useEffect } from 'react'; +import { Form, FormGroup, Label, Input, Button } from 'reactstrap'; +import PhoneInput from 'react-phone-input-2'; +import { toast } from 'react-toastify'; import { useDispatch, useSelector } from 'react-redux'; -import { useEffect } from 'react'; -import { useState } from 'react'; import Joi from 'joi'; -import { toast } from 'react-toastify'; import { fetchMaterialTypes, postBuildingInventoryType, resetPostBuildingInventoryTypeResult, -} from 'actions/bmdashboard/invTypeActions'; -import { fetchInvUnits } from 'actions/bmdashboard/invUnitActions'; -import Select from 'react-select'; -import { similarity } from './SimilarityCheck'; +} from '../../../actions/bmdashboard/invTypeActions'; +import { fetchInvUnits } from '../../../actions/bmdashboard/invUnitActions'; +import { boxStyle } from '../../../styles'; +import './AddMaterial.css'; +import DragAndDrop from '../../common/DragAndDrop/DragAndDrop'; + +const initialFormState = { + // project: 'Project1', + name: '', + invoice: '', + unitPrice: '', + currency: 'USD', + quantity: '', + unit: '', + purchaseDate: '', + shippingFee: '', + taxes: '', + areaCode: '+1', + phoneNumber: '', + images: [], + link: '', + description: '', +}; -function AddMaterial() { +export default function AddMaterialForm() { + const [formData, setFormData] = useState(initialFormState); + const [areaCode, setAreaCode] = useState('1'); + const [phoneNumber, setPhoneNumber] = useState(''); + const [uploadedFiles, setUploadedFiles] = useState([]); // log here for correct state snapshot (will show each render) + const [errors, setErrors] = useState({}); const dispatch = useDispatch(); const postBuildingInventoryResult = useSelector(state => state.bmInvTypes.postedResult); - const buildingInventoryUnits = useSelector(state => state.bmInvUnits.list); - const [formattedUnits, setFormattedUnits] = useState([]); - const [similarityData, setSimilarityData] = useState([]); - - const [material, setMaterial] = useState({ - name: '', - unit: '', - customUnit: '', - customUnitCheck: false, - description: '', - allowNewMeasurement: false, - }); - const [validations, setValidations] = useState({ - name: '', - unit: '', - customUnit: '', - description: '', - commonUnit: '', - customUnitCheck: '', - total: true, - }); + const materialTypes = useSelector(state => state.bmInvTypes.list); + const [selectedMaterial, setSelectedMaterial] = useState(''); + const [newMaterial, setNewMaterial] = useState(''); + const [showTextbox, setShowTextbox] = useState(false); + const [selectedUnit, setSelectedUnit] = useState(''); + const [newUnit, setNewUnit] = useState(''); + const units = useSelector(state => state.bmInvUnits.list); + // console.log(materialTypes); + // console.log(units) + const createdBy = useSelector(state => state.auth.user.email); useEffect(() => { dispatch(fetchMaterialTypes()); dispatch(fetchInvUnits()); - }, []); - - useEffect(() => { - const _formattedUnits = buildingInventoryUnits.map(proj => { - return { label: proj.unit, value: proj.unit }; - }); - setFormattedUnits(_formattedUnits); - }, [buildingInventoryUnits]); + }, [dispatch]); useEffect(() => { if (postBuildingInventoryResult?.error === true) { @@ -76,358 +70,439 @@ function AddMaterial() { } }, [postBuildingInventoryResult]); - const obj = { + useEffect(() => { + dispatch(fetchMaterialTypes()); + }, [dispatch]); + + const validationObj = { name: Joi.string() .min(3) - .max(50) + .max(15) + .required(), + unit: Joi.string() + .min(1) + .max(15) .required(), description: Joi.string() - .min(10) - .max(150) + .min(5) + .max(500) .required(), - unit: Joi.optional(), - customUnit: Joi.string() - .allow('') - .optional(), + invoice: Joi.string().required(), + quantity: Joi.number() + .min(1) + .max(999) + .integer() + .required(), + unitPrice: Joi.number() + .min(1) + .required(), + purchaseDate: Joi.date().required(), + createdBy, }; - const schema = Joi.object(obj).options({ abortEarly: false, allowUnknown: true }); - const validationHandler = (field, value, complete) => { - let validate; - let propertySchema; - let validationErrorFlag = false; - if (complete) { - validate = schema.validate(material); - } else if (field !== 'customUnitCheck' && field !== 'allowNewMeasurement') { - propertySchema = Joi.object({ [field]: obj[field] }); - validate = propertySchema.validate({ [field]: value }); - } + const schema = Joi.object(validationObj).unknown(); - if (!material.unit && !material.customUnit) { - if (complete || field === 'unit' || field === 'customUnit') { - validations.commonUnit = 'At least one of "unit" or "customUnit" must have a valid value'; - validationErrorFlag = true; - } - } else if (material.unit && material.customUnit) { - if (complete || field === 'unit' || field === 'customUnit') { - validations.commonUnit = 'Only one of the unit should have a value'; - validationErrorFlag = true; - } - } else { - validations.commonUnit = ''; - } + const validate = data => { + const result = schema.validate(data, { abortEarly: false }); + if (!result.error) return null; - if (validate?.error) { - for (let i = 0; i < validate.error.details.length; i += 1) { - const errorObj = validate.error.details[i]; - if (errorObj.context.peersWithLabels) { - for (let j = 0; j < errorObj.context.peersWithLabels.length; j += 1) { - validations[errorObj.context.peersWithLabels[j]] = errorObj.message; - validationErrorFlag = true; - } - } else validations[errorObj.context.label] = errorObj.message; - validationErrorFlag = true; - } - } else if (!complete) { - validations[field] = ''; - } + const errorMessages = {}; + result.error.details.forEach(detail => { + errorMessages[detail.path[0]] = detail.message; + }); + return errorMessages; + }; - if (material.customUnit !== '') { - const _similarityData = []; - for (let i = 0; i < buildingInventoryUnits.length; i += 1) { - const similarityPercent = similarity(buildingInventoryUnits[i].unit, material.customUnit); - // console.log(buildingInventoryUnits[i].unit, similarityPercent) - if (similarityPercent > 0.5) { - const simObj = { - unitFromStore: buildingInventoryUnits[i].unit, - similarityPercent: similarityPercent * 100, - }; - _similarityData.push(simObj); - } - } - setSimilarityData([..._similarityData]); - } else { - const _similarityData = []; - setSimilarityData([..._similarityData]); - } - if (complete && similarityData.length !== 0 && !material.customUnitCheck) { - validationErrorFlag = validationErrorFlag || true; - validations.customUnitCheck = 'Please confirm or select a unit from available ones'; - } else { - validationErrorFlag = validationErrorFlag || false; - validations.customUnitCheck = ''; - } + const handleInputChange = (name, value) => { + setFormData(prevData => ({ + ...prevData, + [name]: value, + })); + }; - validations.total = validationErrorFlag; + const { unitPrice, quantity, taxes, shippingFee } = formData; - setValidations({ ...validations }); - return validationErrorFlag; - }; + const calculateTotalPrice = (price, totalQuantity) => price * totalQuantity; + const calculateTotalTax = (taxPercentage, totalPrice) => (taxPercentage * totalPrice) / 100; + + const totalPrice = calculateTotalPrice(unitPrice, quantity); + const totalTax = calculateTotalTax(Number(taxes), totalPrice); + const totalPriceWithShipping = (totalPrice + totalTax + Number(shippingFee)).toFixed(2); - const unitSelectHandler = value => { - material.customUnit = ''; - material.customUnitCheck = false; - material.allowNewMeasurement = false; - material.unit = value; - setMaterial({ ...material }); + const phoneChange = (name, phone) => { + setFormData(prevData => ({ + ...prevData, + [name]: phone, + })); }; - const changeHandler = e => { - const field = e.target.name; - const { value } = e.target; - material[field] = value; - if (field === 'customUnit') { - if (value) { - material.unit = ''; - } + const handleSubmit = async event => { + event.preventDefault(); + const validationErrors = validate(formData); + setErrors(validationErrors || {}); + + if (validationErrors) { + return; } - // if (field === 'unit') { - // if (value !== '') { - // material.customUnit = ''; - // material.customUnitCheck = false; - // material.allowNewMeasurement = false; - // } + const imageURL = uploadedFiles.map(file => URL.createObjectURL(file)); + const updatedFormData = { + ...formData, + category: 'Material', + images: imageURL[0], + areaCode, + phoneNumber, + totalPriceWithShipping, + }; + dispatch(postBuildingInventoryType(updatedFormData)); + setSelectedMaterial(''); + setSelectedUnit(''); + setNewMaterial(''); // Reset newMaterial + setShowTextbox(false); + setFormData(initialFormState); + setUploadedFiles([]); + setAreaCode(1); + setPhoneNumber(''); // } - if (field === 'customUnitCheck' || field === 'allowNewMeasurement') { - material[field] = e.target.checked; - } - setMaterial({ ...material }); - if (field !== null) validationHandler(field, value); + // TODO: validate form data + // TODO: submit data to API }; - const submitHandler = () => { - const error = validationHandler(null, null, true); - if (!error) { - // formatted for react-select - const _material = { ...material }; - _material.unit = material.unit?.value; - dispatch(postBuildingInventoryType(_material)); - } + const handleCancelClick = () => { + setSelectedMaterial(''); + setSelectedUnit(''); + setFormData(initialFormState); + setUploadedFiles([]); + setAreaCode(1); + setPhoneNumber(''); }; - return ( -