Android_Retail/src/Pages/CancelReschedule/BookingReScedule.jsx

652 lines
33 KiB
JavaScript

import { useCallback, useEffect, useRef, useState } from 'react';
import { useDispatch } from 'react-redux';
import dayjs from 'dayjs';
import { Form, DatePicker } from 'antd';
import { DropDowns } from '../../Components/Forms/DropDown';
import { Messages } from '../../Components/Notifications/Messages';
import FormHeader from '../PageComponents/FormHeader';
import { InputField } from '../../Components/Forms/InputField';
import { BiSearchAlt } from "react-icons/bi";
import { getRescheduleSlot, getSlotReschedule } from '../../Features/BookingScreen/BookingData/BookingData';
import { getSession } from '../../Services/Others';
import { postRescheduleSlot } from '../../Features/CancelReschedule/CancelApplicableProd';
import "../../Styles/CancelReschedule/BookingRechedule.scss"
import { DatePicProd } from "../../Components/Forms/DatePickerProduct";
import { useNavigate } from "react-router-dom";
import { FaEye } from "react-icons/fa";
import Buttons from "../../Components/Forms/Buttons";
import { ArrowRightOutlined } from '@ant-design/icons';
import { changeBreadCrumb } from '../../Features/AppPage/CenterPage';
const SEARCH_OPTIONS = [
{ label: 'Bill No', value: 'Bill No' },
{ label: 'Date', value: 'Date' },
];
const subDirectory = import.meta.env.ENV_BASE_URL;
const breadcrumbItems = [
{ name: "Home", link: `${subDirectory}app-page/home` },
{ name: "Booking Reschedule", link: `${subDirectory}setting/booking-reschedule` },
{ name: "List", link: `${subDirectory}setting/booking-reschedule/list` },
];
const BookingReScedule = () => {
const formRef = useRef();
const navigateTo = useNavigate();
const CompId = getSession('CompId');
const BranchId = getSession('BranchId');
const AppId = getSession('AppId');
const UserId = getSession('UserId');
const UserType = getSession('UserType');
const dispatch = useDispatch();
const [searchType, setSearchType] = useState('Bill No');
const [form, setForm] = useState({ BillNo: '', Date: '' });
const [messages, setMessage] = useState({ type: null, data: null });
const [billData, setBillData] = useState([]);
const [selectedRowKey, setSelectedRowKey] = useState(null);
const [productSlots, setProductSlots] = useState({});
const [selectedSlots, setSelectedSlots] = useState({});
const [dateSelections, setDateSelections] = useState({});
const handelNavigateToList = () => {
navigateTo(`${subDirectory}setting/booking-reschedule/list`);
};
useEffect(() => {
dispatch(changeBreadCrumb({ items: breadcrumbItems }));
}, []);
const handleBillSelect = (orderId) => {
setSelectedRowKey(orderId);
setSelectedSlots({});
setDateSelections({});
setProductSlots({});
};
const handleSlotChange = (InwardDtlId, slot) => {
if (slot.status === "booked" || slot.status === "blocked") return;
setSelectedSlots(prev => ({
...prev,
customerSloteDtl: billData?.[0]?.productDetails?.[0],
[InwardDtlId]: {
SlotFrom: slot.from,
SlotTo: slot.to,
Rate: slot.amount,
slotId: slot.id,
SalesQty: slot.SalesQty,
InwardDtlId: slot.InwardDtlId
}
}));
};
const handleDateChange = async (item, date) => {
const { ProdId, InwardDtlId, Type, Rate, TotalAmt, TaxAmt, SinglePc } = item;
try {
if (date) {
const formattedDate = date.format('YYYY-MM-DD');
const Payload = {
AppId,
BranchId,
CompId,
prodId: ProdId,
date: formattedDate
};
const res = await dispatch(getSlotReschedule(Payload)).unwrap();
if (!res?.data?.data?.[0]?.ProductDetail) {
throw new Error('No slot data available');
}
const productDetails = res.data.data[0].ProductDetail;
const variants = productDetails[0]?.ProdVariantDetails || [];
const formattedSlots = variants.map((v, index) => {
const stock = v.StockDetails?.[0] || {};
const available = Number(stock.TotalSlotQty || 0) - Number(stock.BookedQty || 0);
let status = "available";
if (stock.SlotStatus === "Blocked") status = "blocked";
else if (available <= 0) status = "booked";
else if (available < Number(stock.TotalSlotQty)) status = "partial";
const amount = v.SinglePc === "Y" ? stock.OnePcsPrice || 0 : stock.SellPrice || 0;
const slotInwardDtlId = stock?.InwardDtlId;
return {
id: index,
name: v.ProdVariantName,
InwardDtlId: slotInwardDtlId,
from: v.AvailableFrom,
to: v.AvailableTo,
SalesQty: stock.SalesQty,
amount,
available,
status,
productSlotDetails: item
};
});
// Store slots specific to this product
setProductSlots(prev => ({
...prev,
[InwardDtlId]: formattedSlots
}));
// Store date selection for this product
setDateSelections(prev => ({
...prev,
[InwardDtlId]: {
ProdId,
InwardDtlId,
Type,
Rate,
TotalAmt,
TaxAmt,
SinglePc: SinglePc,
RescheduleDate: formattedDate,
}
}));
} else {
// Remove date selection and slots for this product
setDateSelections(prev => {
const updated = { ...prev };
delete updated[InwardDtlId];
return updated;
});
setSelectedSlots(prev => {
const updated = { ...prev };
delete updated[InwardDtlId];
return updated;
});
setProductSlots(prev => {
const updated = { ...prev };
delete updated[InwardDtlId];
return updated;
});
}
} catch (error) {
setMessage({
type: 'error',
data: error?.message || 'An error occurred while fetching slot data'
});
}
};
const formatTime = (timeStr) => {
if (!timeStr) return "";
const [hour, minute] = timeStr.split(":").map(Number);
const period = hour >= 12 ? "PM" : "AM";
const formattedHour = hour % 12 || 12;
return `${formattedHour}:${String(minute).padStart(2, '0')} ${period}`;
};
const handleSearchTypeChange = (value) => {
setSearchType(value);
setForm({ BillNo: '', Date: '' });
setBillData([]);
setSelectedRowKey(null);
};
const handleInputChange = (name, value) => {
setForm((prev) => ({ ...prev, [name]: value }));
};
const handleSubmit = async () => {
const formValues = formRef.current?.getFieldsValue();
const { BillNo, Date } = formValues;
let date = null;
if (Date !== undefined && Date) {
date = Date.format('YYYY-MM-DD');
}
const data = {
segmentValue: searchType,
date,
AppId,
CompId,
BranchId,
OrderId: searchType === 'Bill No'
? `O${AppId}-${CompId}-${BranchId}-${UserId}-${BillNo}`
: undefined,
};
try {
const res = await dispatch(getRescheduleSlot(data)).unwrap();
if (res?.data?.statusCode === 1) {
setBillData(res?.data?.data);
if (res?.data?.data?.length === 1) {
handleBillSelect(res?.data?.data?.[0]?.OrderId);
} else {
setSelectedRowKey(null);
}
} else {
setBillData([]);
setSelectedRowKey(null);
setMessage({ type: 'error', data: res?.data?.response });
}
} catch (error) {
setBillData([]);
setMessage({ type: 'error', data: error?.message || 'An error occurred while fetching data' });
}
};
const onComplete = useCallback(() => {
setMessage({ type: null, data: null });
}, []);
const onFinishPost = async () => {
const selectedDateKeys = Object.keys(dateSelections);
if (!selectedDateKeys.length) {
setMessage({ type: "error", data: "Please select a date for at least one item" });
return;
}
// Check if all selected dates have slots selected
const hasUnselectedSlots = selectedDateKeys.some(key => !selectedSlots[key]);
if (hasUnselectedSlots) {
setMessage({ type: "error", data: "Please select time slots for all selected dates" });
return;
}
try {
const orderData = billData.find(bill => bill.OrderId === selectedRowKey);
const SalesId = orderData?.productDetails?.[0]?.SalesId;
const PaymentStatus = orderData?.PaymentStatus;
const salesQty = orderData?.productDetails?.[0]?.SalesQty;
// Build payload only for products that have both date AND slot selected
const RescheduleSlotsDtl = selectedDateKeys.map(key => {
const dateInfo = dateSelections[key];
const slotInfo = selectedSlots[key];
return {
ProdId: dateInfo.ProdId,
InwardDtlId: slotInfo.InwardDtlId,
Type: dateInfo.Type,
Qty: salesQty,
Rate: dateInfo.Rate,
TotalAmt: dateInfo.TotalAmt,
TaxAmt: dateInfo.TaxAmt,
SinglePc: dateInfo.SinglePc,
RescheduleDate: dateInfo.RescheduleDate,
SlotFrom: slotInfo.SlotFrom,
SlotTo: slotInfo.SlotTo
};
});
const originalPayment = orderData?.OrderPaymentDtls?.[0] || {};
const Amount = RescheduleSlotsDtl.reduce((acc, item) => acc + (item.TotalAmt || 0), 0);
const payload = {
AppId,
CompId,
BranchId,
OrderId: selectedRowKey,
SalesId,
RescheduleSlotsDtl,
OrderPaymentDtl: [
{
PaymentType: originalPayment.PaymentType,
Amount,
PaymentStatus,
PaymentOptionType: originalPayment.PaymentOptionType
}
],
CreatedBy: UserId
};
console.log('Payload being sent:', payload);
const res = await dispatch(postRescheduleSlot(payload)).unwrap();
if (res?.data?.statusCode === 1) {
setMessage({ type: "success", data: res?.data?.response || "Rescheduled successfully" });
setSelectedRowKey(null);
formRef.current?.resetFields();
setDateSelections({});
setSelectedSlots({});
setBillData([]);
setProductSlots({});
setSearchType("Bill No");
} else {
setMessage({ type: "error", data: res?.data?.response || "Failed to reschedule" });
}
} catch (error) {
setMessage({ type: "error", data: error?.message || "An error occurred" });
}
};
const getLabel = (value) => {
const option = SEARCH_OPTIONS.find(opt => opt.value === value);
return option ? option.label : '';
};
const handleSearch = () => {
formRef.current?.submit();
};
const selectedProducts = billData
?.find(({ OrderId }) => OrderId === selectedRowKey)
?.productDetails || [];
const hasDateSelections = Object.keys(dateSelections).length > 0;
return (
<div className="bookingReschedule">
<div className="bookingReschedule__container">
<div className="userPageContent">
<Messages
messageType={messages.type}
messageData={messages.data}
onComplete={onComplete}
/>
{/* Header Section */}
<div className="bookingReschedule__header">
<FormHeader title={'Slot Reschedule'} />
<button className="bookingReschedule__viewBtn" onClick={handelNavigateToList}>
<FaEye /> <span>View List</span>
</button>
</div>
{/* Search Form */}
<div className='formContainer'>
<Form
ref={formRef}
layout="vertical"
autoComplete="off"
onFinish={handleSubmit}
>
<p className="BOFO-Offer-Saveformheader">Search Your Bill Here:</p>
<div className='InoutSalesBill'>
<Form.Item
name="searchType"
initialValue={searchType}
rules={[{ required: true, message: 'Please select search type' }]}
>
<DropDowns
label={<label className="required">Search By</label>}
className="field-DropDown"
valueData={searchType}
isOnChange={!!searchType}
onChangeFunction={handleSearchTypeChange}
options={SEARCH_OPTIONS}
/>
</Form.Item>
{searchType === 'Bill No' && (
<Form.Item
name={'BillNo'}
rules={[{ required: true, message: `Please enter Bill No` }]}
>
<InputField
label={<label className="required">{getLabel(searchType)}</label>}
type="text"
value={form.BillNo}
onChange={e => handleInputChange('BillNo', e.target.value)}
/>
</Form.Item>
)}
{searchType === 'Date' && (
<Form.Item
name="Date"
rules={[{ required: true, message: 'Please select date' }]}
>
<DatePicProd
cancelFuture={true}
onChange={(dateString) => handleInputChange('Date', dateString)}
canSelectPast={true}
fieldState={form.Date}
valueData={form?.Date}
/>
</Form.Item>
)}
<div
className="bookingReschedule__searchBtn"
onClick={handleSearch}
>
<BiSearchAlt size={20} />
<p style={{ whiteSpace: "nowrap" }}>Search</p>
</div>
</div>
</Form>
</div>
{/* Bill Selection */}
{billData?.length > 0 && (
<div className="selected-items-info">
{searchType === 'Date' && (<div>
<p className="BOFO-Offer-Saveformheader">Select your Bill:</p>
<DropDowns
label={<label className="required">All Bill No</label>}
defaultValue={billData?.length === 1 ? billData?.[0]?.OrderId : ""}
options={billData?.map(({ OrderId, NetAmount }) => ({
label: `Bill No: ${OrderId.match(/\d+/g).pop()} | Amount: ₹${NetAmount}`,
value: OrderId
}))}
className="field-DropDown"
valueData={billData?.length === 1 ? billData?.[0]?.OrderId : selectedRowKey}
isOnchanges={!!selectedRowKey}
onChangeFunction={handleBillSelect}
/>
</div>)}
{/* Product Table */}
{selectedRowKey && (
<div className="bookingReschedule__productTable">
<p className="BOFO-Offer-Saveformheader">Select Date to Reschedule</p>
<div className="table-scroll-containers">
<table className="custom-product-table">
<thead>
<tr>
<th>Select Date</th>
<th>Facility</th>
<th>Name</th>
<th>Current Slot</th>
<th>Current Booking Slot</th>
<th>Current Date</th>
<th>Slot Qty</th>
<th>Rate</th>
</tr>
</thead>
<tbody>
{selectedProducts.map((item) => (
<tr key={item.InwardDtlId}>
<td>
<DatePicker
value={dateSelections[item.InwardDtlId]
? dayjs(dateSelections[item.InwardDtlId].RescheduleDate)
: null
}
onChange={(date) => handleDateChange(item, date)}
placeholder="Select date"
format="DD/MM/YYYY"
disabledDate={(current) => current && current < dayjs().startOf('day')}
/>
</td>
<td> {item.ProdCatName} </td>
<td> {item.ProdSubCatName} {item.ProdName} </td>
<td>{item.ProdVariantName}</td>
<td>{formatTime(item.AvailableFrom)} {formatTime(item.AvailableTo)}</td>
<td>{dayjs(item.BookingDate).format('DD/MM/YYYY')}</td>
<td style={{textAlign:'center'}}>{item.SalesQty}</td>
<td>{item.Rate}</td>
</tr>
))}
</tbody>
</table>
</div>
</div>
)}
{/* Slot Selection - Card Style */}
{hasDateSelections && (
<div className="bookingReschedule__slotSection">
{Object.keys(dateSelections).map((InwardDtlId) => {
const product = selectedProducts.find(p => p.InwardDtlId === InwardDtlId);
const slotsForProduct = productSlots[InwardDtlId] || [];
if (!product) return null;
return (
<div key={InwardDtlId} className="bookingReschedule__slotCard">
<div className="bookingReschedule__slotCard__header">
<h4 className="bookingReschedule__slotCard__title">{product.ProdName}</h4>
<p className="bookingReschedule__slotCard__date">
{dayjs(dateSelections[InwardDtlId].RescheduleDate).format('DD MMM YYYY')}
</p>
</div>
{selectedSlots[InwardDtlId] && (
<div className="bookingReschedule__selectedInfo">
<strong> Selected Slot:</strong>
<span style={{ marginLeft: "10px" }}>
{formatTime(selectedSlots[InwardDtlId].SlotFrom)} -
{formatTime(selectedSlots[InwardDtlId].SlotTo)} |
{selectedSlots[InwardDtlId].Rate}
</span>
</div>
)}
<div className="bookingReschedule__slotsGrid">
{slotsForProduct.length > 0 ? (
slotsForProduct?.map((slot) => {
const isSelected = selectedSlots[InwardDtlId]?.slotId === slot.id;
const isBooked = slot.status === "booked";
const isPartial = slot.status === "partial";
const isBlocked = slot.status === "blocked";
const singlePcis = slot.productSlotDetails?.SinglePc;
const SalesQty = slot.productSlotDetails?.SalesQty;
const singleRate = slot.productSlotDetails?.Rate;
const isInsufficientQty = SalesQty > slot.available;
let buttonClass = "bookingReschedule__slotBtn";
if (isBooked) buttonClass += " bookingReschedule__slotBtn--booked";
else if (isBlocked) buttonClass += " bookingReschedule__slotBtn--blocked";
else if (isSelected) buttonClass += " bookingReschedule__slotBtn--selected";
else if (isPartial && singlePcis === 'N') buttonClass += " bookingReschedule__slotBtn--partial";
else if (isPartial && singlePcis === 'Y') buttonClass += " bookingReschedule__slotBtn--partialY";
if (singlePcis === 'Y') {
return (
<button
key={slot.id}
onClick={() => handleSlotChange(InwardDtlId, slot)}
disabled={isBooked || isBlocked || isInsufficientQty}
className={buttonClass}
>
<div className="bookingReschedule__slotBtn__time">
{formatTime(slot.from)} - {formatTime(slot.to)}
</div>
<div className="bookingReschedule__slotBtn__price">
{isBooked ? "Sold Out" : isBlocked ? "Blocked" : `${singleRate}`}
</div>
{(isPartial || !isPartial) && (
<div className="bookingReschedule__slotBtn__status">
{slot.available} Available
</div>
)}
</button>
);
}
else {
return (
<button
key={slot.id}
onClick={() => handleSlotChange(InwardDtlId, slot)}
disabled={isBooked || isBlocked || isPartial}
className={buttonClass}
>
<div className="bookingReschedule__slotBtn__time">
{formatTime(slot.from)} - {formatTime(slot.to)}
</div>
<div className="bookingReschedule__slotBtn__price">
{isBooked ? "Sold Out" : isBlocked ? "Blocked" : `${slot.amount}`}
</div>
{isPartial && !isBooked && !isBlocked && (
<div className="bookingReschedule__slotBtn__status">
{slot.available} Available
</div>
)}
</button>
);
}
})
) : (
<p style={{
gridColumn: "1 / -1",
textAlign: "center",
color: "#999",
padding: "20px"
}}>
No slots available for this date
</p>
)}
</div>
</div>
);
})}
{/* Legend */}
<div className="bookingReschedule__legend">
<div className="bookingReschedule__legend__item">
<div className="bookingReschedule__legend__box bookingReschedule__legend__box--available"></div>
<span>Available</span>
</div>
<div className="bookingReschedule__legend__item">
<div className="bookingReschedule__legend__box bookingReschedule__legend__box--booked"></div>
<span>Booked</span>
</div>
<div className="bookingReschedule__legend__item">
<div className="bookingReschedule__legend__box bookingReschedule__legend__box--blocked"></div>
<span>Blocked</span>
</div>
<div className="bookingReschedule__legend__item">
<div className="bookingReschedule__legend__box bookingReschedule__legend__box--partial"></div>
<span>Partial Available</span>
</div>
<div className="bookingReschedule__legend__item">
<div className="bookingReschedule__legend__box bookingReschedule__legend__box--selected"></div>
<span>Selected</span>
</div>
</div>
</div>
)}
{/* <div className="bookingReschedule__divider"> </div> */}
</div>
)}
{/* Submit Button */}
{hasDateSelections && (
<div className="bookingReschedule__submitBtn">
<Buttons
buttonText="SUBMIT RESCHEDULE"
handleSubmit={onFinishPost}
icon={<ArrowRightOutlined />}
/>
</div>
)}
</div>
</div>
</div>
);
};
export default BookingReScedule;