diff --git a/src/pages/nearle/orders/miltiUploadBackup.js b/src/pages/nearle/orders/miltiUploadBackup.js new file mode 100644 index 0000000..6a8c6b8 --- /dev/null +++ b/src/pages/nearle/orders/miltiUploadBackup.js @@ -0,0 +1,1657 @@ +import React from 'react'; +import Loader from 'components/Loader'; +import { useEffect, useState, Fragment, useRef } from 'react'; +import { useTheme } from '@mui/material/styles'; +import MainCard from 'components/MainCard'; +import axios from 'axios'; +import ClearIcon from '@mui/icons-material/Clear'; +import { SearchOutlined, CloseOutlined, ExclamationCircleOutlined, FileAddOutlined } from '@ant-design/icons'; +import { Empty } from 'antd'; +import MyLocationIcon from '@mui/icons-material/MyLocation'; +import { DatePicker } from '@mui/x-date-pickers/DatePicker'; +import { LocalizationProvider } from '@mui/x-date-pickers/LocalizationProvider'; +import { AdapterDayjs } from '@mui/x-date-pickers/AdapterDayjs'; +import dayjs from 'dayjs'; +var utc = require('dayjs/plugin/utc'); +dayjs.extend(utc); +import { enqueueSnackbar } from 'notistack'; +import { useNavigate } from 'react-router'; +import Papa from 'papaparse'; +import * as XLSX from 'xlsx'; + +import { + FormControl, + InputAdornment, + Grid, + Typography, + Stack, + Box, + Button, + TextField, + Autocomplete, + Chip, + Divider, + Dialog, + DialogTitle, + DialogContent, + Checkbox, + DialogActions, + CircularProgress, + IconButton, + OutlinedInput, + FormGroup, + FormControlLabel, + Table, + TableContainer, + TableCell, + TableBody, + TableRow, + Paper, + TableHead, + FormLabel, + RadioGroup, + Radio, + Backdrop, + List, + ListItem, + ListItemText +} from '@mui/material'; +import CircularLoader from 'components/nearle_components/CircularLoader'; + +const MultipleOrders = () => { + const navigate = useNavigate(); + const theme = useTheme(); + const locationRef = useRef(null); + const tenantRef = useRef(null); + const userid = localStorage.getItem('userid'); + const [locations, setLocations] = useState([]); + const [tenantlist, setTenantlist] = useState([]); + const [loading, setLoading] = useState(false); + const [btnLoading, setBtnLoading] = useState(false); + const [appId, setAppId] = useState(0); + const [tenantLocations, setTenantlocations] = useState([]); + // const [tenantid, setTenantid] = useState(0); + const tenantid = localStorage.getItem('tenantid') || 0; + const [locationid, setLocationid] = useState(0); + const [basePrice, setBasePrice] = useState(0); + const [pricePerKm, setPricePerKm] = useState(0); + const [minKm, setMinKm] = useState(0); + const [pickCust, setPickCust] = useState(null); + const [dropCust, setDropCust] = useState([]); + const [isCustomerOpen, setIsCustomerOpen] = useState(false); + const [searchCustList, setSearchCustList] = useState(''); + const [customerlist, setCustomerlist] = useState([]); + const [startdate, setStartdate] = useState(dayjs().format('MM-DD-YYYY')); + const [timeslotarr, setTimeslotarr] = useState([]); + const [starttime, setStatrttime] = useState(); + const [endtime, setEndtime] = useState(); + const [selectedtime, setSelectedtime] = useState(''); + const [alertmessage, setAlertmessage] = useState(''); + const [otherinstructions, setOtherinstructions] = useState(''); + const [admintoken, setAdmintoken] = useState(); + const [totaldist, settotaldist] = useState(0); + const [totalAmt, settotalAmt] = useState(0); + const [totalQty, settotalQty] = useState(0); + const [totalCash, settotalCash] = useState(0); + const [users, setUsers] = useState([]); + const [uploadType, setUploadType] = useState(null); + const [tenantValue, setTenantValue] = useState(null); + const [locationValue, setLocationValue] = useState(null); + const [pickupSlotsList, setPickupSlotsList] = useState(null); + const [pickupSlot, setPickupSlot] = useState(null); + + useEffect(() => { + if (timeslotarr[0]) { + let arr = []; + timeslotarr.map((val) => { + if (dayjs().diff(dayjs(`${dayjs(startdate).format('MM-DD-YYYY')} ${dayjs(val).format('HH:mm:ss')}`), 'm') <= 0) { + arr.push(val); + } + }); + } + }, [timeslotarr]); + + // =============================================== || opentoast || =============================================== + const opentoast = (message, variant, time) => { + enqueueSnackbar(message, { + variant: variant, + anchorOrigin: { vertical: 'top', horizontal: 'right' }, + autoHideDuration: time ? time : 1500 + }); + console.log(alertmessage); + }; + + // 🔹 Smart toast wrapper — prevents duplicate toasts for same message within 3 seconds + let toastCache = {}; + const OpenToast = (message, type = 'info', timeout = 10000) => { + const key = `${type}-${message}`; + if (toastCache[key]) return; // skip duplicates + opentoast(message, type, timeout); // your existing toast/snackbar + toastCache[key] = true; + setTimeout(() => delete toastCache[key], 3000); // reset after delay + }; + + // ==============================|| fetchAppLocations ||============================== // + + const fetchAppLocations = async () => { + setLoading(true); + + try { + const locationRes = await axios.get(`${process.env.REACT_APP_URL}/partners/getlocations/?userid=${userid}`); + console.log('fetchAppLocations', locationRes.data.details); + setLocations(locationRes.data.details); + } catch (err) { + console.log('locationRes', err); + OpenToast(err.message, 'error', 5000); + } finally { + setLoading(false); + } + }; + useEffect(() => { + fetchAppLocations(); + }, []); + + // ===================================================== || fetchtenantinfolist || ===================================================== + + const fetchtenantinfolist = async () => { + setLoading(true); + await axios + .get(`${process.env.REACT_APP_URL}/tenants/gettenants/?applocationid=${appId}&status=active`) + + .then((res) => { + console.log(res); + if (res.data.status) { + let arr = []; + res.data.details.map((val) => { + arr.push({ + ...val, + label: `${val.tenantname}` + }); + }); + setTenantlist(arr); + } + setLoading(false); + }) + .catch((err) => { + console.log(err); + setLoading(false); + }); + }; + useEffect(() => { + appId && fetchtenantinfolist(); + }, [appId]); + // ============================================= || fetchTenantPricing || ============================================= + + const fetchTenantPricing = async (id) => { + try { + const pricingResponse = await axios.get(`${process.env.REACT_APP_URL}/tenants/gettenantpricing/?tenantid=${id}`); + console.log('pricingResponse', pricingResponse.data.details); + setBasePrice(pricingResponse.data.details.baseprice); + setPricePerKm(pricingResponse.data.details.priceperkm); + setMinKm(pricingResponse.data.details.minkm); + } catch (error) { + console.log('fetchTenantPricing error', error); + } + }; + // ============================================= || gettenantlocations (branches) || ============================================= + const gettenantlocations = async (id) => { + try { + const res = await axios.get(`${process.env.REACT_APP_URL}/tenants/gettenantlocations/?tenantid=${id}`); + console.log('gettenantlocations', res.data.details); + if (res.data.details.length == 1) { + setTenantlocations(res.data.details); + setPickCust(res.data.details[0]); + setLocationid(res.data.details[0].locationid); + setLocationValue(res.data.details[0].locationid); + setPickupSlotsList(res.data.details[0].slots); + } else { + setTenantlocations(res.data.details); + } + } catch (err) { + console.log('gettenantlocations', err); + } + }; + useEffect(() => { + gettenantlocations(tenantid); + }, [tenantid]); + // ========================================================= || clientdetails || ========================================================= + const clientdetails = async () => { + try { + let url = + searchCustList == '' + ? `${process.env.REACT_APP_URL}/customers/gettenantcustomers/?tenantid=${tenantid}&pageno=1&pagesize=30` + : `${process.env.REACT_APP_URL}/customers/search/?tenantid=${tenantid}&keyword=${searchCustList}`; + await axios + .get(url) + .then((res) => { + if (res.data.status) { + console.log('clientdetails', res.data.details); + + setCustomerlist(res.data.details); + let arr = []; + res.data.details.map((val) => { + arr.push({ + label: `${val.firstname} | ${val.contactno}`, + ...val + }); + }); + } + }) + .catch((err) => { + console.log(err); + opentoast('server error', 'warning'); + }); + } catch (err) { + console.log(err); + } + }; + useEffect(() => { + if (tenantid) { + clientdetails(); + } + }, [searchCustList.length > 3, searchCustList == '', tenantid]); + + // ========================================================= || calculateTotal(dist , charge) || ========================================================= + const calculateTotal = () => { + let a1 = 0; + let a2 = 0; + let a3 = 0; + let a4 = 0; + dropCust.map((customer) => { + a1 += customer.distance; + a2 += customer.totalcharge; + a3 += customer.quantity; + a4 += customer.collectionamt; + }); + settotaldist(a1); + settotalAmt(a2); + settotalQty(a3); + settotalCash(a4); + }; + useEffect(() => { + calculateTotal(); + }, [dropCust]); + + // ========================================================= || handleCheckboxChange || ========================================================= + const handleCheckboxChange = async (event, customer) => { + setLoading(true); + if (event.target.checked) { + // If the checkbox is checked, calculate the distance and add the customer + try { + const obj = await calculateDistance(customer); + const { roundedDistance, totalcharge } = obj; + // Create a new customer object with the distance property + const updatedCustomer = { + ...customer, + distance: roundedDistance, + totalcharge: totalcharge + }; + + // Add the updated customer object to dropCust + setDropCust((prevDropCust) => [...prevDropCust, updatedCustomer]); + + // Log the rounded distance + // console.log(`Rounded Distance: ${roundedDistance} km`); + } catch (error) { + console.error('Failed to calculate distance:', error); + } finally { + setLoading(false); + } + } else { + // If the checkbox is unchecked, remove the customer from dropCust + setDropCust((prevDropCust) => { + return prevDropCust.filter((cust) => cust.customerid !== customer.customerid); + }); + setLoading(false); + } + }; + // ========================================================= || handleCheckboxChange1 || ========================================================= + // const handleCheckboxChange1 = async (customer) => { + // console.log('customer', customer); + // setLoading(true); + // try { + // const obj = await calculateDistance(customer); + // const { roundedDistance, totalcharge } = obj; + // // Create a new customer object with the distance property + // const updatedCustomer = { + // ...customer, + // distance: roundedDistance, + // totalcharge: totalcharge + // }; + + // // Add the updated customer object to dropCust + // setDropCust((prevDropCust) => [...prevDropCust, updatedCustomer]); + + // // Log the rounded distance + // console.log(`Rounded Distance: ${roundedDistance} km`); + // setLoading(false); + // } catch (error) { + // console.error('Failed to calculate distance:', error); + // } + // }; + const handleCheckboxChange1 = async (customer) => { + console.log('customer', customer); + + setLoading(true); + + try { + setDropCust((prevDropCust) => { + const isAlreadySelected = prevDropCust.some((c) => c.firstname === customer.firstname); + + // 🔴 REMOVE if already exists + if (isAlreadySelected) { + return prevDropCust.filter((c) => c.firstname !== customer.firstname); + } + + // 🟢 ADD if not exists (calculate distance) + return prevDropCust; + }); + + // Only calculate distance if customer is not already added + const alreadyExists = dropCust.some((c) => c.firstname === customer.firstname); + + if (!alreadyExists) { + const obj = await calculateDistance(customer); + const { roundedDistance, totalcharge } = obj; + + const updatedCustomer = { + ...customer, + distance: roundedDistance, + totalcharge + }; + + setDropCust((prevDropCust) => [...prevDropCust, updatedCustomer]); + + console.log(`Rounded Distance: ${roundedDistance} km`); + } + } catch (error) { + console.error('Failed to calculate distance:', error); + } finally { + setLoading(false); + } + }; + + // ========================================================= || calculateDistance || ========================================================= + + // 🔹 Main distance calculation function + const calculateDistance = async (customer) => { + const service = new google.maps.DistanceMatrixService(); + + // Helper: safely get distance matrix + const getDistanceMatrix = (origins, destinations) => { + return new Promise((resolve, reject) => { + // 2; + try { + if (!origins || !destinations) { + return reject(new Error('Origin or destination data missing.')); + } + service.getDistanceMatrix( + { + origins: [new google.maps.LatLng(origins.latitude, origins.longitude)], + destinations: [new google.maps.LatLng(destinations.latitude, destinations.longitude)], + travelMode: 'DRIVING', + unitSystem: google.maps.UnitSystem.METRIC + }, + (response, status) => { + if (status === 'OK') { + resolve(response); + } else { + reject(new Error(`Google API error: ${status}`)); + } + } + ); + } catch (err) { + reject(new Error(`Unexpected error inside DistanceMatrixService: ${err.message}`)); + } + }); + }; + + try { + // --- Input validation --- + if (!customer || typeof customer !== 'object') { + throw new Error('Invalid customer data: expected an object.'); + } + + if (!pickCust || typeof pickCust !== 'object') { + throw new Error('Origin (pickCust) data missing or invalid.'); + } + + // --- Call Google Maps API --- + const response = await getDistanceMatrix(pickCust, customer); + + // --- Validate response structure --- + if (!response.rows?.[0]?.elements?.[0] || !response.rows[0].elements[0].distance?.value) { + throw new Error('Malformed Distance Matrix response: missing distance value.'); + } + // --- Compute distance --- + const distanceInMeters = response.rows[0].elements[0].distance.value; + const distanceInKilometers = distanceInMeters / 1000; + const roundedDistance = Math.round(distanceInKilometers); + + // --- Calculate total charge --- + let totalcharge; + if (roundedDistance < minKm) { + totalcharge = basePrice; + } else { + totalcharge = (roundedDistance - minKm) * pricePerKm + basePrice; + } + return { roundedDistance, totalcharge }; + } catch (error) { + // --- Categorized smart error handling --- + console.log('on calculateDistance', error.message); + if (error.message.includes('Google API')) { + console.log('🚨 Google Maps API Error:', error.message); + OpenToast('Invalid file format, upload valid file', 'error', 5000); + } else if (error.message.includes('Invalid coordinates')) { + console.log('📍 Invalid coordinate format:', error.message, 3000); + OpenToast('Invalid coordinate format. Check location data.', 'warning'), 3000; + } else if (error.message.includes('Malformed Distance Matrix')) { + console.log('⚠️ Unexpected Google response structure:', error.message); + OpenToast('Google Distance Matrix returned invalid data.', 'error', 3000); + } else if (error.message.includes('Origin') || error.message.includes('customer')) { + console.log('❌ Missing or invalid input data:', error.message); + OpenToast('Missing or invalid input data for distance calculation.', 'warning', 3000); + } else { + console.log('💥 Unexpected error calculating distance:', error); + OpenToast('Unexpected error during distance calculation.', 'error', 3000); + } + + throw error; // keeps your current flow intact + } + }; + + // ==================================================== || fetchTiming || ==================================================== + const fetchTiming = async () => { + setLoading(true); + await axios + .get(`${process.env.REACT_APP_URL}/utils/getapplocations/?applocationid=${appId}`) + .then((res) => { + console.log('fetchTiming', res); + const { opentime, closetime } = res.data.details[0]; + if (res.data.status) { + setStatrttime(`${dayjs().format('MM-DD-YYYY')} ${opentime}`); + setEndtime(`${dayjs().format('MM-DD-YYYY')} ${closetime}`); + console.log('starttime', `${dayjs().format('MM-DD-YYYY')} ${opentime}`); + console.log('endtime', `${dayjs().format('MM-DD-YYYY')} ${closetime} `); + let arr = []; + for ( + let i = `${dayjs().format('MM-DD-YYYY')} ${opentime}`, j = 0; + dayjs(`${dayjs().format('MM-DD-YYYY')} ${closetime} `).diff(i, 'm') >= 0; + j++, i = dayjs(i).add(30, 'm') + ) { + arr.push(i); + } + console.log('setTimeslotarr', arr); + setTimeslotarr(arr); + } + setLoading(false); + }) + .catch((err) => { + console.log(err); + setLoading(false); + }); + }; + useEffect(() => { + if (appId) { + fetchTiming(); + } + }, [appId]); + + const fetchAppAdminTokens = async () => { + setLoading(true); + await axios + .get(`${process.env.REACT_APP_URL}/utils/getapplocationconfig/?applocationid=${appId}`) + .then((res) => { + const userfcmtokemArray = res.data.details.applocationadmins.map((admin) => admin.userfcmtokem); // fcm => firebase cloud messaging + console.log('fetchAppAdminTokens', res); + console.log('userfcmtokemArray', userfcmtokemArray); + if (res.data.status) { + setAdmintoken(userfcmtokemArray); + } + setLoading(false); + }) + .catch((err) => { + console.log(err); + setLoading(false); + }); + }; + + useEffect(() => { + if (appId) { + fetchAppAdminTokens(); + } + }, [appId]); + + useEffect(() => { + console.log('pickCust', pickCust); + }, [pickCust]); + useEffect(() => { + console.log('dropCust', dropCust); + }, [dropCust]); + // // ==================================================== || fetchtenantinfo || ==================================================== + // const fetchtenantinfo = async () => { + // setLoading(true); + // console.log('tenantid', tenantid); + + // await axios + // .get(`${process.env.REACT_APP_URL}/tenants/gettenantinfo/?tenantid=${tenantid}`) + // .then((res) => { + // console.log('fetchtenantinfo', res); + // if (res.data.status) { + // setTenantid(res.data.details.tenantid); + // } + // setLoading(false); + // }) + // .catch((err) => { + // console.log(err); + // setLoading(false); + // }); + // }; + // useEffect(() => { + // if (tenantid) { + // fetchtenantinfo(); + // } + // }, [tenantid]); + // ================================================== || sendnotifications || ================================================== + const sendnotifications = async () => { + setLoading(true); + await axios + .post(`${process.env.REACT_APP_URL}/utils/sendnotifications`, { + priority: 'high', + registration_ids: admintoken, + data: { + accessid: process.env.REACT_APP_RIDER_ACCESS_ID + }, + notification: { + title: 'Nearle Merchant', + body: 'An Order has been placed successfully,kindly process the same', + sound: 'ring' + } + }) + .then((res) => { + console.log(res); + if (res.data.message == 'Success') { + enqueueSnackbar('Notification sent Successfully', { + variant: 'success', + anchorOrigin: { vertical: 'top', horizontal: 'right' }, + autoHideDuration: 1000 + }); + } + setLoading(false); + }) + .catch((err) => { + console.log(err); + enqueueSnackbar(err.message, { + variant: 'error', + anchorOrigin: { vertical: 'top', horizontal: 'right' }, + autoHideDuration: 1000 + }); + setLoading(false); + }); + }; + + const cleanReceiverName = (name) => { + if (typeof name !== 'string') return name; + return name.replace(/^[\d.\s]+/, '').trim(); + }; + + const handleFileUpload = (event) => { + console.log('Normal upload started...'); + try { + const file = event.target.files?.[0]; + if (!file) { + opentoast('No file selected.', 'warning'); + return; + } + + const fileName = file.name.toLowerCase(); + const isCSV = fileName.endsWith('.csv'); + const isExcel = fileName.endsWith('.xls') || fileName.endsWith('.xlsx'); + + // Invalid file + if (!isCSV && !isExcel) { + opentoast('Invalid file type. Please upload a CSV or Excel file.', 'warning'); + return; + } + + // ---------------------- CSV ---------------------- + if (isCSV) { + Papa.parse(file, { + header: true, + dynamicTyping: true, + skipEmptyLines: true, + complete: (results) => { + const data = results.data || []; + if (data.length === 0) { + opentoast('CSV file is empty or invalid.', 'warning'); + setUsers([]); + return; + } + const cleanedData = data.map((row) => ({ + ...row, + firstname: cleanReceiverName(row.firstname) + })); + console.log('✅ Parsed CSV Data:', cleanedData); + setUsers(cleanedData); + opentoast('CSV file uploaded successfully, ✅', 'success', 3000); + opentoast(' Press Continue to add delivery customers', 'warning', 5000); + }, + error: (error) => { + console.error('❌ CSV Parse Error:', error.message); + opentoast(`CSV parsing failed: ${error.message}`, 'warning'); + } + }); + } + + // ---------------------- EXCEL (.xls / .xlsx) ---------------------- + if (isExcel) { + const reader = new FileReader(); + + reader.onload = (e) => { + try { + const data = e.target.result; + + // Use correct mode for binary Excel formats + const workbook = XLSX.read(data, { + type: 'binary', + cellDates: true, + cellNF: false, + cellText: false + }); + const firstSheet = workbook.SheetNames[0]; + const worksheet = workbook.Sheets[firstSheet]; + const jsonData = XLSX.utils.sheet_to_json(worksheet, { defval: '' }); + + if (!jsonData || jsonData.length === 0) { + opentoast('Excel file is empty or invalid.', 'warning'); + setUsers([]); + return; + } + + const cleanedData = jsonData.map((row) => ({ + ...row, + firstname: cleanReceiverName(row.firstname) + })); + + console.log('✅ Parsed Excel Data:', cleanedData); + setUsers(cleanedData); + opentoast('Excel file uploaded successfully ✅, press continue', 'success', 3000); + } catch (err) { + console.error('❌ Excel Parse Error:', err); + opentoast(`Error reading Excel: ${err.message}`, 'warning'); + } + }; + + // ✅ Key fix: use readAsBinaryString for both .xls & .xlsx + reader.readAsBinaryString(file); + } + } catch (err) { + console.error('Unexpected error during file upload:', err.message); + opentoast(`Unexpected error: ${err.message}`, 'warning'); + } + }; + + // your header mapping + const headerMap = { + 'pickupdate(yyyy-mmm-dd)': 'date', + 'sendername*': 'locationname', + 'senderphone*': 'locationcontact', + 'senderaddress*': 'locationaddress', + 'receivername*': 'firstname', + 'receiverphone*': 'contactno', + 'receiveralternatephone*': 'altcontactno', + receiverfulladdress: 'address', + receiverlatitude: 'latitude', + receiverlongitude: 'longitude', + 'itemdescription*': 'description', + Quantity: 'quantity', + ' Collect Cash': 'collectionamt' + }; + + // helper to normalize headers + const normalizeHeader = (header) => header?.toString().trim().toLowerCase().replace(/\s+/g, ''); + + const handleFileDirectUpload = (event) => { + try { + const file = event.target.files?.[0]; + if (!file) { + opentoast('No file selected.', 'warning'); + return; + } + + const fileName = file.name.toLowerCase(); + const isCSV = fileName.endsWith('.csv'); + const isExcel = fileName.endsWith('.xls') || fileName.endsWith('.xlsx'); + + if (!isCSV && !isExcel) { + opentoast('Invalid file type. Please upload a CSV or Excel file.', 'warning'); + return; + } + + const processData = (data, headers) => { + console.log('data', data); + const normalizedMap = {}; + for (const key in headerMap) { + normalizedMap[normalizeHeader(key)] = headerMap[key]; + } + console.log('normalizedMap', normalizedMap); + + const mappedData = data.map((row) => { + const newRow = {}; + for (const key in row) { + const cleanKey = normalizeHeader(key); + const newKey = normalizedMap[cleanKey] || cleanKey; + let value = row[key]; + if (newKey === 'firstname') value = cleanReceiverName(value); + + newRow[newKey] = value; + } + + return newRow; + }); + + const missingCols = Object.keys(headerMap).filter((clientCol) => !headers.includes(normalizeHeader(clientCol))); + + if (missingCols.length > 0) { + isExcel && opentoast(`Missing columns: ${missingCols.join(', ')}`, 'warning'); + } + + console.log('✅ Final Processed Data:', mappedData); + setUsers(mappedData); + opentoast('File uploaded and successfully ', 'success', 3000); + opentoast('Press Continue', 'warning', 3000); + }; + + // ============ CSV handler ============ + if (isCSV) { + Papa.parse(file, { + header: true, + dynamicTyping: true, + skipEmptyLines: true, + complete: (results) => { + if (!results.data?.length) { + opentoast('CSV file is empty or has no valid rows.', 'warning'); + setUsers([]); + return; + } + const headers = results.meta.fields.map(normalizeHeader); + processData(results.data, headers); + }, + error: (error) => { + console.error('❌ CSV Parsing Error:', error); + opentoast(`CSV parsing failed: ${error.message}`, 'warning'); + } + }); + } + + // ============ Excel handler ============ + if (isExcel) { + const reader = new FileReader(); + reader.onload = (e) => { + try { + const data = e.target.result; + // Try reading as binary first + let workbook; + try { + workbook = XLSX.read(data, { type: 'binary' }); + } catch { + // fallback for modern XLSX files + const arrayBuffer = new Uint8Array(data); + workbook = XLSX.read(arrayBuffer, { type: 'array' }); + } + + const firstSheet = workbook.SheetNames[0]; + const worksheet = workbook.Sheets[firstSheet]; + const jsonData = XLSX.utils.sheet_to_json(worksheet, { defval: '' }); + + if (!jsonData?.length) { + opentoast('Excel file is empty or invalid.', 'warning'); + setUsers([]); + return; + } + + const headers = Object.keys(jsonData[0]).map(normalizeHeader); + processData(jsonData, headers); + } catch (err) { + console.error('❌ Error processing Excel:', err); + opentoast(`Error reading Excel: ${err.message}`, 'warning'); + } + }; + + // Important: use readAsBinaryString for Excel + reader.readAsBinaryString(file); + } + } catch (err) { + console.error('Unexpected error during file upload:', err); + opentoast(`Unexpected error: ${err.message}`, 'warning'); + } + }; + + // =============================================== || createorders || =============================================== + const createorders = async () => { + // ===================== Build Payload ===================== + const arr = dropCust.map((customer) => ({ + applocationid: pickCust.applocationid, + configid: 9, + partnerid: pickCust.partnerid, + partneruserid: +userid, + paymentstatus: 1, + paymenttype: 42, + pickupaddress: pickCust.address || '', + pickupcity: pickCust.city || '', + pickupcontactno: pickCust.contactno || '', + pickupcustomer: pickCust.locationname || '', + pickuplandmark: pickCust.landmark || '', + pickuplat: pickCust.latitude, + pickuplocation: pickCust.suburb || '', + pickuplocationid: pickCust.locationid || 0, + pickuplong: pickCust.longitude, + tenantid: pickCust.tenantid, + + customerid: +customer?.customerid, + deliveryaddress: customer.address || '', + deliverycharge: +customer.totalcharge || 0, + deliverycity: customer.city || '', + deliverycontactno: customer.contactno?.toString() || '', + deliverycustomer: customer.firstname || '', + deliveryid: +customer.customerid, + deliverylandmark: customer.landmark || '', + deliverylat: customer.latitude?.toString() || '', + deliverylocation: customer.suburb || '', + deliverylocationid: customer.deliverylocationid || 0, + deliverylong: customer.longitude?.toString() || '', + deliverytime: `${dayjs(startdate).format('YYYY-MM-DD')} ${dayjs(selectedtime.$d).format('HH:mm:ss')}`, + deliverytype: 'B', + + itemcount: 1, + quantity: customer.quantity, + collectionamt: customer.collectionamt, + kms: customer.distance?.toString() || '0', + locationid: +pickCust.locationid, + moduleid: +pickCust.moduleid, + + orderamount: +customer.totalcharge || 0, + ordercharges: 0.0, + orderdate: dayjs().format('YYYY-MM-DD HH:mm:ss'), + ordernotes: otherinstructions, + orderstatus: 'created', + ordervalue: +customer.totalcharge || 0, + pickupSlot + })); + + console.log('arr', arr); + + // ===================== Validation ===================== + if (!tenantid) { + opentoast('Choose Client', 'warning'); + return; + } + setLoading(true); + try { + const res = await axios.post(`${process.env.REACT_APP_URL}/orders/createorders`, arr); + if (res.data.status) { + opentoast('Order Created Successfully', 'success', 2000); + if (admintoken) { + sendnotifications(); + } + navigate('/nearle/orders'); + setLoading(false); + } else { + console.log(res.data); + console.error('Create order failed (API response):', res.data); + opentoast(res?.data?.message || 'Order creation failed. Please try again.', 'warning', 3000); + } + } catch (err) { + opentoast(err.message, 'error', 2000); + console.log('create orders', err.message); + console.error('Create order error:', { + message: err.message, + response: err.response, + request: err.request, + stack: err.stack + }); + + // Exact but short error for user + let toastMessage = 'Something went wrong. Please try again.'; + + if (err.response) { + // Server responded with error + toastMessage = err.response.data?.message || `Server error (${err.response.status})`; + } else if (err.request) { + // No response received + toastMessage = 'Network error. Check your internet connection.'; + } + opentoast(toastMessage, 'error'); + setLoading(false); + } finally { + setLoading(false); + setBtnLoading(false); + } + }; + + const [fileName, setFileName] = useState(''); + const removeFileExtension = (fileName) => { + return fileName.replace(/\.[^/.]+$/, ''); + }; + + const onFileChange = (event) => { + const file = event.target.files[0]; + if (!file) return; + const cleanedName = removeFileExtension(file.name); + setFileName((prev) => (prev ? `${prev}, ${cleanedName}` : cleanedName)); + if (tenantid === 916) { + handleFileDirectUpload(event); + } else { + handleFileDirectUpload(event); + // handleFileUpload(event); + } + }; + + const handleQuantityChange = (customerid, value) => { + setDropCust((prev) => prev.map((cust) => (cust.customerid === customerid ? { ...cust, quantity: Number(value) || 0 } : cust))); + }; + const handleCollectionAmtChange = (customerid, value) => { + setDropCust((prev) => prev.map((cust) => (cust.customerid === customerid ? { ...cust, collectionamt: Number(value) || 0 } : cust))); + }; + + return ( + <> + {loading && ( + <> + + {/* */} + + )} + { + theme.zIndex.drawer + 1 + }} + open={btnLoading} // when loader = true, backdrop covers the page + > + + + } + + + {/* ===================================================== ||Business Location || ===================================================== */} + + + Create Multiple Order + + + {tenantLocations?.length === 1 ? ( + + + + ) + }} + /> + ) : ( + `${option.locationname} (${option.suburb})`} + value={locationValue} + onOpen={(event) => { + if (!appId && !tenantid) { + event.preventDefault(); + + OpenToast('Please select Location and Tenant first!', 'warning', 3000); + + setTimeout(() => { + locationRef.current?.focus(); + }, 0); + } else if (!tenantid) { + event.preventDefault(); + + OpenToast('Please select Tenant first!', 'warning', 3000); + + setTimeout(() => { + tenantRef.current?.focus(); + }, 0); + } + }} + onChange={(event, value, reason) => { + if (reason === 'clear') { + setLocationid(0); + setLocationValue(null); + setPickCust(null); + } else { + setLocationid(value?.locationid || 0); + setLocationValue(value); + setPickCust(value); + setPickupSlotsList(value?.slots); + } + }} + renderInput={(params) => } + /> + )} + + + + + {/* ===================================================== || Pickup || ===================================================== */} + + + {locationid !== 0 && ( + + + + + + )} + {/* ================================================= || Time || ================================================= */} + + + + + + { + setStartdate(e); + let dateres11 = dayjs().diff(dayjs(`${dayjs(e).format('YYYY-MM-DD')}`), 'd'); + console.log('dateres11'); + console.log(dateres11); + setSelectedtime(''); + if (dateres11 <= 0) { + console.log('startdate', e); + setStartdate(e); + + let arr = []; + timeslotarr.map((val) => { + if (dayjs().diff(dayjs(`${dayjs(e).format('MM-DD-YYYY')} ${dayjs(val).format('HH:mm:ss')}`), 'm') <= 0) { + arr.push(val); + } + }); + } else { + setAlertmessage('choose Upcoming Date'); + opentoast('choose Upcoming Date', 'warning'); + setStartdate(NaN); + } + }} + value={dayjs(startdate)} + sx={{ width: 'auto', mt: 0 }} + disablePast + /> + + + {/* {timeslotarr.length > 0 && ( + + + + + + + Time + + + + + + { + setStartdate(e); + let dateres11 = dayjs().diff(dayjs(`${dayjs(e).format('YYYY-MM-DD')}`), 'd'); + console.log('dateres11'); + console.log(dateres11); + setSelectedtime(''); + if (dateres11 <= 0) { + console.log('startdate', e); + setStartdate(e); + + let arr = []; + timeslotarr.map((val) => { + if ( + dayjs().diff(dayjs(`${dayjs(e).format('MM-DD-YYYY')} ${dayjs(val).format('HH:mm:ss')}`), 'm') <= 0 + ) { + arr.push(val); + } + }); + } else { + setAlertmessage('choose Upcoming Date'); + opentoast('choose Upcoming Date', 'warning'); + setStartdate(NaN); + } + }} + value={dayjs(startdate)} + sx={{ width: 'auto', mt: 0 }} + disablePast + /> + + + + + + + {timeslotarr.map((val, index) => { + if ( + dayjs().diff(dayjs(`${dayjs(startdate).format('MM-DD-YYYY')} ${dayjs(val).format('HH:mm:ss')}`), 'm') <= 0 + ) { + return ( + + + { + console.log('selectedtime', val); + setSelectedtime(val); + }} + // onClick={() => { + // if (distance > appLocaRadius) { + // setOpen(true); + // } else if (showDistance) { + // console.log('selectedtime', val); + // setSelectedtime(val); + // } else { + // opentoast('Out of city limit', 'error'); + // } + // }} + /> + + + ); + } + })} + + + + + + )} */} + + + { + if (reason === 'clear') { + setSelectedtime(null); + setPickupSlot(null); + } else { + // Convert to AM/PM and merge with date + const formattedTime = dayjs(newValue.time, 'HH:mm').format('hh:mm A'); + setSelectedtime(formattedTime); + const finalDateTime = dayjs(`${startdate} ${formattedTime}`, 'MM-DD-YYYY hh:mm A').format('YYYY-MM-DD hh:mm A'); + setPickupSlot(finalDateTime); + } + }} + getOptionLabel={(option) => `${option.name} (${dayjs(option.time, 'HH:mm').format('hh:mm A')})`} + renderInput={(params) => } + /> + + + + + + {/* ===================================================== || Drop || ===================================================== */} + + + + + Drop ({dropCust?.length || 0}) + + + {/* ================= Upload CSV ================= */} + {uploadType === 0 && ( + <> + {fileName && ( + + + + {fileName} + + + )} + + + + + + )} + + {/* ================= Continue ================= */} + {users.length >= 1 && uploadType === 0 && ( + + )} + + {/* ================= Select Customers ================= */} + {uploadType === 1 && ( + + )} + + {/* ================= Upload Type ================= */} + + + Upload Type + { + if (!locationid) { + OpenToast('Please select Business Location!', 'warning', 3000); + return; + } + setUploadType(Number(e.target.value)); + setDropCust([]); + setUsers([]); + setFileName(''); + }} + > + } label="Excel / CSV" /> + } label="Selection" /> + + + + + + } + > + + + {dropCust?.length > 0 ? ( + <> + + + S.No + Customer + Address + Quantity + + + Cash Collect + + Kms + + Charge + Action + + + + + {dropCust?.map((customer, index) => ( + + {index + 1} + {customer.firstname} + {customer.address} + + {uploadType == 0 ? ( + {customer.quantity} + ) : ( + handleQuantityChange(customer.customerid, e.target.value)} + inputProps={{ min: 0 }} + /> + )} + + + {uploadType == 0 ? ( + ₹{Number(customer.collectionamt || 0).toFixed(2)} + ) : ( + { + if (e.target.value <= 0) { + handleCollectionAmtChange(customer.customerid, 0); + } else { + handleCollectionAmtChange(customer.customerid, e.target.value); + } + }} + inputProps={{ min: 0 }} + InputProps={{ + startAdornment: + }} + /> + )} + + + {customer.distance} + {`₹${customer?.totalcharge?.toFixed(2)}`} + + { + <> + handleCheckboxChange(event, customer)} + onClick={() => handleCheckboxChange1(customer)} + /> + + } + + + ))} + {dropCust?.length != 0 && ( + + Total + + + + {`${totalQty} `} + + + + {`${totalCash?.toFixed(2)} `} + + + {`${totaldist} `} + + + {`₹${totalAmt?.toFixed(2)}`} + + + + + )} + + + ) : ( + + {/* Header */} + + {' '} + + Important Instructions + + + + {/* Ordered List */} + + + Choose either Upload Type to upload CSV/Excel files, or + Selection Type to select from saved customers. + + + + Uploaded CSV or Excel files must follow the required format and contain the correct column names. + + + + Multiple files can be uploaded, but only one file at a time. + + + + Invalid or incorrectly formatted files will not be processed. + + + + )} +
+
+
+
+ + + {/* ================================================= || Notes || ================================================= */} + + + + setOtherinstructions(e.target.value)} + /> + + + + + + + +
+ {/* ============================================= || saved address Dialog || ============================================= */} + { + setIsCustomerOpen(false); + }} + fullWidth + sx={{ minWidth: 'lg' }} + > + + + {`Select Drop Customers (${dropCust.length || 0})`} + + + setSearchCustList(e.target.value)} + sx={{ + '& .MuiOutlinedInput-input': { + p: '10.5px 0px 12px' + }, + bgcolor: 'white' + }} + startAdornment={ + + + + } + endAdornment={ + { + setSearchCustList(''); + }} + > + + + } + autoComplete="off" + /> + + + + + + + {customerlist?.length == 0 ? ( + + + + ) : ( + + {customerlist && + customerlist?.map((customer, index) => ( + + cust.customerid === customer.customerid)} // Set the checked state of the checkbox based on whether the customer is in `dropCust` + onChange={(event) => handleCheckboxChange(event, customer)} + /> + } + label={ +
+ + {`${customer.firstname} (${customer.contactno})`} + + + + {customer.address} + +
+ } + /> +
+ ))} +
+ )} +
+ + + + +
+ + ); +}; + +export default MultipleOrders; diff --git a/src/pages/nearle/orders/multipleOrders.js b/src/pages/nearle/orders/multipleOrders.js index 6a8c6b8..668fd53 100644 --- a/src/pages/nearle/orders/multipleOrders.js +++ b/src/pages/nearle/orders/multipleOrders.js @@ -71,7 +71,7 @@ const MultipleOrders = () => { const [appId, setAppId] = useState(0); const [tenantLocations, setTenantlocations] = useState([]); // const [tenantid, setTenantid] = useState(0); - const tenantid = localStorage.getItem('tenantid') || 0; + const tenantid = localStorage.getItem('tenantid'); const [locationid, setLocationid] = useState(0); const [basePrice, setBasePrice] = useState(0); const [pricePerKm, setPricePerKm] = useState(0); @@ -193,6 +193,9 @@ const MultipleOrders = () => { console.log('fetchTenantPricing error', error); } }; + useEffect(() => { + fetchTenantPricing(tenantid); + }, [tenantid]); // ============================================= || gettenantlocations (branches) || ============================================= const gettenantlocations = async (id) => { try { @@ -989,83 +992,74 @@ const MultipleOrders = () => { spacing: 5 }} > - {/* ===================================================== ||Business Location || ===================================================== */} - - Create Multiple Order - - - {tenantLocations?.length === 1 ? ( - - - - ) - }} - /> - ) : ( - `${option.locationname} (${option.suburb})`} - value={locationValue} - onOpen={(event) => { - if (!appId && !tenantid) { - event.preventDefault(); - - OpenToast('Please select Location and Tenant first!', 'warning', 3000); - - setTimeout(() => { - locationRef.current?.focus(); - }, 0); - } else if (!tenantid) { - event.preventDefault(); - - OpenToast('Please select Tenant first!', 'warning', 3000); - - setTimeout(() => { - tenantRef.current?.focus(); - }, 0); - } - }} - onChange={(event, value, reason) => { - if (reason === 'clear') { - setLocationid(0); - setLocationValue(null); - setPickCust(null); - } else { - setLocationid(value?.locationid || 0); - setLocationValue(value); - setPickCust(value); - setPickupSlotsList(value?.slots); - } - }} - renderInput={(params) => } - /> - )} - + + Create Multiple Order + + + + {/* ===================================================== || Choose App location || ===================================================== */} + {/* ===================================================== ||Business Location || ===================================================== */} + + {tenantLocations?.length == 1 ? ( + + + + ) + }} + /> + ) : ( + `${option.locationname} (${option.suburb})` || ''} + value={locationValue} + onOpen={(event) => { + if (!appId && !tenantid) { + event.preventDefault(); + OpenToast('Please select Location and Tenant first!', 'warning', 3000); + setTimeout(() => { + locationRef.current?.focus(); + }, 0); + } else if (!tenantid) { + event.preventDefault(); + OpenToast('Please select Tenant first!', 'warning', 3000); + setTimeout(() => { + tenantRef.current?.focus(); + }, 0); + } + }} + onChange={(event, value, reason) => { + if (reason === 'clear') { + setLocationid(0); + setLocationValue(null); + setPickCust(null); + } else { + setLocationid(value.locationid || 0); + setLocationValue(value); + setPickCust(value); + setPickupSlotsList(value?.slots); + } + }} + renderInput={(params) => } + /> + )} + + + + {/* ===================================================== || Pickup || ===================================================== */} @@ -1337,7 +1331,7 @@ const MultipleOrders = () => { value={uploadType} onChange={(e) => { if (!locationid) { - OpenToast('Please select Business Location!', 'warning', 3000); + OpenToast('Please select Business Location !', 'warning', 3000); return; } setUploadType(Number(e.target.value)); @@ -1521,8 +1515,7 @@ const MultipleOrders = () => {