Merge pull request 'Fix #5: Support custom fields in Quick Add screen' (#17) from feature/5-quick-add-custom-fields into main

Reviewed-on: Pozomind/pozo-retail-app#17
This commit is contained in:
karthikalakshmi 2026-01-28 23:29:21 -05:00
commit ee474db911
2 changed files with 220 additions and 88 deletions

View File

@ -6,6 +6,7 @@ import {
PlusCircleOutlined, PlusCircleOutlined,
InfoCircleOutlined, InfoCircleOutlined,
} from '@ant-design/icons'; } from '@ant-design/icons';
import { IoSettingsOutline } from "react-icons/io5";
import Buttons from '../../../../Components/Forms/Buttons.jsx'; import Buttons from '../../../../Components/Forms/Buttons.jsx';
import { InputField } from '../../../../Components/Forms/InputField.jsx'; import { InputField } from '../../../../Components/Forms/InputField.jsx';
import { Messages } from '../../../../Components/Notifications/Messages'; import { Messages } from '../../../../Components/Notifications/Messages';
@ -31,7 +32,9 @@ import {
supplierSelector, supplierSelector,
postProductData, postProductData,
postTax, postTax,
getHsnData getHsnData,
getFieldSetupData,
postFieldSetup
} from '../../../../Features/ProductPage/ProductPage.js'; } from '../../../../Features/ProductPage/ProductPage.js';
import { import {
ChangeNewQrProduct, ChangeNewQrProduct,
@ -78,8 +81,12 @@ const ProductForm = ({
const productFieldPreferences = appPreferences?.find( const productFieldPreferences = appPreferences?.find(
(preference) => preference?.PreferredCatName?.toLowerCase() === "product form fields" (preference) => preference?.PreferredCatName?.toLowerCase() === "product form fields"
)?.PreferenceCatDetails; )?.PreferenceCatDetails;
// const CustomQuickAddField = appPreferences?.find(
// (preference) => preference?.PreferredCatName?.toLowerCase() === "quick add custom fields"
// )?.PreferenceCatDetails;
const categoryId = appPreferences?.find(
p => p?.PreferredCatName?.toLowerCase() === "quick add custom fields"
)?.PreferredCatId;
const UomTypePreference = appPreferences?.find((preference) => preference?.PreferredCatName === 'Product Uom')?.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 UomPreference = UomData?.filter((item) => UomTypePreference?.some?.((e) => e?.PreferredSubCatName?.toLowerCase() == item?.ConfigName?.toLowerCase() && e?.PreferredStatus == "Y"))
@ -87,6 +94,7 @@ const ProductForm = ({
const filteredFields = productFieldPreferences?.filter( const filteredFields = productFieldPreferences?.filter(
(field) => field?.PreferredStatus === 'Y' (field) => field?.PreferredStatus === 'Y'
); );
const cessField = filteredFields?.find((field) => field?.PreferredSubCatName === 'Cess'); const cessField = filteredFields?.find((field) => field?.PreferredSubCatName === 'Cess');
const hsnField = filteredFields?.find((field) => field?.PreferredSubCatName === 'HSN'); const hsnField = filteredFields?.find((field) => field?.PreferredSubCatName === 'HSN');
const taxField = filteredFields?.find((field) => field?.PreferredSubCatName === 'Tax'); const taxField = filteredFields?.find((field) => field?.PreferredSubCatName === 'Tax');
@ -95,15 +103,21 @@ const ProductForm = ({
const [messageType, setMessageType] = useState(null); const [messageType, setMessageType] = useState(null);
const [messageData, setMessageData] = useState(null); const [messageData, setMessageData] = useState(null);
const [SelectedUom, setSelectedUom] = useState(null); const [SelectedUom, setSelectedUom] = useState(null);
const [SelectedTaxId, setSelectedTaxId] = useState(null); const [SelectedTaxId, setSelectedTaxId] = useState(null);
const [SelectedTaxNameId, setSelectedTaxNameId] = useState(null); const [SelectedTaxNameId, setSelectedTaxNameId] = useState(null);
const [OpenTaxModel, setOpenTaxModel] = useState(false); const [OpenTaxModel, setOpenTaxModel] = useState(false);
const [OpenAdd, setOpenAdd] = useState(false); // const [OpenAdd, setOpenAdd] = useState(false);
const [imageBrandUrl, setBrandImageUrl] = useState(''); const [imageBrandUrl, setBrandImageUrl] = useState('');
const [selectedHsn, setSelectedHsn] = useState(""); const [selectedHsn, setSelectedHsn] = useState("");
const [HsnDetails, setHsnDetails] = useState([]); const [HsnDetails, setHsnDetails] = useState([]);
const [selectedAdditionalColumn, setSelectedAdditionalColumn] = useState([]);
const [showColumnModal, setShowColumnModal] = useState(false);
const [tempSelectedColumns, setTempSelectedColumns] = useState([]);
const [tableFieldPreferences, setTableFieldPreferences] = useState([]);
const [selectedFields, setSelectedFields] = useState([]);
console.log(selectedAdditionalColumn, "selectedAdditionalColumn")
console.log(tempSelectedColumns, "tempSelectedColumns")
const CompId = getSession('CompId'); const CompId = getSession('CompId');
const BranchId = getSession('BranchId'); const BranchId = getSession('BranchId');
const AppId = getSession('AppId'); const AppId = getSession('AppId');
@ -149,19 +163,39 @@ const ProductForm = ({
setSelectedTaxId(TaxId); setSelectedTaxId(TaxId);
} }
}, [TaxData]); }, [TaxData]);
useEffect(() => {
getFieldSetup();
}, [categoryId])
const getFieldSetup = async () => {
try {
const response = await dispatch(getFieldSetupData({ AppId, CompId, BranchId, categoryId, Type: "QA" })).unwrap();
if (response?.data?.statusCode === 1) {
console.log(response?.data?.data?.[0]?.ConfigDtl, "Field Setup Data");
setSelectedFields(response?.data?.data?.[0]?.ConfigDtl?.filter(c => c.ConfigId && c.Access === 'Y')?.map(c => c.ConfigId) || []);
setSelectedAdditionalColumn(response?.data?.data?.[0]?.ConfigDtl?.filter(c => c.ConfigId && c.Access === 'Y')?.map(c => c.ConfigName) || [])
setTableFieldPreferences(response?.data?.data?.[0]?.ConfigDtl?.map(c => ({
value: c.ConfigId,
label: c.ConfigName,
access: c.Access
})) || []);
}
} catch (error) {
console.error('Error fetching field setup:', error);
}
};
const onComplete = useCallback(() => { const onComplete = useCallback(() => {
setMessageData(null); setMessageData(null);
setMessageType(null); setMessageType(null);
}, []); }, []);
const openAdditionalDetails = () => { // const openAdditionalDetails = () => {
if (OpenAdd) { // if (OpenAdd) {
setOpenAdd(false); // setOpenAdd(false);
} else { // } else {
setOpenAdd(true); // setOpenAdd(true);
} // }
}; // };
const getApplicationPreference = async () => { const getApplicationPreference = async () => {
@ -367,7 +401,7 @@ const ProductForm = ({
setSelectedUom(null); setSelectedUom(null);
setSelectedTaxId(null); setSelectedTaxId(null);
handleCancelData(); handleCancelData();
setOpenAdd(false); // setOpenAdd(false);
if (ProductSearchType != 'C') { if (ProductSearchType != 'C') {
let data = { let data = {
CompId: CompId, CompId: CompId,
@ -444,6 +478,36 @@ const ProductForm = ({
} }
} }
const handleFieldSetupSubmit = async () => {
const postData = {
"AppId": AppId,
"CompId": CompId,
"BranchId": BranchId,
"Type": "QA",
"FormType": "Quick Add",
"TypeId": categoryId,
"ConfigDtl": selectedFields?.map((field) => ({
"ConfigId": field,
"Access": 'Y',
})),
"CreatedBy": UserId
}
const response = await dispatch(postFieldSetup(postData))?.unwrap();
if (response?.data?.statusCode === 1) {
setSelectedAdditionalColumn([...tempSelectedColumns]);
setShowColumnModal(false);
setTempSelectedColumns([])
setMessageType("success");
setMessageData(response?.data?.response);
await getFieldSetup();
} else {
setMessageType("error");
setMessageData("Failed to set up fields");
}
}
return ( return (
<div> <div>
<Messages <Messages
@ -455,6 +519,27 @@ const ProductForm = ({
<FormHeader title={'Quick Add'} /> <FormHeader title={'Quick Add'} />
</div> </div>
<div> <div>
<div style={{
display: 'flex', alignItems: 'center', gap: '10px', justifyContent: "flex-end"
, fontFamily: "Poppins", fontWeight: "500", textDecoration: "underline", cursor: "Pointer", width: "100%"
}}
onClick={() => {
setTempSelectedColumns([...selectedAdditionalColumn]);
setShowColumnModal(true);
}}>
<Tooltip title="Add Fields" placement="left">
{/* <span>Additional Fields</span> */}
<IoSettingsOutline
onClick={() => {
setTempSelectedColumns([...selectedAdditionalColumn]);
setShowColumnModal(true);
}}
style={{ cursor: 'pointer', fontSize: '20px' }}
/>
</Tooltip>
</div>
<Form ref={formRef} className="formDivAnt" onFinish={onFinish}> <Form ref={formRef} className="formDivAnt" onFinish={onFinish}>
<div> <div>
<div className="productinputForm"> <div className="productinputForm">
@ -667,30 +752,35 @@ const ProductForm = ({
</div> </div>
)} )}
{OpenAdd && ( {true && (
<> <>
{taxField && <Form.Item name="TaxId"> {(taxField && selectedAdditionalColumn?.includes('Tax')) &&
<DropDowns <Form.Item name="TaxId">
options={TaxData?.map((option) => ({ <DropDowns
value: option.TaxId, options={TaxData?.map((option) => ({
label: getOptionLabel( value: option.TaxId,
option, label: getOptionLabel(
SelectedTaxId === option.TaxId option,
), SelectedTaxId === option.TaxId
// label: option.TaxIdName + ' - ' + option.TaxPercentage + ' % ', ),
}))} // label: option.TaxIdName + ' - ' + option.TaxPercentage + ' % ',
label="Tax" }))}
className="field-DropDown" label="Tax"
isOnchanges={SelectedTaxId ? true : false} className="field-DropDown"
onChangeFunction={handleTaxDropDownChange} isOnchanges={SelectedTaxId ? true : false}
valueData={SelectedTaxId} onChangeFunction={handleTaxDropDownChange}
/> valueData={SelectedTaxId}
<Tooltip title="Add Tax" placement="top"> />
<PlusCircleOutlined onClick={addTax} /> </Form.Item>
</Tooltip> }
</Form.Item>} {(taxField && selectedAdditionalColumn?.includes('Tax')) &&
<Form.Item name="TaxId">
<Tooltip title="Add Tax" placement="top">
<PlusCircleOutlined onClick={addTax} />
</Tooltip>
</Form.Item>}
{cessField && <Form.Item {(cessField && selectedAdditionalColumn?.includes('Cess')) && <Form.Item
name="Cess" name="Cess"
rules={[ rules={[
{ {
@ -708,13 +798,13 @@ const ProductForm = ({
<InputField autoComplete="off" label="Cess" /> <InputField autoComplete="off" label="Cess" />
</Form.Item>} </Form.Item>}
{hsnField && {(hsnField && selectedAdditionalColumn?.includes('Hsn Code')) &&
<Form.Item <Form.Item
name="HSNCode" name="HSNCode"
rules={[ rules={[
{ {
message: 'Please enter HSN', message: 'Please enter HSN',
}, },
]} ]}
> >
@ -779,32 +869,34 @@ const ProductForm = ({
} }
<Form.Item {selectedAdditionalColumn?.includes('WholeSale') &&
name="WhSalePrice" <Form.Item
rules={[ name="WhSalePrice"
{ rules={[
pattern: /^[0-9]\d*(\.\d+)?$/, {
message: 'Please Enter WholeSale', pattern: /^[0-9]\d*(\.\d+)?$/,
}, message: 'Please Enter WholeSale',
{
validator: async (_, value) => {
await validateSafeInput(value); // To block HTML tags & SQL keywords
return Promise.resolve();
}, },
}, {
]} validator: async (_, value) => {
> await validateSafeInput(value); // To block HTML tags & SQL keywords
<InputField return Promise.resolve();
autoComplete="off" },
label={<label>WholeSale</label>} },
/> ]}
</Form.Item> >
<InputField
autoComplete="off"
label={<label>WholeSale</label>}
/>
</Form.Item>
}
</> </>
)} )}
</div> </div>
<div className="prodAddDetails" onClick={openAdditionalDetails}> {/* <div className="prodAddDetails" onClick={openAdditionalDetails}>
Additional Details Additional Details
</div> </div> */}
</div> </div>
</div> </div>
<div style={{ display: 'flex', justifyContent: 'flex-end' }}> <div style={{ display: 'flex', justifyContent: 'flex-end' }}>
@ -823,6 +915,7 @@ const ProductForm = ({
open={OpenTaxModel} open={OpenTaxModel}
footer={true} footer={true}
buttonText="Submit" buttonText="Submit"
destroyOnClose={true}
children={ children={
<Form ref={formTaxRef} className="formDivAnt"> <Form ref={formTaxRef} className="formDivAnt">
<div className="subRowFlex"> <div className="subRowFlex">
@ -901,6 +994,51 @@ const ProductForm = ({
handleSubmit={submitTax} handleSubmit={submitTax}
handleCancel={handleTax} handleCancel={handleTax}
></DefaultModal> ></DefaultModal>
<DefaultModal
title="Select Additional Fields"
open={showColumnModal}
footer={true}
buttonText="Apply"
destroyOnClose={true}
handleSubmit={handleFieldSetupSubmit}
handleCancel={() => {
setShowColumnModal(false);
setSelectedFields([...selectedAdditionalColumn.map(col =>
tableFieldPreferences?.find(pref => pref.label === col)?.value
).filter(Boolean)]);
setTempSelectedColumns([...selectedAdditionalColumn]);
}}
>
<div style={{ padding: '20px' }}>
{tableFieldPreferences?.map((option) => (
<div key={option.value} className='Customized_QuickAdd'>
<label>
<input
type="checkbox"
checked={selectedFields?.includes(option.value)}
onChange={(e) => {
const isChecked = e.target.checked;
setSelectedFields(prev =>
isChecked
? [...prev, option.value]
: prev.filter(item => item !== option.value)
);
setTempSelectedColumns(prev =>
isChecked
? [...prev, option.label]
: prev.filter(item => item !== option.label)
);
}}
style={{ marginRight: '8px' }}
/>
{option.label}
</label>
</div>
))}
</div>
</DefaultModal>
</div> </div>
</div> </div>
); );

View File

@ -1,24 +1,9 @@
.cropUploadFieldSmall { .cropUploadFieldSmall {
:where(
.css-dev-only-do-not-override-1v5z42l :where(.css-dev-only-do-not-override-1v5z42l).ant-upload-wrapper.ant-upload-picture-card-wrapper .ant-upload-list.ant-upload-list-picture-card .ant-upload-list-item-container,
).ant-upload-wrapper.ant-upload-picture-card-wrapper :where(.css-dev-only-do-not-override-1v5z42l).ant-upload-wrapper.ant-upload-picture-circle-wrapper .ant-upload-list.ant-upload-list-picture-card .ant-upload-list-item-container,
.ant-upload-list.ant-upload-list-picture-card :where(.css-dev-only-do-not-override-1v5z42l).ant-upload-wrapper.ant-upload-picture-card-wrapper .ant-upload-list.ant-upload-list-picture-circle .ant-upload-list-item-container,
.ant-upload-list-item-container, :where(.css-dev-only-do-not-override-1v5z42l).ant-upload-wrapper.ant-upload-picture-circle-wrapper .ant-upload-list.ant-upload-list-picture-circle .ant-upload-list-item-container {
:where(
.css-dev-only-do-not-override-1v5z42l
).ant-upload-wrapper.ant-upload-picture-circle-wrapper
.ant-upload-list.ant-upload-list-picture-card
.ant-upload-list-item-container,
:where(
.css-dev-only-do-not-override-1v5z42l
).ant-upload-wrapper.ant-upload-picture-card-wrapper
.ant-upload-list.ant-upload-list-picture-circle
.ant-upload-list-item-container,
:where(
.css-dev-only-do-not-override-1v5z42l
).ant-upload-wrapper.ant-upload-picture-circle-wrapper
.ant-upload-list.ant-upload-list-picture-circle
.ant-upload-list-item-container {
width: 65px; width: 65px;
height: 65px; height: 65px;
} }
@ -125,6 +110,7 @@
.float-label { .float-label {
margin-bottom: 0; margin-bottom: 0;
} }
.ant-form-item-explain-error { .ant-form-item-explain-error {
position: relative; position: relative;
top: 16px; top: 16px;
@ -333,14 +319,14 @@
} }
.viewerExcelupload .ant-table-wrapper, .viewerExcelupload .ant-table-wrapper,
.viewerExcelupload .ant-table-tbody > tr.ant-table-row > td { .viewerExcelupload .ant-table-tbody>tr.ant-table-row>td {
padding: 4px; padding: 4px;
font-size: 14px; font-size: 14px;
font-weight: 600; font-weight: 600;
} }
.viewerExcelupload .ant-table-wrapper, .viewerExcelupload .ant-table-wrapper,
.viewerExcelupload .ant-table-thead > tr > th { .viewerExcelupload .ant-table-thead>tr>th {
padding: 1px 8px !important; padding: 1px 8px !important;
} }
@ -395,7 +381,7 @@
} }
.productinputForm { .productinputForm {
.ant-input-affix-wrapper > input.ant-input { .ant-input-affix-wrapper>input.ant-input {
padding-top: 1.5rem; padding-top: 1.5rem;
} }
} }
@ -449,7 +435,7 @@
} }
.product-Add-HSN { .product-Add-HSN {
.ant-input-affix-wrapper > input.ant-input { .ant-input-affix-wrapper>input.ant-input {
padding: 12px 0 0 0 !important; padding: 12px 0 0 0 !important;
} }
} }
@ -490,7 +476,15 @@
} }
.productFormproductImg{ .productFormproductImg {
width: 350px; width: 350px;
align-items: center; align-items: center;
} }
.Customized_QuickAdd {
margin-bottom: 10px;
font-family: 'Poppins', sans-serif;
font-size: 16px;
font-weight: 400;
font-style: normal;
}