This commit is contained in:
2026-02-09 16:51:12 -05:00
parent 5bda671a59
commit bddd0dbe87
12 changed files with 888 additions and 345 deletions

BIN
.DS_Store vendored

Binary file not shown.

View File

@@ -1,16 +1,16 @@
{
"files": {
"main.css": "/static/css/main.6d8e0960.css",
"main.js": "/static/js/main.34bdd0b7.js",
"main.js": "/static/js/main.dd937a88.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.6d8e0960.css.map": "/static/css/main.6d8e0960.css.map",
"main.34bdd0b7.js.map": "/static/js/main.34bdd0b7.js.map",
"main.dd937a88.js.map": "/static/js/main.dd937a88.js.map",
"787.c4e7f8f9.chunk.js.map": "/static/js/787.c4e7f8f9.chunk.js.map"
},
"entrypoints": [
"static/css/main.6d8e0960.css",
"static/js/main.34bdd0b7.js"
"static/js/main.dd937a88.js"
]
}

View File

@@ -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.34bdd0b7.js"></script><link href="/static/css/main.6d8e0960.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.dd937a88.js"></script><link href="/static/css/main.6d8e0960.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

File diff suppressed because one or more lines are too long

BIN
client/.DS_Store vendored

Binary file not shown.

View File

@@ -90,6 +90,25 @@ const EventsCalendar = () => {
const [newReminderTitleCategory, setNewReminderTitleCategory] = useState('');
const [newReminderAssociatedEntity, setNewReminderAssociatedEntity] = useState(null);
// Edit modal state (for non-medical tabs)
const [showEditModal, setShowEditModal] = useState(false);
const [editingEventId, setEditingEventId] = useState(null);
const [editEventTitle, setEditEventTitle] = useState('');
const [editEventStartDateTime, setEditEventStartDateTime] = useState(null);
const [editEventLocation, setEditEventLocation] = useState('');
const [editEventRecurring, setEditEventRecurring] = useState('');
// Activity edit fields
const [editActivityCategory, setEditActivityCategory] = useState('');
// Attendance edit fields
const [editAttendanceCustomer, setEditAttendanceCustomer] = useState(null);
const [editAttendanceReason, setEditAttendanceReason] = useState('');
// Meal Plan edit fields
const [editMealType, setEditMealType] = useState('');
const [editMealIngredients, setEditMealIngredients] = useState('');
// Important Dates edit fields
const [editReminderTitleCategory, setEditReminderTitleCategory] = useState('');
const [editReminderAssociatedEntity, setEditReminderAssociatedEntity] = useState(null);
// Helper function to format name from "lastname, firstname" to "firstname lastname"
const formatFullName = (name) => {
if (!name) return '';
@@ -458,6 +477,115 @@ const EventsCalendar = () => {
});
}
// Edit modal functions (for non-medical tabs)
const openEditModal = (calendarEvent) => {
const eventData = allEvents.find(e => e.id === calendarEvent.id) || calendarEvent;
setEditingEventId(eventData.id);
setEditEventStartDateTime(eventData.start_time ? new Date(eventData.start_time) : new Date());
if (currentTab === 'activitiesCalendar') {
setEditEventTitle(eventData.title || '');
setEditActivityCategory(eventData.activity_category || eventData.color || '');
setEditEventLocation(eventData.event_location || '');
setEditEventRecurring(eventData.rrule || '');
} else if (currentTab === 'incidentsCalendar') {
setEditAttendanceCustomer(eventData.target_uuid ? { value: eventData.target_uuid, label: eventData.target_name } : null);
setEditAttendanceReason(eventData.description || '');
} else if (currentTab === 'mealPlanCalendar') {
setEditEventTitle(eventData.title || '');
setEditMealType(eventData.meal_type || '');
setEditMealIngredients(eventData.ingredients || '');
setEditEventRecurring(eventData.rrule || '');
} else if (currentTab === 'reminderDatesCalendar') {
setEditReminderTitleCategory(eventData.event_reminder_type || '');
setEditReminderAssociatedEntity(eventData.target_uuid ? { value: eventData.target_uuid, label: eventData.target_name } : null);
setEditEventRecurring(eventData.rrule || '');
}
setShowEditModal(true);
};
const closeEditModal = () => {
setShowEditModal(false);
setEditingEventId(null);
setEditEventTitle('');
setEditEventStartDateTime(null);
setEditEventLocation('');
setEditEventRecurring('');
setEditActivityCategory('');
setEditAttendanceCustomer(null);
setEditAttendanceReason('');
setEditMealType('');
setEditMealIngredients('');
setEditReminderTitleCategory('');
setEditReminderAssociatedEntity(null);
};
const handleEditSave = () => {
const userName = localStorage.getItem('user') && JSON.parse(localStorage.getItem('user'))?.name;
let data = {
edit_by: userName,
edit_date: new Date(),
};
if (currentTab === 'activitiesCalendar') {
data = {
...data,
title: editEventTitle,
start_time: editEventStartDateTime,
stop_time: editEventStartDateTime,
activity_category: editActivityCategory,
color: editActivityCategory,
event_location: editEventLocation,
rrule: editEventRecurring || undefined,
};
} else if (currentTab === 'incidentsCalendar') {
data = {
...data,
title: editAttendanceCustomer?.label ? `Attendance Note - ${editAttendanceCustomer.label}` : 'Attendance Note',
start_time: editEventStartDateTime,
stop_time: editEventStartDateTime,
target_type: 'customer',
target_uuid: editAttendanceCustomer?.value,
target_name: editAttendanceCustomer?.label,
description: editAttendanceReason,
};
} else if (currentTab === 'mealPlanCalendar') {
data = {
...data,
title: editEventTitle,
start_time: editEventStartDateTime,
stop_time: editEventStartDateTime,
meal_type: editMealType,
color: editMealType === 'breakfast' ? 'brown' : (editMealType === 'lunch' ? 'green' : 'red'),
ingredients: editMealIngredients,
rrule: editEventRecurring || undefined,
};
} else if (currentTab === 'reminderDatesCalendar') {
const isMemberCategory = ['birthday', 'adcaps_completion', 'center_qualification_expiration'].includes(editReminderTitleCategory);
data = {
...data,
title: getReminderTitleLabel(editReminderTitleCategory),
start_time: editEventStartDateTime,
stop_time: editEventStartDateTime,
event_reminder_type: editReminderTitleCategory,
target_type: isMemberCategory ? 'customer' : 'vehicle',
target_uuid: editReminderAssociatedEntity?.value,
target_name: editReminderAssociatedEntity?.label,
rrule: editEventRecurring || undefined,
color: isMemberCategory ? 'member_related' : 'vehicle_maintenance',
};
}
EventsService.updateEvent(editingEventId, data).then(() => {
EventsService.getAllEvents({ from: EventsService.formatDate(fromDate), to: EventsService.formatDate(toDate) }).then((data) => {
setAllEvents(data.data);
closeEditModal();
});
});
};
const toggleColorFilter = (colorValue) => {
setSelectedColorFilters(prev =>
prev.includes(colorValue)
@@ -480,6 +608,13 @@ const EventsCalendar = () => {
setTargetedEventType(eventTypeMap[value]);
setCurrentTab(value);
setSelectedColorFilters([]);
// Dismiss any open calendar event tile popup via plugin API
try {
calendar?.config?.plugins?.eventModal?.close();
} catch (e) {
// Fallback: remove the modal element from DOM
document.querySelectorAll('.sx__event-modal').forEach(el => el.remove());
}
}
@@ -492,7 +627,7 @@ const EventsCalendar = () => {
<div className="sx__event-modal__time" style={{ display: 'flex', gap: '12px', marginTop: '8px' }}>
<PencilSquare
size={16}
onClick={() => currentTab === 'medicalCalendar' ? goToEdit(calendarEvent?.id) : goToEdit(calendarEvent?.id)}
onClick={() => currentTab === 'medicalCalendar' ? goToEdit(calendarEvent?.id) : openEditModal(calendarEvent)}
style={{ cursor: 'pointer' }}
title="Edit"
/>
@@ -1245,6 +1380,223 @@ const getReminderTitleLabel = (value) => {
</Button>
</Modal.Footer>
</Modal>
{/* Edit Event Modal (non-medical tabs) */}
<Modal show={showEditModal} onHide={closeEditModal} size="sm" dialogClassName="calendar-event-modal">
<Modal.Header closeButton>
<Modal.Title>
{currentTab === 'activitiesCalendar' && 'Edit Activity'}
{currentTab === 'incidentsCalendar' && 'Edit Attendance Note'}
{currentTab === 'mealPlanCalendar' && 'Edit Meal Item'}
{currentTab === 'reminderDatesCalendar' && 'Edit Important Date'}
</Modal.Title>
</Modal.Header>
<Modal.Body>
{/* Activity Edit Fields */}
{currentTab === 'activitiesCalendar' && (
<>
<div className="mb-3">
<div className="field-label">Activity Name<span className="required">*</span></div>
<input type="text" className="form-control" placeholder="Enter activity name" value={editEventTitle || ''} onChange={e => setEditEventTitle(e.target.value)}/>
</div>
<div className="mb-3">
<div className="field-label">Date & Time</div>
<DatePicker
className="form-control"
selected={editEventStartDateTime}
onChange={setEditEventStartDateTime}
showTimeSelect
timeFormat="HH:mm"
timeIntervals={15}
dateFormat="MM/dd/yyyy, HH:mm"
/>
</div>
<div className="mb-3">
<div className="field-label">Category<span className="required">*</span></div>
<select className="form-control" value={editActivityCategory} onChange={(e) => setEditActivityCategory(e.target.value)}>
<option value="">Select Category</option>
<option value="red">Classes</option>
<option value="pink">Games</option>
<option value="green">Events</option>
<option value="blue">Outings</option>
<option value="purple">Personal Care</option>
<option value="brown">Care Activities</option>
</select>
</div>
<div className="mb-3">
<div className="field-label">Location</div>
<input type="text" className="form-control" placeholder="Enter location" value={editEventLocation || ''} onChange={e => setEditEventLocation(e.target.value)}/>
</div>
<div className="mb-3">
<div className="field-label">Repeat</div>
<select className="form-control" value={editEventRecurring} onChange={(e) => setEditEventRecurring(e.target.value)}>
<option value="">No (One-time)</option>
<option value="FREQ=DAILY">Daily</option>
<option value="FREQ=WEEKLY">Weekly</option>
<option value="FREQ=MONTHLY">Monthly</option>
<option value="FREQ=YEARLY">Yearly</option>
</select>
</div>
</>
)}
{/* Attendance Note Edit Fields */}
{currentTab === 'incidentsCalendar' && (
<>
<div className="mb-3">
<div className="field-label">Customer Name<span className="required">*</span></div>
<Select
value={editAttendanceCustomer}
onChange={setEditAttendanceCustomer}
options={customers.filter(item => item?.status === 'active' && item?.type !== 'discharged').map(c => ({ value: c.id, label: c.name }))}
placeholder="Select Customer"
isClearable
/>
</div>
<div className="mb-3">
<div className="field-label">Date<span className="required">*</span></div>
<DatePicker
className="form-control"
selected={editEventStartDateTime}
onChange={setEditEventStartDateTime}
dateFormat="MM/dd/yyyy"
/>
</div>
<div className="mb-3">
<div className="field-label">Reason</div>
<input type="text" className="form-control" placeholder="Enter reason" value={editAttendanceReason || ''} onChange={e => setEditAttendanceReason(e.target.value)}/>
</div>
</>
)}
{/* Meal Item Edit Fields */}
{currentTab === 'mealPlanCalendar' && (
<>
<div className="mb-3">
<div className="field-label">Dish Name<span className="required">*</span></div>
<input type="text" className="form-control" placeholder="Enter dish name" value={editEventTitle || ''} onChange={e => setEditEventTitle(e.target.value)}/>
</div>
<div className="mb-3">
<div className="field-label">Meal Type<span className="required">*</span></div>
<select className="form-control" value={editMealType} onChange={(e) => setEditMealType(e.target.value)}>
<option value="">Select Meal Type</option>
<option value="breakfast">Breakfast</option>
<option value="lunch">Lunch</option>
<option value="snack">Snack</option>
</select>
</div>
<div className="mb-3">
<div className="field-label">Date</div>
<DatePicker
className="form-control"
selected={editEventStartDateTime}
onChange={setEditEventStartDateTime}
dateFormat="MM/dd/yyyy"
/>
</div>
<div className="mb-3">
<div className="field-label">Repeat</div>
<select className="form-control" value={editEventRecurring} onChange={(e) => setEditEventRecurring(e.target.value)}>
<option value="">No (One-time)</option>
<option value="FREQ=DAILY">Daily</option>
<option value="FREQ=WEEKLY">Weekly</option>
<option value="FREQ=MONTHLY">Monthly</option>
</select>
</div>
<div className="mb-3">
<div className="field-label">Ingredients</div>
<input type="text" className="form-control" placeholder="Enter ingredients" value={editMealIngredients || ''} onChange={e => setEditMealIngredients(e.target.value)}/>
</div>
</>
)}
{/* Important Dates Edit Fields */}
{currentTab === 'reminderDatesCalendar' && (
<>
<div className="mb-3">
<div className="field-label">Title<span className="required">*</span></div>
<select className="form-control" value={editReminderTitleCategory} onChange={(e) => {
setEditReminderTitleCategory(e.target.value);
setEditReminderAssociatedEntity(null);
}}>
<option value="">Select Title</option>
<optgroup label="Member">
<option value="adcaps_completion">ADCAPS Completion</option>
<option value="center_qualification_expiration">Center Qualification Expiration</option>
</optgroup>
<optgroup label="Vehicle">
<option value="oil_change">Oil Change</option>
<option value="monthly_vehicle_inspection">Monthly Vehicle Inspection</option>
<option value="emissions_inspection">Emissions Inspection</option>
<option value="insurance_expiration">Insurance Expiration</option>
<option value="license_plate_expiration">License Plate Expiration</option>
</optgroup>
</select>
</div>
{editReminderTitleCategory && (
<div className="mb-3">
<div className="field-label">
{['birthday', 'adcaps_completion', 'center_qualification_expiration'].includes(editReminderTitleCategory)
? 'Associated Person'
: 'Associated Vehicle'}<span className="required">*</span>
</div>
<Select
value={editReminderAssociatedEntity}
onChange={setEditReminderAssociatedEntity}
options={
['birthday', 'adcaps_completion', 'center_qualification_expiration'].includes(editReminderTitleCategory)
? customers.filter(item => item?.status === 'active' && item?.type !== 'discharged').map(c => ({ value: c.id, label: c.name }))
: vehicles.map(v => ({ value: v.id, label: v.vehicle_number }))
}
placeholder={['birthday', 'adcaps_completion', 'center_qualification_expiration'].includes(editReminderTitleCategory) ? 'Select Person' : 'Select Vehicle'}
isClearable
/>
</div>
)}
<div className="mb-3">
<div className="field-label">Date</div>
<DatePicker
className="form-control"
selected={editEventStartDateTime}
onChange={setEditEventStartDateTime}
dateFormat="MM/dd/yyyy"
/>
</div>
<div className="mb-3">
<div className="field-label">Category</div>
<div className="field-value" style={{ padding: '8px 0', color: '#666' }}>
{editReminderTitleCategory
? (['birthday', 'adcaps_completion', 'center_qualification_expiration'].includes(editReminderTitleCategory) ? 'Member Related' : 'Vehicle Related')
: '-'}
</div>
</div>
<div className="mb-3">
<div className="field-label">Repeat Cycle</div>
<select className="form-control" value={editEventRecurring} onChange={(e) => setEditEventRecurring(e.target.value)}>
<option value="">No (One-time)</option>
<option value="FREQ=DAILY">Daily</option>
<option value="FREQ=WEEKLY">Weekly</option>
<option value="FREQ=MONTHLY">Monthly</option>
<option value="FREQ=YEARLY">Yearly</option>
</select>
</div>
</>
)}
</Modal.Body>
<Modal.Footer>
<Button variant="secondary" size="sm" onClick={closeEditModal}>
Cancel
</Button>
<Button variant="primary" size="sm" onClick={handleEditSave} disabled={
(currentTab === 'activitiesCalendar' && (!editEventTitle || !editActivityCategory)) ||
(currentTab === 'incidentsCalendar' && (!editAttendanceCustomer || !editEventStartDateTime)) ||
(currentTab === 'mealPlanCalendar' && (!editEventTitle || !editMealType)) ||
(currentTab === 'reminderDatesCalendar' && (!editReminderTitleCategory || !editReminderAssociatedEntity))
}>
Save
</Button>
</Modal.Footer>
</Modal>
</div>
</div>
</>

View File

@@ -29,7 +29,7 @@ import {
YES_NO, YES_NO_TEXT,
PREFERRED_TEXT_LANGUAGE, PREFERRED_TEXT_LANGUAGE_TEXT
} from "../../shared";
import { Upload } from "react-bootstrap-icons";
import { Upload, BoxArrowRight } from "react-bootstrap-icons";
const UpdateCustomer = () => {
const navigate = useNavigate();
@@ -87,6 +87,13 @@ const UpdateCustomer = () => {
const [dischargeReason, setDischargeReason] = useState('');
const [dischargeReasonOther, setDischargeReasonOther] = useState('');
// Discharge confirmation modal
const [showDischargeConfirmModal, setShowDischargeConfirmModal] = useState(false);
const [dischargeConfirmDate, setDischargeConfirmDate] = useState(new Date());
const [dischargeConfirmReason, setDischargeConfirmReason] = useState('');
const [dischargeConfirmReasonOther, setDischargeConfirmReasonOther] = useState('');
const [isDischarging, setIsDischarging] = useState(false);
// Care & Services
const [dietaryRestrictions, setDietaryRestrictions] = useState([]);
const [dietaryRestrictionsOther, setDietaryRestrictionsOther] = useState('');
@@ -209,6 +216,55 @@ const UpdateCustomer = () => {
currentCustomer?.type !== 'deceased';
};
// Discharge confirmation modal handlers
const openDischargeConfirmModal = () => {
setDischargeConfirmDate(new Date());
setDischargeConfirmReason('');
setDischargeConfirmReasonOther('');
setShowDischargeConfirmModal(true);
};
const closeDischargeConfirmModal = () => {
setShowDischargeConfirmModal(false);
setDischargeConfirmDate(new Date());
setDischargeConfirmReason('');
setDischargeConfirmReasonOther('');
};
const handleDischarge = async () => {
if (!dischargeConfirmDate || !dischargeConfirmReason) {
alert('Please fill in Discharge Date and Discharge Reason.');
return;
}
setIsDischarging(true);
const currentUserName = localStorage.getItem('user') ? JSON.parse(localStorage.getItem('user'))?.name : '';
const dischargeData = {
status: 'inactive',
type: 'discharged',
discharge_date: formatDateForBackend(dischargeConfirmDate),
discharge_reason: dischargeConfirmReason,
discharge_reason_other: dischargeConfirmReasonOther,
discharge_by: currentUserName,
edit_by: currentUserName,
edit_date: new Date()
};
try {
await CustomerService.updateCustomer(currentCustomer.id, dischargeData);
closeDischargeConfirmModal();
alert('Customer has been discharged successfully.');
navigate(`/customers/${currentCustomer.id}`);
} catch (error) {
console.error('Error discharging customer:', error);
alert('Error discharging customer. Please try again.');
} finally {
setIsDischarging(false);
}
};
const onPharmacyChange = (selectedPharmacy) => {
setPharmacy(selectedPharmacy);
setPharmacyId(selectedPharmacy?.value);
@@ -1177,6 +1233,13 @@ const UpdateCustomer = () => {
)}
</div>
)}
{isCustomerActive() && (
<div style={{ marginTop: '16px' }}>
<button className="btn btn-warning btn-sm" onClick={openDischargeConfirmModal}>
<BoxArrowRight className="me-2" size={16}></BoxArrowRight>Discharge Customer
</button>
</div>
)}
<div className="list row mb-5">
<div className="col-md-12 col-sm-12 col-xs-12">
@@ -1639,6 +1702,64 @@ const UpdateCustomer = () => {
</Button>
</Modal.Footer>
</Modal>
{/* Discharge Confirmation Modal */}
<Modal show={showDischargeConfirmModal} onHide={closeDischargeConfirmModal}>
<Modal.Header closeButton>
<Modal.Title>Discharge Customer</Modal.Title>
</Modal.Header>
<Modal.Body>
<p className="text-muted mb-3">
Are you sure you want to discharge <strong>{currentCustomer?.firstname} {currentCustomer?.lastname}</strong>?
This will set the customer's status to inactive.
</p>
<div className="mb-3">
<label className="form-label">Discharge Date<span className="text-danger">*</span></label>
<DatePicker
selected={dischargeConfirmDate}
onChange={(date) => setDischargeConfirmDate(date)}
dateFormat="MM/dd/yyyy"
placeholderText="MM/DD/YYYY"
className="form-control"
/>
</div>
<div className="mb-3">
<label className="form-label">Discharge Reason<span className="text-danger">*</span></label>
<select className="form-control" value={dischargeConfirmReason} onChange={e => setDischargeConfirmReason(e.target.value)}>
<option value="">Select...</option>
{Object.keys(CUSTOMER_DISCHARGE_REASON).map(key => (
<option key={key} value={CUSTOMER_DISCHARGE_REASON[key]}>
{CUSTOMER_DISCHARGE_REASON_TEXT[CUSTOMER_DISCHARGE_REASON[key]]}
</option>
))}
</select>
</div>
{dischargeConfirmReason === 'other' && (
<div className="mb-3">
<label className="form-label">Discharge Reason (Other)</label>
<input
type="text"
className="form-control"
placeholder="Please specify..."
value={dischargeConfirmReasonOther}
onChange={e => setDischargeConfirmReasonOther(e.target.value)}
/>
</div>
)}
</Modal.Body>
<Modal.Footer>
<Button variant="secondary" onClick={closeDischargeConfirmModal}>
Cancel
</Button>
<Button
variant="warning"
onClick={handleDischarge}
disabled={isDischarging || !dischargeConfirmDate || !dischargeConfirmReason}
>
{isDischarging ? 'Discharging...' : 'Confirm Discharge'}
</Button>
</Modal.Footer>
</Modal>
</>
);
};

View File

@@ -1,9 +1,8 @@
import React, {useState, useEffect} from "react";
import { PencilSquare, BoxArrowRight } from "react-bootstrap-icons";
import { PencilSquare } from "react-bootstrap-icons";
import { useNavigate, useParams, useSearchParams } from "react-router-dom";
import { AuthService, CustomerService } from "../../services";
import { Spinner, Breadcrumb, BreadcrumbItem, Tabs, Tab, Modal, Button } from "react-bootstrap";
import DatePicker from "react-datepicker";
import { Spinner, Breadcrumb, BreadcrumbItem, Tabs, Tab } from "react-bootstrap";
import {
CUSTOMER_TYPE_TEXT,
PROGRAM_TYPE_TEXT,
@@ -17,7 +16,6 @@ import {
EMERGENCY_CONTACT_ROLE_OPTIONS,
DAYS_OF_WEEK_OPTIONS,
REFERRAL_SOURCE_TEXT,
CUSTOMER_DISCHARGE_REASON,
CUSTOMER_DISCHARGE_REASON_TEXT,
DIETARY_RESTRICTIONS_OPTIONS,
DIET_TEXTURE_TEXT,
@@ -35,12 +33,6 @@ const ViewCustomer = () => {
const [currentAvatar, setCurrentAvatar] = useState(undefined);
const [activeTab, setActiveTab] = useState(searchParams.get('tab') || 'personalInfo');
// Discharge modal state
const [showDischargeModal, setShowDischargeModal] = useState(false);
const [dischargeDate, setDischargeDate] = useState(new Date());
const [dischargeReason, setDischargeReason] = useState('');
const [dischargeReasonOther, setDischargeReasonOther] = useState('');
const [isDischarging, setIsDischarging] = useState(false);
const redirectTo = () => {
navigate(`/customers/list`)
@@ -58,68 +50,6 @@ const ViewCustomer = () => {
currentCustomer?.type !== 'deceased';
};
// Handle discharge modal
const openDischargeModal = () => {
setDischargeDate(new Date());
setDischargeReason('');
setDischargeReasonOther('');
setShowDischargeModal(true);
};
const closeDischargeModal = () => {
setShowDischargeModal(false);
setDischargeDate(new Date());
setDischargeReason('');
setDischargeReasonOther('');
};
// Format date for backend
const formatDateForBackend = (date) => {
if (!date) return '';
const d = new Date(date);
const month = String(d.getMonth() + 1).padStart(2, '0');
const day = String(d.getDate()).padStart(2, '0');
const year = d.getFullYear();
return `${month}/${day}/${year}`;
};
// Handle discharge save
const handleDischarge = async () => {
if (!dischargeDate || !dischargeReason) {
alert('Please fill in Discharge Date and Discharge Reason.');
return;
}
setIsDischarging(true);
const currentUserName = localStorage.getItem('user') ? JSON.parse(localStorage.getItem('user'))?.name : '';
const dischargeData = {
status: 'inactive',
type: 'discharged',
discharge_date: formatDateForBackend(dischargeDate),
discharge_reason: dischargeReason,
discharge_reason_other: dischargeReasonOther,
discharge_by: currentUserName,
edit_by: currentUserName,
edit_date: new Date()
};
try {
await CustomerService.updateCustomer(currentCustomer.id, dischargeData);
// Reload customer data
const updatedData = await CustomerService.getCustomer(currentCustomer.id);
setCurrentCustomer(updatedData.data);
closeDischargeModal();
alert('Customer has been discharged successfully.');
} catch (error) {
console.error('Error discharging customer:', error);
alert('Error discharging customer. Please try again.');
} finally {
setIsDischarging(false);
}
};
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.')
@@ -795,72 +725,9 @@ const ViewCustomer = () => {
</Tabs>
<div className="list-func-panel">
<button className="btn btn-primary me-2" onClick={() => goToEdit(currentCustomer?.id)}><PencilSquare className="me-2" size={16}></PencilSquare>Edit</button>
{isCustomerActive() && (
<button className="btn btn-warning" onClick={openDischargeModal}>
<BoxArrowRight className="me-2" size={16}></BoxArrowRight>Discharge Customer
</button>
)}
</div>
</div>
</div>
{/* Discharge Modal */}
<Modal show={showDischargeModal} onHide={closeDischargeModal}>
<Modal.Header closeButton>
<Modal.Title>Discharge Customer</Modal.Title>
</Modal.Header>
<Modal.Body>
<p className="text-muted mb-3">
Are you sure you want to discharge <strong>{currentCustomer?.firstname} {currentCustomer?.lastname}</strong>?
This will set the customer's status to inactive.
</p>
<div className="mb-3">
<label className="form-label">Discharge Date<span className="text-danger">*</span></label>
<DatePicker
selected={dischargeDate}
onChange={(date) => setDischargeDate(date)}
dateFormat="MM/dd/yyyy"
placeholderText="MM/DD/YYYY"
className="form-control"
/>
</div>
<div className="mb-3">
<label className="form-label">Discharge Reason<span className="text-danger">*</span></label>
<select className="form-control" value={dischargeReason} onChange={e => setDischargeReason(e.target.value)}>
<option value="">Select...</option>
{Object.keys(CUSTOMER_DISCHARGE_REASON).map(key => (
<option key={key} value={CUSTOMER_DISCHARGE_REASON[key]}>
{CUSTOMER_DISCHARGE_REASON_TEXT[CUSTOMER_DISCHARGE_REASON[key]]}
</option>
))}
</select>
</div>
{dischargeReason === 'other' && (
<div className="mb-3">
<label className="form-label">Discharge Reason (Other)</label>
<input
type="text"
className="form-control"
placeholder="Please specify..."
value={dischargeReasonOther}
onChange={e => setDischargeReasonOther(e.target.value)}
/>
</div>
)}
</Modal.Body>
<Modal.Footer>
<Button variant="secondary" onClick={closeDischargeModal}>
Cancel
</Button>
<Button
variant="warning"
onClick={handleDischarge}
disabled={isDischarging || !dischargeDate || !dischargeReason}
>
{isDischarging ? 'Discharging...' : 'Confirm Discharge'}
</Button>
</Modal.Footer>
</Modal>
</>
);
};

View File

@@ -4,7 +4,7 @@ import { useNavigate, useParams } from "react-router-dom";
import { customerSlice } from "./../../store";
import { AuthService, CustomerService, EventsService, LabelService } from "../../services";
import { CUSTOMER_TYPE, ManageTable } from "../../shared";
import { Spinner, Breadcrumb, BreadcrumbItem, Tabs, Tab, Dropdown } from "react-bootstrap";
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 }) => {
@@ -27,6 +27,10 @@ const DashboardCustomersList = ({ additionalButtons, showBreadcrumb = false, tit
const [serviceRequirementFilter, setServiceRequirementFilter] = useState('');
const [tagsFilter, setTagsFilter] = useState([]);
const [availableLabels, setAvailableLabels] = useState([]);
const [showAvatarModal, setShowAvatarModal] = useState(false);
const [avatarData, setAvatarData] = useState(null);
const [avatarCustomerName, setAvatarCustomerName] = useState('');
const [avatarLoading, setAvatarLoading] = useState(false);
const [columns, setColumns] = useState([
{
key: 'name',
@@ -271,6 +275,25 @@ const DashboardCustomersList = ({ additionalButtons, showBreadcrumb = false, tit
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'}}>
<table className="personnel-info-table">
@@ -290,7 +313,7 @@ const DashboardCustomersList = ({ additionalButtons, showBreadcrumb = false, tit
filteredCustomers.map((customer, index) => <tr key={customer.id}>
<td className="td-checkbox"><input type="checkbox" checked={selectedItems.includes(customer.id)} onClick={()=>toggleItem(customer?.id)}/></td>
<td className="td-index">{index + 1}</td>
{columns.find(col => col.key === 'name')?.show && <td> {AuthService.canAddOrEditCustomers() && <PencilSquare size={16} className="clickable me-2" onClick={() => goToEdit(customer?.id)}></PencilSquare>} {AuthService.canViewCustomers() && <PersonSquare onClick={() => goToView(customer?.id)} size={16} className="clickable me-2" />} {customer?.name}</td>}
{columns.find(col => col.key === 'name')?.show && <td> {AuthService.canAddOrEditCustomers() && <PencilSquare size={16} className="clickable me-2" onClick={() => goToView(customer?.id)}></PencilSquare>} {AuthService.canViewCustomers() && <PersonSquare onClick={() => showProfilePicture(customer)} size={16} className="clickable me-2" />} {customer?.name}</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>}
@@ -467,6 +490,24 @@ const DashboardCustomersList = ({ additionalButtons, showBreadcrumb = false, tit
</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>
</>
)
};

View File

@@ -2,15 +2,11 @@ import React, {useState, useEffect, useRef} from "react";
import { useNavigate } from "react-router-dom";
import { AuthService, EventsService, CustomerService, ResourceService } from "../../services";
import moment from 'moment';
import { Spinner, Breadcrumb, BreadcrumbItem, Tabs, Tab, Button, Modal, Dropdown } from "react-bootstrap";
import { Breadcrumb, Tabs, Tab, Button, Modal, Dropdown } from "react-bootstrap";
import { useCalendarApp, ScheduleXCalendar } from '@schedule-x/react';
import {
viewWeek,
viewDay,
viewMonthGrid,
viewMonthAgenda,
createViewDay,
createViewMonthAgenda,
createViewWeek,
createViewMonthGrid
} from '@schedule-x/calendar';
@@ -18,7 +14,7 @@ import { createEventsServicePlugin } from '@schedule-x/events-service';
import { createEventModalPlugin} from '@schedule-x/event-modal';
import '@schedule-x/theme-default/dist/calendar.css';
import { Archive, PencilSquare, Filter } from "react-bootstrap-icons";
// import { Scheduler } from "@aldabil/react-scheduler";
import DatePicker from "react-datepicker";
const EventsCalendar = () => {
@@ -26,6 +22,7 @@ const EventsCalendar = () => {
const [events, setEvents] = useState([]);
const calendarColumnRef = useRef(null);
const [listHeight, setListHeight] = useState(null);
const [allEvents, setAllEvents] = useState([]);
const [customers, setCustomers] = useState([]);
const [resources, setResources] = useState([]);
const [fromDate, setFromDate] = useState(new Date(new Date().getFullYear(), new Date().getMonth(), 1));
@@ -34,6 +31,7 @@ const EventsCalendar = () => {
const [currentTotalTranslate2, setCurrentTotalTranslate2] = useState(0);
const [currentTotalResource, setCurrentTotalResource] = useState(0);
const [showDeletedItems, setShowDeletedItems] = useState(false);
const [selectedColorFilters, setSelectedColorFilters] = useState([]);
const [timeData, setTimeData] = useState([]);
const [showFilterDropdown, setShowFilterDropdown] = useState(false);
const eventsServicePlugin = createEventsServicePlugin();
@@ -41,6 +39,20 @@ const EventsCalendar = () => {
const [groupedEvents, setGroupedEvents] = useState(new Map());
const [currentRangeStart, setCurrentRangeStart] = useState(null);
const [currentRangeEnd, setCurrentRangeEnd] = useState(null);
const [showCreationModal, setShowCreationModal] = useState(false);
const [newEventStartDateTime, setNewEventStartDateTime] = useState(new Date());
const [newEventEndDateTime, setNewEventEndDateTime] = useState(new Date());
const [newEventColor, setNewEventColor] = useState('');
// Medical appointment specific fields
const [newEventCustomer, setNewEventCustomer] = useState('');
const [newEventResource, setNewEventResource] = useState('');
const [newEventInterpreter, setNewEventInterpreter] = useState('');
const [newEventFasting, setNewEventFasting] = useState('');
const [newEventNeedId, setNewEventNeedId] = useState('');
const [newEventNewPatient, setNewEventNewPatient] = useState('');
const [newEventDisability, setNewEventDisability] = useState('');
const [newEventTransMethod, setNewEventTransMethod] = useState('');
// Helper function to format name from "lastname, firstname" to "firstname lastname"
const formatFullName = (name) => {
@@ -52,36 +64,21 @@ const EventsCalendar = () => {
return name;
};
// Helper function to get shortened name
const getShortenedName = (name) => {
if (!name) return '';
const fullName = formatFullName(name);
const parts = fullName.split(' ');
if (parts.length >= 2) {
return `${parts[0]} ${parts[parts.length - 1].charAt(0)}`; // FirstName L
}
return fullName;
};
// Helper function to format event title - now uses full name, CSS handles overflow
const formatEventTitle = (customerName, startTime) => {
const fullName = formatFullName(customerName);
// Return full name - CSS will handle truncation with text-overflow: ellipsis
return fullName;
};
// Get full name for description/tooltip
const getEventDescription = (customerName, doctorName) => {
const fullName = formatFullName(customerName);
return `${fullName} - ${doctorName}`;
return doctorName ? `${fullName} - ${doctorName}` : fullName;
};
const calendar = useCalendarApp({
views: [createViewMonthGrid(), createViewDay(), createViewMonthAgenda(), createViewWeek()],
views: [createViewMonthGrid(), createViewDay(), createViewWeek()],
monthGridOptions: {
/**
* Number of events to display in a day cell before the "+ N events" button is shown
* */
nEventsPerDay: 50,
},
defaultView: viewMonthGrid.name,
@@ -91,25 +88,34 @@ const EventsCalendar = () => {
plugins: [eventModalService, eventsServicePlugin],
callbacks: {
onRangeUpdate(range) {
console.log('new calendar range start date', range.start);
console.log('new calendar range end date', range.end);
setCurrentRangeStart(range.start);
setCurrentRangeEnd(range.end);
// Update fromDate/toDate for API fetching based on the range
const startDate = new Date(range.start);
const endDate = new Date(range.end);
setFromDate(new Date(startDate.getFullYear(), startDate.getMonth(), 1));
setToDate(new Date(endDate.getFullYear(), endDate.getMonth() + 1, 0));
},
onClickDate(date) {
// Parse as local date to avoid UTC timezone offset shifting the day
const [y, m, d] = date.split('-').map(Number);
const localDate = new Date(y, m - 1, d);
// Default to 10:00 AM
localDate.setHours(10, 0, 0, 0);
setNewEventStartDateTime(localDate);
setNewEventEndDateTime(localDate);
setShowCreationModal(true);
},
onClickDateTime(dateTime) {
setNewEventStartDateTime(new Date(dateTime.replace(' ', 'T')));
setNewEventEndDateTime(new Date(dateTime.replace(' ', 'T')));
setShowCreationModal(true);
}
}
})
});
// Filter events based on current calendar range
const getFilteredEvents = () => {
console.log("EventsCalendar - range:", currentRangeStart, "to", currentRangeEnd, "events:", events.length);
if (!currentRangeStart || !currentRangeEnd) {
// If no range set yet, show all events for current month
const now = moment();
return events.filter(event => {
const eventDate = moment(event.start_time);
@@ -117,7 +123,6 @@ const EventsCalendar = () => {
});
}
// Filter events within the calendar's visible range
const rangeStart = moment(currentRangeStart);
const rangeEnd = moment(currentRangeEnd);
@@ -141,7 +146,7 @@ const EventsCalendar = () => {
}
}
return eventsDateMap;
}
};
useEffect(() => {
@@ -161,10 +166,14 @@ const EventsCalendar = () => {
});
}, []);
useEffect(() => {
EventsService.getAllEvents({ from: EventsService.formatDate(fromDate), to: EventsService.formatDate(toDate) }).then(data => setAllEvents(data?.data));
}, [fromDate, toDate]);
useEffect(() => {
if (customers?.length > 0 && resources.length > 0) {
EventsService.getAllEvents({ from: EventsService.formatDate(fromDate), to: EventsService.formatDate(toDate) }).then((data) => {
setEvents(data.data.filter((item) => {
const orignialEvents = [...allEvents];
setEvents(orignialEvents?.filter(item => item.type === 'medical')?.map((item) => {
const customerField = item?.data?.customer ? (customers?.find(c => c.id === item?.data?.customer)?.name || item?.data?.client_name || '') : (item?.data?.client_name || '');
const doctorField = item?.data?.resource ? ((resources?.find(r => r.id === item?.data?.resource))?.name || item?.data?.resource_name || '') : (item?.data?.resource_name || '');
item.event_id = item.id;
@@ -177,15 +186,15 @@ const EventsCalendar = () => {
item.newPatient = item?.data?.new_patient || '';
item.needId = item?.data?.need_id || '';
item.disability = item?.data?.disability || '';
item.startTime = item?.start_time? `${moment(new Date(item?.start_time)).format('hh:mm A')}` : '' ;
item.endTime = item?.start_time? `${moment(new Date(item?.end_time)).format('hh:mm A')}` : '' ;
item.startTime = item?.start_time? `${moment(new Date(item?.start_time)).format('YYYY-MM-DD HH:mm')}` : '' ;
item.endTime = item?.start_time? `${moment(new Date(item?.end_time)).format('YYYY-MM-DD HH:mm')}` : '' ;
item.fasting = item?.data?.fasting || '';
item.transportation = item?.link_event_name || '';
item.title = formatEventTitle(customerField, item?.start_time);
item.description = getEventDescription(customerField, doctorField); // Full info for tooltip
item.start = item?.start_time? `${moment(new Date(item?.start_time)).format('YYYY-MM-DD HH:mm')}` : '';
item.end = item.stop_time? `${moment(new Date(item?.stop_time)).format('YYYY-MM-DD HH:mm')}` : '';
const transportationInfo = EventsService.getTransportationInfo(data.data, item, timeData);
item.description = getEventDescription(customerField, doctorField);
item.start = item?.start_time? `${moment(new Date(item?.start_time)).format('YYYY-MM-DD HH:mm')}` : `${moment().format('YYYY-MM-DD HH:mm')}`;
item.end = item?.stop_time? `${moment(new Date(item?.stop_time)).format('YYYY-MM-DD HH:mm')}` : (item?.start_time? `${moment(item?.start_time).format('YYYY-MM-DD HH:mm')}` : `${moment().format('YYYY-MM-DD HH:mm')}`);
const transportationInfo = EventsService.getTransportationInfo(allEvents, item, timeData);
const { isFutureEvent, maxTranslate1, maxTranslate2, maxResource, totalTranslate1, totalTranslate2, totalResource} = transportationInfo;
item.color = item?.color;
item._options = { additionalClasses: [`event-${item?.color || 'primary'}`]};
@@ -200,15 +209,21 @@ const EventsCalendar = () => {
item.totalResource = totalResource;
setCurrentTotalResource(item.totalResource);
return item;
}).filter(item => item.type === 'medical')?.filter(item => (!showDeletedItems && item.status === 'active') || showDeletedItems));
})}
}, [fromDate, toDate, customers, resources, timeData, showDeletedItems]);
})?.filter(item => (!showDeletedItems && item.status === 'active') || showDeletedItems)
?.filter(item => {
if (selectedColorFilters.length === 0) return true;
if (selectedColorFilters.includes(item.color)) return true;
// When "Drop-Off Only" (purple) is selected, also show events with no label
if (selectedColorFilters.includes('purple') && !item.color) return true;
return false;
}));
}
}, [customers, resources, timeData, allEvents, showDeletedItems, selectedColorFilters])
useEffect(() => {
if (events && calendar) {
console.log("EventsCalendar useEffect - events:", events.length, "range:", currentRangeStart, "to", currentRangeEnd);
calendar?.eventsService?.set(events);
setGroupedEvents(getGroupedEvents());
}
@@ -223,10 +238,7 @@ const EventsCalendar = () => {
}
};
// Initial measurement after render
const timer = setTimeout(updateListHeight, 100);
// Update on window resize
window.addEventListener('resize', updateListHeight);
return () => {
@@ -236,7 +248,6 @@ const EventsCalendar = () => {
}, [events]);
const redirectToAdmin = () => {
navigate(`/medical`)
}
@@ -257,59 +268,29 @@ const EventsCalendar = () => {
navigate(`/medical/events/multiple-list`)
}
const goToView = (id) => {
navigate(`/medical/events/${id}`)
}
const disableEvent = (id) => {
const currentEvent = events.find(item => item.id === id);
EventsService.disableEvent(id, { status: 'inactive', edit_by: localStorage.getItem('user') && JSON.parse(localStorage.getItem('user'))?.name,
edit_date: new Date(),
edit_history: currentEvent?.edit_history? [...currentEvent.edit_history, { employee: localStorage.getItem('user') && JSON.parse(localStorage.getItem('user'))?.name, date: new Date() }] : [{ employee: localStorage.getItem('user') && JSON.parse(localStorage.getItem('user'))?.name, date: new Date() }]}).then(() => {
EventsService.getAllEvents({ from: EventsService.formatDate(fromDate), to: EventsService.formatDate(toDate) }).then((data) => {
setEvents(data.data.filter((item) => {
const customerField = item?.data?.customer ? (customers?.find(c => c.id === item?.data?.customer)?.name || item?.data?.client_name || '') : (item?.data?.client_name || '');
const doctorField = item?.data?.resource ? ((resources?.find(r => r.id === item?.data?.resource))?.name || item?.data?.resource_name || '') : (item?.data?.resource_name || '');
item.event_id = item.id;
item.customer = customerField;
item.doctor = doctorField;
item.phone = item?.data?.resource ? ((resources?.find(r => r.id === item?.data?.resource))?.phone || item?.data?.resource_phone || '') : (item?.data?.resource_phone || '');
item.contact = item?.data?.resource? ((resources?.find(r => r.id === item?.data?.resource))?.contact || item?.data?.resource_contact || '') : (item?.data?.resource_contact || '')
item.address = item?.data?.resource ? ((resources?.find(r => r.id === item?.data?.resource))?.address || item?.data?.resource_address || '') : (item?.data?.resource_address || '');
item.translation = item?.data?.interpreter || '';
item.newPatient = item?.data?.new_patient || '';
item.needId = item?.data?.need_id || '';
item.disability = item?.data?.disability || '';
item.startTime = item?.start_time? `${moment(new Date(item?.start_time)).format('hh:mm A')}` : '' ;
item.endTime = item?.start_time? `${moment(new Date(item?.end_time)).format('hh:mm A')}` : '' ;
item.fasting = item?.data?.fasting || '';
item.transportation = item?.link_event_name || '';
item.title = formatEventTitle(customerField, item?.start_time);
item.start = item?.start_time? `${moment(new Date(item?.start_time)).format('YYYY-MM-DD HH:mm')}` : '';
item.end = item.stop_time? `${moment(new Date(item?.stop_time)).format('YYYY-MM-DD HH:mm')}` : '';
item._options = { additionalClasses: [`event-${item?.color || 'primary'}`]};
const transportationInfo = EventsService.getTransportationInfo(data.data, item, timeData);
const { isFutureEvent, maxTranslate1, maxTranslate2, maxResource, totalTranslate1, totalTranslate2, totalResource} = transportationInfo;
item.color = item?.color;
item.showWarnings = isFutureEvent;
item.maxTranslate1 = maxTranslate1;
item.maxTranslate2 = maxTranslate2;
item.maxResource = maxResource;
item.totalTranslate1 = totalTranslate1;
setCurrentTotalTranslate1(item.totalTranslate1);
item.totalTranslate2 = totalTranslate2;
setCurrentTotalTranslate2(item.totalTranslate2);
item.totalResource = totalResource;
setCurrentTotalResource(item.totalResource);
return item;
}).filter(item => item.type === 'medical' && ((!showDeletedItems && item.status === 'active') || showDeletedItems)));
setAllEvents(data?.data);
})
});
}
const toggleColorFilter = (colorValue) => {
setSelectedColorFilters(prev =>
prev.includes(colorValue)
? prev.filter(c => c !== colorValue)
: [...prev, colorValue]
);
};
const FilterAndClose = () => {
setShowFilterDropdown(false);
}
@@ -317,18 +298,29 @@ const EventsCalendar = () => {
const cleanFilterAndClose = () => {
setShowFilterDropdown(false);
setShowDeletedItems(false);
setSelectedColorFilters([]);
}
const customComponents = {
eventModal: ({calendarEvent}) => {
return <>
<div className="sx__event-modal__title">{calendarEvent?.customer}</div>
<div className="sx__event-modal__time">{`${calendarEvent?.doctor}`}</div>
<div className="sx__event-modal__time">{`${calendarEvent?.startTime}`}</div>
<div className="sx__event-modal__time">
<PencilSquare size={16} onClick={() => goToEdit(calendarEvent?.id)} className="me-4"></PencilSquare>
<Archive size={16} onClick={() =>{disableEvent(calendarEvent?.id)}}></Archive> </div>
{calendarEvent?.doctor && <div className="sx__event-modal__time">{`${calendarEvent?.doctor}`}</div>}
<div className="sx__event-modal__time">{`${calendarEvent?.start}`}</div>
<div className="sx__event-modal__time" style={{ display: 'flex', gap: '12px', marginTop: '8px' }}>
<PencilSquare
size={16}
onClick={() => goToEdit(calendarEvent?.id)}
style={{ cursor: 'pointer' }}
title="Edit"
/>
<Archive
size={16}
onClick={() => disableEvent(calendarEvent?.id)}
style={{ cursor: 'pointer' }}
title="Delete"
/>
</div>
</>
}
};
@@ -349,7 +341,44 @@ const EventsCalendar = () => {
<input type="checkbox" value={showDeletedItems} checked={showDeletedItems === true} onClick={() => setShowDeletedItems(!showDeletedItems)} />
</div>
</div>
<div className="list row">
<hr style={{ margin: '8px 0' }} />
<div style={{ display: 'flex', flexDirection: 'column' }}>
<div className="field-label" style={{ marginBottom: '8px' }}>Filter by Type</div>
{EventsService.labelOptions.map((item) => (
<div key={item.value} className="d-flex align-items-center mb-1" style={{ cursor: 'pointer' }} onClick={() => toggleColorFilter(item.value)}>
<input
type="checkbox"
checked={selectedColorFilters.includes(item.value)}
onChange={() => toggleColorFilter(item.value)}
style={{ marginRight: '8px', marginLeft: '0' }}
onClick={(e) => e.stopPropagation()}
/>
<span
className={`event-${item.value}`}
style={{
width: '14px',
height: '14px',
borderRadius: '3px',
display: 'inline-block',
marginRight: '6px',
flexShrink: 0
}}
></span>
<span style={{ fontSize: '12px' }}>{item.label}</span>
</div>
))}
{selectedColorFilters.length > 0 && (
<div style={{ marginTop: '4px' }}>
<span
style={{ fontSize: '11px', color: '#0066B1', cursor: 'pointer', textDecoration: 'underline' }}
onClick={() => setSelectedColorFilters([])}
>
Clear all
</span>
</div>
)}
</div>
<div className="list row" style={{ marginTop: '8px' }}>
<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>
@@ -360,30 +389,7 @@ const EventsCalendar = () => {
},
);
return (
<>
<div className="list row mb-4">
<Breadcrumb>
<Breadcrumb.Item href="/medical/index">Medical</Breadcrumb.Item>
<Breadcrumb.Item active>
Medical Event Calendar
</Breadcrumb.Item>
</Breadcrumb>
<div className="col-md-12 text-primary">
<h4>
Medical Event Calendar
</h4>
</div>
</div>
<div className="app-main-content-list-container" style={{"min-width": "1500px"}}>
<div className="app-main-content-list-func-container">
<Tabs defaultActiveKey="eventsCalendar" id="events-calendar-tab">
<Tab eventKey="eventsCalendar" title="Medical Appointments">
</Tab>
</Tabs>
<div className="multi-columns-container" style={{ display: 'flex', alignItems: 'flex-start', width: '100%' }}>
const calendarView = <div className="multi-columns-container" style={{ display: 'flex', alignItems: 'flex-start', width: '100%' }}>
<div className="column-container" ref={calendarColumnRef} style={{ minWidth: '1000px', flexShrink: 0, display: 'flex', flexDirection: 'column' }}>
{calendar && <ScheduleXCalendar customComponents={customComponents} calendarApp={calendar} />}
{/* Legend */}
@@ -442,6 +448,89 @@ const EventsCalendar = () => {
</div>
</div>
</div>
const handleClose = () => {
setShowCreationModal(false);
setNewEventStartDateTime(undefined);
setNewEventEndDateTime(undefined);
setNewEventColor('');
setNewEventCustomer('');
setNewEventResource('');
setNewEventInterpreter('');
setNewEventFasting('');
setNewEventNeedId('');
setNewEventNewPatient('');
setNewEventDisability('');
setNewEventTransMethod('');
}
const handleSave = () => {
const userName = localStorage.getItem('user') && JSON.parse(localStorage.getItem('user'))?.name;
const selectedCustomer = customers.find(c => c.id === newEventCustomer);
const selectedResource = resources.find(r => r.id === newEventResource);
const data = {
type: 'medical',
status: 'active',
create_by: userName,
edit_by: userName,
edit_date: new Date(),
create_date: new Date(),
edit_history: [{ employee: userName, date: new Date() }],
title: selectedCustomer ? `${selectedCustomer.name} - Medical Appointment` : 'Medical Appointment',
start_time: newEventStartDateTime,
stop_time: newEventStartDateTime,
color: newEventColor,
confirmed: true,
data: {
customer: newEventCustomer,
client_name: selectedCustomer?.name || '',
resource: newEventResource,
resource_name: selectedResource?.name || '',
resource_phone: selectedResource?.phone || '',
resource_address: selectedResource?.address || '',
interpreter: newEventInterpreter,
fasting: newEventFasting,
need_id: newEventNeedId,
new_patient: newEventNewPatient,
disability: newEventDisability,
trans_method: newEventTransMethod,
}
};
EventsService.createNewEvent(data).then(() => {
EventsService.getAllEvents({ from: EventsService.formatDate(fromDate), to: EventsService.formatDate(toDate) }).then((data) => {
setAllEvents(data.data);
handleClose();
})
});
}
return (
<>
<div className="list row mb-4">
<Breadcrumb>
<Breadcrumb.Item href="/medical/index">Medical</Breadcrumb.Item>
<Breadcrumb.Item active>
Medical Event Calendar
</Breadcrumb.Item>
</Breadcrumb>
<div className="col-md-12 text-primary">
<h4>
Medical Event Calendar
</h4>
</div>
</div>
<div className="app-main-content-list-container" style={{"min-width": "1500px"}}>
<div className="app-main-content-list-func-container">
<Tabs defaultActiveKey="medicalCalendar" id="events-calendar-tab">
<Tab eventKey="medicalCalendar" title="Medical Appointments">
</Tab>
</Tabs>
{ calendarView }
<div className="list-func-panel">
<Dropdown
key={'event-calendar-filter'}
@@ -456,6 +545,79 @@ const EventsCalendar = () => {
<Dropdown.Menu as={customMenu}/>
</Dropdown>
</div>
<Modal show={showCreationModal} onHide={handleClose} size="sm" dialogClassName="calendar-event-modal">
<Modal.Header closeButton>
<Modal.Title>New Medical Appointment</Modal.Title>
</Modal.Header>
<Modal.Body>
<div className="mb-3">
<div className="field-label">Customer<span className="required">*</span></div>
<select className="form-control" value={newEventCustomer} onChange={(e) => setNewEventCustomer(e.target.value)}>
<option value="">Select Customer</option>
{customers.filter(item => item?.status === 'active' && item?.type !== 'discharged').map((item) => <option key={item.id} value={item.id}>{item.name}</option>)}
</select>
</div>
<div className="mb-3">
<div className="field-label">Provider<span className="required">*</span></div>
<select className="form-control" value={newEventResource} onChange={(e) => setNewEventResource(e.target.value)}>
<option value="">Select Provider</option>
{resources.filter(item => item?.status === 'active').map((item) => <option key={item.id} value={item.id}>{item.name}</option>)}
</select>
</div>
<div className="mb-3">
<div className="field-label">Appointment Time<span className="required">*</span></div>
<DatePicker
className="form-control"
selected={newEventStartDateTime}
onChange={setNewEventStartDateTime}
showTimeSelect
timeFormat="HH:mm"
timeIntervals={15}
dateFormat="MM/dd/yyyy, HH:mm"
/>
</div>
<div className="mb-3">
<div className="field-label">Language Support</div>
<select className="form-control" value={newEventInterpreter} onChange={(e) => setNewEventInterpreter(e.target.value)}>
<option value="">Select</option>
<option value="Checkin">Checkin</option>
<option value="Center">Center</option>
<option value="Family">Family</option>
<option value="Office in person">Office in person</option>
<option value="Office by phone">Office by phone</option>
<option value="Nurse">Nurse</option>
</select>
</div>
<div className="mb-3">
<div className="field-label">Transportation Support</div>
<select className="form-control" value={newEventTransMethod} onChange={(e) => setNewEventTransMethod(e.target.value)}>
<option value="">Select</option>
<option value="ByOwn">Self-Transport</option>
<option value="By Center Transportation">By Center Transportation</option>
<option value="DropOff Only">Drop-Off Only</option>
<option value="Pickup Only">Pick-Up Only</option>
<option value="Client Does Not need to Go">Medication Pickup Only</option>
</select>
</div>
<div className="mb-3">
<div className="field-label">Label</div>
<select className="form-control" value={newEventColor} onChange={(e) => setNewEventColor(e.target.value)}>
<option value="">Select</option>
{EventsService.labelOptions?.map((item) => <option key={item.value} value={item.value}>{item.label}</option>)}
</select>
</div>
</Modal.Body>
<Modal.Footer>
<Button variant="secondary" size="sm" onClick={handleClose}>
Cancel
</Button>
<Button variant="primary" size="sm" onClick={handleSave} disabled={
!newEventCustomer || !newEventResource || !newEventStartDateTime
}>
Save
</Button>
</Modal.Footer>
</Modal>
</div>
</div>
</>