added a tax, brand, sub-category, category adding in turbo add

This commit is contained in:
unknown 2026-03-04 18:41:02 +05:30
parent 3e9b910616
commit 0268282eb1
9 changed files with 2216 additions and 1638 deletions

View File

@ -205,6 +205,17 @@ export const getProdSubCatData = createAsyncThunk(
}
);
export const getActiveProdSubCatData = createAsyncThunk(
'product/getActiveProdSubCatData',
async ({ AppId }) => {
if (AppId !== null && AppId !== undefined) {
return await axiosRetailInstanceData.get(
`/configMaster?TypeName=Product Sub-Category&AppId=${AppId}`
);
}
}
);
export const getProductData = createAsyncThunk(
'product/getProductData',
async (ProdData) => {
@ -661,6 +672,7 @@ const initialState = {
uomData: [],
prodCatData: [],
prodSubCatData: [],
allProdSubCatData: [],
productData: [],
prodTaxData: [],
taxData: [],
@ -723,6 +735,13 @@ const productSlice = createSlice({
state.prodSubCatData = [];
}
}),
builder.addCase(getActiveProdSubCatData.fulfilled, (state, action) => {
if (action?.payload?.status) {
state.allProdSubCatData = action?.payload?.data?.data;
} else {
state.allProdSubCatData = [];
}
}),
builder.addCase(getBrandData.fulfilled, (state, action) => {
if (action?.payload?.status) {
state.brandData = action?.payload?.data?.data;
@ -768,6 +787,8 @@ export const uomDataSelector = (state) => state.productPage?.uomData;
export const prodCatDataSelector = (state) => state.productPage?.prodCatData;
export const prodSubCatDataSelector = (state) =>
state.productPage?.prodSubCatData;
export const allProdSubCatDataSelector = (state) =>
state.productPage?.allProdSubCatData;
export const productDataSelector = (state) => state.productPage?.productData;
export const brandDataSelector = (state) => state.productPage?.brandData;
export const taxSelector = (state) => state.productPage?.taxData;

View File

@ -97,7 +97,7 @@ import { ImShare } from 'react-icons/im';
const RetailApiurl = import.meta.env.ENV_API_URL;
function BSReprint({ setOpenModel = () => {} }) {
function BSReprint({ setOpenModel = () => { } }) {
const dispatch = useDispatch();
const queryClient = useQueryClient();
const appPreferences = useSelector(ApplicationPreferences);
@ -133,7 +133,7 @@ function BSReprint({ setOpenModel = () => {} }) {
const SettingDataSelector = useSelector(PreferenceData);
const salesBillEdit = useSelector(GlobalSalesBillEdit);
const custDisable = useSelector(GlobalSelectedCustDisable);
console.log(SettingDataSelector, 'SettingDataSelectoraa');
console.log(DefReason, 'DefReason');
const MobileA4Print = SettingDataSelector?.[0]?.SettingDtlDetails?.find(
(setting) =>
setting?.SettingIdName?.toLowerCase() === 'mobilea4' &&
@ -292,6 +292,7 @@ function BSReprint({ setOpenModel = () => {} }) {
} else {
setDefReason({});
reprintReasonRef?.current?.resetFields();
reprintReasonRef?.current?.setFieldsValue({ SetAsDefault: false });
setIsDuplicate('Original');
}
};
@ -607,7 +608,7 @@ function BSReprint({ setOpenModel = () => {} }) {
const selectedStyle =
stylesMap[
printerTemplateStyle == undefined ? 'Style 13' : printerTemplateStyle
printerTemplateStyle == undefined ? 'Style 13' : printerTemplateStyle
];
if (selectedStyle) {
if (data === 'without') {
@ -1054,7 +1055,7 @@ function BSReprint({ setOpenModel = () => {} }) {
}
}}
>
<AiFillPrinter className="BSC1Payment-icon" />
<AiFillPrinter color={Object.keys(DefReason)?.length > 0 ? '#52c41a' : ''} className="BSC1Payment-icon" />
Bill Without Token
</button>
<button
@ -1072,13 +1073,14 @@ function BSReprint({ setOpenModel = () => {} }) {
}
}}
>
<AiFillPrinter className="BSC1Payment-icon" />
<AiFillPrinter color={Object.keys(DefReason)?.length > 0 ? '#52c41a' : ''} className="BSC1Payment-icon" />
Bill With Token
</button>
</div>
) : (
<AiFillPrinter
style={{ fontSize: '22px', cursor: 'pointer' }}
color={Object.keys(DefReason)?.length > 0 ? '#52c41a' : ''}
onClick={() => {
setReprintData([row]);
if (Object.keys(DefReason)?.length > 0) {
@ -1529,7 +1531,7 @@ function BSReprint({ setOpenModel = () => {} }) {
<Tables
columns={columns}
data={filteredData}
// pagination={handlePageChange}
// pagination={handlePageChange}
/>
{countOfProduct > 10 && (
<Pagination

File diff suppressed because it is too large Load Diff

View File

@ -10,6 +10,7 @@ import {
getCommonAppPreference,
} from '../../Features/BrachLogin/BranchLogin.js';
import {
allProdSubCatDataSelector,
bulkpostdata,
getBrandData,
getFieldSetupData,
@ -23,6 +24,7 @@ import {
postFieldSetup,
prodCatDataSelector,
prodSubCatDataSelector,
prodTaxDataSelector,
productTypeDataSelector,
putProductData,
uomDataSelector,
@ -57,6 +59,11 @@ import { BiBarcodeReader } from 'react-icons/bi';
import BarcodeScanner from '../BookingScreen/Components/UtillComponents/BarcodeScanner.jsx';
import BarcodeScannerTurbo from '../BookingScreen/Components/UtillComponents/BarcodeScannerTurbo.jsx';
import BarCodeScan from '../../Services/BarCodeScan.jsx';
import AddCategoryModal from './Utils/AddCategoryModal.jsx';
import { getConfigTypeData, postConfiguration } from '../../Features/ConfigMasterPage/ConfigMasterPage.js';
import AddSubCategoryModal from './Utils/AddSubCategoryModal.jsx';
import AddBrandModal from './Utils/AddBrandModal.jsx';
import AddTaxModal from './Utils/AddTaxModal.jsx';
const subDirectory = import.meta.env.ENV_BASE_URL;
const items = [
@ -153,9 +160,13 @@ const createValidationRules = (products) => ({
const TurboAddForm = () => {
const dispatch = useDispatch();
// Redux selectors
const taxData = useSelector(taxSelector);
const taxRef = useRef();
const brandRef = useRef();
const categoryRef = useRef();
const subCategoryRef = useRef();
const scannerRef = useRef();
console.log(taxData, 'taxData');
const prodTaxData = useSelector(prodTaxDataSelector);
const taxData = useSelector(taxSelector);
const UomData = useSelector(uomDataSelector);
const productTypeData = useSelector(productTypeDataSelector);
const ApplicationPreferenceData = useSelector(ApplicationPreferences);
@ -164,8 +175,8 @@ const TurboAddForm = () => {
)?.PreferredCatId;
const prodCategories = useSelector(prodCatDataSelector);
const prodSubCategories = useSelector(prodSubCatDataSelector);
const allProdSubCategories = useSelector(allProdSubCatDataSelector);
const searchText = useSelector(GlobalSearchData);
// Session data
const AppId = useMemo(() => getSession('AppId'), []);
const CompId = useMemo(() => getSession('CompId'), []);
@ -173,6 +184,11 @@ const TurboAddForm = () => {
const UserId = useMemo(() => getSession('UserId'), []);
// State
const [openTaxModal, setOpenTaxModal] = useState(false);
const [openCategoryModal, setOpenCategoryModal] = useState(false);
const [openSubCategoryModal, setOpenSubCategoryModal] = useState(false);
const [openBrandModal, setOpenBrandModal] = useState(false);
const [imageCategoryUrl, setImageCategoryUrl] = useState('');
const [voiceData, setVoiceData] = useState(null);
const [messageType, setMessageType] = useState(null);
const [messageData, setMessageData] = useState(null);
@ -257,7 +273,7 @@ const TurboAddForm = () => {
uomPref?.some?.(
(e) =>
e?.PreferredSubCatName?.toLowerCase() ===
item?.ConfigName?.toLowerCase() && e?.PreferredStatus === 'Y'
item?.ConfigName?.toLowerCase() && e?.PreferredStatus === 'Y'
)
);
@ -372,6 +388,8 @@ const TurboAddForm = () => {
[UomPreference, taxData, prodCategories, prodSubCategories]
);
console.log(selectOptions?.tax, taxData, "selectOptions tax");
// Filtered products for search
const filteredProducts = useMemo(() => {
if (!searchTerm.trim()) return products;
@ -953,14 +971,14 @@ const TurboAddForm = () => {
prev.map((p) =>
p.id === productId
? {
...p,
availableSubCategories,
subCategory: subCategoryId,
subCategoryName,
availableBrands,
brand: '',
brandName: '',
}
...p,
availableSubCategories,
subCategory: subCategoryId,
subCategoryName,
availableBrands,
brand: '',
brandName: '',
}
: p
)
);
@ -1013,7 +1031,7 @@ const TurboAddForm = () => {
return p;
});
});
} catch (error) {}
} catch (error) { }
}
},
[handleInputChange, selectOptions.subCategories]
@ -1189,7 +1207,7 @@ const TurboAddForm = () => {
(p, i) =>
i !== idx &&
p.productName?.trim().toLowerCase() ===
product.productName?.trim().toLowerCase() &&
product.productName?.trim().toLowerCase() &&
p.uom === product.uom &&
p.nou === product.nou &&
p?.category === product?.category &&
@ -1613,11 +1631,11 @@ const TurboAddForm = () => {
rowIndex === 0 && !isMobile ? rowIndex + 1 : rowIndex;
return idx === index
? {
...p,
productName: found.ProdName,
barcode: enteredValue,
productImage: found.ImageUrl,
}
...p,
productName: found.ProdName,
barcode: enteredValue,
productImage: found.ImageUrl,
}
: p;
})
);
@ -1687,7 +1705,7 @@ const TurboAddForm = () => {
const duplicateExists = products.some(
(row) =>
row.productName?.trim().toLowerCase() ===
baseData.productName?.trim().toLowerCase() &&
baseData.productName?.trim().toLowerCase() &&
row.nou === baseData.nou &&
row.uom === baseData.uom
);
@ -1936,12 +1954,12 @@ const TurboAddForm = () => {
// onClick={() => handleQrcode()}
// style={{ cursor: 'pointer', marginLeft: '8px' }}
// />
<BarCodeScan
style={{FontSize:'12px', marginLeft: '8px' }}
<BarCodeScan
style={{ FontSize: '12px', marginLeft: '8px' }}
onScan={(value) => {
changeSearchedData(value);
}}
/>
changeSearchedData(value);
}}
/>
)}
<EnhancedVoiceForm
setVoiceData={setVoiceData}
@ -2022,6 +2040,12 @@ const TurboAddForm = () => {
field="category"
options={defaultCategories}
onSubmit={handleDefaultSetting}
triggerModal={setOpenCategoryModal}
Component={<AddCategoryModal
ref={categoryRef}
open={openCategoryModal}
close={setOpenCategoryModal}
/>}
/>
</th>
{hasField('Sub Category') && (
@ -2039,6 +2063,15 @@ const TurboAddForm = () => {
? `Selected Category : ${defaultCategories?.find((cat) => cat.value === selectedDefaultCategory)?.label}`
: null
}
triggerModal={setOpenSubCategoryModal}
Component={<AddSubCategoryModal
open={openSubCategoryModal}
close={setOpenSubCategoryModal}
categoryData={prodCategories}
products={products}
ref={subCategoryRef}
setProducts={setProducts}
/>}
/>
</th>
)}
@ -2048,7 +2081,7 @@ const TurboAddForm = () => {
<DefaultSettingPopover
showField={
defaultSubCategories?.length > 0 &&
selectedDefaultSubCategory
selectedDefaultSubCategory
? `Selected Sub-Category : ${defaultSubCategories?.find((subCat) => subCat.value === selectedDefaultSubCategory)?.label}`
: null
}
@ -2060,6 +2093,16 @@ const TurboAddForm = () => {
checkBoxHeading={
'Apply to all selected sub-category rows'
}
triggerModal={setOpenBrandModal}
needAllSubCategories={true}
Component={<AddBrandModal
open={openBrandModal}
close={setOpenBrandModal}
subCategories={allProdSubCategories}
products={products}
setProducts={setProducts}
ref={brandRef}
/>}
/>
</th>
)}
@ -2090,6 +2133,14 @@ const TurboAddForm = () => {
options={selectOptions.tax}
onSubmit={handleDefaultSetting}
currentDefault={defaults.tax}
triggerModal={setOpenTaxModal}
Component={<AddTaxModal
open={openTaxModal}
close={setOpenTaxModal}
prodTaxData={prodTaxData}
ref={taxRef}
flag={true}
/>}
/>
</th>
)}
@ -2195,7 +2246,7 @@ const TurboAddForm = () => {
(p) =>
p.barcode &&
p.barcode.toLowerCase() ===
newValue?.toLowerCase()
newValue?.toLowerCase()
);
if (barcodeExists) {

View File

@ -0,0 +1,204 @@
import { Form } from 'antd';
import { DefaultModal } from '../../../Components/Modal/DefaultModal';
import { getSession, validateSafeInput } from '../../../Services/Others';
import { DropDowns } from '../../../Components/Forms/DropDown';
import Imageupload from '../../../Components/Forms/Upload.jsx';
import { InputField } from '../../../../ownLib/my-ui-lib';
import { forwardRef, useCallback, useState } from 'react';
import { getConfigTypeData, postConfiguration } from '../../../Features/ConfigMasterPage/ConfigMasterPage.js';
import { useDispatch } from 'react-redux';
import { getBrandData } from '../../../Features/ProductPage/ProductPage.js';
import { Messages } from '../../../Components/Notifications/Messages.jsx';
const AddBrandModal = forwardRef((
{
open = false,
close = () => { },
subCategories = [],
products = [],
setProducts = () => { },
}, ref) => {
const dispatch = useDispatch();
const AppId = getSession('AppId');
const UserId = getSession('UserId');
const [messageType, setMessageType] = useState(null);
const [messageData, setMessageData] = useState(null);
const [imageBrandUrl, setBrandImageUrl] = useState('');
const [selectedSubCategory, setSelectedSubCategory] = useState(null);
const handleBrandModalClose = () => {
close(false);
setBrandImageUrl('');
setSelectedSubCategory(null);
ref?.current?.resetFields();
};
const handleBrandImageUrlUpdate = (url) => {
setBrandImageUrl(url);
};
const handleSubCatDropDownChange = (value) => {
setSelectedSubCategory(value);
ref?.current?.setFieldsValue({ SubCategoryId: value });
};
const handleBrandSubmit = async () => {
const subBrandData = await ref?.current?.validateFields();
const subBrandTypeId = await dispatch(
getConfigTypeData({ TypeName: 'Product Brand' })
).unwrap();
const addBrandData = {
TypeId: subBrandTypeId?.data?.data?.[0]?.TypeId,
ConfigName: subBrandData?.SubConfigName,
AlphaNumFId: AppId,
NumFId: selectedSubCategory,
SmallIcon: imageBrandUrl,
CreatedBy: UserId,
};
const response = await dispatch(postConfiguration(addBrandData)).unwrap();
if (response?.data?.statusCode == 1) {
setBrandImageUrl('');
setMessageType('success');
setMessageData(response?.data?.response);
close(false);
const brandResponse = await dispatch(getBrandData({ ConfigId: selectedSubCategory }))?.unwrap();
ref?.current?.resetFields();
if (products?.length > 0) {
const hasTheSubCategory = products.some(product => product.subCategory === selectedSubCategory);
if (hasTheSubCategory) {
if (brandResponse?.data?.statusCode == 1) {
const brands = brandResponse?.data?.data || [];
const availableBrands = brands?.filter(
(subCat) =>
subCat.NumFId === parseInt(selectedSubCategory) && !!subCat?.ConfigName
)?.map((subCat) => ({
label: subCat.ConfigName,
value: subCat.ConfigId,
}));
const updatedProducts = products?.map((product) => {
if (product.subCategory === selectedSubCategory) {
return {
...product,
availableBrands,
};
}
return product;
});
setProducts(updatedProducts);
}
}
}
setSelectedSubCategory(null);
} else {
setBrandImageUrl('');
setSelectedSubCategory(null);
setMessageType('error');
setMessageData(response?.data?.response);
ref?.current?.resetFields();
}
};
const onComplete = useCallback(() => {
setMessageType(null);
setMessageData(null);
}, []);
return (
<>
<Messages messageData={messageData} messageType={messageType} onComplete={onComplete} />
<DefaultModal
title="Add Brand"
open={open}
footer={true}
buttonText="Submit"
handleSubmit={handleBrandSubmit}
handleCancel={handleBrandModalClose}
children={
<Form ref={ref} className="formDivAnt">
<div className="ProSubRowFlex">
<div className="subColumnFlex">
<div className="subProdCatData">
<Form.Item
name="SubCategoryId"
rules={[
{
required: true,
message: 'Please Select Sub-Category',
},
]}
>
<DropDowns
options={subCategories?.map((option) => ({
value: option.ConfigId,
label: option.ConfigName,
}))}
label={<label class="required">Sub-Category</label>}
className="field-DropDown"
onChangeFunction={handleSubCatDropDownChange}
valueData={selectedSubCategory}
/>
</Form.Item>
<div className="upload_btn">
<p>Upload Icon</p>
<br></br>
<div style={{ width: 'max-content' }}>
<Imageupload
singleImage={true}
updateImageUrl={handleBrandImageUrlUpdate}
ImageLink={imageBrandUrl ? imageBrandUrl : ''}
/>
</div>
</div>
</div>
</div>
<div className="subColumnFlex">
<div>
<Form.Item
name="SubConfigName"
rules={[
{
required: true,
pattern: /^(?!\s*$).+/,
message: 'Please Enter Brand Name',
},
{
validator: async (_, value) => {
await validateSafeInput(value);
if (value && value.length > 50) {
return Promise.reject(
'Brand Name should not exceed 50 characters'
);
}
return Promise.resolve();
},
},
]}
>
<InputField
label={<label class="required">Brand Name</label>}
autocomplete="off"
/>
</Form.Item>
</div>
</div>
</div>
</Form>
}
/>
</>
)
});
export default AddBrandModal

View File

@ -0,0 +1,147 @@
import { forwardRef, useCallback, useState } from "react";
import Imageupload from '../../../Components/Forms/Upload.jsx';
import { DefaultModal } from "../../../Components/Modal/DefaultModal";
import { InputField } from "../../../Components/Forms/InputField";
import { Form } from "antd";
import { getSession, validateSafeInput } from "../../../Services/Others.js";
import { useDispatch } from "react-redux";
import { getConfigTypeData, postConfiguration } from "../../../Features/ConfigMasterPage/ConfigMasterPage.js";
import { getProdCatData } from "../../../Features/ProductPage/ProductPage.js";
import { Messages } from "../../../Components/Notifications/Messages.jsx";
const AddCategoryModal = forwardRef((
{
open = false,
close = () => { },
}, ref) => {
const dispatch = useDispatch();
const AppId = getSession('AppId');
const UserId = getSession('UserId');
const [messageType, setMessageType] = useState('');
const [messageData, setMessageData] = useState('');
const [imageCategoryUrl, setImageCategoryUrl] = useState('');
const handleImageCategoryUrlUpdate = (url) => {
setImageCategoryUrl(url);
};
const handleSubmitCategory = async () => {
try {
const values = await ref?.current?.validateFields();
const categoryTypeId = await dispatch(
getConfigTypeData({ TypeName: 'Product Category' })
).unwrap();
const data = {
TypeId: categoryTypeId?.data?.data?.[0]?.TypeId,
ConfigName: values?.ConfigName,
AlphaNumFId: AppId,
SmallIcon: imageCategoryUrl,
CreatedBy: UserId,
};
const response = await dispatch(postConfiguration(data)).unwrap();
if (response?.data?.statusCode === 1) {
await dispatch(getProdCatData({ AppId: AppId }));
close(false);
setImageCategoryUrl('');
setMessageType('success');
setMessageData(response?.data?.response);
ref?.current?.resetFields();
} else {
setMessageType('error');
setMessageData(response?.data?.response);
}
return;
}
catch (error) {
console.log('Error:', error?.message);
return;
}
}
const handleModalClose = () => {
close(false);
setImageCategoryUrl('');
ref?.current?.resetFields();
};
const onComplete = useCallback(() => {
setMessageData(null);
setMessageType(null);
}, []);
return (
<>
<Messages messageData={messageData} messageType={messageType} onComplete={onComplete} />
<DefaultModal
title="Add Category"
open={open}
footer={true}
buttonText="Submit"
children={
<Form ref={ref} className="formDivAnt">
<div className="ProSubRowFlex">
<div className="subColumnFlex">
<div>
<Form.Item
name="ConfigName"
rules={[
{
required: true,
pattern: /^(?!\s*$).+/,
message: 'Please Enter Category Name',
},
{
validator: async (_, value) => {
await validateSafeInput(value);
if (value && value.length > 50) {
return Promise.reject(
'Category Name should not exceed 50 characters'
);
}
return Promise.resolve();
},
},
]}
>
<InputField
label={
<label class="required">Category Name</label>
}
autocomplete="off"
/>
</Form.Item>
</div>
</div>
<div className="subColumnFlex">
<div className="upload_btn">
<p>Upload Icon</p>
<br></br>
<div style={{ width: 'max-content' }}>
<Imageupload
singleImage={true}
updateImageUrl={handleImageCategoryUrlUpdate}
ImageLink={imageCategoryUrl ? imageCategoryUrl : ''}
/>
</div>
</div>
</div>
</div>
</Form>
}
handleSubmit={handleSubmitCategory}
handleCancel={handleModalClose}
/>
</>
)
});
export default AddCategoryModal;

View File

@ -0,0 +1,337 @@
import { Form, Table } from 'antd';
import { forwardRef, useCallback, useState } from 'react';
import { DropDowns } from '../../../Components/Forms/DropDown';
import { getSession, validateSafeInput } from '../../../Services/Others';
import { InputField } from '../../../Components/Forms/InputField';
import { PlusCircleOutlined, DeleteFilled } from '@ant-design/icons';
import { Tooltip, message, Space } from 'antd';
import { DefaultModal } from '../../../Components/Modal/DefaultModal';
import { Messages } from '../../../Components/Notifications/Messages';
import { getProdSubCatData, postConfigurationmultiple } from '../../../Features/ProductPage/ProductPage';
import { useDispatch } from 'react-redux';
import { getConfigTypeData } from '../../../Features/ConfigMasterPage/ConfigMasterPage';
const AddSubCategoryModal = forwardRef((
{
open = false,
close = () => { },
categoryData = [],
products = [],
setProducts = () => { },
flag = false,
}, ref) => {
const AppId = getSession('AppId');
const UserId = getSession('UserId');
const dispatch = useDispatch();
const [page, setPage] = useState(1);
const [searchedText, setSearchedText] = useState('');
const [sortedInfo, setSortedInfo] = useState({});
const [editDataval, setEditDataVal] = useState(true);
const [messageType, setMessageType] = useState('');
const [messageData, setMessageData] = useState('');
const [imageSubCategoryUrl, setSubCategoryImageUrl] = useState('');
const [selectedCategory, setSelectedCategory] = useState(null);
const [tableData, setTableData] = useState([]);
const Columns = [
{
title: 'Sl.No',
key: 'sno',
align: 'center',
render: (text, object, index) => (
<a style={{ color: 'black' }}>{(page - 1) * 10 + index + 1}</a>
),
},
{
title: 'SubCategory Name',
dataIndex: 'SubCategory',
key: 'SubCategory',
editable: true,
render: (text) => <a style={{ color: 'black' }}>{text}</a>,
filteredValue: [searchedText],
onFilter: (value, record) => {
return (
String(record.TableName)
?.toLowerCase()
?.includes(value?.toLowerCase()) ||
String(record.TableName)
?.toLowerCase()
?.includes(value?.toLowerCase()) ||
String(record.ChairCount)
?.toLowerCase()
?.includes(value?.toLowerCase()) ||
String(record.ChairLevelSer)
?.toLowerCase()
?.includes(value?.toLowerCase()) ||
String(record.DineInTypeName)
?.toLowerCase()
?.includes(value?.toLowerCase())
);
},
sorter: (a, b) => a?.ProdName?.length - b?.ProdName?.length,
sortOrder:
sortedInfo.columnKey === 'SubCategory' ? sortedInfo.order : null,
ellipsis: true,
},
{
title: 'Action',
key: 'Action',
dataIndex: 'Action',
align: 'center',
render: (_, record, index) => (
// Tabledata.length >= 1 ? (
<Space size="middle">
<a>
{/* {record.ActiveStatus === "A" ? ( */}
<DeleteFilled
style={{
color: '#FF4D4F',
}}
onClick={() => statusFormatter(record, index)}
/>
</a>
</Space>
),
},
];
const handleAddSubCategory = async () => {
const subCategoryData = await ref?.current?.validateFields();
const alreadyExists = tableData.some(
(item) => item.SubCategory === subCategoryData?.SubConfigName
);
if (!alreadyExists) {
setTableData((prev) => {
const newIndex = prev.length + 1;
const datas = {
category: selectedCategory,
SubCategory: subCategoryData?.SubConfigName,
key: newIndex, // Push index here
};
return [...prev, datas];
});
} else {
setMessageType('error');
setMessageData('Sub Category Already Exist');
}
ref.current.resetFields(['SubConfigName']);
};
const handleSubmitSubCategory = async () => {
if (!tableData || tableData.length === 0) {
setMessageType('error');
setMessageData('Please add at least one Sub-Category.');
return;
}
const subCategoryTypeId = await dispatch(
getConfigTypeData({ TypeName: 'Product Sub-Category' })
).unwrap();
const addSubCategoryData = {
TypeId: subCategoryTypeId?.data?.data?.[0]?.TypeId || 0,
ConfigDetails: tableData.map((item) => ({
ConfigName: item.SubCategory,
})),
AlphaNumFId: AppId,
NumFId: selectedCategory,
CreatedBy: UserId,
};
const response = await dispatch(
postConfigurationmultiple(addSubCategoryData)
).unwrap();
if (response?.data?.statusCode == 1) {
setSubCategoryImageUrl('');
setMessageType('success');
setMessageData('Sub-Category Added Successfully');
close(false);
setSelectedCategory(null);
const subCatResponse = await dispatch(getProdSubCatData({ ConfigId: selectedCategory })).unwrap();
setTableData([]);
ref?.current?.resetFields();
if (products?.length > 0) {
const hasTheCategory = products.some(product => product.category === selectedCategory);
if (hasTheCategory) {
if (subCatResponse?.data?.statusCode == 1) {
const subCategories = subCatResponse?.data?.data || [];
const availableSubCategories = subCategories?.filter(
(subCat) =>
subCat.NumFId === parseInt(selectedCategory) && !!subCat?.ConfigName
)?.map((subCat) => ({
label: subCat.ConfigName,
value: subCat.ConfigId,
}));
const updatedProducts = products?.map((product) => {
if (product.category === selectedCategory) {
return {
...product,
availableSubCategories,
};
}
return product;
});
setProducts(updatedProducts);
}
}
}
console.log(products, "selectedCategory", selectedCategory)
} else {
setMessageType('error');
setMessageData(response?.data?.response);
ref?.current?.resetFields();
}
};
const handleCatDropDownChange = (value) => {
setSelectedCategory(value);
ref?.current?.setFieldsValue({ CategoryId: value });
};
const statusFormatter = (record, index) => {
let filteredData = tableData.filter(
(item) => item.SubCategory != record?.SubCategory
);
setTableData(filteredData);
};
const handleSubCatModalClose = () => {
close(false);
setSubCategoryImageUrl('');
setSelectedCategory(null);
setTableData([]);
ref?.current?.resetFields();
};
const handleEdit = () => {
};
const onComplete = useCallback(() => {
setMessageData(null);
setMessageType(null);
}, []);
return (
<>
<Messages messageData={messageData} messageType={messageType} onComplete={onComplete} />
<DefaultModal
title="Add SubCategory"
open={open}
footer={true}
buttonText="Submit"
handleSubmit={handleSubmitSubCategory}
handleCancel={handleSubCatModalClose}
children={
<Form ref={ref} className="formDivAnt">
<div className="ProSubRowFlex">
<div className="subColumnFlex">
<div className="subProdCatData">
<Form.Item
name="CategoryId"
rules={[
{
required: true,
message: 'Please select a Category',
},
]}
>
<DropDowns
options={categoryData?.map((option) => ({
value: option?.ConfigId,
label: option?.ConfigName,
}))}
label={<label class="required">Category</label>}
className="field-DropDown"
onChangeFunction={handleCatDropDownChange}
valueData={selectedCategory}
disabled={tableData?.length >= 1}
/>
</Form.Item>
</div>
</div>
<div className="subColumnFlex">
<div>
<Form.Item name="SubConfigName">
<InputField
label={
<label class="required">SubCategory Name</label>
}
autocomplete="off"
/>
</Form.Item>
</div>
</div>
<div style={{ fontSize: '25px' }}>
<Tooltip title="Add Products" placement="top">
<PlusCircleOutlined
className="product-add-iconMargin"
onClick={async () => {
try {
const values =
await ref.current?.validateFields(
['CategoryId', 'SubConfigName']
);
const trimmedValue = values?.SubConfigName
? values.SubConfigName.trim()
: '';
if (!values?.CategoryId) {
throw new Error('Please Select Category');
}
if (!trimmedValue) {
throw new Error('SubCategory Name is required');
}
await validateSafeInput(trimmedValue);
if (trimmedValue.length > 50) {
throw new Error(
'SubCategory Name should not exceed 50 characters'
);
}
!editDataval ? handleEdit() : handleAddSubCategory();
} catch (error) {
message.error(
error.message || 'Please fill required fields'
);
}
}}
/>
</Tooltip>
</div>
<div
className="Quotation-tables"
style={{
maxwidth: '500px',
overflowX: 'auto',
scrollbarWidth: 'thin',
}}
>
<Table
bordered
columns={Columns}
dataSource={tableData}
/>
</div>
</div>
</Form>
}
/>
</>
)
});
export default AddSubCategoryModal

View File

@ -0,0 +1,193 @@
import { forwardRef, useCallback, useState } from "react";
import { DefaultModal } from "../../../Components/Modal/DefaultModal";
import { Form } from "antd";
import { DropDowns } from "../../../Components/Forms/DropDown";
import { InputField } from "../../../Components/Forms/InputField";
import { getSession, validateSafeInput } from "../../../Services/Others";
import { useDispatch } from "react-redux";
import { Messages } from "../../../Components/Notifications/Messages";
import { DatePic } from '../../../Components/Forms/DatePicker.jsx';
import { getAdmin as getTax } from "../../../Features/Tax/Tax.js";
import { getAdmin, postTax } from "../../../Features/ProductPage/ProductPage.js";
const AddTaxModal = forwardRef((
{
open = false,
close = () => { },
prodTaxData = [],
flag = false,
}, ref) => {
const dispatch = useDispatch();
console.log(prodTaxData, 'prodTaxData in modal');
const AppId = getSession('AppId');
const CompId = getSession('CompId');
const UserId = getSession('UserId');
const [messageType, setMessageType] = useState(null);
const [messageData, setMessageData] = useState(null);
const [selectedTaxNameId, setSelectedTaxNameId] = useState(null);
const handleTaxNameDropDownChange = async (ConfigId) => {
ref.current?.setFieldsValue({ TaxName: ConfigId });
await setSelectedTaxNameId(ConfigId);
};
const handleTaxModalClose = () => {
close(false);
setSelectedTaxNameId(null);
ref.current?.resetFields();
}
const handleSubmitTax = async () => {
try {
const subTaxData = await ref?.current?.validateFields();
const effectiveDate = new Date(subTaxData.EffectiveFrom);
const formattedDate = effectiveDate.toLocaleDateString('en-CA');
const addTaxData = {
CompId: CompId,
AppId: AppId,
TaxName: subTaxData?.TaxName,
TaxPercentage: subTaxData?.TaxPercentage,
EffectiveFrom: formattedDate,
Reference: subTaxData.Reference,
CreatedBy: UserId,
};
const response = await dispatch(postTax(addTaxData)).unwrap();
if (response?.data?.statusCode == 1) {
setMessageType('success');
setMessageData(response?.data?.response);
close(false);
if (flag) {
await dispatch(getTax({ CompId: CompId, AppId: AppId }));
} else {
await dispatch(getAdmin({ CompId: CompId, AppId: AppId }));
}
ref?.current?.resetFields();
setSelectedTaxNameId(null);
} else {
setMessageType('error');
setMessageData(response?.data?.response);
}
} catch (error) {
console.log('Error:', error?.message);
}
};
const onComplete = useCallback(() => {
setMessageType(null);
setMessageData(null);
}, []);
return (
<>
<Messages messageData={messageData} messageType={messageType} onComplete={onComplete} />
<DefaultModal
title="Add Tax"
open={open}
footer={true}
buttonText="Submit"
children={
<Form ref={ref} className="formDivAnt">
<div className="ProSubRowFlex">
<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"
inputMode="decimal"
onInput={(e) => {
let cleanedValue = e.target.value.replace(
/[^0-9.]/g,
''
); // Remove invalid characters
const parts = cleanedValue.split('.');
if (cleanedValue.startsWith('.')) {
cleanedValue = '0' + cleanedValue;
}
e.target.value =
parts.length > 2
? `${parts[0]}.${parts.slice(1).join('')}`
: cleanedValue;
}}
/>
</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>
<label className="required">Effective From :</label>
</div>
<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>
</Form>
}
handleSubmit={handleSubmitTax}
handleCancel={handleTaxModalClose}
/>
</>
)
});
export default AddTaxModal;

View File

@ -2,12 +2,19 @@ import { memo, useState, useEffect } from "react";
import { DropDowns } from "../../../Components/Forms/DropDown";
import { Checkbox, Popover } from "antd";
import { Settings, X } from "lucide-react";
import { PlusCircleOutlined } from '@ant-design/icons';
import { Tooltip } from 'antd';
import { getActiveProdSubCatData } from "../../../Features/ProductPage/ProductPage";
import { useDispatch } from "react-redux";
import { getSession } from "../../../Services/Others";
const DefaultSettingPopover = memo(({ field, options, onSubmit, currentDefault, popOverHeading, checkBoxHeading = 'Apply to all existing rows', showField }) => {
const DefaultSettingPopover = memo(({ field, options, onSubmit, currentDefault, popOverHeading, checkBoxHeading = 'Apply to all existing rows', showField, Component = null, triggerModal = () => { }, needAllSubCategories = false }) => {
const dispatch = useDispatch();
const [selectedValue, setSelectedValue] = useState(currentDefault);
const [applyToAll, setApplyToAll] = useState(false);
const [open, setOpen] = useState(false);
console.log(showField, "showField")
const [fetchSubCategoriesData, setFetchSubCategoriesData] = useState(false);
console.log(Component, "showField")
useEffect(() => {
if (open) {
setSelectedValue(currentDefault);
@ -15,6 +22,17 @@ const DefaultSettingPopover = memo(({ field, options, onSubmit, currentDefault,
}
}, [open, currentDefault]);
useEffect(() => {
const fetchAllSubCategories = async () => {
await (dispatch(getActiveProdSubCatData({ AppId: getSession('AppId') })))?.unwrap();
}
if (fetchSubCategoriesData) {
fetchAllSubCategories();
}
}, [fetchSubCategoriesData]);
const handleSubmit = () => {
onSubmit(field, selectedValue, applyToAll);
setOpen(false);
@ -40,20 +58,33 @@ const DefaultSettingPopover = memo(({ field, options, onSubmit, currentDefault,
</div>
{showField && <div
style={{
fontSize: '14px',fontWeight: '500', color: '#333', marginBottom: '10px', padding: '5px 10px', backgroundColor: '#f0f0f0', borderRadius: '4px', textAlign: 'center'
}}
style={{
fontSize: '14px', fontWeight: '500', color: '#333', marginBottom: '10px', padding: '5px 10px', backgroundColor: '#f0f0f0', borderRadius: '4px', textAlign: 'center'
}}
>{showField}</div>}
<div className="popover-section">
<div className="popover-section" style={{ display: 'flex', alignItems: 'flex-start', gap: '5px' }}>
<DropDowns
label={`Select default ${getFieldTitle(field).toLowerCase()}`}
valueData={selectedValue}
onChangeFunction={setSelectedValue}
options={options}
/>
{Component && (
<Tooltip title={`Add ${getFieldTitle(field)}`} placement="top">
<PlusCircleOutlined
className="product-add-iconMargin"
onClick={() => {
triggerModal(true);
if (needAllSubCategories) {
setFetchSubCategoriesData(true);
}
}}
/>
</Tooltip>
)}
</div>
{Component}
<div className="popover-checkbox">
<Checkbox
checked={applyToAll}
@ -76,16 +107,18 @@ const DefaultSettingPopover = memo(({ field, options, onSubmit, currentDefault,
);
return (
<Popover
content={content}
trigger="click"
placement="bottomLeft"
open={open}
onOpenChange={setOpen}
overlayClassName="default-setting-popover"
>
<Settings size={14} className="setting-icon" />
</Popover>
<>
<Popover
content={content}
trigger="click"
placement="bottomLeft"
open={open}
onOpenChange={setOpen}
overlayClassName="default-setting-popover"
>
<Settings size={14} className="setting-icon" />
</Popover>
</>
);
});