Merge pull request 'added otp and time restriction for kiosk' (#203) from temp/kiosk-otp-time-restriction into main

Reviewed-on: Pozomind/pozo-retail-app#203
This commit is contained in:
karthikalakshmi 2026-03-02 11:59:27 +05:30
commit c43f54c031
13 changed files with 737 additions and 160 deletions

View File

@ -45,6 +45,13 @@ export const putupi = createAsyncThunk('putupi', async (putData) => {
return await axiosRetailInstanceData.put(`/upi`, putData);
});
export const checkLastOrderTimeLimit = createAsyncThunk('kiosk/lastOrderTimeLimit', async (data) => {
const { MobileNo, CompId, AppId, BranchId } = data;
if (MobileNo) {
return await axiosRetailInstanceData.get(`/Selfbookingeligibility?mobileNo=${MobileNo}&CompId=${CompId}&AppId=${AppId}&BranchId=${BranchId}`);
}
});
export const delupi = createAsyncThunk('delupi', async (deleteData) => {
if (
data?.CompId != null &&

View File

@ -205,7 +205,7 @@ const SalesCountComponent = React.memo(
let AutoReceiveStock = preferencedata?.[0]?.SettingDtlDetails?.some(
(s) =>
s?.SettingIdName?.toLowerCase() === 'autoreceivestock' &&
s?.SettingValue === 'Y'
s?.SettingValue === 'N'
);
setAutoReceiveStock(!AutoReceiveStock);
if (AutoReceiveStock) {

View File

@ -7,6 +7,7 @@ import { MdCurrencyRupee } from 'react-icons/md';
import PozoDineInIcon from '../KioskIcons/KioskDineIn';
import TakeAway from '../KioskIcons/KioskTakeAway';
import {
changeKioskCustomerMobileNo,
changeKioskPageName,
getKioskDarkColour,
globalCurrentKioskPageName,
@ -54,6 +55,7 @@ const KioskFooter = (props) => {
const Oncancel = () => {
dispatch(changeKioskPageName('initialPage'));
dispatch(changeAllGlbStatustData());
dispatch(changeKioskCustomerMobileNo(null));
// clear all global val
};
@ -110,6 +112,7 @@ const KioskFooter = (props) => {
};
const handlePaymentCancel = () => {
setPaymentModalOpen(false);
dispatch(changeKioskCustomerMobileNo(null));
};
return (
<div>

View File

@ -3,6 +3,7 @@ import { useDispatch, useSelector } from 'react-redux';
import { Popconfirm, Drawer } from 'antd';
import { QuestionCircleOutlined } from '@ant-design/icons';
import {
changeKioskCustomerMobileNo,
changeKioskPageName,
getKioskDarkColour,
globalCurrentKioskPageName,
@ -75,6 +76,7 @@ const KioskFooter2 = (props) => {
const Oncancel = () => {
dispatch(changeKioskPageName('initialPage'));
dispatch(changeAllGlbStatustData());
dispatch(changeKioskCustomerMobileNo(null));
// clear all global val
};
const MobileNumberPage = async () => {
@ -105,6 +107,7 @@ const KioskFooter2 = (props) => {
};
const handlePaymentCancel = () => {
setPaymentModalOpen(false);
dispatch(changeKioskCustomerMobileNo(null));
};
return (

View File

@ -7,6 +7,7 @@ import TakeAway from '../KioskIcons/KioskTakeAway';
import { IoIosArrowForward } from 'react-icons/io';
import { MdCurrencyRupee } from 'react-icons/md';
import {
changeKioskCustomerMobileNo,
changeKioskPageName,
getKioskDarkColour,
globalCurrentKioskPageName,
@ -81,6 +82,7 @@ const KioskFooter3 = (props) => {
const Oncancel = () => {
dispatch(changeKioskPageName('initialPage'));
dispatch(changeAllGlbStatustData());
dispatch(changeKioskCustomerMobileNo(null));
// clear all global val
};
@ -101,6 +103,7 @@ const KioskFooter3 = (props) => {
};
const handlePaymentCancel = () => {
setPaymentModalOpen(false);
dispatch(changeKioskCustomerMobileNo(null));
};
return (
<div className="KioskFooter3-master">

View File

@ -2,6 +2,7 @@ import { Badge, Drawer, Popconfirm } from 'antd';
import '../../../Styles/Kiosk/Components/KioskHeader.scss';
import { IoIosArrowBack } from 'react-icons/io';
import {
changeKioskCustomerMobileNo,
changeKioskPageName,
getKioskCompLogo,
getKioskLightColour,
@ -16,10 +17,12 @@ import {
changeAllGlbStatustData,
getProductCategories,
} from '../../../Features/BookingScreen/BookingData/KioskBookingData';
import { useState } from 'react';
import { useEffect, useState } from 'react';
import KisokOrderCart2 from '../OrderCart/OrderCart2';
import KisokOrderCart1 from '../OrderCart/OrderCart1';
import KisokOrderCart3 from '../OrderCart/OrderCart3';
import { MdFullscreen, MdFullscreenExit } from "react-icons/md";
const KioskHeader = (props) => {
const dispatch = useDispatch();
@ -37,7 +40,36 @@ const KioskHeader = (props) => {
}, 0);
const cartTheme = props?.KioskThemeData?.KioskOrderCart?.[0];
const [open, setOpen] = useState(false);
const [isFullscreen, setIsFullscreen] = useState(false);
console.log(cartTheme, "propsprops")
const toggleFullscreen = () => {
if (!document.fullscreenElement) {
document.documentElement.requestFullscreen();
setIsFullscreen(true);
} else {
document.exitFullscreen();
setIsFullscreen(false);
}
};
useEffect(() => {
// Function to check fullscreen state
const checkFullscreen = () => {
setIsFullscreen(!!document.fullscreenElement);
};
// Check on initial render
checkFullscreen();
// Listen for changes (enter / exit fullscreen)
document.addEventListener('fullscreenchange', checkFullscreen);
return () => {
document.removeEventListener('fullscreenchange', checkFullscreen);
};
}, []);
const BackToCategory = async () => {
await dispatch(
getProductCategories({
@ -51,6 +83,7 @@ const KioskHeader = (props) => {
const BackToCardBasedonPreCatWithClear = async () => {
await dispatch(changeAllGlbStatustData());
await dispatch(changeKioskPageName('initialPage'));
dispatch(changeKioskCustomerMobileNo(null));
};
const BackToCardBasedonPreCat = async () => {
if (
@ -84,6 +117,12 @@ const KioskHeader = (props) => {
>
<div className="header-icon-shopping">
<p>ORDER TOTAL</p>
<div className='headerFullMin'>
{isFullscreen ? (
<MdFullscreenExit size={25} onClick={toggleFullscreen} className='FullMinIcon' />
) : (
<MdFullscreen size={25} onClick={toggleFullscreen} className='FullMinIcon' />
)}
{KioskOrderDetails?.length > 0 && (
<div className="shopicon">
<Badge
@ -102,6 +141,7 @@ const KioskHeader = (props) => {
</div>
)}
</div>
</div>
<div className="kioskcategory-menu2">
<div id="menu" onClick={() => BackToCategory()}>
Menu

View File

@ -10,8 +10,11 @@ import {
import '../../../Styles/Kiosk/InitialPage/InitialPage1.scss';
import { changeBookingType } from '../../../Features/BookingScreen/BookingData/KioskBookingData';
import initialBg from '../../../Images/InitialPage1bgImg.png';
import { useEffect, useRef } from 'react';
import { isMobile } from 'react-device-detect';
const InitialPage1 = (props) => {
const actionContainerRef = useRef(null);
const dispatch = useDispatch();
const getKioskBranchNamedata = useSelector(getKioskBranchName);
const getKioskCompLogodata = useSelector(getKioskCompLogo);
@ -23,6 +26,38 @@ const InitialPage1 = (props) => {
await dispatch(changeBookingType(eventName));
};
useEffect(() => {
const toggleFullScreen = () => {
if (!document.fullscreenElement) {
document.documentElement.requestFullscreen?.().catch((err) => {
console.log('Fullscreen blocked:', err);
});
} else {
document.exitFullscreen?.();
}
};
const handleDoubleClick = (e) => {
// Ignore if double click is inside Dine/TakeAway section
if (
actionContainerRef.current &&
actionContainerRef.current.contains(e.target)
) {
return;
}
toggleFullScreen();
};
window.addEventListener('dblclick', handleDoubleClick);
return () => {
window.removeEventListener('dblclick', handleDoubleClick);
};
}, []);
console.log(isMobile, "isMobile");
return (
<>
<div className="InitialPAge1-Container">
@ -60,7 +95,7 @@ const InitialPage1 = (props) => {
/> */}
</p>
</div>
<div>
<div ref={actionContainerRef}>
{(SalesType === 'D' || SalesType === 'B') && (
<div
onClick={() => {

View File

@ -81,10 +81,13 @@ import { ApplicationPreferences } from '../../../Features/BrachLogin/BranchLogin
import { VerifiedPaymentDtl } from '../../../Features/BookingScreen/Customer/addCustomer.js';
import PaymentGatewayEmbedded from '../../BookingScreen/Components/UtillComponents/PaymentGatewayEmbedded.jsx';
import MobileMultiPdfPrintTrigger from '../../BookingScreen/Components/UtillComponents/MobileMultiPdfPrintTrigger.jsx';
import { checkLastOrderTimeLimit, sendupiotp } from '../../../Features/UpiDetails/UpiDetails.js';
import { isIndianMobile } from '../../../utils/calculations.js';
const CustomPaymentUrl = import.meta.env.ENV_CUSTOM_PAYMENT_URL;
const KioskPayment = (props) => {
const paymentGatewayRef = React.useRef(null);
const dispatch = useDispatch();
const SessionData = props?.sessionData;
const DeviceMacAddress = getSession('DeviceAddress');
@ -92,6 +95,7 @@ const KioskPayment = (props) => {
const [open, setOpen] = useState(props.modalOpen);
const [width, setWidth] = useState(window.innerWidth);
const [showCancelConfirm, setShowCancelConfirm] = useState(false);
const appPreferences = useSelector(ApplicationPreferences);
const CustomerMobileNumber = useSelector(globalKioskCustomerMobileNo);
const OrderId = useSelector(GlobalOrderId);
@ -147,9 +151,28 @@ const KioskPayment = (props) => {
setting?.SettingIdName?.toLowerCase() === 'mobilea4' &&
setting?.SettingValue === 'Y'
);
const isOtpEnabled = SettingDataSelector?.[0]?.SettingDtlDetails?.find(
(setting) =>
setting?.SettingIdName?.toLowerCase() === 'kioskotp' &&
setting?.SettingValue === 'Y'
);
const isTimeLimitEnabled = SettingDataSelector?.[0]?.SettingDtlDetails?.find(
(setting) =>
(setting?.SettingIdName?.toLowerCase() === 'hours' || setting?.SettingIdName?.toLowerCase() === 'minutes') &&
setting?.SettingValue === 'Y'
);
const bookingTypePreference = appPreferences?.find(
(preference) => preference?.PreferredCatName === 'Booking Type'
)?.PreferenceCatDetails;
const [showPaymentMethods, setShowPaymentMethods] = useState(null);
const [resendTimer, setResendTimer] = useState(0)
const [canResend, setCanResend] = useState(true)
const [otpInput, setOtpInput] = useState(['', '', '', '']);
const [otpIndex, setOtpIndex] = useState(0);
const [otpSent, setOtpSent] = useState(false);
const [otpVerifed, setOtpVerified] = useState(false);
const [otpNumber, setOTPNumber] = useState(null);
const [paymentUrl, setpaymentUrl] = useState(null);
const [businessUPI, setBusinessUPI] = useState(false);
console.log(PrintOrderDetails, 'PrintOrderDetails');
@ -161,16 +184,20 @@ const KioskPayment = (props) => {
useEffect(() => {
TotalCalculation();
}, [KioskOrderDetails]);
// useEffect(() => {
// let data = {
// CompId: SessionData.CompId,
// BranchId: SessionData.BranchId,
// AppId: SessionData.AppId,
// DeviceMacAddress: DeviceMacAddress,
// };
// dispatch(getKioskPrinterDetails(data)).unwrap();
// dispatch(getPreferenceData({ CompId: SessionData.CompId, BranchId: SessionData.BranchId, AppId: SessionData.AppId })).unwrap();
// }, []);
useEffect(() => {
if (SessionData.CompId && SessionData.BranchId && SessionData.AppId) {
if (DeviceMacAddress) {
let data = {
CompId: SessionData.CompId,
BranchId: SessionData.BranchId,
AppId: SessionData.AppId,
DeviceMacAddress: DeviceMacAddress,
};
dispatch(getKioskPrinterDetails(data)).unwrap();
}
dispatch(getPreferenceData({ CompId: SessionData.CompId, BranchId: SessionData.BranchId, AppId: SessionData.AppId })).unwrap();
}
}, [SessionData.CompId, SessionData.BranchId, SessionData.AppId]);
useEffect(() => {
const getPrintData = async () => {
@ -325,7 +352,7 @@ const KioskPayment = (props) => {
}, [width]);
useEffect(() => {
number?.length === 10 && dispatch(changeKioskCustomerMobileNo(number));
isIndianMobile(number) && dispatch(changeKioskCustomerMobileNo(number));
}, [number]);
const onChange = (e) => {
@ -348,8 +375,7 @@ const KioskPayment = (props) => {
const items = [
{
key: '1',
label: `${
paymentGatewayMethods?.[selectedOption]?.CardType == 'CRDC' ||
label: `${paymentGatewayMethods?.[selectedOption]?.CardType == 'CRDC' ||
paymentGatewayMethods?.[selectedOption]?.CardType == 'DBCRD'
? 'Choose Your Card'
: paymentGatewayMethods?.[selectedOption]?.CardType == 'WLT'
@ -396,6 +422,33 @@ const KioskPayment = (props) => {
});
};
const handleOtpNumber = (num) => {
if (otpIndex > 3) return;
const newOtp = [...otpInput];
newOtp[otpIndex] = num.toString();
setOtpInput(newOtp);
if (otpIndex < 3) {
setOtpIndex(otpIndex + 1);
}
};
const handleOtpBackspace = () => {
if (otpIndex < 0) return;
const newOtp = [...otpInput];
if (newOtp[otpIndex]) {
newOtp[otpIndex] = '';
} else if (otpIndex > 0) {
newOtp[otpIndex - 1] = '';
setOtpIndex(otpIndex - 1);
}
setOtpInput(newOtp);
};
const handleBackspace = () => {
setNumber((prevNumber) => prevNumber.slice(0, -1));
};
@ -709,7 +762,27 @@ const KioskPayment = (props) => {
return null;
}
const checkLast30MinOrder = async (MobileNo) => {
const response = await dispatch(checkLastOrderTimeLimit({
MobileNo,
AppId: SessionData.AppId,
CompId: SessionData.CompId,
BranchId: SessionData.BranchId,
}))?.unwrap();
return response?.data;
};
const handlePaymentOption = async (option) => {
if (isTimeLimitEnabled) {
const withinTimeLimit = await checkLast30MinOrder(number);
if (withinTimeLimit?.IsEligible === false) {
setMessageData(withinTimeLimit?.response);
setMessageType('warning');
return;
}
}
setSelectedPaymentOption(option);
dispatch(changeSelectedKioskPaymentOption(option));
@ -1761,6 +1834,80 @@ const KioskPayment = (props) => {
clearAllStates();
};
const sendOTP = async () => {
const valid = isIndianMobile(number);
if (valid) {
if (isTimeLimitEnabled) {
const withinTimeLimit = await checkLast30MinOrder(number);
if (withinTimeLimit?.IsEligible === false) {
setMessageData(withinTimeLimit?.response);
setMessageType('warning');
return;
}
}
} else {
setMessageData('Please enter valid mobile No.');
setMessageType('warning');
return
}
try {
setOtpIndex(0);
setOtpSent(true);
let response = await dispatch(sendupiotp({
"MobileNo": number,
"CreatedBy": 0
}))?.unwrap()
if (response?.data?.statusCode == 1) {
setMessageData('OTP sent to your mobile number');
setMessageType('success');
setOTPNumber(response?.data?.OTP);
startResendTimer();
}
}
catch (error) {
console.log(error)
}
}
const startResendTimer = () => {
setCanResend(false);
setResendTimer(30);
const timer = setInterval(() => {
setResendTimer((prev) => {
if (prev <= 1) {
clearInterval(timer);
setCanResend(true);
return 0;
}
return prev - 1;
});
}, 1000);
}
const handleOtpChange = (e, index) => {
const value = e.target.value.replace(/\D/g, '');
const newOtp = [...otpInput];
newOtp[index] = value;
setOtpInput(newOtp);
};
const verifyOtp = () => {
const enteredOtp = otpInput.join('');
if (enteredOtp === otpNumber?.toString()) {
setMessageType('success');
setMessageData('OTP Verified Successfully');
setOtpSent(false);
setOtpInput(['', '', '', '']);
setShowPaymentMethods(true);
setOtpVerified(true);
} else {
setMessageType('error');
setMessageData('Invalid OTP');
}
};
return (
<div>
<Messages
@ -1889,10 +2036,13 @@ const KioskPayment = (props) => {
</div>
) : (
<div className="paymentModal-Master">
<h4 className="Titlepayment">PAYMENT</h4>
<h4 className="Titlepayment">{isOtpEnabled && !showPaymentMethods
? "Verify Mobile Number"
: "PAYMENT"}</h4>
<div className="paymentModal_main">
{!otpSent && !otpVerifed && (<>
<div className="prompt">
<p>
<p style={{ fontFamily: 'Poppins', fontWeight: '600' }}>
Get your order bill on your mobile <br />
via SMS / Whatsapp
</p>
@ -1902,8 +2052,10 @@ const KioskPayment = (props) => {
className={`KioskPayment-inputModal ${number ? 'green-border' : ''}`}
>
<input type="text" value={number} readOnly />
{number?.length === 10 && !isIndianMobile(number) && <div className='input-err'>Please Enter Valid Mobile Number</div>}
</div>
{/* input boxxes */}
<div
className="KioskPayment-dailpad-main"
@ -1943,16 +2095,108 @@ const KioskPayment = (props) => {
</div>
</div>
</div>
{isOtpEnabled && (
<>
<button className='KioskOPTBTn'
// disabled={number?.length === 10}
onClick={() => sendOTP()}
>Get OTP</button>
</>
)}
</>)}
{(isOtpEnabled && otpSent) && (
<div className="otp-section">
{/* Mobile Number + Edit */}
<div className="otp-mobile-row">
<span className="otp-mobile-number">{number}</span>
<button
className="otp-edit-btn"
onClick={() => {
setOtpSent(false);
setOtpInput(['', '', '', '']);
setShowPaymentMethods(false);
}}
>
Edit
</button>
</div>
{/* 4 Digit OTP Input */}
<div className="otp-input-container">
{otpInput.map((digit, index) => (
<input
key={index}
type="text"
value={digit}
className={`otp-box ${index === otpIndex ? 'active' : ''}`}
onClick={() => setOtpIndex(index)}
readOnly
/>
))}
</div>
<div className="KioskPayment-dailpad-main">
<div
className="Kiosk-dailpadModal"
style={{ backgroundColor: getKioskLightColourdata }}
>
<div className="KioskPayment-dailpad-1">
<p onClick={() => handleOtpNumber(1)}>1</p>
<p onClick={() => handleOtpNumber(2)}>2</p>
<p onClick={() => handleOtpNumber(3)}>3</p>
</div>
<div className="KioskPayment-dailpad-2">
<p onClick={() => handleOtpNumber(4)}>4</p>
<p onClick={() => handleOtpNumber(5)}>5</p>
<p onClick={() => handleOtpNumber(6)}>6</p>
</div>
<div className="KioskPayment-dailpad-3">
<p onClick={() => handleOtpNumber(7)}>7</p>
<p onClick={() => handleOtpNumber(8)}>8</p>
<p onClick={() => handleOtpNumber(9)}>9</p>
</div>
<div className="KioskPayment-dailpad-4">
<p onClick={handleOtpBackspace}>
<HiOutlineBackspace size={30} />
</p>
<p onClick={() => handleOtpNumber(0)}>0</p>
<p style={{ background: 'none' }}></p>
</div>
</div>
</div>
{/* Buttons */}
<div className="otp-buttons">
<button
className="verify-btn"
disabled={otpInput.join('').length !== 4}
onClick={verifyOtp}
>
Verify
</button>
<button
className="resend-btn"
disabled={!canResend}
onClick={sendOTP}
>
{canResend ? "Resend OTP" : `Resend in ${resendTimer}s`}
</button>
</div>
</div>
)}
{(!isOtpEnabled || (showPaymentMethods && otpVerifed)) && <>
<p className="choiseTitle">Select Payment Method</p>
{console.log('selectedOption', selectedPaymentOption)}
<div
className="paymentChoise"
style={{
pointerEvents:
(number?.length !== 10 || selectedPaymentOption) &&
(!isIndianMobile(number) || selectedPaymentOption) &&
'none',
opacity:
(number?.length !== 10 || selectedPaymentOption) && '40%',
(!isIndianMobile(number) || selectedPaymentOption) && '40%',
}}
>
{/* {upiAvailability && (
@ -1999,6 +2243,7 @@ const KioskPayment = (props) => {
</div>
)}
</div>
</>}
</div>
</div>
)
@ -2076,10 +2321,28 @@ const KioskPayment = (props) => {
paymentSucess={paymentSucess}
ClearAllGlobalStateDatas={ClearAllGlobalStateDatas}
// CustomerDisplay={CustomerDisplay}
ref={paymentGatewayRef}
/>
</>
}
/>
{showCancelConfirm && (
<Modal
open={showCancelConfirm}
title="Cancel Payment"
onOk={() => {
paymentGatewayRef.current?.clearPolling();
setBusinessUPI(false);
setShowCancelConfirm(false);
ClearAllGlobalStateDatas();
}}
onCancel={() => setShowCancelConfirm(false)}
okText="Yes"
cancelText="No"
>
Do you want to cancel the payment?
</Modal>
)}
</div>
);
};

View File

@ -27,6 +27,14 @@
display: flex;
justify-content: center;
padding: 1rem 1rem;
flex-direction: column;
.input-err{
color: red;
font-size: 13px;
margin-top: 4px;
font-family: 'Poppins', sans-serif;
}
input {
width: 50vw;
@ -317,10 +325,7 @@
padding: 54px 35px !important;
}
.paymentCardMain
.ant-collapse
.ant-collapse-item:last-child
> .ant-collapse-content {
.paymentCardMain .ant-collapse .ant-collapse-item:last-child>.ant-collapse-content {
overflow: scroll;
height: 26vh;
}
@ -337,6 +342,7 @@
}
@media (max-width: 768px) {
.paymentModal_main .KioskPayment-inputModal input,
.paymentModal_main .Kiosk-dailpadModal {
width: 65vw;
@ -436,3 +442,122 @@
height: 80vh;
}
}
.KioskOPTBTn {
width: 70%;
padding: 12px 16px;
font-size: 20px;
font-family: "Poppins";
font-weight: 400;
border: none;
outline: none;
background-color: #000000;
color: #fff;
border-radius: 10px;
cursor: pointer;
}
.otp-section {
margin-top: 20px;
text-align: center;
.otp-mobile-row {
display: flex;
justify-content: center;
align-items: center;
gap: 15px;
margin-bottom: 20px;
.otp-mobile-number {
font-size: 20px;
font-weight: 600;
letter-spacing: 1px;
}
.otp-edit-btn {
background: none;
border: none;
color: #007bff;
font-size: 16px;
cursor: pointer;
&:hover {
text-decoration: underline;
}
}
}
.otp-input-container {
display: flex;
justify-content: center;
gap: 20px;
margin-bottom: 25px;
.otp-box {
width: 65px;
height: 65px;
font-size: 28px;
text-align: center;
border: 2px solid #ccc;
border-radius: 8px;
outline: none;
transition: 0.2s ease;
&:focus {
border-color: #28a745;
box-shadow: 0 0 6px rgba(40, 167, 69, 0.4);
}
}
.otp-box.active {
border: 2px solid #28a745;
}
}
.otp-buttons {
display: flex;
justify-content: center;
gap: 20px;
button {
min-width: 140px;
padding: 12px 18px;
font-size: 16px;
font-weight: 500;
border-radius: 8px;
cursor: pointer;
transition: 0.2s ease;
font-family: 'Poppins', sans-serif !important;
display: flex;
align-items: center;
justify-content: center;
&:disabled {
opacity: 0.5;
cursor: not-allowed;
}
}
.verify-btn {
background-color: #28a745;
border: none;
color: #fff;
&:hover:not(:disabled) {
background-color: #218838;
}
}
.resend-btn {
background-color: #ffc107;
border: none;
color: #000;
&:hover:not(:disabled) {
background-color: #e0a800;
}
}
}
}

View File

@ -22,7 +22,7 @@ import { ApplicationPreferences } from '../../Features/BrachLogin/BranchLogin.js
import './PreferenceList.scss';
const PreferenceList = () => {
const { SadminuserAccess } = useAuth();
const { SadminuserAccess, featureaddDetails } = useAuth();
const dispatch = useDispatch();
const formRef = useRef();
@ -57,16 +57,17 @@ const PreferenceList = () => {
const settingsList = tableValue[0]?.SettingDtlDetails ?? preferenceConfigs;
const allSettingsMap = {};
settingsList?.forEach(({ SettingIdName, SettingValue, SettingCount }) => {
allSettingsMap[SettingIdName?.toLowerCase()] = { value: SettingValue, count: SettingCount };
settingsList?.forEach(({ SettingIdName, SettingValue, SettingCount, SettingTime }) => {
allSettingsMap[SettingIdName?.toLowerCase()] = { value: SettingValue, count: SettingCount, time: SettingTime?.split(' ')?.[0] };
});
console.log(settingsList, 'settingsList');
console.log(featureaddDetails, 'featureaddDetails');
const buildInitialValues = () => ({
dashboard: allSettingsMap['dashboard']?.value === 'Y',
shortcutkeys: allSettingsMap['shortcutkeys']?.value === 'Y',
autoreceivestock: allSettingsMap['autoreceivestock']?.value === 'Y',
kioskotp: allSettingsMap['kioskotp']?.value === 'Y',
decimal: allSettingsMap['decimal']?.value === 'Y',
enabledModules: [
...(allSettingsMap['dinein']?.value === 'Y' ? ['dinein'] : []),
@ -86,7 +87,17 @@ const PreferenceList = () => {
preorderpricechange: allSettingsMap['preorderpricechange']?.value === 'Y',
quantityrestriction: allSettingsMap['overalllimit']?.value === 'Y' || allSettingsMap['itemwiselimit']?.value === 'Y',
quantityrestrictiontype: allSettingsMap['overalllimit']?.value === 'Y' ? 'overall' : allSettingsMap['itemwiselimit']?.value === 'Y' ? 'itemwise' : 'overall',
kiosktimelimit: allSettingsMap['hours']?.value === 'Y' || allSettingsMap['minutes']?.value === 'Y',
kiosktimelimittype: allSettingsMap['hours']?.value === 'Y' ? 'hours' : allSettingsMap['minutes']?.value === 'Y' ? 'minutes' : 'minutes',
maxquantity: allSettingsMap['overalllimit']?.value === 'Y' ? allSettingsMap['overalllimit']?.count || 1 : allSettingsMap['itemwiselimit']?.value === 'Y' ? allSettingsMap['itemwiselimit']?.count || 1 : 1,
maxtime:
allSettingsMap['hours']?.value === 'Y'
? (allSettingsMap['hours']?.time
? parseFloat(allSettingsMap['hours']?.time / 60)
: 0.5)
: allSettingsMap['minutes']?.value === 'Y'
? parseFloat(allSettingsMap['minutes']?.time || 30)
: 30,
billOption: ['whatsapp', 'sms', 'email'].some(
(k) => allSettingsMap[k]?.value === 'Y'
)
@ -158,6 +169,7 @@ const PreferenceList = () => {
if (settingsList) {
const built = buildInitialValues();
setInitialValues(built);
console.log(built, 'built');
formRef.current?.setFieldsValue(built);
}
}, [settingsList]);
@ -295,6 +307,7 @@ const PreferenceList = () => {
dashboard: values.dashboard,
shortcutkeys: values.shortcutkeys,
autoreceivestock: values.autoreceivestock,
kioskotp: values.kioskotp,
decimal: values.decimal,
searchfocus: values.searchfocus,
addproductcard: values.addproductcard,
@ -317,6 +330,8 @@ const PreferenceList = () => {
editbill: values.editbill,
overalllimit: values.quantityrestriction && values.quantityrestrictiontype === 'overall',
itemwiselimit: values.quantityrestriction && values.quantityrestrictiontype === 'itemwise',
hours: values.kiosktimelimit && values.kiosktimelimittype === 'hours',
minutes: values.kiosktimelimit && values.kiosktimelimittype === 'minutes',
};
const arrayValues = {
@ -349,7 +364,13 @@ const PreferenceList = () => {
SettingId: setting.SettingId,
Comments: setting.Comments || 'Enter Comment',
SettingValue: isChecked ? 'Y' : 'N',
SettingCount: ((key === 'overalllimit' || key === 'itemwiselimit') && isChecked) ? parseFloat(values.maxquantity) || null : null
SettingCount: ((key === 'overalllimit' || key === 'itemwiselimit') && isChecked) ? parseFloat(values.maxquantity) || null : null,
SettingTime:
((key === "hours" && isChecked)
? ((parseFloat(values.maxtime) * 60)?.toFixed(2) + ' ' + 'Minutes' || null)
: (key === "minutes" && isChecked)
? (parseFloat(values.maxtime)?.toFixed(2) + ' ' + 'Minutes' || null)
: null),
};
});
@ -459,6 +480,7 @@ const PreferenceList = () => {
</div>
<Form
key={JSON.stringify(initialValues)}
ref={formRef}
layout="vertical"
onFinish={handleSubmit}
@ -511,6 +533,65 @@ const PreferenceList = () => {
</Checkbox.Group>
</Form.Item>
)}
{featureaddDetails?.find(
(item) => item?.FeatureAddonName?.toLowerCase() === 'kiosk sales'
) && renderCheckbox('kioskotp', 'Enable OTP verification for Kiosk Orders?')}
{featureaddDetails?.find(
(item) => item?.FeatureAddonName?.toLowerCase() === 'kiosk sales'
) && (allSettingsMap['hours'] !== undefined || allSettingsMap['minutes'] !== undefined) && <>
<Form.Item name="kiosktimelimit" label="Restrict orders for the same customer within a time limit?" valuePropName="checked">
<Checkbox>Yes</Checkbox>
</Form.Item>
<Form.Item noStyle shouldUpdate={(prev, curr) => prev.kiosktimelimit !== curr.kiosktimelimit}>
{({ getFieldValue }) => getFieldValue('kiosktimelimit') && (
<>
<Form.Item name="kiosktimelimittype" label="How would you like to apply the time restriction?">
<Radio.Group onChange={() => formRef.current?.setFieldsValue({ maxtime: undefined })}>
<Radio value="minutes">Minutes</Radio>
<Radio value="hours">Hours</Radio>
</Radio.Group>
</Form.Item>
<Form.Item
noStyle
shouldUpdate={(prev, curr) =>
prev.kiosktimelimittype !== curr.kiosktimelimittype
}
>
{({ getFieldValue }) => {
const type = getFieldValue('kiosktimelimittype');
const isHours = type === 'hours';
return (
<Form.Item
name="maxtime"
label={`Maximum Time Limit (${isHours ? 'Hours' : 'Minutes'}):`}
rules={[
{ required: true, message: 'Please enter maximum time' },
{
type: 'number',
min: isHours ? 0.25 : 1,
message: isHours
? 'Minimum allowed is 0.25 hours'
: 'Minimum allowed is 1 minute',
},
]}
>
<InputNumber
min={isHours ? 0.25 : 1}
step={isHours ? 0.25 : 1}
precision={isHours ? 2 : 0}
style={{ width: '200px' }}
/>
</Form.Item>
);
}}
</Form.Item>
</>
)}
</Form.Item>
</>}
</div>
<Divider />

View File

@ -437,6 +437,7 @@ const CreateTransfer = () => {
InwardDtlId: item.InwardDtlId,
ProdVariantName:item.ProdVariantName,
RequestedQty: Number(item.ReceivedQty),
ProdVariantName: item.ProdVariantName,
})),
CreatedBy: UserId,
};

View File

@ -67,3 +67,17 @@
font-weight: 600;
flex-direction: row-reverse;
}
.headerFullMin{
display: flex;
align-items: center;
gap: 10px;
}
.FullMinIcon{
background-color: #fff;
height: 40px;
width: 40px;
padding: 5px;
border-radius: 4px;
transition: all 0.3s ease-in-out;
cursor: pointer;
}

View File

@ -8,3 +8,5 @@ export const capitalizeFirstLetter = (text) => {
if (!text) return '';
return text.charAt(0).toUpperCase() + text.slice(1);
};
export const isIndianMobile = (mobile) => /^\d{10}$/.test(mobile) && /^[6-9]/.test(mobile);