All checks were successful
Build And Deploy Main / build-and-deploy (push) Successful in 33s
578 lines
22 KiB
JavaScript
578 lines
22 KiB
JavaScript
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 } from "../../services";
|
|
import { CUSTOMER_TYPE, CUSTOMER_TYPE_TEXT, PROGRAM_TYPE, PROGRAM_TYPE_TEXT, PAY_SOURCE, PAY_SOURCE_TEXT, YES_NO, YES_NO_TEXT, ManageTable } from "../../shared";
|
|
import { Spinner, Breadcrumb, BreadcrumbItem, Tabs, Tab, Dropdown, Modal } from "react-bootstrap";
|
|
import { Columns, Download, Filter, PencilSquare, PersonSquare, Plus } from "react-bootstrap-icons";
|
|
|
|
const DashboardCustomersList = ({ additionalButtons, showBreadcrumb = false, title = null }) => {
|
|
const HIDDEN_CUSTOMER_TYPE_FILTER_VALUES = [CUSTOMER_TYPE.TRANSFERRED, CUSTOMER_TYPE.DECEASED, CUSTOMER_TYPE.DISCHARGED];
|
|
const CUSTOMER_LIST_COLUMN_TAB_MAP = {
|
|
name: 'personalInfo',
|
|
chinese_name: 'personalInfo',
|
|
email: 'personalInfo',
|
|
type: 'careServices',
|
|
pickup_status: 'careServices',
|
|
birth_date: 'personalInfo',
|
|
gender: 'personalInfo',
|
|
language: 'personalInfo',
|
|
medicare_number: 'medicalInsurance',
|
|
medicaid_number: 'medicalInsurance',
|
|
address: 'personalInfo',
|
|
phone: 'personalInfo',
|
|
emergency_contact: 'medicalInsurance',
|
|
tags: 'personalInfo'
|
|
};
|
|
const getVisibleColumnsByPermission = (columnList = []) => {
|
|
return columnList.filter((column) => {
|
|
if (column.key === 'name') return true;
|
|
const mappedTab = CUSTOMER_LIST_COLUMN_TAB_MAP[column.key];
|
|
if (!mappedTab) return true;
|
|
return AuthService.canViewCustomerTab(mappedTab);
|
|
});
|
|
};
|
|
const CUSTOMER_FILTER_TAB_MAP = {
|
|
customerType: 'careServices',
|
|
programType: 'personalInfo',
|
|
paySource: 'personalInfo',
|
|
eyesOn: 'medicalInsurance'
|
|
};
|
|
const canViewFilterControl = (filterKey) => {
|
|
const mappedTab = CUSTOMER_FILTER_TAB_MAP[filterKey];
|
|
if (!mappedTab) return true;
|
|
return AuthService.canViewCustomerTab(mappedTab);
|
|
};
|
|
const visibleCustomerTypeFilter = canViewFilterControl('customerType');
|
|
const visibleProgramTypeFilter = canViewFilterControl('programType');
|
|
const visiblePaySourceFilter = canViewFilterControl('paySource');
|
|
const visibleEyesOnFilter = canViewFilterControl('eyesOn');
|
|
const hasVisibleFilters = visibleCustomerTypeFilter || visibleProgramTypeFilter || visiblePaySourceFilter || visibleEyesOnFilter;
|
|
const navigate = useNavigate();
|
|
const dispatch = useDispatch();
|
|
const site = EventsService.site;
|
|
const [customers, setCustomers] = useState([]);
|
|
const [keyword, setKeyword] = useState('');
|
|
const [showInactive, setShowInactive] = useState(false);
|
|
const [transferMap, setTransferMap] = 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 [showManageTableDropdown, setShowManageTableDropdown] = useState(false);
|
|
const [showExportDropdown, setShowExportDropdown] = useState(false);
|
|
const [customerTypeFilter, setCustomerTypeFilter] = useState('');
|
|
const [programTypeFilter, setProgramTypeFilter] = useState('');
|
|
const [paySourceFilter, setPaySourceFilter] = useState('');
|
|
const [eyesOnFilter, setEyesOnFilter] = useState('');
|
|
const [showAvatarModal, setShowAvatarModal] = useState(false);
|
|
const [avatarData, setAvatarData] = useState(null);
|
|
const [avatarCustomerName, setAvatarCustomerName] = useState('');
|
|
const [avatarLoading, setAvatarLoading] = useState(false);
|
|
const [customerAvatars, setCustomerAvatars] = useState({});
|
|
const isAllCustomersPage = title === 'All Customers';
|
|
const [columns, setColumns] = useState(getVisibleColumnsByPermission([
|
|
{
|
|
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: 'tags',
|
|
label: 'Tags',
|
|
show: true
|
|
}
|
|
]));
|
|
useEffect(() => {
|
|
setColumns((prevColumns) => getVisibleColumnsByPermission(prevColumns));
|
|
}, []);
|
|
|
|
|
|
useEffect(() => {
|
|
if (!AuthService.canViewCustomers()) {
|
|
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`);
|
|
}
|
|
setShowSpinner(true);
|
|
CustomerService.getAllCustomers().then((data) => {
|
|
setCustomers(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));
|
|
}).finally(() => {
|
|
setShowSpinner(false);
|
|
});
|
|
}, []);
|
|
|
|
// Load avatars for customers - same approach as ViewCustomer
|
|
useEffect(() => {
|
|
if (customers.length === 0) return;
|
|
customers.forEach((customer) => {
|
|
if (customerAvatars[customer.id] !== undefined) return; // Already loaded or attempted
|
|
// Mark as loading to prevent duplicate requests
|
|
setCustomerAvatars(prev => ({ ...prev, [customer.id]: false }));
|
|
CustomerService.getAvatar(customer.id)
|
|
.then(result => {
|
|
if (result?.data) {
|
|
// Store raw data exactly like ViewCustomer does
|
|
setCustomerAvatars(prev => ({ ...prev, [customer.id]: result.data }));
|
|
} else {
|
|
setCustomerAvatars(prev => ({ ...prev, [customer.id]: null }));
|
|
}
|
|
})
|
|
.catch(() => {
|
|
setCustomerAvatars(prev => ({ ...prev, [customer.id]: null }));
|
|
});
|
|
});
|
|
}, [customers]);
|
|
|
|
useEffect(() => {
|
|
let filtered = customers;
|
|
|
|
// Basic keyword filter
|
|
if (keyword) {
|
|
filtered = filtered.filter((item) => item?.name.toLowerCase().includes(keyword.toLowerCase()));
|
|
}
|
|
|
|
// Active/Inactive filter
|
|
if (showInactive) {
|
|
// Discharged Customers tab: show customers with type discharged, transferred, or deceased
|
|
filtered = filtered.filter(item => item.type === CUSTOMER_TYPE.TRANSFERRED || item.type === CUSTOMER_TYPE.DECEASED || item.type === CUSTOMER_TYPE.DISCHARGED);
|
|
} else {
|
|
// Active Customers tab: show customers who are active AND not discharged/transferred/deceased
|
|
filtered = filtered.filter(item => item.status === 'active' && item.type !== CUSTOMER_TYPE.TRANSFERRED && item.type !== CUSTOMER_TYPE.DECEASED && item.type !== CUSTOMER_TYPE.DISCHARGED);
|
|
}
|
|
|
|
if (customerTypeFilter && visibleCustomerTypeFilter) {
|
|
filtered = filtered.filter((item) => (item?.type || '') === customerTypeFilter);
|
|
}
|
|
|
|
if (programTypeFilter && visibleProgramTypeFilter) {
|
|
filtered = filtered.filter((item) => (item?.program || '') === programTypeFilter);
|
|
}
|
|
|
|
if (paySourceFilter && visiblePaySourceFilter) {
|
|
filtered = filtered.filter((item) => (item?.pay_source || '') === paySourceFilter);
|
|
}
|
|
|
|
if (eyesOnFilter && visibleEyesOnFilter) {
|
|
filtered = filtered.filter((item) => {
|
|
const normalized = `${item?.eyes_on || (item?.disability ? YES_NO.YES : '')}`.toLowerCase();
|
|
return normalized === eyesOnFilter;
|
|
});
|
|
}
|
|
|
|
setFilteredCustomers(filtered);
|
|
}, [keyword, customers, showInactive, customerTypeFilter, programTypeFilter, paySourceFilter, eyesOnFilter])
|
|
|
|
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) => {
|
|
if (sorting.key === key) {
|
|
setSorting({key: key, order: sorting.order === 'asc' ? 'desc' : 'asc'});
|
|
} else {
|
|
setSorting({key: key, order: 'asc'});
|
|
}
|
|
}
|
|
|
|
const toggleSelectedAllItems = () => {
|
|
if (selectedItems.length === filteredCustomers.length) {
|
|
setSelectedItems([]);
|
|
} else {
|
|
setSelectedItems(filteredCustomers.map(item => item.id));
|
|
}
|
|
}
|
|
|
|
const toggleItem = (id) => {
|
|
if (selectedItems.includes(id)) {
|
|
setSelectedItems(selectedItems.filter(item => item !== id));
|
|
} else {
|
|
setSelectedItems([...selectedItems, id]);
|
|
}
|
|
}
|
|
|
|
const showArchive = (value) => {
|
|
setShowInactive(value === 'archivedCustomers');
|
|
}
|
|
|
|
const checkSelectAll = () => {
|
|
return selectedItems.length === filteredCustomers.length && filteredCustomers.length > 0;
|
|
}
|
|
|
|
const cleanFilterAndClose = () => {
|
|
setCustomerTypeFilter('');
|
|
setProgramTypeFilter('');
|
|
setPaySourceFilter('');
|
|
setEyesOnFilter('');
|
|
setShowFilterDropdown(false);
|
|
}
|
|
|
|
const FilterAndClose = () => {
|
|
setShowFilterDropdown(false);
|
|
}
|
|
|
|
const getOptionsFromEnum = (enumMap, textMap) => {
|
|
return Object.values(enumMap).map((value) => ({
|
|
value,
|
|
label: textMap?.[value] || value
|
|
}));
|
|
};
|
|
|
|
const handleColumnsChange = (newColumns) => {
|
|
setColumns(getVisibleColumnsByPermission(newColumns));
|
|
}
|
|
|
|
const goToEdit = (id) => {
|
|
navigate(`/customers/edit/${id}`);
|
|
}
|
|
|
|
const goToCreateNew = () => {
|
|
navigate(`/customers`);
|
|
}
|
|
|
|
const setTransferValue = (customerId, site) => {
|
|
setTransferMap(prev => ({
|
|
...prev,
|
|
[customerId]: site
|
|
}));
|
|
}
|
|
|
|
const goToView = (id) => {
|
|
navigate(`/customers/${id}`);
|
|
}
|
|
|
|
const showProfilePicture = (customer) => {
|
|
setAvatarCustomerName(customer?.name || '');
|
|
setAvatarData(null);
|
|
setAvatarLoading(true);
|
|
setShowAvatarModal(true);
|
|
CustomerService.getAvatar(customer?.id).then((data) => {
|
|
setAvatarData(data.data);
|
|
setAvatarLoading(false);
|
|
}).catch(() => {
|
|
setAvatarLoading(false);
|
|
});
|
|
}
|
|
|
|
const closeAvatarModal = () => {
|
|
setShowAvatarModal(false);
|
|
setAvatarData(null);
|
|
setAvatarCustomerName('');
|
|
}
|
|
|
|
const table = <div className="list row mb-4">
|
|
<div
|
|
className="col-md-12"
|
|
style={{
|
|
overflow: 'auto',
|
|
minHeight: isAllCustomersPage ? 'calc(100vh - 360px)' : undefined,
|
|
paddingBottom: isAllCustomersPage ? '24px' : undefined
|
|
}}
|
|
>
|
|
<table className="personnel-info-table">
|
|
<thead>
|
|
<tr>
|
|
<th className="th-index">No.</th>
|
|
{
|
|
columns.filter(col => col.show).map((column, index) => <th className="sortable-header" key={index}>
|
|
{column.label} <span className="float-right" onClick={() => sortTableWithField(column.key)}><img src={`/images/${getSortingImg(column.key)}.png`}></img></span>
|
|
</th>)
|
|
}
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
{
|
|
filteredCustomers.map((customer, index) => <tr key={customer.id}>
|
|
<td className="td-index">{index + 1}</td>
|
|
{columns.find(col => col.key === 'name')?.show && <td style={{ verticalAlign: 'middle' }}>
|
|
<div style={{ display: 'flex', alignItems: 'center', gap: '8px' }}>
|
|
{AuthService.canViewCustomers() && (
|
|
customerAvatars[customer.id] && customerAvatars[customer.id] !== false ? (
|
|
<img
|
|
src={`data:image/png;base64, ${customerAvatars[customer.id]}`}
|
|
alt={customer?.name}
|
|
onClick={() => showProfilePicture(customer)}
|
|
style={{
|
|
width: '64px',
|
|
height: '64px',
|
|
borderRadius: '8px',
|
|
objectFit: 'cover',
|
|
cursor: 'pointer',
|
|
flexShrink: 0
|
|
}}
|
|
/>
|
|
) : (
|
|
<PersonSquare
|
|
onClick={() => showProfilePicture(customer)}
|
|
size={64}
|
|
className="clickable"
|
|
style={{ flexShrink: 0, color: '#ccc' }}
|
|
/>
|
|
)
|
|
)}
|
|
{AuthService.canEditAnyCustomerTab() && <PencilSquare size={16} className="clickable" onClick={() => isAllCustomersPage ? goToEdit(customer?.id) : goToView(customer?.id)} style={{ flexShrink: 0 }}></PencilSquare>}
|
|
<span className="clickable" style={{ color: '#0066B1', textDecoration: 'underline', cursor: 'pointer' }} onClick={() => goToView(customer?.id)}>{customer?.name}</span>
|
|
</div>
|
|
</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 === 'tags')?.show && <td>{customer?.tags?.join(', ')}</td>}
|
|
</tr>)
|
|
}
|
|
</tbody>
|
|
</table>
|
|
</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">
|
|
{visibleCustomerTypeFilter && <div className="me-4">
|
|
<div className="field-label">Customer Type</div>
|
|
<select value={customerTypeFilter} onChange={(e) => setCustomerTypeFilter(e.currentTarget.value)}>
|
|
<option value="">All</option>
|
|
{getOptionsFromEnum(CUSTOMER_TYPE, CUSTOMER_TYPE_TEXT).filter((item) => !HIDDEN_CUSTOMER_TYPE_FILTER_VALUES.includes(item.value)).map((item) => (
|
|
<option key={item.value} value={item.value}>{item.label}</option>
|
|
))}
|
|
</select>
|
|
</div>}
|
|
{visibleProgramTypeFilter && <div className="me-4">
|
|
<div className="field-label">Program Type</div>
|
|
<select value={programTypeFilter} onChange={(e) => setProgramTypeFilter(e.currentTarget.value)}>
|
|
<option value="">All</option>
|
|
{getOptionsFromEnum(PROGRAM_TYPE, PROGRAM_TYPE_TEXT).map((item) => (
|
|
<option key={item.value} value={item.value}>{item.label}</option>
|
|
))}
|
|
</select>
|
|
</div>}
|
|
</div>
|
|
<div className="app-main-content-fields-section margin-sm dropdown-container">
|
|
{visiblePaySourceFilter && <div className="me-4">
|
|
<div className="field-label">Pay Source</div>
|
|
<select value={paySourceFilter} onChange={(e) => setPaySourceFilter(e.currentTarget.value)}>
|
|
<option value="">All</option>
|
|
{getOptionsFromEnum(PAY_SOURCE, PAY_SOURCE_TEXT).map((item) => (
|
|
<option key={item.value} value={item.value}>{item.label}</option>
|
|
))}
|
|
</select>
|
|
</div>}
|
|
</div>
|
|
<div className="app-main-content-fields-section margin-sm dropdown-container">
|
|
{visibleEyesOnFilter && <div className="me-4">
|
|
<div className="field-label">Eyes-On</div>
|
|
<select value={eyesOnFilter} onChange={(e) => setEyesOnFilter(e.currentTarget.value)}>
|
|
<option value="">All</option>
|
|
{getOptionsFromEnum(YES_NO, YES_NO_TEXT).map((item) => (
|
|
<option key={item.value} value={item.value}>{item.label}</option>
|
|
))}
|
|
</select>
|
|
</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>
|
|
);
|
|
},
|
|
);
|
|
|
|
return (
|
|
<>
|
|
{showSpinner && <div className="spinner-overlay">
|
|
<Spinner animation="border" role="status">
|
|
<span className="visually-hidden">Loading...</span>
|
|
</Spinner>
|
|
</div>}
|
|
{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' : ''} ${isAllCustomersPage ? 'all-customers-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="Discharged 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)} />
|
|
{hasVisibleFilters && <Dropdown
|
|
key={'filter-customers'}
|
|
id="filter-customers"
|
|
className="me-2"
|
|
show={showFilterDropdown}
|
|
onToggle={(isOpen) => {
|
|
if (isOpen) {
|
|
setShowManageTableDropdown(false);
|
|
setShowExportDropdown(false);
|
|
}
|
|
setShowFilterDropdown(isOpen);
|
|
}}
|
|
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}
|
|
show={showManageTableDropdown}
|
|
onToggle={(isOpen) => {
|
|
if (isOpen) {
|
|
setShowFilterDropdown(false);
|
|
setShowExportDropdown(false);
|
|
}
|
|
setShowManageTableDropdown(isOpen);
|
|
}}
|
|
/>
|
|
{typeof additionalButtons === 'function'
|
|
? additionalButtons({
|
|
showExportDropdown,
|
|
onExportToggle: (isOpen) => {
|
|
if (isOpen) {
|
|
setShowFilterDropdown(false);
|
|
setShowManageTableDropdown(false);
|
|
}
|
|
setShowExportDropdown(isOpen);
|
|
}
|
|
})
|
|
: additionalButtons
|
|
}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<Modal show={showAvatarModal} onHide={closeAvatarModal} centered>
|
|
<Modal.Header closeButton>
|
|
<Modal.Title style={{ fontSize: '16px' }}>{avatarCustomerName}</Modal.Title>
|
|
</Modal.Header>
|
|
<Modal.Body style={{ display: 'flex', justifyContent: 'center', alignItems: 'center', minHeight: '200px' }}>
|
|
{avatarLoading && <Spinner animation="border" variant="primary" />}
|
|
{!avatarLoading && avatarData && (
|
|
<img
|
|
src={`data:image/png;base64, ${avatarData}`}
|
|
alt={avatarCustomerName}
|
|
style={{ maxWidth: '100%', maxHeight: '400px', objectFit: 'contain' }}
|
|
/>
|
|
)}
|
|
{!avatarLoading && !avatarData && (
|
|
<div style={{ textAlign: 'center', color: '#999' }}>No profile picture available</div>
|
|
)}
|
|
</Modal.Body>
|
|
</Modal>
|
|
</>
|
|
)
|
|
};
|
|
|
|
export default DashboardCustomersList;
|