fix
This commit is contained in:
@@ -1,16 +1,16 @@
|
||||
{
|
||||
"files": {
|
||||
"main.css": "/static/css/main.8bf7011f.css",
|
||||
"main.js": "/static/js/main.5ccb77ca.js",
|
||||
"main.css": "/static/css/main.01f852fa.css",
|
||||
"main.js": "/static/js/main.d902953d.js",
|
||||
"static/js/787.c4e7f8f9.chunk.js": "/static/js/787.c4e7f8f9.chunk.js",
|
||||
"static/media/landing.png": "/static/media/landing.d4c6072db7a67dff6a78.png",
|
||||
"index.html": "/index.html",
|
||||
"main.8bf7011f.css.map": "/static/css/main.8bf7011f.css.map",
|
||||
"main.5ccb77ca.js.map": "/static/js/main.5ccb77ca.js.map",
|
||||
"main.01f852fa.css.map": "/static/css/main.01f852fa.css.map",
|
||||
"main.d902953d.js.map": "/static/js/main.d902953d.js.map",
|
||||
"787.c4e7f8f9.chunk.js.map": "/static/js/787.c4e7f8f9.chunk.js.map"
|
||||
},
|
||||
"entrypoints": [
|
||||
"static/css/main.8bf7011f.css",
|
||||
"static/js/main.5ccb77ca.js"
|
||||
"static/css/main.01f852fa.css",
|
||||
"static/js/main.d902953d.js"
|
||||
]
|
||||
}
|
||||
@@ -1 +1 @@
|
||||
<!doctype html><html lang="en"><head><meta charset="utf-8"/><link rel="icon" href="/favicon.ico"/><meta name="viewport" content="width=device-width,initial-scale=1"/><meta name="theme-color" content="#000000"/><meta name="description" content="Web site created using create-react-app"/><link rel="apple-touch-icon" href="/logo192.png"/><script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.10/lodash.min.js"></script><link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0/css/all.min.css"><link rel="manifest" href="/manifest.json"/><title>Worldshine Transportation</title><script defer="defer" src="/static/js/main.5ccb77ca.js"></script><link href="/static/css/main.8bf7011f.css" rel="stylesheet"></head><body><noscript>You need to enable JavaScript to run this app.</noscript><div id="root"></div></body></html>
|
||||
<!doctype html><html lang="en"><head><meta charset="utf-8"/><link rel="icon" href="/favicon.ico"/><meta name="viewport" content="width=device-width,initial-scale=1"/><meta name="theme-color" content="#000000"/><meta name="description" content="Web site created using create-react-app"/><link rel="apple-touch-icon" href="/logo192.png"/><script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.10/lodash.min.js"></script><link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0/css/all.min.css"><link rel="manifest" href="/manifest.json"/><title>Worldshine Transportation</title><script defer="defer" src="/static/js/main.d902953d.js"></script><link href="/static/css/main.01f852fa.css" rel="stylesheet"></head><body><noscript>You need to enable JavaScript to run this app.</noscript><div id="root"></div></body></html>
|
||||
File diff suppressed because one or more lines are too long
1
app/views/static/css/main.01f852fa.css.map
Normal file
1
app/views/static/css/main.01f852fa.css.map
Normal file
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -206,12 +206,12 @@ input {
|
||||
padding: 4px 24px;
|
||||
}
|
||||
|
||||
/* List pages: container yields to children width/height, no scrolling (browser-level scrolling) */
|
||||
/* List pages: container constrained to page width, table scrolls inside */
|
||||
.app-main-content-list-container.list-page {
|
||||
max-width: none;
|
||||
max-height: none;
|
||||
width: fit-content;
|
||||
min-width: 100%;
|
||||
max-width: 100%;
|
||||
width: 100%;
|
||||
box-sizing: border-box;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.app-main-content-list-func-container {
|
||||
@@ -243,11 +243,18 @@ input {
|
||||
color: #0066B1 !important;
|
||||
}
|
||||
|
||||
/* List pages: tab-pane should not scroll, let browser handle it */
|
||||
/* List pages: tab-pane contains scrollable table */
|
||||
.app-main-content-list-container.list-page .tab-pane {
|
||||
border-top: 1px solid #ccc;
|
||||
padding-top: 24px;
|
||||
overflow: visible;
|
||||
overflow-x: auto;
|
||||
max-width: 100%;
|
||||
}
|
||||
|
||||
/* List pages: table wrapper should scroll horizontally */
|
||||
.app-main-content-list-container.list-page .col-md-12 {
|
||||
overflow-x: auto;
|
||||
max-width: 100%;
|
||||
}
|
||||
|
||||
/* Form pages: tab-pane should not scroll either */
|
||||
@@ -1803,6 +1810,12 @@ input[type="checkbox"] {
|
||||
min-width: 0;
|
||||
height: 100%;
|
||||
gap: clamp(8px, 1vw, 16px);
|
||||
min-height: 0;
|
||||
}
|
||||
|
||||
/* Ensure middle column gallery fills remaining space */
|
||||
.fullscreen-mode .column-container:nth-child(2) {
|
||||
justify-content: flex-start;
|
||||
}
|
||||
|
||||
.fullscreen-mode .column-card {
|
||||
@@ -2056,12 +2069,55 @@ input[type="checkbox"] {
|
||||
min-height: 0;
|
||||
}
|
||||
|
||||
.fullscreen-mode .attendance-note-wrapper,
|
||||
.fullscreen-mode .gallery-wrapper {
|
||||
.fullscreen-mode .attendance-note-wrapper {
|
||||
flex: 0 0 auto;
|
||||
margin-bottom: clamp(8px, 1vw, 16px);
|
||||
}
|
||||
|
||||
.fullscreen-mode .gallery-wrapper {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
margin-bottom: 0;
|
||||
min-height: 0;
|
||||
padding-bottom: clamp(8px, 1vw, 16px);
|
||||
}
|
||||
|
||||
.fullscreen-mode .gallery-wrapper .card {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
min-height: 0;
|
||||
}
|
||||
|
||||
.fullscreen-mode .gallery-wrapper .card-body {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
min-height: 0;
|
||||
}
|
||||
|
||||
.fullscreen-mode .gallery-wrapper .fullscreen-carousel {
|
||||
flex: 1;
|
||||
min-height: 0;
|
||||
}
|
||||
|
||||
.fullscreen-mode .gallery-wrapper .fullscreen-carousel,
|
||||
.fullscreen-mode .gallery-wrapper .carousel-inner,
|
||||
.fullscreen-mode .gallery-wrapper .carousel-item {
|
||||
height: 100% !important;
|
||||
}
|
||||
|
||||
.fullscreen-mode .gallery-wrapper .fullscreen-carousel-img {
|
||||
height: 100% !important;
|
||||
object-fit: cover;
|
||||
}
|
||||
|
||||
.fullscreen-mode .gallery-wrapper .fullscreen-placeholder {
|
||||
flex: 1;
|
||||
height: auto !important;
|
||||
}
|
||||
|
||||
.fullscreen-mode .fullscreen-date-display {
|
||||
margin-bottom: clamp(4px, 0.5vw, 12px);
|
||||
}
|
||||
|
||||
@@ -1,128 +1,32 @@
|
||||
import React, {useState, useEffect} from "react";
|
||||
import { useDispatch, useSelector } from "react-redux";
|
||||
import { useNavigate, useParams } from "react-router-dom";
|
||||
import { customerSlice } from "./../../store";
|
||||
import { AuthService, CustomerService, EventsService, LabelService } from "../../services";
|
||||
import { CUSTOMER_TYPE, ManageTable, Export } from "../../shared";
|
||||
import { Spinner, Breadcrumb, BreadcrumbItem, Tabs, Tab, Dropdown, OverlayTrigger, Popover } from "react-bootstrap";
|
||||
import { Columns, Download, Filter, PencilSquare, PersonSquare, Plus } from "react-bootstrap-icons";
|
||||
|
||||
import { useNavigate } from "react-router-dom";
|
||||
import { AuthService, CustomerService } from "../../services";
|
||||
import { Export } from "../../shared";
|
||||
import { Plus } from "react-bootstrap-icons";
|
||||
import DashboardCustomersList from "../dashboard/DashboardCustomersList";
|
||||
|
||||
const CustomersList = () => {
|
||||
const navigate = useNavigate();
|
||||
const dispatch = useDispatch();
|
||||
const [customers, setCustomers] = useState([]);
|
||||
const [keyword, setKeyword] = useState('');
|
||||
const [showInactive, setShowInactive] = useState(false);
|
||||
const [transferMap, setTransferMap] = useState({});
|
||||
// const [events, setEvents] = useState([]);
|
||||
const [showSpinner, setShowSpinner] = useState(false);
|
||||
const [sorting, setSorting] = useState({key: '', order: ''});
|
||||
const [selectedItems, setSelectedItems] = useState([]);
|
||||
const [filteredCustomers, setFilteredCustomers] = useState(customers);
|
||||
const [showFilterDropdown, setShowFilterDropdown] = useState(false);
|
||||
const [healthConditionFilter, setHealthConditionFilter] = useState('');
|
||||
const [paymentStatusFilter, setPaymentStatusFilter] = useState('');
|
||||
const [serviceRequirementFilter, setServiceRequirementFilter] = useState('');
|
||||
const [tagsFilter, setTagsFilter] = useState([]);
|
||||
const [availableLabels, setAvailableLabels] = useState([]);
|
||||
const [customerAvatars, setCustomerAvatars] = useState({});
|
||||
|
||||
// Pagination state
|
||||
const [currentPage, setCurrentPage] = useState(1);
|
||||
const [itemsPerPage] = useState(25);
|
||||
const [loadingAvatarId, setLoadingAvatarId] = useState(null);
|
||||
const [columns, setColumns] = useState([
|
||||
{
|
||||
key: 'name',
|
||||
label:'Name',
|
||||
show: true
|
||||
},
|
||||
{
|
||||
key: 'chinese_name',
|
||||
label: 'Preferred Name',
|
||||
show: true
|
||||
},
|
||||
{
|
||||
key: 'email',
|
||||
label: 'Email',
|
||||
show: true
|
||||
},
|
||||
{
|
||||
key: 'type',
|
||||
label: 'Type',
|
||||
show: true
|
||||
},
|
||||
{
|
||||
key: 'pickup_status',
|
||||
label: 'Pickup Status',
|
||||
show: true
|
||||
},
|
||||
{
|
||||
key: 'birth_date',
|
||||
label: 'Date of Birth',
|
||||
show: true
|
||||
},
|
||||
{
|
||||
key: 'gender',
|
||||
label: 'Gender',
|
||||
show: true
|
||||
},
|
||||
{
|
||||
key: 'language',
|
||||
label: 'Language',
|
||||
show: true
|
||||
},
|
||||
{
|
||||
key: 'medicare_number',
|
||||
label: 'Medicare Number',
|
||||
show: true
|
||||
},
|
||||
{
|
||||
key: 'medicaid_number',
|
||||
label: 'Medicaid Number',
|
||||
show: true
|
||||
},
|
||||
{
|
||||
key: 'address',
|
||||
label: 'Address',
|
||||
show: true
|
||||
},
|
||||
{
|
||||
key: 'phone',
|
||||
label: 'Phone',
|
||||
show: true
|
||||
},
|
||||
{
|
||||
key: 'emergency_contact',
|
||||
label: 'Fasting',
|
||||
show: true
|
||||
},
|
||||
{
|
||||
key: 'health_condition',
|
||||
label: 'Health Condition',
|
||||
show: true
|
||||
},
|
||||
{
|
||||
key: 'payment_status',
|
||||
label: 'Payment Status',
|
||||
show: true
|
||||
},
|
||||
{
|
||||
key: 'payment_due_date',
|
||||
label: 'Payment Due Date',
|
||||
show: true
|
||||
},
|
||||
{
|
||||
key: 'service_requirement',
|
||||
label: 'Service Requirement',
|
||||
show: true
|
||||
},
|
||||
{
|
||||
key: 'tags',
|
||||
label: 'Tags',
|
||||
show: true
|
||||
}
|
||||
const [columns] = useState([
|
||||
{ key: 'name', label:'Name', show: true },
|
||||
{ key: 'chinese_name', label: 'Preferred Name', show: true },
|
||||
{ key: 'email', label: 'Email', show: true },
|
||||
{ key: 'type', label: 'Type', show: true },
|
||||
{ key: 'pickup_status', label: 'Pickup Status', show: true },
|
||||
{ key: 'birth_date', label: 'Date of Birth', show: true },
|
||||
{ key: 'gender', label: 'Gender', show: true },
|
||||
{ key: 'language', label: 'Language', show: true },
|
||||
{ key: 'medicare_number', label: 'Medicare Number', show: true },
|
||||
{ key: 'medicaid_number', label: 'Medicaid Number', show: true },
|
||||
{ key: 'address', label: 'Address', show: true },
|
||||
{ key: 'phone', label: 'Phone', show: true },
|
||||
{ key: 'emergency_contact', label: 'Fasting', show: true },
|
||||
{ key: 'health_condition', label: 'Health Condition', show: true },
|
||||
{ key: 'payment_status', label: 'Payment Status', show: true },
|
||||
{ key: 'payment_due_date', label: 'Payment Due Date', show: true },
|
||||
{ key: 'service_requirement', label: 'Service Requirement', show: true },
|
||||
{ key: 'tags', label: 'Tags', show: true }
|
||||
]);
|
||||
|
||||
useEffect(() => {
|
||||
@@ -130,660 +34,47 @@ const CustomersList = () => {
|
||||
window.alert('You haven\'t login yet OR this user does not have access to this page. Please change an admin account to login.')
|
||||
AuthService.logout();
|
||||
navigate(`/login`);
|
||||
return;
|
||||
}
|
||||
CustomerService.getAllCustomers().then((data) => {
|
||||
const customerData = data.data.map((item) =>{
|
||||
item.phone = item?.phone || item?.home_phone || item?.mobile_phone;
|
||||
item.address = item?.address1 || item?.address2 || item?.address3 || item?.address4|| item?.address5;
|
||||
|
||||
return item;
|
||||
}).sort((a, b) => a.lastname > b.lastname ? 1: -1);
|
||||
|
||||
const customerData = data.data.map((item) => {
|
||||
item.phone = item?.phone || item?.home_phone || item?.mobile_phone;
|
||||
item.address = item?.address1 || item?.address2 || item?.address3 || item?.address4 || item?.address5;
|
||||
return item;
|
||||
}).sort((a, b) => a.lastname > b.lastname ? 1 : -1);
|
||||
setCustomers(customerData);
|
||||
// Removed bulk avatar loading - now loaded on-demand when user clicks
|
||||
})
|
||||
LabelService.getAll().then((data) => {
|
||||
setAvailableLabels(data.data);
|
||||
})
|
||||
}, []);
|
||||
|
||||
// Load avatar on-demand when user clicks on profile icon
|
||||
const loadAvatarOnDemand = (customerId) => {
|
||||
if (customerAvatars[customerId] !== undefined) return; // Already loaded or attempted
|
||||
|
||||
setLoadingAvatarId(customerId);
|
||||
CustomerService.getAvatarAsBlob(`customer_avatar_${customerId}`)
|
||||
.then(result => {
|
||||
if (result?.data && result.data.size > 0) {
|
||||
const url = URL.createObjectURL(result.data);
|
||||
setCustomerAvatars(prev => ({ ...prev, [customerId]: url }));
|
||||
} else {
|
||||
setCustomerAvatars(prev => ({ ...prev, [customerId]: null }));
|
||||
}
|
||||
setLoadingAvatarId(null);
|
||||
})
|
||||
.catch(() => {
|
||||
setCustomerAvatars(prev => ({ ...prev, [customerId]: null }));
|
||||
setLoadingAvatarId(null);
|
||||
});
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
let filtered = customers;
|
||||
|
||||
// Basic keyword filter
|
||||
if (keyword) {
|
||||
filtered = filtered.filter((item) => item?.name.toLowerCase().includes(keyword.toLowerCase()));
|
||||
}
|
||||
|
||||
// Active/Inactive filter
|
||||
if (showInactive) {
|
||||
filtered = filtered.filter(item => (item.type === CUSTOMER_TYPE.TRANSFERRED || item.type === CUSTOMER_TYPE.DECEASED || item.type === CUSTOMER_TYPE.DISCHARED) && item.status !== 'active');
|
||||
} else {
|
||||
filtered = filtered.filter(item => (item.type !== CUSTOMER_TYPE.TRANSFERRED && item.type!=CUSTOMER_TYPE.DECEASED && item.type!=CUSTOMER_TYPE.DISCHARED) && item.status === 'active');
|
||||
}
|
||||
|
||||
// Health condition filter
|
||||
if (healthConditionFilter) {
|
||||
filtered = filtered.filter(item => item?.health_condition === healthConditionFilter);
|
||||
}
|
||||
|
||||
// Payment status filter
|
||||
if (paymentStatusFilter) {
|
||||
filtered = filtered.filter(item => item?.payment_status === paymentStatusFilter);
|
||||
}
|
||||
|
||||
// Service requirement filter
|
||||
if (serviceRequirementFilter) {
|
||||
filtered = filtered.filter(item => item?.service_requirement === serviceRequirementFilter);
|
||||
}
|
||||
|
||||
// Tags filter
|
||||
if (tagsFilter.length > 0) {
|
||||
filtered = filtered.filter(item => {
|
||||
if (!item?.tags || item.tags.length === 0) return false;
|
||||
return tagsFilter.some(tag => item.tags.includes(tag));
|
||||
});
|
||||
}
|
||||
|
||||
setFilteredCustomers(filtered);
|
||||
setCurrentPage(1); // Reset to first page when filters change
|
||||
}, [keyword, customers, showInactive, healthConditionFilter, paymentStatusFilter, serviceRequirementFilter, tagsFilter])
|
||||
|
||||
// Pagination calculations
|
||||
const totalPages = Math.ceil(filteredCustomers.length / itemsPerPage);
|
||||
const startIndex = (currentPage - 1) * itemsPerPage;
|
||||
const endIndex = startIndex + itemsPerPage;
|
||||
const paginatedCustomers = filteredCustomers.slice(startIndex, endIndex);
|
||||
|
||||
const goToPage = (page) => {
|
||||
if (page >= 1 && page <= totalPages) {
|
||||
setCurrentPage(page);
|
||||
}
|
||||
};
|
||||
|
||||
const getPageNumbers = () => {
|
||||
const pages = [];
|
||||
const maxVisiblePages = 5;
|
||||
|
||||
if (totalPages <= maxVisiblePages) {
|
||||
for (let i = 1; i <= totalPages; i++) pages.push(i);
|
||||
} else {
|
||||
if (currentPage <= 3) {
|
||||
for (let i = 1; i <= 4; i++) pages.push(i);
|
||||
pages.push('...');
|
||||
pages.push(totalPages);
|
||||
} else if (currentPage >= totalPages - 2) {
|
||||
pages.push(1);
|
||||
pages.push('...');
|
||||
for (let i = totalPages - 3; i <= totalPages; i++) pages.push(i);
|
||||
} else {
|
||||
pages.push(1);
|
||||
pages.push('...');
|
||||
for (let i = currentPage - 1; i <= currentPage + 1; i++) pages.push(i);
|
||||
pages.push('...');
|
||||
pages.push(totalPages);
|
||||
}
|
||||
}
|
||||
return pages;
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
const newCustomers = [...customers];
|
||||
const sortedCustomers = sorting.key === '' ? newCustomers : newCustomers.sort((a, b) => {
|
||||
return a[sorting.key]?.localeCompare(b[sorting.key]);
|
||||
});
|
||||
setCustomers(
|
||||
sorting.order === 'asc' ? sortedCustomers : sortedCustomers.reverse()
|
||||
)
|
||||
}, [sorting]);
|
||||
|
||||
const getSortingImg = (key) => {
|
||||
return sorting.key === key ? (sorting.order === 'asc' ? 'up_arrow' : 'down_arrow') : 'default';
|
||||
}
|
||||
|
||||
const sortTableWithField = (key) => {
|
||||
let newSorting = {
|
||||
key,
|
||||
order: 'asc',
|
||||
}
|
||||
|
||||
if (sorting.key === key && sorting.order === 'asc') {
|
||||
newSorting = {...newSorting, order: 'desc'};
|
||||
}
|
||||
setSorting(newSorting);
|
||||
}
|
||||
|
||||
const toggleSelectedAllItems = () => {
|
||||
if (selectedItems.length !== filteredCustomers.length || selectedItems.length === 0) {
|
||||
const newSelectedItems = [...filteredCustomers].map((customer) => customer.id);
|
||||
setSelectedItems(newSelectedItems);
|
||||
} else {
|
||||
setSelectedItems([]);
|
||||
}
|
||||
}
|
||||
|
||||
const toggleItem = (id) => {
|
||||
if (selectedItems.includes(id)) {
|
||||
const newSelectedItems = [...selectedItems].filter((item) => item !== id);
|
||||
setSelectedItems(newSelectedItems);
|
||||
} else {
|
||||
const newSelectedItems = [...selectedItems, id];
|
||||
setSelectedItems(newSelectedItems);
|
||||
}
|
||||
}
|
||||
|
||||
const showArchive = (value) => {
|
||||
console.log('here', value);
|
||||
setShowInactive(value === 'archivedCustomers');
|
||||
// Recover all filters
|
||||
setKeyword('');
|
||||
setSorting({key: '', order: ''});
|
||||
setSelectedItems([]);
|
||||
}
|
||||
|
||||
const checkSelectAll = () => {
|
||||
return selectedItems.length === filteredCustomers.length && selectedItems.length > 0;
|
||||
}
|
||||
|
||||
const cleanFilterAndClose = () => {
|
||||
setHealthConditionFilter('');
|
||||
setPaymentStatusFilter('');
|
||||
setServiceRequirementFilter('');
|
||||
setTagsFilter([]);
|
||||
setShowFilterDropdown(false);
|
||||
}
|
||||
|
||||
const FilterAndClose = () => {
|
||||
setShowFilterDropdown(false);
|
||||
}
|
||||
|
||||
const toggleTagFilter = (tagName) => {
|
||||
if (tagsFilter.includes(tagName)) {
|
||||
setTagsFilter(tagsFilter.filter(tag => tag !== tagName));
|
||||
} else {
|
||||
setTagsFilter([...tagsFilter, tagName]);
|
||||
}
|
||||
}
|
||||
|
||||
const handleColumnsChange = (newColumns) => {
|
||||
setColumns(newColumns);
|
||||
}
|
||||
|
||||
const goToEdit = (id) => {
|
||||
navigate(`/customers/edit/${id}`)
|
||||
}
|
||||
}, [navigate]);
|
||||
|
||||
const goToCreateNew = () => {
|
||||
navigate(`/customers`)
|
||||
navigate(`/customers`);
|
||||
}
|
||||
|
||||
const setTransferValue = (customerId, site) => {
|
||||
const currentMap = Object.assign({}, transferMap);
|
||||
if (site !== undefined && site !== null && site !== '' && site !== 0) {
|
||||
currentMap[customerId] = site;
|
||||
setTransferMap(currentMap);
|
||||
} else {
|
||||
if (customerId) {
|
||||
delete currentMap[customerId];
|
||||
setTransferMap(currentMap);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const goToView = (id) => {
|
||||
navigate(`/customers/${id}`)
|
||||
}
|
||||
const site = EventsService.site;
|
||||
|
||||
const transferCustomer = (customerId) => {
|
||||
if (site !== undefined && site !== null && site !== '' && site !== 0) {
|
||||
setShowSpinner(true);
|
||||
const currentCustomer = customers.find((c) => c.id === customerId);
|
||||
if (currentCustomer) {
|
||||
EventsService.getByCustomer({name: currentCustomer?.name, id: currentCustomer?.id, namecn: currentCustomer?.name_cn}).then((eventsData) => {
|
||||
const events = eventsData?.data;
|
||||
CustomerService.updateCustomer(customerId, { ...currentCustomer, site: transferMap[customerId] }).then(() => {
|
||||
// const eventsWithCustomer = events.filter(ev => ev?.data?.customer === customerId || ev?.data?.client_name === currentCustomer?.name || ev?.target_name === currentCustomer?.name);
|
||||
if (events?.length > 0) {
|
||||
Promise.all(events?.map(
|
||||
item => EventsService.updateEvent(item?.id, {
|
||||
...item, site: transferMap[customerId]
|
||||
}))).then(() => {
|
||||
CustomerService.getAllCustomers().then((data) => {
|
||||
setCustomers(data.data?.sort((a, b) => a.lastname > b.lastname ? 1: -1));
|
||||
setShowSpinner(false);
|
||||
})
|
||||
setShowSpinner(false);
|
||||
}).catch((err) => setShowSpinner(false))
|
||||
} else {
|
||||
CustomerService.getAllCustomers().then((data) => {
|
||||
setCustomers(data.data?.sort((a, b) => a.lastname > b.lastname ? 1: -1));
|
||||
setShowSpinner(false);
|
||||
})
|
||||
}
|
||||
|
||||
}).catch(err => setShowSpinner(false))
|
||||
})
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
const exportCSV = (customer) => {
|
||||
const csvString = [
|
||||
[...Object.keys(customer)], // Specify your headers here
|
||||
Object.keys(customer).map((key) => key && customer[key] && `"${customer[key]}"` || "") // Map your data fields accordingly
|
||||
]
|
||||
.map(row => row.join(","))
|
||||
.join("\n");
|
||||
// Create a Blob from the CSV string
|
||||
const blob = new Blob([csvString], { type: 'text/csv' });
|
||||
// Generate a download link and initiate the download
|
||||
const url = URL.createObjectURL(blob);
|
||||
const link = document.createElement('a');
|
||||
link.href = url;
|
||||
link.download = `customer_${customer.name}.csv`;
|
||||
document.body.appendChild(link);
|
||||
link.click();
|
||||
document.body.removeChild(link);
|
||||
URL.revokeObjectURL(url);
|
||||
EventsService.getByCustomer({name: customer?.name, id: customer?.id, namecn: customer?.name_cn}).then((data) => {
|
||||
const events = data.data;
|
||||
if (events && events?.length > 0) {
|
||||
const lastEle = events[events.length - 1]
|
||||
const eventscsvString = [
|
||||
[...Object.keys(lastEle).filter(item => item !== 'data'), ...Object.keys(lastEle?.data)],
|
||||
...events.map((event) => {
|
||||
return [
|
||||
...Object.keys(lastEle).filter(item => item !== 'data').map((key) => event[key] && `"${event[key]}"` || ''),
|
||||
...Object.keys(lastEle?.data).map((key) => event?.data[key] && `"${event?.data[key]}"` || '')
|
||||
]
|
||||
})
|
||||
].map(row => row.join(","))
|
||||
.join("\n");
|
||||
// Create a Blob from the CSV string
|
||||
const blobCSV = new Blob([eventscsvString], { type: 'text/csv' });
|
||||
// Generate a download link and initiate the download
|
||||
const urlCSV = URL.createObjectURL(blobCSV);
|
||||
const csvlink = document.createElement('a');
|
||||
csvlink.href = urlCSV;
|
||||
csvlink.download = `Customer_${customer.name}_Medical_Events.csv`;
|
||||
document.body.appendChild(csvlink);
|
||||
csvlink.click();
|
||||
document.body.removeChild(csvlink);
|
||||
URL.revokeObjectURL(urlCSV);
|
||||
} else {
|
||||
window.alert('No medical events found for this user')
|
||||
}
|
||||
|
||||
})
|
||||
}
|
||||
|
||||
const table = <div className="list row mb-4">
|
||||
<div className="col-md-12">
|
||||
<div style={{
|
||||
maxHeight: '600px',
|
||||
overflowY: 'auto',
|
||||
overflowX: 'auto',
|
||||
position: 'relative'
|
||||
}}>
|
||||
<table className="personnel-info-table" style={{ position: 'relative' }}>
|
||||
<thead>
|
||||
<tr>
|
||||
<th className="th-checkbox" style={{ position: 'sticky', left: 0, zIndex: 3, backgroundColor: '#0066B1', paddingLeft: '14px' }}><input type="checkbox" checked={checkSelectAll()} onClick={() => toggleSelectedAllItems()}></input></th>
|
||||
<th className="th-index" style={{ position: 'sticky', left: '50px', zIndex: 3, backgroundColor: '#0066B1', paddingLeft: '14px' }}>No.</th>
|
||||
{
|
||||
columns.filter(col => col.show).map((column, index) => <th
|
||||
className="sortable-header"
|
||||
key={index}
|
||||
style={column.key === 'name' ? {
|
||||
position: 'sticky',
|
||||
left: '100px',
|
||||
zIndex: 3,
|
||||
backgroundColor: '#0066B1',
|
||||
paddingLeft: '14px',
|
||||
//borderRight: '2px solid #0056a0',
|
||||
boxShadow: '2px 0 5px rgba(0,0,0,0.1)'
|
||||
} : {}}
|
||||
>
|
||||
{column.label} <span className="float-right" onClick={() => sortTableWithField(column.key)}><img src={`/images/${getSortingImg(column.key)}.png`}></img></span>
|
||||
</th>)
|
||||
}
|
||||
<th></th>
|
||||
<th>Transfer To</th>
|
||||
</tr>
|
||||
|
||||
</thead>
|
||||
<tbody>
|
||||
{
|
||||
paginatedCustomers.map((customer, index) => {
|
||||
const avatarData = customerAvatars[customer.id];
|
||||
const isLoadingAvatar = loadingAvatarId === customer.id;
|
||||
|
||||
const profilePopover = (
|
||||
<Popover id={`popover-${customer.id}`}>
|
||||
<Popover.Body style={{ textAlign: 'center' }}>
|
||||
{isLoadingAvatar ? (
|
||||
<Spinner animation="border" size="sm" />
|
||||
) : avatarData ? (
|
||||
<img
|
||||
src={avatarData}
|
||||
alt={customer.name}
|
||||
style={{ width: '200px', height: '200px', objectFit: 'cover', borderRadius: '8px' }}
|
||||
/>
|
||||
) : (
|
||||
<PersonSquare size={200} />
|
||||
)}
|
||||
</Popover.Body>
|
||||
</Popover>
|
||||
);
|
||||
|
||||
const actualIndex = startIndex + index; // Calculate actual index for display
|
||||
const rowBgColor = index % 2 === 0 ? 'white' : '#eee';
|
||||
const stickyBaseStyle = {
|
||||
position: 'sticky',
|
||||
zIndex: 2,
|
||||
paddingLeft: '14px',
|
||||
};
|
||||
const checkboxStickyStyle = {
|
||||
...stickyBaseStyle,
|
||||
left: 0,
|
||||
backgroundColor: rowBgColor,
|
||||
};
|
||||
const indexStickyStyle = {
|
||||
...stickyBaseStyle,
|
||||
left: '50px',
|
||||
backgroundColor: rowBgColor,
|
||||
};
|
||||
const nameStickyStyle = {
|
||||
...stickyBaseStyle,
|
||||
left: '100px',
|
||||
backgroundColor: rowBgColor,
|
||||
boxShadow: '2px 0 5px rgba(0,0,0,0.1)'
|
||||
};
|
||||
|
||||
return <tr key={customer.id}>
|
||||
<td className="td-checkbox" style={checkboxStickyStyle}><input type="checkbox" checked={selectedItems.includes(customer.id)} onClick={()=>toggleItem(customer?.id)}/></td>
|
||||
<td className="td-index" style={indexStickyStyle}>{actualIndex + 1}</td>
|
||||
{columns.find(col => col.key === 'name')?.show && <td style={nameStickyStyle}>
|
||||
{AuthService.canAddOrEditCustomers() && <PencilSquare size={16} className="clickable me-2" onClick={() => goToEdit(customer?.id)}></PencilSquare>}
|
||||
{AuthService.canViewCustomers() && (
|
||||
<OverlayTrigger
|
||||
trigger="click"
|
||||
placement="right"
|
||||
overlay={profilePopover}
|
||||
rootClose
|
||||
>
|
||||
<span style={{ cursor: 'pointer' }} onClick={() => loadAvatarOnDemand(customer.id)}>
|
||||
<PersonSquare size={16} className="clickable me-2" />
|
||||
</span>
|
||||
</OverlayTrigger>
|
||||
)}
|
||||
<span className="clickable" onClick={() => goToView(customer?.id)} style={{ textDecoration: 'underline', cursor: 'pointer' }}>{customer?.name}</span>
|
||||
</td>}
|
||||
{columns.find(col => col.key === 'chinese_name')?.show && <td>{customer?.name_cn}</td>}
|
||||
{columns.find(col => col.key === 'email')?.show && <td>{customer?.email}</td>}
|
||||
{columns.find(col => col.key === 'type')?.show && <td>{customer?.type}</td>}
|
||||
{columns.find(col => col.key === 'pickup_status')?.show && <td>{customer?.pickup_status}</td>}
|
||||
{columns.find(col => col.key === 'birth_date')?.show && <td>{customer?.birth_date}</td>}
|
||||
{columns.find(col => col.key === 'gender')?.show && <td>{customer?.gender}</td>}
|
||||
{columns.find(col => col.key === 'language')?.show && <td>{customer?.language}</td>}
|
||||
{columns.find(col => col.key === 'medicare_number')?.show && <td>{customer?.medicare_number}</td>}
|
||||
{columns.find(col => col.key === 'medicaid_number')?.show && <td>{customer?.medicaid_number}</td>}
|
||||
{columns.find(col => col.key === 'address')?.show && <td>{customer?.address1 || customer?.address2 || customer?.address3 || customer?.address4 || customer?.address5}</td>}
|
||||
{columns.find(col => col.key === 'phone')?.show && <td>{customer?.phone || customer?.home_phone || customer?.mobile_phone}</td>}
|
||||
{columns.find(col => col.key === 'emergency_contact')?.show && <td>{customer?.emergency_contact}</td>}
|
||||
{columns.find(col => col.key === 'health_condition')?.show && <td>{customer?.health_condition}</td>}
|
||||
{columns.find(col => col.key === 'payment_status')?.show && <td>{customer?.payment_status}</td>}
|
||||
{columns.find(col => col.key === 'payment_due_date')?.show && <td>{customer?.payment_due_date}</td>}
|
||||
{columns.find(col => col.key === 'service_requirement')?.show && <td>{customer?.service_requirement}</td>}
|
||||
{columns.find(col => col.key === 'tags')?.show && <td>{customer?.tags?.join(', ')}</td>}
|
||||
<td>
|
||||
{AuthService.canViewCustomers() && <button className="btn btn-link btn-sm me-2" onClick={() => exportCSV(customer)}>Export Medical Events</button>}
|
||||
</td>
|
||||
<td>
|
||||
{AuthService.canAddOrEditCustomers() &&
|
||||
<div>
|
||||
<select className="transfer-select" value={transferMap[customer?.id]} onChange={(e) => setTransferValue(customer?.id, e.target.value)}>
|
||||
<option value=""></option>
|
||||
{ site !== 1 && <option value="1">Gaithersburg - 1</option>}
|
||||
{ site !== 2 && <option value="2">Beltsville - 2</option>}
|
||||
{ site !== 3 && <option value="3">Frederick - 3</option>}
|
||||
</select>
|
||||
{ transferMap[customer?.id] && <button className="btn btn-primary btn-sm me-2" onClick={() => transferCustomer(customer?.id)}>Confirm</button> }
|
||||
</div>
|
||||
|
||||
}
|
||||
</td>
|
||||
</tr>
|
||||
})
|
||||
}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
{/* Pagination Controls */}
|
||||
{totalPages > 1 && (
|
||||
<div className="pagination-controls" style={{
|
||||
display: 'flex',
|
||||
justifyContent: 'space-between',
|
||||
alignItems: 'center',
|
||||
padding: '15px 0',
|
||||
marginTop: '10px'
|
||||
}}>
|
||||
<div style={{ color: '#666', fontSize: '13px' }}>
|
||||
Showing {startIndex + 1} - {Math.min(endIndex, filteredCustomers.length)} of {filteredCustomers.length} customers
|
||||
</div>
|
||||
<div style={{ display: 'flex', gap: '5px', alignItems: 'center' }}>
|
||||
<button
|
||||
className="btn btn-sm btn-outline-primary"
|
||||
onClick={() => goToPage(1)}
|
||||
disabled={currentPage === 1}
|
||||
style={{ padding: '4px 8px' }}
|
||||
>
|
||||
First
|
||||
</button>
|
||||
<button
|
||||
className="btn btn-sm btn-outline-primary"
|
||||
onClick={() => goToPage(currentPage - 1)}
|
||||
disabled={currentPage === 1}
|
||||
style={{ padding: '4px 8px' }}
|
||||
>
|
||||
Prev
|
||||
</button>
|
||||
{getPageNumbers().map((page, index) => (
|
||||
page === '...' ? (
|
||||
<span key={`ellipsis-${index}`} style={{ padding: '4px 8px' }}>...</span>
|
||||
) : (
|
||||
<button
|
||||
key={page}
|
||||
className={`btn btn-sm ${currentPage === page ? 'btn-primary' : 'btn-outline-primary'}`}
|
||||
onClick={() => goToPage(page)}
|
||||
style={{ padding: '4px 10px', minWidth: '35px' }}
|
||||
>
|
||||
{page}
|
||||
</button>
|
||||
)
|
||||
))}
|
||||
<button
|
||||
className="btn btn-sm btn-outline-primary"
|
||||
onClick={() => goToPage(currentPage + 1)}
|
||||
disabled={currentPage === totalPages}
|
||||
style={{ padding: '4px 8px' }}
|
||||
>
|
||||
Next
|
||||
</button>
|
||||
<button
|
||||
className="btn btn-sm btn-outline-primary"
|
||||
onClick={() => goToPage(totalPages)}
|
||||
disabled={currentPage === totalPages}
|
||||
style={{ padding: '4px 8px' }}
|
||||
>
|
||||
Last
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>;
|
||||
|
||||
const customFilterMenu = React.forwardRef(
|
||||
({ children, style, className, 'aria-labelledby': labeledBy }, ref) => {
|
||||
return (
|
||||
<div
|
||||
ref={ref}
|
||||
style={style}
|
||||
className={className}
|
||||
aria-labelledby={labeledBy}
|
||||
>
|
||||
<h6>Filter By</h6>
|
||||
<div className="app-main-content-fields-section margin-sm dropdown-container">
|
||||
<div className="me-4">
|
||||
<div className="field-label">Health Condition</div>
|
||||
<select value={healthConditionFilter} onChange={(e) => setHealthConditionFilter(e.target.value)}>
|
||||
<option value=""></option>
|
||||
<option value="diabetes">Diabetes</option>
|
||||
<option value="1-1">1-1</option>
|
||||
<option value="rounding list">Rounding List</option>
|
||||
<option value="MOLST/POA/Advanced Directive">MOLST/POA/Advanced Directive</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div className="app-main-content-fields-section margin-sm dropdown-container">
|
||||
<div className="me-4">
|
||||
<div className="field-label">Payment Status</div>
|
||||
<select value={paymentStatusFilter} onChange={(e) => setPaymentStatusFilter(e.target.value)}>
|
||||
<option value=""></option>
|
||||
<option value="paid">Paid</option>
|
||||
<option value="overdue">Overdue</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div className="app-main-content-fields-section margin-sm dropdown-container">
|
||||
<div className="me-4">
|
||||
<div className="field-label">Service Requirement</div>
|
||||
<select value={serviceRequirementFilter} onChange={(e) => setServiceRequirementFilter(e.target.value)}>
|
||||
<option value=""></option>
|
||||
<option value="wheelchair">Wheelchair</option>
|
||||
<option value="special care">Special Care</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div className="app-main-content-fields-section margin-sm dropdown-container">
|
||||
<div className="me-4">
|
||||
<div className="field-label">Tags</div>
|
||||
<div style={{ maxHeight: '150px', overflowY: 'auto' }}>
|
||||
{availableLabels.map((label) => (
|
||||
<div key={label.id} style={{ marginBottom: '5px' }}>
|
||||
<input
|
||||
type="checkbox"
|
||||
id={`tag-${label.id}`}
|
||||
checked={tagsFilter.includes(label.label_name)}
|
||||
onChange={() => toggleTagFilter(label.label_name)}
|
||||
/>
|
||||
<label htmlFor={`tag-${label.id}`} style={{ marginLeft: '5px' }}>
|
||||
{label.label_name}
|
||||
</label>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="list row">
|
||||
<div className="col-md-12">
|
||||
<button className="btn btn-default btn-sm float-right" onClick={() => cleanFilterAndClose()}> Cancel </button>
|
||||
<button className="btn btn-primary btn-sm float-right" onClick={() => FilterAndClose()}> Filter </button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
},
|
||||
const additionalButtons = (
|
||||
<>
|
||||
<button className="btn btn-primary me-2" onClick={() => goToCreateNew()}>
|
||||
<Plus size={16}></Plus>Add New Customer
|
||||
</button>
|
||||
<Export
|
||||
columns={columns}
|
||||
data={customers.map((customer) => ({
|
||||
...customer,
|
||||
address: customer?.address1 || customer?.address2 || customer?.address3 || customer?.address4 || customer?.address5,
|
||||
phone: customer?.phone || customer?.home_phone || customer?.mobile_phone,
|
||||
tags: customer?.tags?.join(', ')
|
||||
}))}
|
||||
filename="customers"
|
||||
/>
|
||||
</>
|
||||
);
|
||||
|
||||
return (
|
||||
<>
|
||||
{showSpinner && <div className="spinner-overlay">
|
||||
<Spinner animation="border" role="status">
|
||||
<span className="visually-hidden">Loading...</span>
|
||||
</Spinner>
|
||||
</div>}
|
||||
<div className="list row mb-4">
|
||||
<Breadcrumb>
|
||||
<Breadcrumb.Item href="/">General</Breadcrumb.Item>
|
||||
<Breadcrumb.Item active>
|
||||
Customer Information
|
||||
</Breadcrumb.Item>
|
||||
</Breadcrumb>
|
||||
<div className="col-md-12 text-primary">
|
||||
<h4>
|
||||
All Customers
|
||||
{/* <button className="btn btn-primary btn-sm" onClick={() => {goToCreateNew()}}>Create New Customer</button>
|
||||
<button className="btn btn-link btn-sm" onClick={() => {redirectToAdmin()}}>Back</button> */}
|
||||
</h4>
|
||||
</div>
|
||||
</div>
|
||||
<div className="app-main-content-list-container list-page">
|
||||
<div className="app-main-content-list-func-container">
|
||||
<Tabs defaultActiveKey="activeCustomers" id="customers-tab" onSelect={(k) => showArchive(k)}>
|
||||
<Tab eventKey="activeCustomers" title="Active Customers">
|
||||
{table}
|
||||
</Tab>
|
||||
<Tab eventKey="archivedCustomers" title="Discharge Customers">
|
||||
{table}
|
||||
</Tab>
|
||||
</Tabs>
|
||||
<div className="list-func-panel">
|
||||
<input className="me-2 with-search-icon" type="text" placeholder="Search" value={keyword} onChange={(e) => setKeyword(e.currentTarget.value)} />
|
||||
<Dropdown
|
||||
key={'filter-customers'}
|
||||
id="filter-customers"
|
||||
className="me-2"
|
||||
show={showFilterDropdown}
|
||||
onToggle={() => setShowFilterDropdown(!showFilterDropdown)}
|
||||
autoClose={false}
|
||||
>
|
||||
<Dropdown.Toggle variant="primary">
|
||||
<Filter size={16} className="me-2"></Filter>Filter
|
||||
</Dropdown.Toggle>
|
||||
<Dropdown.Menu as={customFilterMenu}/>
|
||||
</Dropdown>
|
||||
<ManageTable columns={columns} onColumnsChange={handleColumnsChange} />
|
||||
<button className="btn btn-primary me-2" onClick={() => goToCreateNew()}><Plus size={16}></Plus>Add New Customer</button>
|
||||
<Export
|
||||
columns={columns}
|
||||
data={filteredCustomers.map((customer) => ({
|
||||
...customer,
|
||||
address: customer?.address1 || customer?.address2 || customer?.address3 || customer?.address4 || customer?.address5,
|
||||
phone: customer?.phone || customer?.home_phone || customer?.mobile_phone,
|
||||
tags: customer?.tags?.join(', ')
|
||||
}))}
|
||||
filename="customers"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
)
|
||||
<DashboardCustomersList
|
||||
showBreadcrumb={true}
|
||||
title="All Customers"
|
||||
additionalButtons={additionalButtons}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
export default CustomersList;
|
||||
export default CustomersList;
|
||||
|
||||
@@ -35,11 +35,19 @@
|
||||
.dashboard-event-item {
|
||||
padding: 8px;
|
||||
border-radius: 4px;
|
||||
width: 100%;
|
||||
max-width: 100%;
|
||||
box-sizing: border-box;
|
||||
overflow: hidden;
|
||||
word-wrap: break-word;
|
||||
overflow-wrap: break-word;
|
||||
}
|
||||
|
||||
.dashboard-event-title {
|
||||
font-size: 0.85rem;
|
||||
font-weight: 500;
|
||||
word-wrap: break-word;
|
||||
overflow-wrap: break-word;
|
||||
}
|
||||
|
||||
.dashboard-event-time {
|
||||
@@ -50,4 +58,25 @@
|
||||
.dashboard-event-description {
|
||||
font-size: 0.75rem;
|
||||
color: #666;
|
||||
word-wrap: break-word;
|
||||
overflow-wrap: break-word;
|
||||
}
|
||||
|
||||
/* Ensure right sidebar content stays within bounds */
|
||||
.dashboard-right-sidebar .column-card {
|
||||
width: 100%;
|
||||
max-width: 100%;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.dashboard-right-sidebar .column-card > div {
|
||||
width: 100%;
|
||||
max-width: 100%;
|
||||
}
|
||||
|
||||
/* Override min-width for event tiles in dashboard to ensure they fit */
|
||||
.dashboard-right-sidebar .event-list-item-container {
|
||||
min-width: 0;
|
||||
width: 100%;
|
||||
max-width: 100%;
|
||||
}
|
||||
@@ -7,7 +7,7 @@ import { CUSTOMER_TYPE, ManageTable } from "../../shared";
|
||||
import { Spinner, Breadcrumb, BreadcrumbItem, Tabs, Tab, Dropdown } from "react-bootstrap";
|
||||
import { Columns, Download, Filter, PencilSquare, PersonSquare, Plus } from "react-bootstrap-icons";
|
||||
|
||||
const DashboardCustomersList = () => {
|
||||
const DashboardCustomersList = ({ additionalButtons, showBreadcrumb = false, title = null }) => {
|
||||
const navigate = useNavigate();
|
||||
const dispatch = useDispatch();
|
||||
const site = EventsService.site;
|
||||
@@ -20,6 +20,7 @@ const DashboardCustomersList = () => {
|
||||
const [selectedItems, setSelectedItems] = useState([]);
|
||||
const [filteredCustomers, setFilteredCustomers] = useState(customers);
|
||||
const [showFilterDropdown, setShowFilterDropdown] = useState(false);
|
||||
const [showManageTableDropdown, setShowManageTableDropdown] = useState(false);
|
||||
const [healthConditionFilter, setHealthConditionFilter] = useState('');
|
||||
const [paymentStatusFilter, setPaymentStatusFilter] = useState('');
|
||||
const [serviceRequirementFilter, setServiceRequirementFilter] = useState('');
|
||||
@@ -393,7 +394,20 @@ const DashboardCustomersList = () => {
|
||||
<span className="visually-hidden">Loading...</span>
|
||||
</Spinner>
|
||||
</div>}
|
||||
<div className="app-main-content-list-container">
|
||||
{showBreadcrumb && (
|
||||
<div className="list row mb-4">
|
||||
<Breadcrumb>
|
||||
<Breadcrumb.Item href="/">General</Breadcrumb.Item>
|
||||
<Breadcrumb.Item active>
|
||||
Customer Information
|
||||
</Breadcrumb.Item>
|
||||
</Breadcrumb>
|
||||
<div className="col-md-12 text-primary">
|
||||
<h4>{title || 'All Customers'}</h4>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
<div className={`app-main-content-list-container ${showBreadcrumb ? 'list-page' : ''}`}>
|
||||
<div className="app-main-content-list-func-container">
|
||||
<Tabs defaultActiveKey="activeCustomers" id="customers-tab" onSelect={(k) => showArchive(k)}>
|
||||
<Tab eventKey="activeCustomers" title="Active Customers">
|
||||
@@ -410,7 +424,12 @@ const DashboardCustomersList = () => {
|
||||
id="filter-customers"
|
||||
className="me-2"
|
||||
show={showFilterDropdown}
|
||||
onToggle={() => setShowFilterDropdown(!showFilterDropdown)}
|
||||
onToggle={(isOpen) => {
|
||||
if (isOpen) {
|
||||
setShowManageTableDropdown(false);
|
||||
}
|
||||
setShowFilterDropdown(isOpen);
|
||||
}}
|
||||
autoClose={false}
|
||||
>
|
||||
<Dropdown.Toggle variant="primary">
|
||||
@@ -418,8 +437,18 @@ const DashboardCustomersList = () => {
|
||||
</Dropdown.Toggle>
|
||||
<Dropdown.Menu as={customFilterMenu}/>
|
||||
</Dropdown>
|
||||
<ManageTable columns={columns} onColumnsChange={handleColumnsChange} />
|
||||
{/* Removed Create New Customer button and Export functionality */}
|
||||
<ManageTable
|
||||
columns={columns}
|
||||
onColumnsChange={handleColumnsChange}
|
||||
show={showManageTableDropdown}
|
||||
onToggle={(isOpen) => {
|
||||
if (isOpen) {
|
||||
setShowFilterDropdown(false);
|
||||
}
|
||||
setShowManageTableDropdown(isOpen);
|
||||
}}
|
||||
/>
|
||||
{additionalButtons}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -90,8 +90,7 @@ const RoutesSection = ({transRoutes, copyList, sectionName, drivers, vehicles, c
|
||||
{`${sectionName}: `} <span className="route-stats">{
|
||||
(sectionName.includes('Inbound') ||
|
||||
sectionName.includes('Outbound')) &&
|
||||
(`${seniors?.length} Scheduled ${seniors?.filter(
|
||||
item => ![PERSONAL_ROUTE_STATUS.UNEXPECTED_ABSENT, PERSONAL_ROUTE_STATUS.SCHEDULED_ABSENT].includes(item?.customer_route_status))?.length} ${ sectionName.includes('Inbound') ? 'Checked In': 'Checked Out'} (${seniors.filter(item => [CUSTOMER_TYPE.MEMBER, CUSTOMER_TYPE.SELF_PAY].includes(item.customer_type) && ![PERSONAL_ROUTE_STATUS.UNEXPECTED_ABSENT, PERSONAL_ROUTE_STATUS.SCHEDULED_ABSENT].includes(item?.customer_route_status))?.length} Members ${seniors.filter(item=> [CUSTOMER_TYPE.VISITOR].includes(item?.customer_type) && ![PERSONAL_ROUTE_STATUS.UNEXPECTED_ABSENT, PERSONAL_ROUTE_STATUS.SCHEDULED_ABSENT].includes(item?.customer_route_status))?.length} Visitors)`)}</span>
|
||||
(`${seniors?.length} Scheduled (${seniors.filter(item => [CUSTOMER_TYPE.MEMBER, CUSTOMER_TYPE.SELF_PAY].includes(item.customer_type) && ![PERSONAL_ROUTE_STATUS.UNEXPECTED_ABSENT, PERSONAL_ROUTE_STATUS.SCHEDULED_ABSENT].includes(item?.customer_route_status))?.length} Members ${seniors.filter(item=> [CUSTOMER_TYPE.VISITOR].includes(item?.customer_type) && ![PERSONAL_ROUTE_STATUS.UNEXPECTED_ABSENT, PERSONAL_ROUTE_STATUS.SCHEDULED_ABSENT].includes(item?.customer_route_status))?.length} Visitors)`)}</span>
|
||||
</h6>
|
||||
{ canAddNew && (
|
||||
<small className="me-4" onClick={() => { if (routeType) {redirect(routeType)} else {redirect()}}}>
|
||||
|
||||
@@ -2,9 +2,13 @@ import React, { useState } from "react";
|
||||
import { Dropdown } from "react-bootstrap";
|
||||
import { Columns } from "react-bootstrap-icons";
|
||||
|
||||
const ManageTable = ({ columns, onColumnsChange }) => {
|
||||
const [showManageTableDropdown, setShowManageTableDropdown] = useState(false);
|
||||
const ManageTable = ({ columns, onColumnsChange, show, onToggle }) => {
|
||||
const [internalShow, setInternalShow] = useState(false);
|
||||
const [tempColumns, setTempColumns] = useState(columns);
|
||||
|
||||
// Use external control if provided, otherwise use internal state
|
||||
const showManageTableDropdown = show !== undefined ? show : internalShow;
|
||||
const handleToggle = onToggle || (() => setInternalShow(!internalShow));
|
||||
|
||||
const handleColumnToggle = (columnKey) => {
|
||||
const updatedColumns = tempColumns.map(col =>
|
||||
@@ -15,12 +19,20 @@ const ManageTable = ({ columns, onColumnsChange }) => {
|
||||
|
||||
const handleDone = () => {
|
||||
onColumnsChange(tempColumns);
|
||||
setShowManageTableDropdown(false);
|
||||
if (onToggle) {
|
||||
onToggle(false);
|
||||
} else {
|
||||
setInternalShow(false);
|
||||
}
|
||||
};
|
||||
|
||||
const handleCancel = () => {
|
||||
setTempColumns(columns);
|
||||
setShowManageTableDropdown(false);
|
||||
if (onToggle) {
|
||||
onToggle(false);
|
||||
} else {
|
||||
setInternalShow(false);
|
||||
}
|
||||
};
|
||||
|
||||
const customManageTableMenu = React.forwardRef(
|
||||
@@ -69,7 +81,7 @@ const ManageTable = ({ columns, onColumnsChange }) => {
|
||||
id="manage-table"
|
||||
className="me-2"
|
||||
show={showManageTableDropdown}
|
||||
onToggle={() => setShowManageTableDropdown(!showManageTableDropdown)}
|
||||
onToggle={handleToggle}
|
||||
autoClose={false}
|
||||
>
|
||||
<Dropdown.Toggle variant="primary">
|
||||
|
||||
Reference in New Issue
Block a user