diff --git a/client/src/components/customers/CustomersList.js b/client/src/components/customers/CustomersList.js index eaabce1..dc62b22 100644 --- a/client/src/components/customers/CustomersList.js +++ b/client/src/components/customers/CustomersList.js @@ -1,97 +1,26 @@ -import React, {useState, useEffect} from "react"; +import React from "react"; import { useNavigate } from "react-router-dom"; -import { AuthService, CustomerService } from "../../services"; +import { AuthService } from "../../services"; import { Export } from "../../shared"; import { Plus } from "react-bootstrap-icons"; import DashboardCustomersList from "../dashboard/DashboardCustomersList"; const CustomersList = () => { const navigate = useNavigate(); - const [customers, setCustomers] = useState([]); - 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', - health_condition: 'medicalInsurance', - payment_status: 'careServices', - payment_due_date: 'careServices', - service_requirement: 'careServices', - 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 [columns] = 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: '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(() => { - 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`); - 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); - setCustomers(customerData); - }); - }, [navigate]); const goToCreateNew = () => { navigate(`/customers`); } - const additionalButtons = ({ showExportDropdown, onExportToggle }) => ( + const additionalButtons = ({ showExportDropdown, onExportToggle, exportColumns = [], exportData = [] }) => ( <> {AuthService.canCreateCustomer() && } {AuthService.canExportCustomerReport() && ( ({ - ...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(', ') - }))} + columns={exportColumns} + data={exportData} filename="customers" show={showExportDropdown} onToggle={onExportToggle} diff --git a/client/src/components/dashboard/DashboardCustomersList.js b/client/src/components/dashboard/DashboardCustomersList.js index 873024f..2b572b2 100644 --- a/client/src/components/dashboard/DashboardCustomersList.js +++ b/client/src/components/dashboard/DashboardCustomersList.js @@ -3,31 +3,151 @@ 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 { + CUSTOMER_TYPE, + CUSTOMER_TYPE_TEXT, + PROGRAM_TYPE, + PROGRAM_TYPE_TEXT, + PAY_SOURCE, + PAY_SOURCE_TEXT, + LEGAL_SEX_TEXT, + MARITAL_STATUS_TEXT, + IMMIGRATION_STATUS_TEXT, + LANGUAGE_OPTIONS, + DAYS_OF_WEEK_OPTIONS, + REFERRAL_SOURCE_TEXT, + CUSTOMER_DISCHARGE_REASON_TEXT, + DIETARY_RESTRICTIONS_OPTIONS, + DIET_TEXTURE_TEXT, + TRANSPORTATION_TYPE_TEXT, + PREFERRED_TEXT_LANGUAGE_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 DISCHARGE_ONLY_COLUMN_KEYS = new Set(['discharge_date', 'discharge_by', 'discharge_reason']); + const CUSTOMER_COLUMNS = [ + { key: 'customer_name', label: 'Customer Name', show: true }, + { key: 'customer_type', label: 'Customer Type', show: true }, + { key: 'program_type_display', label: 'Program Type', show: true }, + { key: 'pay_source_display', label: 'Pay Source', show: true }, + { key: 'date_of_birth', label: 'Date Of Birth', show: true }, + { key: 'legal_sex_display', label: 'Legal Sex', show: true }, + { key: 'marital_status_display', label: 'Marital Status', show: true }, + { key: 'marriage_date', label: 'Marriage Date', show: true }, + { key: 'immigration_status_display', label: 'Immigration Status', show: true }, + { key: 'language_spoken_display', label: 'Language Spoken', show: true }, + { key: 'phone_number', label: 'Phone Number', show: true }, + { key: 'email', label: 'Email', show: true }, + { key: 'address_display', label: 'Address', show: true }, + { key: 'emergency_contact_display', label: 'Emergency Contact', show: true }, + { key: 'admission_date', label: 'Admission Date', show: true }, + { key: 'days_of_week_display', label: 'Days of Week', show: true }, + { key: 'admission_date_duplicate', label: 'Admission Date', show: true }, + { key: 'enrolled_date', label: 'Enrolled Date', show: true }, + { key: 'created_by', label: 'Created By', show: true }, + { key: 'referral_source_display', label: 'Referral Source', show: true }, + { key: 'discharge_date', label: 'Discharge Date', show: true }, + { key: 'discharge_by', label: 'Discharge By', show: true }, + { key: 'discharge_reason', label: 'Discharge Reason', show: true }, + { key: 'notes_and_attachments', label: 'Notes And Attachments', show: true }, + { key: 'notes_for_driver', label: 'Notes for Driver', show: true }, + { key: 'dietary_restrictions_display', label: 'Dietary Restrictions', show: true }, + { key: 'diet_texture_display', label: 'Diet Texture', show: true }, + { key: 'meal_type', label: 'Meal Type', show: true }, + { key: 'table_number', label: 'Table Number', show: true }, + { key: 'seat_number', label: 'Seat Number', show: true }, + { key: 'transportation_type_display', label: 'Transportation Type', show: true }, + { key: 'consent_to_text_messages_display', label: 'Consent To Text Messages', show: true }, + { key: 'preferred_text_language_display', label: 'Preferred Text Language', show: true }, + { key: 'consent_to_media_use_display', label: 'Consent to Media Use', show: true }, + { key: 'primary_care_physician_display', label: 'Primary Care Physician', show: true }, + { key: 'pharmacy_name', label: 'Pharmacy Name', show: true }, + { key: 'pharmacy_id', label: 'Pharmacy ID', show: true }, + { key: 'diabetes_mellitus_display', label: 'Diabetes Mellitus', show: true }, + { key: 'eyes_on_display', label: 'Eyes-On', show: true }, + { key: 'wheelchair_display', label: 'Wheelchair', show: true }, + { key: 'molst', label: 'MOLST', show: true }, + { key: 'provisions_for_advance_medical', label: 'Provisions for Advance Medical', show: true }, + { key: 'hospice_display', label: 'Hospice', show: true }, + { key: 'burial_arrangements', label: 'Burial Arrangements', show: true }, + { key: 'power_of_attorney', label: 'Power of Attorney', show: true }, + { key: 'requires_rounding_display', label: 'Requires Rounding', show: true }, + { key: 'rounding_notes', label: 'Rounding Notes', show: true }, + { key: 'adcaps_completed_date', label: 'Adcaps Completed Date', show: true }, + { key: 'center_qualification_renew_date', label: 'Center Qualification Renew Date', show: true }, + { key: 'medicaid_renew_date', label: 'Medicaid Renew Date', show: true }, + { key: 'id_expiration_date', label: 'ID Expiration Date', show: true }, + { key: 'medicare_number', label: 'Medicare Number', show: true }, + { key: 'medicaid_number', label: 'Medicaid Number', show: true }, + { key: 'social_security_number', label: 'Social Security Number', show: true }, + { key: 'adcaps_id', label: 'Adcaps ID', show: true } + ]; const CUSTOMER_LIST_COLUMN_TAB_MAP = { - name: 'personalInfo', - chinese_name: 'personalInfo', + customer_name: 'personalInfo', + customer_type: 'careServices', + program_type_display: 'personalInfo', + pay_source_display: 'personalInfo', + date_of_birth: 'personalInfo', + legal_sex_display: 'personalInfo', + marital_status_display: 'personalInfo', + marriage_date: 'personalInfo', + immigration_status_display: 'personalInfo', + language_spoken_display: 'personalInfo', + phone_number: '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' + address_display: 'personalInfo', + emergency_contact_display: 'personalInfo', + admission_date: 'careServices', + days_of_week_display: 'careServices', + admission_date_duplicate: 'careServices', + enrolled_date: 'careServices', + created_by: 'careServices', + referral_source_display: 'careServices', + discharge_date: 'careServices', + discharge_by: 'careServices', + discharge_reason: 'careServices', + notes_and_attachments: 'careServices', + notes_for_driver: 'careServices', + dietary_restrictions_display: 'careServices', + diet_texture_display: 'careServices', + meal_type: 'careServices', + table_number: 'careServices', + seat_number: 'careServices', + transportation_type_display: 'careServices', + consent_to_text_messages_display: 'careServices', + preferred_text_language_display: 'careServices', + consent_to_media_use_display: 'careServices', + primary_care_physician_display: 'medicalInsurance', + pharmacy_name: 'medicalInsurance', + pharmacy_id: 'medicalInsurance', + diabetes_mellitus_display: 'medicalInsurance', + eyes_on_display: 'medicalInsurance', + wheelchair_display: 'medicalInsurance', + molst: 'medicalInsurance', + provisions_for_advance_medical: 'medicalInsurance', + hospice_display: 'medicalInsurance', + burial_arrangements: 'medicalInsurance', + power_of_attorney: 'medicalInsurance', + requires_rounding_display: 'medicalInsurance', + rounding_notes: 'medicalInsurance', + adcaps_completed_date: 'confidentialDetails', + center_qualification_renew_date: 'confidentialDetails', + medicaid_renew_date: 'confidentialDetails', + id_expiration_date: 'confidentialDetails', + medicare_number: 'confidentialDetails', + medicaid_number: 'confidentialDetails', + social_security_number: 'confidentialDetails', + adcaps_id: 'confidentialDetails' }; const getVisibleColumnsByPermission = (columnList = []) => { return columnList.filter((column) => { - if (column.key === 'name') return true; + if (column.key === 'customer_name') return true; const mappedTab = CUSTOMER_LIST_COLUMN_TAB_MAP[column.key]; if (!mappedTab) return true; return AuthService.canViewCustomerTab(mappedTab); @@ -74,82 +194,115 @@ const DashboardCustomersList = ({ additionalButtons, showBreadcrumb = false, tit const [customerAvatars, setCustomerAvatars] = useState({}); const isAllCustomersPage = title === 'All Customers'; const customerTabOrder = ['personalInfo', 'careServices', 'medicalInsurance', 'confidentialDetails', 'formSubmission']; - 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 - } - ])); + const [columns, setColumns] = useState(getVisibleColumnsByPermission(CUSTOMER_COLUMNS)); useEffect(() => { setColumns((prevColumns) => getVisibleColumnsByPermission(prevColumns)); }, []); + const toDisplayText = (value) => { + if (value === undefined || value === null) return ''; + const normalized = `${value}`.trim(); + if (!normalized) return ''; + const lowered = normalized.toLowerCase(); + if (lowered === 'yes') return 'Yes'; + if (lowered === 'no') return 'No'; + return normalized; + }; + + const getOptionLabel = (options = [], value = '') => { + const normalizedValue = `${value || ''}`.trim().toLowerCase(); + const option = options.find((item) => `${item?.value || ''}`.trim().toLowerCase() === normalizedValue); + return option?.label || `${value || ''}`; + }; + + const formatAddress = (customer) => { + const modernAddress = [ + customer?.address_line_1, + customer?.address_line_2, + customer?.city, + customer?.state, + customer?.zip_code + ].filter(Boolean).join(', '); + return modernAddress || customer?.address1 || customer?.address2 || customer?.address3 || customer?.address4 || customer?.address5 || ''; + }; + + const formatEmergencyContact = (customer) => { + const modernContact = [customer?.emergency_contact_name, customer?.emergency_contact_phone].filter(Boolean).join(' - '); + return modernContact || customer?.emergency_contact || customer?.emergency_contact2 || ''; + }; + + const formatCustomerRecord = (customer) => { + const paySource = customer?.pay_source || ''; + const paySourceLabel = PAY_SOURCE_TEXT[paySource] || paySource; + const paySourceDisplay = paySource === 'other' && customer?.pay_source_other + ? `${paySourceLabel} (${customer.pay_source_other})` + : paySourceLabel; + + const immigrationStatus = customer?.immigration_status || ''; + const immigrationStatusLabel = IMMIGRATION_STATUS_TEXT[immigrationStatus] || immigrationStatus; + const immigrationStatusDisplay = immigrationStatus === 'other' && customer?.immigration_status_other + ? `${immigrationStatusLabel} (${customer.immigration_status_other})` + : immigrationStatusLabel; + + const referralSource = customer?.referral_source || ''; + const referralSourceLabel = REFERRAL_SOURCE_TEXT[referralSource] || referralSource; + const referralSourceDisplay = referralSource === 'other' && customer?.referral_source_other + ? `${referralSourceLabel} (${customer.referral_source_other})` + : referralSourceLabel; + + const dischargeReason = customer?.discharge_reason || ''; + const dischargeReasonLabel = CUSTOMER_DISCHARGE_REASON_TEXT[dischargeReason] || dischargeReason; + const dischargeReasonDisplay = dischargeReason === 'other' && customer?.discharge_reason_other + ? `${dischargeReasonLabel} (${customer.discharge_reason_other})` + : dischargeReasonLabel; + + return { + ...customer, + customer_name: customer?.name || '', + customer_type: CUSTOMER_TYPE_TEXT[customer?.type] || customer?.type || '', + program_type_display: PROGRAM_TYPE_TEXT[customer?.program_type || customer?.program] || customer?.program_type || customer?.program || '', + pay_source_display: paySourceDisplay, + date_of_birth: customer?.birth_date || '', + legal_sex_display: LEGAL_SEX_TEXT[customer?.legal_sex] || customer?.legal_sex || customer?.gender || '', + marital_status_display: MARITAL_STATUS_TEXT[customer?.marital_status] || customer?.marital_status || '', + marriage_date: customer?.marriage_date || '', + immigration_status_display: immigrationStatusDisplay, + language_spoken_display: Array.isArray(customer?.language_spoken) && customer.language_spoken.length > 0 + ? customer.language_spoken.map((lang) => getOptionLabel(LANGUAGE_OPTIONS, lang)).join(', ') + : (customer?.language || ''), + phone_number: customer?.phone || customer?.home_phone || customer?.mobile_phone || '', + address_display: formatAddress(customer), + emergency_contact_display: formatEmergencyContact(customer), + admission_date: customer?.admission_date || '', + days_of_week_display: Array.isArray(customer?.days_of_week) ? customer.days_of_week.map((day) => getOptionLabel(DAYS_OF_WEEK_OPTIONS, day)).join(', ') : '', + admission_date_duplicate: customer?.admission_date || '', + enrolled_date: customer?.enrolled_date || '', + created_by: customer?.create_by || '', + referral_source_display: referralSourceDisplay, + discharge_reason: dischargeReasonDisplay, + notes_and_attachments: customer?.note || '', + notes_for_driver: customer?.notes_for_driver || '', + dietary_restrictions_display: Array.isArray(customer?.dietary_restrictions) + ? customer.dietary_restrictions.map((item) => getOptionLabel(DIETARY_RESTRICTIONS_OPTIONS, item)).join(', ') + : '', + diet_texture_display: DIET_TEXTURE_TEXT[customer?.diet_texture] || customer?.diet_texture || '', + meal_type: customer?.meal_requirement || '', + table_number: customer?.table_id || '', + seat_number: customer?.seat_number || '', + transportation_type_display: TRANSPORTATION_TYPE_TEXT[customer?.transportation_type] || customer?.transportation_type || '', + consent_to_text_messages_display: YES_NO_TEXT[`${customer?.consent_to_text_messages || ''}`.toLowerCase()] || toDisplayText(customer?.consent_to_text_messages || ''), + preferred_text_language_display: PREFERRED_TEXT_LANGUAGE_TEXT[customer?.preferred_text_language] || customer?.preferred_text_language || '', + consent_to_media_use_display: YES_NO_TEXT[`${customer?.consent_to_media_use || ''}`.toLowerCase()] || toDisplayText(customer?.consent_to_media_use || ''), + primary_care_physician_display: customer?.primary_care_physician || customer?.care_provider || '', + pharmacy_name: customer?.pharmacy || '', + diabetes_mellitus_display: toDisplayText(customer?.diabetes_mellitus || ''), + eyes_on_display: YES_NO_TEXT[`${customer?.eyes_on || (customer?.disability ? YES_NO.YES : '')}`.toLowerCase()] || toDisplayText(customer?.eyes_on || (customer?.disability ? YES_NO.YES : '')), + wheelchair_display: toDisplayText(customer?.wheelchair || ''), + hospice_display: toDisplayText(customer?.hospice || ''), + requires_rounding_display: YES_NO_TEXT[`${customer?.requires_rounding || ''}`.toLowerCase()] || toDisplayText(customer?.requires_rounding || '') + }; + }; + useEffect(() => { if (!AuthService.canViewCustomers()) { @@ -159,12 +312,11 @@ const DashboardCustomersList = ({ additionalButtons, showBreadcrumb = false, tit } 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)); + setCustomers( + data.data + .map((item) => formatCustomerRecord(item)) + .sort((a, b) => a.lastname > b.lastname ? 1 : -1) + ); }).finally(() => { setShowSpinner(false); }); @@ -214,7 +366,7 @@ const DashboardCustomersList = ({ additionalButtons, showBreadcrumb = false, tit } if (programTypeFilter && visibleProgramTypeFilter) { - filtered = filtered.filter((item) => (item?.program || '') === programTypeFilter); + filtered = filtered.filter((item) => (item?.program_type || item?.program || '') === programTypeFilter); } if (paySourceFilter && visiblePaySourceFilter) { @@ -351,6 +503,10 @@ const DashboardCustomersList = ({ additionalButtons, showBreadcrumb = false, tit setAvatarCustomerName(''); } + const visibleTableColumns = columns.filter((column) => ( + column.show && (showInactive || !DISCHARGE_ONLY_COLUMN_KEYS.has(column.key)) + )); + const table =
No. { - columns.filter(col => col.show).map((column, index) => + visibleTableColumns.map((column, index) => {column.label} sortTableWithField(column.key)}> ) } @@ -375,49 +531,40 @@ const DashboardCustomersList = ({ additionalButtons, showBreadcrumb = false, tit { filteredCustomers.map((customer, index) => {index + 1} - {columns.find(col => col.key === 'name')?.show && -
- {AuthService.canViewCustomers() && ( - customerAvatars[customer.id] && customerAvatars[customer.id] !== false ? ( - {customer?.name} showProfilePicture(customer)} - style={{ - width: '64px', - height: '64px', - borderRadius: '8px', - objectFit: 'cover', - cursor: 'pointer', - flexShrink: 0 - }} - /> - ) : ( - showProfilePicture(customer)} - size={64} - className="clickable" - style={{ flexShrink: 0, color: '#ccc' }} - /> - ) - )} - {AuthService.canEditAnyCustomerTab() && goToEdit(customer?.id)} style={{ flexShrink: 0 }}>} - goToView(customer?.id)}>{customer?.name} -
- } - {columns.find(col => col.key === 'chinese_name')?.show && {customer?.name_cn}} - {columns.find(col => col.key === 'email')?.show && {customer?.email}} - {columns.find(col => col.key === 'type')?.show && {customer?.type}} - {columns.find(col => col.key === 'pickup_status')?.show && {customer?.pickup_status}} - {columns.find(col => col.key === 'birth_date')?.show && {customer?.birth_date}} - {columns.find(col => col.key === 'gender')?.show && {customer?.gender}} - {columns.find(col => col.key === 'language')?.show && {customer?.language}} - {columns.find(col => col.key === 'medicare_number')?.show && {customer?.medicare_number}} - {columns.find(col => col.key === 'medicaid_number')?.show && {customer?.medicaid_number}} - {columns.find(col => col.key === 'address')?.show && {customer?.address1 || customer?.address2 || customer?.address3 || customer?.address4 || customer?.address5}} - {columns.find(col => col.key === 'phone')?.show && {customer?.phone || customer?.home_phone || customer?.mobile_phone}} - {columns.find(col => col.key === 'emergency_contact')?.show && {customer?.emergency_contact}} - {columns.find(col => col.key === 'tags')?.show && {customer?.tags?.join(', ')}} + {visibleTableColumns.map((column) => ( + column.key === 'customer_name' ? ( + +
+ {AuthService.canViewCustomers() && ( + customerAvatars[customer.id] && customerAvatars[customer.id] !== false ? ( + {customer?.name} showProfilePicture(customer)} + style={{ + width: '64px', + height: '64px', + borderRadius: '8px', + objectFit: 'cover', + cursor: 'pointer', + flexShrink: 0 + }} + /> + ) : ( + showProfilePicture(customer)} + size={64} + className="clickable" + style={{ flexShrink: 0, color: '#ccc' }} + /> + ) + )} + {AuthService.canEditAnyCustomerTab() && goToEdit(customer?.id)} style={{ flexShrink: 0 }}>} + goToView(customer?.id)}>{customer?.customer_name} +
+ + ) : {customer?.[column.key] || ''} + ))} ) } @@ -540,7 +687,7 @@ const DashboardCustomersList = ({ additionalButtons, showBreadcrumb = false, tit } column.key !== 'pickup_status') : columns} + columns={showInactive ? columns : columns.filter((column) => !DISCHARGE_ONLY_COLUMN_KEYS.has(column.key))} onColumnsChange={handleColumnsChange} show={showManageTableDropdown} onToggle={(isOpen) => { @@ -554,6 +701,14 @@ const DashboardCustomersList = ({ additionalButtons, showBreadcrumb = false, tit {typeof additionalButtons === 'function' ? additionalButtons({ showExportDropdown, + exportColumns: visibleTableColumns, + exportData: filteredCustomers.map((customer) => { + const exportRecord = {}; + visibleTableColumns.forEach((column) => { + exportRecord[column.key] = customer?.[column.key] || ''; + }); + return exportRecord; + }), onExportToggle: (isOpen) => { if (isOpen) { setShowFilterDropdown(false); diff --git a/client/src/components/event-request/EventRequestList.js b/client/src/components/event-request/EventRequestList.js index 299ce51..ae3f068 100644 --- a/client/src/components/event-request/EventRequestList.js +++ b/client/src/components/event-request/EventRequestList.js @@ -21,11 +21,6 @@ const EventRequestList = () => { label:'Customer Name', show: true }, - { - key: 'resource_display', - label: 'Doctor', - show: true - }, { key: 'source', label: 'Request By', @@ -101,10 +96,26 @@ const EventRequestList = () => { }, [sorting]); const filterRequestsFun = (item, statusParam, keywordParam) => { - - return (item?.customer_display?.toLowerCase()?.includes(keywordParam?.toLowerCase()) || item?.source?.toLowerCase()?.includes(keywordParam?.toLowerCase()) || - item?.resource_display?.toLowerCase()?.includes(keywordParam?.toLowerCase())) && item?.status === statusParam - + const normalizedKeyword = `${keywordParam || ''}`.toLowerCase().trim(); + const statusLabel = getStatusLabel(item?.status || ''); + const completeBy = item?.edit_history?.[item?.edit_history?.length - 1]?.employee || ''; + if (!normalizedKeyword) { + return item?.status === statusParam; + } + return ( + item?.status === statusParam && + ( + item?.customer_display?.toLowerCase()?.includes(normalizedKeyword) || + item?.source?.toLowerCase()?.includes(normalizedKeyword) || + item?.type?.toLowerCase()?.includes(normalizedKeyword) || + item?.transportation?.toLowerCase()?.includes(normalizedKeyword) || + item?.symptom?.toLowerCase()?.includes(normalizedKeyword) || + item?.np?.toLowerCase()?.includes(normalizedKeyword) || + item?.upload?.toLowerCase()?.includes(normalizedKeyword) || + statusLabel.toLowerCase().includes(normalizedKeyword) || + completeBy.toLowerCase().includes(normalizedKeyword) + ) + ); } const redirectToAdmin = () => { @@ -254,6 +265,16 @@ const EventRequestList = () => { } const getStatusLabel = (status) => status === 'active' ? 'In Progress' : (status==='done' ? 'Done' : 'Deleted') + const activeTabStatus = showDone ? 'done' : 'active'; + const filteredEventRequests = eventRequests.filter((item) => filterRequestsFun(item, activeTabStatus, keyword)); + const exportColumns = showDone ? [...columns, { key: 'complete_by', label: 'Complete By', show: true }] : columns; + const exportData = filteredEventRequests.map((eventRequest) => ({ + ...eventRequest, + create_date: eventRequest?.create_date ? new Date(eventRequest.create_date).toLocaleDateString() : '', + status: getStatusLabel(eventRequest?.status), + source: EventRequestsService.sourceList.find((item) => item?.value === eventRequest?.source)?.label || eventRequest?.source, + complete_by: eventRequest?.edit_history && eventRequest?.edit_history[eventRequest?.edit_history?.length - 1]?.employee || '' + })); const table = (statusParam, keywordParam) =>
@@ -267,7 +288,7 @@ const EventRequestList = () => { {column.label} sortTableWithField(column.key)}> ) } - Completed By + {statusParam === 'done' && Complete By} Comments Comment on the record @@ -280,7 +301,6 @@ const EventRequestList = () => { toggleItem(eventRequest?.id)}/> {index + 1} {columns.find(col => col.key === 'customer_display')?.show && {eventRequest?.customer_display}} - {columns.find(col => col.key === 'resource_display')?.show && {eventRequest?.resource_display}} {columns.find(col => col.key === 'source')?.show && {EventRequestsService.sourceList.find((item) => item?.value === eventRequest?.source)?.label || eventRequest?.source}} {columns.find(col => col.key === 'type')?.show && {eventRequest?.type}} {columns.find(col => col.key === 'transportation')?.show && {eventRequest?.transportation || '-'}} @@ -289,7 +309,7 @@ const EventRequestList = () => { {columns.find(col => col.key === 'upload')?.show && {eventRequest?.upload}} {columns.find(col => col.key === 'status')?.show && {getStatusLabel(eventRequest?.status)}} {columns.find(col => col.key === 'create_date')?.show && {new Date(eventRequest?.create_date).toLocaleDateString()}} - { eventRequest.status === 'done' && eventRequest?.edit_history && eventRequest?.edit_history[eventRequest?.edit_history?.length - 1]?.employee || ''} + {statusParam === 'done' && {eventRequest?.edit_history && eventRequest?.edit_history[eventRequest?.edit_history?.length - 1]?.employee || ''}} {eventRequest?.notes?.map((note) => { return

{`${note?.author}: ${note?.content}`}

})} @@ -340,8 +360,8 @@ const EventRequestList = () => { {AuthService.canEditAppointmentRequests() && } filterRequestsFun(item, showDone ? 'done': 'active', keyword))} + columns={exportColumns} + data={exportData} filename="event-requests" />
diff --git a/client/src/components/events/EventsList.js b/client/src/components/events/EventsList.js index b1e5200..b2ffc5e 100644 --- a/client/src/components/events/EventsList.js +++ b/client/src/components/events/EventsList.js @@ -485,6 +485,14 @@ const EventsList = () => {
; + const exportColumns = columns.filter((column) => column.show); + const exportData = events + .filter(event => event.status === (showDeletedItems ? 'inactive' : 'active')) + .map((event) => ({ + ...event, + customer: `${event?.customer || ''}${event?.chinese_name ? ` (${event.chinese_name})` : ''}` + })); + return ( <> @@ -531,8 +539,8 @@ const EventsList = () => { {AuthService.canEditMedicalEvents() && } {AuthService.canEditDriverAssignment() && ( event.status === (showDeletedItems ? 'inactive' : 'active'))} + columns={exportColumns} + data={exportData} filename="events" /> )} diff --git a/client/src/components/events/EventsMultipleList.js b/client/src/components/events/EventsMultipleList.js index b4412f0..230dfa0 100644 --- a/client/src/components/events/EventsMultipleList.js +++ b/client/src/components/events/EventsMultipleList.js @@ -6,6 +6,7 @@ import Select from 'react-select'; import { Spinner, Breadcrumb, BreadcrumbItem, Tabs, Tab, Button, Modal, DropdownButton, Dropdown } from "react-bootstrap"; import { Columns, Download, Filter, PencilSquare, PersonSquare, Plus } from "react-bootstrap-icons"; import { ManageTable, Export } from "../../shared/components"; +import { CUSTOMER_TYPE_TEXT, PROGRAM_TYPE_TEXT, PAY_SOURCE_TEXT } from "../../shared/constants/customer.constant"; const EventsMultipleList = () => { @@ -24,72 +25,138 @@ const EventsMultipleList = () => { const [showFilterDropdown, setShowFilterDropdown] = useState(false); const [columns, setColumns] = useState([ { - key: 'customer', - label:'Customer', + key: 'customer_name', + label:'Customer Name (Full Name + Preferred Name)', show: true }, { - key: 'chinese_name', - label: 'Preferred Name', + key: 'customer_type', + label: 'Customer Type', show: true }, { - key: 'member_type', - label: 'Member Type', + key: 'program_type', + label: 'Program Type', show: true }, { - key: 'eyes_on', - label: 'Eyes-on', + key: 'pay_source', + label: 'Pay Source', show: true }, { - key: 'doctor', - label: 'Doctor', + key: 'dob', + label: 'Date Of Birth', show: true }, { - key: 'phone', - label: 'Phone', + key: 'language_support', + label: 'Language Support', show: true }, { - key: 'address', - label: 'Address', + key: 'transportation_support', + label: 'Transportation Support', show: true }, { - key: 'translation', - label: 'Translation', - show: true - }, - { - key: 'newPatient', - label: 'New Patient', - show: true - }, - { - key: 'needId', - label: 'Need ID', - show: true - }, - { - key: 'startTime', - label: 'Start Time', - show: true - }, - { - key: 'transportation', + key: 'driver', label: 'Driver', show: true }, { - key: 'fasting', - label: 'Fasting', + key: 'appointment_time', + label: 'Appointment Time', + show: true + }, + { + key: 'eyes_on', + label: 'Eyes-On', + show: true + }, + { + key: 'new_patient', + label: 'New Patient', + show: true + }, + { + key: 'doctor_order', + label: "Doctor's Order", + show: true + }, + { + key: 'fasting_required', + label: 'Fasting Required', + show: true + }, + { + key: 'id_needed', + label: 'ID Needed', + show: true + }, + { + key: 'need_medication_list', + label: 'Need Medication List', + show: true + }, + { + key: 'provider', + label: 'Provider', + show: true + }, + { + key: 'provider_phone_number', + label: 'Provider Phone Number', + show: true + }, + { + key: 'provider_address', + label: 'Provider Address', + show: true + }, + { + key: 'notes', + label: 'Notes', + show: true + }, + { + key: 'reason', + label: 'Reason', + show: true + }, + { + key: 'requirement', + label: 'Requirement', show: true } ]); + const toDisplayText = (value) => { + if (value === undefined || value === null) return ''; + const normalized = `${value}`.trim(); + if (!normalized) return ''; + const lowered = normalized.toLowerCase(); + if (lowered === 'yes') return 'Yes'; + if (lowered === 'no') return 'No'; + return normalized; + }; + + const getOptionLabel = (options = [], value = '') => { + const normalizedValue = `${value || ''}`.trim().toLowerCase(); + const option = options.find((item) => `${item?.value || ''}`.trim().toLowerCase() === normalizedValue); + return option?.label || `${value || ''}`; + }; + + const getCustomerPaySource = (customer) => { + if (!customer) return ''; + const paySource = customer?.pay_source || ''; + const paySourceLabel = PAY_SOURCE_TEXT[paySource] || paySource; + if (paySource === 'other' && customer?.pay_source_other) { + return `${paySourceLabel} (${customer.pay_source_other})`; + } + return paySourceLabel; + }; + const checkDisability = (customers, event) => { const currentCustomer = customers?.find(c => c?.id === event?.data?.customer || c?.name === event?.data?.client_name || c?.name === event?.target_name); return currentCustomer?.disability || event?.data?.disability?.toLowerCase() === 'yes' || false; @@ -113,6 +180,31 @@ const EventsMultipleList = () => { if (fromDate && toDate && customers?.length > 0 && resources?.length > 0) { EventsService.getAllEvents({ from: EventsService.formatDate(fromDate), to: EventsService.formatDate(toDate) }).then((data) => { setEvents(data.data.filter((item) => { + const currentCustomer = customers.find(c => c.id === item?.data?.customer) || customers?.find(c => c?.name === item?.data?.client_name || c?.name === item?.target_name); + const currentResource = resources.find(r => r.id === item?.data?.resource); + const customerName = item?.data?.customer ? (currentCustomer?.name || item?.data?.client_name || '') : (item?.data?.client_name || ''); + const preferredName = item?.data?.customer ? (currentCustomer?.name_cn || '') : (currentCustomer?.name_cn || ''); + item.customer_name = preferredName ? `${customerName} (${preferredName})` : customerName; + item.customer_type = CUSTOMER_TYPE_TEXT[currentCustomer?.type] || currentCustomer?.type || ''; + item.program_type = PROGRAM_TYPE_TEXT[currentCustomer?.program_type] || currentCustomer?.program_type || ''; + item.pay_source = getCustomerPaySource(currentCustomer); + item.dob = item?.data?.customer ? (currentCustomer?.birth_date || '') : (item?.data?.client_birth_date || ''); + item.language_support = getOptionLabel(EventsService.interpreterLevelOptions, item?.data?.interpreter || ''); + item.transportation_support = getOptionLabel(EventsService.transportationTypeOptions, item?.data?.trans_method || ''); + item.driver = item?.link_event_name || ''; + item.appointment_time = item?.start_time? `${EventsService.formatDate(new Date(item?.start_time))} ${new Date(item?.start_time).toLocaleTimeString('en-US', { hour: '2-digit', minute: 'numeric', hour12: true })}` : '' ; + item.eyes_on = checkDisability(customers, item) ? 'Yes' : 'No'; + item.new_patient = toDisplayText(item?.data?.new_patient || ''); + item.doctor_order = toDisplayText(item?.data?.doc_order || ''); + item.fasting_required = item?.data?.fasting || ''; + item.id_needed = toDisplayText(item?.data?.need_id || ''); + item.need_medication_list = toDisplayText(item?.data?.need_med_list || ''); + item.provider = item?.data?.resource ? (currentResource?.name || item?.data?.resource_name || '') : (item?.data?.resource_name || ''); + item.provider_phone_number = item?.data?.resource ? (currentResource?.phone || item?.data?.resource_phone || '') : (item?.data?.resource_phone || ''); + item.provider_address = item?.data?.resource ? (currentResource?.address || item?.data?.resource_address || '') : (item?.data?.resource_address || ''); + item.notes = item?.data?.notes || ''; + item.reason = item?.data?.reason || ''; + item.requirement = item?.data?.other || ''; item.customer = (item?.data?.customer ? (customers.find(c=>c.id === item?.data?.customer)?.name || item?.data?.client_name || '') : (item?.data?.client_name || '')); item.doctor = item?.data?.resource ? (resources.find(r=> r.id === item?.data?.resource)?.name || item?.data?.resource_name || '') : (item?.data?.resource_name || ''); item.phone = item?.data?.resource ? (resources.find(r=> r.id === item?.data?.resource)?.phone || item?.data?.resource_phone || '') : (item?.data?.resource_phone || ''); @@ -139,8 +231,8 @@ const EventsMultipleList = () => { useEffect(() => { setFilteredEvents(events?.filter((event) => { if (selectedResource && selectedResource.label !== '' && selectedResource.value !== '') { - if (selectedResource?.label?.replaceAll(' ', '').replaceAll('*', '').replaceAll('*', '').includes(event.doctor?.replaceAll(' ', '').replaceAll('*', '').replaceAll('*', '')) - || event.doctor?.replaceAll(' ', '').replaceAll('*', '').replaceAll('*', '').includes(selectedResource?.label?.replaceAll(' ', '').replaceAll('*', '').replaceAll('*', ''))) { + if (selectedResource?.label?.replaceAll(' ', '').replaceAll('*', '').replaceAll('*', '').includes(event.provider?.replaceAll(' ', '').replaceAll('*', '').replaceAll('*', '')) + || event.provider?.replaceAll(' ', '').replaceAll('*', '').replaceAll('*', '').includes(selectedResource?.label?.replaceAll(' ', '').replaceAll('*', '').replaceAll('*', ''))) { return event; } } else { @@ -148,7 +240,7 @@ const EventsMultipleList = () => { } }).filter((event => { if (selectedCustomer && selectedCustomer !== '' && selectedCustomer.value !== '') { - if (selectedCustomer?.label?.replaceAll(' ', '').replaceAll('(Eyes-on)', '').replaceAll('(discharged)', '').replaceAll('*', '').replaceAll('*', '') === (event.customer?.replaceAll(' ', '').replaceAll('(discharged)', '').replaceAll('(Eyes-on)', '').replaceAll('*', '').replaceAll('*', ''))) { + if ((event.customer_name || '').toLowerCase().includes((selectedCustomer?.label || '').toLowerCase())) { return event; } } else { @@ -174,6 +266,31 @@ const EventsMultipleList = () => { EventsService.updateEvent(id, {confirmed: true}).then(() => { EventsService.getAllEvents({ from: EventsService.formatDate(fromDate), to: EventsService.formatDate(toDate) }).then((data) => { setEvents(data.data.filter((item) => { + const currentCustomer = customers.find(c => c.id === item?.data?.customer) || customers?.find(c => c?.name === item?.data?.client_name || c?.name === item?.target_name); + const currentResource = resources.find(r => r.id === item?.data?.resource); + const customerName = item?.data?.customer ? (currentCustomer?.name || item?.data?.client_name || '') : (item?.data?.client_name || ''); + const preferredName = item?.data?.customer ? (currentCustomer?.name_cn || '') : (currentCustomer?.name_cn || ''); + item.customer_name = preferredName ? `${customerName} (${preferredName})` : customerName; + item.customer_type = CUSTOMER_TYPE_TEXT[currentCustomer?.type] || currentCustomer?.type || ''; + item.program_type = PROGRAM_TYPE_TEXT[currentCustomer?.program_type] || currentCustomer?.program_type || ''; + item.pay_source = getCustomerPaySource(currentCustomer); + item.dob = item?.data?.customer ? (currentCustomer?.birth_date || '') : (item?.data?.client_birth_date || ''); + item.language_support = getOptionLabel(EventsService.interpreterLevelOptions, item?.data?.interpreter || ''); + item.transportation_support = getOptionLabel(EventsService.transportationTypeOptions, item?.data?.trans_method || ''); + item.driver = item?.link_event_name || ''; + item.appointment_time = item?.start_time? `${EventsService.formatDate(new Date(item?.start_time))} ${new Date(item?.start_time).toLocaleTimeString('en-US', { hour: '2-digit', minute: 'numeric', hour12: true })}` : '' ; + item.eyes_on = checkDisability(customers, item) ? 'Yes' : 'No'; + item.new_patient = toDisplayText(item?.data?.new_patient || ''); + item.doctor_order = toDisplayText(item?.data?.doc_order || ''); + item.fasting_required = item?.data?.fasting || ''; + item.id_needed = toDisplayText(item?.data?.need_id || ''); + item.need_medication_list = toDisplayText(item?.data?.need_med_list || ''); + item.provider = item?.data?.resource ? (currentResource?.name || item?.data?.resource_name || '') : (item?.data?.resource_name || ''); + item.provider_phone_number = item?.data?.resource ? (currentResource?.phone || item?.data?.resource_phone || '') : (item?.data?.resource_phone || ''); + item.provider_address = item?.data?.resource ? (currentResource?.address || item?.data?.resource_address || '') : (item?.data?.resource_address || ''); + item.notes = item?.data?.notes || ''; + item.reason = item?.data?.reason || ''; + item.requirement = item?.data?.other || ''; item.customer = (item?.data?.customer ? (customers.find(c=>c.id === item?.data?.customer)?.name || item?.data?.client_name || '') : (item?.data?.client_name || '')); item.doctor = item?.data?.resource ? (resources.find(r=> r.id === item?.data?.resource)?.name || item?.data?.resource_name || '') : (item?.data?.resource_name || ''); item.phone = item?.data?.resource ? (resources.find(r=> r.id === item?.data?.resource)?.phone || item?.data?.resource_phone || '') : (item?.data?.resource_phone || ''); @@ -314,8 +431,6 @@ const EventsMultipleList = () => { {column.label} sortTableWithField(column.key)}> ) } - Customer Date of Birth - Transportation @@ -325,21 +440,27 @@ const EventsMultipleList = () => { filteredEvents && filteredEvents.filter(event => event.status === statusParam).map((medicalEvent, index) => toggleItem(medicalEvent?.id)}/> {index + 1} - {columns.find(col => col.key === 'customer')?.show && {medicalEvent?.customer}} - {columns.find(col => col.key === 'chinese_name')?.show && {medicalEvent?.chinese_name}} - {columns.find(col => col.key === 'member_type')?.show && {medicalEvent?.member_type}} + {columns.find(col => col.key === 'customer_name')?.show && {medicalEvent?.customer_name}} + {columns.find(col => col.key === 'customer_type')?.show && {medicalEvent?.customer_type}} + {columns.find(col => col.key === 'program_type')?.show && {medicalEvent?.program_type}} + {columns.find(col => col.key === 'pay_source')?.show && {medicalEvent?.pay_source}} + {columns.find(col => col.key === 'dob')?.show && {medicalEvent?.dob}} + {columns.find(col => col.key === 'language_support')?.show && {medicalEvent?.language_support}} + {columns.find(col => col.key === 'transportation_support')?.show && {medicalEvent?.transportation_support}} + {columns.find(col => col.key === 'driver')?.show && {medicalEvent?.driver}} + {columns.find(col => col.key === 'appointment_time')?.show && {medicalEvent?.appointment_time}} {columns.find(col => col.key === 'eyes_on')?.show && {medicalEvent?.eyes_on}} - {columns.find(col => col.key === 'doctor')?.show && {medicalEvent?.doctor}} - {columns.find(col => col.key === 'phone')?.show && {medicalEvent?.phone}} - {columns.find(col => col.key === 'address')?.show && {medicalEvent?.address}} - {columns.find(col => col.key === 'translation')?.show && {medicalEvent?.translation}} - {columns.find(col => col.key === 'newPatient')?.show && {medicalEvent?.newPatient}} - {columns.find(col => col.key === 'needId')?.show && {medicalEvent?.needId}} - {columns.find(col => col.key === 'startTime')?.show && {medicalEvent?.startTime}} - {columns.find(col => col.key === 'transportation')?.show && {medicalEvent?.transportation}} - {columns.find(col => col.key === 'fasting')?.show && {medicalEvent?.fasting}} - {medicalEvent?.dob} - {medicalEvent?.transMethod} + {columns.find(col => col.key === 'new_patient')?.show && {medicalEvent?.new_patient}} + {columns.find(col => col.key === 'doctor_order')?.show && {medicalEvent?.doctor_order}} + {columns.find(col => col.key === 'fasting_required')?.show && {medicalEvent?.fasting_required}} + {columns.find(col => col.key === 'id_needed')?.show && {medicalEvent?.id_needed}} + {columns.find(col => col.key === 'need_medication_list')?.show && {medicalEvent?.need_medication_list}} + {columns.find(col => col.key === 'provider')?.show && {medicalEvent?.provider}} + {columns.find(col => col.key === 'provider_phone_number')?.show && {medicalEvent?.provider_phone_number}} + {columns.find(col => col.key === 'provider_address')?.show && {medicalEvent?.provider_address}} + {columns.find(col => col.key === 'notes')?.show && {medicalEvent?.notes}} + {columns.find(col => col.key === 'reason')?.show && {medicalEvent?.reason}} + {columns.find(col => col.key === 'requirement')?.show && {medicalEvent?.requirement}} {medicalEvent?.confirmed ? 'Confirmed' : (AuthService.canEditMedicalEvents() ? : 'Pending')} ) } @@ -358,6 +479,17 @@ const EventsMultipleList = () => { setShowFilterDropdown(false); } + const exportColumns = columns.filter((column) => column.show); + const exportData = filteredEvents + .filter(event => event.status === (showDeletedItems ? 'inactive' : 'active')) + .map((event) => { + const row = {}; + exportColumns.forEach((column) => { + row[column.key] = event?.[column.key] || ''; + }); + return row; + }); + const customMenu = React.forwardRef( ({ children, style, className, 'aria-labelledby': labeledBy }, ref) => { @@ -415,7 +547,7 @@ const EventsMultipleList = () => {
-
Resource
+
Provider