Files
worldshine-redesign/client/src/components/trans-routes/RouteCustomerEditor.js
Lixian Zhou e7f47e664c
All checks were successful
Build And Deploy Main / build-and-deploy (push) Successful in 33s
fix
2026-03-10 16:21:27 -04:00

1031 lines
38 KiB
JavaScript

import React, {useEffect, useRef, useState, useCallback} from 'react';
import { useDrag, useDrop, DndProvider } from 'react-dnd';
import { HTML5Backend } from 'react-dnd-html5-backend';
import update from 'immutability-helper';
import { Modal, Button } from "react-bootstrap";
import { CustomerService } from '../../services';
import { PERSONAL_ROUTE_STATUS } from '../../shared';
import ReactPaginate from 'react-paginate';
import { GripVertical, Pencil, RecordCircleFill, XSquare } from 'react-bootstrap-icons';
const ItemTypes = {
CARD: 'card',
UNASSIGNED_CUSTOMER: 'unassigned_customer',
};
const Card = ({ content, index, moveCard }) => {
const ref = useRef(null);
const [{ handlerId, isOver, draggedIndex }, drop] = useDrop({
accept: ItemTypes.CARD,
collect(monitor) {
return {
handlerId: monitor.getHandlerId(),
isOver: monitor.isOver({ shallow: true }),
draggedIndex: monitor.getItem()?.index,
}
},
drop(item, monitor) {
if (!ref.current) {
return
}
const dragIndex = item.index
const hoverIndex = index
// Don't replace items with themselves
if (dragIndex === hoverIndex) {
return
}
// Determine rectangle on screen
const hoverBoundingRect = ref.current?.getBoundingClientRect()
// Get vertical middle
const hoverMiddleY =
(hoverBoundingRect.bottom - hoverBoundingRect.top) / 2
// Determine mouse position
const clientOffset = monitor.getClientOffset()
// Get pixels to the top
const hoverClientY = clientOffset.y - hoverBoundingRect.top
// Only perform the move when the mouse has crossed half of the items height
// When dragging downwards, only move when the cursor is below 50%
// When dragging upwards, only move when the cursor is above 50%
// Dragging downwards
if (dragIndex < hoverIndex && hoverClientY < hoverMiddleY) {
return
}
// Dragging upwards
if (dragIndex > hoverIndex && hoverClientY > hoverMiddleY) {
return
}
// Time to actually perform the action
moveCard(dragIndex, hoverIndex)
// Note: we're mutating the monitor item here!
// Generally it's better to avoid mutations,
// but it's good here for the sake of performance
// to avoid expensive index searches.
item.index = hoverIndex
},
})
const [{ isDragging }, drag] = useDrag({
type: ItemTypes.CARD,
item: () => {
return { index }
},
collect: (monitor) => ({
isDragging: monitor.isDragging(),
}),
})
const opacity = isDragging ? 0 : 1
const showDropTopIndicator = isOver && draggedIndex !== undefined && draggedIndex > index;
const showDropBottomIndicator = isOver && draggedIndex !== undefined && draggedIndex < index;
const dropZoneStyle = {
opacity,
paddingTop: '10px',
paddingBottom: '10px',
borderRadius: '8px',
backgroundColor: isOver ? '#eef6ff' : 'transparent',
borderTop: showDropTopIndicator ? '4px solid #0d6efd' : '4px solid transparent',
borderBottom: showDropBottomIndicator ? '4px solid #0d6efd' : '4px solid transparent',
transition: 'background-color 0.12s ease, border-color 0.12s ease'
};
drag(drop(ref))
return (
<div ref={ref} style={dropZoneStyle} data-handler-id={handlerId}>
{content}
</div>
)
}
const RouteCustomerEditor = ({currentRoute, setNewCustomerList = (a) => {}, viewMode, editFun, onAddCustomer = null, scheduledAbsentCustomerIds = []}) => {
const [customers, setCustomers] = useState([]);
const [initializedRouteId, setInitializedRouteId] = useState(null);
const [showAddPersonnelModal, setShowAddPersonnelModal] = useState(false);
const [showAddAptGroupModal, setShowAddAptGroupModal] = useState(false);
const [showEditAptGroupModal, setShowEditAptGroupModal] = useState(false);
const [editGroupIndex, setEditGroupIndex] = useState(-1);
const [customerOptions, setCustomerOptions] = useState([]);
const [customerFilter, setCustomerFilter] = useState('');
const [lastNameFilter, setLastNameFilter] = useState(undefined);
const [newRouteCustomerList, setNewRouteCustomerList] = useState([]);
const [newRouteGroupedCustomerList, setNewRouteGroupedCustomerList] = useState([]);
const [newGroupName, setNewGroupName] = useState('');
const [newGroupAddress, setNewGroupAddress] = useState('');
// We start with an empty list of items.
const [currentItems, setCurrentItems] = useState(null);
// Here we use item offsets; we could also use page offsets
// following the API or data you're working with.
const [itemOffset, setItemOffset] = useState(0);
const [pageCount, setPageCount] = useState(0);
const itemsPerPage = 10;
const hasActiveFilters = Boolean((customerFilter || '').trim()) || Boolean(lastNameFilter);
const scheduledAbsentIdsSet = new Set([
...(Array.isArray(scheduledAbsentCustomerIds) ? scheduledAbsentCustomerIds : []),
...((currentRoute?.route_customer_list || [])
.filter((item) => item?.customer_route_status === PERSONAL_ROUTE_STATUS.SCHEDULED_ABSENT)
.map((item) => item?.customer_id)
.filter(Boolean))
]);
const getAbsentNameStyle = (customerId) => (
scheduledAbsentIdsSet.has(customerId) ? { color: '#dc3545', fontWeight: 700 } : {}
);
// Helper function to get all customer IDs already in the route
const getAssignedCustomerIds = () => {
const assignedIds = new Set();
customers.forEach(item => {
if (item.customer_id) {
assignedIds.add(item.customer_id);
}
if (item.customers) {
item.customers.forEach(c => {
if (c.customer_id) {
assignedIds.add(c.customer_id);
}
});
}
});
return assignedIds;
};
const formatStructuredAddress = (line1, line2, city, state, zipCode, note) => {
const cityState = [city, state].filter(Boolean).join(', ');
const mainAddress = [line1, line2, cityState, zipCode]
.filter(item => item && String(item).trim() !== '')
.join(' ')
.trim();
const addressNote = (note || '').trim();
if (!mainAddress) return '';
return addressNote ? `${mainAddress} (${addressNote})` : mainAddress;
};
const getCustomerAddressOptions = (customer) => {
if (!customer) return [];
const addresses = [
formatStructuredAddress(customer.address_line_1, customer.address_line_2, customer.city, customer.state, customer.zip_code, customer.address_note),
formatStructuredAddress(customer.address2_line_1, customer.address2_line_2, customer.city2, customer.state2, customer.zip_code2, customer.address2_note),
formatStructuredAddress(customer.address3_line_1, customer.address3_line_2, customer.city3, customer.state3, customer.zip_code3, customer.address3_note),
formatStructuredAddress(customer.address4_line_1, customer.address4_line_2, customer.city4, customer.state4, customer.zip_code4, customer.address4_note),
];
return addresses.filter(address => (address || '').trim() !== '');
};
const getCustomerSearchAddresses = (customer) => {
if (!customer) return [];
const addresses = [
formatStructuredAddress(customer.address_line_1, customer.address_line_2, customer.city, customer.state, customer.zip_code),
formatStructuredAddress(customer.address2_line_1, customer.address2_line_2, customer.city2, customer.state2, customer.zip_code2),
formatStructuredAddress(customer.address3_line_1, customer.address3_line_2, customer.city3, customer.state3, customer.zip_code3),
formatStructuredAddress(customer.address4_line_1, customer.address4_line_2, customer.city4, customer.state4, customer.zip_code4),
];
return addresses.filter(address => (address || '').trim() !== '');
};
const getCustomerSearchText = (customer) => {
const allAddressText = getCustomerSearchAddresses(customer).join(' ');
return [
customer?.name,
customer?.id,
customer?.apartment,
allAddressText,
// Keep legacy fields searchable for older records.
customer?.address1,
customer?.address2,
customer?.address3,
customer?.address4,
customer?.address5,
]
.map(item => (item || '').toString().toLowerCase())
.join(' ');
};
const matchesCustomerSearch = (customer, keyword) => {
const normalizedKeyword = (keyword || '').toLowerCase().trim();
if (!normalizedKeyword) return true;
return getCustomerSearchText(customer).includes(normalizedKeyword);
};
useEffect(() => {
// Fetch items from another resources.
const endOffset = itemOffset + itemsPerPage;
const assignedIds = getAssignedCustomerIds();
// Filter out customers already in the route
const availableCustomers = customerOptions?.filter(customer => !assignedIds.has(customer.id));
const filteredCustomers = availableCustomers
?.filter(customer => (lastNameFilter && (customer.lastname?.toLowerCase().indexOf(lastNameFilter) === 0)) || !lastNameFilter)
?.filter((customer) => matchesCustomerSearch(customer, customerFilter)) || [];
if (hasActiveFilters) {
setCurrentItems(filteredCustomers.slice(itemOffset, endOffset));
setPageCount(Math.ceil(filteredCustomers.length / itemsPerPage));
} else {
setCurrentItems(filteredCustomers);
setPageCount(0);
if (itemOffset !== 0) {
setItemOffset(0);
}
}
}, [customerOptions, itemOffset, customerFilter, lastNameFilter, customers]);
const handlePageClick = (event) => {
const assignedIds = getAssignedCustomerIds();
const availableCustomers = customerOptions?.filter(customer => !assignedIds.has(customer.id));
const matchedCount = availableCustomers?.filter((customer) => matchesCustomerSearch(customer, customerFilter)).length || 0;
const newOffset = matchedCount > 0 ? (event.selected * itemsPerPage) % matchedCount : 0;
console.log(
`User requested page number ${event.selected}, which is offset ${newOffset}`
);
setItemOffset(newOffset);
};
const closeAddPersonnelModal = () => {
setShowAddPersonnelModal(false);
setNewRouteCustomerList([]);
}
const openAddPersonnelModal = () => {
setItemOffset(0);
setPageCount(0);
setLastNameFilter(undefined);
if (customerOptions.length === 0) {
CustomerService.getAllActiveCustomers().then((data) => {
// Filter out discharged customers
const filtered = (data.data || []).filter(customer => {
const isDischarged = customer.type === 'discharged' ||
(customer.name && customer.name.toLowerCase().includes('discharged')) ||
customer.status !== 'active';
return !isDischarged;
});
setCustomerOptions(filtered);
})
}
setShowAddPersonnelModal(true);
}
const closeAddAptGroupModal = () => {
setShowAddAptGroupModal(false);
}
const openAddAptGroupModal = () => {
setItemOffset(0);
setPageCount(0);
setLastNameFilter(undefined);
if (customerOptions.length === 0) {
CustomerService.getAllActiveCustomers().then((data) => {
// Filter out discharged customers
const filtered = (data.data || []).filter(customer => {
const isDischarged = customer.type === 'discharged' ||
(customer.name && customer.name.toLowerCase().includes('discharged')) ||
customer.status !== 'active';
return !isDischarged;
});
setCustomerOptions(filtered);
})
}
setShowAddAptGroupModal(true);
}
const closeEditAptGroupModal = () => {
setShowEditAptGroupModal(false);
setNewGroupAddress('');
setNewGroupName('');
setNewRouteGroupedCustomerList([]);
setEditGroupIndex(-1);
}
const openEditAptGroupModal = (index, group) => {
setItemOffset(0);
setPageCount(0);
setLastNameFilter(undefined);
if (customerOptions.length === 0) {
CustomerService.getAllActiveCustomers().then((data) => {
// Filter out discharged customers
const filtered = (data.data || []).filter(customer => {
const isDischarged = customer.type === 'discharged' ||
(customer.name && customer.name.toLowerCase().includes('discharged')) ||
customer.status !== 'active';
return !isDischarged;
});
setCustomerOptions(filtered);
})
}
setNewGroupAddress(group.customers[0].customer_group_address);
setNewGroupName(group.customer_group);
setNewRouteGroupedCustomerList(group.customers);
setEditGroupIndex(index);
setShowEditAptGroupModal(true);
}
const toggleItemToRouteList = (customer, value) => {
if (value === 'false') {
const customerAddresses = getCustomerAddressOptions(customer);
setNewRouteCustomerList([].concat(newRouteCustomerList).concat([{
customer_id: customer.id,
customer_name: `${customer.name} ${customer.name_cn?.length > 0 ? `(${customer.name_cn})` : ``}`,
customer_address: customerAddresses[0] || '',
customer_avatar: customer.avatar,
customer_type: customer.type,
customer_pickup_status: customer.pickup_status,
customer_note: customer.note,
customer_special_needs: customer.notes_for_driver || '',
customer_phone: customer.phone || customer.mobile_phone || customer.home_phone,
customer_route_status: PERSONAL_ROUTE_STATUS.NO_STATUS,
customer_pickup_order: customers.length + newRouteCustomerList.length + 1,
customer_table_id: customer.table_id,
customer_language: customer.language
}]));
} else {
setNewRouteCustomerList([].concat(newRouteCustomerList.filter((item) => item.customer_id !== customer.id)));
}
}
const toggleGroupedItemToRouteList = (customer, value) => {
if (value === 'false') {
const customerAddresses = getCustomerAddressOptions(customer);
setNewRouteGroupedCustomerList([].concat(newRouteGroupedCustomerList).concat([{
customer_id: customer.id,
customer_name: `${customer.name} ${customer.name_cn?.length > 0 ? `(${customer.name_cn})` : ``}`,
customer_address: customerAddresses[0] || '',
customer_avatar: customer.avatar,
customer_group: newGroupName,
customer_group_address: newGroupAddress,
customer_type: customer.type,
customer_pickup_status: customer.pickup_status,
customer_note: customer.note,
customer_special_needs: customer.notes_for_driver || '',
customer_phone: customer.phone || customer.mobile_phone || customer.home_phone,
customer_route_status: PERSONAL_ROUTE_STATUS.NO_STATUS,
customer_pickup_order: customers.length + 1,
customer_table_id: customer.table_id,
customer_language: customer.language
}]));
} else {
setNewRouteGroupedCustomerList([].concat(newRouteGroupedCustomerList.filter((item) => item.customer_id !== customer.id)));
}
}
const setCustomerAddress = (id, value) => {
setNewRouteCustomerList(newRouteCustomerList.map((item) => {
if (item.customer_id === id) {
return {
...item,
customer_address: value
}
} else {
return item;
}
}))
}
const setGroupedCustomerAddress = (id, value) => {
setNewRouteGroupedCustomerList(newRouteGroupedCustomerList.map((item) => {
if (item.customer_id === id) {
return {
...item,
customer_address: value
}
} else {
return item;
}
}))
}
const removeGroupedCustomerFromSelection = (id) => {
setNewRouteGroupedCustomerList((prevList) => prevList.filter((item) => item.customer_id !== id));
}
const setNewGroupNameAction = (value) => {
setNewGroupName(value);
for (const item of newRouteGroupedCustomerList) {
item.customer_group = value;
}
}
const setNewGroupAddressAction = (value) => {
setNewGroupAddress(value);
for (const item of newRouteGroupedCustomerList) {
item.customer_group_address = value;
}
}
const checkGroupRequiredField = () => {
if ((!newGroupName || newGroupName.replace(' ', '') === '') || (!newGroupAddress || newGroupAddress.replace(' ', '') === '')) {
window.alert('Group Name and Group Address is Required')
return false;
}
return true;
}
const addPersonnel = () => {
const merged = [...customers];
newRouteCustomerList.forEach((newCustomer) => {
const existingIndex = merged.findIndex((item) => item?.customer_id === newCustomer?.customer_id);
if (existingIndex >= 0) {
// If the customer already exists, overwrite with latest selection (including address).
merged[existingIndex] = {
...merged[existingIndex],
...newCustomer,
};
} else {
merged.push(newCustomer);
}
});
setCustomers(merged);
setShowAddPersonnelModal(false);
setNewRouteCustomerList([]);
}
// Function to add a customer from external drop (like unassigned customers)
const addCustomerFromDrop = useCallback((customerData, dropIndex = null) => {
if (!customerData || !customerData.id) return;
// Check if customer already exists
const customerExists = customers.some(item => {
if (item.customer_id) {
return item.customer_id === customerData.id;
}
if (item.customers) {
return item.customers.some(c => c.customer_id === customerData.id);
}
return false;
});
if (customerExists) return;
const newCustomer = {
customer_id: customerData.id,
customer_name: `${customerData.name} ${customerData.name_cn?.length > 0 ? `(${customerData.name_cn})` : ``}`,
customer_address: getCustomerAddressOptions(customerData)[0] || '',
customer_avatar: customerData.avatar,
customer_type: customerData.type,
customer_pickup_status: customerData.pickup_status,
customer_note: customerData.note,
customer_special_needs: customerData.notes_for_driver || '',
customer_phone: customerData.phone || customerData.mobile_phone || customerData.home_phone,
customer_route_status: PERSONAL_ROUTE_STATUS.NO_STATUS,
customer_pickup_order: customers.length + 1,
customer_table_id: customerData.table_id,
customer_language: customerData.language
};
setCustomers(prevCustomers => {
if (dropIndex !== null && dropIndex >= 0 && dropIndex <= prevCustomers.length) {
// Insert at specific position
const newList = [...prevCustomers];
newList.splice(dropIndex, 0, newCustomer);
return newList;
} else {
// Add to end
return [...prevCustomers, newCustomer];
}
});
}, [customers]);
const addAptGroup = () => {
if (checkGroupRequiredField()) {
const result = [].concat(customers).concat([{
customers: newRouteGroupedCustomerList,
customer_pickup_order: customers.length + 1,
customer_group: newGroupName,
}]);
setCustomers(result.filter((item, pos) => result.indexOf(item) === pos));
setShowAddAptGroupModal(false);
setNewRouteGroupedCustomerList([]);
setNewGroupAddress('');
setNewGroupName('');
setEditGroupIndex(-1);
}
}
const editAptGroup = () => {
if (checkGroupRequiredField()) {
const result = [].concat(customers);
result[editGroupIndex] = {
...result[editGroupIndex],
customers: newRouteGroupedCustomerList,
customer_group: newGroupName,
}
setCustomers(result.filter((item, pos) => result.indexOf(item) === pos));
setShowEditAptGroupModal(false);
setNewGroupAddress('');
setNewGroupName('');
setNewRouteGroupedCustomerList([]);
setEditGroupIndex(-1);
}
}
// Only initialize customers from currentRoute when the route ID changes
// Don't reset when currentRoute object reference changes (which happens on every render)
// Note: template sub-document routes use _id instead of id
const currentRouteId = currentRoute?.id || currentRoute?._id;
useEffect(() => {
if (currentRouteId && currentRouteId !== initializedRouteId) {
setCustomers(getRouteCustomersWithGroups());
setInitializedRouteId(currentRouteId);
}
}, [currentRouteId, initializedRouteId])
const getRouteCustomersWithGroups = () => {
const customerList = currentRoute?.route_customer_list?.map(item => Object.assign({}, item, {routeType: currentRoute.type, routeId: currentRoute.id || currentRoute._id}));
const result = {};
if (customerList) {
for (const customer of customerList) {
if (customer.customer_group) {
if (result[customer.customer_group]) {
result[customer.customer_group].push(customer);
} else {
result[customer.customer_group] = [];
result[customer.customer_group].push(customer);
}
} else {
if (result.no_group) {
result.no_group.push(customer);
} else {
result.no_group = [];
result.no_group.push(customer);
}
}
}
}
let finalResult = [];
for (const key of Object.keys(result)) {
if (key === 'no_group') {
finalResult = finalResult.concat(result[key]);
} else {
finalResult.push({
customer_pickup_order: result[key][0].customer_pickup_order,
customer_group: key,
customers: result[key]
})
}
}
return finalResult.sort((a, b) => a.customer_pickup_order - b.customer_pickup_order);
}
const deleteCustomer = (id) => {
if (!window.confirm('Are you sure you want to remove this customer from the route?')) {
return;
}
setCustomers(customers.filter((customer) => customer.customer_id !== id));
}
const deleteGroup = (index) => {
if (!window.confirm('Are you sure you want to remove this group from the route?')) {
return;
}
const arr = [].concat(customers);
arr.splice(index, 1);
setCustomers(arr);
}
const reorderItems = useCallback((dragIndex, hoverIndex) => {
setCustomers((prevCards) => {
return update(prevCards, {
$splice: [
[dragIndex, 1],
[hoverIndex, 0, prevCards[dragIndex]]
]
})
});
}, []);
const Items = ({ currentItems }) => {
return currentItems?.map(
(customer) => {
const addressOptions = getCustomerAddressOptions(customer);
const customerDisplayName = customer?.name || '';
const customerChineseName = customer?.name_cn || '';
return <div key={customer.id} className="option-item">
<input className="me-4 mt-2" type="checkbox" checked={newRouteCustomerList.find((item) => item.customer_id === customer.id)!==undefined} value={newRouteCustomerList.find((item) => item.customer_id === customer.id)!==undefined} onChange={(e) => toggleItemToRouteList(customer, e.target.value)}/>
<div>
<div>{`${customerDisplayName}${customerChineseName ? `(${customerChineseName})` : ''}`}</div>
{newRouteCustomerList.find((item) => item.customer_id === customer.id) && (<div>
{addressOptions.map((address, idx) => (
<div key={`${customer.id}-address-${idx}`}>
<input className="me-4" name={`${customer.id}-address`} type="radio" onChange={(e) => setCustomerAddress(customer.id, e.currentTarget.value)} value={address} checked={newRouteCustomerList.find((item) => item.customer_id === customer.id)?.customer_address===address}/>
<small>{address}</small>
</div>
))}
</div>)}
</div>
</div>
}
)
};
const ItemsGroup = ({ currentItems }) => {
const assignedIds = getAssignedCustomerIds();
return currentItems?.filter(customer => !assignedIds.has(customer.id)).filter((customer) => matchesCustomerSearch(customer, customerFilter)).map(
(customer) => {
const addressOptions = getCustomerAddressOptions(customer);
const customerDisplayName = customer?.name || '';
const customerChineseName = customer?.name_cn || '';
return <div key={customer.id} className="option-item">
<input className="me-4 mt-2" type="checkbox" checked={newRouteGroupedCustomerList.find((item) => item.customer_id === customer.id)!==undefined} value={newRouteGroupedCustomerList.find((item) => item.customer_id === customer.id)!==undefined} onChange={(e) => toggleGroupedItemToRouteList(customer, e.target.value)}/>
<div>
<div>{`${customerDisplayName}${customerChineseName ? `(${customerChineseName})` : ''}`}</div>
{newRouteGroupedCustomerList.find((item) => item.customer_id === customer.id) && (<div>
{addressOptions.map((address, idx) => (
<div key={`${customer.id}-group-address-${idx}`}>
<input className="me-4" name={`${customer.id}-address`} type="radio" onChange={(e) => setGroupedCustomerAddress(customer.id, e.currentTarget.value)} value={address} checked={newRouteGroupedCustomerList.find((item) => item.customer_id === customer.id)?.customer_address===address}/>
<small>{address}</small>
</div>
))}
</div>)}
</div>
</div>
}
)
}
const getCurrentAssignedNumber = () => {
let count = 0;
for (const item of customers) {
if (item.customers) {
for (const customer of item.customers) {
count++;
}
} else {
count++;
}
}
return count;
}
// Expose addCustomerFromDrop via callback
useEffect(() => {
if (onAddCustomer && typeof onAddCustomer === 'function') {
onAddCustomer(addCustomerFromDrop);
}
}, [addCustomerFromDrop, onAddCustomer]);
useEffect(() => {
const result = [];
for (const item of customers) {
if (item.customer_group) {
for (const customer of item.customers) {
customer.customer_pickup_order = customers.indexOf(item);
result.push(customer);
}
} else {
item.customer_pickup_order = customers.indexOf(item);
result.push(item);
}
}
setNewCustomerList(result);
}, [customers])
// Create a drop zone for unassigned customers
const CustomersDropZone = ({ children }) => {
const [{ isOver }, drop] = useDrop({
accept: 'UNASSIGNED_CUSTOMER',
drop: (item, monitor) => {
if (addCustomerFromDrop && item && item.id) {
addCustomerFromDrop(item, null); // Add to end
}
return { dropped: true };
},
collect: (monitor) => ({
isOver: monitor.isOver(),
}),
});
return (
<div
ref={drop}
className="customers-container mb-4"
style={{
backgroundColor: isOver ? '#f0f0f0' : 'transparent',
minHeight: isOver ? '100px' : 'auto',
border: isOver ? '2px dashed #0066B1' : '2px dashed transparent',
borderRadius: '4px',
padding: isOver ? '8px' : '0'
}}
>
{children}
</div>
);
};
return (
<DndProvider backend={HTML5Backend}>
{ !viewMode && <h6 class="text-primary">Customers Assigned ({getCurrentAssignedNumber()})</h6>}
{ viewMode && <h6 class="text-primary">Route Assignment <button className="btn btn-sm btn-primary" onClick={() => editFun('assignment')}><Pencil size={16} className="me-2"></Pencil>Edit </button></h6>}
{!viewMode && <CustomersDropZone>
{customers.map((item, index) => {
if (item?.customers) {
return <Card key={index} index={index} moveCard={reorderItems} content={(<div className="customers-dnd-item-container">
<div className="stop-index"><span>{`Stop ${index+1}`}</span><RecordCircleFill size={16} color={"#0066B1"} className="ms-2"></RecordCircleFill> </div>
<GripVertical className="me-4" size={20}></GripVertical>
<div className="customer-dnd-item" onClick={() => openEditAptGroupModal(index, item)}>
<span className="me-2">{item.customer_group} </span> <span>{item.customers[0]?.customer_group_address}</span>
<div className="customer-dnd-item-content">{item.customers.map(customer =>
<div key={customer.customer_id}>
<small className="me-2" style={getAbsentNameStyle(customer.customer_id)}>{customer.customer_name}</small>
<small className="me-2">{customer.customer_address}</small>
<small className="me-2">{customer.customer_pickup_status}</small>
</div>)}
</div>
</div>
<div className="customer-delete-btn"><button className="btn btn-default" onClick={()=> deleteGroup(index)}><XSquare size={14}></XSquare></button></div>
</div>)}></Card>
} else {
return <Card key={index} index={index} moveCard={reorderItems} content={<div className="customers-dnd-item-container">
<div className="stop-index"><span>{`Stop ${index+1}`}</span><RecordCircleFill size={16} color={"#0066B1"} className="ms-2"></RecordCircleFill> </div>
<GripVertical className="me-4" size={20}></GripVertical>
<div className="customer-dnd-item">
<span style={getAbsentNameStyle(item.customer_id)}>{item.customer_name} </span>
<small className="me-2">{item.customer_address}</small>
<small className="me-2">{item.customer_pickup_status}</small>
</div>
<div className="customer-delete-btn"><button onClick={() => deleteCustomer(item.customer_id)} className="btn btn-default"><XSquare size={14}></XSquare> </button></div>
</div>}>
</Card>
}
})}
<div className="new-customers-dnd-item-container">
<div className="stop-index"><span>{`Stop ${customers?.length+1}`}</span><RecordCircleFill size={16} color={"#ccc"} className="ms-2"></RecordCircleFill> </div>
<div>
<button className="btn btn-primary btn-sm me-2 mb-2" onClick={() => openAddPersonnelModal()}> + Add Personnel </button>
<button className="btn btn-primary btn-sm me-2 mb-2" onClick={() => openAddAptGroupModal()}> + Add Apt Group </button>
</div>
</div>
</CustomersDropZone>}
{
viewMode && <div className="customers-container mb-4">
{customers.map((item, index) => {
if (item?.customers) {
return <div className="customers-dnd-item-container">
<div className="stop-index"><span>{`Stop ${index+1}`}</span><RecordCircleFill size={16} color={"#0066B1"} className="ms-2"></RecordCircleFill> </div>
<div className="customer-dnd-item" onClick={() => openEditAptGroupModal(index, item)}>
<span className="me-2">{item.customer_group} </span> <span>{item.customers[0]?.customer_group_address}</span>
<div className="customer-dnd-item-content">{item.customers.map(customer =>
<div key={customer.customer_id}>
<small className="me-2" style={getAbsentNameStyle(customer.customer_id)}>{customer.customer_name}</small>
<small className="me-2">{customer.customer_address}</small>
<small className="me-2">{customer.customer_pickup_status}</small>
</div>)}
</div>
</div>
</div>
} else {
return <div className="customers-dnd-item-container">
<div className="stop-index"><span>{`Stop ${index+1}`}</span><RecordCircleFill size={16} color={"#0066B1"} className="ms-2"></RecordCircleFill> </div>
<div className="customer-dnd-item">
<span style={getAbsentNameStyle(item.customer_id)}>{item.customer_name} </span>
<small className="me-2">{item.customer_address}</small>
<small className="me-2">{item.customer_pickup_status}</small>
</div>
</div>
}
})}
</div>
}
<Modal show={showAddPersonnelModal} onHide={() => closeAddPersonnelModal()}>
<Modal.Header closeButton>
<Modal.Title>Add Personnel</Modal.Title>
</Modal.Header>
<Modal.Body>
<>
<div className="app-main-content-fields-section">
<div className="me-4">
<div className="field-label">Type in UserId OR Name OR Address to Search
</div>
<input type="text" className="mb-4" value={customerFilter} onChange={(e) => setCustomerFilter(e.target.value)}/>
</div>
</div>
<div>
<div className="app-main-content-fields-section">
{['A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z'].map(item => {
return <a key={item} className="me-2" onClick={() => {setLastNameFilter(item?.toLowerCase())} }>{item}</a>
})}
</div>
</div>
<a className="mb-4" onClick={() => setLastNameFilter(undefined)}>Clear All</a>
<div className="customers-container mt-4">
<div style={{ maxHeight: '420px', overflowY: 'auto' }}>
<Items currentItems={currentItems} />
</div>
{hasActiveFilters && pageCount > 1 && <ReactPaginate
className="customers-pagination"
breakLabel="..."
nextLabel=">"
onPageChange={handlePageClick}
pageRangeDisplayed={5}
pageCount={pageCount}
previousLabel="<"
renderOnZeroPageCount={null}
containerClassName="pagination justify-content-center"
pageClassName="page-item"
pageLinkClassName="page-link"
previousClassName="page-item"
previousLinkClassName="page-link"
nextClassName="page-item"
nextLinkClassName="page-link"
activeClassName="active"
breakClassName="page-item"
breakLinkClassName="page-link"
/>}
</div>
</>
</Modal.Body>
<Modal.Footer>
<Button variant="link" onClick={() => closeAddPersonnelModal()}>
Cancel
</Button>
<Button variant="primary" size="sm" onClick={() => addPersonnel()}>
Add Personnel
</Button>
</Modal.Footer>
</Modal>
<Modal show={showAddAptGroupModal} onHide={() => closeAddAptGroupModal()}>
<Modal.Header closeButton>
<Modal.Title>Add Apt Group</Modal.Title>
</Modal.Header>
<Modal.Body>
<>
<div className="app-main-content-fields-section">
<div className="me-4">
<div className="field-label">Group Name
<span className="required">*</span>
</div>
<input type="text" value={newGroupName} onChange={(e) => setNewGroupNameAction(e.target.value)}/>
</div>
<div className="me-4">
<div className="field-label">Group Address
<span className="required">*</span>
</div>
<input type="text" value={newGroupAddress} onChange={(e) => setNewGroupAddressAction(e.target.value)}/>
</div>
</div>
<div className="app-main-content-fields-section">
<div className="me-4">
<div className="field-label">Type in user Id or Name to Search
</div>
<input type="text" className="mb-4" value={customerFilter} onChange={(e) => setCustomerFilter(e.target.value)}/>
</div>
</div>
<div>
<div className="mb-4">
{['A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z'].map(item => {
return <a key={item} className="me-2" onClick={() => {setLastNameFilter(item?.toLowerCase())} }>{item}</a>
})}
</div>
</div>
<a className="mb-4" onClick={() => setLastNameFilter(undefined)}>Clear All</a>
<div className="customers-container mt-4">
<div style={{ maxHeight: '420px', overflowY: 'auto' }}>
<ItemsGroup currentItems={currentItems} />
</div>
{hasActiveFilters && pageCount > 1 && <ReactPaginate
className="customers-pagination"
breakLabel="..."
nextLabel=">"
onPageChange={handlePageClick}
pageRangeDisplayed={5}
pageCount={pageCount}
previousLabel="<"
renderOnZeroPageCount={null}
containerClassName="pagination justify-content-center"
pageClassName="page-item"
pageLinkClassName="page-link"
previousClassName="page-item"
previousLinkClassName="page-link"
nextClassName="page-item"
nextLinkClassName="page-link"
activeClassName="active"
breakClassName="page-item"
breakLinkClassName="page-link"
/>}
</div>
</>
</Modal.Body>
<Modal.Footer>
<Button variant="link" size="sm" onClick={() => closeAddAptGroupModal()}>
Cancel
</Button>
<Button variant="primary" size="sm" onClick={() => addAptGroup()}>
Add Apt Group
</Button>
</Modal.Footer>
</Modal>
<Modal show={showEditAptGroupModal} onHide={() => closeEditAptGroupModal()}>
<Modal.Header closeButton>
<Modal.Title>Update Apt Group</Modal.Title>
</Modal.Header>
<Modal.Body>
<>
<div className="app-main-content-fields-section">
<div className="me-4">
<div className="field-label">Group Name
<span className="required">*</span>
</div>
<input type="text" value={newGroupName} onChange={(e) => setNewGroupNameAction(e.target.value)}/>
</div>
<div className="me-4">
<div className="field-label">Group Address
<span className="required">*</span>
</div>
<input type="text" value={newGroupAddress} onChange={(e) => setNewGroupAddressAction(e.target.value)}/>
</div>
</div>
<div className="app-main-content-fields-section">
<div className="me-4" style={{ width: '100%' }}>
<div className="field-label">Selected Customers ({newRouteGroupedCustomerList.length})</div>
{newRouteGroupedCustomerList.length === 0 ? (
<small className="text-muted">No customer selected yet.</small>
) : (
<div className="customers-container" style={{ maxHeight: '180px', overflowY: 'auto', padding: '8px' }}>
{newRouteGroupedCustomerList.map((customer) => (
<div
key={`selected-group-customer-${customer.customer_id}`}
className="d-flex align-items-center justify-content-between mb-2"
style={{ gap: '12px' }}
>
<div>
<div><small>{customer.customer_name}</small></div>
<div><small className="text-muted">{customer.customer_address}</small></div>
</div>
<Button
variant="outline-danger"
size="sm"
onClick={() => removeGroupedCustomerFromSelection(customer.customer_id)}
>
Delete
</Button>
</div>
))}
</div>
)}
</div>
</div>
<div className="app-main-content-fields-section">
<div className="me-4">
<div className="field-label">Type in user Id or Name to Search
</div>
<input type="text" className="mb-4" value={customerFilter} onChange={(e) => setCustomerFilter(e.target.value)}/>
</div>
</div>
<div>
<div className="mb-4">
{['A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z'].map(item => {
return <a key={item} className="me-2" onClick={() => {setLastNameFilter(item?.toLowerCase())} }>{item}</a>
})}
</div>
</div>
<a className="mb-4" onClick={() => setLastNameFilter(undefined)}>Clear All</a>
<div className="customers-container mt-4">
<div style={{ maxHeight: '420px', overflowY: 'auto' }}>
<ItemsGroup currentItems={currentItems} />
</div>
{hasActiveFilters && pageCount > 1 && <ReactPaginate
className="customers-pagination"
breakLabel="..."
nextLabel=">"
onPageChange={handlePageClick}
pageRangeDisplayed={5}
pageCount={pageCount}
previousLabel="<"
renderOnZeroPageCount={null}
containerClassName="pagination justify-content-center"
pageClassName="page-item"
pageLinkClassName="page-link"
previousClassName="page-item"
previousLinkClassName="page-link"
nextClassName="page-item"
nextLinkClassName="page-link"
activeClassName="active"
breakClassName="page-item"
breakLinkClassName="page-link"
/>}
</div>
</>
</Modal.Body>
<Modal.Footer>
<Button variant="link" size="sm" onClick={() => closeEditAptGroupModal()}>
Cancel
</Button>
<Button variant="primary" size="sm" onClick={() => editAptGroup()}>
Update Apt Group
</Button>
</Modal.Footer>
</Modal>
</DndProvider>
);
};
export default RouteCustomerEditor;