Android_Retail/src/Pages/BookingScreen/Forms/QuickAdd/QuickAdd.jsx

910 lines
32 KiB
React
Raw Normal View History

2026-01-27 18:27:29 +05:30
import { useState, useEffect, useRef, useCallback } from 'react';
import { useDispatch, useSelector } from 'react-redux';
import { Form, Tooltip } from 'antd';
import {
ArrowRightOutlined,
PlusCircleOutlined,
InfoCircleOutlined,
} from '@ant-design/icons';
import Buttons from '../../../../Components/Forms/Buttons.jsx';
import { InputField } from '../../../../Components/Forms/InputField.jsx';
import { Messages } from '../../../../Components/Notifications/Messages';
import { DropDowns } from '../../../../Components/Forms/DropDown.jsx';
import { DatePic } from '../../../../Components/Forms/DatePicker.jsx';
import { DefaultModal } from '../../../../Components/Modal/DefaultModal.jsx';
import FormHeader from '../../../PageComponents/FormHeader.jsx';
import { getSession, validateSafeInput } from '../../../../Services/Others';
import {
getAdmin,
getProductTypeData,
getUomData,
getProdCatData,
getProdSubCatData,
getProdTaxData,
getSupplierData,
productTypeDataSelector,
uomDataSelector,
prodCatDataSelector,
prodSubCatDataSelector,
prodTaxDataSelector,
taxSelector,
supplierSelector,
postProductData,
postTax,
getHsnData
} from '../../../../Features/ProductPage/ProductPage.js';
import {
ChangeNewQrProduct,
GlobalSearchData,
changeSearchedData,
getSelectedFavItems,
GlobalProductCategorie,
GlobalProductSubCategorie,
getLayoutproductCard,
getCardDataWithoutSub,
getCardDataWithoutSubmodule,
getLayoutCategories,
} from '../../../../Features/BookingScreen/BookingData/BookingData';
import {
IoCloseSharp,
} from 'react-icons/io5';
import '../../../../Styles/Product/Product.scss';
import { getTemplateData } from '../../../../Features/ThemeChange/ThemeChange';
import Imageupload from '../../../../Components/Forms/Upload.jsx';
import { ApplicationPreferences, getCommonAppPreference } from '../../../../Features/BrachLogin/BranchLogin.js';
const ProductForm = ({
formType,
handleCancelData,
ProductSearch,
ProductSearchType,
cardData,
}) => {
const dispatch = useDispatch();
const formRef = useRef(null);
const formTaxRef = useRef(null);
const ProductTypeData = useSelector(productTypeDataSelector);
const UomData = useSelector(uomDataSelector);
const ProdCatData = useSelector(prodCatDataSelector);
const ProdSubCatData = useSelector(prodSubCatDataSelector);
const ProdTaxData = useSelector(prodTaxDataSelector);
const TaxData = useSelector(taxSelector);
const SupplierData = useSelector(supplierSelector);
const ProductSearchValue = useSelector(GlobalSearchData);
const templateData = useSelector(getTemplateData);
const prodCat = useSelector(GlobalProductCategorie);
const ProdSubCat = useSelector(GlobalProductSubCategorie);
const appPreferences = useSelector(ApplicationPreferences)
const productFieldPreferences = appPreferences?.find(
(preference) => preference?.PreferredCatName?.toLowerCase() === "product form fields"
)?.PreferenceCatDetails;
const UomTypePreference = appPreferences?.find((preference) => preference?.PreferredCatName === 'Product Uom')?.PreferenceCatDetails
const UomPreference = UomData?.filter((item) => UomTypePreference?.some?.((e) => e?.PreferredSubCatName?.toLowerCase() == item?.ConfigName?.toLowerCase() && e?.PreferredStatus == "Y"))
const filteredFields = productFieldPreferences?.filter(
(field) => field?.PreferredStatus === 'Y'
);
const cessField = filteredFields?.find((field) => field?.PreferredSubCatName === 'Cess');
const hsnField = filteredFields?.find((field) => field?.PreferredSubCatName === 'HSN');
const taxField = filteredFields?.find((field) => field?.PreferredSubCatName === 'Tax');
//local states
const [messageType, setMessageType] = useState(null);
const [messageData, setMessageData] = useState(null);
const [SelectedUom, setSelectedUom] = useState(null);
const [SelectedTaxId, setSelectedTaxId] = useState(null);
const [SelectedTaxNameId, setSelectedTaxNameId] = useState(null);
const [OpenTaxModel, setOpenTaxModel] = useState(false);
const [OpenAdd, setOpenAdd] = useState(false);
const [imageBrandUrl, setBrandImageUrl] = useState('');
const [selectedHsn, setSelectedHsn] = useState("");
const [HsnDetails, setHsnDetails] = useState([]);
const CompId = getSession('CompId');
const BranchId = getSession('BranchId');
const AppId = getSession('AppId');
const UserId = getSession('UserId');
useEffect(() => {
getApplicationPreference()
dispatch(getProductTypeData());
dispatch(getUomData());
dispatch(getProdCatData({ AppId: AppId }));
dispatch(
getSupplierData({ CompId: CompId, AppId: AppId, BranchId: BranchId })
);
dispatch(getProdTaxData({ AppId: AppId }));
dispatch(getAdmin({ CompId: CompId, AppId: AppId }));
}, []);
useEffect(() => {
if (formType == 'add') {
let ProductCatId = ProdCatData?.filter(
(item) => item.ConfigName?.toLowerCase() === 'General'?.toLowerCase()
)?.[0]?.['ConfigId'];
dispatch(getProdSubCatData({ ConfigId: ProductCatId }));
}
}, [ProdCatData]);
useEffect(() => {
if (formType == 'add' && cardData?.length !== 1) {
let UomId = UomPreference?.filter(
(item) => item.ConfigName?.toLowerCase() === 'PCS'?.toLowerCase()
)?.[0]?.['ConfigId'];
formRef.current?.setFieldsValue({ UOM: UomId });
setSelectedUom(UomId);
}
}, []);
useEffect(() => {
if (formType == 'add') {
let TaxId = TaxData?.filter(
(item) => item.TaxIdName?.toLowerCase() === 'NIL'?.toLowerCase()
)?.[0]?.['TaxId'];
formRef.current?.setFieldsValue({ TaxId: TaxId });
setSelectedTaxId(TaxId);
}
}, [TaxData]);
const onComplete = useCallback(() => {
setMessageData(null);
setMessageType(null);
}, []);
const openAdditionalDetails = () => {
if (OpenAdd) {
setOpenAdd(false);
} else {
setOpenAdd(true);
}
};
const getApplicationPreference = async () => {
const response = await dispatch(getCommonAppPreference(AppId)).unwrap();
}
const handleSellPrice = (_, value) => {
return new Promise((resolve, reject) => {
if (!value) {
return reject('Selling Price is required');
}
if (!/^\d+(\.\d{1,2})?$/.test(value)) {
return reject('Invalid format (Max 2 decimal places allowed)');
}
if (parseFloat(value) === 0) {
return reject('Selling Price cannot be 0');
}
if (value.length > 10) {
return reject('MRP cannot exceed 10 digits');
}
const MRP = formRef?.current?.getFieldsValue()?.MRP || 0;
if (MRP !== undefined && parseFloat(MRP) < parseFloat(value)) {
return reject('Please enter Selling Price less than MRP');
}
return resolve();
});
};
useEffect(() => {
if (cardData?.length === 1) {
AutoProdFun();
}
}, [cardData]);
const getUOMConfigId = async (uomName) => {
if (!uomName) return getUOMConfigId('PCS');
const normalize = (str) => str?.toLowerCase().replace(/s$/, ''); // remove trailing 's'
const normalizedInput = normalize(uomName);
// Try exact match
let match = UomPreference.find(
(item) => normalize(item.ConfigName || '') === normalizedInput
);
// If no exact match, try partial match (like "kg" in "KGS")
if (!match) {
match = UomPreference.find((item) =>
normalize(item.ConfigName || '').includes(normalizedInput)
);
}
return match ? match.ConfigId : getUOMConfigId('PCS'); // fallback to PCS
};
const AutoProdFun = async () => {
const regex = /([\d.]+)\s*([a-zA-Z]+)\)?$/;
const match = cardData?.[0]?.ProdName.match(regex);
if (match) {
const quantity = parseFloat(match[1]);
const uom = match[2];
const name = cardData?.[0]?.ProdName.slice(0, match.index)
?.replace(/[\(\),]+$/, '')
?.trim();
// return { name, quantity, uom };
const configId = await getUOMConfigId(uom);
console.log(configId, 'configIdconfigIdconfigIdconfigId');
// setSelectedUom(configId)
handleUomDropDownChange(configId);
console.log(`ConfigId for ${uom}:`, configId);
formRef.current?.setFieldsValue({ ProdName: name });
formRef.current?.setFieldsValue({ Size: quantity });
formRef.current?.setFieldsValue({ BrandName: cardData?.[0]?.Brand });
setBrandImageUrl(cardData?.[0]?.ImageUrl);
} else {
const configId = await getUOMConfigId('PCS');
formRef.current?.setFieldsValue({
ProdName: cardData?.[0]?.ProdName.trim(),
});
formRef.current?.setFieldsValue({ Size: 1 });
handleUomDropDownChange(configId);
formRef.current?.setFieldsValue({ BrandName: cardData?.[0]?.Brand });
setBrandImageUrl(cardData?.[0]?.ImageUrl);
}
};
const handleUomDropDownChange = async (ConfigId) => {
formRef.current?.setFieldsValue({ UOM: ConfigId });
await setSelectedUom(ConfigId);
};
const submitTax = async () => {
const subTaxData = await formTaxRef?.current?.validateFields();
const effectiveDate = new Date(subTaxData.EffectiveFrom);
const formattedDate = effectiveDate.toLocaleDateString('en-CA');
const addTaxData = {
CompId: getSession('CompId'),
AppId: getSession('AppId'),
TaxName: subTaxData?.TaxName,
TaxPercentage: subTaxData?.TaxPercentage,
EffectiveFrom: formattedDate,
Reference: subTaxData.Reference,
CreatedBy: UserId,
};
let response = {};
response = await dispatch(postTax(addTaxData)).unwrap();
if (response?.data?.statusCode == 1) {
setMessageType('success');
setMessageData(response?.data?.response);
setOpenTaxModel(false);
dispatch(getAdmin({ CompId: CompId, AppId: AppId }));
formTaxRef?.current?.resetFields();
} else {
setMessageType('error');
setMessageData(response?.data?.response);
formTaxRef?.current?.resetFields();
}
};
const handleTax = () => {
setOpenTaxModel(false);
};
const handleTaxDropDownChange = async (TaxId) => {
formRef.current?.setFieldsValue({ TaxId: TaxId });
await setSelectedTaxId(TaxId);
};
const handleTaxNameDropDownChange = async (ConfigId) => {
formTaxRef.current?.setFieldsValue({ TaxName: ConfigId });
await setSelectedTaxNameId(ConfigId);
};
const getOptionLabel = (option, selected) => {
return selected
? option.TaxPercentage + ' %'
: option.TaxIdName + ' - ' + option.TaxPercentage + ' % ';
};
const onFinish = async (values) => {
let postData = values;
postData['CompId'] = CompId;
postData['BranchId'] = BranchId;
postData['AppId'] = AppId;
postData['ProdType'] = ProductTypeData?.filter(
(item) => item.ConfigName === 'Product'
)?.[0]?.['ConfigId'];
postData['ProdCat'] = ProdCatData?.filter(
(item) => item.ConfigName === 'General'
)?.[0]?.['ConfigId'];
postData['ProdSubCat'] = ProdSubCatData?.filter(
(item) => item.ConfigName === 'General'
)?.[0]?.['ConfigId'];
postData['QtyBasedPrice'] = 'N';
postData['ProdQtywisePriceDetails'] = [];
postData['StockAvailable'] = 'N';
postData['TokenAvailable'] = 'N';
postData['TaxId'] = SelectedTaxId;
postData['InwardDate'] = new Date().toJSON();
postData['SuppId'] = SupplierData?.filter(
(item) => item.SuppName?.toLowerCase() === 'Self'?.toLowerCase()
)?.[0]?.['SuppId'];
postData['AutoGenerateQr'] = 'N';
postData['QRCode'] =
ProductSearchValue?.length > 0 ? ProductSearchValue : null;
postData['CreatedBy'] = UserId;
postData['ProdLogo'] = imageBrandUrl;
if (SelectedTaxId) {
postData['Cess'] =
values?.Cess == undefined || values?.Cess == null || values?.Cess == ''
? 0
: values?.Cess;
}
let response = {};
if (formType === 'add') {
try {
response = await dispatch(postProductData(postData)).unwrap();
} catch (err) {
if (err['message'] == 'Request failed with status code 422') {
response = {
data: {
statusCode: 0,
response: 'Please Give Required Fields',
data: [],
},
};
}
}
}
if (response?.data?.statusCode == 1) {
setMessageType('success');
setMessageData(response?.data?.response);
formRef.current?.resetFields();
setSelectedUom(null);
setSelectedTaxId(null);
handleCancelData();
setOpenAdd(false);
if (ProductSearchType != 'C') {
let data = {
CompId: CompId,
BranchId: BranchId,
AppId: AppId,
};
await dispatch(getSelectedFavItems(data)).unwrap();
await dispatch(getLayoutCategories(data)).unwrap();
let data1 = {
CompId: CompId,
BranchId: BranchId,
AppId: AppId,
ProdSubCat: ProdSubCat,
};
let data2 = {
compId: CompId,
branchId: BranchId,
appId: AppId,
prodCat: prodCat,
};
if (
templateData?.BookingLayout?.[1]?.some(
(item) => item?.OptionName === 'SubCategory'
) &&
ProdSubCat
) {
await dispatch(getLayoutproductCard(data1)).unwrap();
} else if (
!templateData?.BookingLayout?.[1]?.some(
(item) => item?.OptionName === 'SubCategory'
)
) {
await dispatch(getCardDataWithoutSubmodule(data2)).unwrap();
} else {
await dispatch(getCardDataWithoutSub(data2)).unwrap();
}
}
let productData = response?.data?.ProductDetails?.[0];
dispatch(ChangeNewQrProduct({ ...productData, Quantity: parseInt(1) }));
if (
ProductSearchValue &&
(ProductSearchType == 'I' || ProductSearchType == 'C')
) {
dispatch(changeSearchedData([]));
}
} else {
setMessageType('error');
setMessageData(response?.data?.response);
}
};
const addTax = () => {
setOpenTaxModel(true);
};
const updateBrandImageUrl = (url) => {
setBrandImageUrl(url);
};
const handleHsnData = async (value) => {
const response = await dispatch(getHsnData(value)).unwrap();
if (response?.data?.statusCode === 1) {
const data = response?.data?.data;
setTimeout(() => {
setHsnDetails(data);
}, 1000);
}
else {
setMessageData(response?.data?.response);
setMessageType('error');
}
}
return (
<div>
<Messages
messageType={messageType}
messageData={messageData}
onComplete={onComplete}
/>
<div className="formName">
<FormHeader title={'Quick Add'} />
</div>
<div>
<Form ref={formRef} className="formDivAnt" onFinish={onFinish}>
<div>
<div className="productinputForm">
<div className="subColumnFlex">
{ProductSearch && <div>Qrcode : {ProductSearchValue}</div>}
<div className="subRowFlex">
<Form.Item
name="ProdName"
rules={[
{
required: true,
pattern: /^(?!\s*$).+/,
message: 'Please Enter Product Name',
},
{
validator: async (_, value) => {
await validateSafeInput(value);
if (value && value.length > 50) {
return Promise.reject(
'Product Name should not exceed 50 characters'
);
}
return Promise.resolve();
},
},
]}
>
<InputField
autoComplete="off"
label={<label class="required">Product Name</label>}
isOnChange={cardData?.length === 1}
/>
</Form.Item>
<Form.Item
name="Size"
rules={[
{
required: true,
message: 'Please Enter No.of.Units',
},
{
validator: async (_, value) => {
await validateSafeInput(value);
if (value && value.length > 10) {
return Promise.reject(
'No.of.Units should not exceed 10 characters'
);
}
return Promise.resolve();
},
},
]}
>
<InputField
autoComplete="off"
label={<label class="required">No.of.Units</label>}
// inputMode="decimal"
isOnChange={cardData?.length === 1}
// onInput={(e) => {
// const cleanedValue = e.target.value.replace(
// /[^0-9.]/g,
// ""
// );
// const parts = cleanedValue.split(".");
// e.target.value =
// parts.length > 2
// ? `${parts[0]}.${parts.slice(1).join("")}`
// : cleanedValue;
// }}
suffix={
<Tooltip
title={
<>
<div>Enter No.of.Units as:</div>
<div>
- Single number (<strong>5</strong>)
</div>
<div>
- Decimal (<strong>1.500 or 1.5</strong>)
</div>
<div>
- Width x Height (<strong>10x10</strong>)
</div>
</>
}
>
<InfoCircleOutlined
style={{
color: 'rgba(0,0,0,.45)',
}}
/>
</Tooltip>
}
/>
</Form.Item>
<Form.Item
name="UOM"
rules={[
{
required: true,
message: 'Please Select Uom',
},
]}
>
<DropDowns
options={UomPreference?.map((option) => ({
value: option.ConfigId,
label: option.ConfigName,
}))}
label={<label class="required">Uom</label>}
className="field-DropDown"
isOnchanges={SelectedUom ? true : false}
onChangeFunction={handleUomDropDownChange}
valueData={SelectedUom}
disabled={formType == 'edit' ? true : false}
/>
</Form.Item>
<Form.Item
name="MRP"
rules={[
{
required: true,
// pattern: /^[1-9]\d*(\.\d+)?$/,
message: 'Please Enter MRP',
},
{
validator: async (_, value) => {
await validateSafeInput(value);
if (value && value.length > 10) {
return Promise.reject(
'MRP should not exceed 10 characters'
);
}
return Promise.resolve();
},
},
]}
>
<InputField
autoComplete="off"
label={<label class="required">MRP</label>}
inputMode="decimal"
onInput={(e) => {
const cleanedValue = e.target.value?.replace(
/[^0-9.]/g,
''
);
const parts = cleanedValue.split('.');
e.target.value =
parts.length > 2
? `${parts[0]}.${parts.slice(1).join('')}`
: cleanedValue;
}}
/>
</Form.Item>
<Form.Item
name="SellPrice"
rules={[{ validator: handleSellPrice }]}
>
<InputField
autoComplete="off"
label={<label class="required">Sales Price</label>}
inputMode="decimal"
onInput={(e) => {
const cleanedValue = e.target.value?.replace(
/[^0-9.]/g,
''
);
const parts = cleanedValue.split('.');
e.target.value =
parts.length > 2
? `${parts[0]}.${parts.slice(1).join('')}`
: cleanedValue;
}}
/>
</Form.Item>
{cardData?.length === 1 && (
<Form.Item
name="BrandName"
rules={[
{
validator: async (_, value) => {
await validateSafeInput(value);
},
},
]}
>
<InputField
autoComplete="off"
isOnChange={cardData?.length === 1}
label={<label class="required">Brand</label>}
/>
</Form.Item>
)}
{cardData?.length === 1 && (
<div className="upload_btn">
<p>Upload Icon</p>
<br></br>
<Imageupload
singleImage={true}
updateImageUrl={updateBrandImageUrl}
ImageLink={imageBrandUrl ? imageBrandUrl : ''}
/>
</div>
)}
{OpenAdd && (
<>
{taxField && <Form.Item name="TaxId">
<DropDowns
options={TaxData?.map((option) => ({
value: option.TaxId,
label: getOptionLabel(
option,
SelectedTaxId === option.TaxId
),
// label: option.TaxIdName + ' - ' + option.TaxPercentage + ' % ',
}))}
label="Tax"
className="field-DropDown"
isOnchanges={SelectedTaxId ? true : false}
onChangeFunction={handleTaxDropDownChange}
valueData={SelectedTaxId}
/>
<Tooltip title="Add Tax" placement="top">
<PlusCircleOutlined onClick={addTax} />
</Tooltip>
</Form.Item>}
{cessField && <Form.Item
name="Cess"
rules={[
{
pattern: /^\d*\.?\d+$/,
message: 'Please Enter Cess',
},
{
validator: async (_, value) => {
await validateSafeInput(value); // To block HTML tags & SQL keywords
return Promise.resolve();
},
},
]}
>
<InputField autoComplete="off" label="Cess" />
</Form.Item>}
{hsnField &&
<Form.Item
name="HSNCode"
rules={[
{
message: 'Please enter HSN',
},
]}
>
<InputField
label={<label>HSN Code</label>}
id="HSNCode"
value={selectedHsn}
type="text"
list="hsnList"
maxLength={100}
autoComplete="off"
isOnChange={selectedHsn ? true : false}
onChange={(e) => {
const value = e?.target?.value || "";
setSelectedHsn(value);
if (value.length >= 1) {
formRef.current?.setFieldsValue({
HSNCode: value,
});
handleHsnData(value);
}
}}
suffix={
selectedHsn?.length >= 1 ? (
<IoCloseSharp onClick={() => setSelectedHsn("")} />
) : (
<div />
)
}
/>
{HsnDetails?.length > 0 && (
<datalist id="hsnList">
{HsnDetails
.filter((item) => {
const search = selectedHsn?.toLowerCase() || "";
return (
item.HSN_CD?.toLowerCase()?.includes(search) ||
item.Description?.toLowerCase()?.includes(search)
);
})
.map((item, index) => (
<option
key={index}
value={item.HSN_CD}
label={item.Description}
onClick={() => {
// setSelectedHsn(item.HSN_CD);
formRef.current?.setFieldsValue({
HSNCode: item.HSN_CD,
});
}}
/>
))}
</datalist>
)}
</Form.Item>
}
<Form.Item
name="WhSalePrice"
rules={[
{
pattern: /^[0-9]\d*(\.\d+)?$/,
message: 'Please Enter WholeSale',
},
{
validator: async (_, value) => {
await validateSafeInput(value); // To block HTML tags & SQL keywords
return Promise.resolve();
},
},
]}
>
<InputField
autoComplete="off"
label={<label>WholeSale</label>}
/>
</Form.Item>
</>
)}
</div>
<div className="prodAddDetails" onClick={openAdditionalDetails}>
Additional Details
</div>
</div>
</div>
<div style={{ display: 'flex', justifyContent: 'flex-end' }}>
<Buttons
buttonText="SUBMIT"
color="901D77"
icon={<ArrowRightOutlined />}
htmlType={true}
/>
</div>
</div>
</Form>
<DefaultModal
title="Add Tax"
open={OpenTaxModel}
footer={true}
buttonText="Submit"
children={
<Form ref={formTaxRef} className="formDivAnt">
<div className="subRowFlex">
<Form.Item name="TaxName">
<DropDowns
options={ProdTaxData?.map((option) => ({
value: option.ConfigId,
label: option.ConfigName,
}))}
label="Tax Name"
className="field-DropDown"
onChangeFunction={handleTaxNameDropDownChange}
valueData={SelectedTaxNameId}
/>
</Form.Item>
<Form.Item
name="TaxPercentage"
rules={[
{
required: true,
pattern: /^(?:100(?:\.0+)?|\d{1,2}(?:\.\d+)?)$/,
message: 'Please Enter Valid Percentage',
},
{
validator: async (_, value) => {
await validateSafeInput(value); // To block HTML tags & SQL keywords
return Promise.resolve();
},
},
]}
>
<InputField autoComplete="off" label="Tax Percentage" />
</Form.Item>
<Form.Item
name="Reference"
rules={[
{
pattern: /^(?!\s*$).+/,
message: 'Please Enter Reference',
},
{
validator: async (_, value) => {
await validateSafeInput(value); // To block HTML tags & SQL keywords
return Promise.resolve();
},
},
]}
>
<InputField label="Reference No" autoComplete="off" />
</Form.Item>
<div style={{ display: 'flex', marginTop: '30px' }}>
<label className="required">Effective From :</label>
<Form.Item
name="EffectiveFrom"
rules={[
{
required: true,
message: 'Please Enter Effective From',
},
]}
>
<DatePic
field="EffectiveFrom"
type="number"
min={1}
max={100}
fieldState={true}
/>
</Form.Item>
</div>
</div>
</Form>
}
handleSubmit={submitTax}
handleCancel={handleTax}
></DefaultModal>
</div>
</div>
);
};
export default ProductForm;