Fix
This commit is contained in:
parent
98d86c0f70
commit
c1991211aa
BIN
app/.DS_Store
vendored
BIN
app/.DS_Store
vendored
Binary file not shown.
@ -1,15 +1,18 @@
|
||||
const devUri = "mongodb://localhost:27017/worldshine";
|
||||
const localUri = "mongodb+srv://new-user-test:Testing123@cluster0.qkzim.mongodb.net/leapbase?retryWrites=true&w=majority";
|
||||
// const localUri = "mongodb+srv://new-user-test:Testing123@cluster0.qkzim.mongodb.net/leapbase?retryWrites=true&w=majority";
|
||||
const localUri = "mongodb+srv://liyang1000000:Testing123@juxing.iosxg2f.mongodb.net/worldshine?retryWrites=true&w=majority&appName=juxing";
|
||||
|
||||
module.exports = {
|
||||
baseUrl: "mongodb://localhost:27017/",
|
||||
fileUrl: "https://worldshine.mayo.llc/files/",
|
||||
database: "worldshine",
|
||||
url: devUri,
|
||||
// baseUrl: "mongodb://localhost:27017/",
|
||||
// fileUrl: "https://worldshine.mayo.llc/files/",
|
||||
// database: "worldshine",
|
||||
// url: devUri,
|
||||
|
||||
// on local enable this
|
||||
// url: localUri,
|
||||
url: localUri,
|
||||
// baseUrl: "mongodb+srv://new-user-test:Testing123@cluster0.qkzim.mongodb.net/",
|
||||
// database: "leapbase",
|
||||
// fileUrl: "http://localhost:8080/files/",
|
||||
// imgBucket: "photos",
|
||||
baseUrl: "mongodb+srv://liyang1000000:Testing123@juxing.iosxg2f.mongodb.net/",
|
||||
database: "worldshine",
|
||||
fileUrl: "http://localhost:8080/files/",
|
||||
imgBucket: "photos",
|
||||
};
|
||||
@ -47,7 +47,10 @@ exports.createCalendarEvent = (req, res) => {
|
||||
event_location: req.body.event_location,
|
||||
event_prediction_date: req.body.event_prediction_date,
|
||||
event_reminder_type: req.body.event_reminder_type,
|
||||
rrule: req.body.rrule
|
||||
rrule: req.body.rrule,
|
||||
meal_type: req.body.meal_type,
|
||||
activity_category: req.body.activity_category,
|
||||
ingredients: req.body.ingredients
|
||||
});
|
||||
// Save event in the database
|
||||
calendarEvent
|
||||
|
||||
@ -12,16 +12,76 @@ exports.createCustomer = (req, res) => {
|
||||
const site = splitSite.findSiteNumber(req);
|
||||
// Create a Customer
|
||||
const customer = new Customer({
|
||||
// Basic Info
|
||||
username: req.body.username || req.body.email || '',
|
||||
name: req.body.name || '',
|
||||
firstname: req.body.firstname || '',
|
||||
middle_name: req.body.middle_name || '',
|
||||
lastname: req.body.lastname || '',
|
||||
name_cn: req.body.name_cn || '',
|
||||
name_on_id: req.body.name_on_id || '',
|
||||
type: req.body.type || '',
|
||||
program_type: req.body.program_type || '',
|
||||
pay_source: req.body.pay_source || '',
|
||||
pay_source_other: req.body.pay_source_other || '',
|
||||
birth_date: req.body.birth_date || null,
|
||||
legal_sex: req.body.legal_sex || '',
|
||||
gender: req.body.gender || req.body.legal_sex || '',
|
||||
marital_status: req.body.marital_status || '',
|
||||
marriage_date: req.body.marriage_date || null,
|
||||
immigration_status: req.body.immigration_status || '',
|
||||
immigration_status_other: req.body.immigration_status_other || '',
|
||||
language_spoken: req.body.language_spoken || [],
|
||||
language_spoken_other: req.body.language_spoken_other || '',
|
||||
language: req.body.language || '',
|
||||
|
||||
// Contact Info
|
||||
email: req.body.email || '',
|
||||
password: req.body.password ? bcrypt.hashSync(req.body.password, 8) : '',
|
||||
phone: req.body.phone || '',
|
||||
mobile_phone: req.body.mobile_phone || '',
|
||||
home_phone: req.body.home_phone || '',
|
||||
phone: req.body.phone || '',
|
||||
language: req.body.language || '',
|
||||
status: 'active',
|
||||
|
||||
// Address 1
|
||||
address_line_1: req.body.address_line_1 || '',
|
||||
address_line_2: req.body.address_line_2 || '',
|
||||
city: req.body.city || '',
|
||||
state: req.body.state || '',
|
||||
zip_code: req.body.zip_code || '',
|
||||
address_note: req.body.address_note || '',
|
||||
|
||||
// Address 2
|
||||
address2_line_1: req.body.address2_line_1 || '',
|
||||
address2_line_2: req.body.address2_line_2 || '',
|
||||
city2: req.body.city2 || '',
|
||||
state2: req.body.state2 || '',
|
||||
zip_code2: req.body.zip_code2 || '',
|
||||
address2_note: req.body.address2_note || '',
|
||||
|
||||
// Address 3
|
||||
address3_line_1: req.body.address3_line_1 || '',
|
||||
address3_line_2: req.body.address3_line_2 || '',
|
||||
city3: req.body.city3 || '',
|
||||
state3: req.body.state3 || '',
|
||||
zip_code3: req.body.zip_code3 || '',
|
||||
address3_note: req.body.address3_note || '',
|
||||
|
||||
// Address 4
|
||||
address4_line_1: req.body.address4_line_1 || '',
|
||||
address4_line_2: req.body.address4_line_2 || '',
|
||||
city4: req.body.city4 || '',
|
||||
state4: req.body.state4 || '',
|
||||
zip_code4: req.body.zip_code4 || '',
|
||||
address4_note: req.body.address4_note || '',
|
||||
|
||||
// Address 5
|
||||
address5_line_1: req.body.address5_line_1 || '',
|
||||
address5_line_2: req.body.address5_line_2 || '',
|
||||
city5: req.body.city5 || '',
|
||||
state5: req.body.state5 || '',
|
||||
zip_code5: req.body.zip_code5 || '',
|
||||
address5_note: req.body.address5_note || '',
|
||||
|
||||
// Legacy address fields
|
||||
address1: req.body.address1 || '',
|
||||
address2: req.body.address2 || '',
|
||||
address3: req.body.address3 || '',
|
||||
@ -32,75 +92,130 @@ exports.createCustomer = (req, res) => {
|
||||
state1: req.body.state1 || '',
|
||||
zip_code1: req.body.zip_code1 || '',
|
||||
street_address_2: req.body.street_address_2 || '',
|
||||
city2: req.body.city2 || '',
|
||||
state2: req.body.state2 || '',
|
||||
zip_code2: req.body.zip_code2 || '',
|
||||
street_address_3: req.body.street_address_3 || '',
|
||||
city3: req.body.city3 || '',
|
||||
state3: req.body.state3 || '',
|
||||
zip_code3: req.body.zip_code3 || '',
|
||||
street_address_4: req.body.street_address_4 || '',
|
||||
city4: req.body.city4 || '',
|
||||
state4: req.body.state4 || '',
|
||||
zip_code4: req.body.zip_code4 || '',
|
||||
street_address_5: req.body.street_address_5 || '',
|
||||
city5: req.body.city5 || '',
|
||||
state5: req.body.state5 || '',
|
||||
zip_code5: req.body.zip_code5 || '',
|
||||
firstname: req.body.firstname || '',
|
||||
lastname: req.body.lastname || '',
|
||||
birth_date: req.body.birth_date || null,
|
||||
create_by: req.body.create_by || '',
|
||||
create_date: new Date(),
|
||||
edit_by: req.body.edit_by || '',
|
||||
edit_date: new Date(),
|
||||
note: req.body.note || '',
|
||||
care_provider: req.body.care_provider || '',
|
||||
apartment: req.body.apartment || '',
|
||||
|
||||
// Emergency Contact
|
||||
emergency_contact: req.body.emergency_contact || '',
|
||||
emergency_contact2: req.body.emergency_contact2 || '',
|
||||
emergency_contact_name: req.body.emergency_contact_name || '',
|
||||
emergency_contact_phone: req.body.emergency_contact_phone || '',
|
||||
emergency_contact_relationship: req.body.emergency_contact_relationship || '',
|
||||
emergency_contact_relationship_other: req.body.emergency_contact_relationship_other || '',
|
||||
emergency_contact_role: req.body.emergency_contact_role || [],
|
||||
emergency_contact2_name: req.body.emergency_contact2_name || '',
|
||||
emergency_contact2_phone: req.body.emergency_contact2_phone || '',
|
||||
emergency_contact2_relationship: req.body.emergency_contact2_relationship || '',
|
||||
medicare_number: req.body.medicare_number || '',
|
||||
medicaid_number: req.body.medicaid_number || '',
|
||||
pharmacy: req.body.pharmacy || '',
|
||||
type: req.body.type || '',
|
||||
avatar: req.body.avatar || '',
|
||||
special_needs: req.body.special_needs || '',
|
||||
pickup_status: req.body.pickup_status || '',
|
||||
pharmacy_id: req.body.pharmacy_id || '',
|
||||
pin: req.body.pin || '',
|
||||
emergency_contact2_relationship_other: req.body.emergency_contact2_relationship_other || '',
|
||||
emergency_contact2_role: req.body.emergency_contact2_role || [],
|
||||
|
||||
// Schedule
|
||||
days_of_week: req.body.days_of_week || [],
|
||||
|
||||
// Admission & Discharge Record
|
||||
admission_date: req.body.admission_date || null,
|
||||
seating: req.body.seating || '',
|
||||
vehicle_no: req.body.vehicle_no || '',
|
||||
caller: req.body.caller || '',
|
||||
enrolled_date: req.body.enrolled_date || null,
|
||||
create_by: req.body.create_by || '',
|
||||
create_date: new Date(),
|
||||
referral_source: req.body.referral_source || '',
|
||||
referral_source_other: req.body.referral_source_other || '',
|
||||
discharge_date: req.body.discharge_date || null,
|
||||
placement: req.body.placement || '',
|
||||
nickname: req.body.nickname || '',
|
||||
discharge_by: req.body.discharge_by || '',
|
||||
discharge_reason: req.body.discharge_reason || '',
|
||||
discharge_reason_other: req.body.discharge_reason_other || '',
|
||||
join_reason: req.body.join_reason || '',
|
||||
|
||||
// Care & Services
|
||||
dietary_restrictions: req.body.dietary_restrictions || [],
|
||||
dietary_restrictions_other: req.body.dietary_restrictions_other || '',
|
||||
diet_texture: req.body.diet_texture || '',
|
||||
table_id: req.body.table_id || '',
|
||||
groups: req.body.groups || null,
|
||||
tags: req.body.tags || null,
|
||||
roles: req.body.roles || null,
|
||||
apartment: req.body.apartment || '',
|
||||
private_note: req.body.private_note || '',
|
||||
parent_id: '5eee3552b02fac3d4acfd5ea',
|
||||
site,
|
||||
disability: req.body.disability || false,
|
||||
weight: req.body.weight || '',
|
||||
height: req.body.height || '',
|
||||
gender: req.body.gender || '',
|
||||
seat_number: req.body.seat_number || '',
|
||||
seating: req.body.seating || '',
|
||||
transportation_type: req.body.transportation_type || '',
|
||||
consent_to_text_messages: req.body.consent_to_text_messages || '',
|
||||
text_msg_enabled: req.body.text_msg_enabled || false,
|
||||
health_condition: String,
|
||||
preferred_text_language: req.body.preferred_text_language || '',
|
||||
consent_to_media_use: req.body.consent_to_media_use || '',
|
||||
pickup_status: req.body.pickup_status || '',
|
||||
|
||||
// Medical & Insurance - Providers
|
||||
care_provider: req.body.care_provider || '',
|
||||
primary_care_physician: req.body.primary_care_physician || '',
|
||||
pharmacy: req.body.pharmacy || '',
|
||||
pharmacy_id: req.body.pharmacy_id || '',
|
||||
|
||||
// General Conditions
|
||||
diabetes_mellitus: req.body.diabetes_mellitus || '',
|
||||
eyes_on: req.body.eyes_on || '',
|
||||
disability: req.body.disability || false,
|
||||
wheelchair: req.body.wheelchair || '',
|
||||
special_needs: req.body.special_needs || '',
|
||||
health_condition: req.body.health_condition || '',
|
||||
allergy_info: req.body.allergy_info || '',
|
||||
meal_requirement: req.body.meal_requirement || '',
|
||||
service_requirement: req.body.service_requirement || '',
|
||||
|
||||
// Legal
|
||||
molst: req.body.molst || '',
|
||||
provisions_for_advance_medical: req.body.provisions_for_advance_medical || '',
|
||||
hospice: req.body.hospice || '',
|
||||
burial_arrangements: req.body.burial_arrangements || '',
|
||||
power_of_attorney: req.body.power_of_attorney || '',
|
||||
|
||||
// Rounding
|
||||
requires_rounding: req.body.requires_rounding || '',
|
||||
rounding_notes: req.body.rounding_notes || '',
|
||||
|
||||
// Confidential Details
|
||||
medicare_number: req.body.medicare_number || '',
|
||||
medicaid_number: req.body.medicaid_number || '',
|
||||
social_security_number: req.body.social_security_number || '',
|
||||
adcaps_id: req.body.adcaps_id || '',
|
||||
|
||||
// Compliance & Deadlines
|
||||
adcaps_completed_date: req.body.adcaps_completed_date || null,
|
||||
center_qualification_renew_date: req.body.center_qualification_renew_date || null,
|
||||
medicaid_renew_date: req.body.medicaid_renew_date || null,
|
||||
id_expiration_date: req.body.id_expiration_date || null,
|
||||
|
||||
// Form Submission
|
||||
hipaa_authorization_form: req.body.hipaa_authorization_form || '',
|
||||
medication_management_consent_form: req.body.medication_management_consent_form || '',
|
||||
freedom_of_choice_form: req.body.freedom_of_choice_form || '',
|
||||
meal_benefit_application_form: req.body.meal_benefit_application_form || '',
|
||||
photo_video_release_form: req.body.photo_video_release_form || '',
|
||||
security_deposit_agreement_form: req.body.security_deposit_agreement_form || '',
|
||||
recreational_program_contract_form: req.body.recreational_program_contract_form || '',
|
||||
tb_form: req.body.tb_form || '',
|
||||
pre_screening_form: req.body.pre_screening_form || '',
|
||||
|
||||
// Additional Information
|
||||
note: req.body.note || '',
|
||||
private_note: req.body.private_note || '',
|
||||
|
||||
// Other fields
|
||||
password: req.body.password ? bcrypt.hashSync(req.body.password, 8) : '',
|
||||
avatar: req.body.avatar || '',
|
||||
parent_id: '5eee3552b02fac3d4acfd5ea',
|
||||
nickname: req.body.nickname || '',
|
||||
pin: req.body.pin || '',
|
||||
vehicle_no: req.body.vehicle_no || '',
|
||||
caller: req.body.caller || '',
|
||||
placement: req.body.placement || '',
|
||||
height: req.body.height || '',
|
||||
weight: req.body.weight || '',
|
||||
status: 'active',
|
||||
roles: req.body.roles || [],
|
||||
groups: req.body.groups || [],
|
||||
tags: req.body.tags || [],
|
||||
edit_by: req.body.edit_by || '',
|
||||
edit_date: new Date(),
|
||||
payment_due_date: req.body.payment_due_date || '',
|
||||
payment_status: req.body.payment_status || '',
|
||||
join_reason: req.body.join_reason || '',
|
||||
discharge_reason: req.body.discharge_reason || ''
|
||||
site
|
||||
});
|
||||
// Save Customer in the database
|
||||
customer
|
||||
|
||||
@ -14,26 +14,17 @@ exports.createEmployee = (req, res) => {
|
||||
// Create a Employee
|
||||
const employee = new Employee({
|
||||
username: req.body.username || req.body.email || '',
|
||||
name: req.body.name || '',
|
||||
name_cn: req.body.name_cn || '',
|
||||
email: req.body.email || '',
|
||||
password: req.body.password ? bcrypt.hashSync(req.body.password, 8) : '',
|
||||
roles: req.body.roles || [],
|
||||
mobile_phone: req.body.mobile_phone || '',
|
||||
phone: req.body.phone || '',
|
||||
home_phone: req.body.home_phone || '',
|
||||
language: req.body.language || '',
|
||||
employment_status: req.body.employment_status || '',
|
||||
status: req.body.status || 'active',
|
||||
address: req.body.address || '',
|
||||
title: req.body.title || '',
|
||||
title_cn: req.body.title_cn || '',
|
||||
firstname: req.body.firstname || '',
|
||||
lastname: req.body.lastname || '',
|
||||
department: req.body.department || '',
|
||||
birth_date: req.body.birth_date || null,
|
||||
date_hired: req.body.date_hired || '',
|
||||
driver_capacity: req.body.driver_capacity || null,
|
||||
date_hired: req.body.date_hired || null,
|
||||
create_by: req.body.create_by || '',
|
||||
create_date: new Date(),
|
||||
edit_by: req.body.edit_by || '',
|
||||
@ -41,7 +32,68 @@ exports.createEmployee = (req, res) => {
|
||||
note: req.body.note || '',
|
||||
tags: req.body.tags || [],
|
||||
fetch_route_time: req.body.fetch_route_time || null,
|
||||
site
|
||||
site,
|
||||
// new fields added and legacy fields are used in HR system
|
||||
trinet_id: req.body.trinet_id || '',
|
||||
name: req.body.name || '',
|
||||
firstname: req.body.firstname || '',
|
||||
middlename: req.body.middlename || '',
|
||||
lastname: req.body.lastname || '',
|
||||
preferred_name: req.body.preferred_name || '',
|
||||
ssn: req.body.ssn || '',
|
||||
marital_status: req.body.marital_status || '',
|
||||
gender: req.body.gender || '',
|
||||
city: req.body.city || '',
|
||||
street_address: req.body.street_address || '',
|
||||
state: req.body.state || '',
|
||||
zip: req.body.zip || '',
|
||||
country: req.body.country || '',
|
||||
work_email: req.body.work_email || '',
|
||||
personal_email: req.body.personal_email || '',
|
||||
phone: req.body.phone || '',
|
||||
work_phone: req.body.work_phone || '',
|
||||
birth_date: req.body.birth_date || '',
|
||||
work_phone_ext: req.body.work_phone_ext || '',
|
||||
dietary_restrictions: req.body.dietary_restrictions || '',
|
||||
tshirt_size: req.body.tshirt_size || '',
|
||||
title: req.body.title || '',
|
||||
title_cn: req.body.title_cn || '',
|
||||
department: req.body.department || '',
|
||||
manager_name: req.body.manager_name || '',
|
||||
manager_email: req.body.manager_email || '',
|
||||
manager_trinet_id: req.body.manager_trinet_id || '',
|
||||
manager_id: req.body.manager_id || '',
|
||||
direct_reports: req.body.direct_reports || [],
|
||||
current_employment_start_date: req.body.current_employment_start_date || '',
|
||||
initial_employment_start_date: req.body.initial_employment_start_date || '',
|
||||
status: req.body.status || 'active',
|
||||
work_location: req.body.work_location || '',
|
||||
employment_type: req.body.employment_type || '',
|
||||
employment_start_date: req.body.employment_start_date || '',
|
||||
employment_end_date: req.body.employment_end_date || '',
|
||||
termination_date: req.body.termination_date || '',
|
||||
termination_type: req.body.termination_type || '',
|
||||
termination_reason: req.body.termination_reason || '',
|
||||
full_time_start_date: req.body.full_time_start_date || '',
|
||||
end_date: req.body.end_date || '',
|
||||
month_of_service: req.body.month_of_service || null,
|
||||
full_years_of_service: req.body.full_years_of_service || null,
|
||||
compensation_type: req.body.compensation_type || '',
|
||||
compensation: req.body.compensation || null,
|
||||
worker_type: req.body.worker_type || '',
|
||||
monthly_salary: req.body.monthly_salary || null,
|
||||
currency: req.body.currency || '',
|
||||
salary: req.body.salary || null,
|
||||
hourly_wage: req.body.hourly_wage || null,
|
||||
work_site: req.body.work_site || null,
|
||||
next_anniversary: req.body.next_anniversary || null,
|
||||
job_id: req.body.job_id || '',
|
||||
job_title: req.body.job_title || '',
|
||||
job_category: req.body.job_category || '',
|
||||
add_to_payroll: req.body.add_to_payroll || false,
|
||||
collect_tax_information: req.body.collect_tax_information || false,
|
||||
fingerprints: req.body.fingerprints || [],
|
||||
entry_passcode: req.body.entry_passcode || []
|
||||
});
|
||||
// Save Employee in the database
|
||||
employee
|
||||
@ -67,6 +119,9 @@ exports.getAllEmployees = (req, res) => {
|
||||
if (params.role) {
|
||||
condition.roles = params.role;
|
||||
}
|
||||
if (params.site) {
|
||||
condition.site = params.site;
|
||||
}
|
||||
Employee.find(condition)
|
||||
.then(data => {
|
||||
res.send(data);
|
||||
@ -78,6 +133,29 @@ exports.getAllEmployees = (req, res) => {
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
// Retrive all Employees without site
|
||||
exports.getAllEmployeesAcrossSites = (req, res) => {
|
||||
var params = req.query;
|
||||
var condition = {};
|
||||
if (params.status) {
|
||||
condition.status = params.status;
|
||||
}
|
||||
if (params.role) {
|
||||
condition.roles = params.role;
|
||||
}
|
||||
Employee.find(condition)
|
||||
.then(data => {
|
||||
res.send(data);
|
||||
})
|
||||
.catch(err => {
|
||||
res.status(500).send({
|
||||
message:
|
||||
err.message || "Some error occurred while retrieving employees."
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
// Retrieve all Active Employee from the database.
|
||||
exports.getAllActiveEmployees = (req, res) => {
|
||||
var params = req.query;
|
||||
@ -86,6 +164,9 @@ exports.getAllActiveEmployees = (req, res) => {
|
||||
if (params.roles) {
|
||||
condition.roles = params.roles;
|
||||
}
|
||||
if (params.site) {
|
||||
condition.site = params.site;
|
||||
}
|
||||
Employee.find(condition)
|
||||
.then(data => {
|
||||
res.send(data);
|
||||
|
||||
@ -10,28 +10,46 @@ exports.createResource = (req, res) => {
|
||||
}
|
||||
const site = splitSite.findSiteNumber(req);
|
||||
const resource = new Resource({
|
||||
name: req.body.name,
|
||||
name_original: req.body.name_original,
|
||||
name_branch: req.body.name_branch,
|
||||
// Basic Information
|
||||
name: req.body.name, // Provider name
|
||||
office_name: req.body.office_name || '',
|
||||
specialty: req.body.specialty,
|
||||
type: req.body.type, // value may be ['doctor', 'pharmacy' or 'other']
|
||||
color: req.body.color,
|
||||
address: req.body.address,
|
||||
|
||||
// Legacy fields for backward compatibility
|
||||
name_original: req.body.name_original || req.body.office_name || '',
|
||||
name_branch: req.body.name_branch || '',
|
||||
|
||||
// Contact Information
|
||||
phone: req.body.phone, // Office Phone Number
|
||||
contact: req.body.contact, // Secondary Phone Number
|
||||
fax: req.body.fax,
|
||||
email: req.body.email,
|
||||
|
||||
// Address (split fields)
|
||||
address_line_1: req.body.address_line_1 || '',
|
||||
address_line_2: req.body.address_line_2 || '',
|
||||
city: req.body.city,
|
||||
state: req.body.state,
|
||||
zipcode: req.body.zipcode,
|
||||
phone: req.body.phone,
|
||||
status: req.body.status || 'active', // value might be ['active', 'inactive']
|
||||
|
||||
// Legacy address field
|
||||
address: req.body.address || req.body.address_line_1 || '',
|
||||
|
||||
// Additional Information
|
||||
note: req.body.note,
|
||||
|
||||
// Legacy fields
|
||||
description: req.body.description || '',
|
||||
color: req.body.color || '',
|
||||
category: req.body.category,
|
||||
|
||||
// System fields
|
||||
status: req.body.status || 'active',
|
||||
create_by: req.body.create_by,
|
||||
create_date: req.body.create_date || new Date(),
|
||||
parent_id: req.body.parent_id,
|
||||
ext_id: req.body.ext_id,
|
||||
category: req.body.category,
|
||||
description: req.body.description,
|
||||
contact: req.body.contact,
|
||||
fax: req.body.fax,
|
||||
email: req.body.email,
|
||||
note: req.body.note,
|
||||
data: req.body.data,
|
||||
edit_by: req.body.edit_by,
|
||||
edit_date: req.body.edit_date || new Date(),
|
||||
|
||||
@ -10,26 +10,44 @@ exports.createVehicle = (req, res) => {
|
||||
const site = splitSite.findSiteNumber(req);
|
||||
// Create a Vehicle
|
||||
const vehicle = new Vehicle({
|
||||
// Basic Information
|
||||
vehicle_number: req.body.vehicle_number,
|
||||
tag: req.body.tag || '',
|
||||
ezpass: req.body.ezpass || '',
|
||||
gps_tag: req.body.gps_tag || '',
|
||||
mileage: req.body.mileage || 0,
|
||||
responsible_driver: req.body.responsible_driver || '',
|
||||
responsible_driver_id: req.body.responsible_driver_id || '',
|
||||
capacity: req.body.capacity || 0,
|
||||
mileage: req.body.mileage || 0,
|
||||
make: req.body.make || '',
|
||||
vehicle_model: req.body.vehicle_model || '',
|
||||
year: req.body.year || '',
|
||||
checklist: req.body.checklist || '',
|
||||
status: 'active',
|
||||
site,
|
||||
has_lift_equip: req.body.has_lift_equip,
|
||||
vin: req.body.vin || '',
|
||||
note: req.body.note || '',
|
||||
insurance_expire_on: req.body.insurance_expire_on,
|
||||
title_registration_on: req.body.title_registration_on,
|
||||
emission_test_on: req.body.emission_test_on,
|
||||
tag: req.body.tag || '',
|
||||
gps_tag: req.body.gps_tag || '',
|
||||
ezpass: req.body.ezpass || '',
|
||||
has_lift_equip: req.body.has_lift_equip,
|
||||
fuel_type: req.body.fuel_type || '',
|
||||
title: req.body.title || '',
|
||||
title_other: req.body.title_other || '',
|
||||
|
||||
// Compliance & Deadlines
|
||||
insurance_start_date: req.body.insurance_start_date || '',
|
||||
vehicle_registration_date: req.body.vehicle_registration_date || '',
|
||||
|
||||
// Legacy fields (keeping for backward compatibility)
|
||||
insurance_expire_on: req.body.insurance_expire_on || '',
|
||||
title_registration_on: req.body.title_registration_on || '',
|
||||
emission_test_on: req.body.emission_test_on || '',
|
||||
oil_change_mileage: req.body.oil_change_mileage,
|
||||
oil_change_date: req.body.oil_change_date
|
||||
oil_change_date: req.body.oil_change_date || '',
|
||||
|
||||
// Check List
|
||||
checklist: req.body.checklist || [],
|
||||
|
||||
// Additional Information
|
||||
note: req.body.note || '',
|
||||
|
||||
// System fields
|
||||
status: 'active',
|
||||
site
|
||||
});
|
||||
|
||||
// Save Vehicle in the database
|
||||
|
||||
@ -81,7 +81,12 @@ module.exports = mongoose => {
|
||||
event_location: String,
|
||||
event_prediction_date: String,
|
||||
event_reminder_type: String,
|
||||
rrule: String
|
||||
rrule: String,
|
||||
meal_type: String,
|
||||
// meal_type could be 'breakfast', 'lunch', 'snack'
|
||||
activity_category: String,
|
||||
// activity_category could be 'classes', 'games', 'events', 'outings', 'personal_care', 'care_activities'
|
||||
ingredients: String
|
||||
},
|
||||
{ collection: 'calendar_event', timestamps: true }
|
||||
);
|
||||
|
||||
@ -2,33 +2,84 @@ const uniqueValidator = require('mongoose-unique-validator');
|
||||
module.exports = mongoose => {
|
||||
var schema = mongoose.Schema(
|
||||
{
|
||||
// Basic Info
|
||||
username: {
|
||||
type: String,
|
||||
unique: true
|
||||
},
|
||||
name: String,
|
||||
firstname: String,
|
||||
middle_name: String,
|
||||
lastname: String,
|
||||
name_cn: String,
|
||||
name_on_id: String,
|
||||
type: String,
|
||||
program_type: String,
|
||||
pay_source: String,
|
||||
pay_source_other: String,
|
||||
birth_date: String,
|
||||
legal_sex: String,
|
||||
gender: String, // keeping for backward compatibility
|
||||
marital_status: String,
|
||||
marriage_date: String,
|
||||
immigration_status: String,
|
||||
immigration_status_other: String,
|
||||
language_spoken: [{
|
||||
type: String
|
||||
}],
|
||||
language_spoken_other: String,
|
||||
language: String, // keeping for backward compatibility
|
||||
|
||||
// Contact Info
|
||||
email: {
|
||||
type: String,
|
||||
unique: true
|
||||
},
|
||||
parent_id: String,
|
||||
password: String,
|
||||
care_provider: String,
|
||||
emergency_contact: String,
|
||||
emergency_contact2: String,
|
||||
emergency_contact_name: String,
|
||||
emergency_contact_phone: String,
|
||||
emergency_contact_relationship: String,
|
||||
emergency_contact2_name: String,
|
||||
emergency_contact2_phone: String,
|
||||
emergency_contact2_relationship: String,
|
||||
medicare_number: String,
|
||||
medicaid_number: String,
|
||||
pharmacy: String,
|
||||
birth_date: String,
|
||||
firstname: String,
|
||||
lastname: String,
|
||||
phone: String,
|
||||
mobile_phone: String,
|
||||
home_phone: String,
|
||||
|
||||
// Address 1
|
||||
address_line_1: String,
|
||||
address_line_2: String,
|
||||
city: String,
|
||||
state: String,
|
||||
zip_code: String,
|
||||
address_note: String,
|
||||
|
||||
// Address 2
|
||||
address2_line_1: String,
|
||||
address2_line_2: String,
|
||||
city2: String,
|
||||
state2: String,
|
||||
zip_code2: String,
|
||||
address2_note: String,
|
||||
|
||||
// Address 3
|
||||
address3_line_1: String,
|
||||
address3_line_2: String,
|
||||
city3: String,
|
||||
state3: String,
|
||||
zip_code3: String,
|
||||
address3_note: String,
|
||||
|
||||
// Address 4
|
||||
address4_line_1: String,
|
||||
address4_line_2: String,
|
||||
city4: String,
|
||||
state4: String,
|
||||
zip_code4: String,
|
||||
address4_note: String,
|
||||
|
||||
// Address 5
|
||||
address5_line_1: String,
|
||||
address5_line_2: String,
|
||||
city5: String,
|
||||
state5: String,
|
||||
zip_code5: String,
|
||||
address5_note: String,
|
||||
|
||||
// Legacy address fields (keeping for backward compatibility)
|
||||
address1: String,
|
||||
address2: String,
|
||||
address3: String,
|
||||
@ -39,75 +90,171 @@ module.exports = mongoose => {
|
||||
state1: String,
|
||||
zip_code1: String,
|
||||
street_address_2: String,
|
||||
city2: String,
|
||||
state2: String,
|
||||
zip_code2: String,
|
||||
street_address_3: String,
|
||||
city3: String,
|
||||
state3: String,
|
||||
zip_code3: String,
|
||||
street_address_4: String,
|
||||
city4: String,
|
||||
state4: String,
|
||||
zip_code4: String,
|
||||
street_address_5: String,
|
||||
city5: String,
|
||||
state5: String,
|
||||
zip_code5: String,
|
||||
phone: String,
|
||||
mobile_phone: String,
|
||||
type: String,
|
||||
avatar: String,
|
||||
special_needs: String,
|
||||
note: String,
|
||||
language: String,
|
||||
status: String,
|
||||
pickup_status: String,
|
||||
apartment: String,
|
||||
|
||||
// Emergency Contact
|
||||
emergency_contact: String, // legacy field
|
||||
emergency_contact2: String, // legacy field
|
||||
emergency_contact_name: String,
|
||||
emergency_contact_phone: String,
|
||||
emergency_contact_relationship: String,
|
||||
emergency_contact_relationship_other: String,
|
||||
emergency_contact_role: [{
|
||||
type: String
|
||||
}],
|
||||
emergency_contact2_name: String,
|
||||
emergency_contact2_phone: String,
|
||||
emergency_contact2_relationship: String,
|
||||
emergency_contact2_relationship_other: String,
|
||||
emergency_contact2_role: [{
|
||||
type: String
|
||||
}],
|
||||
emergency_contact3_name: String,
|
||||
emergency_contact3_phone: String,
|
||||
emergency_contact3_relationship: String,
|
||||
emergency_contact3_relationship_other: String,
|
||||
emergency_contact3_role: [{
|
||||
type: String
|
||||
}],
|
||||
emergency_contact4_name: String,
|
||||
emergency_contact4_phone: String,
|
||||
emergency_contact4_relationship: String,
|
||||
emergency_contact4_relationship_other: String,
|
||||
emergency_contact4_role: [{
|
||||
type: String
|
||||
}],
|
||||
emergency_contact5_name: String,
|
||||
emergency_contact5_phone: String,
|
||||
emergency_contact5_relationship: String,
|
||||
emergency_contact5_relationship_other: String,
|
||||
emergency_contact5_role: [{
|
||||
type: String
|
||||
}],
|
||||
|
||||
// Schedule
|
||||
days_of_week: [{
|
||||
type: String
|
||||
}],
|
||||
|
||||
// Admission & Discharge Record
|
||||
admission_date: String,
|
||||
enrolled_date: String,
|
||||
create_by: String,
|
||||
create_date: Date,
|
||||
edit_by: String,
|
||||
edit_date: Date,
|
||||
password: String,
|
||||
referral_source: String,
|
||||
referral_source_other: String,
|
||||
discharge_date: String,
|
||||
discharge_by: String,
|
||||
discharge_reason: String,
|
||||
discharge_reason_other: String,
|
||||
join_reason: String,
|
||||
|
||||
// Care & Services
|
||||
dietary_restrictions: [{
|
||||
type: String
|
||||
}],
|
||||
dietary_restrictions_other: String,
|
||||
diet_texture: String,
|
||||
table_id: String,
|
||||
seat_number: String,
|
||||
seating: String, // legacy field
|
||||
transportation_type: String,
|
||||
consent_to_text_messages: String,
|
||||
text_msg_enabled: Boolean, // legacy field
|
||||
preferred_text_language: String,
|
||||
consent_to_media_use: String,
|
||||
pickup_status: String,
|
||||
|
||||
// Medical & Insurance - Providers
|
||||
care_provider: String,
|
||||
primary_care_physician: String,
|
||||
pharmacy: String,
|
||||
pharmacy_id: String,
|
||||
|
||||
// General Conditions
|
||||
diabetes_mellitus: String,
|
||||
eyes_on: String,
|
||||
disability: Boolean, // legacy field for eyes_on
|
||||
wheelchair: String,
|
||||
special_needs: String,
|
||||
health_condition: String,
|
||||
allergy_info: String,
|
||||
meal_requirement: String,
|
||||
service_requirement: String,
|
||||
|
||||
// Legal
|
||||
molst: String,
|
||||
provisions_for_advance_medical: String,
|
||||
hospice: String,
|
||||
burial_arrangements: String,
|
||||
power_of_attorney: String,
|
||||
|
||||
// Rounding
|
||||
requires_rounding: String,
|
||||
rounding_notes: String,
|
||||
|
||||
// Confidential Details
|
||||
medicare_number: String,
|
||||
medicaid_number: String,
|
||||
social_security_number: String,
|
||||
adcaps_id: String,
|
||||
|
||||
// Compliance & Deadlines
|
||||
adcaps_completed_date: String,
|
||||
center_qualification_renew_date: String,
|
||||
medicaid_renew_date: String,
|
||||
id_expiration_date: String,
|
||||
|
||||
// Form Submission - Admission Forms
|
||||
hipaa_authorization_form: String,
|
||||
medication_management_consent_form: String,
|
||||
freedom_of_choice_form: String,
|
||||
meal_benefit_application_form: String,
|
||||
photo_video_release_form: String,
|
||||
security_deposit_agreement_form: String,
|
||||
recreational_program_contract_form: String,
|
||||
|
||||
// Medical Forms
|
||||
tb_form: String,
|
||||
pre_screening_form: String,
|
||||
|
||||
// Additional Information
|
||||
note: String,
|
||||
private_note: String,
|
||||
|
||||
// Other legacy fields
|
||||
password: String,
|
||||
salt: String,
|
||||
api_token: String,
|
||||
avatar: String,
|
||||
parent_id: String,
|
||||
nickname: String,
|
||||
pin: String,
|
||||
admission_date: String,
|
||||
home_phone: String,
|
||||
seating: String,
|
||||
vehicle_no: String,
|
||||
caller: String,
|
||||
placement: String,
|
||||
height: String,
|
||||
weight: String,
|
||||
status: String,
|
||||
roles: [{
|
||||
type: String
|
||||
}],
|
||||
discharge_date: String,
|
||||
placement: String,
|
||||
nickname: String,
|
||||
table_id: String,
|
||||
salt: String,
|
||||
groups: [{
|
||||
type: String
|
||||
}],
|
||||
tags: [{
|
||||
type: String
|
||||
}],
|
||||
api_token: String,
|
||||
data: String,
|
||||
title: String,
|
||||
apartment: String,
|
||||
private_note: String,
|
||||
site: Number,
|
||||
disability: Boolean,
|
||||
height: String,
|
||||
weight: String,
|
||||
gender: String,
|
||||
text_msg_enabled: Boolean,
|
||||
health_condition: String,
|
||||
allergy_info: String,
|
||||
meal_requirement: String,
|
||||
service_requirement: String,
|
||||
edit_by: String,
|
||||
edit_date: Date,
|
||||
payment_due_date: String,
|
||||
payment_status: String,
|
||||
join_reason: String,
|
||||
discharge_reason: String
|
||||
payment_status: String
|
||||
},
|
||||
{ collection: 'customer', timestamps: true }
|
||||
);
|
||||
|
||||
@ -2,7 +2,6 @@ module.exports = mongoose => {
|
||||
var schema = mongoose.Schema(
|
||||
{
|
||||
username: String,
|
||||
name: String,
|
||||
name_cn: String,
|
||||
email: String,
|
||||
password: String,
|
||||
@ -10,20 +9,12 @@ module.exports = mongoose => {
|
||||
type: String
|
||||
}],
|
||||
mobile_phone: String,
|
||||
phone: String,
|
||||
home_phone: String,
|
||||
language: String,
|
||||
employment_status: String,
|
||||
status: String,
|
||||
address: String,
|
||||
title: String,
|
||||
title_cn: String,
|
||||
firstname: String,
|
||||
lastname: String,
|
||||
department: String,
|
||||
birth_date: String,
|
||||
driver_capacity: Number,
|
||||
date_hired: String,
|
||||
driver_capacity: Number,
|
||||
create_by: String,
|
||||
create_date: Date,
|
||||
edit_by: String,
|
||||
@ -33,7 +24,75 @@ module.exports = mongoose => {
|
||||
type: String
|
||||
}],
|
||||
fetch_route_time: Date,
|
||||
site: Number
|
||||
site: Number,
|
||||
// new fields added and legacy feilds are used in HR system
|
||||
trinet_id: String,
|
||||
name: String,
|
||||
firstname: String,
|
||||
middlename: String,
|
||||
lastname: String,
|
||||
preferred_name: String,
|
||||
ssn: String,
|
||||
marital_status: String,
|
||||
gender: String,
|
||||
city: String,
|
||||
street_address: String,
|
||||
state: String,
|
||||
zip: String,
|
||||
country: String,
|
||||
work_email: String,
|
||||
personal_email: String,
|
||||
phone: String,
|
||||
work_phone: String,
|
||||
birth_date: String, // date of birth
|
||||
work_phone_ext: String, // work phone extension
|
||||
dietary_restrictions: String,
|
||||
tshirt_size: String,
|
||||
title: String,
|
||||
title_cn: String,
|
||||
department: String,
|
||||
manager_name: String,
|
||||
manager_email: String,
|
||||
manager_trinet_id: String,
|
||||
manager_id: String,
|
||||
direct_reports: [{
|
||||
type: String
|
||||
}],
|
||||
current_employment_start_date: String,
|
||||
initial_employment_start_date: String,
|
||||
status: String,
|
||||
work_location: String,
|
||||
employment_type: String,
|
||||
employment_start_date: String,
|
||||
employment_end_date: String,
|
||||
termination_date: String,
|
||||
termination_type: String,
|
||||
termination_reason: String,
|
||||
full_time_start_date: String,
|
||||
end_date: String,
|
||||
month_of_service: Number,
|
||||
full_years_of_service: Number,
|
||||
compensation_type: String,
|
||||
compensation: Number,
|
||||
worker_type: String,
|
||||
monthly_salary: Number,
|
||||
currency: String,
|
||||
salary: Number,
|
||||
hourly_wage: Number,
|
||||
work_site: Number,
|
||||
next_anniversary: Number,
|
||||
job_id: String,
|
||||
job_title: String,
|
||||
job_category: String,
|
||||
|
||||
add_to_payroll: Boolean,
|
||||
collect_tax_information: Boolean,
|
||||
fingerprints: [{
|
||||
type: String
|
||||
}],
|
||||
entry_passcode: [{
|
||||
type: String
|
||||
}]
|
||||
},
|
||||
{ collection: 'employee', timestamps: true }
|
||||
);
|
||||
|
||||
@ -32,4 +32,6 @@ db.label = require("./label.model")(mongoose);
|
||||
db.seating = require("./seating.model")(mongoose);
|
||||
db.attendance_note = require("./attendance-note.model")(mongoose);
|
||||
db.carousel = require("./carousel.model")(mongoose);
|
||||
db.fingerprint_attendance = require("./fingerprint-attendance.model")(mongoose);
|
||||
db.dailyRoutesTemplate = require("./daily-routes-template.model")(mongoose);
|
||||
module.exports = db;
|
||||
@ -5,33 +5,49 @@ module.exports = mongoose => {
|
||||
});
|
||||
var schema = mongoose.Schema(
|
||||
{
|
||||
name: String,
|
||||
name_original: String,
|
||||
name_branch: String,
|
||||
// Basic Information
|
||||
name: String, // Provider name (renamed from doctor name)
|
||||
office_name: String, // Merged from name_original and name_branch
|
||||
specialty: String,
|
||||
type: String, // value may be ['doctor', 'pharmacy' or 'other']
|
||||
color: String,
|
||||
address: String,
|
||||
|
||||
// Legacy fields for backward compatibility
|
||||
name_original: String,
|
||||
name_branch: String,
|
||||
|
||||
// Contact Information
|
||||
phone: String, // Office Phone Number
|
||||
contact: String, // Secondary Phone Number
|
||||
fax: String, // Fax Number
|
||||
email: String,
|
||||
|
||||
// Address (split fields)
|
||||
address_line_1: String,
|
||||
address_line_2: String,
|
||||
city: String,
|
||||
state: String,
|
||||
zipcode: String,
|
||||
phone: String,
|
||||
|
||||
// Legacy address field
|
||||
address: String,
|
||||
|
||||
// Additional Information
|
||||
note: String, // Merged from description and note
|
||||
|
||||
// Legacy fields
|
||||
description: String,
|
||||
color: String,
|
||||
status: String, // value might be ['active', 'inactive']
|
||||
|
||||
// System fields
|
||||
create_by: String,
|
||||
create_date: Date,
|
||||
parent_id: String,
|
||||
ext_id: String,
|
||||
category: String,
|
||||
description: String,
|
||||
contact: String,
|
||||
fax: String,
|
||||
email: String,
|
||||
note: String,
|
||||
data: Object,
|
||||
edit_by: String,
|
||||
edit_date: Date,
|
||||
create_by: String,
|
||||
create_date: Date,
|
||||
site: Number,
|
||||
images: [{
|
||||
type: String
|
||||
|
||||
@ -2,38 +2,52 @@ const uniqueValidator = require('mongoose-unique-validator');
|
||||
module.exports = mongoose => {
|
||||
var schema = mongoose.Schema(
|
||||
{
|
||||
// Basic Information
|
||||
vehicle_number: {
|
||||
type: Number,
|
||||
required: true,
|
||||
},
|
||||
responsible_driver: String,
|
||||
responsible_driver_id: String,
|
||||
capacity: Number,
|
||||
mileage: Number,
|
||||
make: String,
|
||||
vehicle_model: String,
|
||||
year: String,
|
||||
vin: String,
|
||||
tag: {
|
||||
type: String,
|
||||
required: true
|
||||
},
|
||||
ezpass: {
|
||||
type: String,
|
||||
},
|
||||
gps_tag: {
|
||||
type: String,
|
||||
},
|
||||
mileage: Number,
|
||||
capacity: Number,
|
||||
make: String,
|
||||
vehicle_model: String,
|
||||
year: String,
|
||||
checklist: [{
|
||||
type: String
|
||||
}],
|
||||
status: String,
|
||||
site: Number,
|
||||
vin: String,
|
||||
gps_tag: String,
|
||||
ezpass: String,
|
||||
has_lift_equip: Boolean,
|
||||
fuel_type: String,
|
||||
title: String,
|
||||
title_other: String,
|
||||
|
||||
// Compliance & Deadlines
|
||||
insurance_start_date: String,
|
||||
vehicle_registration_date: String,
|
||||
|
||||
// Legacy fields (keeping for backward compatibility)
|
||||
insurance_expire_on: String,
|
||||
title_registration_on: String,
|
||||
emission_test_on: String,
|
||||
oil_change_mileage: Number,
|
||||
oil_change_date: String,
|
||||
note: String
|
||||
|
||||
// Check List
|
||||
checklist: [{
|
||||
type: String
|
||||
}],
|
||||
|
||||
// Additional Information
|
||||
note: String,
|
||||
|
||||
// System fields
|
||||
status: String,
|
||||
site: Number
|
||||
},
|
||||
{ collection: 'vehicle', timestamps: true }
|
||||
);
|
||||
|
||||
@ -10,7 +10,9 @@ module.exports = app => {
|
||||
});
|
||||
var router = require("express").Router();
|
||||
// Retrieve all employee
|
||||
router.get("/",[authJwt.verifyToken], employees.getAllEmployees);
|
||||
router.get("/", employees.getAllEmployees);
|
||||
// Retrive employees across sites
|
||||
router.get("/all-sites", employees.getAllEmployeesAcrossSites);
|
||||
// Create a new employee
|
||||
router.post("/", employees.createEmployee);
|
||||
// Update an employee
|
||||
|
||||
BIN
app/views/.DS_Store
vendored
BIN
app/views/.DS_Store
vendored
Binary file not shown.
@ -1,16 +1,16 @@
|
||||
{
|
||||
"files": {
|
||||
"main.css": "/static/css/main.2c06dda5.css",
|
||||
"main.js": "/static/js/main.cddce86b.js",
|
||||
"main.css": "/static/css/main.7fa0ef40.css",
|
||||
"main.js": "/static/js/main.ea61a51a.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.2c06dda5.css.map": "/static/css/main.2c06dda5.css.map",
|
||||
"main.cddce86b.js.map": "/static/js/main.cddce86b.js.map",
|
||||
"main.7fa0ef40.css.map": "/static/css/main.7fa0ef40.css.map",
|
||||
"main.ea61a51a.js.map": "/static/js/main.ea61a51a.js.map",
|
||||
"787.c4e7f8f9.chunk.js.map": "/static/js/787.c4e7f8f9.chunk.js.map"
|
||||
},
|
||||
"entrypoints": [
|
||||
"static/css/main.2c06dda5.css",
|
||||
"static/js/main.cddce86b.js"
|
||||
"static/css/main.7fa0ef40.css",
|
||||
"static/js/main.ea61a51a.js"
|
||||
]
|
||||
}
|
||||
@ -1 +1 @@
|
||||
<!doctype html><html lang="en"><head><meta charset="utf-8"/><link rel="icon" href="/favicon.ico"/><meta name="viewport" content="width=device-width,initial-scale=1"/><meta name="theme-color" content="#000000"/><meta name="description" content="Web site created using create-react-app"/><link rel="apple-touch-icon" href="/logo192.png"/><script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.10/lodash.min.js"></script><link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0/css/all.min.css"><link rel="manifest" href="/manifest.json"/><title>Worldshine Transportation</title><script defer="defer" src="/static/js/main.cddce86b.js"></script><link href="/static/css/main.2c06dda5.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.ea61a51a.js"></script><link href="/static/css/main.7fa0ef40.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
File diff suppressed because one or more lines are too long
@ -1,140 +0,0 @@
|
||||
/*!
|
||||
Copyright (c) 2018 Jed Watson.
|
||||
Licensed under the MIT License (MIT), see
|
||||
http://jedwatson.github.io/classnames
|
||||
*/
|
||||
|
||||
/*!
|
||||
* @kurkle/color v0.3.4
|
||||
* https://github.com/kurkle/color#readme
|
||||
* (c) 2024 Jukka Kurkela
|
||||
* Released under the MIT License
|
||||
*/
|
||||
|
||||
/*!
|
||||
* Chart.js v4.5.0
|
||||
* https://www.chartjs.org
|
||||
* (c) 2025 Chart.js Contributors
|
||||
* Released under the MIT License
|
||||
*/
|
||||
|
||||
/*!
|
||||
* Signature Pad v2.3.2
|
||||
* https://github.com/szimek/signature_pad
|
||||
*
|
||||
* Copyright 2017 Szymon Nowak
|
||||
* Released under the MIT license
|
||||
*
|
||||
* The main idea and some parts of the code (e.g. drawing variable width Bézier curve) are taken from:
|
||||
* http://corner.squareup.com/2012/07/smoother-signatures.html
|
||||
*
|
||||
* Implementation of interpolation using cubic Bézier curves is taken from:
|
||||
* http://benknowscode.wordpress.com/2012/09/14/path-interpolation-using-cubic-bezier-and-control-point-estimation-in-javascript
|
||||
*
|
||||
* Algorithm for approximated length of a Bézier curve is taken from:
|
||||
* http://www.lemoda.net/maths/bezier-length/index.html
|
||||
*
|
||||
*/
|
||||
|
||||
/*! regenerator-runtime -- Copyright (c) 2014-present, Facebook, Inc. -- license (MIT): https://github.com/facebook/regenerator/blob/main/LICENSE */
|
||||
|
||||
/**
|
||||
* @license
|
||||
* Lodash <https://lodash.com/>
|
||||
* Copyright OpenJS Foundation and other contributors <https://openjsf.org/>
|
||||
* Released under MIT license <https://lodash.com/license>
|
||||
* Based on Underscore.js 1.8.3 <http://underscorejs.org/LICENSE>
|
||||
* Copyright Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors
|
||||
*/
|
||||
|
||||
/**
|
||||
* @license React
|
||||
* react-dom.production.min.js
|
||||
*
|
||||
* Copyright (c) Facebook, Inc. and its affiliates.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
/**
|
||||
* @license React
|
||||
* react-is.production.min.js
|
||||
*
|
||||
* Copyright (c) Facebook, Inc. and its affiliates.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
/**
|
||||
* @license React
|
||||
* react-jsx-runtime.production.min.js
|
||||
*
|
||||
* Copyright (c) Facebook, Inc. and its affiliates.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
/**
|
||||
* @license React
|
||||
* react.production.min.js
|
||||
*
|
||||
* Copyright (c) Facebook, Inc. and its affiliates.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
/**
|
||||
* @license React
|
||||
* scheduler.production.min.js
|
||||
*
|
||||
* Copyright (c) Facebook, Inc. and its affiliates.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
/**
|
||||
* @license React
|
||||
* use-sync-external-store-shim.production.min.js
|
||||
*
|
||||
* Copyright (c) Facebook, Inc. and its affiliates.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
/**
|
||||
* @license React
|
||||
* use-sync-external-store-shim/with-selector.production.min.js
|
||||
*
|
||||
* Copyright (c) Facebook, Inc. and its affiliates.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
/**
|
||||
* React Router v6.3.0
|
||||
*
|
||||
* Copyright (c) Remix Software Inc.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE.md file in the root directory of this source tree.
|
||||
*
|
||||
* @license MIT
|
||||
*/
|
||||
|
||||
/** @license React v16.13.1
|
||||
* react-is.production.min.js
|
||||
*
|
||||
* Copyright (c) Facebook, Inc. and its affiliates.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
//! moment.js
|
||||
File diff suppressed because one or more lines are too long
BIN
client/.DS_Store
vendored
BIN
client/.DS_Store
vendored
Binary file not shown.
BIN
client/src/.DS_Store
vendored
BIN
client/src/.DS_Store
vendored
Binary file not shown.
@ -10,6 +10,12 @@ body {
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.btn-warning {
|
||||
height: 35px;
|
||||
font-size: 13px;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.text-primary {
|
||||
color: #0066B1;
|
||||
}
|
||||
@ -103,7 +109,7 @@ input {
|
||||
}
|
||||
|
||||
.app-side-bar-container {
|
||||
width: 300px;
|
||||
width: 250px;
|
||||
border-right: 1px solid #ccc;
|
||||
height: 100vh;
|
||||
}
|
||||
@ -112,7 +118,7 @@ input {
|
||||
padding: 16px;
|
||||
border-top: 1px solid #ccc;
|
||||
border-right: 1px solid #ccc;
|
||||
width: 300px;
|
||||
width: 250px;
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
@ -135,7 +141,7 @@ input {
|
||||
}
|
||||
|
||||
.app-side-bar-menu-container {
|
||||
height: calc(100vh - 120px);
|
||||
height: calc(100vh - 150px);
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
@ -166,7 +172,7 @@ input {
|
||||
.app-side-bar-logo {
|
||||
padding: 16px;
|
||||
border-bottom: 1px solid #ccc;
|
||||
width: 300px;
|
||||
width: 250px;
|
||||
}
|
||||
|
||||
.app-layout .collapsed {
|
||||
@ -180,6 +186,8 @@ input {
|
||||
}
|
||||
|
||||
.app-main-container {
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
padding: 40px;
|
||||
height: 100vh;
|
||||
overflow: auto;
|
||||
@ -198,6 +206,14 @@ input {
|
||||
padding: 4px 24px;
|
||||
}
|
||||
|
||||
/* List pages: container yields to children width/height, no scrolling (browser-level scrolling) */
|
||||
.app-main-content-list-container.list-page {
|
||||
max-width: none;
|
||||
max-height: none;
|
||||
width: fit-content;
|
||||
min-width: 100%;
|
||||
}
|
||||
|
||||
.app-main-content-list-func-container {
|
||||
position: relative;
|
||||
}
|
||||
@ -227,10 +243,18 @@ input {
|
||||
color: #0066B1 !important;
|
||||
}
|
||||
|
||||
.app-main-content-list-func-container .tab-pane {
|
||||
/* List pages: tab-pane should not scroll, let browser handle it */
|
||||
.app-main-content-list-container.list-page .tab-pane {
|
||||
border-top: 1px solid #ccc;
|
||||
padding-top: 24px;
|
||||
overflow: auto;
|
||||
overflow: visible;
|
||||
}
|
||||
|
||||
/* Form pages: tab-pane should not scroll either */
|
||||
.app-main-content-list-container.form-page .tab-pane {
|
||||
border-top: 1px solid #ccc;
|
||||
padding-top: 24px;
|
||||
overflow: visible;
|
||||
}
|
||||
|
||||
.app-main-content-list-func-container .list-func-panel {
|
||||
@ -358,13 +382,20 @@ table tr {
|
||||
border: 1px solid white;
|
||||
}
|
||||
|
||||
table {
|
||||
width: 100%;
|
||||
table-layout: auto;
|
||||
}
|
||||
|
||||
table tr th {
|
||||
border: 1px solid white;
|
||||
padding: 8px 16px;
|
||||
font-size: 12px;
|
||||
background-color: #0066B1;
|
||||
color: white;
|
||||
min-width: 250px;
|
||||
width: fit-content;
|
||||
max-width: 250px;
|
||||
overflow-wrap: break-word;
|
||||
}
|
||||
|
||||
table tr td {
|
||||
@ -372,7 +403,9 @@ table tr td {
|
||||
padding: 8px 16px;
|
||||
font-size: 12px;
|
||||
color: #333;
|
||||
min-width: 250px;
|
||||
width: fit-content;
|
||||
max-width: 250px;
|
||||
overflow-wrap: break-word;
|
||||
}
|
||||
|
||||
.clickable {
|
||||
@ -381,13 +414,13 @@ table tr td {
|
||||
|
||||
.td-checkbox, .th-checkbox {
|
||||
width: 30px !important;
|
||||
padding-left: 8px !important;
|
||||
padding-left: 14px !important;
|
||||
min-width: 5px !important;
|
||||
}
|
||||
|
||||
.td-index, .th-index {
|
||||
width: 30px !important;
|
||||
padding-left: 8px !important;
|
||||
padding-left: 14px !important;
|
||||
min-width: 5px !important;
|
||||
}
|
||||
|
||||
@ -413,11 +446,20 @@ table .group td {
|
||||
transform: translate(0px, 37px)!important;
|
||||
}
|
||||
|
||||
/* Form pages: container should be 100% width and height, not scrollable */
|
||||
.app-main-content-list-container.form-page {
|
||||
width: 100%;
|
||||
max-width: none;
|
||||
min-height: calc(100vh - 140px);
|
||||
overflow: visible;
|
||||
}
|
||||
|
||||
.app-main-content-fields-section {
|
||||
display: flex;
|
||||
align-items: baseline;
|
||||
margin-top: 24px;
|
||||
margin-bottom: 40px;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.app-main-content-fields-section.flex-end {
|
||||
@ -425,6 +467,7 @@ table .group td {
|
||||
align-items: flex-end;
|
||||
margin-top: 24px;
|
||||
margin-bottom: 40px;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.app-main-content-fields-section.column {
|
||||
@ -456,6 +499,7 @@ table .group td {
|
||||
color: black;
|
||||
font-weight: 500;
|
||||
line-height: 2;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.multi-columns-container {
|
||||
@ -497,12 +541,15 @@ table .group td {
|
||||
|
||||
.app-main-content-fields-section.short .field-body {
|
||||
margin-right: 20px;
|
||||
min-width: 140px;
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.app-main-content-fields-section.short {
|
||||
margin-bottom: 0;
|
||||
margin-top: 12px;
|
||||
justify-content: space-between;
|
||||
display: flex;
|
||||
gap: 16px;
|
||||
}
|
||||
|
||||
.app-main-content-fields-section.with-function .react-datepicker-wrapper {
|
||||
@ -516,21 +563,21 @@ table .group td {
|
||||
|
||||
.app-main-content-fields-section.dropdown-container input[type=text] {
|
||||
width: 210px;
|
||||
height: 35px;
|
||||
height: 45px;
|
||||
}
|
||||
|
||||
.app-main-content-fields-section.dropdown-container input[type=number] {
|
||||
width: 210px;
|
||||
height: 35px;
|
||||
height: 45px;
|
||||
}
|
||||
|
||||
.app-main-content-fields-section.dropdown-container select {
|
||||
width: 210px;
|
||||
height: 35px;
|
||||
height: 45px;
|
||||
}
|
||||
|
||||
.app-main-content-fields-section.with-function .react-datepicker-wrapper input[type=text] {
|
||||
height: 35px;
|
||||
height: 45px;
|
||||
width: 120px;
|
||||
}
|
||||
|
||||
@ -601,8 +648,9 @@ table .group td {
|
||||
select {
|
||||
margin-left: 5px;
|
||||
min-width: 200px;
|
||||
height: 40px;
|
||||
height: 45px;
|
||||
padding: 5px;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
a {
|
||||
@ -611,11 +659,12 @@ a {
|
||||
}
|
||||
|
||||
input[type="text"] {
|
||||
height: 30px;
|
||||
height: 45px;
|
||||
padding-left: 15px;
|
||||
padding-right: 15px;
|
||||
border-radius: 8px;
|
||||
border: 1px solid #ccc;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
textarea {
|
||||
@ -628,33 +677,54 @@ textarea {
|
||||
}
|
||||
|
||||
input[type="number"] {
|
||||
height: 30px;
|
||||
height: 45px;
|
||||
padding-left: 15px;
|
||||
padding-right: 15px;
|
||||
border-radius: 8px;
|
||||
border: 1px solid #ccc;
|
||||
/* box-sizing: border-box; */
|
||||
}
|
||||
|
||||
/* Exclude react-time-picker inputs from the padding rules above */
|
||||
.react-time-picker__inputGroup__input {
|
||||
padding-left: initial;
|
||||
padding-right: initial;
|
||||
}
|
||||
|
||||
input[type="email"] {
|
||||
height: 30px;
|
||||
height: 45px;
|
||||
padding-left: 15px;
|
||||
padding-right: 15px;
|
||||
border-radius: 8px;
|
||||
border: 1px solid #ccc;
|
||||
/* box-sizing: border-box; */
|
||||
}
|
||||
|
||||
input[type="password"] {
|
||||
height: 30px;
|
||||
height: 45px;
|
||||
padding-left: 15px;
|
||||
padding-right: 15px;
|
||||
border-radius: 8px;
|
||||
border: 1px solid #ccc;
|
||||
/* box-sizing: border-box; */
|
||||
}
|
||||
|
||||
input[type="checkbox"] {
|
||||
margin-left: 8px;
|
||||
}
|
||||
|
||||
|
||||
/* Search input with magnifier icon */
|
||||
.with-search-icon {
|
||||
height: 35px !important;
|
||||
padding-left: 40px !important;
|
||||
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='16' fill='%23666' viewBox='0 0 16 16'%3E%3Cpath d='M11.742 10.344a6.5 6.5 0 1 0-1.397 1.398h-.001c.03.04.062.078.098.115l3.85 3.85a1 1 0 0 0 1.415-1.414l-3.85-3.85a1.007 1.007 0 0 0-.115-.1zM12 6.5a5.5 5.5 0 1 1-11 0 5.5 5.5 0 0 1 11 0z'/%3E%3C/svg%3E");
|
||||
background-repeat: no-repeat;
|
||||
background-position: 12px center;
|
||||
background-size: 18px 18px;
|
||||
min-width: 200px;
|
||||
}
|
||||
|
||||
.clickable {
|
||||
cursor: pointer;
|
||||
}
|
||||
@ -702,6 +772,32 @@ input[type="checkbox"] {
|
||||
|
||||
.event-list-item-container {
|
||||
border-radius: 8px;
|
||||
min-width: 280px;
|
||||
}
|
||||
|
||||
/* Center Calendar small modal styling */
|
||||
.calendar-event-modal {
|
||||
max-width: 380px !important;
|
||||
}
|
||||
|
||||
.calendar-event-modal .modal-content {
|
||||
width: 380px !important;
|
||||
}
|
||||
|
||||
.calendar-event-modal .form-control,
|
||||
.calendar-event-modal select.form-control,
|
||||
.calendar-event-modal input.form-control {
|
||||
width: 100% !important;
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
.calendar-event-modal .modal-body {
|
||||
padding: 16px 20px;
|
||||
}
|
||||
|
||||
.calendar-event-modal .field-label {
|
||||
font-size: 13px;
|
||||
margin-bottom: 4px;
|
||||
}
|
||||
|
||||
.event-red {
|
||||
@ -764,6 +860,44 @@ input[type="checkbox"] {
|
||||
background-color: #FFD1DC !important;
|
||||
}
|
||||
|
||||
/* Calendar event title styles - auto-truncate based on container width */
|
||||
.sx__month-grid-event .sx__month-grid-event__title,
|
||||
.sx__week-grid__event .sx__event-title,
|
||||
.sx__day-grid__event .sx__event-title,
|
||||
.sx__month-agenda-event .sx__month-agenda-event__title {
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
max-width: 100%;
|
||||
}
|
||||
|
||||
/* Ensure calendar events show full name on hover */
|
||||
.sx__month-grid-event,
|
||||
.sx__week-grid__event,
|
||||
.sx__day-grid__event,
|
||||
.sx__month-agenda-event {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
/* Calendar event height - adjust to content, no cropping */
|
||||
.sx__month-grid-event {
|
||||
height: auto !important;
|
||||
min-height: 20px;
|
||||
overflow: visible !important;
|
||||
white-space: normal !important;
|
||||
}
|
||||
|
||||
.sx__month-grid-event .sx__month-grid-event__title {
|
||||
white-space: normal !important;
|
||||
overflow: visible !important;
|
||||
text-overflow: clip !important;
|
||||
line-height: 1.3;
|
||||
}
|
||||
|
||||
.sx__month-grid-day__events {
|
||||
overflow: visible !important;
|
||||
}
|
||||
|
||||
.btn-no-deco {
|
||||
text-decoration: none!important;
|
||||
padding-top: 6px;
|
||||
@ -994,7 +1128,7 @@ input[type="checkbox"] {
|
||||
font-size: 13px;
|
||||
cursor: move;
|
||||
background: #eee;
|
||||
margin-left: 60px;
|
||||
margin-left: 65px;
|
||||
}
|
||||
|
||||
.customers-dnd-item-container-absent {
|
||||
@ -1009,8 +1143,9 @@ input[type="checkbox"] {
|
||||
|
||||
.new-customers-dnd-item-container {
|
||||
border: none;
|
||||
padding: 0 16px;
|
||||
padding: 0 16px 0 25px;
|
||||
margin-bottom: 24px;
|
||||
margin-top: 16px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
font-size: 13px;
|
||||
@ -1024,6 +1159,7 @@ input[type="checkbox"] {
|
||||
.stop-index {
|
||||
position: absolute;
|
||||
left: 0;
|
||||
padding-right: 12px;
|
||||
}
|
||||
|
||||
.customer-dnd-item {
|
||||
@ -1053,10 +1189,44 @@ input[type="checkbox"] {
|
||||
}
|
||||
|
||||
.react-datepicker__input-container input {
|
||||
height: 35px;
|
||||
height: 45px;
|
||||
width: 100%;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
/* DatePicker blue theme styling */
|
||||
.react-datepicker__header {
|
||||
background-color: #0066B1 !important;
|
||||
border-bottom: none !important;
|
||||
}
|
||||
|
||||
.react-datepicker__current-month,
|
||||
.react-datepicker__day-name,
|
||||
.react-datepicker-time__header {
|
||||
color: white !important;
|
||||
}
|
||||
|
||||
.react-datepicker__day--selected,
|
||||
.react-datepicker__day--keyboard-selected,
|
||||
.react-datepicker__time-container .react-datepicker__time .react-datepicker__time-box ul.react-datepicker__time-list li.react-datepicker__time-list-item--selected {
|
||||
background-color: #0066B1 !important;
|
||||
color: white !important;
|
||||
}
|
||||
|
||||
.react-datepicker__day:hover {
|
||||
background-color: #EAF3FF !important;
|
||||
}
|
||||
|
||||
.react-datepicker__navigation-icon::before {
|
||||
border-color: white !important;
|
||||
}
|
||||
|
||||
.react-datepicker__triangle::before,
|
||||
.react-datepicker__triangle::after {
|
||||
border-bottom-color: #0066B1 !important;
|
||||
}
|
||||
|
||||
|
||||
.admin-nav a {
|
||||
text-decoration: none !important;
|
||||
}
|
||||
@ -1413,6 +1583,16 @@ input[type="checkbox"] {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.seat-circle.has-customer {
|
||||
cursor: pointer;
|
||||
transition: transform 0.2s, box-shadow 0.2s;
|
||||
}
|
||||
|
||||
.seat-circle.has-customer:hover {
|
||||
transform: scale(1.1);
|
||||
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.3);
|
||||
}
|
||||
|
||||
.guest-name {
|
||||
text-align: center;
|
||||
font-size: 10px;
|
||||
@ -1593,66 +1773,199 @@ input[type="checkbox"] {
|
||||
width: 100vw;
|
||||
height: 100vh;
|
||||
z-index: 9998;
|
||||
overflow: auto;
|
||||
overflow: hidden;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.fullscreen-mode .app-main-content-list-container {
|
||||
border: none;
|
||||
padding: 20px;
|
||||
min-height: 100vh;
|
||||
width: 100%;
|
||||
max-width: 1400px;
|
||||
margin: 0 auto;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.fullscreen-mode .app-main-content-list-func-container {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
margin-top: auto;
|
||||
flex-direction: column;
|
||||
padding: clamp(10px, 2vw, 30px);
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.fullscreen-mode .multi-columns-container {
|
||||
width: 100%;
|
||||
max-width: 1200px;
|
||||
margin: 0 auto;
|
||||
height: 100%;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
gap: 20px;
|
||||
gap: clamp(10px, 1.5vw, 25px);
|
||||
flex: 1;
|
||||
min-height: 0;
|
||||
}
|
||||
|
||||
.fullscreen-mode .column-container {
|
||||
flex: 1;
|
||||
max-width: 400px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
min-width: 0;
|
||||
height: 100%;
|
||||
gap: clamp(8px, 1vw, 16px);
|
||||
}
|
||||
|
||||
.fullscreen-mode .column-card {
|
||||
background-color: rgba(255, 255, 255, 0.8) !important;
|
||||
backdrop-filter: blur(5px);
|
||||
margin-bottom: 20px;
|
||||
background-color: rgba(255, 255, 255, 0.9) !important;
|
||||
backdrop-filter: blur(8px);
|
||||
border-radius: clamp(6px, 0.8vw, 12px);
|
||||
padding: clamp(10px, 1.5vw, 24px);
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
min-height: 0;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.fullscreen-mode .card {
|
||||
background-color: rgba(255, 255, 255, 0.8) !important;
|
||||
backdrop-filter: blur(5px);
|
||||
background-color: rgba(255, 255, 255, 0.9) !important;
|
||||
backdrop-filter: blur(8px);
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.fullscreen-mode .card-body {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
/* Dynamic font sizing for fullscreen mode */
|
||||
.fullscreen-mode h4 {
|
||||
font-size: clamp(16px, 2vw, 28px);
|
||||
}
|
||||
|
||||
.fullscreen-mode h5 {
|
||||
font-size: clamp(14px, 1.8vw, 24px);
|
||||
}
|
||||
|
||||
.fullscreen-mode h6 {
|
||||
font-size: clamp(12px, 1.4vw, 20px);
|
||||
margin-bottom: clamp(8px, 1vw, 16px) !important;
|
||||
}
|
||||
|
||||
.fullscreen-mode p,
|
||||
.fullscreen-mode span,
|
||||
.fullscreen-mode div {
|
||||
font-size: clamp(10px, 1.1vw, 16px);
|
||||
}
|
||||
|
||||
.fullscreen-mode .text-primary {
|
||||
font-size: clamp(16px, 2vw, 32px) !important;
|
||||
}
|
||||
|
||||
/* Tables in fullscreen - fill available space */
|
||||
.fullscreen-mode .info-screen-appointments-table {
|
||||
width: 100%;
|
||||
background-color: rgba(255, 255, 255, 0.95);
|
||||
table-layout: fixed;
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.fullscreen-mode .info-screen-appointments-table th,
|
||||
.fullscreen-mode .info-screen-appointments-table td {
|
||||
font-size: clamp(10px, 1.1vw, 16px);
|
||||
padding: clamp(6px, 0.8vw, 14px) clamp(8px, 1vw, 16px);
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
.fullscreen-mode .info-screen-appointments-table th {
|
||||
font-weight: 600;
|
||||
background-color: rgba(0, 102, 177, 0.1);
|
||||
}
|
||||
|
||||
.fullscreen-mode .list.row {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
margin-bottom: 0 !important;
|
||||
}
|
||||
|
||||
.fullscreen-mode .list.row .col-md-12 {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.fullscreen-mode .app-main-content-fields-section {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
/* Clock and weather cards - equal sizing */
|
||||
.fullscreen-mode .row.mb-4 {
|
||||
display: flex;
|
||||
gap: clamp(8px, 1vw, 16px);
|
||||
margin-bottom: clamp(10px, 1.5vw, 24px) !important;
|
||||
}
|
||||
|
||||
.fullscreen-mode .col-md-6 {
|
||||
flex: 1;
|
||||
max-width: none;
|
||||
width: auto;
|
||||
}
|
||||
|
||||
/* Clock scaling */
|
||||
.fullscreen-mode .clock-container {
|
||||
transform: scale(clamp(0.6, 0.08vw + 0.5, 1.2));
|
||||
transform-origin: center;
|
||||
}
|
||||
|
||||
.fullscreen-mode .clock {
|
||||
width: clamp(60px, 8vw, 120px);
|
||||
height: clamp(60px, 8vw, 120px);
|
||||
}
|
||||
|
||||
/* Weather icon scaling */
|
||||
.fullscreen-mode .weather-icon i {
|
||||
font-size: clamp(32px, 4vw, 64px) !important;
|
||||
}
|
||||
|
||||
/* Gallery/Carousel scaling */
|
||||
.fullscreen-mode .carousel {
|
||||
height: clamp(120px, 15vh, 250px) !important;
|
||||
}
|
||||
|
||||
.fullscreen-mode .carousel-item img {
|
||||
height: clamp(120px, 15vh, 250px) !important;
|
||||
}
|
||||
|
||||
/* Attendance note card adjustments */
|
||||
.fullscreen-mode .mb-4:has(.card) {
|
||||
flex: 0 0 auto;
|
||||
}
|
||||
|
||||
.fullscreen-mode .img-fluid {
|
||||
max-height: clamp(80px, 10vh, 150px) !important;
|
||||
}
|
||||
|
||||
/* Logo scaling */
|
||||
.fullscreen-mode .info-logo,
|
||||
.fullscreen-mode img[alt="Worldshine Logo"] {
|
||||
height: clamp(20px, 2.5vw, 40px) !important;
|
||||
}
|
||||
|
||||
.fullscreen-mode .logo-worldshine {
|
||||
font-size: clamp(12px, 1.4vw, 20px) !important;
|
||||
}
|
||||
|
||||
.fullscreen-hint {
|
||||
position: fixed;
|
||||
top: 20px;
|
||||
right: 20px;
|
||||
background-color: rgba(0, 0, 0, 0.7);
|
||||
top: clamp(10px, 1.5vw, 25px);
|
||||
right: clamp(10px, 1.5vw, 25px);
|
||||
background-color: rgba(0, 0, 0, 0.75);
|
||||
color: white;
|
||||
padding: 10px 15px;
|
||||
border-radius: 5px;
|
||||
padding: clamp(8px, 1vw, 15px) clamp(12px, 1.5vw, 20px);
|
||||
border-radius: clamp(4px, 0.5vw, 8px);
|
||||
z-index: 9999;
|
||||
font-size: 14px;
|
||||
font-size: clamp(11px, 1.2vw, 16px);
|
||||
animation: fadeIn 0.3s ease-in;
|
||||
backdrop-filter: blur(4px);
|
||||
}
|
||||
|
||||
@keyframes fadeIn {
|
||||
@ -1674,39 +1987,125 @@ input[type="checkbox"] {
|
||||
background-attachment: fixed;
|
||||
}
|
||||
|
||||
/* Info Screen Table Overrides for Full Screen */
|
||||
.fullscreen-mode .info-screen-appointments-table {
|
||||
background-color: rgba(255, 255, 255, 0.9);
|
||||
}
|
||||
|
||||
/* Responsive adjustments for full screen mode */
|
||||
@media (max-width: 1200px) {
|
||||
.fullscreen-mode .multi-columns-container {
|
||||
max-width: 1000px;
|
||||
flex-direction: column;
|
||||
gap: 15px;
|
||||
}
|
||||
|
||||
.fullscreen-mode .column-container {
|
||||
max-width: 100%;
|
||||
/* Prevent wrapping in fullscreen table cells */
|
||||
.fullscreen-mode .personnel-info-table {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.fullscreen-mode .personnel-info-table tbody {
|
||||
display: block;
|
||||
max-height: calc(100% - 40px);
|
||||
overflow-y: auto;
|
||||
overflow-x: hidden;
|
||||
}
|
||||
|
||||
.fullscreen-mode .personnel-info-table thead,
|
||||
.fullscreen-mode .personnel-info-table tbody tr {
|
||||
display: table;
|
||||
width: 100%;
|
||||
table-layout: fixed;
|
||||
}
|
||||
|
||||
.fullscreen-mode .personnel-info-table thead {
|
||||
width: calc(100% - 8px); /* Account for scrollbar */
|
||||
}
|
||||
|
||||
/* Responsive adjustments for smaller screens in fullscreen */
|
||||
@media (max-width: 1400px) {
|
||||
.fullscreen-mode .multi-columns-container {
|
||||
gap: clamp(8px, 1vw, 15px);
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
.fullscreen-mode .app-main-content-list-container {
|
||||
padding: 10px;
|
||||
@media (max-width: 1200px) {
|
||||
.fullscreen-mode .column-container:nth-child(2) .row.mb-4 {
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.fullscreen-mode .multi-columns-container {
|
||||
gap: 10px;
|
||||
.fullscreen-mode .carousel {
|
||||
height: clamp(100px, 12vh, 180px) !important;
|
||||
}
|
||||
|
||||
.fullscreen-mode .column-card {
|
||||
margin-bottom: 15px;
|
||||
.fullscreen-mode .carousel-item img {
|
||||
height: clamp(100px, 12vh, 180px) !important;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-height: 700px) {
|
||||
.fullscreen-mode .carousel {
|
||||
height: clamp(80px, 10vh, 140px) !important;
|
||||
}
|
||||
|
||||
.fullscreen-mode .carousel-item img {
|
||||
height: clamp(80px, 10vh, 140px) !important;
|
||||
}
|
||||
|
||||
.fullscreen-mode h6 {
|
||||
margin-bottom: clamp(4px, 0.5vw, 10px) !important;
|
||||
}
|
||||
}
|
||||
|
||||
/* Additional fullscreen utility classes */
|
||||
.fullscreen-mode .fullscreen-title {
|
||||
font-weight: 600;
|
||||
margin-bottom: clamp(8px, 1vw, 16px);
|
||||
}
|
||||
|
||||
.fullscreen-mode .time-weather-row {
|
||||
flex: 0 0 auto;
|
||||
min-height: 0;
|
||||
}
|
||||
|
||||
.fullscreen-mode .attendance-note-wrapper,
|
||||
.fullscreen-mode .gallery-wrapper {
|
||||
flex: 0 0 auto;
|
||||
margin-bottom: clamp(8px, 1vw, 16px);
|
||||
}
|
||||
|
||||
.fullscreen-mode .fullscreen-date-display {
|
||||
margin-bottom: clamp(4px, 0.5vw, 12px);
|
||||
}
|
||||
|
||||
.fullscreen-mode .fullscreen-time {
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.fullscreen-mode .fullscreen-logo {
|
||||
height: clamp(20px, 2.5vw, 35px);
|
||||
margin-right: clamp(6px, 0.8vw, 12px);
|
||||
}
|
||||
|
||||
.fullscreen-mode .fullscreen-intro {
|
||||
line-height: 1.4;
|
||||
overflow: hidden;
|
||||
display: -webkit-box;
|
||||
-webkit-line-clamp: 4;
|
||||
-webkit-box-orient: vertical;
|
||||
}
|
||||
|
||||
.fullscreen-mode .fullscreen-note-img {
|
||||
width: 100%;
|
||||
object-fit: cover;
|
||||
border-radius: clamp(4px, 0.5vw, 8px);
|
||||
}
|
||||
|
||||
.fullscreen-mode .fullscreen-placeholder {
|
||||
height: clamp(60px, 8vh, 120px);
|
||||
background-color: rgba(248, 249, 250, 0.8);
|
||||
border: 2px dashed #dee2e6;
|
||||
border-radius: clamp(4px, 0.5vw, 8px);
|
||||
}
|
||||
|
||||
.fullscreen-mode .fullscreen-carousel {
|
||||
border-radius: clamp(4px, 0.5vw, 8px);
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.fullscreen-mode .fullscreen-carousel-img {
|
||||
object-fit: cover;
|
||||
border-radius: clamp(4px, 0.5vw, 8px);
|
||||
}
|
||||
|
||||
/* InfoScreen custom classes for moving inline styles to CSS */
|
||||
|
||||
.info-title {
|
||||
|
||||
@ -25,6 +25,12 @@ import Login from "./components/login/Login";
|
||||
import RoutesHistory from "./components/trans-routes/RoutesHistory";
|
||||
import RouteTemplatesList from "./components/trans-routes/RouteTemplatesList";
|
||||
import RouteTemplateEdit from "./components/trans-routes/RouteTemplateEdit";
|
||||
import DailyTemplatesList from "./components/trans-routes/DailyTemplatesList";
|
||||
import ViewDailyTemplate from "./components/trans-routes/ViewDailyTemplate";
|
||||
import ViewDailyTemplateRoute from "./components/trans-routes/ViewDailyTemplateRoute";
|
||||
import UpdateDailyTemplateRoute from "./components/trans-routes/UpdateDailyTemplateRoute";
|
||||
import CreateTemplateRoute from "./components/trans-routes/CreateTemplateRoute";
|
||||
import MealStatus from "./components/trans-routes/MealStatus";
|
||||
import CreateCustomer from "./components/customers/CreateCustomer";
|
||||
import CustomersList from "./components/customers/CustomersList";
|
||||
import ViewCustomer from "./components/customers/ViewCustomer";
|
||||
@ -119,6 +125,11 @@ function App() {
|
||||
<Route path="history" element={<RoutesHistory />} />
|
||||
<Route path="templates" element={<RouteTemplatesList />} />
|
||||
<Route path="templates/edit/:id" element={<RouteTemplateEdit />} />
|
||||
<Route path="daily-templates/list" element={<DailyTemplatesList />} />
|
||||
<Route path="daily-templates/view/:id" element={<ViewDailyTemplate />} />
|
||||
<Route path="daily-templates/:id/view-route/:routeId" element={<ViewDailyTemplateRoute />} />
|
||||
<Route path="daily-templates/:id/update-route/:routeId" element={<UpdateDailyTemplateRoute />} />
|
||||
<Route path="daily-templates/:id/create-route" element={<CreateTemplateRoute />} />
|
||||
<Route path="route-signature" element={<RouteSignatureList/>} />
|
||||
<Route path="route-report-with-signature/:id" element={<RouteReportWithSignature/>} />
|
||||
</Route>
|
||||
@ -158,6 +169,7 @@ function App() {
|
||||
|
||||
<Route path="/seating" element={<Seating />} />
|
||||
<Route path="/center-calendar" element={<CenterCalendar/>} />
|
||||
<Route path="/meal-status" element={<MealStatus />} />
|
||||
|
||||
<Route path="/medical" element={<Medical />}>
|
||||
<Route path="" element={<Navigate replace to="index" />} />
|
||||
|
||||
@ -646,7 +646,7 @@ const AdminView = () => {
|
||||
<>
|
||||
<div className="list row mb-4">
|
||||
<Breadcrumb>
|
||||
<Breadcrumb.Item>Dashboard</Breadcrumb.Item>
|
||||
<Breadcrumb.Item href="/">Dashboard</Breadcrumb.Item>
|
||||
<Breadcrumb.Item active>
|
||||
Admin View
|
||||
</Breadcrumb.Item>
|
||||
|
||||
@ -660,7 +660,7 @@ const CustomerReport = () => {
|
||||
<>
|
||||
<div className="list row mb-4">
|
||||
<Breadcrumb>
|
||||
<Breadcrumb.Item>Transportation</Breadcrumb.Item>
|
||||
<Breadcrumb.Item href="/trans-routes/dashboard">Transportation</Breadcrumb.Item>
|
||||
<Breadcrumb.Item active>
|
||||
Customer Reports
|
||||
</Breadcrumb.Item>
|
||||
|
||||
@ -17,6 +17,7 @@ import '@schedule-x/theme-default/dist/calendar.css';
|
||||
import { Archive, PencilSquare, Filter } from "react-bootstrap-icons";
|
||||
import DatePicker from "react-datepicker";
|
||||
import { vehicleSlice } from "../../store";
|
||||
import Select from 'react-select';
|
||||
// import { Scheduler } from "@aldabil/react-scheduler";
|
||||
|
||||
|
||||
@ -40,6 +41,8 @@ const EventsCalendar = () => {
|
||||
const eventModalService = createEventModalPlugin();
|
||||
const eventRecurrence = createEventRecurrencePlugin();
|
||||
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())
|
||||
@ -59,6 +62,65 @@ const EventsCalendar = () => {
|
||||
const [vehicles, setVehicles] = useState([]);
|
||||
const [employees, setEmployees] = 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('');
|
||||
|
||||
// Activity specific fields
|
||||
const [newActivityCategory, setNewActivityCategory] = useState('');
|
||||
|
||||
// Attendance Note specific fields
|
||||
const [newAttendanceCustomer, setNewAttendanceCustomer] = useState(null);
|
||||
const [newAttendanceReason, setNewAttendanceReason] = useState('');
|
||||
|
||||
// Meal Plan specific fields
|
||||
const [newMealType, setNewMealType] = useState('');
|
||||
const [newMealIngredients, setNewMealIngredients] = useState('');
|
||||
|
||||
// Important Dates specific fields
|
||||
const [newReminderTitleCategory, setNewReminderTitleCategory] = useState('');
|
||||
const [newReminderAssociatedEntity, setNewReminderAssociatedEntity] = useState(null);
|
||||
|
||||
// Helper function to format name from "lastname, firstname" to "firstname lastname"
|
||||
const formatFullName = (name) => {
|
||||
if (!name) return '';
|
||||
if (name.includes(',')) {
|
||||
const parts = name.split(',').map(part => part.trim());
|
||||
return `${parts[1]} ${parts[0]}`; // firstname lastname
|
||||
}
|
||||
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 doctorName ? `${fullName} - ${doctorName}` : fullName;
|
||||
};
|
||||
|
||||
const eventTypeMap = {
|
||||
medicalCalendar: 'medical',
|
||||
activitiesCalendar: 'activity',
|
||||
@ -81,34 +143,57 @@ const EventsCalendar = () => {
|
||||
events: events,
|
||||
plugins: [eventModalService, eventsServicePlugin, eventRecurrence],
|
||||
callbacks: {
|
||||
onSelectedDateUpdate(date) {
|
||||
setFromDate(new Date(new Date(date).getFullYear(), new Date(date).getMonth(), 1));
|
||||
setToDate(new Date(new Date(date).getFullYear(), new Date(date).getMonth() + 1, 0));
|
||||
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) {
|
||||
|
||||
|
||||
setNewEventStartDateTime(new Date(date))
|
||||
setNewEventEndDateTime(new Date(date));
|
||||
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("CenterCalendar - 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);
|
||||
return eventDate.isSame(now, 'month');
|
||||
});
|
||||
}
|
||||
|
||||
// Filter events within the calendar's visible range
|
||||
const rangeStart = moment(currentRangeStart);
|
||||
const rangeEnd = moment(currentRangeEnd);
|
||||
|
||||
return events.filter(event => {
|
||||
const eventDate = moment(event.start_time);
|
||||
return eventDate.isBetween(rangeStart, rangeEnd, 'day', '[]');
|
||||
});
|
||||
};
|
||||
|
||||
const getGroupedEvents = () => {
|
||||
const eventsDateMap = new Map();
|
||||
for (const eventItem of events) {
|
||||
const filteredEvents = getFilteredEvents();
|
||||
for (const eventItem of filteredEvents) {
|
||||
const dateString = moment(eventItem.start_time).format('MMM Do, YYYY');
|
||||
if (eventsDateMap.has(dateString)) {
|
||||
eventsDateMap.set(dateString, [...eventsDateMap.get(dateString), eventItem]);
|
||||
@ -134,7 +219,7 @@ const EventsCalendar = () => {
|
||||
EmployeeService.getAllEmployees().then((data) => {
|
||||
setEmployees(data.data);
|
||||
});
|
||||
CustomerService.getAllCustomers().then((data) => {
|
||||
CustomerService.getAllActiveCustomers().then((data) => {
|
||||
setCustomers(data.data);
|
||||
});
|
||||
ResourceService.getAll().then((data) => {
|
||||
@ -171,7 +256,8 @@ const EventsCalendar = () => {
|
||||
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 = currentTab==='medicalCalendar' ? `${customerField}, provider: ${doctorField}`: item.title;
|
||||
item.title = currentTab==='medicalCalendar' ? formatEventTitle(customerField, item?.start_time) : item.title;
|
||||
item.description = currentTab==='medicalCalendar' ? getEventDescription(customerField, doctorField) : item.description; // Full info for tooltip
|
||||
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);
|
||||
@ -203,12 +289,15 @@ const EventsCalendar = () => {
|
||||
}
|
||||
}, [customers, resources, timeData, currentTab, allEvents, showDeletedItems])
|
||||
|
||||
|
||||
|
||||
useEffect(() => {
|
||||
if (events && calendar) {
|
||||
console.log("CenterCalendar useEffect - events:", events.length, "range:", currentRangeStart, "to", currentRangeEnd);
|
||||
calendar?.eventsService?.set(events);
|
||||
setGroupedEvents(getGroupedEvents());
|
||||
}
|
||||
}, [events]);
|
||||
}, [events, currentRangeStart, currentRangeEnd]);
|
||||
|
||||
|
||||
const redirectToAdmin = () => {
|
||||
@ -303,13 +392,23 @@ const EventsCalendar = () => {
|
||||
const customComponents = {
|
||||
eventModal: ({calendarEvent}) => {
|
||||
return <>
|
||||
|
||||
<div className="sx__event-modal__title">{currentTab === 'medicalCalendar' ? calendarEvent?.customer : calendarEvent?.title}</div>
|
||||
{ calendarEvent?.doctor && <div className="sx__event-modal__time">{`${calendarEvent?.doctor}`}</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">
|
||||
{currentTab === 'medicalCalendar' && <PencilSquare size={16} onClick={() => goToEdit(calendarEvent?.id)} className="me-4"></PencilSquare>}
|
||||
<Archive size={16} onClick={() =>{disableEvent(calendarEvent?.id)}}></Archive> </div>
|
||||
<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)}
|
||||
style={{ cursor: 'pointer' }}
|
||||
title="Edit"
|
||||
/>
|
||||
<Archive
|
||||
size={16}
|
||||
onClick={() => disableEvent(calendarEvent?.id)}
|
||||
style={{ cursor: 'pointer' }}
|
||||
title="Delete"
|
||||
/>
|
||||
</div>
|
||||
</>
|
||||
}
|
||||
};
|
||||
@ -341,29 +440,126 @@ const EventsCalendar = () => {
|
||||
},
|
||||
);
|
||||
|
||||
// Get legend options based on current tab
|
||||
const getLegendOptions = () => {
|
||||
switch (currentTab) {
|
||||
case 'medicalCalendar':
|
||||
return EventsService.labelOptions;
|
||||
case 'activitiesCalendar':
|
||||
return EventsService.activityColorOptions;
|
||||
case 'incidentsCalendar':
|
||||
return EventsService.incidentColorOptions;
|
||||
case 'mealPlanCalendar':
|
||||
return EventsService.mealPlanColorOptions;
|
||||
case 'reminderDatesCalendar':
|
||||
return EventsService.colorOptions;
|
||||
default:
|
||||
return [];
|
||||
}
|
||||
};
|
||||
|
||||
const calendarView = <div className="multi-columns-container">
|
||||
<div className="column-container" style={{'minWidth': '1000px'}}>
|
||||
{calendar && <ScheduleXCalendar customComponents={customComponents} calendarApp={calendar} />}
|
||||
{/* Dynamic Legend */}
|
||||
<div className="calendar-legend mt-3">
|
||||
<h6 className="text-muted mb-2" style={{ fontSize: '12px' }}>Legend:</h6>
|
||||
<div className="d-flex flex-wrap gap-3">
|
||||
{getLegendOptions().map((item) => (
|
||||
<div key={item.value} className="d-flex align-items-center">
|
||||
<span
|
||||
className={`event-${item.value}`}
|
||||
style={{
|
||||
width: '16px',
|
||||
height: '16px',
|
||||
borderRadius: '4px',
|
||||
display: 'inline-block',
|
||||
marginRight: '6px'
|
||||
}}
|
||||
></span>
|
||||
<span style={{ fontSize: '12px' }}>{item.label}</span>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="column-container">
|
||||
<div className="column-card">
|
||||
<h6 className="text-primary me-4">List</h6>
|
||||
<div className="column-card" style={{ maxHeight: '800px', overflowY: 'auto', overflowX: 'hidden', padding: '16px' }}>
|
||||
<h6 className="text-primary mb-3">List</h6>
|
||||
{(!groupedEvents || groupedEvents.size === 0) && (
|
||||
<div style={{ padding: '24px', textAlign: 'center', color: '#999' }}>
|
||||
No events for this period
|
||||
</div>
|
||||
)}
|
||||
{
|
||||
Array.from(groupedEvents?.keys())?.map((key) => {
|
||||
return <>
|
||||
return <div key={key}>
|
||||
<h6 className="text-primary me-2">{key}</h6>
|
||||
{
|
||||
groupedEvents.get(key).map(eventItem => <div className={`event-${eventItem.color || 'primary'} mb-4 event-list-item-container`}>
|
||||
<div className="event-item-flex">
|
||||
<div className="sx__month-agenda-event__title">{currentTab === 'medicalCalendar' ? eventItem.customer : eventItem.title}</div>
|
||||
<div className="sx__event-modal__time">{`${moment(eventItem?.start_time).format('hh:mm A')} ${eventItem?.end_time ? `- ${moment(eventItem?.end_time).format('hh:mm A')}` : '' }`}</div>
|
||||
groupedEvents.get(key).map(eventItem => <div
|
||||
key={eventItem.id}
|
||||
className={`event-${eventItem.color || 'primary'} mb-3 event-list-item-container`}
|
||||
onClick={() => currentTab === 'medicalCalendar' && goToView(eventItem.id)}
|
||||
style={{ cursor: currentTab === 'medicalCalendar' ? 'pointer' : 'default', padding: '8px 12px', borderRadius: '4px' }}
|
||||
>
|
||||
{/* Medical Events */}
|
||||
{currentTab === 'medicalCalendar' && (
|
||||
<>
|
||||
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center' }}>
|
||||
<span className="sx__month-agenda-event__title">{formatFullName(eventItem.customer)}</span>
|
||||
<span style={{ fontSize: '12px' }}>{moment(eventItem?.start_time).format('HH:mm')} - {moment(eventItem?.stop_time || eventItem?.start_time).format('HH:mm')}</span>
|
||||
</div>
|
||||
<div className="sx__event-modal__time with-padding">{ currentTab === 'medicalCalendar' ? `provider: ${eventItem?.doctor}` : eventItem?.description}</div>
|
||||
{eventItem?.event_prediction_date && <div className="sx__event-modal__time with-padding">{ `Vehicle: ${eventItem?.target_name}`}</div>}
|
||||
{eventItem?.event_prediction_date && <div className="sx__event-modal__time with-padding">{ `Deadline: ${moment(eventItem?.event_prediction_date).format('MM/DD/YYYY')}`}</div>}
|
||||
<div className="sx__event-modal__time" style={{ fontSize: '12px', marginTop: '4px' }}>Provider: {eventItem?.doctor || '-'}</div>
|
||||
</>
|
||||
)}
|
||||
|
||||
{/* Activities */}
|
||||
{currentTab === 'activitiesCalendar' && (
|
||||
<>
|
||||
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center' }}>
|
||||
<span className="sx__month-agenda-event__title">{eventItem.title}</span>
|
||||
<span style={{ fontSize: '12px' }}>{moment(eventItem?.start_time).format('HH:mm')}</span>
|
||||
</div>
|
||||
<div className="sx__event-modal__time" style={{ fontSize: '12px', marginTop: '4px' }}>Location: {eventItem?.event_location || '-'}</div>
|
||||
</>
|
||||
)}
|
||||
|
||||
{/* Attendance Notes */}
|
||||
{currentTab === 'incidentsCalendar' && (
|
||||
<>
|
||||
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center' }}>
|
||||
<span className="sx__month-agenda-event__title">{eventItem?.target_name || eventItem.title}</span>
|
||||
<span style={{ fontSize: '12px' }}>{moment(eventItem?.start_time).format('MM/DD')}</span>
|
||||
</div>
|
||||
<div className="sx__event-modal__time" style={{ fontSize: '12px', marginTop: '4px' }}>Reason: {eventItem?.description || '-'}</div>
|
||||
</>
|
||||
)}
|
||||
|
||||
{/* Meal Plan */}
|
||||
{currentTab === 'mealPlanCalendar' && (
|
||||
<>
|
||||
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center' }}>
|
||||
<span className="sx__month-agenda-event__title">{eventItem.title}</span>
|
||||
<span style={{ fontSize: '12px', textTransform: 'capitalize' }}>{eventItem?.meal_type || '-'}</span>
|
||||
</div>
|
||||
<div className="sx__event-modal__time" style={{ fontSize: '12px', marginTop: '4px' }}>Ingredients: {eventItem?.ingredients || '-'}</div>
|
||||
</>
|
||||
)}
|
||||
|
||||
{/* Important Dates / Reminders */}
|
||||
{currentTab === 'reminderDatesCalendar' && (
|
||||
<>
|
||||
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center' }}>
|
||||
<span className="sx__month-agenda-event__title">{eventItem.title}</span>
|
||||
<span style={{ fontSize: '12px' }}>{moment(eventItem?.start_time).format('MM/DD')}</span>
|
||||
</div>
|
||||
<div className="sx__event-modal__time" style={{ fontSize: '12px', marginTop: '4px' }}>{eventItem?.target_type === 'vehicle' ? 'Vehicle' : 'Person'}: {eventItem?.target_name || '-'}</div>
|
||||
{eventItem?.event_prediction_date && <div className="sx__event-modal__time" style={{ fontSize: '12px', marginTop: '2px' }}>Deadline: {moment(eventItem?.event_prediction_date).format('MM/DD/YYYY')}</div>}
|
||||
</>
|
||||
)}
|
||||
</div>)
|
||||
}
|
||||
</>
|
||||
</div>
|
||||
})
|
||||
}
|
||||
</div>
|
||||
@ -383,54 +579,162 @@ const handleClose = () => {
|
||||
setNewEventTargetType('');
|
||||
setShowCreationModal(false);
|
||||
setNewEventEndDateTime(undefined);
|
||||
setNewEventColor('');
|
||||
setNewEventRecurring(undefined);
|
||||
setNewEventReminderType('');
|
||||
// Reset medical fields
|
||||
setNewEventCustomer('');
|
||||
setNewEventResource('');
|
||||
setNewEventInterpreter('');
|
||||
setNewEventFasting('');
|
||||
setNewEventNeedId('');
|
||||
setNewEventNewPatient('');
|
||||
setNewEventDisability('');
|
||||
setNewEventTransMethod('');
|
||||
// Reset activity fields
|
||||
setNewActivityCategory('');
|
||||
// Reset attendance note fields
|
||||
setNewAttendanceCustomer(null);
|
||||
setNewAttendanceReason('');
|
||||
// Reset meal plan fields
|
||||
setNewMealType('');
|
||||
setNewMealIngredients('');
|
||||
// Reset important dates fields
|
||||
setNewReminderTitleCategory('');
|
||||
setNewReminderAssociatedEntity(null);
|
||||
}
|
||||
const handleSave = () => {
|
||||
const data = {
|
||||
title: newEventTitle,
|
||||
description: newEventDescription,
|
||||
const userName = localStorage.getItem('user') && JSON.parse(localStorage.getItem('user'))?.name;
|
||||
|
||||
let data = {
|
||||
type: newEventType,
|
||||
department: newEventDepartment,
|
||||
status: 'active',
|
||||
create_by: userName,
|
||||
edit_by: userName,
|
||||
edit_date: new Date(),
|
||||
create_date: new Date(),
|
||||
edit_history: [{ employee: userName, date: new Date() }]
|
||||
};
|
||||
|
||||
// Handle Medical Appointments
|
||||
if (currentTab === 'medicalCalendar') {
|
||||
const selectedCustomer = customers.find(c => c.id === newEventCustomer);
|
||||
const selectedResource = resources.find(r => r.id === newEventResource);
|
||||
data = {
|
||||
...data,
|
||||
title: selectedCustomer ? `${selectedCustomer.name} - Medical Appointment` : 'Medical Appointment',
|
||||
start_time: newEventStartDateTime,
|
||||
stop_time: newEventStartDateTime,
|
||||
color: newEventColor,
|
||||
source_type: newEventSourceType,
|
||||
source_uuid: newEventSource?.value,
|
||||
source_name: newEventSource?.label,
|
||||
target_type: newEventTargetType,
|
||||
target_uuid: newEventTarget?.value,
|
||||
target_name: newEventTarget?.label,
|
||||
event_location: newEventLocation,
|
||||
event_prediction_date: newEventFutureDate && moment(newEventFutureDate).format('MM/DD/YYYY'),
|
||||
event_reminder_type: newEventReminderType,
|
||||
rrule: newEventRecurring,
|
||||
status: 'active',
|
||||
create_by: localStorage.getItem('user') && JSON.parse(localStorage.getItem('user'))?.name,
|
||||
edit_by: localStorage.getItem('user') && JSON.parse(localStorage.getItem('user'))?.name,
|
||||
edit_date: new Date(),
|
||||
create_date: new Date(),
|
||||
edit_history: [{ employee: localStorage.getItem('user') && JSON.parse(localStorage.getItem('user'))?.name, date: new Date() }]
|
||||
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,
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
// Handle Activities
|
||||
if (currentTab === 'activitiesCalendar') {
|
||||
data = {
|
||||
...data,
|
||||
title: newEventTitle,
|
||||
start_time: newEventStartDateTime,
|
||||
stop_time: newEventStartDateTime,
|
||||
activity_category: newActivityCategory,
|
||||
color: newActivityCategory, // category maps to color for display
|
||||
event_location: newEventLocation,
|
||||
rrule: newEventRecurring,
|
||||
};
|
||||
}
|
||||
|
||||
// Handle Attendance Notes (Incidents)
|
||||
if (currentTab === 'incidentsCalendar') {
|
||||
data = {
|
||||
...data,
|
||||
title: newAttendanceCustomer?.label ? `Attendance Note - ${newAttendanceCustomer.label}` : 'Attendance Note',
|
||||
start_time: newEventStartDateTime,
|
||||
stop_time: newEventStartDateTime,
|
||||
target_type: 'customer',
|
||||
target_uuid: newAttendanceCustomer?.value,
|
||||
target_name: newAttendanceCustomer?.label,
|
||||
description: newAttendanceReason,
|
||||
color: 'blue', // Default color for attendance notes
|
||||
};
|
||||
}
|
||||
|
||||
// Handle Meal Plan
|
||||
if (currentTab === 'mealPlanCalendar') {
|
||||
data = {
|
||||
...data,
|
||||
title: newEventTitle,
|
||||
start_time: newEventStartDateTime,
|
||||
stop_time: newEventStartDateTime,
|
||||
meal_type: newMealType,
|
||||
color: newMealType === 'breakfast' ? 'brown' : (newMealType === 'lunch' ? 'green' : 'red'),
|
||||
ingredients: newMealIngredients,
|
||||
rrule: newEventRecurring,
|
||||
};
|
||||
}
|
||||
|
||||
// Handle Important Dates (Reminders)
|
||||
if (currentTab === 'reminderDatesCalendar') {
|
||||
const isMemberCategory = ['birthday', 'adcaps_completion', 'center_qualification_expiration'].includes(newReminderTitleCategory);
|
||||
data = {
|
||||
...data,
|
||||
title: getReminderTitleLabel(newReminderTitleCategory),
|
||||
start_time: newEventStartDateTime,
|
||||
stop_time: newEventStartDateTime,
|
||||
event_reminder_type: newReminderTitleCategory,
|
||||
target_type: isMemberCategory ? 'customer' : 'vehicle',
|
||||
target_uuid: newReminderAssociatedEntity?.value,
|
||||
target_name: newReminderAssociatedEntity?.label,
|
||||
rrule: newEventRecurring,
|
||||
color: isMemberCategory ? 'blue' : 'orange',
|
||||
};
|
||||
}
|
||||
|
||||
EventsService.createNewEvent(data).then(() => {
|
||||
EventsService.getAllEvents({ from: EventsService.formatDate(fromDate), to: EventsService.formatDate(toDate) }).then((data) => {
|
||||
setAllEvents(data.data);
|
||||
setShowCreationModal(false);
|
||||
setNewEventDescription('');
|
||||
setNewEventTitle('');
|
||||
setNewEventLocation('');
|
||||
setNewEventSource(undefined);
|
||||
setNewEventTarget(undefined);
|
||||
setNewEventStartDateTime(undefined);
|
||||
setNewEventType('');
|
||||
handleClose();
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
// Helper function to get reminder title label
|
||||
const getReminderTitleLabel = (value) => {
|
||||
const memberOptions = {
|
||||
'birthday': 'Birthday',
|
||||
'adcaps_completion': 'ADCAPS Completion',
|
||||
'center_qualification_expiration': 'Center Qualification Expiration'
|
||||
};
|
||||
const vehicleOptions = {
|
||||
'oil_change': 'Oil Change',
|
||||
'monthly_vehicle_inspection': 'Monthly Vehicle Inspection',
|
||||
'emissions_inspection': 'Emissions Inspection',
|
||||
'insurance_expiration': 'Insurance Expiration',
|
||||
'license_plate_expiration': 'License Plate Expiration'
|
||||
};
|
||||
return memberOptions[value] || vehicleOptions[value] || value;
|
||||
};
|
||||
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className="list row mb-4">
|
||||
<Breadcrumb>
|
||||
<Breadcrumb.Item>General</Breadcrumb.Item>
|
||||
<Breadcrumb.Item href="/">General</Breadcrumb.Item>
|
||||
<Breadcrumb.Item active>
|
||||
Calendar
|
||||
</Breadcrumb.Item>
|
||||
@ -451,7 +755,7 @@ const handleSave = () => {
|
||||
<Tab eventKey="activitiesCalendar" title="Activities">
|
||||
|
||||
</Tab>
|
||||
<Tab eventKey="incidentsCalendar" title="Important Notes And Incidents">
|
||||
<Tab eventKey="incidentsCalendar" title="Attendance">
|
||||
|
||||
</Tab>
|
||||
<Tab eventKey="mealPlanCalendar" title="Meal Plan">
|
||||
@ -476,189 +780,283 @@ const handleSave = () => {
|
||||
<Dropdown.Menu as={customMenu}/>
|
||||
</Dropdown>
|
||||
</div>
|
||||
<Modal show={showCreationModal && currentTab !== 'medicalCalendar'} onHide={handleClose}>
|
||||
<Modal show={showCreationModal} onHide={handleClose} size="sm" dialogClassName="calendar-event-modal">
|
||||
<Modal.Header closeButton>
|
||||
<Modal.Title>Create New Calendar Item</Modal.Title>
|
||||
<Modal.Title>
|
||||
{currentTab === 'medicalCalendar' && 'New Medical Appointment'}
|
||||
{currentTab === 'activitiesCalendar' && 'New Activity'}
|
||||
{currentTab === 'incidentsCalendar' && 'New Attendance Note'}
|
||||
{currentTab === 'mealPlanCalendar' && 'New Meal Item'}
|
||||
{currentTab === 'reminderDatesCalendar' && 'New Important Date'}
|
||||
</Modal.Title>
|
||||
</Modal.Header>
|
||||
<Modal.Body>
|
||||
<div className="app-main-content-fields-section">
|
||||
<div className="me-4">
|
||||
<div className="field-label">Title
|
||||
<span className="required">*</span>
|
||||
</div>
|
||||
<input type="text" placeholder="Briefly Describe activity, incident, menu or reminder" value={newEventTitle || ''} onChange={e => setNewEventTitle(e.target.value)}/>
|
||||
</div>
|
||||
</div>
|
||||
<div className="app-main-content-fields-section">
|
||||
<div className="me-4">
|
||||
<div className="field-label">Description
|
||||
<span className="required">*</span>
|
||||
</div>
|
||||
<textarea type="text" placeholder="Details for activity, incident, menu item and ingredients or reminder," value={newEventDescription || ''} onChange={e => setNewEventDescription(e.target.value)}/>
|
||||
</div>
|
||||
</div>
|
||||
<div className="app-main-content-fields-section">
|
||||
<div className="me-4">
|
||||
<div className="field-label">Type
|
||||
<span className="required">*</span>
|
||||
</div>
|
||||
<div className="field-value">{newEventType}</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="app-main-content-fields-section">
|
||||
<div className="me-4">
|
||||
<div className="field-label">Want event to be recurring?
|
||||
<select value={newEventRecurring} onChange={(e) => setNewEventRecurring(e.target.value)}>
|
||||
<option value=""></option>
|
||||
<option value="FREQ=YEARLY">Yearly</option>
|
||||
<option value="FREQ=MONTHLY">Monthly</option>
|
||||
<option value="FREQ=WEEKLY">Weekly</option>
|
||||
<option value="FREQ=DAILY">Daily</option>
|
||||
{/* Medical Appointment Fields */}
|
||||
{currentTab === 'medicalCalendar' && (
|
||||
<>
|
||||
<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>
|
||||
</div>
|
||||
<div className="app-main-content-fields-section">
|
||||
<div className="me-4">
|
||||
<div className="field-label">Start Time
|
||||
<span className="required">*</span>
|
||||
<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}
|
||||
showTimeInput
|
||||
timeInputLabel="Time:"
|
||||
showTimeSelect
|
||||
timeFormat="HH:mm"
|
||||
timeIntervals={15}
|
||||
dateFormat="MM/dd/yyyy, HH:mm"
|
||||
></DatePicker>
|
||||
/>
|
||||
</div>
|
||||
<div className="me-4">
|
||||
<div className="field-label">End Time
|
||||
<span className="required">*</span>
|
||||
<span className="field-blurb float-right">Make sure end time is later than start time </span>
|
||||
<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>
|
||||
</>
|
||||
)}
|
||||
|
||||
{/* Activity 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={newEventTitle || ''} onChange={e => setNewEventTitle(e.target.value)}/>
|
||||
</div>
|
||||
<div className="mb-3">
|
||||
<div className="field-label">Date & Time</div>
|
||||
<DatePicker
|
||||
selected={newEventEndDateTime}
|
||||
onChange={setNewEventEndDateTime}
|
||||
showTimeInput
|
||||
timeInputLabel="Time:"
|
||||
className="form-control"
|
||||
selected={newEventStartDateTime}
|
||||
onChange={setNewEventStartDateTime}
|
||||
showTimeSelect
|
||||
timeFormat="HH:mm"
|
||||
timeIntervals={15}
|
||||
dateFormat="MM/dd/yyyy, HH:mm"
|
||||
></DatePicker>
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
{ currentTab !== 'mealPlanCalendar' && <> <div className="app-main-content-fields-section">
|
||||
<div className="me-4">
|
||||
<div className="field-label">Who will we take care of in this event:</div>
|
||||
<select value={newEventTargetType} onChange={(e) => setNewEventTargetType(e.target.value)}>
|
||||
<option value=""></option>
|
||||
<option value="vehicle">Vehicle</option>
|
||||
<option value="customer">Customer</option>
|
||||
<option value="employee">Employee</option>
|
||||
<option value="notApplicable">Not Applicable</option>
|
||||
<div className="mb-3">
|
||||
<div className="field-label">Category<span className="required">*</span></div>
|
||||
<select className="form-control" value={newActivityCategory} onChange={(e) => setNewActivityCategory(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>
|
||||
{newEventTargetType && newEventTargetType !== '' && newEventTargetType !== 'notApplicable' && <div className="me-4">
|
||||
<div className="field-label">Please select a candidate</div>
|
||||
<select value={newEventTarget?.value || ''} onChange={(e) => {
|
||||
const selectedOption = e.target.options[e.target.selectedIndex];
|
||||
setNewEventTarget({
|
||||
value: selectedOption.value,
|
||||
label: selectedOption.text
|
||||
})
|
||||
<div className="mb-3">
|
||||
<div className="field-label">Location</div>
|
||||
<input type="text" className="form-control" placeholder="Enter location" value={newEventLocation || ''} onChange={e => setNewEventLocation(e.target.value)}/>
|
||||
</div>
|
||||
<div className="mb-3">
|
||||
<div className="field-label">Repeat</div>
|
||||
<select className="form-control" value={newEventRecurring} onChange={(e) => setNewEventRecurring(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 Fields */}
|
||||
{currentTab === 'incidentsCalendar' && (
|
||||
<>
|
||||
<div className="mb-3">
|
||||
<div className="field-label">Customer Name<span className="required">*</span></div>
|
||||
<Select
|
||||
value={newAttendanceCustomer}
|
||||
onChange={setNewAttendanceCustomer}
|
||||
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={newEventStartDateTime}
|
||||
onChange={setNewEventStartDateTime}
|
||||
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={newAttendanceReason || ''} onChange={e => setNewAttendanceReason(e.target.value)}/>
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
|
||||
{/* Meal Item 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={newEventTitle || ''} onChange={e => setNewEventTitle(e.target.value)}/>
|
||||
</div>
|
||||
<div className="mb-3">
|
||||
<div className="field-label">Meal Type<span className="required">*</span></div>
|
||||
<select className="form-control" value={newMealType} onChange={(e) => setNewMealType(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={newEventStartDateTime}
|
||||
onChange={setNewEventStartDateTime}
|
||||
dateFormat="MM/dd/yyyy"
|
||||
/>
|
||||
</div>
|
||||
<div className="mb-3">
|
||||
<div className="field-label">Repeat</div>
|
||||
<select className="form-control" value={newEventRecurring} onChange={(e) => setNewEventRecurring(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={newMealIngredients || ''} onChange={e => setNewMealIngredients(e.target.value)}/>
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
|
||||
{/* Important Dates Fields */}
|
||||
{currentTab === 'reminderDatesCalendar' && (
|
||||
<>
|
||||
<div className="mb-3">
|
||||
<div className="field-label">Title<span className="required">*</span></div>
|
||||
<select className="form-control" value={newReminderTitleCategory} onChange={(e) => {
|
||||
setNewReminderTitleCategory(e.target.value);
|
||||
setNewReminderAssociatedEntity(null); // Reset when category changes
|
||||
}}>
|
||||
<option value=""></option>
|
||||
{
|
||||
newEventTargetType === 'vehicle' && vehicles.map((item) => <option value={item?.id}>{item?.vehicle_number}</option>)
|
||||
}
|
||||
{
|
||||
newEventTargetType === 'customer' && customers.map((item) => <option value={item?.id}>{item?.name}</option>)
|
||||
}
|
||||
{
|
||||
newEventTargetType === 'employee' && employees.map((item) => <option value={item?.id}>{item?.name}</option>)
|
||||
}
|
||||
|
||||
</select>
|
||||
</div>}
|
||||
</div>
|
||||
|
||||
<div className="app-main-content-fields-section">
|
||||
<div className="me-4">
|
||||
<div className="field-label">Who will be responsible for this event</div>
|
||||
<select value={newEventSourceType} onChange={(e) => setNewEventSourceType(e.target.value)}>
|
||||
<option value=""></option>
|
||||
<option value="employee">Employee</option>
|
||||
<option value="resource">Resource</option>
|
||||
<option value="notApplicable">Not Applicable</option>
|
||||
</select>
|
||||
</div>
|
||||
{newEventSourceType && newEventSourceType !== '' && newEventSourceType !== 'notApplicable' && <div className="me-4">
|
||||
<div className="field-label">Please select a member</div>
|
||||
<select value={newEventSource?.value || ''} onChange={(e) => {
|
||||
const selectedOption = e.target.options[e.target.selectedIndex];
|
||||
setNewEventSource({
|
||||
value: selectedOption.value,
|
||||
label: selectedOption.text
|
||||
})
|
||||
}}>
|
||||
<option value=""></option>
|
||||
{
|
||||
newEventSourceType === 'resource' && resources.map((item) => <option value={item?.id}>{item?.name}</option>)
|
||||
}
|
||||
{
|
||||
newEventSourceType === 'employee' && employees.map((item) => <option value={item?.id}>{item?.name}</option>)
|
||||
}
|
||||
|
||||
</select>
|
||||
</div>}
|
||||
</div> </>}
|
||||
<div className="app-main-content-fields-section">
|
||||
{currentTab === 'reminderDatesCalendar' && <div className="me-4">
|
||||
<div className="field-label">Reminder Type
|
||||
</div>
|
||||
<select value={newEventReminderType} onChange={(e) => setNewEventReminderType(e.target.value)}>
|
||||
<option value=""></option>
|
||||
<option value="">Select Title</option>
|
||||
<optgroup label="Member">
|
||||
<option value="birthday">Birthday</option>
|
||||
<option value="membership">Membership Renew</option>
|
||||
<option value="payment">Customer Payment Due Date</option>
|
||||
<option value="insurance_renew">Customer Insurance Renew</option>
|
||||
<option value="insurance_expire">Vehicle Insurance Renew</option>
|
||||
<option value="title_expire">Vehicle Title Registration</option>
|
||||
<option value="emission_test">Vehicle Emission Test</option>
|
||||
<option value="oil_change">Vehicle Oil Change</option>
|
||||
<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>}
|
||||
{currentTab === 'activitiesCalendar' && <div className="me-4">
|
||||
<div className="field-label">Event Location
|
||||
</div>
|
||||
<input type="text" placeholder="Type in the location this event gonna happen if applicable" value={newEventLocation || ''} onChange={e => setNewEventLocation(e.target.value)}/>
|
||||
</div>}
|
||||
{currentTab === 'reminderDatesCalendar' && <div className="me-4">
|
||||
<div className="field-label">If this is reminder which will happen later, please select the accurate Date it gonna happen:
|
||||
{newReminderTitleCategory && (
|
||||
<div className="mb-3">
|
||||
<div className="field-label">
|
||||
{['birthday', 'adcaps_completion', 'center_qualification_expiration'].includes(newReminderTitleCategory)
|
||||
? 'Associated Person'
|
||||
: 'Associated Vehicle'}<span className="required">*</span>
|
||||
</div>
|
||||
<DatePicker selected={newEventFutureDate}
|
||||
onChange={setNewEventFutureDate} dateFormat="MM/dd/yyyy"/>
|
||||
</div>}
|
||||
|
||||
</div>
|
||||
<div className="app-main-content-fields-section">
|
||||
<div className="me-4">
|
||||
<div className="field-label">Color
|
||||
</div>
|
||||
<select value={newEventColor} onChange={e => setNewEventColor(e.target.value)}>
|
||||
<option value=""></option>
|
||||
{
|
||||
EventsService.colorOptions?.map((item) => <option value={item?.value}>{item?.label}</option>)
|
||||
<Select
|
||||
value={newReminderAssociatedEntity}
|
||||
onChange={setNewReminderAssociatedEntity}
|
||||
options={
|
||||
['birthday', 'adcaps_completion', 'center_qualification_expiration'].includes(newReminderTitleCategory)
|
||||
? 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(newReminderTitleCategory) ? 'Select Person' : 'Select Vehicle'}
|
||||
isClearable
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
<div className="mb-3">
|
||||
<div className="field-label">Date</div>
|
||||
<DatePicker
|
||||
className="form-control"
|
||||
selected={newEventStartDateTime}
|
||||
onChange={setNewEventStartDateTime}
|
||||
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' }}>
|
||||
{newReminderTitleCategory
|
||||
? (['birthday', 'adcaps_completion', 'center_qualification_expiration'].includes(newReminderTitleCategory) ? 'Member Related' : 'Vehicle Related')
|
||||
: '-'}
|
||||
</div>
|
||||
</div>
|
||||
<div className="mb-3">
|
||||
<div className="field-label">Repeat Cycle</div>
|
||||
<select className="form-control" value={newEventRecurring} onChange={(e) => setNewEventRecurring(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>
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
</Modal.Body>
|
||||
<Modal.Footer>
|
||||
<Button variant="secondary" onClick={handleClose}>
|
||||
Close
|
||||
<Button variant="secondary" size="sm" onClick={handleClose}>
|
||||
Cancel
|
||||
</Button>
|
||||
<Button variant="primary" onClick={handleSave}>
|
||||
Save Calendar Item
|
||||
<Button variant="primary" size="sm" onClick={handleSave} disabled={
|
||||
(currentTab === 'medicalCalendar' && (!newEventCustomer || !newEventResource || !newEventStartDateTime)) ||
|
||||
(currentTab === 'activitiesCalendar' && (!newEventTitle || !newActivityCategory)) ||
|
||||
(currentTab === 'incidentsCalendar' && (!newAttendanceCustomer || !newEventStartDateTime)) ||
|
||||
(currentTab === 'mealPlanCalendar' && (!newEventTitle || !newMealType)) ||
|
||||
(currentTab === 'reminderDatesCalendar' && (!newReminderTitleCategory || !newReminderAssociatedEntity))
|
||||
}>
|
||||
Save
|
||||
</Button>
|
||||
</Modal.Footer>
|
||||
</Modal>
|
||||
|
||||
@ -49,7 +49,7 @@ const CenterPhoneList = () => {
|
||||
<>
|
||||
<div className="list row mb-4">
|
||||
<Breadcrumb>
|
||||
<Breadcrumb.Item>General</Breadcrumb.Item>
|
||||
<Breadcrumb.Item href="/">General</Breadcrumb.Item>
|
||||
<Breadcrumb.Item active>
|
||||
Center Phone
|
||||
</Breadcrumb.Item>
|
||||
|
||||
@ -28,7 +28,29 @@ const CreateCenterPhone = () => {
|
||||
|
||||
}
|
||||
|
||||
const validatePhone = () => {
|
||||
const errors = [];
|
||||
|
||||
// Required fields validation
|
||||
if (!phoneTitle || phoneTitle.trim() === '') {
|
||||
errors.push('Phone Title');
|
||||
}
|
||||
if (!phoneNumber || phoneNumber.trim() === '') {
|
||||
errors.push('Phone Number');
|
||||
}
|
||||
|
||||
if (errors.length > 0) {
|
||||
window.alert(`Please fill in the following required fields:\n${errors.join('\n')}`);
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
};
|
||||
|
||||
const savePhone = () => {
|
||||
if (!validatePhone()) {
|
||||
return;
|
||||
}
|
||||
|
||||
const data = {
|
||||
phone_title: phoneTitle,
|
||||
phone_number: phoneNumber
|
||||
@ -43,10 +65,13 @@ const CreateCenterPhone = () => {
|
||||
<>
|
||||
<div className="list row mb-4">
|
||||
<Breadcrumb>
|
||||
<Breadcrumb.Item>General</Breadcrumb.Item>
|
||||
<Breadcrumb.Item active>
|
||||
<Breadcrumb.Item href="/">General</Breadcrumb.Item>
|
||||
<Breadcrumb.Item href="/center-phones/list">
|
||||
Center Phone
|
||||
</Breadcrumb.Item>
|
||||
<Breadcrumb.Item active>
|
||||
Create Center Phone
|
||||
</Breadcrumb.Item>
|
||||
</Breadcrumb>
|
||||
<div className="col-md-12 text-primary">
|
||||
<h4>
|
||||
|
||||
@ -35,7 +35,29 @@ const UpdateCenterPhone = () => {
|
||||
navigate('/center-phones/list')
|
||||
}
|
||||
|
||||
const validatePhone = () => {
|
||||
const errors = [];
|
||||
|
||||
// Required fields validation
|
||||
if (!phoneTitle || phoneTitle.trim() === '') {
|
||||
errors.push('Phone Title');
|
||||
}
|
||||
if (!phoneNumber || phoneNumber.trim() === '') {
|
||||
errors.push('Phone Number');
|
||||
}
|
||||
|
||||
if (errors.length > 0) {
|
||||
window.alert(`Please fill in the following required fields:\n${errors.join('\n')}`);
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
};
|
||||
|
||||
const savePhone = () => {
|
||||
if (!validatePhone()) {
|
||||
return;
|
||||
}
|
||||
|
||||
const data = {
|
||||
phone_title: phoneTitle,
|
||||
phone_number: phoneNumber,
|
||||
@ -48,10 +70,13 @@ const UpdateCenterPhone = () => {
|
||||
<>
|
||||
<div className="list row mb-4">
|
||||
<Breadcrumb>
|
||||
<Breadcrumb.Item>General</Breadcrumb.Item>
|
||||
<Breadcrumb.Item active>
|
||||
<Breadcrumb.Item href="/">General</Breadcrumb.Item>
|
||||
<Breadcrumb.Item href="/center-phones/list">
|
||||
Center Phone
|
||||
</Breadcrumb.Item>
|
||||
<Breadcrumb.Item active>
|
||||
Update Center Phone
|
||||
</Breadcrumb.Item>
|
||||
</Breadcrumb>
|
||||
<div className="col-md-12 text-primary">
|
||||
<h4>
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@ -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, Export } from "../../shared";
|
||||
import { Spinner, Breadcrumb, BreadcrumbItem, Tabs, Tab, Dropdown } from "react-bootstrap";
|
||||
import { Spinner, Breadcrumb, BreadcrumbItem, Tabs, Tab, Dropdown, OverlayTrigger, Popover } from "react-bootstrap";
|
||||
import { Columns, Download, Filter, PencilSquare, PersonSquare, Plus } from "react-bootstrap-icons";
|
||||
|
||||
|
||||
@ -26,6 +26,12 @@ const CustomersList = () => {
|
||||
const [serviceRequirementFilter, setServiceRequirementFilter] = useState('');
|
||||
const [tagsFilter, setTagsFilter] = useState([]);
|
||||
const [availableLabels, setAvailableLabels] = useState([]);
|
||||
const [customerAvatars, setCustomerAvatars] = useState({});
|
||||
|
||||
// Pagination state
|
||||
const [currentPage, setCurrentPage] = useState(1);
|
||||
const [itemsPerPage] = useState(25);
|
||||
const [loadingAvatarId, setLoadingAvatarId] = useState(null);
|
||||
const [columns, setColumns] = useState([
|
||||
{
|
||||
key: 'name',
|
||||
@ -126,22 +132,42 @@ const CustomersList = () => {
|
||||
navigate(`/login`);
|
||||
}
|
||||
CustomerService.getAllCustomers().then((data) => {
|
||||
setCustomers(data.data.map((item) =>{
|
||||
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));
|
||||
}).sort((a, b) => a.lastname > b.lastname ? 1: -1);
|
||||
|
||||
setCustomers(customerData);
|
||||
// Removed bulk avatar loading - now loaded on-demand when user clicks
|
||||
})
|
||||
LabelService.getAll().then((data) => {
|
||||
setAvailableLabels(data.data);
|
||||
})
|
||||
// EventsService.getAllEvents({ from: EventsService.formatDate(new Date()), to: '9999-12-31'}).then((data) => {
|
||||
// console.log('events', data.data)
|
||||
// setEvents(data.data);
|
||||
// })
|
||||
}, []);
|
||||
|
||||
// Load avatar on-demand when user clicks on profile icon
|
||||
const loadAvatarOnDemand = (customerId) => {
|
||||
if (customerAvatars[customerId] !== undefined) return; // Already loaded or attempted
|
||||
|
||||
setLoadingAvatarId(customerId);
|
||||
CustomerService.getAvatarAsBlob(`customer_avatar_${customerId}`)
|
||||
.then(result => {
|
||||
if (result?.data && result.data.size > 0) {
|
||||
const url = URL.createObjectURL(result.data);
|
||||
setCustomerAvatars(prev => ({ ...prev, [customerId]: url }));
|
||||
} else {
|
||||
setCustomerAvatars(prev => ({ ...prev, [customerId]: null }));
|
||||
}
|
||||
setLoadingAvatarId(null);
|
||||
})
|
||||
.catch(() => {
|
||||
setCustomerAvatars(prev => ({ ...prev, [customerId]: null }));
|
||||
setLoadingAvatarId(null);
|
||||
});
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
let filtered = customers;
|
||||
|
||||
@ -181,8 +207,47 @@ const CustomersList = () => {
|
||||
}
|
||||
|
||||
setFilteredCustomers(filtered);
|
||||
setCurrentPage(1); // Reset to first page when filters change
|
||||
}, [keyword, customers, showInactive, healthConditionFilter, paymentStatusFilter, serviceRequirementFilter, tagsFilter])
|
||||
|
||||
// Pagination calculations
|
||||
const totalPages = Math.ceil(filteredCustomers.length / itemsPerPage);
|
||||
const startIndex = (currentPage - 1) * itemsPerPage;
|
||||
const endIndex = startIndex + itemsPerPage;
|
||||
const paginatedCustomers = filteredCustomers.slice(startIndex, endIndex);
|
||||
|
||||
const goToPage = (page) => {
|
||||
if (page >= 1 && page <= totalPages) {
|
||||
setCurrentPage(page);
|
||||
}
|
||||
};
|
||||
|
||||
const getPageNumbers = () => {
|
||||
const pages = [];
|
||||
const maxVisiblePages = 5;
|
||||
|
||||
if (totalPages <= maxVisiblePages) {
|
||||
for (let i = 1; i <= totalPages; i++) pages.push(i);
|
||||
} else {
|
||||
if (currentPage <= 3) {
|
||||
for (let i = 1; i <= 4; i++) pages.push(i);
|
||||
pages.push('...');
|
||||
pages.push(totalPages);
|
||||
} else if (currentPage >= totalPages - 2) {
|
||||
pages.push(1);
|
||||
pages.push('...');
|
||||
for (let i = totalPages - 3; i <= totalPages; i++) pages.push(i);
|
||||
} else {
|
||||
pages.push(1);
|
||||
pages.push('...');
|
||||
for (let i = currentPage - 1; i <= currentPage + 1; i++) pages.push(i);
|
||||
pages.push('...');
|
||||
pages.push(totalPages);
|
||||
}
|
||||
}
|
||||
return pages;
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
const newCustomers = [...customers];
|
||||
const sortedCustomers = sorting.key === '' ? newCustomers : newCustomers.sort((a, b) => {
|
||||
@ -377,13 +442,31 @@ const CustomersList = () => {
|
||||
|
||||
const table = <div className="list row mb-4">
|
||||
<div className="col-md-12">
|
||||
<table className="personnel-info-table">
|
||||
<div style={{
|
||||
maxHeight: '600px',
|
||||
overflowY: 'auto',
|
||||
overflowX: 'auto',
|
||||
position: 'relative'
|
||||
}}>
|
||||
<table className="personnel-info-table" style={{ position: 'relative' }}>
|
||||
<thead>
|
||||
<tr>
|
||||
<th className="th-checkbox"><input type="checkbox" checked={checkSelectAll()} onClick={() => toggleSelectedAllItems()}></input></th>
|
||||
<th className="th-index">No.</th>
|
||||
<th className="th-checkbox" style={{ position: 'sticky', left: 0, zIndex: 3, backgroundColor: '#0066B1', paddingLeft: '14px' }}><input type="checkbox" checked={checkSelectAll()} onClick={() => toggleSelectedAllItems()}></input></th>
|
||||
<th className="th-index" style={{ position: 'sticky', left: '50px', zIndex: 3, backgroundColor: '#0066B1', paddingLeft: '14px' }}>No.</th>
|
||||
{
|
||||
columns.filter(col => col.show).map((column, index) => <th className="sortable-header" key={index}>
|
||||
columns.filter(col => col.show).map((column, index) => <th
|
||||
className="sortable-header"
|
||||
key={index}
|
||||
style={column.key === 'name' ? {
|
||||
position: 'sticky',
|
||||
left: '100px',
|
||||
zIndex: 3,
|
||||
backgroundColor: '#0066B1',
|
||||
paddingLeft: '14px',
|
||||
//borderRight: '2px solid #0056a0',
|
||||
boxShadow: '2px 0 5px rgba(0,0,0,0.1)'
|
||||
} : {}}
|
||||
>
|
||||
{column.label} <span className="float-right" onClick={() => sortTableWithField(column.key)}><img src={`/images/${getSortingImg(column.key)}.png`}></img></span>
|
||||
</th>)
|
||||
}
|
||||
@ -394,10 +477,71 @@ const CustomersList = () => {
|
||||
</thead>
|
||||
<tbody>
|
||||
{
|
||||
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>}
|
||||
paginatedCustomers.map((customer, index) => {
|
||||
const avatarData = customerAvatars[customer.id];
|
||||
const isLoadingAvatar = loadingAvatarId === customer.id;
|
||||
|
||||
const profilePopover = (
|
||||
<Popover id={`popover-${customer.id}`}>
|
||||
<Popover.Body style={{ textAlign: 'center' }}>
|
||||
{isLoadingAvatar ? (
|
||||
<Spinner animation="border" size="sm" />
|
||||
) : avatarData ? (
|
||||
<img
|
||||
src={avatarData}
|
||||
alt={customer.name}
|
||||
style={{ width: '200px', height: '200px', objectFit: 'cover', borderRadius: '8px' }}
|
||||
/>
|
||||
) : (
|
||||
<PersonSquare size={200} />
|
||||
)}
|
||||
</Popover.Body>
|
||||
</Popover>
|
||||
);
|
||||
|
||||
const actualIndex = startIndex + index; // Calculate actual index for display
|
||||
const rowBgColor = index % 2 === 0 ? 'white' : '#eee';
|
||||
const stickyBaseStyle = {
|
||||
position: 'sticky',
|
||||
zIndex: 2,
|
||||
paddingLeft: '14px',
|
||||
};
|
||||
const checkboxStickyStyle = {
|
||||
...stickyBaseStyle,
|
||||
left: 0,
|
||||
backgroundColor: rowBgColor,
|
||||
};
|
||||
const indexStickyStyle = {
|
||||
...stickyBaseStyle,
|
||||
left: '50px',
|
||||
backgroundColor: rowBgColor,
|
||||
};
|
||||
const nameStickyStyle = {
|
||||
...stickyBaseStyle,
|
||||
left: '100px',
|
||||
backgroundColor: rowBgColor,
|
||||
boxShadow: '2px 0 5px rgba(0,0,0,0.1)'
|
||||
};
|
||||
|
||||
return <tr key={customer.id}>
|
||||
<td className="td-checkbox" style={checkboxStickyStyle}><input type="checkbox" checked={selectedItems.includes(customer.id)} onClick={()=>toggleItem(customer?.id)}/></td>
|
||||
<td className="td-index" style={indexStickyStyle}>{actualIndex + 1}</td>
|
||||
{columns.find(col => col.key === 'name')?.show && <td style={nameStickyStyle}>
|
||||
{AuthService.canAddOrEditCustomers() && <PencilSquare size={16} className="clickable me-2" onClick={() => goToEdit(customer?.id)}></PencilSquare>}
|
||||
{AuthService.canViewCustomers() && (
|
||||
<OverlayTrigger
|
||||
trigger="click"
|
||||
placement="right"
|
||||
overlay={profilePopover}
|
||||
rootClose
|
||||
>
|
||||
<span style={{ cursor: 'pointer' }} onClick={() => loadAvatarOnDemand(customer.id)}>
|
||||
<PersonSquare size={16} className="clickable me-2" />
|
||||
</span>
|
||||
</OverlayTrigger>
|
||||
)}
|
||||
<span className="clickable" onClick={() => goToView(customer?.id)} style={{ textDecoration: 'underline', cursor: 'pointer' }}>{customer?.name}</span>
|
||||
</td>}
|
||||
{columns.find(col => col.key === 'chinese_name')?.show && <td>{customer?.name_cn}</td>}
|
||||
{columns.find(col => col.key === 'email')?.show && <td>{customer?.email}</td>}
|
||||
{columns.find(col => col.key === 'type')?.show && <td>{customer?.type}</td>}
|
||||
@ -432,11 +576,75 @@ const CustomersList = () => {
|
||||
|
||||
}
|
||||
</td>
|
||||
</tr>)
|
||||
</tr>
|
||||
})
|
||||
}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
{/* Pagination Controls */}
|
||||
{totalPages > 1 && (
|
||||
<div className="pagination-controls" style={{
|
||||
display: 'flex',
|
||||
justifyContent: 'space-between',
|
||||
alignItems: 'center',
|
||||
padding: '15px 0',
|
||||
marginTop: '10px'
|
||||
}}>
|
||||
<div style={{ color: '#666', fontSize: '13px' }}>
|
||||
Showing {startIndex + 1} - {Math.min(endIndex, filteredCustomers.length)} of {filteredCustomers.length} customers
|
||||
</div>
|
||||
<div style={{ display: 'flex', gap: '5px', alignItems: 'center' }}>
|
||||
<button
|
||||
className="btn btn-sm btn-outline-primary"
|
||||
onClick={() => goToPage(1)}
|
||||
disabled={currentPage === 1}
|
||||
style={{ padding: '4px 8px' }}
|
||||
>
|
||||
First
|
||||
</button>
|
||||
<button
|
||||
className="btn btn-sm btn-outline-primary"
|
||||
onClick={() => goToPage(currentPage - 1)}
|
||||
disabled={currentPage === 1}
|
||||
style={{ padding: '4px 8px' }}
|
||||
>
|
||||
Prev
|
||||
</button>
|
||||
{getPageNumbers().map((page, index) => (
|
||||
page === '...' ? (
|
||||
<span key={`ellipsis-${index}`} style={{ padding: '4px 8px' }}>...</span>
|
||||
) : (
|
||||
<button
|
||||
key={page}
|
||||
className={`btn btn-sm ${currentPage === page ? 'btn-primary' : 'btn-outline-primary'}`}
|
||||
onClick={() => goToPage(page)}
|
||||
style={{ padding: '4px 10px', minWidth: '35px' }}
|
||||
>
|
||||
{page}
|
||||
</button>
|
||||
)
|
||||
))}
|
||||
<button
|
||||
className="btn btn-sm btn-outline-primary"
|
||||
onClick={() => goToPage(currentPage + 1)}
|
||||
disabled={currentPage === totalPages}
|
||||
style={{ padding: '4px 8px' }}
|
||||
>
|
||||
Next
|
||||
</button>
|
||||
<button
|
||||
className="btn btn-sm btn-outline-primary"
|
||||
onClick={() => goToPage(totalPages)}
|
||||
disabled={currentPage === totalPages}
|
||||
style={{ padding: '4px 8px' }}
|
||||
>
|
||||
Last
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>;
|
||||
|
||||
const customFilterMenu = React.forwardRef(
|
||||
@ -521,7 +729,7 @@ const CustomersList = () => {
|
||||
</div>}
|
||||
<div className="list row mb-4">
|
||||
<Breadcrumb>
|
||||
<Breadcrumb.Item>General</Breadcrumb.Item>
|
||||
<Breadcrumb.Item href="/">General</Breadcrumb.Item>
|
||||
<Breadcrumb.Item active>
|
||||
Customer Information
|
||||
</Breadcrumb.Item>
|
||||
@ -534,13 +742,13 @@ const CustomersList = () => {
|
||||
</h4>
|
||||
</div>
|
||||
</div>
|
||||
<div className="app-main-content-list-container">
|
||||
<div className="app-main-content-list-container list-page">
|
||||
<div className="app-main-content-list-func-container">
|
||||
<Tabs defaultActiveKey="activeCustomers" id="customers-tab" onSelect={(k) => showArchive(k)}>
|
||||
<Tab eventKey="activeCustomers" title="Active Customers">
|
||||
{table}
|
||||
</Tab>
|
||||
<Tab eventKey="archivedCustomers" title="Archived Customers">
|
||||
<Tab eventKey="archivedCustomers" title="Discharge Customers">
|
||||
{table}
|
||||
</Tab>
|
||||
</Tabs>
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@ -268,7 +268,7 @@ const DashboardCustomersList = () => {
|
||||
}
|
||||
|
||||
const table = <div className="list row mb-4">
|
||||
<div className="col-md-12">
|
||||
<div className="col-md-12" style={{ overflow: 'auto'}}>
|
||||
<table className="personnel-info-table">
|
||||
<thead>
|
||||
<tr>
|
||||
@ -397,7 +397,7 @@ const DashboardCustomersList = () => {
|
||||
<Tab eventKey="activeCustomers" title="Active Customers">
|
||||
{table}
|
||||
</Tab>
|
||||
<Tab eventKey="archivedCustomers" title="Archived Customers">
|
||||
<Tab eventKey="archivedCustomers" title="Discharge Customers">
|
||||
{table}
|
||||
</Tab>
|
||||
</Tabs>
|
||||
|
||||
@ -37,7 +37,29 @@ const CreateEventRequest = () => {
|
||||
}
|
||||
|
||||
|
||||
const validateEventRequest = () => {
|
||||
const errors = [];
|
||||
|
||||
// Required fields validation
|
||||
if (!currentCustomer || !currentCustomer.id) {
|
||||
errors.push('Patient');
|
||||
}
|
||||
if (!currentResource || !currentResource.id) {
|
||||
errors.push('Doctor');
|
||||
}
|
||||
|
||||
if (errors.length > 0) {
|
||||
window.alert(`Please fill in the following required fields:\n${errors.join('\n')}`);
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
};
|
||||
|
||||
const saveEventRequest = () => {
|
||||
if (!validateEventRequest()) {
|
||||
return;
|
||||
}
|
||||
|
||||
const newEventRequest = {
|
||||
customer_id: currentCustomer?.id,
|
||||
customer_display: `${currentCustomer?.name} - ${currentCustomer?.name_cn} - ${currentCustomer?.birth_date}`,
|
||||
@ -112,8 +134,8 @@ const CreateEventRequest = () => {
|
||||
<>
|
||||
<div className="list row mb-4">
|
||||
<Breadcrumb>
|
||||
<Breadcrumb.Item>Medical</Breadcrumb.Item>
|
||||
<Breadcrumb.Item active>
|
||||
<Breadcrumb.Item href="/medical/index">Medical</Breadcrumb.Item>
|
||||
<Breadcrumb.Item href="/medical/event-request/list">
|
||||
Appointment Request Information
|
||||
</Breadcrumb.Item>
|
||||
<Breadcrumb.Item active>
|
||||
@ -135,34 +157,42 @@ const CreateEventRequest = () => {
|
||||
<span className="required">*</span>
|
||||
</div>
|
||||
<Select styles={{
|
||||
control: (baseStyles, state) => ({
|
||||
control: (baseStyles) => ({
|
||||
...baseStyles,
|
||||
width: '350px',
|
||||
height: '45px',
|
||||
'padding-top': 0,
|
||||
'padding-bottom': 0,
|
||||
'margin-top': 0,
|
||||
'margin-bottom': 0
|
||||
borderRadius: '8px'
|
||||
}),
|
||||
indicatorSeparator: (baseStyles, state) => ({
|
||||
...baseStyles,
|
||||
width: 0
|
||||
indicatorSeparator: () => ({
|
||||
display: 'none'
|
||||
}),
|
||||
indicatorsContainer: (baseStyles) => ({
|
||||
dropdownIndicator: (baseStyles) => ({
|
||||
...baseStyles,
|
||||
'margin-top': '-10px'
|
||||
paddingRight: '8px',
|
||||
color: '#333',
|
||||
'&:hover': { color: '#000' }
|
||||
}),
|
||||
valueContainer: (baseStyles) => ({
|
||||
...baseStyles,
|
||||
height: '43px',
|
||||
padding: '0 8px',
|
||||
}),
|
||||
input: (baseStyles) => ({
|
||||
...baseStyles,
|
||||
margin: '0px',
|
||||
padding: '0px',
|
||||
height: '30px',
|
||||
width: '290px'
|
||||
}),
|
||||
placeholder: (baseStyles) => ({
|
||||
...baseStyles,
|
||||
'margin-top': '-10px',
|
||||
'font-size': '13px'
|
||||
fontSize: '13px'
|
||||
}),
|
||||
singleValue: (baseStyles, state) => ({
|
||||
singleValue: (baseStyles) => ({
|
||||
...baseStyles,
|
||||
'margin-top': '-10px',
|
||||
'font-size': '13px'
|
||||
margin: '0px',
|
||||
fontSize: '13px'
|
||||
})
|
||||
}} value={customerDisplay || ''} onChange={selectedCustomer => onCustomerChange(selectedCustomer)} options={[{value: '', label: ''}, ...customers.map(customer => ({
|
||||
}} value={customerDisplay || ''} onChange={selectedCustomer => onCustomerChange(selectedCustomer)} options={[{value: '', label: ''}, ...customers.filter(customer => customer?.status === 'active' && customer?.type !== 'discharged').map(customer => ({
|
||||
value: customer?.id || '',
|
||||
label: `${customer?.name} - ${customer?.name_cn} - ${customer?.birth_date}` || ''
|
||||
}))]}></Select>
|
||||
|
||||
@ -305,7 +305,7 @@ const EventRequestList = () => {
|
||||
<>
|
||||
<div className="list row mb-4">
|
||||
<Breadcrumb>
|
||||
<Breadcrumb.Item>Medical</Breadcrumb.Item>
|
||||
<Breadcrumb.Item href="/medical/index">Medical</Breadcrumb.Item>
|
||||
<Breadcrumb.Item active>
|
||||
Appointment Requests Information
|
||||
</Breadcrumb.Item>
|
||||
@ -318,7 +318,7 @@ const EventRequestList = () => {
|
||||
</h4>
|
||||
</div>
|
||||
</div>
|
||||
<div className="app-main-content-list-container">
|
||||
<div className="app-main-content-list-container list-page">
|
||||
<div className="app-main-content-list-func-container">
|
||||
<Tabs defaultActiveKey="activeRequests" id="requests-tab" onSelect={(k) => showDoneItems(k)}>
|
||||
<Tab eventKey="activeRequests" title="Active Requests">
|
||||
|
||||
@ -51,7 +51,29 @@ const CreateEvent = () => {
|
||||
navigate(`/medical/events/edit/${id}?from=create`)
|
||||
}
|
||||
|
||||
const validateEvent = () => {
|
||||
const errors = [];
|
||||
|
||||
// Required fields validation
|
||||
if (!currentCustomer || !currentCustomer.id) {
|
||||
errors.push('Customer');
|
||||
}
|
||||
if (!startTime) {
|
||||
errors.push('Appointment Time');
|
||||
}
|
||||
|
||||
if (errors.length > 0) {
|
||||
window.alert(`Please fill in the following required fields:\n${errors.join('\n')}`);
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
};
|
||||
|
||||
const saveEvent = () => {
|
||||
if (!validateEvent()) {
|
||||
return;
|
||||
}
|
||||
|
||||
console.log('customer', currentCustomer);
|
||||
// console.log('resource', currentResource);
|
||||
|
||||
@ -137,8 +159,8 @@ const CreateEvent = () => {
|
||||
<>
|
||||
<div className="list row mb-4">
|
||||
<Breadcrumb>
|
||||
<Breadcrumb.Item>Medical</Breadcrumb.Item>
|
||||
<Breadcrumb.Item active>
|
||||
<Breadcrumb.Item href="/medical/index">Medical</Breadcrumb.Item>
|
||||
<Breadcrumb.Item href="/medical/events/list">
|
||||
Medical Appointment Information
|
||||
</Breadcrumb.Item>
|
||||
<Breadcrumb.Item active>
|
||||
@ -150,54 +172,62 @@ const CreateEvent = () => {
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="app-main-content-list-container">
|
||||
<div className="app-main-content-list-container form-page">
|
||||
<div className="app-main-content-list-func-container">
|
||||
<Tabs defaultActiveKey="eventClientInfo" id="event-tab">
|
||||
<Tab eventKey="eventClientInfo" title="Appointment Client And Time Information">
|
||||
<Tab eventKey="eventClientInfo" title="Appointment Customer And Time Information">
|
||||
<div className="multi-columns-container">
|
||||
<div className="column-container">
|
||||
<div className="column-card">
|
||||
<h6 className="text-primary">Client And Start Time</h6>
|
||||
<h6 className="text-primary">Customer And Appointment Time</h6>
|
||||
<div className="app-main-content-fields-section">
|
||||
<div className="me-4">
|
||||
<div className="field-label">Client
|
||||
<div className="field-label">Customer
|
||||
<span className="required">*</span>
|
||||
</div>
|
||||
<Select styles={{
|
||||
control: (baseStyles, state) => ({
|
||||
control: (baseStyles) => ({
|
||||
...baseStyles,
|
||||
width: '350px',
|
||||
height: '45px',
|
||||
'padding-top': 0,
|
||||
'padding-bottom': 0,
|
||||
'margin-top': 0,
|
||||
'margin-bottom': 0
|
||||
borderRadius: '8px'
|
||||
}),
|
||||
indicatorSeparator: (baseStyles, state) => ({
|
||||
...baseStyles,
|
||||
width: 0
|
||||
indicatorSeparator: () => ({
|
||||
display: 'none'
|
||||
}),
|
||||
indicatorsContainer: (baseStyles) => ({
|
||||
dropdownIndicator: (baseStyles) => ({
|
||||
...baseStyles,
|
||||
'margin-top': '-10px'
|
||||
paddingRight: '8px',
|
||||
color: '#333',
|
||||
'&:hover': { color: '#000' }
|
||||
}),
|
||||
valueContainer: (baseStyles) => ({
|
||||
...baseStyles,
|
||||
height: '43px',
|
||||
padding: '0 8px',
|
||||
}),
|
||||
input: (baseStyles) => ({
|
||||
...baseStyles,
|
||||
margin: '0px',
|
||||
padding: '0px',
|
||||
height: '30px',
|
||||
width: '290px'
|
||||
}),
|
||||
placeholder: (baseStyles) => ({
|
||||
...baseStyles,
|
||||
'margin-top': '-10px',
|
||||
'font-size': '13px'
|
||||
fontSize: '13px'
|
||||
}),
|
||||
singleValue: (baseStyles, state) => ({
|
||||
singleValue: (baseStyles) => ({
|
||||
...baseStyles,
|
||||
'margin-top': '-10px',
|
||||
'font-size': '13px'
|
||||
margin: '0px',
|
||||
fontSize: '13px'
|
||||
})
|
||||
}} value={customerName || ''} onChange={selectedCustomer => onCustomerChange(selectedCustomer)} options={[{value: '', label: ''}, ...customers.map(customer => ({
|
||||
}} value={customerName || ''} onChange={selectedCustomer => onCustomerChange(selectedCustomer)} options={[{value: '', label: ''}, ...customers.filter(customer => customer?.status === 'active' && customer?.type !== 'discharged').map(customer => ({
|
||||
value: customer?.id || '',
|
||||
label: customer?.name || ''
|
||||
}))]}></Select>
|
||||
</div>
|
||||
<div className="me-4">
|
||||
<div className="field-label">Start Time
|
||||
<div className="field-label">Appointment Time
|
||||
<span className="required">*</span>
|
||||
</div>
|
||||
<DatePicker
|
||||
@ -221,10 +251,10 @@ const CreateEvent = () => {
|
||||
<div className="column-card">
|
||||
{
|
||||
currentCustomer && <>
|
||||
<h6 className="text-primary">Client Information</h6>
|
||||
<h6 className="text-primary">Customer Information</h6>
|
||||
<div className="app-main-content-fields-section short">
|
||||
<div className="field-body">
|
||||
<div className="field-label">Client Name</div>
|
||||
<div className="field-label">Customer Name</div>
|
||||
<div className="field-value">{currentCustomer?.name || ''}</div>
|
||||
</div>
|
||||
<div className="field-body">
|
||||
|
||||
@ -37,6 +37,42 @@ const EventsCalendar = () => {
|
||||
const eventsServicePlugin = createEventsServicePlugin();
|
||||
const eventModalService = createEventModalPlugin();
|
||||
const [groupedEvents, setGroupedEvents] = useState(new Map());
|
||||
const [currentRangeStart, setCurrentRangeStart] = useState(null);
|
||||
const [currentRangeEnd, setCurrentRangeEnd] = useState(null);
|
||||
|
||||
// Helper function to format name from "lastname, firstname" to "firstname lastname"
|
||||
const formatFullName = (name) => {
|
||||
if (!name) return '';
|
||||
if (name.includes(',')) {
|
||||
const parts = name.split(',').map(part => part.trim());
|
||||
return `${parts[1]} ${parts[0]}`; // firstname lastname
|
||||
}
|
||||
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}`;
|
||||
};
|
||||
|
||||
const calendar = useCalendarApp({
|
||||
views: [createViewMonthGrid(), createViewDay(), createViewMonthAgenda(), createViewWeek()],
|
||||
@ -52,21 +88,47 @@ const EventsCalendar = () => {
|
||||
events: events,
|
||||
plugins: [eventModalService, eventsServicePlugin],
|
||||
callbacks: {
|
||||
// onRangeUpdate(range) {
|
||||
// console.log('new range', range.start);
|
||||
// console.log('new range end', range.end);
|
||||
// },
|
||||
onSelectedDateUpdate(date) {
|
||||
setFromDate(new Date(new Date(date).getFullYear(), new Date(date).getMonth(), 1));
|
||||
setToDate(new Date(new Date(date).getFullYear(), new Date(date).getMonth() + 1, 0));
|
||||
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));
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
// 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);
|
||||
return eventDate.isSame(now, 'month');
|
||||
});
|
||||
}
|
||||
|
||||
// Filter events within the calendar's visible range
|
||||
const rangeStart = moment(currentRangeStart);
|
||||
const rangeEnd = moment(currentRangeEnd);
|
||||
|
||||
return events.filter(event => {
|
||||
const eventDate = moment(event.start_time);
|
||||
return eventDate.isBetween(rangeStart, rangeEnd, 'day', '[]');
|
||||
});
|
||||
};
|
||||
|
||||
const getGroupedEvents = () => {
|
||||
const eventsDateMap = new Map();
|
||||
console.log('events', events);
|
||||
for (const eventItem of events) {
|
||||
const filteredEvents = getFilteredEvents();
|
||||
for (const eventItem of filteredEvents) {
|
||||
const dateString = moment(eventItem.start_time).format('MMM Do, YYYY');
|
||||
if (eventsDateMap.has(dateString)) {
|
||||
eventsDateMap.set(dateString, [...eventsDateMap.get(dateString), eventItem]);
|
||||
@ -76,7 +138,6 @@ const EventsCalendar = () => {
|
||||
eventsDateMap.set(dateString, value);
|
||||
}
|
||||
}
|
||||
console.log('eventsMap', eventsDateMap);
|
||||
return eventsDateMap;
|
||||
}
|
||||
|
||||
@ -87,7 +148,7 @@ const EventsCalendar = () => {
|
||||
AuthService.logout();
|
||||
navigate(`/login`);
|
||||
}
|
||||
CustomerService.getAllCustomers().then((data) => {
|
||||
CustomerService.getAllActiveCustomers().then((data) => {
|
||||
setCustomers(data.data);
|
||||
});
|
||||
ResourceService.getAll().then((data) => {
|
||||
@ -118,7 +179,8 @@ const EventsCalendar = () => {
|
||||
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 = `${customerField}, provider: ${doctorField}`;
|
||||
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);
|
||||
@ -140,16 +202,15 @@ const EventsCalendar = () => {
|
||||
})}
|
||||
}, [fromDate, toDate, customers, resources, timeData, showDeletedItems]);
|
||||
|
||||
|
||||
|
||||
useEffect(() => {
|
||||
if (events && calendar) {
|
||||
// events?.forEach((item) => {
|
||||
// if (!calendar.eventsService.getAll()?.find(evItem => evItem.id === item.id)) {
|
||||
// calendar.eventsService.add(item)
|
||||
// } } );
|
||||
console.log("EventsCalendar useEffect - events:", events.length, "range:", currentRangeStart, "to", currentRangeEnd);
|
||||
calendar?.eventsService?.set(events);
|
||||
setGroupedEvents(getGroupedEvents());
|
||||
}
|
||||
}, [events]);
|
||||
}, [events, currentRangeStart, currentRangeEnd]);
|
||||
|
||||
|
||||
|
||||
@ -203,7 +264,7 @@ const EventsCalendar = () => {
|
||||
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 = `${customerField}, provider: ${doctorField}`;
|
||||
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'}`]};
|
||||
@ -280,7 +341,7 @@ const EventsCalendar = () => {
|
||||
<>
|
||||
<div className="list row mb-4">
|
||||
<Breadcrumb>
|
||||
<Breadcrumb.Item>Medical</Breadcrumb.Item>
|
||||
<Breadcrumb.Item href="/medical/index">Medical</Breadcrumb.Item>
|
||||
<Breadcrumb.Item active>
|
||||
Medical Event Calendar
|
||||
</Breadcrumb.Item>
|
||||
@ -301,22 +362,26 @@ const EventsCalendar = () => {
|
||||
{calendar && <ScheduleXCalendar customComponents={customComponents} calendarApp={calendar} />}
|
||||
</div>
|
||||
<div className="column-container">
|
||||
<div className="column-card">
|
||||
<div className="column-card" style={{ maxHeight: '800px', overflowY: 'auto', overflowX: 'hidden' }}>
|
||||
<h6 className="text-primary me-4">List</h6>
|
||||
{
|
||||
Array.from(groupedEvents?.keys())?.map((key) => {
|
||||
return <>
|
||||
return <div key={key}>
|
||||
<h6 className="text-primary me-2">{key}</h6>
|
||||
{
|
||||
groupedEvents.get(key).map(eventItem => <div className={`event-${eventItem.color || 'primary'} mb-4 event-list-item-container`}>
|
||||
groupedEvents.get(key).map(eventItem => <div
|
||||
key={eventItem.id}
|
||||
className={`event-${eventItem.color || 'primary'} mb-4 event-list-item-container`}
|
||||
onClick={() => goToView(eventItem.id)}
|
||||
style={{ cursor: 'pointer' }}
|
||||
>
|
||||
<div className="event-item-flex">
|
||||
<div className="sx__month-agenda-event__title">{eventItem.customer}</div>
|
||||
<div className="sx__event-modal__time">{`${moment(eventItem?.start_time).format('hh:mm A')} ${eventItem?.end_time ? `- ${moment(eventItem?.end_time).format('hh:mm A')}` : '' }`}</div>
|
||||
<div className="sx__month-agenda-event__title">{`${moment(eventItem?.start_time).format('hh:mm A')}: ${formatFullName(eventItem.customer)}`}</div>
|
||||
</div>
|
||||
<div className="sx__event-modal__time with-padding">{`provider: ${eventItem?.doctor}`}</div>
|
||||
</div>)
|
||||
}
|
||||
</>
|
||||
</div>
|
||||
})
|
||||
}
|
||||
</div>
|
||||
|
||||
@ -13,7 +13,9 @@ const EventsList = () => {
|
||||
const [customers, setCustomers] = useState([]);
|
||||
const [resources, setResources] = useState([]);
|
||||
const [selectedDate, setSelectedDate] = useState(new Date());
|
||||
const [sorting, setSorting] = useState({key: '', order: ''});
|
||||
// Multi-column sorting: array of {key, order} objects
|
||||
// First item has highest priority, subsequent items are secondary sorts
|
||||
const [sortingRules, setSortingRules] = useState([]);
|
||||
const [selectedItems, setSelectedItems] = useState([]);
|
||||
const [showTransportationModal, setShowTransportationModal] = useState(false);
|
||||
const [driver, setDriver] = useState(null);
|
||||
@ -45,7 +47,7 @@ const EventsList = () => {
|
||||
},
|
||||
{
|
||||
key: 'doctor',
|
||||
label: 'Doctor',
|
||||
label: 'Provider',
|
||||
show: true
|
||||
},
|
||||
{
|
||||
@ -60,7 +62,7 @@ const EventsList = () => {
|
||||
},
|
||||
{
|
||||
key: 'translation',
|
||||
label: 'Translation',
|
||||
label: 'Language Support',
|
||||
show: true
|
||||
},
|
||||
{
|
||||
@ -70,7 +72,7 @@ const EventsList = () => {
|
||||
},
|
||||
{
|
||||
key: 'needId',
|
||||
label: 'Need ID',
|
||||
label: 'ID Needed',
|
||||
show: true
|
||||
},
|
||||
{
|
||||
@ -80,17 +82,17 @@ const EventsList = () => {
|
||||
},
|
||||
{
|
||||
key: 'startTime',
|
||||
label: 'Start Time',
|
||||
label: 'Appointment Time',
|
||||
show: true
|
||||
},
|
||||
{
|
||||
key: 'fasting',
|
||||
label: 'Fasting',
|
||||
label: 'Fasting Required',
|
||||
show: true
|
||||
},
|
||||
{
|
||||
key: 'transportation',
|
||||
label: 'Driver',
|
||||
label: 'Transportation Support',
|
||||
show: true
|
||||
}
|
||||
]);
|
||||
@ -100,6 +102,39 @@ const EventsList = () => {
|
||||
return currentCustomer?.disability || event?.data?.disability?.toLowerCase() === 'yes' || false;
|
||||
};
|
||||
|
||||
// Default sort: 1) driver name ascending, 2) start time early to late, 3) address ascending, 4) language (empty first)
|
||||
const applyDefaultSort = (eventsArray) => {
|
||||
return [...eventsArray].sort((a, b) => {
|
||||
// 1. First sort by driver (transportation) name ascending
|
||||
const driverA = (a.transportation || '').toLowerCase();
|
||||
const driverB = (b.transportation || '').toLowerCase();
|
||||
if (driverA !== driverB) {
|
||||
return driverA.localeCompare(driverB);
|
||||
}
|
||||
|
||||
// 2. Then sort by start time (early to late)
|
||||
const timeA = a.start_time ? new Date(a.start_time).getTime() : 0;
|
||||
const timeB = b.start_time ? new Date(b.start_time).getTime() : 0;
|
||||
if (timeA !== timeB) {
|
||||
return timeA - timeB;
|
||||
}
|
||||
|
||||
// 3. Then sort by resource address ascending
|
||||
const addressA = (a.address || '').toLowerCase();
|
||||
const addressB = (b.address || '').toLowerCase();
|
||||
if (addressA !== addressB) {
|
||||
return addressA.localeCompare(addressB);
|
||||
}
|
||||
|
||||
// 4. Finally sort by language (empty values first, then values with content)
|
||||
const langA = (a.translation || '').trim();
|
||||
const langB = (b.translation || '').trim();
|
||||
if (langA === '' && langB !== '') return -1; // Empty comes first
|
||||
if (langA !== '' && langB === '') return 1; // Empty comes first
|
||||
return langA.localeCompare(langB); // Both empty or both have values - sort alphabetically
|
||||
});
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
if (!AuthService.canAccessLegacySystem()) {
|
||||
window.alert('You haven\'t login yet OR this user does not have access to this page. Please change an admin account to login.')
|
||||
@ -121,7 +156,7 @@ const EventsList = () => {
|
||||
useEffect(() => {
|
||||
if (customers?.length > 0 && resources?.length>0) {
|
||||
EventsService.getAllEvents({ date: EventsService.formatDate(selectedDate) }).then((data) => {
|
||||
setEvents(data.data.filter((item) => {
|
||||
const processedEvents = data.data.filter((item) => {
|
||||
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,7 +174,11 @@ const EventsList = () => {
|
||||
item.dob = item?.data?.customer ? customers.find(c => c.id === item?.data?.customer)?.birth_date : (item?.data?.client_birth_date || '');
|
||||
item.transMethod = item?.data?.trans_method;
|
||||
return item;
|
||||
}).filter(item => item.type === 'medical' && item.confirmed));
|
||||
}).filter(item => item.type === 'medical' && item.confirmed);
|
||||
|
||||
// Apply default sort (driver name → start time → address)
|
||||
const sortedEvents = applyDefaultSort(processedEvents);
|
||||
setEvents(sortedEvents);
|
||||
setTransportationOptionsList(data.data.filter((item) => item.type === 'transportation' && item.status === 'active'))
|
||||
})
|
||||
}
|
||||
@ -147,15 +186,40 @@ const EventsList = () => {
|
||||
|
||||
|
||||
|
||||
useEffect(() => {
|
||||
const newEvents = [...events];
|
||||
const sortedEvents = sorting.key === '' ? newEvents : newEvents.sort((a, b) => {
|
||||
return a[sorting.key].localeCompare(b[sorting.key]);
|
||||
// Apply multi-column sorting
|
||||
// After all custom sorting rules, always sort by language (empty first)
|
||||
const applyMultiColumnSort = (eventsArray, rules) => {
|
||||
if (rules.length === 0) {
|
||||
return applyDefaultSort(eventsArray);
|
||||
}
|
||||
|
||||
return [...eventsArray].sort((a, b) => {
|
||||
// First apply all custom sorting rules
|
||||
for (const rule of rules) {
|
||||
const valA = (a[rule.key] || '').toString().toLowerCase();
|
||||
const valB = (b[rule.key] || '').toString().toLowerCase();
|
||||
const comparison = valA.localeCompare(valB);
|
||||
|
||||
if (comparison !== 0) {
|
||||
return rule.order === 'desc' ? -comparison : comparison;
|
||||
}
|
||||
}
|
||||
|
||||
// After all rules, always sort by language (empty values first, then values with content)
|
||||
const langA = (a.translation || '').trim();
|
||||
const langB = (b.translation || '').trim();
|
||||
if (langA === '' && langB !== '') return -1; // Empty comes first
|
||||
if (langA !== '' && langB === '') return 1; // Empty comes first
|
||||
return langA.localeCompare(langB); // Both empty or both have values - sort alphabetically
|
||||
});
|
||||
setEvents(
|
||||
sorting.order === 'asc' ? sortedEvents : sortedEvents.reverse()
|
||||
)
|
||||
}, [sorting]);
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
if (events.length === 0) return;
|
||||
|
||||
const sortedEvents = applyMultiColumnSort(events, sortingRules);
|
||||
setEvents(sortedEvents);
|
||||
}, [sortingRules]);
|
||||
|
||||
|
||||
const redirectToAdmin = () => {
|
||||
@ -199,24 +263,46 @@ const EventsList = () => {
|
||||
setShowDeletedItems(value === 'archivedEvents');
|
||||
// Recover all filters
|
||||
// setKeyword('');
|
||||
setSorting({key: '', order: ''});
|
||||
setSortingRules([]);
|
||||
setSelectedItems([]);
|
||||
}
|
||||
|
||||
// Get the sort indicator for a column (shows priority number if multi-column)
|
||||
const getSortingImg = (key) => {
|
||||
return sorting.key === key ? (sorting.order === 'asc' ? 'up_arrow' : 'down_arrow') : 'default';
|
||||
const ruleIndex = sortingRules.findIndex(rule => rule.key === key);
|
||||
if (ruleIndex === -1) {
|
||||
return 'default';
|
||||
}
|
||||
return sortingRules[ruleIndex].order === 'asc' ? 'up_arrow' : 'down_arrow';
|
||||
}
|
||||
|
||||
// Get the sort priority number for display (1, 2, 3, etc.)
|
||||
const getSortPriority = (key) => {
|
||||
const ruleIndex = sortingRules.findIndex(rule => rule.key === key);
|
||||
return ruleIndex === -1 ? null : ruleIndex + 1;
|
||||
}
|
||||
|
||||
// Multi-column sort: clicking a column adds it to sort rules or toggles its order
|
||||
// Behavior: asc → desc → remove from sorting
|
||||
const sortTableWithField = (key) => {
|
||||
let newSorting = {
|
||||
key,
|
||||
order: 'asc',
|
||||
}
|
||||
const existingIndex = sortingRules.findIndex(rule => rule.key === key);
|
||||
|
||||
if (sorting.key === key && sorting.order === 'asc') {
|
||||
newSorting = {...newSorting, order: 'desc'};
|
||||
if (existingIndex === -1) {
|
||||
// Column not in sort rules - add it with 'asc' order
|
||||
setSortingRules([...sortingRules, { key, order: 'asc' }]);
|
||||
} else {
|
||||
const existingRule = sortingRules[existingIndex];
|
||||
if (existingRule.order === 'asc') {
|
||||
// Toggle to desc
|
||||
const newRules = [...sortingRules];
|
||||
newRules[existingIndex] = { key, order: 'desc' };
|
||||
setSortingRules(newRules);
|
||||
} else {
|
||||
// Already desc - remove from sorting rules
|
||||
const newRules = sortingRules.filter((_, index) => index !== existingIndex);
|
||||
setSortingRules(newRules);
|
||||
}
|
||||
}
|
||||
setSorting(newSorting);
|
||||
}
|
||||
|
||||
const toggleSelectedAllItems = () => {
|
||||
@ -308,13 +394,10 @@ const EventsList = () => {
|
||||
item.transMethod = item?.data?.trans_method;
|
||||
return item;
|
||||
}).filter(item => item.type === 'medical' && item.confirmed);
|
||||
const newEvents = [...eventsResults];
|
||||
const sortedEvents = sorting.key === '' ? newEvents : newEvents.sort((a, b) => {
|
||||
return a[sorting.key].localeCompare(b[sorting.key]);
|
||||
});
|
||||
setEvents(
|
||||
sorting.order === 'asc' ? sortedEvents : sortedEvents.reverse()
|
||||
)
|
||||
|
||||
// Apply current sorting rules (or default if none)
|
||||
const sortedEvents = applyMultiColumnSort(eventsResults, sortingRules);
|
||||
setEvents(sortedEvents);
|
||||
setTransportationOptionsList(data.data.filter((item) => item.type === 'transportation' && item.status === 'active'));
|
||||
closePanel();
|
||||
})
|
||||
@ -349,13 +432,10 @@ const EventsList = () => {
|
||||
item.transMethod = item?.data?.trans_method;
|
||||
return item;
|
||||
}).filter(item => item.type === 'medical' && item.confirmed);
|
||||
const newEvents = [...eventsResults];
|
||||
const sortedEvents = sorting.key === '' ? newEvents : newEvents.sort((a, b) => {
|
||||
return a[sorting.key].localeCompare(b[sorting.key]);
|
||||
});
|
||||
setEvents(
|
||||
sorting.order === 'asc' ? sortedEvents : sortedEvents.reverse()
|
||||
)
|
||||
|
||||
// Apply current sorting rules (or default if none)
|
||||
const sortedEvents = applyMultiColumnSort(eventsResults, sortingRules);
|
||||
setEvents(sortedEvents);
|
||||
// setTransportationOptionsList(data.data.filter((item) => item.type === 'transportation' && item.status === 'active'));
|
||||
closePanel();
|
||||
})
|
||||
@ -373,7 +453,11 @@ const EventsList = () => {
|
||||
<th className="th-index">No.</th>
|
||||
{
|
||||
columns.filter(col => col.show).map((column, index) => <th className="sortable-header" key={index}>
|
||||
{column.label} <span className="float-right" onClick={() => sortTableWithField(column.key)}><img src={`/images/${getSortingImg(column.key)}.png`}></img></span>
|
||||
{column.label}
|
||||
<span className="float-right" onClick={() => sortTableWithField(column.key)} style={{ cursor: 'pointer' }}>
|
||||
{getSortPriority(column.key) && <small style={{ marginRight: '2px', fontSize: '10px', color: '#666' }}>{getSortPriority(column.key)}</small>}
|
||||
<img src={`/images/${getSortingImg(column.key)}.png`}></img>
|
||||
</span>
|
||||
</th>)
|
||||
}
|
||||
<th>Customer Date of Birth</th>
|
||||
@ -413,7 +497,7 @@ const EventsList = () => {
|
||||
<>
|
||||
<div className="list row mb-4">
|
||||
<Breadcrumb>
|
||||
<Breadcrumb.Item>Medical</Breadcrumb.Item>
|
||||
<Breadcrumb.Item href="/medical/index">Medical</Breadcrumb.Item>
|
||||
<Breadcrumb.Item active>
|
||||
Appointment Information
|
||||
</Breadcrumb.Item>
|
||||
@ -426,7 +510,7 @@ const EventsList = () => {
|
||||
</h4>
|
||||
</div>
|
||||
</div>
|
||||
<div className="app-main-content-list-container">
|
||||
<div className="app-main-content-list-container list-page">
|
||||
<div className="app-main-content-list-func-container">
|
||||
<Tabs defaultActiveKey="activeEvents" id="requests-tab" onSelect={(k) => showDeleted(k)}>
|
||||
<Tab eventKey="activeEvents" title="Active Appointments">
|
||||
|
||||
@ -375,7 +375,8 @@ const EventsMultipleList = () => {
|
||||
control: (baseStyles, state) => ({
|
||||
...baseStyles,
|
||||
width: '210px',
|
||||
height: '35px',
|
||||
height: '45px',
|
||||
minHeight: '45px',
|
||||
'padding-top': 0,
|
||||
'padding-bottom': 0,
|
||||
'margin-top': 0,
|
||||
@ -383,23 +384,27 @@ const EventsMultipleList = () => {
|
||||
}),
|
||||
indicatorSeparator: (baseStyles, state) => ({
|
||||
...baseStyles,
|
||||
width: 0
|
||||
display: 'none'
|
||||
}),
|
||||
dropdownIndicator: (baseStyles) => ({
|
||||
...baseStyles,
|
||||
paddingRight: '12px'
|
||||
}),
|
||||
indicatorsContainer: (baseStyles) => ({
|
||||
...baseStyles,
|
||||
'margin-top': '-20px'
|
||||
'margin-top': '-5px'
|
||||
}),
|
||||
placeholder: (baseStyles) => ({
|
||||
...baseStyles,
|
||||
'margin-top': '-20px',
|
||||
'margin-top': '-5px',
|
||||
'font-size': '13px'
|
||||
}),
|
||||
singleValue: (baseStyles, state) => ({
|
||||
...baseStyles,
|
||||
'margin-top': '-20px',
|
||||
'margin-top': '-5px',
|
||||
'font-size': '13px'
|
||||
})
|
||||
}} value={selectedCustomer || ''} onChange={selectedData => {setSelectedCustomer(selectedData); setSelectedItems([]);}} options={[{value: '', label: ''}, ...customers.map(resource => ({
|
||||
}} value={selectedCustomer || ''} onChange={selectedData => {setSelectedCustomer(selectedData); setSelectedItems([]);}} options={[{value: '', label: ''}, ...customers.filter(customer => customer?.status === 'active' && customer?.type !== 'discharged').map(resource => ({
|
||||
value: resource?.id || '',
|
||||
label: resource?.name || '',
|
||||
}))]}></Select>
|
||||
@ -412,7 +417,8 @@ const EventsMultipleList = () => {
|
||||
control: (baseStyles, state) => ({
|
||||
...baseStyles,
|
||||
width: '210px',
|
||||
height: '25px',
|
||||
height: '45px',
|
||||
minHeight: '45px',
|
||||
'padding-top': 0,
|
||||
'padding-bottom': 0,
|
||||
'margin-top': 0,
|
||||
@ -420,11 +426,15 @@ const EventsMultipleList = () => {
|
||||
}),
|
||||
indicatorSeparator: (baseStyles, state) => ({
|
||||
...baseStyles,
|
||||
width: 0
|
||||
display: 'none'
|
||||
}),
|
||||
dropdownIndicator: (baseStyles) => ({
|
||||
...baseStyles,
|
||||
paddingRight: '12px'
|
||||
}),
|
||||
indicatorsContainer: (baseStyles) => ({
|
||||
...baseStyles,
|
||||
'margin-top': '-20px'
|
||||
'margin-top': '-5px'
|
||||
}),
|
||||
placeholder: (baseStyles) => ({
|
||||
...baseStyles,
|
||||
@ -458,7 +468,7 @@ const EventsMultipleList = () => {
|
||||
<>
|
||||
<div className="list row mb-4 noprint">
|
||||
<Breadcrumb>
|
||||
<Breadcrumb.Item>Medical</Breadcrumb.Item>
|
||||
<Breadcrumb.Item href="/medical/index">Medical</Breadcrumb.Item>
|
||||
<Breadcrumb.Item active>
|
||||
Appointment Information (Multi Days)
|
||||
</Breadcrumb.Item>
|
||||
@ -472,7 +482,7 @@ const EventsMultipleList = () => {
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="app-main-content-list-container">
|
||||
<div className="app-main-content-list-container list-page">
|
||||
<div className="app-main-content-list-func-container">
|
||||
<Tabs defaultActiveKey="activeEvents" id="requests-tab" onSelect={(k) => showDeleted(k)}>
|
||||
<Tab eventKey="activeEvents" title="Active Appointments">
|
||||
@ -485,10 +495,6 @@ const EventsMultipleList = () => {
|
||||
<div className="field-label">To</div>
|
||||
<DatePicker selected={toDate} onChange={(v) => {setToDate(v); setSelectedItems([]);}} />
|
||||
</div>
|
||||
<button className="btn btn-primary btn-sm me-2" onClick={() => {generateMedicalNotificationDocs()}}>Medical Notifications Doc</button>
|
||||
<button className="btn btn-primary btn-sm me-2" onClick={() => {generateMedicalNotificationPdf()}}>Medical Notifications Pdf</button>
|
||||
<button className="btn btn-primary btn-sm me-2" onClick={() => {generateVisitRecordSheet()}}>Visit Record Sheet</button>
|
||||
<button className="btn btn-primary btn-sm me-2" onClick={() => {generateVisitRecordPDF()}}>Visit Record PDF</button>
|
||||
</div>
|
||||
{table('active')}
|
||||
</Tab>
|
||||
@ -502,10 +508,6 @@ const EventsMultipleList = () => {
|
||||
<div className="field-label">To</div>
|
||||
<DatePicker selected={toDate} onChange={(v) => {setToDate(v); setSelectedItems([]);}} />
|
||||
</div>
|
||||
<button className="btn btn-primary btn-sm me-2" onClick={() => {generateMedicalNotificationDocs()}}>Medical Notifications Doc</button>
|
||||
<button className="btn btn-primary btn-sm me-2" onClick={() => {generateMedicalNotificationPdf()}}>Medical Notifications Pdf</button>
|
||||
<button className="btn btn-primary btn-sm me-2" onClick={() => {generateVisitRecordSheet()}}>Visit Record Sheet</button>
|
||||
<button className="btn btn-primary btn-sm me-2" onClick={() => {generateVisitRecordPDF()}}>Visit Record PDF</button>
|
||||
</div>
|
||||
{table('inactive')}
|
||||
</Tab>
|
||||
@ -531,6 +533,12 @@ const EventsMultipleList = () => {
|
||||
columns={columns}
|
||||
data={filteredEvents.filter(event => event.status === (showDeletedItems ? 'inactive' : 'active'))}
|
||||
filename="events-multiple"
|
||||
customActions={[
|
||||
{ label: 'Medical Notifications Doc', onClick: generateMedicalNotificationDocs },
|
||||
{ label: 'Medical Notifications Pdf', onClick: generateMedicalNotificationPdf },
|
||||
{ label: 'Visit Record Sheet', onClick: generateVisitRecordSheet },
|
||||
{ label: 'Visit Record PDF', onClick: generateVisitRecordPDF }
|
||||
]}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
import React, {useState, useEffect} from "react";
|
||||
// import { useDispatch } from "react-redux";
|
||||
import { useNavigate, useParams } from "react-router-dom";
|
||||
import { useNavigate, useParams, useSearchParams } from "react-router-dom";
|
||||
// import { customerSlice } from "./../../store";
|
||||
import { AuthService, CustomerService, EventsService, ResourceService } from "../../services";
|
||||
import Select from 'react-select';
|
||||
@ -11,6 +11,8 @@ import { Spinner, Breadcrumb, BreadcrumbItem, Tabs, Tab } from "react-bootstrap"
|
||||
const UpdateEvent = () => {
|
||||
const navigate = useNavigate();
|
||||
const urlParams = useParams();
|
||||
const [searchParams] = useSearchParams();
|
||||
const [activeTab, setActiveTab] = useState(searchParams.get('tab') || 'eventInfo');
|
||||
const [currentEvent, setCurrentEvent] = useState(undefined);
|
||||
const [medicalResource, setMedicalResource] = useState(undefined);
|
||||
const [currentResource, setCurrentResource] = useState(undefined);
|
||||
@ -70,7 +72,32 @@ const UpdateEvent = () => {
|
||||
navigate(`/medical/events/${urlParams.id}`);
|
||||
}
|
||||
|
||||
const validateEvent = () => {
|
||||
const errors = [];
|
||||
|
||||
// Required fields validation
|
||||
if (!currentCustomer || !currentCustomer.id) {
|
||||
errors.push('Customer');
|
||||
}
|
||||
if (!currentResource || !currentResource.id) {
|
||||
errors.push('Provider');
|
||||
}
|
||||
if (!startTime) {
|
||||
errors.push('Appointment Time');
|
||||
}
|
||||
|
||||
if (errors.length > 0) {
|
||||
window.alert(`Please fill in the following required fields:\n${errors.join('\n')}`);
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
};
|
||||
|
||||
const saveEvent = () => {
|
||||
if (!validateEvent()) {
|
||||
return;
|
||||
}
|
||||
|
||||
let newEventDataClientAndResource = {};
|
||||
let newEventClientAndResource = {};
|
||||
if (currentCustomer) {
|
||||
@ -266,8 +293,8 @@ const UpdateEvent = () => {
|
||||
<>
|
||||
<div className="list row mb-4">
|
||||
<Breadcrumb>
|
||||
<Breadcrumb.Item>Medical</Breadcrumb.Item>
|
||||
<Breadcrumb.Item active>
|
||||
<Breadcrumb.Item href="/medical/index">Medical</Breadcrumb.Item>
|
||||
<Breadcrumb.Item href="/medical/events/list">
|
||||
Medical Appointment Information
|
||||
</Breadcrumb.Item>
|
||||
<Breadcrumb.Item active>
|
||||
@ -279,54 +306,62 @@ const UpdateEvent = () => {
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="app-main-content-list-container">
|
||||
<div className="app-main-content-list-container form-page">
|
||||
<div className="app-main-content-list-func-container">
|
||||
<Tabs defaultActiveKey="eventInfo" id="event-tab">
|
||||
{ !hideCreateFields && <Tab eventKey="eventClientInfo" title="Appointment Client And Time Information">
|
||||
<Tabs activeKey={activeTab} onSelect={(k) => setActiveTab(k)} id="event-tab">
|
||||
{ !hideCreateFields && <Tab eventKey="eventClientInfo" title="Appointment Customer And Time Information">
|
||||
<div className="multi-columns-container">
|
||||
<div className="column-container">
|
||||
<div className="column-card">
|
||||
<h6 className="text-primary">Client And Start Time</h6>
|
||||
<h6 className="text-primary">Customer And Appointment Time</h6>
|
||||
<div className="app-main-content-fields-section">
|
||||
<div className="me-4">
|
||||
<div className="field-label">Client
|
||||
<div className="field-label">Customer
|
||||
<span className="required">*</span>
|
||||
</div>
|
||||
<Select styles={{
|
||||
control: (baseStyles, state) => ({
|
||||
control: (baseStyles) => ({
|
||||
...baseStyles,
|
||||
width: '350px',
|
||||
height: '45px',
|
||||
'padding-top': 0,
|
||||
'padding-bottom': 0,
|
||||
'margin-top': 0,
|
||||
'margin-bottom': 0
|
||||
borderRadius: '8px'
|
||||
}),
|
||||
indicatorSeparator: (baseStyles, state) => ({
|
||||
...baseStyles,
|
||||
width: 0
|
||||
indicatorSeparator: () => ({
|
||||
display: 'none'
|
||||
}),
|
||||
indicatorsContainer: (baseStyles) => ({
|
||||
dropdownIndicator: (baseStyles) => ({
|
||||
...baseStyles,
|
||||
'margin-top': '-10px'
|
||||
paddingRight: '8px',
|
||||
color: '#333',
|
||||
'&:hover': { color: '#000' }
|
||||
}),
|
||||
valueContainer: (baseStyles) => ({
|
||||
...baseStyles,
|
||||
height: '43px',
|
||||
padding: '0 8px',
|
||||
}),
|
||||
input: (baseStyles) => ({
|
||||
...baseStyles,
|
||||
margin: '0px',
|
||||
padding: '0px',
|
||||
height: '30px',
|
||||
width: '290px'
|
||||
}),
|
||||
placeholder: (baseStyles) => ({
|
||||
...baseStyles,
|
||||
'margin-top': '-10px',
|
||||
'font-size': '13px'
|
||||
fontSize: '13px'
|
||||
}),
|
||||
singleValue: (baseStyles, state) => ({
|
||||
singleValue: (baseStyles) => ({
|
||||
...baseStyles,
|
||||
'margin-top': '-10px',
|
||||
'font-size': '13px'
|
||||
margin: '0px',
|
||||
fontSize: '13px'
|
||||
})
|
||||
}} value={customerName || ''} onChange={selectedCustomer => onCustomerChange(selectedCustomer)} options={[{value: '', label: ''}, ...customers.map(customer => ({
|
||||
}} value={customerName || ''} onChange={selectedCustomer => onCustomerChange(selectedCustomer)} options={[{value: '', label: ''}, ...customers.filter(customer => customer?.status === 'active' && customer?.type !== 'discharged').map(customer => ({
|
||||
value: customer?.id || '',
|
||||
label: customer?.name || ''
|
||||
}))]}></Select>
|
||||
</div>
|
||||
<div className="me-4">
|
||||
<div className="field-label">Start Time
|
||||
<div className="field-label">Appointment Time
|
||||
<span className="required">*</span>
|
||||
</div>
|
||||
<DatePicker
|
||||
@ -350,10 +385,10 @@ const UpdateEvent = () => {
|
||||
<div className="column-card">
|
||||
{
|
||||
currentCustomer && <>
|
||||
<h6 className="text-primary">Client Information</h6>
|
||||
<h6 className="text-primary">Customer Information</h6>
|
||||
<div className="app-main-content-fields-section short">
|
||||
<div className="field-body">
|
||||
<div className="field-label">Client Name</div>
|
||||
<div className="field-label">Customer Name</div>
|
||||
<div className="field-value">{currentCustomer?.name || ''}</div>
|
||||
</div>
|
||||
<div className="field-body">
|
||||
@ -392,23 +427,15 @@ const UpdateEvent = () => {
|
||||
<h6 className="text-primary">Appointment Details</h6>
|
||||
<div className="app-main-content-fields-section">
|
||||
<div className="me-4">
|
||||
<div className="field-label">Resource
|
||||
<div className="field-label">Provider
|
||||
<span className="required">*</span>
|
||||
</div>
|
||||
{currentResource ?
|
||||
(<><span>{currentResource?.name}</span> <span><button className="btn btn-link btn-sm me-2 mb-2" onClick={()=> setShowResourceModal(true)}>Update</button></span></>) :
|
||||
(currentEvent?.data?.resource_name ? <><span>{currentEvent?.data?.resource_name}</span> <span><button className="btn btn-link btn-sm me-2 mb-2" onClick={()=> setShowResourceModal(true)}>Update</button></span></> : <button className="btn btn-link btn-sm me-2 mb-2" onClick={()=> setShowResourceModal(true)}>Please click here to add resources</button>)}
|
||||
</div>
|
||||
</div>
|
||||
<div className="app-main-content-fields-section">
|
||||
<div className="me-4">
|
||||
<div className="field-label">Title
|
||||
<span className="required">*</span>
|
||||
</div>
|
||||
<input type="text" value={title || ''} onChange={e => setTitle(e.target.value)}/>
|
||||
(currentEvent?.data?.resource_name ? <><span>{currentEvent?.data?.resource_name}</span> <span><button className="btn btn-link btn-sm me-2 mb-2" onClick={()=> setShowResourceModal(true)}>Update</button></span></> : <button className="btn btn-link btn-sm me-2 mb-2" onClick={()=> setShowResourceModal(true)}>Please click here to add provider</button>)}
|
||||
</div>
|
||||
<div className="me-4">
|
||||
<div className="field-label">Tag
|
||||
<div className="field-label">Label
|
||||
<span className="required">*</span>
|
||||
</div>
|
||||
<select value={color} onChange={e => setColor(e.target.value)}>
|
||||
@ -451,7 +478,7 @@ const UpdateEvent = () => {
|
||||
</div>
|
||||
<div className="app-main-content-fields-section">
|
||||
<div className="me-4">
|
||||
<div className="field-label">Need Id
|
||||
<div className="field-label">ID Needed
|
||||
</div>
|
||||
<select value={needId} onChange={e => setNeedId(e.target.value)}>
|
||||
<option value=""></option>
|
||||
@ -483,16 +510,13 @@ const UpdateEvent = () => {
|
||||
</select>
|
||||
</div>
|
||||
<div className="me-4">
|
||||
<div className="field-label">Transportation Needed
|
||||
<div className="field-label">Transportation Support
|
||||
</div>
|
||||
<select value={transMethod} onChange={e => setTransMethod(e.target.value)}>
|
||||
<option value=""></option>
|
||||
<option value="by own">By Own</option>
|
||||
<option value="televisit">Televisit</option>
|
||||
<option value="pickup only">Pickup Only</option>
|
||||
<option value="dropoff only">Dropoff Only</option>
|
||||
<option value="client does not need to go">Client Does Not Need To Go</option>
|
||||
<option value="Center Transportation">By Center Transportation</option>
|
||||
{
|
||||
EventsService.transportationTypeOptions?.map((item) => <option key={item?.value} value={item?.value}>{item?.label}</option>)
|
||||
}
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
@ -563,7 +587,7 @@ const UpdateEvent = () => {
|
||||
</div>
|
||||
<Modal show={showResourceModal} fullscreen={'xxl-down'} onHide={() => setShowResourceModal(false)}>
|
||||
<Modal.Header closeButton>
|
||||
<Modal.Title>Select the Resource</Modal.Title>
|
||||
<Modal.Title>Select the Provider</Modal.Title>
|
||||
</Modal.Header>
|
||||
<Modal.Body>
|
||||
<div className="app-main-content-fields-section">
|
||||
@ -572,7 +596,7 @@ const UpdateEvent = () => {
|
||||
<input type="text" value={keyword} onChange={e => setKeyword(e.target.value)}/>
|
||||
</div>
|
||||
<div className="mb-4 me-4">
|
||||
<div className="field-label">Filter By Resource Type</div>
|
||||
<div className="field-label">Filter By Provider Type</div>
|
||||
<select value={resourceType} onChange={e => setResourceType(e.target.value)}>
|
||||
<option value=""></option>
|
||||
<option value="doctor">Doctor</option>
|
||||
|
||||
@ -1,130 +1,207 @@
|
||||
import React, {useState, useEffect} from "react";
|
||||
// import { useDispatch } from "react-redux";
|
||||
import { useNavigate, useParams } from "react-router-dom";
|
||||
// import { customerSlice } from "./../../store";
|
||||
import { PencilSquare } from "react-bootstrap-icons";
|
||||
import { useNavigate, useParams, useSearchParams } from "react-router-dom";
|
||||
import { AuthService, CustomerService, EventsService, ResourceService } from "../../services";
|
||||
import { Breadcrumb, Tabs, Tab } from "react-bootstrap";
|
||||
|
||||
const ViewEvent = () => {
|
||||
const navigate = useNavigate();
|
||||
const urlParams = useParams();
|
||||
const [searchParams] = useSearchParams();
|
||||
const [activeTab, setActiveTab] = useState(searchParams.get('tab') || 'eventInfo');
|
||||
const [currentEvent, setCurrentEvent] = useState(undefined);
|
||||
const [resources, setResources] = useState([]);
|
||||
const [customers, setCustomers] = useState([]);
|
||||
const params = new URLSearchParams(window.location.search);
|
||||
|
||||
const redirectToCalendar = () => {
|
||||
navigate(`/medical/events/calendar`);
|
||||
}
|
||||
|
||||
const redirectTo = (id) => {
|
||||
const redirectTo = () => {
|
||||
navigate(`/medical/events/list`);
|
||||
}
|
||||
|
||||
const goToEdit = (id) => {
|
||||
navigate(`/medical/events/edit/${id}?tab=${activeTab}`);
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
if (!AuthService.canAccessLegacySystem()) {
|
||||
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`);
|
||||
}
|
||||
Promise.all([ResourceService.getAll(), CustomerService.getAllCustomers()]).then(([resourcesData, customersData]) => {
|
||||
Promise.all([ResourceService.getAll(), CustomerService.getAllActiveCustomers()]).then(([resourcesData, customersData]) => {
|
||||
setResources(resourcesData.data);
|
||||
setCustomers(customersData.data);
|
||||
if (!currentEvent) {
|
||||
EventsService.getEvent(urlParams.id).then(eventData => {
|
||||
setCurrentEvent(eventData.data);
|
||||
})
|
||||
|
||||
}
|
||||
})
|
||||
}, []);
|
||||
|
||||
const getResourceName = () => {
|
||||
if (currentEvent?.data?.resource) {
|
||||
return resources.find(r => r.id === currentEvent?.data?.resource)?.name || currentEvent?.data?.resource_name;
|
||||
}
|
||||
return currentEvent?.data?.resource_name || '';
|
||||
};
|
||||
|
||||
const getCustomerName = () => {
|
||||
if (currentEvent?.data?.customer) {
|
||||
return customers.find(c => c.id === currentEvent?.data?.customer)?.name || currentEvent?.data?.client_name;
|
||||
}
|
||||
return currentEvent?.data?.client_name || '';
|
||||
};
|
||||
|
||||
const formatDateTime = (dateString) => {
|
||||
if (!dateString) return '';
|
||||
const date = new Date(dateString);
|
||||
return `${date.toLocaleDateString()} ${date.toLocaleTimeString()}`;
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className="list row">
|
||||
<div className="col-md-12 col-xs-12">
|
||||
<div className="list row mb-4">
|
||||
<Breadcrumb>
|
||||
<Breadcrumb.Item href="/medical/index">Medical</Breadcrumb.Item>
|
||||
<Breadcrumb.Item href="/medical/events/list">
|
||||
Medical Events
|
||||
</Breadcrumb.Item>
|
||||
<Breadcrumb.Item active>
|
||||
View Event Details
|
||||
</Breadcrumb.Item>
|
||||
</Breadcrumb>
|
||||
<div className="col-md-12 text-primary">
|
||||
<h5>View Medical Event Details<button className="btn btn-link btn-sm" onClick={() => {redirectTo()}}>Back</button></h5>
|
||||
<h4>View Medical Event Details <button className="btn btn-link btn-sm" onClick={() => {redirectTo()}}>Back</button></h4>
|
||||
</div>
|
||||
</div>
|
||||
<div className="list row mb-4">
|
||||
<div className="col-md-6 mb-4">
|
||||
<div>Resource:</div>
|
||||
{ currentEvent?.data?.resource ? (resources.find(r => r.id === currentEvent?.data?.resource)?.name || currentEvent?.data?.resource_name) : currentEvent?.data?.resource_name}
|
||||
|
||||
<div className="app-main-content-list-container form-page">
|
||||
<div className="app-main-content-list-func-container">
|
||||
<Tabs activeKey={activeTab} onSelect={(k) => setActiveTab(k)} id="event-view-tab">
|
||||
<Tab eventKey="eventInfo" title="Event Information">
|
||||
<h6 className="text-primary">Appointment Details</h6>
|
||||
<div className="app-main-content-fields-section">
|
||||
<div className="field-body">
|
||||
<div className="field-label">Provider</div>
|
||||
<div className="field-value">{getResourceName()}</div>
|
||||
</div>
|
||||
<div className="col-md-6 mb-4">
|
||||
<div>Client:</div>
|
||||
{ currentEvent?.data?.customer ? (customers.find(r => r.id === currentEvent?.data?.customer)?.name || currentEvent?.data?.client_name) : currentEvent?.data?.client_name}
|
||||
<div className="field-body">
|
||||
<div className="field-label">Customer</div>
|
||||
<div className="field-value">{getCustomerName()}</div>
|
||||
</div>
|
||||
<div className="col-md-6 mb-4">
|
||||
<div>Title:</div> {currentEvent?.title}
|
||||
</div>
|
||||
<div className="col-md-6 mb-4">
|
||||
<div>Start Time:</div> {`${new Date(currentEvent?.start_time)?.toLocaleDateString()} ${new Date(currentEvent?.start_time)?.toLocaleTimeString()}`}
|
||||
</div>
|
||||
{/* <div className="col-md-12 mb-4">
|
||||
<div>Description:</div> {currentEvent?.description}
|
||||
</div> */}
|
||||
<div className="col-md-4 mb-4">
|
||||
<div>Color:</div>
|
||||
<div className="field-body">
|
||||
<div className="field-label">Label</div>
|
||||
<div className="field-value">
|
||||
{currentEvent?.color && (
|
||||
<span style={{
|
||||
display: 'inline-block',
|
||||
width: '16px',
|
||||
height: '16px',
|
||||
borderRadius: '4px',
|
||||
backgroundColor: currentEvent?.color,
|
||||
marginRight: '8px',
|
||||
verticalAlign: 'middle'
|
||||
}}></span>
|
||||
)}
|
||||
{currentEvent?.color || ''}
|
||||
</div>
|
||||
<div className="col-md-4 mb-4">
|
||||
<div>New Patient:</div>
|
||||
{currentEvent?.data?.new_patient}
|
||||
</div>
|
||||
<div className="col-md-4 mb-4">
|
||||
<div>Confirmed:</div>
|
||||
{currentEvent?.data?.confirmed}
|
||||
</div>
|
||||
<div className="col-md-4 mb-4">
|
||||
<div>Fasting:</div>
|
||||
{currentEvent?.data?.fasting}
|
||||
</div>
|
||||
<div className="col-md-4 mb-4">
|
||||
<div>Interpreter Level:</div>
|
||||
{currentEvent?.data?.interpreter}
|
||||
</div>
|
||||
<div className="col-md-4 mb-4">
|
||||
<div>Doctor Order:</div>
|
||||
{currentEvent?.data?.doc_order}
|
||||
</div>
|
||||
<div className="col-md-4 mb-4">
|
||||
<div>Disability: </div>
|
||||
{currentEvent?.data?.disability}
|
||||
</div>
|
||||
<div className="col-md-4 mb-4">
|
||||
<div>Need Id: </div>
|
||||
{currentEvent?.data?.need_id}
|
||||
</div>
|
||||
<div className="col-md-4 mb-4">
|
||||
<div>Need Medication List</div>
|
||||
{currentEvent?.data?.need_med_list}
|
||||
</div>
|
||||
<div className="col-md-4 mb-4">
|
||||
<div>Disability Support:</div>
|
||||
{currentEvent?.data?.disability_support}
|
||||
</div>
|
||||
<div className="col-md-4 mb-4">
|
||||
<div>Transportation:</div>
|
||||
{currentEvent?.data?.trans_method}
|
||||
</div>
|
||||
<div className="col-md-4 mb-4">
|
||||
<div>Notes:</div>
|
||||
{currentEvent?.data?.notes}
|
||||
</div>
|
||||
<div className="col-md-4 mb-4">
|
||||
<div>Reason:</div>
|
||||
{currentEvent?.data?.reason}
|
||||
</div>
|
||||
<div className="col-md-4 mb-4">
|
||||
<div>Other Requirements:</div>
|
||||
{currentEvent?.data?.other}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="app-main-content-fields-section">
|
||||
<div className="field-body">
|
||||
<div className="field-label">Appointment Time</div>
|
||||
<div className="field-value">{formatDateTime(currentEvent?.start_time)}</div>
|
||||
</div>
|
||||
<div className="field-body">
|
||||
<div className="field-label">End Time</div>
|
||||
<div className="field-value">{formatDateTime(currentEvent?.stop_time)}</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<h6 className="text-primary">Patient Information</h6>
|
||||
<div className="app-main-content-fields-section">
|
||||
<div className="field-body">
|
||||
<div className="field-label">New Patient</div>
|
||||
<div className="field-value">{currentEvent?.data?.new_patient || 'No'}</div>
|
||||
</div>
|
||||
<div className="field-body">
|
||||
<div className="field-label">Confirmed</div>
|
||||
<div className="field-value">{currentEvent?.confirmed ? 'Yes' : 'No'}</div>
|
||||
</div>
|
||||
<div className="field-body">
|
||||
<div className="field-label">Fasting Required</div>
|
||||
<div className="field-value">{currentEvent?.data?.fasting || 'No'}</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="app-main-content-fields-section">
|
||||
<div className="field-body">
|
||||
<div className="field-label">Language Support</div>
|
||||
<div className="field-value">{currentEvent?.data?.interpreter || ''}</div>
|
||||
</div>
|
||||
<div className="field-body">
|
||||
<div className="field-label">Eyes-on Required</div>
|
||||
<div className="field-value">{currentEvent?.data?.disability || 'No'}</div>
|
||||
</div>
|
||||
<div className="field-body">
|
||||
<div className="field-label">ID Needed</div>
|
||||
<div className="field-value">{currentEvent?.data?.need_id || 'No'}</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<h6 className="text-primary">Medical Requirements</h6>
|
||||
<div className="app-main-content-fields-section">
|
||||
<div className="field-body">
|
||||
<div className="field-label">Doctor Order</div>
|
||||
<div className="field-value">{currentEvent?.data?.doc_order || ''}</div>
|
||||
</div>
|
||||
<div className="field-body">
|
||||
<div className="field-label">Need Medication List</div>
|
||||
<div className="field-value">{currentEvent?.data?.need_med_list || 'No'}</div>
|
||||
</div>
|
||||
<div className="field-body">
|
||||
<div className="field-label">Disability Support</div>
|
||||
<div className="field-value">{currentEvent?.data?.disability_support || ''}</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<h6 className="text-primary">Transportation & Notes</h6>
|
||||
<div className="app-main-content-fields-section">
|
||||
<div className="field-body">
|
||||
<div className="field-label">Transportation Support</div>
|
||||
<div className="field-value">{currentEvent?.data?.trans_method || ''}</div>
|
||||
</div>
|
||||
<div className="field-body">
|
||||
<div className="field-label">Reason</div>
|
||||
<div className="field-value">{currentEvent?.data?.reason || ''}</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="app-main-content-fields-section">
|
||||
<div className="field-body">
|
||||
<div className="field-label">Notes</div>
|
||||
<div className="field-value">{currentEvent?.data?.notes || ''}</div>
|
||||
</div>
|
||||
<div className="field-body">
|
||||
<div className="field-label">Other Requirements</div>
|
||||
<div className="field-value">{currentEvent?.data?.other || ''}</div>
|
||||
</div>
|
||||
</div>
|
||||
</Tab>
|
||||
</Tabs>
|
||||
<div className="list-func-panel">
|
||||
<button className="btn btn-primary" onClick={() => goToEdit(currentEvent?.id)}>
|
||||
<PencilSquare className="me-2" size={16}></PencilSquare>Edit
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
@ -131,15 +131,27 @@ const SideMenu = () => {
|
||||
{
|
||||
name: 'Transportation Schedule',
|
||||
link: '/trans-routes/dashboard',
|
||||
category: '/trans-routes',
|
||||
category: '/trans-routes/dashboard',
|
||||
roleFunc: AuthService.canViewRoutes
|
||||
},
|
||||
{
|
||||
name: 'Customer Report',
|
||||
link: '/admin/customer-report',
|
||||
category: '/admin/',
|
||||
roleFunc: AuthService.canViewAttendance
|
||||
}
|
||||
name: 'Templates',
|
||||
link: '/trans-routes/daily-templates/list',
|
||||
category: '/trans-routes/daily-templates',
|
||||
roleFunc: AuthService.canViewRoutes
|
||||
},
|
||||
{
|
||||
name: 'Appointment One-Day List',
|
||||
link: '/medical/events/list',
|
||||
category: '/events/list',
|
||||
roleFunc: AuthService.canAccessLegacySystem
|
||||
},
|
||||
// {
|
||||
// name: 'Customer Report',
|
||||
// link: '/admin/customer-report',
|
||||
// category: '/admin/',
|
||||
// roleFunc: AuthService.canViewAttendance
|
||||
// }
|
||||
// {
|
||||
// name: 'Schedule Driver for Appointment',
|
||||
// link: '#',
|
||||
@ -171,12 +183,6 @@ const SideMenu = () => {
|
||||
category: '/events/calendar',
|
||||
roleFunc: AuthService.canAccessLegacySystem
|
||||
},
|
||||
{
|
||||
name: 'Appointment One-Day List',
|
||||
link: '/medical/events/list',
|
||||
category: '/events/list',
|
||||
roleFunc: AuthService.canAccessLegacySystem
|
||||
},
|
||||
{
|
||||
name: 'Appointment Multi-Days List',
|
||||
link: '/medical/events/multiple-list',
|
||||
@ -191,11 +197,11 @@ const SideMenu = () => {
|
||||
collapsed: false,
|
||||
roleFunc: () => true,
|
||||
subNavs: [
|
||||
// {
|
||||
// name: 'Meal Status',
|
||||
// link: '#',
|
||||
// roleFunc: () => true
|
||||
// },
|
||||
{
|
||||
name: 'Meal Status',
|
||||
link: '/meal-status',
|
||||
roleFunc: () => true
|
||||
},
|
||||
{
|
||||
name: 'Seating Chart',
|
||||
link: '/seating',
|
||||
|
||||
@ -122,9 +122,12 @@ const InfoScreen = () => {
|
||||
|
||||
console.log('Attendance note image response:', imageResponse);
|
||||
|
||||
if (imageResponse.data && imageResponse.data.files && imageResponse.data.files.length > 0) {
|
||||
// Handle both response.data.files and response.data.data.files structures
|
||||
const files = imageResponse?.data?.data?.files || imageResponse?.data?.files;
|
||||
|
||||
if (files && Array.isArray(files) && files.length > 0) {
|
||||
// Get the first image file
|
||||
const imageFile = imageResponse.data.files[0];
|
||||
const imageFile = files[0];
|
||||
console.log('Attendance note image file:', imageFile);
|
||||
setAttendanceNote(prev => ({
|
||||
...prev,
|
||||
@ -176,12 +179,18 @@ const InfoScreen = () => {
|
||||
'carousel_item'
|
||||
);
|
||||
console.log('Carousel images response:', imagesResponse);
|
||||
console.log('imagesResponse.data:', imagesResponse?.data);
|
||||
console.log('imagesResponse.data?.data:', imagesResponse?.data?.data);
|
||||
console.log('imagesResponse.data?.files:', imagesResponse?.data?.files);
|
||||
|
||||
if (imagesResponse.data && imagesResponse.data.files && imagesResponse.data.files.length > 0) {
|
||||
// Handle both response.data.files and response.data.data.files structures
|
||||
const files = imagesResponse?.data?.data?.files || imagesResponse?.data?.files;
|
||||
|
||||
if (files && Array.isArray(files) && files.length > 0) {
|
||||
// Get all image files and extract URLs
|
||||
const imageUrls = imagesResponse.data.files.map(file => file.url);
|
||||
const imageUrls = files.map(file => file.url);
|
||||
console.log('Carousel image URLs:', imageUrls);
|
||||
console.log('Carousel files array:', imagesResponse.data.files);
|
||||
console.log('Carousel files array:', files);
|
||||
setCarousel(prev => ({
|
||||
...prev,
|
||||
images: imageUrls
|
||||
@ -189,6 +198,12 @@ const InfoScreen = () => {
|
||||
} else {
|
||||
console.log('No files found in carousel response');
|
||||
console.log('Response structure:', imagesResponse);
|
||||
console.log('files value:', files);
|
||||
console.log('files type:', typeof files);
|
||||
console.log('files isArray:', Array.isArray(files));
|
||||
if (files) {
|
||||
console.log('files length:', files.length);
|
||||
}
|
||||
}
|
||||
} catch (imageError) {
|
||||
console.log('No images found for carousel:', imageError);
|
||||
@ -910,12 +925,10 @@ const InfoScreen = () => {
|
||||
{/* Full Screen Content */}
|
||||
{isFullScreen && (
|
||||
<div className="multi-columns-container">
|
||||
{/* Column 1 - Appointments */}
|
||||
<div className="column-container">
|
||||
<div className="column-card" style={{ backgroundColor: 'rgba(255, 255, 255, 0.8)' }}>
|
||||
<h6 className="text-black" style={{ fontSize: '14px', fontWeight: '600' }}>Appointments</h6>
|
||||
<div className="app-main-content-fields-section">
|
||||
<div className="list row mb-4">
|
||||
<div className="col-md-12">
|
||||
<div className="column-card">
|
||||
<h6 className="text-black fullscreen-title">Appointments</h6>
|
||||
<table className="personnel-info-table info-screen-appointments-table">
|
||||
<thead>
|
||||
<tr>
|
||||
@ -942,29 +955,20 @@ const InfoScreen = () => {
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Column 2 - Time, Weather, Attendance Note, Gallery */}
|
||||
<div className="column-container">
|
||||
{/* First Piece - Time and Weather Cards */}
|
||||
<div className="row mb-4">
|
||||
{/* Time and Weather Cards Row */}
|
||||
<div className="row mb-4 time-weather-row">
|
||||
{/* Time Card */}
|
||||
<div className="col-md-6">
|
||||
<div className="card h-100" style={{borderRadius: '8px', backgroundColor: 'rgba(255, 255, 255, 0.8)'}}>
|
||||
<div className="card h-100">
|
||||
<div className="card-body text-center">
|
||||
<div className="mb-3">
|
||||
<div className="text-black" style={{ fontSize: '12px' }}>
|
||||
{moment(currentTime).format('dddd')}
|
||||
<div className="fullscreen-date-display">
|
||||
<div className="text-black">{moment(currentTime).format('dddd')}</div>
|
||||
<div className="text-black">{moment(currentTime).format('MMM Do, YYYY')}</div>
|
||||
<h5 className="text-primary mt-2 fullscreen-time">{moment(currentTime).format('HH:mm')}</h5>
|
||||
</div>
|
||||
<div className="text-black">
|
||||
{moment(currentTime).format('MMM Do, YYYY')}
|
||||
</div>
|
||||
<h5 className="text-primary mt-2" style={{ fontSize: '20px', fontWeight: '600' }}>
|
||||
{moment(currentTime).format('HH:mm')}
|
||||
</h5>
|
||||
</div>
|
||||
|
||||
{/* CSS Clock */}
|
||||
<div className="clock-container">
|
||||
<div className="clock">
|
||||
<div className="clock-face">
|
||||
@ -987,78 +991,41 @@ const InfoScreen = () => {
|
||||
|
||||
{/* Weather Card */}
|
||||
<div className="col-md-6">
|
||||
<div className="card h-100" style={{borderRadius: '8px', backgroundColor: 'rgba(255, 255, 255, 0.8)'}}>
|
||||
<div className="card h-100">
|
||||
<div className="card-body text-center">
|
||||
<div className="mb-3">
|
||||
<div className="text-black" style={{ fontSize: '12px' }}>
|
||||
New York, NY
|
||||
<div className="fullscreen-date-display">
|
||||
<div className="text-black">New York, NY</div>
|
||||
<div className="text-black">Partly Cloudy</div>
|
||||
<h5 className="text-primary mt-2 fullscreen-time">22°C</h5>
|
||||
</div>
|
||||
<div className="text-black">
|
||||
Partly Cloudy
|
||||
</div>
|
||||
<h5 className="text-primary mt-2" style={{ fontSize: '20px', fontWeight: '600' }}>
|
||||
22°C
|
||||
</h5>
|
||||
</div>
|
||||
|
||||
{/* Weather Icon */}
|
||||
<div className="weather-icon">
|
||||
<i className="fas fa-cloud-sun" style={{ fontSize: '48px', color: '#ffc107' }}></i>
|
||||
<i className="fas fa-cloud-sun"></i>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Second Piece - AttendanceNote Card */}
|
||||
<div className="mb-4">
|
||||
<div className="card" style={{borderRadius: '8px', maxWidth: '450px', backgroundColor: 'rgba(255, 255, 255, 0.8)'}}>
|
||||
{/* AttendanceNote Card */}
|
||||
<div className="attendance-note-wrapper">
|
||||
<div className="card">
|
||||
<div className="card-body">
|
||||
{/* Header with Logo and Edit Button */}
|
||||
<div className="d-flex justify-content-between align-items-start mb-3">
|
||||
<div className="d-flex justify-content-between align-items-start mb-2">
|
||||
<div className="d-flex align-items-center">
|
||||
<img src="/images/logo-trans.png" alt="Worldshine Logo" style={{ height: '30px', marginRight: '10px' }} />
|
||||
<strong className="logo-worldshine" style={{ color: '#0066B1', fontSize: '16px' }}>Worldshine</strong>
|
||||
<img src="/images/logo-trans.png" alt="Worldshine Logo" className="fullscreen-logo" />
|
||||
<strong className="logo-worldshine">Worldshine</strong>
|
||||
</div>
|
||||
<Button
|
||||
variant="link"
|
||||
className="p-0"
|
||||
onClick={handleEditClick}
|
||||
style={{ color: '#666' }}
|
||||
>
|
||||
<PencilSquare size={16} />
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
{/* Content Layout */}
|
||||
<div className="row">
|
||||
{/* Left Side - Text Content */}
|
||||
<div className="col-md-8">
|
||||
<h5 className="text-primary mb-2">{attendanceNote.slogan}</h5>
|
||||
<p className="text-muted" style={{ fontSize: '14px', lineHeight: '1.6' }}>
|
||||
{attendanceNote.introduction}
|
||||
</p>
|
||||
<p className="text-muted fullscreen-intro">{attendanceNote.introduction}</p>
|
||||
</div>
|
||||
|
||||
{/* Right Side - Image */}
|
||||
<div className="col-md-4">
|
||||
{attendanceNote.image ? (
|
||||
<img
|
||||
src={attendanceNote.image}
|
||||
alt="Attendance Note"
|
||||
className="img-fluid rounded"
|
||||
style={{ maxHeight: '120px', width: '100%', objectFit: 'cover' }}
|
||||
/>
|
||||
<img src={attendanceNote.image} alt="Attendance Note" className="img-fluid rounded fullscreen-note-img" />
|
||||
) : (
|
||||
<div
|
||||
className="d-flex align-items-center justify-content-center"
|
||||
style={{
|
||||
height: '120px',
|
||||
backgroundColor: '#f8f9fa',
|
||||
border: '2px dashed #dee2e6',
|
||||
borderRadius: '8px'
|
||||
}}
|
||||
>
|
||||
<div className="d-flex align-items-center justify-content-center fullscreen-placeholder">
|
||||
<span className="text-muted">No image</span>
|
||||
</div>
|
||||
)}
|
||||
@ -1068,56 +1035,26 @@ const InfoScreen = () => {
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Third Piece - Gallery */}
|
||||
<div className="mb-4">
|
||||
<div className="card" style={{borderRadius: '8px', maxWidth: '450px', backgroundColor: 'rgba(255, 255, 255, 0.8)'}}>
|
||||
{/* Gallery Card */}
|
||||
<div className="gallery-wrapper">
|
||||
<div className="card">
|
||||
<div className="card-body">
|
||||
{/* Header with Title and Edit Button */}
|
||||
<div className="d-flex justify-content-between align-items-center mb-3">
|
||||
<h6 className="text-black mb-0" style={{ fontSize: '14px', fontWeight: '500' }}>Gallery</h6>
|
||||
<Button
|
||||
variant="link"
|
||||
className="p-0"
|
||||
onClick={handleCarouselEditClick}
|
||||
style={{ color: '#666' }}
|
||||
>
|
||||
<PencilSquare size={16} />
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
{/* Image Carousel */}
|
||||
<h6 className="text-black mb-2 fullscreen-title">Gallery</h6>
|
||||
{carousel.images.length > 0 ? (
|
||||
<Carousel
|
||||
interval={10000}
|
||||
interval={carousel.images.length > 1 ? 5000 : null}
|
||||
controls={false}
|
||||
indicators={true}
|
||||
style={{ height: '200px' }}
|
||||
indicators={carousel.images.length > 1}
|
||||
className="fullscreen-carousel"
|
||||
>
|
||||
{carousel.images.map((image, index) => (
|
||||
<Carousel.Item key={index}>
|
||||
<img
|
||||
className="d-block w-100"
|
||||
src={image}
|
||||
alt={`Gallery ${index + 1}`}
|
||||
style={{
|
||||
height: '200px',
|
||||
objectFit: 'cover',
|
||||
borderRadius: '4px'
|
||||
}}
|
||||
/>
|
||||
<img className="d-block w-100 fullscreen-carousel-img" src={image} alt={`Gallery ${index + 1}`} />
|
||||
</Carousel.Item>
|
||||
))}
|
||||
</Carousel>
|
||||
) : (
|
||||
<div
|
||||
className="d-flex align-items-center justify-content-center"
|
||||
style={{
|
||||
height: '200px',
|
||||
backgroundColor: '#f8f9fa',
|
||||
border: '2px dashed #dee2e6',
|
||||
borderRadius: '4px'
|
||||
}}
|
||||
>
|
||||
<div className="d-flex align-items-center justify-content-center fullscreen-placeholder">
|
||||
<span className="text-muted">No images in gallery</span>
|
||||
</div>
|
||||
)}
|
||||
@ -1125,17 +1062,16 @@ const InfoScreen = () => {
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Column 3 - Activities and Menu */}
|
||||
<div className="column-container">
|
||||
<div className="column-card mb-4" style={{ backgroundColor: 'rgba(255, 255, 255, 0.8)' }}>
|
||||
<h6 className="text-black mb-3" style={{ fontSize: '14px', fontWeight: '500' }}>Activities</h6>
|
||||
|
||||
{/* Activities Table */}
|
||||
|
||||
<div className="column-card">
|
||||
<h6 className="text-black fullscreen-title">Activities</h6>
|
||||
<table className="personnel-info-table info-screen-appointments-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Time</th>
|
||||
<th>Activity Name</th>
|
||||
<th>Activity</th>
|
||||
<th>Location</th>
|
||||
</tr>
|
||||
</thead>
|
||||
@ -1155,14 +1091,9 @@ const InfoScreen = () => {
|
||||
)}
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
|
||||
</div>
|
||||
<div className="column-card" style={{ backgroundColor: 'rgba(255, 255, 255, 0.8)' }}>
|
||||
{/* Menu Table */}
|
||||
<div className="list row mb-4">
|
||||
<div className="col-md-12">
|
||||
<h6 className="text-black mb-3" style={{ fontSize: '14px', fontWeight: '500' }}>Menu</h6>
|
||||
<div className="column-card">
|
||||
<h6 className="text-black fullscreen-title">Menu</h6>
|
||||
<table className="personnel-info-table info-screen-appointments-table">
|
||||
<thead>
|
||||
<tr>
|
||||
@ -1190,8 +1121,6 @@ const InfoScreen = () => {
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{!isFullScreen && (
|
||||
|
||||
@ -5,7 +5,7 @@ import { useNavigate } from "react-router-dom";
|
||||
const Landing = () => {
|
||||
const navigate = useNavigate();
|
||||
const goToAdmin = () => {
|
||||
navigate('/admin');
|
||||
navigate('/dashboard/dashboard');
|
||||
}
|
||||
|
||||
const getLogoSuffix = () => {
|
||||
@ -31,9 +31,9 @@ const Landing = () => {
|
||||
<div className="landing-content mb-4">
|
||||
<button onClick={() => goToAdmin()} className="btn btn-primary">Center Management Access</button>
|
||||
</div>
|
||||
<div className="landing-content">
|
||||
{/* <div className="landing-content">
|
||||
<button className="btn btn-primary">HR Management Access</button>
|
||||
</div>
|
||||
</div> */}
|
||||
</div>
|
||||
</div>
|
||||
<div className="landing-img"/>
|
||||
|
||||
@ -22,7 +22,29 @@ const CreateMessage = () => {
|
||||
navigate(`/messages/list`);
|
||||
}
|
||||
|
||||
const validateMessage = () => {
|
||||
const errors = [];
|
||||
|
||||
// Required fields validation
|
||||
if (!messageGroup || messageGroup === '') {
|
||||
errors.push('Message Group');
|
||||
}
|
||||
if (!messageName || messageName.trim() === '') {
|
||||
errors.push('Message Name');
|
||||
}
|
||||
|
||||
if (errors.length > 0) {
|
||||
window.alert(`Please fill in the following required fields:\n${errors.join('\n')}`);
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
};
|
||||
|
||||
const saveMessage = () => {
|
||||
if (!validateMessage()) {
|
||||
return;
|
||||
}
|
||||
|
||||
const data = {
|
||||
message_group: messageGroup,
|
||||
message_title: messageTitle,
|
||||
@ -38,10 +60,13 @@ const CreateMessage = () => {
|
||||
<>
|
||||
<div className="list row mb-4">
|
||||
<Breadcrumb>
|
||||
<Breadcrumb.Item>General</Breadcrumb.Item>
|
||||
<Breadcrumb.Item active>
|
||||
<Breadcrumb.Item href="/">General</Breadcrumb.Item>
|
||||
<Breadcrumb.Item href="/messages/list">
|
||||
Messaging
|
||||
</Breadcrumb.Item>
|
||||
<Breadcrumb.Item active>
|
||||
Create Message Template
|
||||
</Breadcrumb.Item>
|
||||
</Breadcrumb>
|
||||
<div className="col-md-12 text-primary">
|
||||
<h4>
|
||||
|
||||
@ -49,7 +49,7 @@ const MessageList = () => {
|
||||
<>
|
||||
<div className="list row mb-4">
|
||||
<Breadcrumb>
|
||||
<Breadcrumb.Item>General</Breadcrumb.Item>
|
||||
<Breadcrumb.Item href="/">General</Breadcrumb.Item>
|
||||
<Breadcrumb.Item active>
|
||||
Messaging
|
||||
</Breadcrumb.Item>
|
||||
|
||||
@ -58,7 +58,7 @@ const MessageTokenEditor = () => {
|
||||
<>
|
||||
<div className="list row mb-4">
|
||||
<Breadcrumb>
|
||||
<Breadcrumb.Item>General</Breadcrumb.Item>
|
||||
<Breadcrumb.Item href="/">General</Breadcrumb.Item>
|
||||
<Breadcrumb.Item active>
|
||||
Messaging
|
||||
</Breadcrumb.Item>
|
||||
|
||||
@ -67,8 +67,8 @@ const SendMessage = () => {
|
||||
<>
|
||||
<div className="list row mb-4">
|
||||
<Breadcrumb>
|
||||
<Breadcrumb.Item>General</Breadcrumb.Item>
|
||||
<Breadcrumb.Item active>
|
||||
<Breadcrumb.Item href="/">General</Breadcrumb.Item>
|
||||
<Breadcrumb.Item href="/messages/list">
|
||||
Messaging
|
||||
</Breadcrumb.Item>
|
||||
<Breadcrumb.Item active>
|
||||
@ -96,32 +96,40 @@ const SendMessage = () => {
|
||||
<span className="required">*</span>
|
||||
</div>
|
||||
<Select styles={{
|
||||
control: (baseStyles, state) => ({
|
||||
control: (baseStyles) => ({
|
||||
...baseStyles,
|
||||
width: '350px',
|
||||
height: '45px',
|
||||
'padding-top': 0,
|
||||
'padding-bottom': 0,
|
||||
'margin-top': 0,
|
||||
'margin-bottom': 0
|
||||
borderRadius: '8px'
|
||||
}),
|
||||
indicatorSeparator: (baseStyles, state) => ({
|
||||
...baseStyles,
|
||||
width: 0
|
||||
indicatorSeparator: () => ({
|
||||
display: 'none'
|
||||
}),
|
||||
indicatorsContainer: (baseStyles) => ({
|
||||
dropdownIndicator: (baseStyles) => ({
|
||||
...baseStyles,
|
||||
'margin-top': '-10px'
|
||||
paddingRight: '8px',
|
||||
color: '#333',
|
||||
'&:hover': { color: '#000' }
|
||||
}),
|
||||
valueContainer: (baseStyles) => ({
|
||||
...baseStyles,
|
||||
height: '43px',
|
||||
padding: '0 8px',
|
||||
}),
|
||||
input: (baseStyles) => ({
|
||||
...baseStyles,
|
||||
margin: '0px',
|
||||
padding: '0px',
|
||||
height: '30px',
|
||||
width: '290px'
|
||||
}),
|
||||
placeholder: (baseStyles) => ({
|
||||
...baseStyles,
|
||||
'margin-top': '-10px',
|
||||
'font-size': '13px'
|
||||
fontSize: '13px'
|
||||
}),
|
||||
singleValue: (baseStyles, state) => ({
|
||||
singleValue: (baseStyles) => ({
|
||||
...baseStyles,
|
||||
'margin-top': '-10px',
|
||||
'font-size': '13px'
|
||||
margin: '0px',
|
||||
fontSize: '13px'
|
||||
})
|
||||
}} value={contactSeniorPhone || ''} onChange={selectedData => onContactSeniorChange(selectedData)} options={[{value: '', label: ''}, ...seniorPhoneList.map(senior => ({
|
||||
value: senior?.mobile_phone || '',
|
||||
@ -133,32 +141,40 @@ const SendMessage = () => {
|
||||
<div className="me-4">
|
||||
<div className="field-label">Select Message Template (Optional)</div>
|
||||
<Select styles={{
|
||||
control: (baseStyles, state) => ({
|
||||
control: (baseStyles) => ({
|
||||
...baseStyles,
|
||||
width: '350px',
|
||||
height: '45px',
|
||||
'padding-top': 0,
|
||||
'padding-bottom': 0,
|
||||
'margin-top': 0,
|
||||
'margin-bottom': 0
|
||||
borderRadius: '8px'
|
||||
}),
|
||||
indicatorSeparator: (baseStyles, state) => ({
|
||||
...baseStyles,
|
||||
width: 0
|
||||
indicatorSeparator: () => ({
|
||||
display: 'none'
|
||||
}),
|
||||
indicatorsContainer: (baseStyles) => ({
|
||||
dropdownIndicator: (baseStyles) => ({
|
||||
...baseStyles,
|
||||
'margin-top': '-10px'
|
||||
paddingRight: '8px',
|
||||
color: '#333',
|
||||
'&:hover': { color: '#000' }
|
||||
}),
|
||||
valueContainer: (baseStyles) => ({
|
||||
...baseStyles,
|
||||
height: '43px',
|
||||
padding: '0 8px',
|
||||
}),
|
||||
input: (baseStyles) => ({
|
||||
...baseStyles,
|
||||
margin: '0px',
|
||||
padding: '0px',
|
||||
height: '30px',
|
||||
width: '290px'
|
||||
}),
|
||||
placeholder: (baseStyles) => ({
|
||||
...baseStyles,
|
||||
'margin-top': '-10px',
|
||||
'font-size': '13px'
|
||||
fontSize: '13px'
|
||||
}),
|
||||
singleValue: (baseStyles, state) => ({
|
||||
singleValue: (baseStyles) => ({
|
||||
...baseStyles,
|
||||
'margin-top': '-10px',
|
||||
'font-size': '13px'
|
||||
margin: '0px',
|
||||
fontSize: '13px'
|
||||
})
|
||||
}} value={messageTemplate || ''} onChange={selectedData => {setMessageTemplate(selectedData); setMessageText(selectedData.value)}} options={[{value: '', label: ''}, ...messageTempateList.map(template => ({
|
||||
value: template.message_body || '',
|
||||
|
||||
@ -77,7 +77,7 @@ const SentMessageList = () => {
|
||||
<>
|
||||
<div className="list row mb-4">
|
||||
<Breadcrumb>
|
||||
<Breadcrumb.Item>General</Breadcrumb.Item>
|
||||
<Breadcrumb.Item href="/">General</Breadcrumb.Item>
|
||||
<Breadcrumb.Item active>
|
||||
Messaging
|
||||
</Breadcrumb.Item>
|
||||
|
||||
@ -41,7 +41,29 @@ const UpdateMessage = () => {
|
||||
navigate(`/messages/list`);
|
||||
}
|
||||
|
||||
const validateMessage = () => {
|
||||
const errors = [];
|
||||
|
||||
// Required fields validation
|
||||
if (!messageGroup || messageGroup === '') {
|
||||
errors.push('Message Group');
|
||||
}
|
||||
if (!messageName || messageName.trim() === '') {
|
||||
errors.push('Message Name');
|
||||
}
|
||||
|
||||
if (errors.length > 0) {
|
||||
window.alert(`Please fill in the following required fields:\n${errors.join('\n')}`);
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
};
|
||||
|
||||
const saveMessage = () => {
|
||||
if (!validateMessage()) {
|
||||
return;
|
||||
}
|
||||
|
||||
const data = {
|
||||
message_group: messageGroup,
|
||||
message_title: messageTitle,
|
||||
@ -57,10 +79,13 @@ const UpdateMessage = () => {
|
||||
<>
|
||||
<div className="list row mb-4">
|
||||
<Breadcrumb>
|
||||
<Breadcrumb.Item>General</Breadcrumb.Item>
|
||||
<Breadcrumb.Item active>
|
||||
<Breadcrumb.Item href="/">General</Breadcrumb.Item>
|
||||
<Breadcrumb.Item href="/messages/list">
|
||||
Messaging
|
||||
</Breadcrumb.Item>
|
||||
<Breadcrumb.Item active>
|
||||
Update Message Template
|
||||
</Breadcrumb.Item>
|
||||
</Breadcrumb>
|
||||
<div className="col-md-12 text-primary">
|
||||
<h4>
|
||||
|
||||
@ -1,34 +1,36 @@
|
||||
import React, {useState, useEffect} from "react";
|
||||
import { useNavigate, useParams } from "react-router-dom";
|
||||
import { AuthService, ResourceService } from "../../services";
|
||||
import { Archive } from "react-bootstrap-icons";
|
||||
import { Spinner, Breadcrumb, BreadcrumbItem, Tabs, Tab } from "react-bootstrap";
|
||||
import { RESOURCE_TYPE_OPTIONS, RESOURCE_SPECIALTY_OPTIONS } from "../../shared/constants";
|
||||
|
||||
const CreateResource = () => {
|
||||
const navigate = useNavigate();
|
||||
const urlParams = useParams();
|
||||
const [name, setName] = useState('');
|
||||
const [originalName, setOriginalName] = useState('');
|
||||
const [branchName, setBranchName] = useState('');
|
||||
|
||||
// Basic Information
|
||||
const [name, setName] = useState(''); // Provider name
|
||||
const [officeName, setOfficeName] = useState('');
|
||||
const [specialty, setSpecialty] = useState('');
|
||||
const [type, setType] = useState('');
|
||||
const [category, setCategory] = useState('');
|
||||
const [description, setDescription] = useState('');
|
||||
const [color, setColor] = useState('');
|
||||
const [address, setAddress] = useState('');
|
||||
|
||||
// Contact Information
|
||||
const [phone, setPhone] = useState(''); // Office Phone Number
|
||||
const [contact, setContact] = useState(''); // Secondary Phone Number
|
||||
const [fax, setFax] = useState(''); // Fax Number
|
||||
const [email, setEmail] = useState('');
|
||||
|
||||
// Address (split fields)
|
||||
const [addressLine1, setAddressLine1] = useState('');
|
||||
const [addressLine2, setAddressLine2] = useState('');
|
||||
const [city, setCity] = useState('');
|
||||
const [state, setState] = useState('');
|
||||
const [zipcode, setZipcode] = useState('');
|
||||
const [contact, setContact] = useState('');
|
||||
const [phone, setPhone] = useState('');
|
||||
const [dataObject, setDataObject] = useState(undefined);
|
||||
|
||||
// Additional Information
|
||||
const [note, setNote] = useState('');
|
||||
const [fax, setFax] = useState('');
|
||||
const [status, setStatus] = useState('active');
|
||||
const [email, setEmail] = useState('');
|
||||
|
||||
|
||||
const redirectTo = (id) => {
|
||||
const redirectTo = () => {
|
||||
navigate(`/medical/resources/list`);
|
||||
}
|
||||
|
||||
@ -36,42 +38,72 @@ const CreateResource = () => {
|
||||
navigate(`/medical/resources/${id}`);
|
||||
}
|
||||
|
||||
const validateResource = () => {
|
||||
const errors = [];
|
||||
|
||||
// Required fields validation
|
||||
if (!name || name.trim() === '') {
|
||||
errors.push('Provider');
|
||||
}
|
||||
if (!phone || phone.trim() === '') {
|
||||
errors.push('Office Phone Number');
|
||||
}
|
||||
|
||||
if (errors.length > 0) {
|
||||
window.alert(`Please fill in the following required fields:\n${errors.join('\n')}`);
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
};
|
||||
|
||||
const saveResource = () => {
|
||||
if (!validateResource()) {
|
||||
return;
|
||||
}
|
||||
|
||||
const combinedAddress = `${addressLine1}${addressLine2 ? ', ' + addressLine2 : ''}, ${city}, ${state} ${zipcode}`.trim();
|
||||
|
||||
const newResource = {
|
||||
parent_id: '5eee3552b02fac3d4acfd5ea',
|
||||
ext_id: '',
|
||||
data: dataObject? JSON.parse(dataObject) : {},
|
||||
name,
|
||||
description,
|
||||
note,
|
||||
name_original: originalName,
|
||||
name_branch: branchName,
|
||||
data: {},
|
||||
|
||||
// Basic Information
|
||||
name, // Provider
|
||||
office_name: officeName,
|
||||
name_original: officeName, // Legacy field
|
||||
specialty,
|
||||
type,
|
||||
|
||||
// Contact Information
|
||||
phone, // Office Phone Number
|
||||
contact, // Secondary Phone Number
|
||||
fax, // Fax Number
|
||||
email,
|
||||
category,
|
||||
color,
|
||||
address,
|
||||
state,
|
||||
|
||||
// Address (split fields)
|
||||
address_line_1: addressLine1,
|
||||
address_line_2: addressLine2,
|
||||
city,
|
||||
state,
|
||||
zipcode,
|
||||
contact,
|
||||
phone,
|
||||
fax,
|
||||
status,
|
||||
address: combinedAddress, // Legacy field
|
||||
|
||||
// Additional Information
|
||||
note,
|
||||
|
||||
// System fields
|
||||
status: 'active',
|
||||
create_by: localStorage.getItem('user') && JSON.parse(localStorage.getItem('user'))?.name,
|
||||
create_date: new Date(),
|
||||
edit_by: localStorage.getItem('user') && JSON.parse(localStorage.getItem('user'))?.name,
|
||||
edit_date: new Date(),
|
||||
edit_history:[{ employee: localStorage.getItem('user') && JSON.parse(localStorage.getItem('user'))?.name, date: new Date() }]
|
||||
}
|
||||
|
||||
console.log('new Resource', newResource);
|
||||
edit_history: [{ employee: localStorage.getItem('user') && JSON.parse(localStorage.getItem('user'))?.name, date: new Date() }]
|
||||
};
|
||||
|
||||
ResourceService.createNewResource(newResource).then(data => redirectToView(data?.data?.id));
|
||||
};
|
||||
|
||||
|
||||
useEffect(() => {
|
||||
if (!AuthService.canAccessLegacySystem()) {
|
||||
window.alert('You haven\'t login yet OR this user does not have access to this page. Please change an admin account to login.')
|
||||
@ -80,13 +112,12 @@ const CreateResource = () => {
|
||||
}
|
||||
}, []);
|
||||
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className="list row mb-4">
|
||||
<Breadcrumb>
|
||||
<Breadcrumb.Item>Medical</Breadcrumb.Item>
|
||||
<Breadcrumb.Item active>
|
||||
<Breadcrumb.Item href="/medical/index">Medical</Breadcrumb.Item>
|
||||
<Breadcrumb.Item href="/medical/resources/list">
|
||||
Provider Information
|
||||
</Breadcrumb.Item>
|
||||
<Breadcrumb.Item active>
|
||||
@ -99,121 +130,98 @@ const CreateResource = () => {
|
||||
</div>
|
||||
<div className="app-main-content-list-container">
|
||||
<div className="app-main-content-list-func-container">
|
||||
<Tabs defaultActiveKey="providerInfo" id="providerss-tab">
|
||||
<Tabs defaultActiveKey="providerInfo" id="providers-tab">
|
||||
<Tab eventKey="providerInfo" title="Provider Information">
|
||||
<h6 className="text-primary">Basic Information</h6>
|
||||
<h6 className="text-primary mt-4">Basic Information</h6>
|
||||
<div className="app-main-content-fields-section">
|
||||
<div className="me-4">
|
||||
<div className="field-label">Doctor Name
|
||||
<span className="required">*</span>
|
||||
</div>
|
||||
<input placeholder="e.g.,Cao Qing" type="text" value={name || ''} onChange={e => setName(e.target.value)}/>
|
||||
<div className="field-label">Provider <span className="required">*</span></div>
|
||||
<input placeholder="e.g., Dr. Cao Qing" type="text" value={name} onChange={e => setName(e.target.value)}/>
|
||||
</div>
|
||||
<div className="me-4">
|
||||
<div className="field-label">Office Name
|
||||
</div>
|
||||
<input type="text" placeholder="e.g.,Silver Spring Family Medicine Clinic" value={originalName || ''} onChange={e => setOriginalName(e.target.value)}/>
|
||||
<div className="field-label">Office Name</div>
|
||||
<input type="text" placeholder="e.g., Silver Spring Family Medicine Clinic" value={officeName} onChange={e => setOfficeName(e.target.value)}/>
|
||||
</div>
|
||||
<div className="me-4">
|
||||
<div className="field-label">Branch Name
|
||||
</div>
|
||||
<input type="text" placeholder="e.g.,Silver Spring" value={branchName || ''} onChange={e => setBranchName(e.target.value)}/>
|
||||
</div>
|
||||
<div className="me-4">
|
||||
<div className="field-label">Specialty
|
||||
</div>
|
||||
<select value={specialty} onChange={e => setSpecialty(e.target.value)}>
|
||||
<option value=""></option>
|
||||
{
|
||||
ResourceService.resourceOptionList.map((item) => <option value={item}>{item}</option>)
|
||||
}
|
||||
</select>
|
||||
</div>
|
||||
<div className="me-4">
|
||||
<div className="field-label">Type
|
||||
</div>
|
||||
<div className="field-label">Type</div>
|
||||
<select value={type} onChange={e => setType(e.target.value)}>
|
||||
<option value="doctor">Doctor</option>
|
||||
<option value="pharmacy">Pharmacy</option>
|
||||
<option value="hospital">Hospital</option>
|
||||
<option value="surgical center">Surgical Center</option>
|
||||
<option value="government agency">Government Agency</option>
|
||||
<option value="other">Other</option>
|
||||
<option value="">Select...</option>
|
||||
{RESOURCE_TYPE_OPTIONS.map((item, index) => (
|
||||
<option key={index} value={item.value}>{item.label}</option>
|
||||
))}
|
||||
</select>
|
||||
</div>
|
||||
<div className="me-4">
|
||||
<div className="field-label">Specialty</div>
|
||||
<select value={specialty} onChange={e => setSpecialty(e.target.value)}>
|
||||
<option value="">Select...</option>
|
||||
{RESOURCE_SPECIALTY_OPTIONS.map((item, index) => (
|
||||
<option key={index} value={item.value}>{item.label}</option>
|
||||
))}
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<h6 className="text-primary">Contact Information</h6>
|
||||
<div className="app-main-content-fields-section">
|
||||
<div className="me-4">
|
||||
<div className="field-label">Office Phone Number
|
||||
<span className="required">*</span>
|
||||
</div>
|
||||
<input placeholder="e.g.,240-463-1098" type="text" value={phone || ''} onChange={e => setPhone(e.target.value)}/>
|
||||
<div className="field-label">Office Phone Number <span className="required">*</span></div>
|
||||
<input placeholder="e.g., 240-463-1098" type="text" value={phone} onChange={e => setPhone(e.target.value)}/>
|
||||
</div>
|
||||
<div className="me-4">
|
||||
<div className="field-label">Contact
|
||||
</div>
|
||||
{/* <textarea value={contact || ''} onChange={e => setContact(e.target.value)}/> */}
|
||||
<input type="text" placeholder="e.g.,240-463-1698" value={contact || ''} onChange={e => setContact(e.target.value)}/>
|
||||
<div className="field-label">Secondary Phone Number</div>
|
||||
<input type="text" placeholder="e.g., 240-463-1698" value={contact} onChange={e => setContact(e.target.value)}/>
|
||||
</div>
|
||||
<div className="me-4">
|
||||
<div className="field-label">Fax Number
|
||||
</div>
|
||||
<input type="text" placement="e.g.,240-463-1698" value={fax || ''} onChange={e => setFax(e.target.value)}/>
|
||||
<div className="field-label">Fax Number</div>
|
||||
<input type="text" placeholder="e.g., 240-463-1698" value={fax} onChange={e => setFax(e.target.value)}/>
|
||||
</div>
|
||||
<div className="me-4">
|
||||
<div className="field-label">Email
|
||||
</div>
|
||||
<input type="email" placement="e.g.,example@gmail.com" value={email || ''} onChange={e => setEmail(e.target.value)}/>
|
||||
<div className="field-label">Email</div>
|
||||
<input type="email" placeholder="e.g., example@gmail.com" value={email} onChange={e => setEmail(e.target.value)}/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<h6 className="text-primary">Provider Address</h6>
|
||||
<div className="app-main-content-fields-section">
|
||||
<div className="me-4">
|
||||
<div className="field-label">Address Line 1
|
||||
<span className="required">*</span>
|
||||
</div>
|
||||
<input type="text" placement="e.g.,555 Cloverly Forest Dr" value={address || ''} onChange={e => setAddress(e.target.value)}/>
|
||||
<div className="field-label">Address Line 1 <span className="required">*</span></div>
|
||||
<input type="text" placeholder="e.g., 555 Cloverly Forest Dr" value={addressLine1} onChange={e => setAddressLine1(e.target.value)}/>
|
||||
</div>
|
||||
<div className="me-4">
|
||||
<div className="field-label">City
|
||||
<span className="required">*</span>
|
||||
<div className="field-label">Address Line 2</div>
|
||||
<input type="text" placeholder="e.g., Suite 200" value={addressLine2} onChange={e => setAddressLine2(e.target.value)}/>
|
||||
</div>
|
||||
<input type="text" placement="e.g.,Rockville" value={city || ''} onChange={e => setCity(e.target.value)}/>
|
||||
</div>
|
||||
<div className="app-main-content-fields-section">
|
||||
<div className="me-4">
|
||||
<div className="field-label">City <span className="required">*</span></div>
|
||||
<input type="text" placeholder="e.g., Rockville" value={city} onChange={e => setCity(e.target.value)}/>
|
||||
</div>
|
||||
<div className="me-4">
|
||||
<div className="field-label">State
|
||||
<span className="required">*</span>
|
||||
</div>
|
||||
<input placement="e.g.,MD" type="text" value={state || ''} onChange={e => setState(e.target.value)}/>
|
||||
<div className="field-label">State <span className="required">*</span></div>
|
||||
<input type="text" placeholder="e.g., MD" value={state} onChange={e => setState(e.target.value)}/>
|
||||
</div>
|
||||
<div className="me-4">
|
||||
<div className="field-label">Zip Code
|
||||
<span className="required">*</span>
|
||||
</div>
|
||||
<input type="text" value={zipcode || ''} onChange={e => setZipcode(e.target.value)}/>
|
||||
<div className="field-label">Zip Code <span className="required">*</span></div>
|
||||
<input type="text" placeholder="e.g., 20850" value={zipcode} onChange={e => setZipcode(e.target.value)}/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<h6 className="text-primary">Additional Information</h6>
|
||||
<div className="app-main-content-fields-section">
|
||||
<div className="me-4">
|
||||
<div className="field-label">Note
|
||||
</div>
|
||||
<textarea value={note || ''} onChange={e => setNote(e.target.value)}/>
|
||||
</div>
|
||||
</div>
|
||||
<div className="app-main-content-fields-section">
|
||||
<div className="me-4">
|
||||
<div className="field-label">Description
|
||||
</div>
|
||||
<input type="text" placeholder={'e.g.,Description'} value={description || ''} onChange={e => setDescription(e.target.value)}/>
|
||||
</div>
|
||||
<div className="me-4">
|
||||
<div className="field-label">Color
|
||||
</div>
|
||||
<input type="text" placeholder={'e.g.,red'} value={color || ''} onChange={e => setColor(e.target.value)}/>
|
||||
<div className="field-label">Note</div>
|
||||
<textarea
|
||||
placeholder="e.g., Preferred provider for cardiology referrals"
|
||||
value={note}
|
||||
onChange={e => setNote(e.target.value)}
|
||||
rows={4}
|
||||
style={{width: '400px'}}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="list row mb-5">
|
||||
<div className="col-md-12 col-sm-12 col-xs-12">
|
||||
<button className="btn btn-default btn-sm float-right" onClick={() => redirectTo()}> Cancel </button>
|
||||
@ -224,14 +232,6 @@ const CreateResource = () => {
|
||||
</Tabs>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* <div className="col-md-4 mb-4">
|
||||
<div>Status:</div>
|
||||
<select value={status} onChange={e => setStatus(e.target.value)}>
|
||||
<option value="active">Active</option>
|
||||
<option value="inactive">Inactive</option>
|
||||
</select>
|
||||
</div> */}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
@ -22,8 +22,13 @@ const ResourcesList = () => {
|
||||
const [currentPage, setCurrentPage] = useState(1);
|
||||
const [columns, setColumns] = useState([
|
||||
{
|
||||
key: 'display_name',
|
||||
label: 'Name',
|
||||
key: 'name',
|
||||
label: 'Provider',
|
||||
show: true
|
||||
},
|
||||
{
|
||||
key: 'office_name',
|
||||
label: 'Office Name',
|
||||
show: true
|
||||
},
|
||||
{
|
||||
@ -36,20 +41,20 @@ const ResourcesList = () => {
|
||||
label: 'Specialty',
|
||||
show: true
|
||||
},
|
||||
{
|
||||
key: 'phone',
|
||||
label: 'Office Phone Number',
|
||||
show: true
|
||||
},
|
||||
{
|
||||
key: 'email',
|
||||
label: 'Email',
|
||||
show: true
|
||||
},
|
||||
{
|
||||
key: 'address',
|
||||
key: 'display_address',
|
||||
label: 'Address',
|
||||
show: true
|
||||
},
|
||||
{
|
||||
key: 'display_contact',
|
||||
label: 'Contact',
|
||||
show: true
|
||||
}
|
||||
]);
|
||||
useEffect(() => {
|
||||
@ -71,7 +76,19 @@ const ResourcesList = () => {
|
||||
return a[sorting.key]?.localeCompare(b[sorting.key]);
|
||||
});
|
||||
const results = sorting.order === 'asc' ? sortedResources : sortedResources.reverse();
|
||||
setCurrentItems(results.slice(itemOffset, endOffset)?.map(item => Object.assign({}, item, { display_name: `${item?.name || ''}${item?.name ? '-': ''}${item?.name_original || ''}${item?.name_original ? '-': ''}${item?.name_branch || ''}`}, {display_contact: item?.contact || item?.phone})));
|
||||
setCurrentItems(results.slice(itemOffset, endOffset)?.map(item => {
|
||||
// Format address for display
|
||||
const line1 = item?.address_line_1 || item?.address || '';
|
||||
const city = item?.city || '';
|
||||
const state = item?.state || '';
|
||||
const zipcode = item?.zipcode || '';
|
||||
const display_address = [line1, city, state, zipcode].filter(Boolean).join(', ') || '-';
|
||||
|
||||
return Object.assign({}, item, {
|
||||
office_name: item?.office_name || item?.name_original || '-',
|
||||
display_address
|
||||
});
|
||||
}));
|
||||
setPageCount(Math.ceil(((showDeletedItems ? resources : resources.filter(item => item.status === 'active') )?.filter(resource => (resource?.name?.toLowerCase().includes(keyword.toLowerCase()) || (resource?.specialty?.toLowerCase().includes(keyword.toLowerCase()) ) || (resource?.address?.toLowerCase().includes(keyword.toLowerCase()) )))).length / itemsPerPage));
|
||||
}, [resources, itemOffset, keyword, itemsPerPage, showDeletedItems, sorting]);
|
||||
|
||||
@ -194,13 +211,14 @@ const ResourcesList = () => {
|
||||
{
|
||||
currentItems?.map((resource, index) => <tr key={resource?.id}>
|
||||
<td className="td-checkbox"><input type="checkbox" checked={selectedItems.includes(resource.id)} onClick={()=>toggleItem(resource?.id)}/></td>
|
||||
<td className="td-index">{index + 1}</td>
|
||||
{columns.find(col => col.key === 'display_name')?.show && <td> {AuthService.canAccessLegacySystem() && <PencilSquare size={16} className="clickable me-2" onClick={() => goToEdit(resource?.id)}></PencilSquare>} {AuthService.canAccessLegacySystem() ? <button className="btn btn-link btn-sm" onClick={() => goToView(resource?.id)}>{resource?.display_name}</button> : resource?.display_name } </td>}
|
||||
{columns.find(col => col.key === 'type')?.show && <td>{resource?.type}</td>}
|
||||
{columns.find(col => col.key === 'specialty')?.show && <td>{resource?.specialty}</td>}
|
||||
{columns.find(col => col.key === 'email')?.show && <td>{resource?.email}</td>}
|
||||
{columns.find(col => col.key === 'address')?.show && <td>{resource?.address}</td>}
|
||||
{columns.find(col => col.key === 'display_contact')?.show && <td>{resource?.display_contact}</td>}
|
||||
<td className="td-index">{itemOffset + index + 1}</td>
|
||||
{columns.find(col => col.key === 'name')?.show && <td> {AuthService.canAccessLegacySystem() && <PencilSquare size={16} className="clickable me-2" onClick={() => goToEdit(resource?.id)}></PencilSquare>} {AuthService.canAccessLegacySystem() ? <button className="btn btn-link btn-sm" onClick={() => goToView(resource?.id)}>{resource?.name || '-'}</button> : (resource?.name || '-') } </td>}
|
||||
{columns.find(col => col.key === 'office_name')?.show && <td>{resource?.office_name || '-'}</td>}
|
||||
{columns.find(col => col.key === 'type')?.show && <td>{resource?.type || '-'}</td>}
|
||||
{columns.find(col => col.key === 'specialty')?.show && <td>{resource?.specialty || '-'}</td>}
|
||||
{columns.find(col => col.key === 'phone')?.show && <td>{resource?.phone || '-'}</td>}
|
||||
{columns.find(col => col.key === 'email')?.show && <td>{resource?.email || '-'}</td>}
|
||||
{columns.find(col => col.key === 'display_address')?.show && <td>{resource?.display_address}</td>}
|
||||
</tr>)
|
||||
}
|
||||
</tbody>
|
||||
@ -249,7 +267,7 @@ const ResourcesList = () => {
|
||||
<>
|
||||
<div className="list row mb-4">
|
||||
<Breadcrumb>
|
||||
<Breadcrumb.Item>Medical</Breadcrumb.Item>
|
||||
<Breadcrumb.Item href="/medical/index">Medical</Breadcrumb.Item>
|
||||
<Breadcrumb.Item active>
|
||||
Provider Information
|
||||
</Breadcrumb.Item>
|
||||
@ -260,15 +278,15 @@ const ResourcesList = () => {
|
||||
</h4>
|
||||
</div>
|
||||
</div>
|
||||
<div className="app-main-content-list-container">
|
||||
<div className="app-main-content-list-container list-page">
|
||||
<div className="app-main-content-list-func-container">
|
||||
<Tabs defaultActiveKey="activeProviders" id="provider-tab" onSelect={(k) => showArchive(k)}>
|
||||
<Tab eventKey="activeProviders" title="Active Providers">
|
||||
{table}
|
||||
</Tab>
|
||||
<Tab eventKey="archivedProviders" title="Archived Providers">
|
||||
{/* <Tab eventKey="archivedProviders" title="Archived Providers">
|
||||
{table}
|
||||
</Tab>
|
||||
</Tab> */}
|
||||
</Tabs>
|
||||
<div className="list-func-panel">
|
||||
<input className="me-2 with-search-icon" type="text" placeholder="Search" value={keyword} onChange={(e) => setKeyword(e.currentTarget.value)} />
|
||||
|
||||
@ -2,36 +2,40 @@ import React, {useState, useEffect} from "react";
|
||||
import { useNavigate, useParams } from "react-router-dom";
|
||||
import { AuthService, ResourceService } from "../../services";
|
||||
import { Archive } from "react-bootstrap-icons";
|
||||
import { Spinner, Breadcrumb, BreadcrumbItem, Tabs, Tab } from "react-bootstrap";
|
||||
import { Spinner, Breadcrumb, BreadcrumbItem, Tabs, Tab, Modal, Button } from "react-bootstrap";
|
||||
import { RESOURCE_TYPE_OPTIONS, RESOURCE_SPECIALTY_OPTIONS } from "../../shared/constants";
|
||||
|
||||
const UpdateResource = () => {
|
||||
const navigate = useNavigate();
|
||||
const urlParams = useParams();
|
||||
const [currentResource, setCurrentResource] = useState(undefined);
|
||||
const [name, setName] = useState('');
|
||||
const [originalName, setOriginalName] = useState('');
|
||||
|
||||
// Basic Information
|
||||
const [name, setName] = useState(''); // Provider name
|
||||
const [officeName, setOfficeName] = useState('');
|
||||
const [specialty, setSpecialty] = useState('');
|
||||
const [type, setType] = useState('');
|
||||
const [category, setCategory] = useState('');
|
||||
const [description, setDescription] = useState('');
|
||||
const [color, setColor] = useState('');
|
||||
const [address, setAddress] = useState('');
|
||||
|
||||
// Contact Information
|
||||
const [phone, setPhone] = useState(''); // Office Phone Number
|
||||
const [contact, setContact] = useState(''); // Secondary Phone Number
|
||||
const [fax, setFax] = useState(''); // Fax Number
|
||||
const [email, setEmail] = useState('');
|
||||
|
||||
// Address (split fields)
|
||||
const [addressLine1, setAddressLine1] = useState('');
|
||||
const [addressLine2, setAddressLine2] = useState('');
|
||||
const [city, setCity] = useState('');
|
||||
const [state, setState] = useState('');
|
||||
const [zipcode, setZipcode] = useState('');
|
||||
const [contact, setContact] = useState('');
|
||||
const [phone, setPhone] = useState('');
|
||||
const [dataObject, setDataObject] = useState(undefined);
|
||||
|
||||
// Additional Information
|
||||
const [note, setNote] = useState('');
|
||||
const [fax, setFax] = useState('');
|
||||
const [status, setStatus] = useState('');
|
||||
const [branchName, setBranchName] = useState('');
|
||||
const [email, setEmail] = useState('');
|
||||
|
||||
// Modal
|
||||
const [showDeleteModal, setShowDeleteModal] = useState(false);
|
||||
|
||||
const params = new URLSearchParams(window.location.search);
|
||||
|
||||
const redirectTo = (id) => {
|
||||
const redirectTo = () => {
|
||||
navigate(`/medical/resources/list`);
|
||||
}
|
||||
|
||||
@ -39,38 +43,88 @@ const UpdateResource = () => {
|
||||
navigate(`/medical/resources/${urlParams.id}`);
|
||||
}
|
||||
|
||||
const saveResource = () => {
|
||||
const newResource = {
|
||||
...currentResource,
|
||||
data: JSON.parse(dataObject),
|
||||
name,
|
||||
description,
|
||||
note,
|
||||
name_original: originalName,
|
||||
name_branch: branchName,
|
||||
specialty,
|
||||
type,
|
||||
category,
|
||||
color,
|
||||
address,
|
||||
state,
|
||||
city,
|
||||
zipcode,
|
||||
contact,
|
||||
phone,
|
||||
email,
|
||||
fax,
|
||||
status,
|
||||
edit_by: localStorage.getItem('user') && JSON.parse(localStorage.getItem('user'))?.name,
|
||||
edit_date: new Date(),
|
||||
edit_history: currentResource?.edit_history? [...currentResource.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() }]
|
||||
const validateResource = () => {
|
||||
const errors = [];
|
||||
|
||||
// Required fields validation
|
||||
if (!name || name.trim() === '') {
|
||||
errors.push('Provider');
|
||||
}
|
||||
if (!phone || phone.trim() === '') {
|
||||
errors.push('Office Phone Number');
|
||||
}
|
||||
|
||||
console.log('new Resource', newResource);
|
||||
|
||||
ResourceService.updateResource(urlParams.id, newResource).then(data => redirectToView());
|
||||
if (errors.length > 0) {
|
||||
window.alert(`Please fill in the following required fields:\n${errors.join('\n')}`);
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
};
|
||||
|
||||
const saveResource = () => {
|
||||
if (!validateResource()) {
|
||||
return;
|
||||
}
|
||||
|
||||
const combinedAddress = `${addressLine1}${addressLine2 ? ', ' + addressLine2 : ''}, ${city}, ${state} ${zipcode}`.trim();
|
||||
|
||||
const updatedResource = {
|
||||
...currentResource,
|
||||
data: currentResource?.data || {},
|
||||
|
||||
// Basic Information
|
||||
name, // Provider
|
||||
office_name: officeName,
|
||||
name_original: officeName, // Legacy field
|
||||
specialty,
|
||||
type,
|
||||
|
||||
// Contact Information
|
||||
phone, // Office Phone Number
|
||||
contact, // Secondary Phone Number
|
||||
fax, // Fax Number
|
||||
email,
|
||||
|
||||
// Address (split fields)
|
||||
address_line_1: addressLine1,
|
||||
address_line_2: addressLine2,
|
||||
city,
|
||||
state,
|
||||
zipcode,
|
||||
address: combinedAddress, // Legacy field
|
||||
|
||||
// Additional Information
|
||||
note,
|
||||
|
||||
// System fields
|
||||
edit_by: localStorage.getItem('user') && JSON.parse(localStorage.getItem('user'))?.name,
|
||||
edit_date: new Date(),
|
||||
edit_history: currentResource?.edit_history
|
||||
? [...currentResource.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() }]
|
||||
};
|
||||
|
||||
ResourceService.updateResource(urlParams.id, updatedResource).then(() => redirectToView());
|
||||
};
|
||||
|
||||
const triggerShowDeleteModal = () => {
|
||||
setShowDeleteModal(true);
|
||||
}
|
||||
|
||||
const closeDeleteModal = () => {
|
||||
setShowDeleteModal(false);
|
||||
}
|
||||
|
||||
const deactivateResource = () => {
|
||||
ResourceService.disableResource(urlParams.id, {
|
||||
status: 'inactive',
|
||||
edit_by: localStorage.getItem('user') && JSON.parse(localStorage.getItem('user'))?.name,
|
||||
edit_date: new Date()
|
||||
}).then(() => {
|
||||
setShowDeleteModal(false);
|
||||
redirectTo();
|
||||
});
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
if (!AuthService.canAccessLegacySystem()) {
|
||||
@ -80,39 +134,41 @@ const UpdateResource = () => {
|
||||
}
|
||||
ResourceService.getResource(urlParams.id).then(resourceData => {
|
||||
setCurrentResource(resourceData.data);
|
||||
})
|
||||
});
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
if (currentResource) {
|
||||
setName(currentResource?.name);
|
||||
setOriginalName(currentResource?.name_original);
|
||||
setBranchName(currentResource?.name_branch);
|
||||
setSpecialty(currentResource?.specialty);
|
||||
setType(currentResource?.type);
|
||||
setCategory(currentResource?.category);
|
||||
setDescription(currentResource?.description);
|
||||
setColor(currentResource?.color);
|
||||
setAddress(currentResource?.address);
|
||||
setState(currentResource?.state);
|
||||
setCity(currentResource?.city);
|
||||
setZipcode(currentResource?.zipcode);
|
||||
setContact(currentResource?.contact);
|
||||
setPhone(currentResource?.phone);
|
||||
setFax(currentResource?.fax);
|
||||
setNote(currentResource?.note);
|
||||
setStatus(currentResource?.status);
|
||||
setDataObject(JSON.stringify(currentResource?.data || {}));
|
||||
}
|
||||
// Basic Information
|
||||
setName(currentResource?.name || '');
|
||||
setOfficeName(currentResource?.office_name || currentResource?.name_original || '');
|
||||
setSpecialty(currentResource?.specialty || '');
|
||||
setType(currentResource?.type || '');
|
||||
|
||||
// Contact Information
|
||||
setPhone(currentResource?.phone || '');
|
||||
setContact(currentResource?.contact || '');
|
||||
setFax(currentResource?.fax || '');
|
||||
setEmail(currentResource?.email || '');
|
||||
|
||||
// Address - try new fields first, then fall back to legacy
|
||||
setAddressLine1(currentResource?.address_line_1 || currentResource?.address || '');
|
||||
setAddressLine2(currentResource?.address_line_2 || '');
|
||||
setCity(currentResource?.city || '');
|
||||
setState(currentResource?.state || '');
|
||||
setZipcode(currentResource?.zipcode || '');
|
||||
|
||||
// Additional Information - merge description into note if note is empty
|
||||
setNote(currentResource?.note || currentResource?.description || '');
|
||||
}
|
||||
}, [currentResource]);
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className="list row mb-4">
|
||||
<Breadcrumb>
|
||||
<Breadcrumb.Item>Medical</Breadcrumb.Item>
|
||||
<Breadcrumb.Item active>
|
||||
<Breadcrumb.Item href="/medical/index">Medical</Breadcrumb.Item>
|
||||
<Breadcrumb.Item href="/medical/resources/list">
|
||||
Provider Information
|
||||
</Breadcrumb.Item>
|
||||
<Breadcrumb.Item active>
|
||||
@ -125,142 +181,128 @@ const UpdateResource = () => {
|
||||
</div>
|
||||
<div className="app-main-content-list-container">
|
||||
<div className="app-main-content-list-func-container">
|
||||
<Tabs defaultActiveKey="providerInfo" id="providerss-tab">
|
||||
<Tabs defaultActiveKey="providerInfo" id="providers-tab">
|
||||
<Tab eventKey="providerInfo" title="Provider Information">
|
||||
<h6 className="text-primary">Basic Information</h6>
|
||||
<div className="app-main-content-fields-section">
|
||||
<div className="me-4">
|
||||
<div className="field-label">Doctor Name
|
||||
<span className="required">*</span>
|
||||
</div>
|
||||
<input placeholder="e.g.,Cao Qing" type="text" value={name || ''} onChange={e => setName(e.target.value)}/>
|
||||
<div className="field-label">Provider <span className="required">*</span></div>
|
||||
<input placeholder="e.g., Dr. Cao Qing" type="text" value={name} onChange={e => setName(e.target.value)}/>
|
||||
</div>
|
||||
<div className="me-4">
|
||||
<div className="field-label">Office Name
|
||||
</div>
|
||||
<input type="text" placeholder="e.g.,Silver Spring Family Medicine Clinic" value={originalName || ''} onChange={e => setOriginalName(e.target.value)}/>
|
||||
<div className="field-label">Office Name</div>
|
||||
<input type="text" placeholder="e.g., Silver Spring Family Medicine Clinic" value={officeName} onChange={e => setOfficeName(e.target.value)}/>
|
||||
</div>
|
||||
<div className="me-4">
|
||||
<div className="field-label">Branch Name
|
||||
</div>
|
||||
<input type="text" placeholder="e.g.,Silver Spring" value={branchName || ''} onChange={e => setBranchName(e.target.value)}/>
|
||||
</div>
|
||||
<div className="me-4">
|
||||
<div className="field-label">Specialty
|
||||
</div>
|
||||
<select value={specialty} onChange={e => setSpecialty(e.target.value)}>
|
||||
<option value=""></option>
|
||||
{
|
||||
ResourceService.resourceOptionList.map((item) => <option value={item}>{item}</option>)
|
||||
}
|
||||
</select>
|
||||
</div>
|
||||
<div className="me-4">
|
||||
<div className="field-label">Type
|
||||
</div>
|
||||
<div className="field-label">Type</div>
|
||||
<select value={type} onChange={e => setType(e.target.value)}>
|
||||
<option value="doctor">Doctor</option>
|
||||
<option value="pharmacy">Pharmacy</option>
|
||||
<option value="hospital">Hospital</option>
|
||||
<option value="surgical center">Surgical Center</option>
|
||||
<option value="government agency">Government Agency</option>
|
||||
<option value="other">Other</option>
|
||||
<option value="">Select...</option>
|
||||
{RESOURCE_TYPE_OPTIONS.map((item, index) => (
|
||||
<option key={index} value={item.value}>{item.label}</option>
|
||||
))}
|
||||
</select>
|
||||
</div>
|
||||
<div className="me-4">
|
||||
<div className="field-label">Specialty</div>
|
||||
<select value={specialty} onChange={e => setSpecialty(e.target.value)}>
|
||||
<option value="">Select...</option>
|
||||
{RESOURCE_SPECIALTY_OPTIONS.map((item, index) => (
|
||||
<option key={index} value={item.value}>{item.label}</option>
|
||||
))}
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<h6 className="text-primary">Contact Information</h6>
|
||||
<div className="app-main-content-fields-section">
|
||||
<div className="me-4">
|
||||
<div className="field-label">Office Phone Number
|
||||
<span className="required">*</span>
|
||||
</div>
|
||||
<input placeholder="e.g.,240-463-1098" type="text" value={phone || ''} onChange={e => setPhone(e.target.value)}/>
|
||||
<div className="field-label">Office Phone Number <span className="required">*</span></div>
|
||||
<input placeholder="e.g., 240-463-1098" type="text" value={phone} onChange={e => setPhone(e.target.value)}/>
|
||||
</div>
|
||||
<div className="me-4">
|
||||
<div className="field-label">Contact
|
||||
</div>
|
||||
{/* <textarea value={contact || ''} onChange={e => setContact(e.target.value)}/> */}
|
||||
<input type="text" placeholder="e.g.,240-463-1698" value={contact || ''} onChange={e => setContact(e.target.value)}/>
|
||||
<div className="field-label">Secondary Phone Number</div>
|
||||
<input type="text" placeholder="e.g., 240-463-1698" value={contact} onChange={e => setContact(e.target.value)}/>
|
||||
</div>
|
||||
<div className="me-4">
|
||||
<div className="field-label">Fax Number
|
||||
</div>
|
||||
<input type="text" placement="e.g.,240-463-1698" value={fax || ''} onChange={e => setFax(e.target.value)}/>
|
||||
<div className="field-label">Fax Number</div>
|
||||
<input type="text" placeholder="e.g., 240-463-1698" value={fax} onChange={e => setFax(e.target.value)}/>
|
||||
</div>
|
||||
<div className="me-4">
|
||||
<div className="field-label">Email
|
||||
</div>
|
||||
<input type="email" placement="e.g.,example@gmail.com" value={email || ''} onChange={e => setEmail(e.target.value)}/>
|
||||
<div className="field-label">Email</div>
|
||||
<input type="email" placeholder="e.g., example@gmail.com" value={email} onChange={e => setEmail(e.target.value)}/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<h6 className="text-primary">Provider Address</h6>
|
||||
<div className="app-main-content-fields-section">
|
||||
<div className="me-4">
|
||||
<div className="field-label">Address Line 1
|
||||
<span className="required">*</span>
|
||||
</div>
|
||||
<input type="text" placement="e.g.,555 Cloverly Forest Dr" value={address || ''} onChange={e => setAddress(e.target.value)}/>
|
||||
<div className="field-label">Address Line 1 <span className="required">*</span></div>
|
||||
<input type="text" placeholder="e.g., 555 Cloverly Forest Dr" value={addressLine1} onChange={e => setAddressLine1(e.target.value)}/>
|
||||
</div>
|
||||
<div className="me-4">
|
||||
<div className="field-label">City
|
||||
<span className="required">*</span>
|
||||
<div className="field-label">Address Line 2</div>
|
||||
<input type="text" placeholder="e.g., Suite 200" value={addressLine2} onChange={e => setAddressLine2(e.target.value)}/>
|
||||
</div>
|
||||
<input type="text" placement="e.g.,Rockville" value={city || ''} onChange={e => setCity(e.target.value)}/>
|
||||
</div>
|
||||
<div className="app-main-content-fields-section">
|
||||
<div className="me-4">
|
||||
<div className="field-label">City <span className="required">*</span></div>
|
||||
<input type="text" placeholder="e.g., Rockville" value={city} onChange={e => setCity(e.target.value)}/>
|
||||
</div>
|
||||
<div className="me-4">
|
||||
<div className="field-label">State
|
||||
<span className="required">*</span>
|
||||
</div>
|
||||
<input placement="e.g.,MD" type="text" value={state || ''} onChange={e => setState(e.target.value)}/>
|
||||
<div className="field-label">State <span className="required">*</span></div>
|
||||
<input type="text" placeholder="e.g., MD" value={state} onChange={e => setState(e.target.value)}/>
|
||||
</div>
|
||||
<div className="me-4">
|
||||
<div className="field-label">Zip Code
|
||||
<span className="required">*</span>
|
||||
</div>
|
||||
<input type="text" value={zipcode || ''} onChange={e => setZipcode(e.target.value)}/>
|
||||
<div className="field-label">Zip Code <span className="required">*</span></div>
|
||||
<input type="text" placeholder="e.g., 20850" value={zipcode} onChange={e => setZipcode(e.target.value)}/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<h6 className="text-primary">Additional Information</h6>
|
||||
<div className="app-main-content-fields-section">
|
||||
<div className="me-4">
|
||||
<div className="field-label">Note
|
||||
</div>
|
||||
<textarea value={note || ''} onChange={e => setNote(e.target.value)}/>
|
||||
</div>
|
||||
</div>
|
||||
<div className="app-main-content-fields-section">
|
||||
<div className="me-4">
|
||||
<div className="field-label">Description
|
||||
</div>
|
||||
<input type="text" placeholder={'e.g.,Description'} value={description || ''} onChange={e => setDescription(e.target.value)}/>
|
||||
</div>
|
||||
<div className="me-4">
|
||||
<div className="field-label">Color
|
||||
</div>
|
||||
<input type="text" placeholder={'e.g.,red'} value={color || ''} onChange={e => setColor(e.target.value)}/>
|
||||
<div className="field-label">Note</div>
|
||||
<textarea
|
||||
placeholder="e.g., Preferred provider for cardiology referrals"
|
||||
value={note}
|
||||
onChange={e => setNote(e.target.value)}
|
||||
rows={4}
|
||||
style={{width: '400px'}}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="list row mb-5">
|
||||
<div className="col-md-12 col-sm-12 col-xs-12">
|
||||
<button className="btn btn-default btn-sm float-right" onClick={() => redirectTo()}> Cancel </button>
|
||||
<button className="btn btn-danger btn-sm me-2 mb-2" onClick={() => triggerShowDeleteModal()}> Delete </button>
|
||||
<button className="btn btn-primary btn-sm float-right" onClick={() => saveResource()}> Save </button>
|
||||
</div>
|
||||
</div>
|
||||
</Tab>
|
||||
</Tabs>
|
||||
<div className="list-func-panel">
|
||||
<button className="btn btn-primary" onClick={() => { ResourceService.disableResource(urlParams?.id, {status: 'inactive'})}}><Archive size={16} className="me-2"></Archive>Archive</button>
|
||||
<button className="btn btn-primary" onClick={() => deactivateResource()}><Archive size={16} className="me-2"></Archive>Archive</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* <div className="col-md-4 mb-4">
|
||||
<div>Status:</div>
|
||||
<select value={status} onChange={e => setStatus(e.target.value)}>
|
||||
<option value="active">Active</option>
|
||||
<option value="inactive">Inactive</option>
|
||||
</select>
|
||||
</div> */}
|
||||
<Modal show={showDeleteModal} onHide={() => closeDeleteModal()}>
|
||||
<Modal.Header closeButton>
|
||||
<Modal.Title>Delete Provider</Modal.Title>
|
||||
</Modal.Header>
|
||||
<Modal.Body>
|
||||
<div>Are you sure you want to delete this provider?</div>
|
||||
</Modal.Body>
|
||||
<Modal.Footer>
|
||||
<Button variant="secondary" onClick={() => closeDeleteModal()}>
|
||||
No
|
||||
</Button>
|
||||
<Button variant="primary" onClick={() => deactivateResource()}>
|
||||
Yes
|
||||
</Button>
|
||||
</Modal.Footer>
|
||||
</Modal>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
@ -1,33 +1,51 @@
|
||||
import React, {useState, useEffect} from "react";
|
||||
// import { useDispatch } from "react-redux";
|
||||
import { useNavigate, useParams } from "react-router-dom";
|
||||
// import { customerSlice } from "./../../store";
|
||||
import { AuthService, ResourceService } from "../../services";
|
||||
import { Spinner, Breadcrumb, BreadcrumbItem, Tabs, Tab } from "react-bootstrap";
|
||||
import { Download, Pencil, Archive } from "react-bootstrap-icons";
|
||||
import { RESOURCE_TYPE_TEXT } from "../../shared/constants";
|
||||
|
||||
const ViewResource = () => {
|
||||
const navigate = useNavigate();
|
||||
const urlParams = useParams();
|
||||
const [currentResource, setCurrentResource] = useState(undefined);
|
||||
|
||||
const params = new URLSearchParams(window.location.search);
|
||||
|
||||
const redirectTo = () => {
|
||||
navigate(`/medical/resources/list`);
|
||||
}
|
||||
|
||||
const goToEdit = (id) => {
|
||||
navigate(`/medical/resources/edit/${id}`)
|
||||
navigate(`/medical/resources/edit/${id}`);
|
||||
}
|
||||
|
||||
const deactivateResource = (id) => {
|
||||
const data = {
|
||||
status: 'inactive'
|
||||
status: 'inactive',
|
||||
edit_by: localStorage.getItem('user') && JSON.parse(localStorage.getItem('user'))?.name,
|
||||
edit_date: new Date()
|
||||
};
|
||||
ResourceService.disableResource(id, data).then(() => {
|
||||
redirectTo();
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
// Helper function to format address
|
||||
const formatAddress = () => {
|
||||
const line1 = currentResource?.address_line_1 || currentResource?.address || '';
|
||||
const line2 = currentResource?.address_line_2 || '';
|
||||
const city = currentResource?.city || '';
|
||||
const state = currentResource?.state || '';
|
||||
const zipcode = currentResource?.zipcode || '';
|
||||
|
||||
if (!line1 && !city && !state && !zipcode) return '-';
|
||||
|
||||
let address = line1;
|
||||
if (line2) address += `, ${line2}`;
|
||||
if (city) address += `, ${city}`;
|
||||
if (state) address += `, ${state}`;
|
||||
if (zipcode) address += ` ${zipcode}`;
|
||||
|
||||
return address || '-';
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
@ -38,15 +56,15 @@ const ViewResource = () => {
|
||||
}
|
||||
ResourceService.getResource(urlParams.id).then(resourceData => {
|
||||
setCurrentResource(resourceData.data);
|
||||
})
|
||||
});
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className="list row mb-4">
|
||||
<Breadcrumb>
|
||||
<Breadcrumb.Item>Medical</Breadcrumb.Item>
|
||||
<Breadcrumb.Item active>
|
||||
<Breadcrumb.Item href="/medical/index">Medical</Breadcrumb.Item>
|
||||
<Breadcrumb.Item href="/medical/resources/list">
|
||||
Provider Information
|
||||
</Breadcrumb.Item>
|
||||
<Breadcrumb.Item active>
|
||||
@ -59,78 +77,84 @@ const ViewResource = () => {
|
||||
</div>
|
||||
<div className="app-main-content-list-container">
|
||||
<div className="app-main-content-list-func-container">
|
||||
<Tabs defaultActiveKey="providerInfo" id="providerss-tab">
|
||||
<Tabs defaultActiveKey="providerInfo" id="providers-tab">
|
||||
<Tab eventKey="providerInfo" title="Provider Information">
|
||||
<h6 className="text-primary">Basic Information</h6>
|
||||
<div className="app-main-content-fields-section">
|
||||
<div className="field-body">
|
||||
<div className="field-label">Name</div>
|
||||
<div className="field-value">{currentResource?.name}</div>
|
||||
<div className="field-label">Provider</div>
|
||||
<div className="field-value">{currentResource?.name || '-'}</div>
|
||||
</div>
|
||||
<div className="field-body">
|
||||
<div className="field-label">Office Name</div>
|
||||
<div className="field-value">{currentResource?.name_original}</div>
|
||||
</div>
|
||||
<div className="field-body">
|
||||
<div className="field-label">Branch Name</div>
|
||||
<div className="field-value">{currentResource?.name_branch}</div>
|
||||
<div className="field-value">{currentResource?.office_name || currentResource?.name_original || '-'}</div>
|
||||
</div>
|
||||
<div className="field-body">
|
||||
<div className="field-label">Specialty</div>
|
||||
<div className="field-value">{currentResource?.specialty}</div>
|
||||
<div className="field-value">{currentResource?.specialty || '-'}</div>
|
||||
</div>
|
||||
<div className="field-body">
|
||||
<div className="field-label">Type</div>
|
||||
<div className="field-value">{currentResource?.type}</div>
|
||||
<div className="field-value">{RESOURCE_TYPE_TEXT[currentResource?.type] || currentResource?.type || '-'}</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<h6 className="text-primary">Contact Information</h6>
|
||||
<div className="app-main-content-fields-section">
|
||||
<div className="field-body">
|
||||
<div className="field-label">Office Phone Number</div>
|
||||
<div className="field-value">{currentResource?.phone}</div>
|
||||
<div className="field-value">{currentResource?.phone || '-'}</div>
|
||||
</div>
|
||||
<div className="field-body">
|
||||
<div className="field-label">Contact</div>
|
||||
<div className="field-value">{currentResource?.contact}</div>
|
||||
<div className="field-label">Secondary Phone Number</div>
|
||||
<div className="field-value">{currentResource?.contact || '-'}</div>
|
||||
</div>
|
||||
<div className="field-body">
|
||||
<div className="field-label">Fax Number</div>
|
||||
<div className="field-value">{currentResource?.fax}</div>
|
||||
<div className="field-value">{currentResource?.fax || '-'}</div>
|
||||
</div>
|
||||
<div className="field-body">
|
||||
<div className="field-label">Email</div>
|
||||
<div className="field-value">{currentResource?.email || '-'}</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<h6 className="text-primary">Provider Address</h6>
|
||||
<div className="app-main-content-fields-section">
|
||||
<div className="field-body">
|
||||
<div className="field-label">Address</div>
|
||||
<div className="field-value">{`${currentResource?.address}, ${currentResource?.city}, ${currentResource?.state}, ${currentResource?.zipcode}`}</div>
|
||||
<div className="field-label">Address Line 1</div>
|
||||
<div className="field-value">{currentResource?.address_line_1 || currentResource?.address || '-'}</div>
|
||||
</div>
|
||||
<div className="field-body">
|
||||
<div className="field-label">Address Line 2</div>
|
||||
<div className="field-value">{currentResource?.address_line_2 || '-'}</div>
|
||||
</div>
|
||||
<div className="field-body">
|
||||
<div className="field-label">City</div>
|
||||
<div className="field-value">{currentResource?.city || '-'}</div>
|
||||
</div>
|
||||
<div className="field-body">
|
||||
<div className="field-label">State</div>
|
||||
<div className="field-value">{currentResource?.state || '-'}</div>
|
||||
</div>
|
||||
<div className="field-body">
|
||||
<div className="field-label">Zip Code</div>
|
||||
<div className="field-value">{currentResource?.zipcode || '-'}</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<h6 className="text-primary">Additional Information</h6>
|
||||
<div className="app-main-content-fields-section">
|
||||
<div className="field-body">
|
||||
<div className="field-label">Note</div>
|
||||
<div className="field-value">{currentResource?.note}</div>
|
||||
</div>
|
||||
<div className="field-body">
|
||||
<div className="field-label">Description</div>
|
||||
<div className="field-value">{currentResource?.description}</div>
|
||||
</div>
|
||||
<div className="field-body">
|
||||
<div className="field-label">Color</div>
|
||||
<div className="field-value">{currentResource?.color}</div>
|
||||
</div>
|
||||
<div className="field-body">
|
||||
<div className="field-label">Status</div>
|
||||
<div className="field-value">{currentResource?.status}</div>
|
||||
<div className="field-value">{currentResource?.note || currentResource?.description || '-'}</div>
|
||||
</div>
|
||||
</div>
|
||||
</Tab>
|
||||
</Tabs>
|
||||
<div className="list-func-panel">
|
||||
<button className="btn btn-primary me-2" onClick={() => goToEdit(currentResource?.id)}><Pencil size={16} className="me-2"></Pencil>Edit</button>
|
||||
<button className="btn btn-primary me-2" onClick={() => deactivateResource()}><Archive size={16} className="me-2"></Archive>Archive</button>
|
||||
{/* <button className="btn btn-primary"><Download size={16} className="me-2"></Download>Download</button> */}
|
||||
<button className="btn btn-primary me-2" onClick={() => deactivateResource(currentResource?.id)}><Archive size={16} className="me-2"></Archive>Archive</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
import React from 'react';
|
||||
|
||||
const CircularTable = ({tableNumber, guests = []}) => {
|
||||
const CircularTable = ({tableNumber, guests = [], inCenterCustomerIds = [], onSeatClick}) => {
|
||||
const getPositions = () => {
|
||||
const positions = [];
|
||||
const seatCount = 8;
|
||||
@ -18,6 +18,12 @@ const CircularTable = ({tableNumber, guests = []}) => {
|
||||
return positions;
|
||||
}
|
||||
|
||||
// Check if a customer is in-center (checked in)
|
||||
const isCustomerInCenter = (customerId) => {
|
||||
if (!customerId) return true; // Empty seats are not grayed out
|
||||
return inCenterCustomerIds.includes(customerId);
|
||||
}
|
||||
|
||||
const seatPositions = getPositions();
|
||||
// const defaultNames = ['Guest 1', 'Guest 2', 'Guest 3', 'Guest 4', 'Guest 5', 'Guest 6', 'Guest 7', 'Guest 8' ];
|
||||
const defaultSeatNumber = ['A', 'B', 'C', 'D', 'E', 'F', 'G', 'H'];
|
||||
@ -32,9 +38,30 @@ const CircularTable = ({tableNumber, guests = []}) => {
|
||||
<div className="table-number">{tableNumber}</div>
|
||||
|
||||
{seatPositions.map((pos, index) => {
|
||||
const guest = guests[index];
|
||||
const isInCenter = isCustomerInCenter(guest?.customerId);
|
||||
const hasCustomer = guest?.customerId && guest?.customerName;
|
||||
|
||||
// Determine seat style:
|
||||
// - Apply label color if it exists
|
||||
// - Gray out if customer is assigned but not in-center (and no label color)
|
||||
const seatStyle = {
|
||||
...(guest?.label?.label_color ? { background: guest.label.label_color } : {}),
|
||||
...(!isInCenter && hasCustomer && !guest?.label?.label_color ? { background: '#cccccc', opacity: 0.6 } : {}),
|
||||
...(!isInCenter && hasCustomer ? { opacity: 0.6 } : {})
|
||||
};
|
||||
|
||||
return (<div className="seat-container" key={index} style={{transform: `translate(${pos.x}px, ${pos.y}px)`}}>
|
||||
<div className="seat-circle" style={guests[index]?.label?.label_color && { background: guests[index]?.label?.label_color}}>{defaultSeatNumber[index]}</div>
|
||||
<div className="guest-name">{guests[index]?.customerName}</div>
|
||||
<div
|
||||
className={`seat-circle ${hasCustomer ? 'has-customer' : ''}`}
|
||||
style={seatStyle}
|
||||
onClick={() => hasCustomer && onSeatClick && onSeatClick(guest)}
|
||||
>
|
||||
{defaultSeatNumber[index]}
|
||||
</div>
|
||||
<div className="guest-name" style={!isInCenter && hasCustomer ? { color: '#999', fontSize: '8px' } : {}}>
|
||||
{guest?.customerName || ''}
|
||||
</div>
|
||||
</div>)
|
||||
})}
|
||||
</div>
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@ -29,8 +29,10 @@ const BreakfastSection = ({transRoutes, breakfastRecords, sectionName, confimHas
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
const routeCustomers = TransRoutesService.getAllInCenterCustomersFromRoutes(transRoutes, breakfastRecords);
|
||||
if (transRoutes && transRoutes.length > 0) {
|
||||
const routeCustomers = TransRoutesService.getAllInCenterCustomersFromRoutes(transRoutes, breakfastRecords || []);
|
||||
setCustomers(routeCustomers);
|
||||
}
|
||||
}, [breakfastRecords, transRoutes]);
|
||||
|
||||
|
||||
@ -53,8 +55,8 @@ const BreakfastSection = ({transRoutes, breakfastRecords, sectionName, confimHas
|
||||
</thead>
|
||||
<tbody>
|
||||
{
|
||||
breakfastRecords?.length >0 && customers?.map((customer) => (
|
||||
<tr className={customer?.has_breakfast ? 'light-green' : 'red'}>
|
||||
customers?.map((customer) => (
|
||||
<tr key={customer?.customer_id} className={customer?.has_breakfast ? 'light-green' : 'red'}>
|
||||
<td>{customer?.customer_name}</td>
|
||||
<td>{customer?.has_breakfast ? 'Yes': 'No'}</td>
|
||||
<td>
|
||||
|
||||
@ -79,13 +79,36 @@ const CreateRoute = () => {
|
||||
navigate(`/trans-routes/templates${params.get('type')?`?type=${params.get('type')}`: ''}${params.get('date')? `&date=${params.get('date')}` : ''}`);
|
||||
}
|
||||
|
||||
const validateRoute = () => {
|
||||
const errors = [];
|
||||
|
||||
// Required fields validation
|
||||
if (!routeName || routeName.trim() === '') {
|
||||
errors.push('Route Name');
|
||||
}
|
||||
if (!newRouteType || newRouteType === '') {
|
||||
errors.push('Route Type');
|
||||
}
|
||||
if (!newDriver || newDriver === '') {
|
||||
errors.push('Driver');
|
||||
}
|
||||
if (!newVehicle || newVehicle === '') {
|
||||
errors.push('Vehicle');
|
||||
}
|
||||
|
||||
if (errors.length > 0) {
|
||||
window.alert(`Please fill in the following required fields:\n${errors.join('\n')}`);
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
};
|
||||
|
||||
const saveRoute = () => {
|
||||
if (!disableSave) {
|
||||
if (!validateRoute()) {
|
||||
return;
|
||||
}
|
||||
setDisableSave(true);
|
||||
if (!routeName || routeName === '') { setErrorMessage('Route Name is Required'); return;}
|
||||
if (!newRouteType || newRouteType === '') {setErrorMessage('Route Type is Required'); return;}
|
||||
if (!newDriver || newDriver === '') {setErrorMessage('Driver is Required'); return;}
|
||||
if (!newVehicle || newVehicle === '') {setErrorMessage('Vehicle is Required'); return;}
|
||||
const now = new Date();
|
||||
let scheduleDate = ((now.getMonth() > 8) ? (now.getMonth() + 1) : ('0' + (now.getMonth() + 1))) + '/' + ((now.getDate() > 9) ? now.getDate() : ('0' + now.getDate())) + '/' + now.getFullYear();
|
||||
if (params.get('date') === 'tomorrow') {
|
||||
@ -175,10 +198,13 @@ const CreateRoute = () => {
|
||||
<>
|
||||
<div className="list row mb-4">
|
||||
<Breadcrumb>
|
||||
<Breadcrumb.Item>Transportation</Breadcrumb.Item>
|
||||
<Breadcrumb.Item active>
|
||||
<Breadcrumb.Item href="/">Transportation</Breadcrumb.Item>
|
||||
<Breadcrumb.Item href="/trans-routes/dashboard">
|
||||
Transportation Routes
|
||||
</Breadcrumb.Item>
|
||||
<Breadcrumb.Item active>
|
||||
Create New Route
|
||||
</Breadcrumb.Item>
|
||||
</Breadcrumb>
|
||||
<div className="col-md-12 text-primary">
|
||||
<h4>
|
||||
@ -187,7 +213,7 @@ const CreateRoute = () => {
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="app-main-content-list-container">
|
||||
<div className="app-main-content-list-container form-page">
|
||||
<div className="app-main-content-list-func-container">
|
||||
<Tabs defaultActiveKey="routeOverview" id="route-view-tab">
|
||||
<Tab eventKey="routeOverview" title="Route Information">
|
||||
|
||||
@ -29,8 +29,10 @@ const LunchSection = ({transRoutes, lunchRecords, sectionName, confirmHasLunch,
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
const routeCustomers = TransRoutesService.getAllInCenterCustomersFromRoutesForLunch(transRoutes, lunchRecords);
|
||||
if (transRoutes && transRoutes.length > 0) {
|
||||
const routeCustomers = TransRoutesService.getAllInCenterCustomersFromRoutesForLunch(transRoutes, lunchRecords || []);
|
||||
setCustomers(routeCustomers);
|
||||
}
|
||||
}, [lunchRecords, transRoutes]);
|
||||
|
||||
|
||||
@ -53,8 +55,8 @@ const LunchSection = ({transRoutes, lunchRecords, sectionName, confirmHasLunch,
|
||||
</thead>
|
||||
<tbody>
|
||||
{
|
||||
lunchRecords?.length >0 && customers?.map((customer) => (
|
||||
<tr className={customer?.has_lunch ? 'light-green' : 'red'}>
|
||||
customers?.map((customer) => (
|
||||
<tr key={customer?.customer_id} className={customer?.has_lunch ? 'light-green' : 'red'}>
|
||||
<td>{customer?.customer_name}</td>
|
||||
<td>{customer?.has_lunch ? 'Yes': 'No'}</td>
|
||||
<td>
|
||||
|
||||
@ -494,28 +494,96 @@ const PersonnelInfoTable = ({transRoutes, showCompletedInfo,
|
||||
|
||||
const saveBulkUpdate = () => {
|
||||
const routeId = transRoutes[0]?.id;
|
||||
|
||||
// Debug logging
|
||||
console.log('=== saveBulkUpdate Debug ===');
|
||||
console.log('bulkEnterCenterTime:', bulkEnterCenterTime);
|
||||
console.log('bulkEnterCenterTime type:', typeof bulkEnterCenterTime);
|
||||
console.log('bulkEnterCenterTime value:', JSON.stringify(bulkEnterCenterTime));
|
||||
console.log('bulkLeaveCenterTime:', bulkLeaveCenterTime);
|
||||
console.log('bulkLeaveCenterTime type:', typeof bulkLeaveCenterTime);
|
||||
console.log('bulkLeaveCenterTime value:', JSON.stringify(bulkLeaveCenterTime));
|
||||
|
||||
if (routeId) {
|
||||
let requestBody = transRoutes.find((route) => route.id === routeId);
|
||||
const dateStr = requestBody?.schedule_date || '';
|
||||
|
||||
console.log('dateStr:', dateStr);
|
||||
console.log('Number of customers to update:', requestBody.route_customer_list.length);
|
||||
console.log('Original customer list IDs:', requestBody.route_customer_list.map(c => ({ id: c.customer_id, name: c.customer_name, pickup_status: c.customer_pickup_status })));
|
||||
|
||||
const updatedCustomerList = requestBody.route_customer_list.map((item) => {
|
||||
// Skip customers who are Unscheduled Absent or Scheduled Absent
|
||||
if (item.customer_pickup_status === PICKUP_STATUS.UNSCHEDULE_ABSENT ||
|
||||
item.customer_pickup_status === PICKUP_STATUS.SCHEDULE_ABSENT) {
|
||||
// Only skip if status is explicitly set to one of these values (not undefined)
|
||||
if (item.customer_pickup_status &&
|
||||
(item.customer_pickup_status === PICKUP_STATUS.UNSCHEDULE_ABSENT ||
|
||||
item.customer_pickup_status === PICKUP_STATUS.SCHEDULE_ABSENT)) {
|
||||
console.log(`Skipping customer ${item.customer_id} (${item.customer_name}) - status: ${item.customer_pickup_status}`);
|
||||
return item;
|
||||
}
|
||||
|
||||
let updatedItem = { ...item };
|
||||
|
||||
if (bulkEnterCenterTime && bulkEnterCenterTime !== '') {
|
||||
updatedItem.customer_enter_center_time = combineDateAndTime(dateStr, bulkEnterCenterTime);
|
||||
updatedItem.customer_route_status = PERSONAL_ROUTE_STATUS.IN_CENTER;
|
||||
// Debug logging for each customer
|
||||
console.log(`Processing customer ${item.customer_id} (${item.customer_name})`);
|
||||
|
||||
// Check if bulkEnterCenterTime is a valid time string (HH:mm format)
|
||||
console.log('Checking bulkEnterCenterTime:', {
|
||||
value: bulkEnterCenterTime,
|
||||
type: typeof bulkEnterCenterTime,
|
||||
isTruthy: !!bulkEnterCenterTime,
|
||||
isString: typeof bulkEnterCenterTime === 'string',
|
||||
trimmed: typeof bulkEnterCenterTime === 'string' ? bulkEnterCenterTime.trim() : 'N/A',
|
||||
trimmedLength: typeof bulkEnterCenterTime === 'string' ? bulkEnterCenterTime.trim().length : 0
|
||||
});
|
||||
|
||||
if (bulkEnterCenterTime && typeof bulkEnterCenterTime === 'string' && bulkEnterCenterTime.trim() !== '') {
|
||||
try {
|
||||
const combinedTime = combineDateAndTime(dateStr, bulkEnterCenterTime);
|
||||
const dateValue = combinedTime.toDate();
|
||||
console.log('Combined enter center time (moment):', combinedTime);
|
||||
console.log('Combined enter center time (Date):', dateValue);
|
||||
updatedItem.customer_enter_center_time = dateValue;
|
||||
// Don't automatically update status when setting time
|
||||
console.log(`Set customer_enter_center_time for ${item.customer_name} to:`, updatedItem.customer_enter_center_time);
|
||||
} catch (e) {
|
||||
console.error('Error combining date and enter center time:', e);
|
||||
}
|
||||
} else {
|
||||
console.log('Skipping bulkEnterCenterTime - condition not met');
|
||||
}
|
||||
|
||||
if (bulkLeaveCenterTime && bulkLeaveCenterTime !== '') {
|
||||
updatedItem.customer_leave_center_time = combineDateAndTime(dateStr, bulkLeaveCenterTime);
|
||||
updatedItem.customer_route_status = PERSONAL_ROUTE_STATUS.LEFT_CENTER;
|
||||
// Check if bulkLeaveCenterTime is a valid time string (HH:mm format)
|
||||
console.log('Checking bulkLeaveCenterTime:', {
|
||||
value: bulkLeaveCenterTime,
|
||||
type: typeof bulkLeaveCenterTime,
|
||||
isTruthy: !!bulkLeaveCenterTime,
|
||||
isString: typeof bulkLeaveCenterTime === 'string',
|
||||
trimmed: typeof bulkLeaveCenterTime === 'string' ? bulkLeaveCenterTime.trim() : 'N/A',
|
||||
trimmedLength: typeof bulkLeaveCenterTime === 'string' ? bulkLeaveCenterTime.trim().length : 0
|
||||
});
|
||||
|
||||
if (bulkLeaveCenterTime && typeof bulkLeaveCenterTime === 'string' && bulkLeaveCenterTime.trim() !== '') {
|
||||
try {
|
||||
const combinedTime = combineDateAndTime(dateStr, bulkLeaveCenterTime);
|
||||
const dateValue = combinedTime.toDate();
|
||||
console.log('Combined leave center time (moment):', combinedTime);
|
||||
console.log('Combined leave center time (Date):', dateValue);
|
||||
updatedItem.customer_leave_center_time = dateValue;
|
||||
// Don't automatically update status when setting time
|
||||
console.log(`Set customer_leave_center_time for ${item.customer_name} to:`, updatedItem.customer_leave_center_time);
|
||||
} catch (e) {
|
||||
console.error('Error combining date and leave center time:', e);
|
||||
}
|
||||
} else {
|
||||
console.log('Skipping bulkLeaveCenterTime - condition not met');
|
||||
}
|
||||
|
||||
console.log(`Final updatedItem for ${item.customer_name}:`, {
|
||||
customer_id: updatedItem.customer_id,
|
||||
customer_enter_center_time: updatedItem.customer_enter_center_time,
|
||||
customer_leave_center_time: updatedItem.customer_leave_center_time
|
||||
});
|
||||
|
||||
if (bulkCustomerRouteStatus && bulkCustomerRouteStatus !== '') {
|
||||
updatedItem.customer_route_status = bulkCustomerRouteStatus;
|
||||
@ -529,6 +597,19 @@ const PersonnelInfoTable = ({transRoutes, showCompletedInfo,
|
||||
});
|
||||
|
||||
requestBody = Object.assign({}, requestBody, {route_customer_list: updatedCustomerList, updatedAt: new Date(), updatedBy: 'admin'});
|
||||
|
||||
// Debug: Log all customers from updated list
|
||||
console.log('=== All Updated Customers ===');
|
||||
updatedCustomerList.forEach((customer, index) => {
|
||||
console.log(`Customer ${index + 1}:`, {
|
||||
customer_id: customer.customer_id,
|
||||
customer_name: customer.customer_name,
|
||||
pickup_status: customer.customer_pickup_status,
|
||||
customer_enter_center_time: customer.customer_enter_center_time,
|
||||
customer_leave_center_time: customer.customer_leave_center_time
|
||||
});
|
||||
});
|
||||
|
||||
let finalParams = { id: routeId, data: requestBody };
|
||||
if (scheduleDate) {
|
||||
finalParams = Object.assign({}, finalParams, {dateText: moment(scheduleDate).format('MM/DD/YYYY'), fromSchedule: true})
|
||||
@ -536,6 +617,28 @@ const PersonnelInfoTable = ({transRoutes, showCompletedInfo,
|
||||
if (dateStr !== '' && dateStr !== moment().format('MM/DD/YYYY')) {
|
||||
finalParams = Object.assign({}, finalParams, {dateText: dateStr})
|
||||
}
|
||||
|
||||
// Log final payload before dispatch, especially for Wang,Huanran
|
||||
const wangCustomer = finalParams.data.route_customer_list?.find(c =>
|
||||
c.customer_id === '63657acff745ffd72affb8d8' || c.customer_name?.includes('Wang,Huanran')
|
||||
);
|
||||
console.log('Wang,Huanran customer in final payload:', wangCustomer);
|
||||
console.log('Final params being dispatched:', {
|
||||
id: finalParams.id,
|
||||
data_keys: Object.keys(finalParams.data),
|
||||
customer_count: finalParams.data.route_customer_list?.length
|
||||
});
|
||||
|
||||
// Stringify to see what would actually be sent (Date serialization)
|
||||
try {
|
||||
const stringified = JSON.stringify(finalParams.data.route_customer_list?.slice(0, 2));
|
||||
console.log('Sample serialized customer list (first 2):', stringified);
|
||||
} catch (e) {
|
||||
console.error('Error stringifying:', e);
|
||||
}
|
||||
|
||||
console.log('=== End saveBulkUpdate Debug ===');
|
||||
|
||||
dispatch(updateRoute(finalParams));
|
||||
} else {
|
||||
window.alert('Fail to update Route: no route Id found.')
|
||||
@ -625,8 +728,8 @@ const PersonnelInfoTable = ({transRoutes, showCompletedInfo,
|
||||
<th>Type</th>
|
||||
{!showCompletedInfo &&<th>Route Type</th>}
|
||||
<th>Pick Up Time</th>
|
||||
{!showCompletedInfo && (<th>Enter Center Time</th>)}
|
||||
{!showCompletedInfo && (<th>Leave Center Time</th>)}
|
||||
<th>Enter Center Time</th>
|
||||
<th>Leave Center Time</th>
|
||||
<th>Drop Off Time</th>
|
||||
{showCompletedInfo && (<th>Schedule Absent</th>)}
|
||||
{showCompletedInfo && (<th>Schedule Absent Note</th>)}
|
||||
@ -697,8 +800,8 @@ const PersonnelInfoTable = ({transRoutes, showCompletedInfo,
|
||||
{ customer.routeType}
|
||||
</td>}
|
||||
<td>{customer.customer_pickup_time && new Date(customer.customer_pickup_time).toLocaleTimeString('en-US', {hour12: false, hour: '2-digit', minute: '2-digit'})}</td>
|
||||
{!showCompletedInfo &&<td>{customer.customer_enter_center_time && new Date(customer.customer_enter_center_time).toLocaleTimeString('en-US', {hour12: false, hour: '2-digit', minute: '2-digit'})}</td>}
|
||||
{!showCompletedInfo &&<td>{customer.customer_leave_center_time && new Date(customer.customer_leave_center_time).toLocaleTimeString('en-US', {hour12: false, hour: '2-digit', minute: '2-digit'})}</td>}
|
||||
<td>{customer.customer_enter_center_time && new Date(customer.customer_enter_center_time).toLocaleTimeString('en-US', {hour12: false, hour: '2-digit', minute: '2-digit'})}</td>
|
||||
<td>{customer.customer_leave_center_time && new Date(customer.customer_leave_center_time).toLocaleTimeString('en-US', {hour12: false, hour: '2-digit', minute: '2-digit'})}</td>
|
||||
<td>{customer.customer_dropoff_time && new Date(customer.customer_dropoff_time).toLocaleTimeString('en-US', {hour12: false, hour: '2-digit', minute: '2-digit'})}</td>
|
||||
{showCompletedInfo && (<td>
|
||||
{ customer.customer_pickup_status === PICKUP_STATUS.SCHEDULE_ABSENT ? 'Yes' : "No" }
|
||||
@ -749,8 +852,8 @@ const PersonnelInfoTable = ({transRoutes, showCompletedInfo,
|
||||
{ customerItem.routeType}
|
||||
</td>}
|
||||
<td>{customerItem.customer_pickup_time && new Date(customerItem.customer_pickup_time).toLocaleTimeString('en-US', {hour12: false})}</td>
|
||||
{!showCompletedInfo &&<td>{customerItem.customer_enter_center_time && new Date(customerItem.customer_enter_center_time).toLocaleTimeString('en-US', {hour12: false})}</td>}
|
||||
{!showCompletedInfo &&<td>{customerItem.customer_leave_center_time && new Date(customerItem.customer_leave_center_time).toLocaleTimeString('en-US', {hour12: false})}</td>}
|
||||
<td>{customerItem.customer_enter_center_time && new Date(customerItem.customer_enter_center_time).toLocaleTimeString('en-US', {hour12: false})}</td>
|
||||
<td>{customerItem.customer_leave_center_time && new Date(customerItem.customer_leave_center_time).toLocaleTimeString('en-US', {hour12: false})}</td>
|
||||
<td>{customerItem.customer_dropoff_time && new Date(customerItem.customer_dropoff_time).toLocaleTimeString('en-US', {hour12: false})}</td>
|
||||
{showCompletedInfo && (<td>
|
||||
{ customerItem.customer_pickup_status === PICKUP_STATUS.SCHEDULE_ABSENT ? 'Yes' : "No" }
|
||||
@ -806,8 +909,8 @@ const PersonnelInfoTable = ({transRoutes, showCompletedInfo,
|
||||
{ customer.routeType}
|
||||
</td>}
|
||||
<td>{customer.customer_pickup_time && new Date(customer.customer_pickup_time).toLocaleTimeString('en-US', {hour12: false})}</td>
|
||||
{!showCompletedInfo &&<td>{customer.customer_enter_center_time && new Date(customer.customer_enter_center_time).toLocaleTimeString('en-US', {hour12: false})}</td>}
|
||||
{!showCompletedInfo &&<td>{customer.customer_leave_center_time && new Date(customer.customer_leave_center_time).toLocaleTimeString('en-US', {hour12: false})}</td>}
|
||||
<td>{customer.customer_enter_center_time && new Date(customer.customer_enter_center_time).toLocaleTimeString('en-US', {hour12: false})}</td>
|
||||
<td>{customer.customer_leave_center_time && new Date(customer.customer_leave_center_time).toLocaleTimeString('en-US', {hour12: false})}</td>
|
||||
<td>{customer.customer_dropoff_time && new Date(customer.customer_dropoff_time).toLocaleTimeString('en-US', {hour12: false})}</td>
|
||||
{showCompletedInfo && (<td>
|
||||
{ customer.customer_pickup_status === PICKUP_STATUS.SCHEDULE_ABSENT ? 'Yes' : "No" }
|
||||
|
||||
@ -2,12 +2,15 @@ import React from "react";
|
||||
import { useNavigate} from "react-router-dom";
|
||||
import { ROUTE_STATUS, PICKUP_STATUS } from "./../../shared";
|
||||
|
||||
const RouteCard = ({transRoute, drivers, vehicles, driver, vehicle, routeIndex}) => {
|
||||
const RouteCard = ({transRoute, drivers, vehicles, driver, vehicle, routeIndex, isTemplate, templateId}) => {
|
||||
const navigate = useNavigate();
|
||||
const params = new URLSearchParams(window.location.search);
|
||||
const scheduleDate = params.get('dateSchedule');
|
||||
const handleOnClick = () => {
|
||||
if (scheduleDate) {
|
||||
if (isTemplate && templateId) {
|
||||
// For templates, use _id and navigate to template route view
|
||||
navigate(`/trans-routes/daily-templates/${templateId}/view-route/${transRoute._id || transRoute.id}`);
|
||||
} else if (scheduleDate) {
|
||||
navigate(`/trans-routes/${transRoute.id}?dateSchedule=${scheduleDate}`);
|
||||
} else {
|
||||
navigate(`/trans-routes/${transRoute.id}`);
|
||||
@ -72,22 +75,24 @@ const RouteCard = ({transRoute, drivers, vehicles, driver, vehicle, routeIndex})
|
||||
return routeStatusData;
|
||||
}
|
||||
|
||||
const routeStatus = getRouteStatus(transRoute);
|
||||
const routeStatus = isTemplate ? { text: '', className: '' } : getRouteStatus(transRoute);
|
||||
|
||||
return (
|
||||
<>
|
||||
{
|
||||
transRoute && (<div onClick={handleOnClick} className={`card-container ${routeStatus.className==='purple' ? routeStatus.className : ''}`}>
|
||||
transRoute && (<div onClick={handleOnClick} className={`card-container ${!isTemplate && routeStatus.className==='purple' ? routeStatus.className : ''}`}>
|
||||
<div className="route-card-title">{`#${routeIndex+1} `}{transRoute.name}</div>
|
||||
<div>{drivers.find((driver) => driver.id === transRoute.driver)?.name}</div>
|
||||
<div>{`${transRoute?.route_customer_list?.length} Participants`}</div>
|
||||
<div>{vehicles.find((vehicle) => vehicle.id === transRoute.vehicle)?.tag}</div>
|
||||
{!isTemplate && (
|
||||
<div className="list row">
|
||||
<div className="col-md-12 card-status">
|
||||
<div className="float-end">{routeStatus?.text}</div>
|
||||
<div className={`${routeStatus.className==='purple' ? '' : routeStatus.className} float-end`} />
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>)
|
||||
}
|
||||
{
|
||||
|
||||
@ -11,6 +11,7 @@ import { GripVertical, Pencil, RecordCircleFill, XSquare } from 'react-bootstrap
|
||||
|
||||
const ItemTypes = {
|
||||
CARD: 'card',
|
||||
UNASSIGNED_CUSTOMER: 'unassigned_customer',
|
||||
};
|
||||
|
||||
const Card = ({ content, index, moveCard }) => {
|
||||
@ -79,8 +80,9 @@ const Card = ({ content, index, moveCard }) => {
|
||||
)
|
||||
}
|
||||
|
||||
const RouteCustomerEditor = ({currentRoute, setNewCustomerList = (a) => {}, viewMode, editFun}) => {
|
||||
const RouteCustomerEditor = ({currentRoute, setNewCustomerList = (a) => {}, viewMode, editFun, onAddCustomer = null}) => {
|
||||
const [customers, setCustomers] = useState([]);
|
||||
const [initializedRouteId, setInitializedRouteId] = useState(null);
|
||||
const [showAddPersonnelModal, setShowAddPersonnelModal] = useState(false);
|
||||
const [showAddAptGroupModal, setShowAddAptGroupModal] = useState(false);
|
||||
const [showEditAptGroupModal, setShowEditAptGroupModal] = useState(false);
|
||||
@ -101,16 +103,38 @@ const RouteCustomerEditor = ({currentRoute, setNewCustomerList = (a) => {}, view
|
||||
const [pageCount, setPageCount] = useState(0);
|
||||
const itemsPerPage = 10;
|
||||
|
||||
// Helper function to get all customer IDs already in the route
|
||||
const getAssignedCustomerIds = () => {
|
||||
const assignedIds = new Set();
|
||||
customers.forEach(item => {
|
||||
if (item.customer_id) {
|
||||
assignedIds.add(item.customer_id);
|
||||
}
|
||||
if (item.customers) {
|
||||
item.customers.forEach(c => {
|
||||
if (c.customer_id) {
|
||||
assignedIds.add(c.customer_id);
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
return assignedIds;
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
// Fetch items from another resources.
|
||||
const endOffset = itemOffset + itemsPerPage;
|
||||
setCurrentItems(customerOptions?.filter(customer => (lastNameFilter && (customer.lastname?.toLowerCase().indexOf(lastNameFilter) === 0)) || !lastNameFilter).filter((customer) => customer.name?.toLowerCase().includes(customerFilter?.toLowerCase()) || customer.id.toLowerCase().includes(customerFilter?.toLowerCase()) || customer.address1?.toLowerCase().includes(customerFilter.toLowerCase()) || customer.address2?.toLowerCase().includes(customerFilter.toLowerCase()) || customer.address3?.toLowerCase().includes(customerFilter.toLowerCase()) || customer.address4?.toLowerCase().includes(customerFilter.toLowerCase()) || customer.address5?.toLowerCase().includes(customerFilter.toLowerCase()) || customer.apartment?.toLowerCase().includes(customerFilter.toLocaleLowerCase()) ).slice(itemOffset, endOffset));
|
||||
setPageCount(Math.ceil(customerOptions?.filter(customer => (lastNameFilter && (customer.lastname?.toLowerCase().indexOf(lastNameFilter) === 0)) || !lastNameFilter).filter((customer) => customer.name.toLowerCase().includes(customerFilter?.toLowerCase()) || customer.id.toLowerCase().includes(customerFilter?.toLowerCase()) || customer.address1?.toLowerCase().includes(customerFilter.toLowerCase()) || customer.address2?.toLowerCase().includes(customerFilter.toLowerCase()) || customer.address3?.toLowerCase().includes(customerFilter.toLowerCase()) || customer.address4?.toLowerCase().includes(customerFilter.toLowerCase()) || customer.address5?.toLowerCase().includes(customerFilter.toLowerCase()) || customer.apartment?.toLowerCase().includes(customerFilter.toLocaleLowerCase()) ).length / itemsPerPage));
|
||||
}, [customerOptions, itemOffset, customerFilter, lastNameFilter]);
|
||||
const assignedIds = getAssignedCustomerIds();
|
||||
// Filter out customers already in the route
|
||||
const availableCustomers = customerOptions?.filter(customer => !assignedIds.has(customer.id));
|
||||
setCurrentItems(availableCustomers?.filter(customer => (lastNameFilter && (customer.lastname?.toLowerCase().indexOf(lastNameFilter) === 0)) || !lastNameFilter).filter((customer) => customer.name?.toLowerCase().includes(customerFilter?.toLowerCase()) || customer.id.toLowerCase().includes(customerFilter?.toLowerCase()) || customer.address1?.toLowerCase().includes(customerFilter.toLowerCase()) || customer.address2?.toLowerCase().includes(customerFilter.toLowerCase()) || customer.address3?.toLowerCase().includes(customerFilter.toLowerCase()) || customer.address4?.toLowerCase().includes(customerFilter.toLowerCase()) || customer.address5?.toLowerCase().includes(customerFilter.toLowerCase()) || customer.apartment?.toLowerCase().includes(customerFilter.toLocaleLowerCase()) ).slice(itemOffset, endOffset));
|
||||
setPageCount(Math.ceil(availableCustomers?.filter(customer => (lastNameFilter && (customer.lastname?.toLowerCase().indexOf(lastNameFilter) === 0)) || !lastNameFilter).filter((customer) => customer.name.toLowerCase().includes(customerFilter?.toLowerCase()) || customer.id.toLowerCase().includes(customerFilter?.toLowerCase()) || customer.address1?.toLowerCase().includes(customerFilter.toLowerCase()) || customer.address2?.toLowerCase().includes(customerFilter.toLowerCase()) || customer.address3?.toLowerCase().includes(customerFilter.toLowerCase()) || customer.address4?.toLowerCase().includes(customerFilter.toLowerCase()) || customer.address5?.toLowerCase().includes(customerFilter.toLowerCase()) || customer.apartment?.toLowerCase().includes(customerFilter.toLocaleLowerCase()) ).length / itemsPerPage));
|
||||
}, [customerOptions, itemOffset, customerFilter, lastNameFilter, customers]);
|
||||
|
||||
const handlePageClick = (event) => {
|
||||
const newOffset = (event.selected * itemsPerPage) % customerOptions?.filter((customer) => customer.name?.toLowerCase().includes(customerFilter?.toLowerCase()) || customer.id?.toLowerCase().includes(customerFilter?.toLowerCase()) || customer.address1?.toLowerCase().includes(customerFilter.toLowerCase()) || customer.address2?.toLowerCase().includes(customerFilter.toLowerCase()) || customer.address3?.toLowerCase().includes(customerFilter.toLowerCase()) || customer.address4?.toLowerCase().includes(customerFilter.toLowerCase()) || customer.address5?.toLowerCase().includes(customerFilter.toLowerCase()) || customer.apartment?.toLowerCase().includes(customerFilter.toLocaleLowerCase()) ).length;
|
||||
const assignedIds = getAssignedCustomerIds();
|
||||
const availableCustomers = customerOptions?.filter(customer => !assignedIds.has(customer.id));
|
||||
const newOffset = (event.selected * itemsPerPage) % availableCustomers?.filter((customer) => customer.name?.toLowerCase().includes(customerFilter?.toLowerCase()) || customer.id?.toLowerCase().includes(customerFilter?.toLowerCase()) || customer.address1?.toLowerCase().includes(customerFilter.toLowerCase()) || customer.address2?.toLowerCase().includes(customerFilter.toLowerCase()) || customer.address3?.toLowerCase().includes(customerFilter.toLowerCase()) || customer.address4?.toLowerCase().includes(customerFilter.toLowerCase()) || customer.address5?.toLowerCase().includes(customerFilter.toLowerCase()) || customer.apartment?.toLowerCase().includes(customerFilter.toLocaleLowerCase()) ).length;
|
||||
console.log(
|
||||
`User requested page number ${event.selected}, which is offset ${newOffset}`
|
||||
);
|
||||
@ -129,7 +153,14 @@ const RouteCustomerEditor = ({currentRoute, setNewCustomerList = (a) => {}, view
|
||||
setLastNameFilter(undefined);
|
||||
if (customerOptions.length === 0) {
|
||||
CustomerService.getAllActiveCustomers().then((data) => {
|
||||
setCustomerOptions(data.data);
|
||||
// Filter out discharged customers
|
||||
const filtered = (data.data || []).filter(customer => {
|
||||
const isDischarged = customer.type === 'discharged' ||
|
||||
(customer.name && customer.name.toLowerCase().includes('discharged')) ||
|
||||
customer.status !== 'active';
|
||||
return !isDischarged;
|
||||
});
|
||||
setCustomerOptions(filtered);
|
||||
})
|
||||
}
|
||||
setShowAddPersonnelModal(true);
|
||||
@ -146,7 +177,14 @@ const RouteCustomerEditor = ({currentRoute, setNewCustomerList = (a) => {}, view
|
||||
setLastNameFilter(undefined);
|
||||
if (customerOptions.length === 0) {
|
||||
CustomerService.getAllActiveCustomers().then((data) => {
|
||||
setCustomerOptions(data.data);
|
||||
// Filter out discharged customers
|
||||
const filtered = (data.data || []).filter(customer => {
|
||||
const isDischarged = customer.type === 'discharged' ||
|
||||
(customer.name && customer.name.toLowerCase().includes('discharged')) ||
|
||||
customer.status !== 'active';
|
||||
return !isDischarged;
|
||||
});
|
||||
setCustomerOptions(filtered);
|
||||
})
|
||||
}
|
||||
setShowAddAptGroupModal(true);
|
||||
@ -167,7 +205,14 @@ const RouteCustomerEditor = ({currentRoute, setNewCustomerList = (a) => {}, view
|
||||
setLastNameFilter(undefined);
|
||||
if (customerOptions.length === 0) {
|
||||
CustomerService.getAllActiveCustomers().then((data) => {
|
||||
setCustomerOptions(data.data);
|
||||
// Filter out discharged customers
|
||||
const filtered = (data.data || []).filter(customer => {
|
||||
const isDischarged = customer.type === 'discharged' ||
|
||||
(customer.name && customer.name.toLowerCase().includes('discharged')) ||
|
||||
customer.status !== 'active';
|
||||
return !isDischarged;
|
||||
});
|
||||
setCustomerOptions(filtered);
|
||||
})
|
||||
}
|
||||
setNewGroupAddress(group.customers[0].customer_group_address);
|
||||
@ -279,6 +324,52 @@ const RouteCustomerEditor = ({currentRoute, setNewCustomerList = (a) => {}, view
|
||||
setNewRouteCustomerList([]);
|
||||
}
|
||||
|
||||
// Function to add a customer from external drop (like unassigned customers)
|
||||
const addCustomerFromDrop = useCallback((customerData, dropIndex = null) => {
|
||||
if (!customerData || !customerData.id) return;
|
||||
|
||||
// Check if customer already exists
|
||||
const customerExists = customers.some(item => {
|
||||
if (item.customer_id) {
|
||||
return item.customer_id === customerData.id;
|
||||
}
|
||||
if (item.customers) {
|
||||
return item.customers.some(c => c.customer_id === customerData.id);
|
||||
}
|
||||
return false;
|
||||
});
|
||||
|
||||
if (customerExists) return;
|
||||
|
||||
const newCustomer = {
|
||||
customer_id: customerData.id,
|
||||
customer_name: `${customerData.name} ${customerData.name_cn?.length > 0 ? `(${customerData.name_cn})` : ``}`,
|
||||
customer_address: customerData.address1 || '',
|
||||
customer_avatar: customerData.avatar,
|
||||
customer_type: customerData.type,
|
||||
customer_pickup_status: customerData.pickup_status,
|
||||
customer_note: customerData.note,
|
||||
customer_special_needs: customerData.special_needs,
|
||||
customer_phone: customerData.phone || customerData.mobile_phone || customerData.home_phone,
|
||||
customer_route_status: PERSONAL_ROUTE_STATUS.NO_STATUS,
|
||||
customer_pickup_order: customers.length + 1,
|
||||
customer_table_id: customerData.table_id,
|
||||
customer_language: customerData.language
|
||||
};
|
||||
|
||||
setCustomers(prevCustomers => {
|
||||
if (dropIndex !== null && dropIndex >= 0 && dropIndex <= prevCustomers.length) {
|
||||
// Insert at specific position
|
||||
const newList = [...prevCustomers];
|
||||
newList.splice(dropIndex, 0, newCustomer);
|
||||
return newList;
|
||||
} else {
|
||||
// Add to end
|
||||
return [...prevCustomers, newCustomer];
|
||||
}
|
||||
});
|
||||
}, [customers]);
|
||||
|
||||
const addAptGroup = () => {
|
||||
if (checkGroupRequiredField()) {
|
||||
const result = [].concat(customers).concat([{
|
||||
@ -312,9 +403,14 @@ const RouteCustomerEditor = ({currentRoute, setNewCustomerList = (a) => {}, view
|
||||
}
|
||||
}
|
||||
|
||||
// Only initialize customers from currentRoute when the route ID changes
|
||||
// Don't reset when currentRoute object reference changes (which happens on every render)
|
||||
useEffect(() => {
|
||||
if (currentRoute?.id && currentRoute.id !== initializedRouteId) {
|
||||
setCustomers(getRouteCustomersWithGroups());
|
||||
}, [currentRoute])
|
||||
setInitializedRouteId(currentRoute.id);
|
||||
}
|
||||
}, [currentRoute?.id, initializedRouteId])
|
||||
const getRouteCustomersWithGroups = () => {
|
||||
const customerList = currentRoute?.route_customer_list?.map(item => Object.assign({}, item, {routeType: currentRoute.type, routeId: currentRoute.id}));
|
||||
const result = {};
|
||||
@ -353,10 +449,16 @@ const RouteCustomerEditor = ({currentRoute, setNewCustomerList = (a) => {}, view
|
||||
}
|
||||
|
||||
const deleteCustomer = (id) => {
|
||||
if (!window.confirm('Are you sure you want to remove this customer from the route?')) {
|
||||
return;
|
||||
}
|
||||
setCustomers(customers.filter((customer) => customer.customer_id !== id));
|
||||
}
|
||||
|
||||
const deleteGroup = (index) => {
|
||||
if (!window.confirm('Are you sure you want to remove this group from the route?')) {
|
||||
return;
|
||||
}
|
||||
const arr = [].concat(customers);
|
||||
arr.splice(index, 1);
|
||||
setCustomers(arr);
|
||||
@ -393,7 +495,8 @@ const RouteCustomerEditor = ({currentRoute, setNewCustomerList = (a) => {}, view
|
||||
};
|
||||
|
||||
const ItemsGroup = ({ currentItems }) => {
|
||||
return currentItems?.filter((customer) => customer.name.toLowerCase().includes(customerFilter.toLowerCase()) || customer.id.toLowerCase().includes(customerFilter.toLowerCase()) || customer.address1?.toLowerCase().includes(customerFilter.toLowerCase()) || customer.address2?.toLowerCase().includes(customerFilter.toLowerCase()) || customer.address3?.toLowerCase().includes(customerFilter.toLowerCase()) || customer.address4?.toLowerCase().includes(customerFilter.toLowerCase()) || customer.address5?.toLowerCase().includes(customerFilter.toLowerCase()) || customer.apartment?.toLowerCase().includes(customerFilter.toLocaleLowerCase()) ).map(
|
||||
const assignedIds = getAssignedCustomerIds();
|
||||
return currentItems?.filter(customer => !assignedIds.has(customer.id)).filter((customer) => customer.name.toLowerCase().includes(customerFilter.toLowerCase()) || customer.id.toLowerCase().includes(customerFilter.toLowerCase()) || customer.address1?.toLowerCase().includes(customerFilter.toLowerCase()) || customer.address2?.toLowerCase().includes(customerFilter.toLowerCase()) || customer.address3?.toLowerCase().includes(customerFilter.toLowerCase()) || customer.address4?.toLowerCase().includes(customerFilter.toLowerCase()) || customer.address5?.toLowerCase().includes(customerFilter.toLowerCase()) || customer.apartment?.toLowerCase().includes(customerFilter.toLocaleLowerCase()) ).map(
|
||||
(customer) => <div key={customer.id} className="option-item">
|
||||
<input className="me-4 mt-2" type="checkbox" checked={newRouteGroupedCustomerList.find((item) => item.customer_id === customer.id)!==undefined} value={newRouteGroupedCustomerList.find((item) => item.customer_id === customer.id)!==undefined} onChange={(e) => toggleGroupedItemToRouteList(customer, e.target.value)}/>
|
||||
<div>
|
||||
@ -424,6 +527,13 @@ const RouteCustomerEditor = ({currentRoute, setNewCustomerList = (a) => {}, view
|
||||
return count;
|
||||
}
|
||||
|
||||
// Expose addCustomerFromDrop via callback
|
||||
useEffect(() => {
|
||||
if (onAddCustomer && typeof onAddCustomer === 'function') {
|
||||
onAddCustomer(addCustomerFromDrop);
|
||||
}
|
||||
}, [addCustomerFromDrop, onAddCustomer]);
|
||||
|
||||
useEffect(() => {
|
||||
const result = [];
|
||||
for (const item of customers) {
|
||||
@ -440,11 +550,43 @@ const RouteCustomerEditor = ({currentRoute, setNewCustomerList = (a) => {}, view
|
||||
setNewCustomerList(result);
|
||||
}, [customers])
|
||||
|
||||
// Create a drop zone for unassigned customers
|
||||
const CustomersDropZone = ({ children }) => {
|
||||
const [{ isOver }, drop] = useDrop({
|
||||
accept: 'UNASSIGNED_CUSTOMER',
|
||||
drop: (item, monitor) => {
|
||||
if (addCustomerFromDrop && item && item.id) {
|
||||
addCustomerFromDrop(item, null); // Add to end
|
||||
}
|
||||
return { dropped: true };
|
||||
},
|
||||
collect: (monitor) => ({
|
||||
isOver: monitor.isOver(),
|
||||
}),
|
||||
});
|
||||
|
||||
return (
|
||||
<DndProvider backend={HTML5Backend}>
|
||||
<div
|
||||
ref={drop}
|
||||
className="customers-container mb-4"
|
||||
style={{
|
||||
backgroundColor: isOver ? '#f0f0f0' : 'transparent',
|
||||
minHeight: isOver ? '100px' : 'auto',
|
||||
border: isOver ? '2px dashed #0d6efd' : '2px dashed transparent',
|
||||
borderRadius: '4px',
|
||||
padding: isOver ? '8px' : '0'
|
||||
}}
|
||||
>
|
||||
{children}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
{ !viewMode && <h6 class="text-primary">Customers Assigned ({getCurrentAssignedNumber()})</h6>}
|
||||
{ viewMode && <h6 class="text-primary">Route Assignment <button className="btn btn-sm btn-primary" onClick={() => editFun('assignment')}><Pencil size={16} className="me-2"></Pencil>Edit </button></h6>}
|
||||
{!viewMode && <div className="customers-container mb-4">
|
||||
{!viewMode && <CustomersDropZone>
|
||||
{customers.map((item, index) => {
|
||||
if (item?.customers) {
|
||||
return <Card key={index} index={index} moveCard={reorderItems} content={(<div className="customers-dnd-item-container">
|
||||
@ -481,12 +623,12 @@ const RouteCustomerEditor = ({currentRoute, setNewCustomerList = (a) => {}, view
|
||||
<div className="new-customers-dnd-item-container">
|
||||
<div className="stop-index"><span>{`Stop ${customers?.length+1}`}</span><RecordCircleFill size={16} color={"#ccc"} className="ms-2"></RecordCircleFill> </div>
|
||||
<div>
|
||||
<button className="btn btn-primary btn-sm me-2" onClick={() => openAddPersonnelModal()}> + Add Personnel </button>
|
||||
<button className="btn btn-primary btn-sm me-2" onClick={() => openAddAptGroupModal()}> + Add Apt Group </button>
|
||||
<button className="btn btn-primary btn-sm me-2 mb-2" onClick={() => openAddPersonnelModal()}> + Add Personnel </button>
|
||||
<button className="btn btn-primary btn-sm me-2 mb-2" onClick={() => openAddAptGroupModal()}> + Add Apt Group </button>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>}
|
||||
</CustomersDropZone>}
|
||||
{
|
||||
viewMode && <div className="customers-container mb-4">
|
||||
{customers.map((item, index) => {
|
||||
@ -718,7 +860,7 @@ const RouteCustomerEditor = ({currentRoute, setNewCustomerList = (a) => {}, view
|
||||
</Button>
|
||||
</Modal.Footer>
|
||||
</Modal>
|
||||
</DndProvider>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
import React, {useEffect, useState} from "react";
|
||||
import React, {useEffect, useState, useRef} from "react";
|
||||
import { useSelector,useDispatch } from "react-redux";
|
||||
import { useParams, useNavigate } from "react-router-dom";
|
||||
import { selectAllRoutes, transRoutesSlice, vehicleSlice, selectTomorrowAllRoutes, selectAllActiveDrivers, selectAllActiveVehicles, selectHistoryRoutes } from "./../../store";
|
||||
@ -10,6 +10,9 @@ import 'react-time-picker/dist/TimePicker.css';
|
||||
import moment from 'moment';
|
||||
import { Archive, GripVertical } from "react-bootstrap-icons";
|
||||
import { PERSONAL_ROUTE_STATUS } from "../../shared";
|
||||
import { DndProvider } from 'react-dnd';
|
||||
import { HTML5Backend } from 'react-dnd-html5-backend';
|
||||
import { useDrag } from 'react-dnd';
|
||||
|
||||
const RouteEdit = () => {
|
||||
const params = useParams();
|
||||
@ -38,6 +41,7 @@ const RouteEdit = () => {
|
||||
const [currentRoute, setCurrentRoute] = useState(undefined);
|
||||
const [allCustomers, setAllCustomers] = useState([]);
|
||||
const [unassignedCustomers, setUnassignedCustomers] = useState([]);
|
||||
const [addCustomerToRoute, setAddCustomerToRoute] = useState(null);
|
||||
const paramsQuery = new URLSearchParams(window.location.search);
|
||||
const scheduleDate = paramsQuery.get('dateSchedule');
|
||||
const editSection = paramsQuery.get('editSection')
|
||||
@ -54,17 +58,43 @@ const RouteEdit = () => {
|
||||
}
|
||||
|
||||
const softDeleteCurrentRoute = () => {
|
||||
if (!window.confirm('Are you sure you want to archive this route? This action cannot be undone.')) {
|
||||
return;
|
||||
}
|
||||
const data = Object.assign({}, currentRoute, {status: ['disabled']})
|
||||
dispatch(updateRoute({ id: currentRoute?.id, data, callback: redirectToDashboard }));
|
||||
// redirectToDashboard();
|
||||
}
|
||||
|
||||
const validateRoute = () => {
|
||||
const errors = [];
|
||||
|
||||
// Required fields validation
|
||||
if (!routeName || routeName.trim() === '') {
|
||||
errors.push('Route Name');
|
||||
}
|
||||
if (!newRouteType || newRouteType === '') {
|
||||
errors.push('Route Type');
|
||||
}
|
||||
if (!newDriver || newDriver === '') {
|
||||
errors.push('Driver');
|
||||
}
|
||||
if (!newVehicle || newVehicle === '') {
|
||||
errors.push('Vehicle');
|
||||
}
|
||||
|
||||
if (errors.length > 0) {
|
||||
window.alert(`Please fill in the following required fields:\n${errors.join('\n')}`);
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
};
|
||||
|
||||
const updateCurrentRoute = () => {
|
||||
try {
|
||||
if (!routeName || routeName === '') { setErrorMessage('Route Name is Required'); return;}
|
||||
if (!newRouteType || newRouteType === '') {setErrorMessage('Route Type is Required'); return;}
|
||||
if (!newDriver || newDriver === '') {setErrorMessage('Driver is Required'); return;}
|
||||
if (!newVehicle || newVehicle === '') {setErrorMessage('Vehicle is Required'); return;}
|
||||
if (!validateRoute()) {
|
||||
return;
|
||||
}
|
||||
let data = Object.assign({}, currentRoute, {name: routeName, driver: newDriver, vehicle: newVehicle, type: newRouteType, route_customer_list: newCustomerList});
|
||||
if (estimatedStartTime && estimatedStartTime !== '') {
|
||||
data = Object.assign({}, data, {estimated_start_time: combineDateAndTime(currentRoute.schedule_date, estimatedStartTime)})
|
||||
@ -146,9 +176,15 @@ const RouteEdit = () => {
|
||||
});
|
||||
|
||||
// Filter out customers that are not assigned to any route
|
||||
return customers.filter(customer =>
|
||||
customer.status === 'active' && !assignedCustomerIds.has(customer.id)
|
||||
);
|
||||
// Also exclude discharged customers (type is 'discharged' or name contains 'discharged')
|
||||
return customers.filter(customer => {
|
||||
const isDischarged = customer.type === 'discharged' ||
|
||||
(customer.name && customer.name.toLowerCase().includes('discharged')) ||
|
||||
(customer.status !== 'active');
|
||||
return customer.status === 'active' &&
|
||||
!assignedCustomerIds.has(customer.id) &&
|
||||
!isDischarged;
|
||||
});
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
@ -180,16 +216,22 @@ const RouteEdit = () => {
|
||||
|
||||
const routeDate = currentRoute.schedule_date;
|
||||
|
||||
// Get routes from the same date as the current route
|
||||
// Get routes from the same date as the current route (excluding current route which we'll handle separately)
|
||||
const sameDateRoutes = [
|
||||
...allRoutes.filter(route => route.schedule_date === routeDate),
|
||||
...tomorrowRoutes.filter(route => route.schedule_date === routeDate),
|
||||
...historyRoutes.filter(route => route.schedule_date === routeDate)
|
||||
...allRoutes.filter(route => route.schedule_date === routeDate && route.id !== currentRoute.id),
|
||||
...tomorrowRoutes.filter(route => route.schedule_date === routeDate && route.id !== currentRoute.id),
|
||||
...historyRoutes.filter(route => route.schedule_date === routeDate && route.id !== currentRoute.id)
|
||||
];
|
||||
|
||||
const unassigned = calculateUnassignedCustomers(allCustomers, sameDateRoutes);
|
||||
// Add a virtual route with the current newCustomerList (to include customers being added in the editor)
|
||||
const routesWithCurrentEdits = [
|
||||
...sameDateRoutes,
|
||||
{ route_customer_list: newCustomerList || [] }
|
||||
];
|
||||
|
||||
const unassigned = calculateUnassignedCustomers(allCustomers, routesWithCurrentEdits);
|
||||
setUnassignedCustomers(unassigned);
|
||||
}, [allCustomers, allRoutes, tomorrowRoutes, historyRoutes, currentRoute]);
|
||||
}, [allCustomers, allRoutes, tomorrowRoutes, historyRoutes, currentRoute, newCustomerList]);
|
||||
|
||||
// useEffect(() => {
|
||||
// if (currentRoute) {
|
||||
@ -203,14 +245,41 @@ const RouteEdit = () => {
|
||||
// setErrorMessage(undefined);
|
||||
// }, [currentRoute])
|
||||
|
||||
// Draggable component for unassigned customers
|
||||
const DraggableUnassignedCustomer = ({ customer }) => {
|
||||
const [{ isDragging }, drag] = useDrag({
|
||||
type: 'UNASSIGNED_CUSTOMER',
|
||||
item: () => customer,
|
||||
collect: (monitor) => ({
|
||||
isDragging: monitor.isDragging(),
|
||||
}),
|
||||
});
|
||||
|
||||
const opacity = isDragging ? 0.5 : 1;
|
||||
|
||||
return (
|
||||
<div ref={drag} style={{ opacity }} className="customers-dnd-item-container-absent">
|
||||
<GripVertical className="me-4" size={14}></GripVertical>
|
||||
<div className="customer-dnd-item">
|
||||
<span>{customer.name} </span>
|
||||
<small className="me-2">{customer.address1}</small>
|
||||
<small className="me-2">{customer.type}</small>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className="list row mb-4">
|
||||
<Breadcrumb>
|
||||
<Breadcrumb.Item>Transportation</Breadcrumb.Item>
|
||||
<Breadcrumb.Item active>
|
||||
<Breadcrumb.Item href="/">Transportation</Breadcrumb.Item>
|
||||
<Breadcrumb.Item href="/trans-routes/dashboard">
|
||||
Transportation Routes
|
||||
</Breadcrumb.Item>
|
||||
<Breadcrumb.Item active>
|
||||
Edit Route
|
||||
</Breadcrumb.Item>
|
||||
</Breadcrumb>
|
||||
<div className="col-md-12 text-primary">
|
||||
<h4>
|
||||
@ -219,7 +288,7 @@ const RouteEdit = () => {
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="app-main-content-list-container">
|
||||
<div className="app-main-content-list-container form-page">
|
||||
<div className="app-main-content-list-func-container">
|
||||
<Tabs defaultActiveKey="routeOverview" id="route-view-tab">
|
||||
<Tab eventKey="routeOverview" title="Route Information">
|
||||
@ -342,7 +411,7 @@ const RouteEdit = () => {
|
||||
{
|
||||
newDriver && newDriver !== '' && <div className="column-card">
|
||||
<h6 className="text-primary">Driver Information</h6>
|
||||
<div className="text-primary">Personal Details</div>
|
||||
<small className="text-primary">Personal Details</small>
|
||||
<div className="app-main-content-fields-section short">
|
||||
<div className="field-body">
|
||||
<div className="field-label">Driver Name</div>
|
||||
@ -357,25 +426,26 @@ const RouteEdit = () => {
|
||||
<div className="field-value">{drivers.find(item => item.id === newDriver)?.title}</div>
|
||||
</div>
|
||||
<div className="field-body">
|
||||
<div className="field-label">Job Status</div>
|
||||
<div className="field-value">{vehicles.find(item => item.id === newVehicle)?.employment_status}</div>
|
||||
<div className="field-label">Job Type</div>
|
||||
<div className="field-value">{drivers.find(item => item.id === newDriver)?.employment_status}</div>
|
||||
</div>
|
||||
<div className="field-body">
|
||||
<div className="field-label">License Type</div>
|
||||
<div className="field-value">{drivers.find(item => item.id === newDriver)?.license_type}</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
<div className="app-main-content-fields-section short">
|
||||
<div className="field-body">
|
||||
<div className="field-label">Driver Capacity</div>
|
||||
<div className="field-value">{drivers.find(item => item.id === newDriver)?.driver_capacity}</div>
|
||||
</div>
|
||||
<div className="field-body">
|
||||
<div className="field-label">Phone Number</div>
|
||||
<div className="field-value">{drivers.find(item => item.id === newDriver)?.phone}</div>
|
||||
</div>
|
||||
<div className="field-body">
|
||||
<div className="field-label">Email</div>
|
||||
<div className="field-value">{vehicles.find(item => item.id === newVehicle)?.email}</div>
|
||||
<div className="field-value">{drivers.find(item => item.id === newDriver)?.email}</div>
|
||||
</div>
|
||||
|
||||
<div className="field-body"></div>
|
||||
<div className="field-body"></div>
|
||||
<div className="field-body"></div>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
@ -383,9 +453,10 @@ const RouteEdit = () => {
|
||||
</div> }
|
||||
|
||||
{
|
||||
editSection === 'assignment' && <div className="multi-columns-container">
|
||||
editSection === 'assignment' && <DndProvider backend={HTML5Backend}>
|
||||
<div className="multi-columns-container">
|
||||
<div className="column-container">
|
||||
<div className="column-card adjust">
|
||||
<div className="column-card adjust" style={{paddingRight: '30px'}}>
|
||||
<div className="col-md-12 mb-4">
|
||||
<RouteCustomerEditor
|
||||
currentRoute={currentRoute ? {
|
||||
@ -395,6 +466,7 @@ const RouteEdit = () => {
|
||||
) || []
|
||||
} : undefined}
|
||||
setNewCustomerList={setNewCustomerList}
|
||||
onAddCustomer={(addFn) => setAddCustomerToRoute(() => addFn)}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
@ -405,7 +477,7 @@ const RouteEdit = () => {
|
||||
<div className="customers-container mb-4">
|
||||
{
|
||||
currentRoute?.route_customer_list.filter(customer => customer?.customer_route_status === PERSONAL_ROUTE_STATUS.SCHEDULED_ABSENT)?.map((abItem) => {
|
||||
return <div className="customers-dnd-item-container-absent">
|
||||
return <div key={abItem.customer_id} className="customers-dnd-item-container-absent">
|
||||
<GripVertical className="me-4" size={14}></GripVertical>
|
||||
<div className="customer-dnd-item">
|
||||
<span>{abItem.customer_name} </span>
|
||||
@ -425,25 +497,19 @@ const RouteEdit = () => {
|
||||
<div className="customers-container mb-4">
|
||||
{
|
||||
unassignedCustomers?.map((customer) => {
|
||||
return <div className="customers-dnd-item-container-absent">
|
||||
<GripVertical className="me-4" size={14}></GripVertical>
|
||||
<div className="customer-dnd-item">
|
||||
<span>{customer.name} </span>
|
||||
<small className="me-2">{customer.address1}</small>
|
||||
<small className="me-2">{customer.type}</small>
|
||||
</div>
|
||||
</div>
|
||||
return <DraggableUnassignedCustomer key={customer.id} customer={customer} />
|
||||
})
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</DndProvider>
|
||||
}
|
||||
</Tab>
|
||||
</Tabs>
|
||||
<div className="list-func-panel">
|
||||
<button className="btn btn-primary" onClick={() => softDeleteCurrentRoute()}><Archive size={16} className="me-2"></Archive>Archive</button>
|
||||
<button className="btn btn-primary" onClick={() => softDeleteCurrentRoute()}><Archive size={16} className="me-2"></Archive>Delete</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@ -76,6 +76,13 @@ const RouteReportWithSignature = () => {
|
||||
}
|
||||
});
|
||||
}, [currentRoute]);
|
||||
// Get checklist result from route
|
||||
const checklistResult = currentRoute?.checklist_result || [];
|
||||
const getChecklistStatus = (itemKey) => {
|
||||
const item = checklistResult.find(c => c.key === itemKey || c.label?.toLowerCase().includes(itemKey.toLowerCase()));
|
||||
return item?.checked ? '✓' : '';
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<style>
|
||||
@ -85,9 +92,50 @@ const RouteReportWithSignature = () => {
|
||||
min-width: auto !important;
|
||||
width: auto !important;
|
||||
max-width: none !important;
|
||||
border: 1px solid #333;
|
||||
padding: 4px 8px;
|
||||
font-size: 12px;
|
||||
}
|
||||
.route-report-table {
|
||||
table-layout: auto !important;
|
||||
border-collapse: collapse;
|
||||
}
|
||||
.route-report-table thead th {
|
||||
background-color: #f5f5f5;
|
||||
text-align: center;
|
||||
color: #000;
|
||||
}
|
||||
.route-report-header {
|
||||
border: 1px solid #333;
|
||||
padding: 8px 16px;
|
||||
margin-bottom: 8px;
|
||||
font-size: 14px;
|
||||
}
|
||||
.route-report-signature-row {
|
||||
border: 1px solid #333;
|
||||
padding: 8px 16px;
|
||||
margin-bottom: 8px;
|
||||
font-size: 14px;
|
||||
}
|
||||
.inspection-table {
|
||||
border-collapse: collapse;
|
||||
width: 100%;
|
||||
margin-top: 16px;
|
||||
}
|
||||
.inspection-table th,
|
||||
.inspection-table td {
|
||||
border: 1px solid #333;
|
||||
padding: 4px 8px;
|
||||
font-size: 11px;
|
||||
}
|
||||
.inspection-table th {
|
||||
background-color: #f5f5f5;
|
||||
color: #000;
|
||||
}
|
||||
.bilingual-label {
|
||||
display: block;
|
||||
font-size: 10px;
|
||||
color: #666;
|
||||
}
|
||||
`}
|
||||
</style>
|
||||
@ -96,116 +144,210 @@ const RouteReportWithSignature = () => {
|
||||
<button className="btn btn-link btn-sm" onClick={() => directToView()}>Back</button>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<div className="float-end" style={{'background': '#eee', padding: '12px 8px', width: '50px', height: '50px', textAlign: 'center'}}>
|
||||
<span>{routeIndex}</span>
|
||||
|
||||
{/* Header Row with Route Info */}
|
||||
<div className="route-report-header" style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center' }}>
|
||||
<div style={{ flex: 1 }}>
|
||||
<strong>Route (路线):</strong> {currentRoute?.name}
|
||||
</div>
|
||||
<div style={{ flex: 1 }}>
|
||||
<strong>{driverLabel} ({isByOwnRoute ? '工作人员' : '司机'}):</strong> {currentDriver?.name}
|
||||
</div>
|
||||
<div className="list row mb-2">
|
||||
<div className="col-md-3 col-sm-6 col-xs-12 mb-4">
|
||||
Route: <span className="report-field-value"><strong>{currentRoute?.name}</strong></span>
|
||||
<div style={{ flex: 1 }}>
|
||||
<strong>Vehicle (车号):</strong> {currentVehicle?.vehicle_number}
|
||||
</div>
|
||||
<div className="col-md-3 col-sm-6 col-xs-12 mb-4">
|
||||
{driverLabel}: <span className="report-field-value"><strong>{currentDriver?.name}</strong></span>
|
||||
<div style={{ flex: 1 }}>
|
||||
<strong>Date (日期):</strong> {currentRoute?.schedule_date && moment(currentRoute?.schedule_date).format('MM/DD/YYYY')}
|
||||
</div>
|
||||
<div className="col-md-3 col-sm-6 col-xs-12 mb-4">
|
||||
Vehicle: <span className="report-field-value"><strong>{currentVehicle?.vehicle_number}</strong></span>
|
||||
<div style={{ background: '#eee', padding: '8px 12px', fontWeight: 'bold' }}>
|
||||
{routeIndex}
|
||||
</div>
|
||||
<div className="col-md-3 col-sm-6 col-xs-12 mb-4">
|
||||
Date: <span className="report-field-value"><strong>{currentRoute?.schedule_date && (new Date(currentRoute?.schedule_date))?.toLocaleDateString()}</strong></span>
|
||||
</div>
|
||||
|
||||
{/* Signature Row */}
|
||||
<div className="route-report-signature-row" style={{ display: 'flex', justifyContent: 'space-between' }}>
|
||||
<div style={{ flex: 1 }}>
|
||||
<strong>{driverLabel}'s Signature ({isByOwnRoute ? '工作人员签字' : '司机签字'}):</strong>
|
||||
{signature && <img width="100px" src={`data:image/jpg;base64, ${signature}`} style={{ marginLeft: '16px' }} alt="Driver Signature" />}
|
||||
{currentRoute?.end_time && <span style={{ marginLeft: '16px' }}>{new Date(currentRoute?.end_time).toLocaleTimeString('en-US', {hour12: false, hour: '2-digit', minute: '2-digit'})}</span>}
|
||||
</div>
|
||||
<div style={{ flex: 1 }}>
|
||||
<strong>Manager's Signature (经理签字):</strong>
|
||||
<img width="100px" src="/images/signature.jpeg" style={{ marginLeft: '16px' }} alt="Manager Signature" />
|
||||
</div>
|
||||
</div>
|
||||
<div className="list row">
|
||||
<div className="col-md-6 col-sm-12 mb-4">
|
||||
{driverLabel}'s Signature: {signature && <span className="mb-2 me-4">{signature && <img width="100px" src={`data:image/jpg;base64, ${signature}`}/>}</span>} {currentRoute?.end_time && <span>{new Date(currentRoute?.end_time).toLocaleTimeString('en-US', {hour12: false, hour: '2-digit', minute: '2-digit'})}</span>}
|
||||
|
||||
</div>
|
||||
<div className="col-md-6 col-sm-12 mb-4">
|
||||
{/* Manager's Signature (经理签字): {directorSignature && <span className="mb-2">{directorSignature && <img width="100px" src={`data:image/jpg;base64, ${directorSignature}`}/>}</span>} */}
|
||||
Manager's Signature: <img width="100px" src="/images/signature.jpeg"/>
|
||||
</div>
|
||||
</div>
|
||||
{/* Main Customer Table */}
|
||||
<div className="list row">
|
||||
<div className="col-md-12 mb-4">
|
||||
<table className="personnel-info-table route-report-table" style={{
|
||||
tableLayout: 'auto',
|
||||
width: '100%'
|
||||
}}>
|
||||
<table className="personnel-info-table route-report-table" style={{ tableLayout: 'auto', width: '100%' }}>
|
||||
<thead>
|
||||
{/* English Header Row */}
|
||||
<tr>
|
||||
<th style={{ minWidth: 'auto !important', width: 'auto !important', maxWidth: 'none !important' }}>No.</th>
|
||||
<th style={{ minWidth: 'auto !important', width: 'auto !important', maxWidth: 'none !important' }}>Name</th>
|
||||
<th style={{ minWidth: 'auto !important', width: 'auto !important', maxWidth: 'none !important' }}>Phone</th>
|
||||
<th style={{ minWidth: 'auto !important', width: 'auto !important', maxWidth: 'none !important' }}>Address</th>
|
||||
<th style={{ minWidth: 'auto !important', width: 'auto !important', maxWidth: 'none !important' }}>Unit</th>
|
||||
<th colSpan={2} style={{ minWidth: 'auto !important', width: 'auto !important', maxWidth: 'none !important' }}>Pick-Up</th>
|
||||
<th style={{ minWidth: 'auto !important', width: 'auto !important', maxWidth: 'none !important' }}>Pick-Up</th>
|
||||
<th style={{ minWidth: 'auto !important', width: 'auto !important', maxWidth: 'none !important' }}>Arrival</th>
|
||||
<th style={{ minWidth: 'auto !important', width: 'auto !important', maxWidth: 'none !important' }}>Departure</th>
|
||||
<th style={{ minWidth: 'auto !important', width: 'auto !important', maxWidth: 'none !important' }}>Drop-Off</th>
|
||||
<th style={{ minWidth: 'auto !important', width: 'auto !important', maxWidth: 'none !important' }}>Notice</th>
|
||||
<th rowSpan={2} style={{ verticalAlign: 'middle' }}>No.<span className="bilingual-label">序号</span></th>
|
||||
<th rowSpan={2} style={{ verticalAlign: 'middle' }}>Name<span className="bilingual-label">姓名</span></th>
|
||||
<th rowSpan={2} style={{ verticalAlign: 'middle' }}>Phone<span className="bilingual-label">联系电话</span></th>
|
||||
<th rowSpan={2} style={{ verticalAlign: 'middle' }}>Address<span className="bilingual-label">地址</span></th>
|
||||
<th rowSpan={2} style={{ verticalAlign: 'middle' }}>Unit<span className="bilingual-label">房间号</span></th>
|
||||
<th colSpan={2} style={{ textAlign: 'center' }}>Attendance<span className="bilingual-label">出勤</span></th>
|
||||
<th rowSpan={2} style={{ verticalAlign: 'middle' }}>Pick-Up<span className="bilingual-label">接到时间</span></th>
|
||||
<th rowSpan={2} style={{ verticalAlign: 'middle' }}>Arrival<span className="bilingual-label">抵达中心</span></th>
|
||||
<th rowSpan={2} style={{ verticalAlign: 'middle' }}>Departure<span className="bilingual-label">离开中心</span></th>
|
||||
<th rowSpan={2} style={{ verticalAlign: 'middle' }}>Drop-Off<span className="bilingual-label">送达时间</span></th>
|
||||
<th rowSpan={2} style={{ verticalAlign: 'middle' }}>Notice<span className="bilingual-label">备注</span></th>
|
||||
</tr>
|
||||
<tr>
|
||||
<th style={{ minWidth: 'auto !important', width: 'auto !important', maxWidth: 'none !important' }}></th>
|
||||
<th style={{ minWidth: 'auto !important', width: 'auto !important', maxWidth: 'none !important' }}></th>
|
||||
<th style={{ minWidth: 'auto !important', width: 'auto !important', maxWidth: 'none !important' }}></th>
|
||||
<th style={{ minWidth: 'auto !important', width: 'auto !important', maxWidth: 'none !important' }}></th>
|
||||
<th style={{ minWidth: 'auto !important', width: 'auto !important', maxWidth: 'none !important' }}></th>
|
||||
<th style={{ minWidth: 'auto !important', width: 'auto !important', maxWidth: 'none !important' }}>Y</th>
|
||||
<th style={{ minWidth: 'auto !important', width: 'auto !important', maxWidth: 'none !important' }}>N</th>
|
||||
<th style={{ minWidth: 'auto !important', width: 'auto !important', maxWidth: 'none !important' }}></th>
|
||||
<th style={{ minWidth: 'auto !important', width: 'auto !important', maxWidth: 'none !important' }}></th>
|
||||
<th style={{ minWidth: 'auto !important', width: 'auto !important', maxWidth: 'none !important' }}></th>
|
||||
<th style={{ minWidth: 'auto !important', width: 'auto !important', maxWidth: 'none !important' }}></th>
|
||||
<th style={{ minWidth: 'auto !important', width: 'auto !important', maxWidth: 'none !important' }}></th>
|
||||
<th style={{ textAlign: 'center' }}>Y</th>
|
||||
<th style={{ textAlign: 'center' }}>N</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{
|
||||
currentRoute?.route_customer_list?.map((customer, index) => {
|
||||
const relativeRouteCustomer = getInboundOrOutboundRouteCustomer(customer?.customer_id);
|
||||
// console.log('cust', relativeRouteCustomer);
|
||||
const otherRouteWithThisCustomer = getOtherRouteWithThisCustomer(customer?.customer_id);
|
||||
// console.log('other', otherRouteWithThisCustomer)
|
||||
const customerInOtherRoute = otherRouteWithThisCustomer?.route_customer_list?.find(cust => cust?.customer_id === customer?.customer_id);
|
||||
// console.log('other user', customerInOtherRoute)
|
||||
const otherOutboundRoute = getOtherOutboundRouteWithThisCustomer(customer?.customer_id);
|
||||
return (<tr>
|
||||
<td>{index+1}</td>
|
||||
|
||||
const isAttending = ![PERSONAL_ROUTE_STATUS.SCHEDULED_ABSENT, PERSONAL_ROUTE_STATUS.UNEXPECTED_ABSENT].includes(relativeRouteCustomer?.customer_route_status) &&
|
||||
![PERSONAL_ROUTE_STATUS.SCHEDULED_ABSENT, PERSONAL_ROUTE_STATUS.UNEXPECTED_ABSENT].includes(customer?.customer_route_status);
|
||||
|
||||
return (
|
||||
<tr key={index}>
|
||||
<td style={{ textAlign: 'center' }}>{index + 1}</td>
|
||||
<td>{customer?.customer_name}</td>
|
||||
<td>{customer?.customer_phone}</td>
|
||||
<td>{customer?.customer_address_override || customer?.customer_address}</td>
|
||||
<td></td>
|
||||
<td>{ ![PERSONAL_ROUTE_STATUS.SCHEDULED_ABSENT, PERSONAL_ROUTE_STATUS.UNEXPECTED_ABSENT].includes(relativeRouteCustomer?.customer_route_status) && ![PERSONAL_ROUTE_STATUS.SCHEDULED_ABSENT, PERSONAL_ROUTE_STATUS.UNEXPECTED_ABSENT].includes(customer?.customer_route_status) ? "✓" : ''}</td>
|
||||
<td>{![PERSONAL_ROUTE_STATUS.SCHEDULED_ABSENT, PERSONAL_ROUTE_STATUS.UNEXPECTED_ABSENT].includes(relativeRouteCustomer?.customer_route_status) && ![PERSONAL_ROUTE_STATUS.SCHEDULED_ABSENT, PERSONAL_ROUTE_STATUS.UNEXPECTED_ABSENT].includes(customer?.customer_route_status) ? "" : 'x'}</td>
|
||||
<td>{customer?.customer_pickup_time ? new Date(customer?.customer_pickup_time).toLocaleTimeString('en-US', {hour12: false, hour: '2-digit', minute: '2-digit', second: '2-digit'}) : (relativeRouteCustomer?.customer_pickup_time? new Date(relativeRouteCustomer?.customer_pickup_time).toLocaleTimeString('en-US', {hour12: false, hour: '2-digit', minute: '2-digit', second: '2-digit'}) : (customerInOtherRoute?.customer_pickup_time ? new Date(customerInOtherRoute?.customer_pickup_time)?.toLocaleTimeString('en-US', {hour12: false, hour: '2-digit', minute: '2-digit', second: '2-digit'}): ''))}</td>
|
||||
<td>{customer?.customer_enter_center_time ? new Date(customer?.customer_enter_center_time).toLocaleTimeString('en-US', {hour12: false, hour: '2-digit', minute: '2-digit', second: '2-digit'}) : (relativeRouteCustomer?.customer_enter_center_time ? new Date(relativeRouteCustomer?.customer_enter_center_time).toLocaleTimeString('en-US', {hour12: false, hour: '2-digit', minute: '2-digit', second: '2-digit'}) : (customerInOtherRoute?.customer_enter_center_time ? new Date(customerInOtherRoute?.customer_enter_center_time)?.toLocaleTimeString('en-US', {hour12: false, hour: '2-digit', minute: '2-digit', second: '2-digit'}): ''))}</td>
|
||||
<td>{customer?.customer_leave_center_time && customer?.customer_route_status !== PERSONAL_ROUTE_STATUS.SKIP_DROPOFF ? new Date(customer?.customer_leave_center_time).toLocaleTimeString('en-US', {hour12: false, hour: '2-digit', minute: '2-digit', second: '2-digit'}) : (relativeRouteCustomer?.customer_leave_center_time && relativeRouteCustomer?.customer_route_status !== PERSONAL_ROUTE_STATUS.SKIP_DROPOFF ? new Date(relativeRouteCustomer?.customer_leave_center_time).toLocaleTimeString('en-US', {hour12: false, hour: '2-digit', minute: '2-digit', second: '2-digit'}) : (customerInOtherRoute?.customer_leave_center_time ? new Date(customerInOtherRoute?.customer_leave_center_time)?.toLocaleTimeString('en-US', {hour12: false, hour: '2-digit', minute: '2-digit', second: '2-digit'}): ''))}</td>
|
||||
<td>{customer?.customer_dropoff_time && customer?.customer_route_status !== PERSONAL_ROUTE_STATUS.SKIP_DROPOFF ? new Date(customer?.customer_dropoff_time).toLocaleTimeString('en-US', {hour12: false, hour: '2-digit', minute: '2-digit', second: '2-digit'}) : (relativeRouteCustomer?.customer_dropoff_time && relativeRouteCustomer?.customer_route_status !== PERSONAL_ROUTE_STATUS.SKIP_DROPOFF ? new Date(relativeRouteCustomer?.customer_dropoff_time).toLocaleTimeString('en-US', {hour12: false, hour: '2-digit', minute: '2-digit', second: '2-digit'}) : (customerInOtherRoute?.customer_dropoff_time ? new Date(customerInOtherRoute?.customer_dropoff_time)?.toLocaleTimeString('en-US', {hour12: false, hour: '2-digit', minute: '2-digit', second: '2-digit'}): ''))}</td>
|
||||
<td>
|
||||
{customer?.customer_type !== CUSTOMER_TYPE.MEMBER && <div>{ CUSTOMER_TYPE_TEXT[customer?.customer_type]}</div> }
|
||||
{ !relativeRouteCustomer && otherRouteWithThisCustomer && (
|
||||
<div>
|
||||
{`${currentRoute?.type === 'inbound' ? 'Switch to Route ' : 'Switch from Route '} ${otherRouteWithThisCustomer?.name}`}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
{
|
||||
customer?.customer_route_status === PERSONAL_ROUTE_STATUS.SKIP_DROPOFF && otherOutboundRoute && (
|
||||
<div>
|
||||
{`Switch to Route ${otherOutboundRoute?.name}`}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
<td style={{ textAlign: 'center' }}>{isAttending ? '✓' : ''}</td>
|
||||
<td style={{ textAlign: 'center' }}>{!isAttending ? '✗' : ''}</td>
|
||||
<td style={{ textAlign: 'center' }}>
|
||||
{customer?.customer_pickup_time
|
||||
? new Date(customer?.customer_pickup_time).toLocaleTimeString('en-US', {hour12: false, hour: '2-digit', minute: '2-digit'})
|
||||
: (relativeRouteCustomer?.customer_pickup_time
|
||||
? new Date(relativeRouteCustomer?.customer_pickup_time).toLocaleTimeString('en-US', {hour12: false, hour: '2-digit', minute: '2-digit'})
|
||||
: (customerInOtherRoute?.customer_pickup_time
|
||||
? new Date(customerInOtherRoute?.customer_pickup_time)?.toLocaleTimeString('en-US', {hour12: false, hour: '2-digit', minute: '2-digit'})
|
||||
: ''))}
|
||||
</td>
|
||||
</tr>)
|
||||
<td style={{ textAlign: 'center' }}>
|
||||
{customer?.customer_enter_center_time
|
||||
? new Date(customer?.customer_enter_center_time).toLocaleTimeString('en-US', {hour12: false, hour: '2-digit', minute: '2-digit'})
|
||||
: (relativeRouteCustomer?.customer_enter_center_time
|
||||
? new Date(relativeRouteCustomer?.customer_enter_center_time).toLocaleTimeString('en-US', {hour12: false, hour: '2-digit', minute: '2-digit'})
|
||||
: (customerInOtherRoute?.customer_enter_center_time
|
||||
? new Date(customerInOtherRoute?.customer_enter_center_time)?.toLocaleTimeString('en-US', {hour12: false, hour: '2-digit', minute: '2-digit'})
|
||||
: ''))}
|
||||
</td>
|
||||
<td style={{ textAlign: 'center' }}>
|
||||
{customer?.customer_leave_center_time && customer?.customer_route_status !== PERSONAL_ROUTE_STATUS.SKIP_DROPOFF
|
||||
? new Date(customer?.customer_leave_center_time).toLocaleTimeString('en-US', {hour12: false, hour: '2-digit', minute: '2-digit'})
|
||||
: (relativeRouteCustomer?.customer_leave_center_time && relativeRouteCustomer?.customer_route_status !== PERSONAL_ROUTE_STATUS.SKIP_DROPOFF
|
||||
? new Date(relativeRouteCustomer?.customer_leave_center_time).toLocaleTimeString('en-US', {hour12: false, hour: '2-digit', minute: '2-digit'})
|
||||
: (customerInOtherRoute?.customer_leave_center_time
|
||||
? new Date(customerInOtherRoute?.customer_leave_center_time)?.toLocaleTimeString('en-US', {hour12: false, hour: '2-digit', minute: '2-digit'})
|
||||
: ''))}
|
||||
</td>
|
||||
<td style={{ textAlign: 'center' }}>
|
||||
{customer?.customer_dropoff_time && customer?.customer_route_status !== PERSONAL_ROUTE_STATUS.SKIP_DROPOFF
|
||||
? new Date(customer?.customer_dropoff_time).toLocaleTimeString('en-US', {hour12: false, hour: '2-digit', minute: '2-digit'})
|
||||
: (relativeRouteCustomer?.customer_dropoff_time && relativeRouteCustomer?.customer_route_status !== PERSONAL_ROUTE_STATUS.SKIP_DROPOFF
|
||||
? new Date(relativeRouteCustomer?.customer_dropoff_time).toLocaleTimeString('en-US', {hour12: false, hour: '2-digit', minute: '2-digit'})
|
||||
: (customerInOtherRoute?.customer_dropoff_time
|
||||
? new Date(customerInOtherRoute?.customer_dropoff_time)?.toLocaleTimeString('en-US', {hour12: false, hour: '2-digit', minute: '2-digit'})
|
||||
: ''))}
|
||||
</td>
|
||||
<td>
|
||||
{customer?.customer_type !== CUSTOMER_TYPE.MEMBER && <div>{CUSTOMER_TYPE_TEXT[customer?.customer_type]}</div>}
|
||||
{!relativeRouteCustomer && otherRouteWithThisCustomer && (
|
||||
<div>{`${currentRoute?.type === 'inbound' ? 'Switch to Route ' : 'Switch from Route '} ${otherRouteWithThisCustomer?.name}`}</div>
|
||||
)}
|
||||
{customer?.customer_route_status === PERSONAL_ROUTE_STATUS.SKIP_DROPOFF && otherOutboundRoute && (
|
||||
<div>{`Switch to Route ${otherOutboundRoute?.name}`}</div>
|
||||
)}
|
||||
{/* Rest stop check for inbound */}
|
||||
{currentRoute?.type === 'inbound' && (() => {
|
||||
const pickupTime = customer?.customer_pickup_time || relativeRouteCustomer?.customer_pickup_time || customerInOtherRoute?.customer_pickup_time;
|
||||
const enterTime = customer?.customer_enter_center_time || relativeRouteCustomer?.customer_enter_center_time || customerInOtherRoute?.customer_enter_center_time;
|
||||
if (pickupTime && enterTime) {
|
||||
const diffMs = new Date(enterTime) - new Date(pickupTime);
|
||||
const diffHours = diffMs / (1000 * 60 * 60);
|
||||
if (diffHours > 1) return <div>Rest Stop</div>;
|
||||
}
|
||||
return null;
|
||||
})()}
|
||||
{/* Rest stop check for outbound */}
|
||||
{currentRoute?.type === 'outbound' && (() => {
|
||||
const leaveTime = customer?.customer_leave_center_time || relativeRouteCustomer?.customer_leave_center_time || customerInOtherRoute?.customer_leave_center_time;
|
||||
const dropoffTime = customer?.customer_dropoff_time || relativeRouteCustomer?.customer_dropoff_time || customerInOtherRoute?.customer_dropoff_time;
|
||||
if (leaveTime && dropoffTime) {
|
||||
const diffMs = new Date(dropoffTime) - new Date(leaveTime);
|
||||
const diffHours = diffMs / (1000 * 60 * 60);
|
||||
if (diffHours > 1) return <div>Rest Stop</div>;
|
||||
}
|
||||
return null;
|
||||
})()}
|
||||
</td>
|
||||
</tr>
|
||||
);
|
||||
})
|
||||
}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Vehicle Inspection Checklist */}
|
||||
<div className="list row">
|
||||
<div className="col-md-12">
|
||||
<table className="inspection-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Item</th>
|
||||
<th>Inspected</th>
|
||||
<th>Item</th>
|
||||
<th>Inspected</th>
|
||||
<th>Item</th>
|
||||
<th>Inspected</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>Tires/Rims<span className="bilingual-label">车胎/车圈</span></td>
|
||||
<td style={{ textAlign: 'center' }}>{getChecklistStatus('tires') || getChecklistStatus('Tires')}</td>
|
||||
<td>Door(s)<span className="bilingual-label">车门</span></td>
|
||||
<td style={{ textAlign: 'center' }}>{getChecklistStatus('door') || getChecklistStatus('Door')}</td>
|
||||
<td>Seat/Seat-belt<span className="bilingual-label">座位/安全带</span></td>
|
||||
<td style={{ textAlign: 'center' }}>{getChecklistStatus('seat') || getChecklistStatus('Seat')}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Mirrors/Windows<span className="bilingual-label">镜子/窗户</span></td>
|
||||
<td style={{ textAlign: 'center' }}>{getChecklistStatus('mirror') || getChecklistStatus('Mirror') || getChecklistStatus('window') || getChecklistStatus('Window')}</td>
|
||||
<td>Light(s)<span className="bilingual-label">车灯</span></td>
|
||||
<td style={{ textAlign: 'center' }}>{getChecklistStatus('light') || getChecklistStatus('Light')}</td>
|
||||
<td>Lift (wheelchair)<span className="bilingual-label">轮椅起降器</span></td>
|
||||
<td style={{ textAlign: 'center' }}>{getChecklistStatus('lift') || getChecklistStatus('Lift')}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Emergency Folder<span className="bilingual-label">紧急情况信息夹</span></td>
|
||||
<td style={{ textAlign: 'center' }}>{getChecklistStatus('emergency') || getChecklistStatus('Emergency')}</td>
|
||||
<td>Tie Down<span className="bilingual-label">固定绳索</span></td>
|
||||
<td style={{ textAlign: 'center' }}>{getChecklistStatus('tie') || getChecklistStatus('Tie')}</td>
|
||||
<td>Heater/AC<span className="bilingual-label">暖气/冷气</span></td>
|
||||
<td style={{ textAlign: 'center' }}>{getChecklistStatus('heater') || getChecklistStatus('Heater') || getChecklistStatus('ac') || getChecklistStatus('AC')}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Fire Extinguisher<span className="bilingual-label">灭火器</span></td>
|
||||
<td style={{ textAlign: 'center' }}>{getChecklistStatus('fire') || getChecklistStatus('Fire') || getChecklistStatus('extinguisher')}</td>
|
||||
<td>Body<span className="bilingual-label">车身</span></td>
|
||||
<td style={{ textAlign: 'center' }}>{getChecklistStatus('body') || getChecklistStatus('Body')}</td>
|
||||
<td>Coolant<span className="bilingual-label">冷冻液</span></td>
|
||||
<td style={{ textAlign: 'center' }}>{getChecklistStatus('coolant') || getChecklistStatus('Coolant')}</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
@ -89,10 +89,13 @@ const RouteView = () => {
|
||||
<>
|
||||
<div className="list row mb-4">
|
||||
<Breadcrumb>
|
||||
<Breadcrumb.Item>Transportation</Breadcrumb.Item>
|
||||
<Breadcrumb.Item active>
|
||||
<Breadcrumb.Item href="/">Transportation</Breadcrumb.Item>
|
||||
<Breadcrumb.Item href="/trans-routes/dashboard">
|
||||
Transportation Routes
|
||||
</Breadcrumb.Item>
|
||||
<Breadcrumb.Item active>
|
||||
View Route
|
||||
</Breadcrumb.Item>
|
||||
</Breadcrumb>
|
||||
<div className="col-md-12 text-primary">
|
||||
<h4>
|
||||
@ -100,7 +103,7 @@ const RouteView = () => {
|
||||
</h4>
|
||||
</div>
|
||||
</div>
|
||||
<div className="app-main-content-list-container">
|
||||
<div className="app-main-content-list-container form-page">
|
||||
<div className="app-main-content-list-func-container">
|
||||
<Tabs defaultActiveKey="routeOverview" id="route-view-tab">
|
||||
<Tab eventKey="routeOverview" title="Route Information">
|
||||
|
||||
@ -1,15 +1,12 @@
|
||||
import React, { useState, useEffect } from "react";
|
||||
import { useSelector, useDispatch } from "react-redux";
|
||||
import { useNavigate, useSearchParams } from "react-router-dom";
|
||||
import { selectInboundRoutes, selectTomorrowAllRoutes, selectTomorrowInboundRoutes, selectTomorrowOutboundRoutes, selectHistoryInboundRoutes, selectHistoryRoutes, selectHistoryOutboundRoutes, selectOutboundRoutes, selectAllBreakfastRecords, selectAllLunchRecords, selectAllSnackRecords, selectAllRoutes, selectAllActiveVehicles, selectAllActiveDrivers, transRoutesSlice } from "./../../store";
|
||||
import { selectInboundRoutes, selectTomorrowAllRoutes, selectTomorrowInboundRoutes, selectTomorrowOutboundRoutes, selectHistoryInboundRoutes, selectHistoryRoutes, selectHistoryOutboundRoutes, selectOutboundRoutes, selectAllRoutes, selectAllActiveVehicles, selectAllActiveDrivers, transRoutesSlice } from "./../../store";
|
||||
import RoutesSection from "./RoutesSection";
|
||||
import PersonnelSection from "./PersonnelSection";
|
||||
import { AuthService, CustomerService, TransRoutesService, DriverService, EventsService, SignatureRequestService } from "../../services";
|
||||
import { AuthService, CustomerService, TransRoutesService, DriverService, EventsService, SignatureRequestService, DailyRoutesTemplateService } from "../../services";
|
||||
import { PERSONAL_ROUTE_STATUS, ROUTE_STATUS, reportRootUrl, CUSTOMER_TYPE_TEXT, PERSONAL_ROUTE_STATUS_TEXT, PICKUP_STATUS, PICKUP_STATUS_TEXT, REPORT_TYPE } from "../../shared";
|
||||
import BreakfastSection from './BreakfastSection';
|
||||
import moment from 'moment';
|
||||
import LunchSection from "./LunchSection";
|
||||
import SnackSection from "./SnackSection";
|
||||
import DatePicker from "react-datepicker";
|
||||
import { CalendarWeek, ClockHistory, Copy, Download, Eraser, Plus, Clock, Send, Filter, CalendarCheck, Check } from "react-bootstrap-icons";
|
||||
import { Breadcrumb, Tabs, Tab, Dropdown, Spinner, Modal, Button } from "react-bootstrap";
|
||||
@ -19,7 +16,7 @@ import RouteCustomerTable from "./RouteCustomerTable";
|
||||
const RoutesDashboard = () => {
|
||||
const navigate = useNavigate();
|
||||
const dispatch = useDispatch();
|
||||
const { fetchAllRoutes, createRoute, fetchAllBreakfastRecords, fetchAllLunchRecords, fetchAllSnackRecords, fetchAllHistoryRoutes, fetchAllTomorrowRoutes, updateRoute } = transRoutesSlice.actions;
|
||||
const { fetchAllRoutes, createRoute, fetchAllHistoryRoutes, fetchAllTomorrowRoutes, updateRoute } = transRoutesSlice.actions;
|
||||
const inboundRoutes = useSelector(selectInboundRoutes);
|
||||
const outboundRoutes = useSelector(selectOutboundRoutes);
|
||||
const tmrInboundRoutes = useSelector(selectTomorrowInboundRoutes);
|
||||
@ -31,10 +28,8 @@ const RoutesDashboard = () => {
|
||||
const allTomorrowRoutes = useSelector(selectTomorrowAllRoutes);
|
||||
const drivers = useSelector(selectAllActiveDrivers);
|
||||
const vehicles = useSelector(selectAllActiveVehicles);
|
||||
const breakfastRecords = useSelector(selectAllBreakfastRecords);
|
||||
const lunchRecords = useSelector(selectAllLunchRecords);
|
||||
const snackRecords = useSelector(selectAllSnackRecords);
|
||||
const [showSyncCustomersLoading, setShowSyncCustomersLoading] = useState(false);
|
||||
const [hasAutoSynced, setHasAutoSynced] = useState(false);
|
||||
const [keyword, setKeyword] = useState('');
|
||||
|
||||
const [directorSignature, setDirectorSignature] = useState(undefined);
|
||||
@ -76,6 +71,16 @@ const RoutesDashboard = () => {
|
||||
const [customerTableId, setCustomerTableId] = useState('');
|
||||
const [routeTypeFilter, setRouteTypeFilter] = useState('');
|
||||
|
||||
// Save template modal state
|
||||
const [showSaveTemplateModal, setShowSaveTemplateModal] = useState(false);
|
||||
const [templateName, setTemplateName] = useState('');
|
||||
const [savingTemplate, setSavingTemplate] = useState(false);
|
||||
|
||||
// Apply template state
|
||||
const [templates, setTemplates] = useState([]);
|
||||
const [selectedTemplateId, setSelectedTemplateId] = useState('');
|
||||
const [applyingTemplate, setApplyingTemplate] = useState(false);
|
||||
|
||||
|
||||
const params = new URLSearchParams(window.location.search);
|
||||
const scheduleDate = params.get('dateSchedule');
|
||||
@ -152,6 +157,13 @@ const RoutesDashboard = () => {
|
||||
setDirectorSignature(data?.data)
|
||||
}
|
||||
});
|
||||
|
||||
// Fetch all daily routes templates
|
||||
DailyRoutesTemplateService.getAll().then((response) => {
|
||||
setTemplates(response.data || []);
|
||||
}).catch(err => {
|
||||
console.error('Error fetching templates:', err);
|
||||
});
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
@ -188,6 +200,12 @@ const RoutesDashboard = () => {
|
||||
setRoutesForShowing(allRoutes);
|
||||
setRoutesInboundForShowing(inboundRoutes);
|
||||
setRoutesOutboundForShowing(processRoutesForAbsentCustomers(inboundRoutes, outboundRoutes));
|
||||
|
||||
// Auto-sync customer info for today's routes when they first load (only once)
|
||||
if (allRoutes && allRoutes.length > 0 && !hasAutoSynced && !showSyncCustomersLoading) {
|
||||
setHasAutoSynced(true);
|
||||
syncCustomersInfo();
|
||||
}
|
||||
} else {
|
||||
if (dateSelected > new Date()) {
|
||||
setRoutesForShowing(allTomorrowRoutes);
|
||||
@ -240,6 +258,7 @@ const RoutesDashboard = () => {
|
||||
setDateSelected(new Date());
|
||||
setOriginDateSelected(undefined);
|
||||
setTargetedDateSelected(undefined);
|
||||
setHasAutoSynced(false); // Reset auto-sync flag when changing tabs
|
||||
// setSelectedDriver(undefined);
|
||||
}
|
||||
|
||||
@ -537,6 +556,12 @@ const RoutesDashboard = () => {
|
||||
}
|
||||
setTimeout(dispatch(fetchAllRoutes()), 10000);
|
||||
}
|
||||
|
||||
const handleCleanAllRoutesStatus = () => {
|
||||
if (window.confirm('Are you sure you want to do this? This cannot be undone.')) {
|
||||
cleanAllRoutesStatus();
|
||||
}
|
||||
}
|
||||
const syncCustomersInfo = () => {
|
||||
setShowSyncCustomersLoading(true);
|
||||
CustomerService.getAllCustomers().then(data => {
|
||||
@ -772,6 +797,11 @@ const RoutesDashboard = () => {
|
||||
|
||||
const customMenuDate = React.forwardRef(
|
||||
({ children, style, className, 'aria-labelledby': labeledBy }, ref) => {
|
||||
const handleDateChange = (v) => {
|
||||
setDateSelected(v);
|
||||
setShowDateDropdown(false);
|
||||
};
|
||||
|
||||
return (
|
||||
<div
|
||||
ref={ref}
|
||||
@ -781,14 +811,8 @@ const RoutesDashboard = () => {
|
||||
>
|
||||
<div className="app-main-content-fields-section margin-sm dropdown-container">
|
||||
<div className="me-4">
|
||||
<div className="field-label">Select Date to Start</div>
|
||||
<DatePicker selected={dateSelected} onChange={(v) => setDateSelected(v)} />
|
||||
</div>
|
||||
</div>
|
||||
<div className="list row">
|
||||
<div className="col-md-12">
|
||||
<button className="btn btn-default btn-sm float-right" onClick={() => cleanDate()}> Cancel </button>
|
||||
<button className="btn btn-primary btn-sm float-right" onClick={() => FilterAndClose()}> Filter </button>
|
||||
<div className="field-label">Select Date to View Report</div>
|
||||
<DatePicker selected={dateSelected} onChange={handleDateChange} />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@ -866,89 +890,163 @@ const RoutesDashboard = () => {
|
||||
setShowSignatureRequestLoading(false);
|
||||
}
|
||||
|
||||
const confimHasBreakfast = (customer) => {
|
||||
TransRoutesService.createBreakfastRecords({
|
||||
customer_id: customer?.customer_id,
|
||||
customer_name: customer?.customer_name,
|
||||
has_breakfast: true,
|
||||
date: moment(new Date()).format('MM/DD/YYYY'),
|
||||
create_by: localStorage.getItem('user') && JSON.parse(localStorage.getItem('user'))?.name,
|
||||
create_date: new Date(),
|
||||
edit_history: [{
|
||||
employee: localStorage.getItem('user') && JSON.parse(localStorage.getItem('user'))?.name,
|
||||
date: new Date()
|
||||
}]
|
||||
}).then(() => {
|
||||
dispatch(fetchAllBreakfastRecords());
|
||||
})
|
||||
|
||||
// Save template modal handlers
|
||||
const openSaveTemplateModal = () => {
|
||||
setShowSaveTemplateModal(true);
|
||||
setTemplateName('');
|
||||
};
|
||||
|
||||
const closeSaveTemplateModal = () => {
|
||||
setShowSaveTemplateModal(false);
|
||||
setTemplateName('');
|
||||
};
|
||||
|
||||
const saveRoutesAsTemplate = () => {
|
||||
if (!templateName || templateName.trim() === '') {
|
||||
alert('Please enter a template name');
|
||||
return;
|
||||
}
|
||||
|
||||
const confirmHasLunch = (customer) => {
|
||||
TransRoutesService.createLunchRecords({
|
||||
customer_id: customer?.customer_id,
|
||||
customer_name: customer?.customer_name,
|
||||
has_lunch: true,
|
||||
date: moment(new Date()).format('MM/DD/YYYY'),
|
||||
create_by: localStorage.getItem('user') && JSON.parse(localStorage.getItem('user'))?.name,
|
||||
create_date: new Date(),
|
||||
edit_history: [{
|
||||
employee: localStorage.getItem('user') && JSON.parse(localStorage.getItem('user'))?.name,
|
||||
date: new Date()
|
||||
}]
|
||||
}).then(() => {
|
||||
dispatch(fetchAllLunchRecords());
|
||||
setSavingTemplate(true);
|
||||
|
||||
// Get the current date string in MM/DD/YYYY format
|
||||
const templateDate = getDateString(dateSelected);
|
||||
|
||||
// Get current user from localStorage
|
||||
const currentUser = localStorage.getItem('user') ? JSON.parse(localStorage.getItem('user'))?.name : '';
|
||||
|
||||
// Clean the routes data - remove all date fields and status
|
||||
const cleanedRoutes = routesForShowing.map(route => {
|
||||
// Clean customer list - set all date fields to null and reset dynamic fields to undefined
|
||||
const cleanedCustomerList = route.route_customer_list?.map(customer => ({
|
||||
...customer,
|
||||
customer_enter_center_time: null,
|
||||
customer_leave_center_time: null,
|
||||
customer_pickup_time: null,
|
||||
customer_dropoff_time: null,
|
||||
// customer_estimated_pickup_time: undefined,
|
||||
// customer_estimated_dropoff_time: undefined,
|
||||
// customer_pickup_status: undefined,
|
||||
// customer_route_status: undefined,
|
||||
// customer_transfer_to_route: undefined,
|
||||
// customer_address_override: undefined
|
||||
})) || [];
|
||||
|
||||
// Return cleaned route
|
||||
return {
|
||||
name: route.name,
|
||||
schedule_date: route.schedule_date,
|
||||
vehicle: route.vehicle,
|
||||
status: [], // Empty array
|
||||
driver: route.driver,
|
||||
type: route.type,
|
||||
start_mileage: route.start_mileage,
|
||||
end_mileage: route.end_mileage,
|
||||
start_time: null,
|
||||
end_time: null,
|
||||
estimated_start_time: null,
|
||||
route_customer_list: cleanedCustomerList,
|
||||
checklist_result: route.checklist_result || []
|
||||
};
|
||||
});
|
||||
|
||||
// Create the template payload
|
||||
const templatePayload = {
|
||||
name: templateName,
|
||||
template_date: templateDate,
|
||||
routes: cleanedRoutes,
|
||||
create_by: currentUser
|
||||
};
|
||||
|
||||
// Call the service to create the template
|
||||
DailyRoutesTemplateService.createNewDailyRoutesTemplate(templatePayload)
|
||||
.then(() => {
|
||||
setSavingTemplate(false);
|
||||
closeSaveTemplateModal();
|
||||
setSuccessMessage(`Template "${templateName}" saved successfully!`);
|
||||
setTimeout(() => setSuccessMessage(undefined), 5000);
|
||||
// Refresh templates list
|
||||
DailyRoutesTemplateService.getAll().then((response) => {
|
||||
setTemplates(response.data || []);
|
||||
});
|
||||
})
|
||||
.catch(err => {
|
||||
setSavingTemplate(false);
|
||||
setErrorMessage(err.message || 'Failed to save template');
|
||||
setTimeout(() => setErrorMessage(undefined), 5000);
|
||||
});
|
||||
};
|
||||
|
||||
// Apply template handler
|
||||
const applyTemplate = () => {
|
||||
if (!selectedTemplateId) {
|
||||
alert('Please select a template');
|
||||
return;
|
||||
}
|
||||
|
||||
const confirmHasSnack = (customer) => {
|
||||
TransRoutesService.createSnackRecords({
|
||||
customer_id: customer?.customer_id,
|
||||
customer_name: customer?.customer_name,
|
||||
has_snack: true,
|
||||
date: moment(new Date()).format('MM/DD/YYYY'),
|
||||
create_by: localStorage.getItem('user') && JSON.parse(localStorage.getItem('user'))?.name,
|
||||
create_date: new Date(),
|
||||
edit_history: [{
|
||||
employee: localStorage.getItem('user') && JSON.parse(localStorage.getItem('user'))?.name,
|
||||
date: new Date()
|
||||
}]
|
||||
}).then(() => {
|
||||
dispatch(fetchAllSnackRecords());
|
||||
})
|
||||
setApplyingTemplate(true);
|
||||
|
||||
// Find the selected template
|
||||
const selectedTemplate = templates.find(t => t.id === selectedTemplateId);
|
||||
if (!selectedTemplate) {
|
||||
alert('Template not found');
|
||||
setApplyingTemplate(false);
|
||||
return;
|
||||
}
|
||||
|
||||
const removeBreakfastRecord = (customer_id) => {
|
||||
const breakfast = breakfastRecords.find(b => b.customer_id === customer_id)?.id;
|
||||
TransRoutesService.deleteBreakfastRecords(breakfast).then(() => {
|
||||
dispatch(fetchAllBreakfastRecords());
|
||||
})
|
||||
// Get the target date in MM/DD/YYYY format
|
||||
const targetDate = getDateString(dateSelected);
|
||||
|
||||
// Create promises for all route creations
|
||||
const createPromises = selectedTemplate.routes.map(route => {
|
||||
const routeData = {
|
||||
...route,
|
||||
schedule_date: targetDate
|
||||
};
|
||||
return TransRoutesService.createNewRoute(routeData);
|
||||
});
|
||||
|
||||
// Execute all route creations
|
||||
Promise.all(createPromises)
|
||||
.then(() => {
|
||||
setApplyingTemplate(false);
|
||||
setSelectedTemplateId('');
|
||||
setSuccessMessage(`Template "${selectedTemplate.name}" applied successfully to ${targetDate}!`);
|
||||
setTimeout(() => setSuccessMessage(undefined), 5000);
|
||||
|
||||
// Refresh the routes based on current date selection
|
||||
const selectedDateString = getDateString(dateSelected);
|
||||
if (selectedDateString === getDateString(new Date())) {
|
||||
dispatch(fetchAllRoutes());
|
||||
} else {
|
||||
if (dateSelected > new Date()) {
|
||||
dispatch(fetchAllTomorrowRoutes({dateText: moment(dateSelected).format('MM/DD/YYYY')}));
|
||||
} else {
|
||||
dispatch(fetchAllHistoryRoutes({dateText: getDateString(dateSelected)}));
|
||||
}
|
||||
const removeLunchRecord = (customer_id) => {
|
||||
const lunch = lunchRecords.find(b => b.customer_id === customer_id)?.id;
|
||||
TransRoutesService.deleteLunchRecords(lunch).then(() => {
|
||||
dispatch(fetchAllLunchRecords());
|
||||
})
|
||||
}
|
||||
const removeSnackRecord = (customer_id) => {
|
||||
const snack = snackRecords.find(b => b.customer_id === customer_id)?.id;
|
||||
TransRoutesService.deleteSnackRecords(snack).then(() => {
|
||||
dispatch(fetchAllSnackRecords());
|
||||
})
|
||||
}
|
||||
.catch(err => {
|
||||
setApplyingTemplate(false);
|
||||
setErrorMessage(err.message || 'Failed to apply template');
|
||||
setTimeout(() => setErrorMessage(undefined), 5000);
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className="list row mb-4">
|
||||
<Breadcrumb>
|
||||
<Breadcrumb.Item>Transportation</Breadcrumb.Item>
|
||||
<Breadcrumb.Item href="/">Transportation</Breadcrumb.Item>
|
||||
<Breadcrumb.Item active>
|
||||
Transportation Routes
|
||||
</Breadcrumb.Item>
|
||||
</Breadcrumb>
|
||||
<div className="col-md-12 text-primary">
|
||||
<h4>
|
||||
All Routes
|
||||
All Routes - {moment(dateSelected).format('MM/DD/YYYY (dddd)')}
|
||||
</h4>
|
||||
</div>
|
||||
</div>
|
||||
@ -958,38 +1056,65 @@ const RoutesDashboard = () => {
|
||||
<Tab eventKey="allRoutesOverview" title="All Routes Overview">
|
||||
{(!dateSelected || getDateString(dateSelected) === getDateString(new Date())) && <div className="app-main-content-fields-section with-function">
|
||||
<button className="btn btn-primary me-2" onClick={() => directToSchedule()}><CalendarCheck size={16} className="me-2"></CalendarCheck> Schedule Tomorrow's Routes</button>
|
||||
<button className="btn btn-primary me-2" onClick={() => syncCustomersInfo()}><Clock size={16} className="me-2"></Clock> {showSyncCustomersLoading? <Spinner size={12}></Spinner> : `Sync Customers Data`}</button>
|
||||
<button className="btn btn-primary me-2" onClick={()=> cleanAllRoutesStatus()}><Eraser size={16} className="me-2"></Eraser > Clean Route Status</button>
|
||||
<button className="btn btn-primary me-2" onClick={() => goToHistoryPage()}><ClockHistory size={16} className="me-2"></ClockHistory> View History</button>
|
||||
{/* <button className="btn btn-primary me-2" onClick={() => syncCustomersInfo()}><Clock size={16} className="me-2"></Clock> {showSyncCustomersLoading? <Spinner size={12}></Spinner> : `Sync Customers Data`}</button> */}
|
||||
<button className="btn btn-primary me-2" onClick={()=> handleCleanAllRoutesStatus()}><Eraser size={16} className="me-2"></Eraser > Clean Route Status</button>
|
||||
{/* <button className="btn btn-primary me-2" onClick={() => goToHistoryPage()}><ClockHistory size={16} className="me-2"></ClockHistory> View History</button> */}
|
||||
<button className="btn btn-primary me-2" onClick={() => copyYesterdayRoutes()}><Copy size={16} className="me-2"></Copy>{showCopyDateLoading? <Spinner size={12}></Spinner> : `Copy Yesterday Routes`}</button>
|
||||
<button className="btn btn-primary" onClick={() => generateRouteReport()}><Download size={16} className="me-2"></Download>Export Route Report</button>
|
||||
</div>}
|
||||
{(dateSelected && dateSelected > new Date()) && <div className="app-main-content-fields-section with-function">
|
||||
<button type="button" className="btn btn-primary btn-sm me-2" disabled={copyDisabled || showCopyTodayLoading} onClick={() => copyTodayRoutesOver()}><Copy size={16} className="me-2"></Copy> {showCopyTodayLoading? 'Loading...' : `Copy Today's Routes Over`}</button>
|
||||
<button type="button" className="btn btn-primary btn-sm me-2" onClick={()=> validateSchedule()}><Check size={16} className="me-2"></Check> Validate and Finish Planning</button>
|
||||
{/* <button type="button" className="btn btn-primary btn-sm me-2" onClick={()=> validateSchedule()}><Check size={16} className="me-2"></Check> Validate and Finish Planning</button> */}
|
||||
<button type="button" className="btn btn-primary btn-sm" onClick={()=> triggerShowDeleteModal()}><Eraser size={16} className="me-2"></Eraser> Clean All Routes</button>
|
||||
</div>}
|
||||
{ (dateSelected <= new Date() || !dateSelected) && <div className="list row">
|
||||
{
|
||||
showCopyDateTargetLoading ? <><Spinner></Spinner></> : <>
|
||||
<div className="col-md-12 mb-4">
|
||||
{(routesInboundForShowing && routesInboundForShowing.length > 0) || (routesOutboundForShowing && routesOutboundForShowing.length > 0) ? (
|
||||
<div style={{display: 'flex', alignItems: 'center', gap: '10px'}}>
|
||||
<button className="btn btn-primary btn-sm" onClick={openSaveTemplateModal}>
|
||||
Save Today's Routes as a Template
|
||||
</button>
|
||||
{/* <button className="btn btn-secondary btn-sm" onClick={() => navigate('/trans-routes/daily-templates/list')}>
|
||||
View and Update Daily Route Templates
|
||||
</button> */}
|
||||
</div>
|
||||
) : (
|
||||
<div style={{display: 'flex', alignItems: 'center', gap: '10px'}}>
|
||||
<select
|
||||
className="form-select"
|
||||
style={{width: 'auto', display: 'inline-block'}}
|
||||
value={selectedTemplateId}
|
||||
onChange={(e) => setSelectedTemplateId(e.target.value)}
|
||||
disabled={applyingTemplate}
|
||||
>
|
||||
<option value="">Choose a daily template to apply to this day</option>
|
||||
{templates.map(template => (
|
||||
<option key={template.id} value={template.id}>
|
||||
{template.name} - {template.template_date}
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
<button
|
||||
className="btn btn-primary btn-sm"
|
||||
onClick={applyTemplate}
|
||||
disabled={!selectedTemplateId || applyingTemplate}
|
||||
>
|
||||
{applyingTemplate ? <><Spinner size="sm" className="me-1" />Applying...</> : 'Submit'}
|
||||
</button>
|
||||
{/* <button className="btn btn-secondary btn-sm" onClick={() => navigate('/trans-routes/daily-templates/list')}>
|
||||
View and Update Daily Route Templates
|
||||
</button> */}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
<div className="col-md-12 mb-4">
|
||||
<RoutesSection transRoutes={routesInboundForShowing} drivers={drivers} vehicles={vehicles} sectionName="Inbound Routes"/>
|
||||
</div>
|
||||
<div className="col-md-12 mb-4">
|
||||
<RoutesSection transRoutes={routesOutboundForShowing} drivers={drivers} vehicles={vehicles} sectionName="Outbound Routes"/>
|
||||
</div>
|
||||
{(!dateSelected || getDateString(dateSelected) === getDateString(new Date())) &&<div className="list row">
|
||||
<div className="col-md-4 col-sm-12 mb-4">
|
||||
<BreakfastSection transRoutes={allRoutes} breakfastRecords={breakfastRecords} confimHasBreakfast={confimHasBreakfast} removeBreakfastRecord={removeBreakfastRecord} sectionName={'Breakfast Info'}/>
|
||||
</div>
|
||||
<div className="col-md-4 col-sm-12 mb-4">
|
||||
<LunchSection transRoutes={allRoutes} lunchRecords={lunchRecords} confirmHasLunch={confirmHasLunch} removeLunchRecord={removeLunchRecord} sectionName={'Lunch Info'}/>
|
||||
</div>
|
||||
<div className="col-md-4 col-sm-12 mb-4">
|
||||
<SnackSection transRoutes={allRoutes} snackRecords={snackRecords} confirmHasSnack={confirmHasSnack} removeSnackRecord={removeSnackRecord} sectionName={'Snack Info'}/>
|
||||
</div>
|
||||
<hr/>
|
||||
</div>}
|
||||
</>
|
||||
}
|
||||
|
||||
@ -1009,6 +1134,45 @@ const RoutesDashboard = () => {
|
||||
<span className="visually-hidden">Loading...</span>
|
||||
</Spinner></div>}
|
||||
{!isLoading && <>
|
||||
<div className="col-md-12 mb-4">
|
||||
{(tmrInboundRoutes && tmrInboundRoutes.length > 0) || (tmrOutboundRoutes && tmrOutboundRoutes.length > 0) ? (
|
||||
<div style={{display: 'flex', alignItems: 'center', gap: '10px'}}>
|
||||
<button className="btn btn-primary btn-sm" onClick={openSaveTemplateModal}>
|
||||
Save Today's Routes as a Template
|
||||
</button>
|
||||
{/* <button className="btn btn-secondary btn-sm" onClick={() => navigate('/trans-routes/daily-templates/list')}>
|
||||
View and Update Daily Route Templates
|
||||
</button> */}
|
||||
</div>
|
||||
) : (
|
||||
<div style={{display: 'flex', alignItems: 'center', gap: '10px'}}>
|
||||
<select
|
||||
className="form-select"
|
||||
style={{width: 'auto', display: 'inline-block'}}
|
||||
value={selectedTemplateId}
|
||||
onChange={(e) => setSelectedTemplateId(e.target.value)}
|
||||
disabled={applyingTemplate}
|
||||
>
|
||||
<option value="">Choose a daily template to apply to this day</option>
|
||||
{templates.map(template => (
|
||||
<option key={template.id} value={template.id}>
|
||||
{template.name} - {template.template_date}
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
<button
|
||||
className="btn btn-primary btn-sm"
|
||||
onClick={applyTemplate}
|
||||
disabled={!selectedTemplateId || applyingTemplate}
|
||||
>
|
||||
{applyingTemplate ? <><Spinner size="sm" className="me-1" />Applying...</> : 'Submit'}
|
||||
</button>
|
||||
{/* <button className="btn btn-secondary btn-sm" onClick={() => navigate('/trans-routes/daily-templates/list')}>
|
||||
View and Update Daily Route Templates
|
||||
</button> */}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
<div className="col-md-12 mb-4">
|
||||
<RoutesSection transRoutes={tmrInboundRoutes} copyList={tmrOutboundRoutes} addText="+Add Route" copyText="+Copy Route from Outbound" canAddNew={true} drivers={drivers} vehicles={vehicles} redirect={goToCreateRoute} routeType="inbound" sectionName="Inbound Routes"/>
|
||||
</div>
|
||||
@ -1034,6 +1198,7 @@ const RoutesDashboard = () => {
|
||||
}
|
||||
</Tab>
|
||||
<Tab eventKey="allRoutesSignature" title="All Routes Signature">
|
||||
<input className="me-2 mb-2 with-search-icon" type="text" placeholder="Search" value={keyword} onChange={(e) => setKeyword(e.currentTarget.value)} />
|
||||
<table className="personnel-info-table me-4">
|
||||
<thead>
|
||||
<tr>
|
||||
@ -1160,7 +1325,7 @@ const RoutesDashboard = () => {
|
||||
autoClose={false}
|
||||
>
|
||||
<Dropdown.Toggle variant="primary">
|
||||
<CalendarWeek size={16} className="me-2"></CalendarWeek>Select Date to View & Copy From
|
||||
<CalendarWeek size={16} className="me-2"></CalendarWeek>Select Date to View OR Copy From
|
||||
</Dropdown.Toggle>
|
||||
<Dropdown.Menu as={customMenuOriginDate}/>
|
||||
</Dropdown>
|
||||
@ -1184,7 +1349,7 @@ const RoutesDashboard = () => {
|
||||
}
|
||||
|
||||
{ currentTab === 'allRoutesSignature' && <>
|
||||
<input className="me-2 with-search-icon" type="text" placeholder="Search" value={keyword} onChange={(e) => setKeyword(e.currentTarget.value)} />
|
||||
|
||||
<Dropdown
|
||||
key={'signature-date'}
|
||||
id="signature-date"
|
||||
@ -1263,6 +1428,36 @@ const RoutesDashboard = () => {
|
||||
</Button>
|
||||
</Modal.Footer>
|
||||
</Modal>
|
||||
|
||||
<Modal show={showSaveTemplateModal} onHide={closeSaveTemplateModal}>
|
||||
<Modal.Header closeButton>
|
||||
<Modal.Title>Save Routes as Template</Modal.Title>
|
||||
</Modal.Header>
|
||||
<Modal.Body>
|
||||
<div className="mb-3">
|
||||
<label className="form-label">Template Name</label>
|
||||
<input
|
||||
type="text"
|
||||
className="form-control"
|
||||
placeholder="Enter template name"
|
||||
value={templateName}
|
||||
onChange={(e) => setTemplateName(e.target.value)}
|
||||
disabled={savingTemplate}
|
||||
/>
|
||||
</div>
|
||||
<div className="text-muted">
|
||||
<small>This will save all routes from {getDateString(dateSelected)} as a reusable template.</small>
|
||||
</div>
|
||||
</Modal.Body>
|
||||
<Modal.Footer>
|
||||
<Button variant="secondary" onClick={closeSaveTemplateModal} disabled={savingTemplate}>
|
||||
Cancel
|
||||
</Button>
|
||||
<Button variant="primary" onClick={saveRoutesAsTemplate} disabled={savingTemplate}>
|
||||
{savingTemplate ? <><Spinner size="sm" className="me-2" />Saving...</> : 'Submit'}
|
||||
</Button>
|
||||
</Modal.Footer>
|
||||
</Modal>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
@ -263,9 +263,9 @@ const RoutesSchedule = () => {
|
||||
<div className="col-lg-3 col-md-6 col-sm-6 col-xs-12 mb-4">
|
||||
<button type="button" className="btn btn-primary btn-sm me-2 mb-4" disabled={copyDisabled || showCopyTodayLoading} onClick={() => copyTodayRoutesOver()}>{showCopyTodayLoading? 'Loading...' : `Copy Today's Routes Over`}</button>
|
||||
</div>
|
||||
<div className="col-lg-4 col-md-6col-sm-12 col-xs-12 mb-4">
|
||||
{/* <div className="col-lg-4 col-md-6col-sm-12 col-xs-12 mb-4">
|
||||
<button type="button" className="btn btn-primary btn-sm" onClick={()=> validateSchedule()}>Validate and Finish Planning</button>
|
||||
</div>
|
||||
</div> */}
|
||||
<div className="col-lg-4 col-md-6 col-sm-12 col-xs-12 mb-4">
|
||||
<button type="button" className="btn btn-primary btn-sm" onClick={()=> triggerShowDeleteModal()}>Clean All Routes</button>
|
||||
</div>
|
||||
|
||||
@ -7,7 +7,7 @@ import { CUSTOMER_TYPE, PERSONAL_ROUTE_STATUS, PICKUP_STATUS } from "../../share
|
||||
import { AuthService } from "../../services";
|
||||
import moment from 'moment';
|
||||
|
||||
const RoutesSection = ({transRoutes, copyList, sectionName, drivers, vehicles, canAddNew, addText, copyText, redirect, routeType}) => {
|
||||
const RoutesSection = ({transRoutes, copyList, sectionName, drivers, vehicles, canAddNew, addText, copyText, redirect, routeType, isTemplate, templateId}) => {
|
||||
const [showCopyModal, setShowCopyModal] = useState(false);
|
||||
const [routesOptions, setRouteOptions] = useState([]);
|
||||
const [newRoute, setNewRoute] = useState(undefined);
|
||||
@ -107,7 +107,7 @@ const RoutesSection = ({transRoutes, copyList, sectionName, drivers, vehicles, c
|
||||
transRoutes && (<>
|
||||
<div className="all-routes-container">
|
||||
{
|
||||
transRoutes.map((r) => Object.assign({}, r, {vehicleNumber: vehicles?.find((v) => r.vehicle === v.id)?.vehicle_number || 0})).sort((a, b) => a?.vehicleNumber - b?.vehicleNumber).map((route, index) => <div key={route.id} className="route-card-container"><RouteCard routeIndex={index} transRoute={route} drivers={drivers} vehicles={vehicles}/></div>)
|
||||
transRoutes.map((r) => Object.assign({}, r, {vehicleNumber: vehicles?.find((v) => r.vehicle === v.id)?.vehicle_number || 0})).sort((a, b) => a?.vehicleNumber - b?.vehicleNumber).map((route, index) => <div key={route.id} className="route-card-container"><RouteCard routeIndex={index} transRoute={route} drivers={drivers} vehicles={vehicles} isTemplate={isTemplate} templateId={templateId}/></div>)
|
||||
}
|
||||
</div>
|
||||
</>)
|
||||
|
||||
@ -29,8 +29,10 @@ const SnackSection = ({transRoutes, snackRecords, sectionName, confirmHasSnack,
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
const routeCustomers = TransRoutesService.getAllInCenterCustomersFromRoutesForSnack(transRoutes, snackRecords);
|
||||
if (transRoutes && transRoutes.length > 0) {
|
||||
const routeCustomers = TransRoutesService.getAllInCenterCustomersFromRoutesForSnack(transRoutes, snackRecords || []);
|
||||
setCustomers(routeCustomers);
|
||||
}
|
||||
}, [snackRecords, transRoutes]);
|
||||
|
||||
|
||||
@ -53,8 +55,8 @@ const SnackSection = ({transRoutes, snackRecords, sectionName, confirmHasSnack,
|
||||
</thead>
|
||||
<tbody>
|
||||
{
|
||||
snackRecords?.length >0 && customers?.map((customer) => (
|
||||
<tr className={customer?.has_snack ? 'light-green' : 'red'}>
|
||||
customers?.map((customer) => (
|
||||
<tr key={customer?.customer_id} className={customer?.has_snack ? 'light-green' : 'red'}>
|
||||
<td>{customer?.customer_name}</td>
|
||||
<td>{customer?.has_snack ? 'Yes': 'No'}</td>
|
||||
<td>
|
||||
|
||||
@ -1,46 +1,87 @@
|
||||
import React, {useEffect, useState} from "react";
|
||||
import { useSelector,useDispatch } from "react-redux";
|
||||
import { useSelector, useDispatch } from "react-redux";
|
||||
import { useNavigate } from "react-router-dom";
|
||||
import { vehicleSlice, selectVehicleError } from "./../../store";
|
||||
import { AuthService, VehicleService } from "../../services";
|
||||
import { AuthService, VehicleService, DriverService } from "../../services";
|
||||
import { Spinner, Breadcrumb, BreadcrumbItem, Tabs, Tab } from "react-bootstrap";
|
||||
import { Upload } from "react-bootstrap-icons";
|
||||
import DatePicker from "react-datepicker";
|
||||
import Select from 'react-select';
|
||||
import moment from 'moment';
|
||||
import {
|
||||
SEATING_CAPACITY_OPTIONS,
|
||||
FUEL_TYPE, FUEL_TYPE_TEXT,
|
||||
VEHICLE_TITLE, VEHICLE_TITLE_TEXT,
|
||||
LIFT_EQUIPPED, LIFT_EQUIPPED_TEXT,
|
||||
REPAIR_PART_NAME, REPAIR_PART_NAME_TEXT,
|
||||
QUANTITY_OPTIONS
|
||||
} from "../../shared";
|
||||
|
||||
const CreateVehicle = () => {
|
||||
const navigate = useNavigate();
|
||||
const dispatch = useDispatch();
|
||||
const { createVehicle } = vehicleSlice.actions;
|
||||
const error = useSelector(selectVehicleError);
|
||||
|
||||
// Basic Information
|
||||
const [vehicleNumber, setVehicleNumber] = useState('');
|
||||
const [responsibleDriver, setResponsibleDriver] = useState(null);
|
||||
const [capacity, setCapacity] = useState('');
|
||||
const [mileage, setMileage] = useState('');
|
||||
const [make, setMake] = useState('');
|
||||
const [vehicleModel, setVehicleModel] = useState('');
|
||||
const [year, setYear] = useState('');
|
||||
const [vin, setVin] = useState('');
|
||||
const [tag, setTag] = useState('');
|
||||
const [gps, setGps] = useState('');
|
||||
const [ezpass, setEzpass] = useState('');
|
||||
const [hasLiftEquip, setHasLiftEquip] = useState('');
|
||||
const [fuelType, setFuelType] = useState('');
|
||||
const [title, setTitle] = useState('');
|
||||
const [titleOther, setTitleOther] = useState('');
|
||||
|
||||
// Compliance & Deadlines
|
||||
const [insuranceStartDate, setInsuranceStartDate] = useState(null);
|
||||
const [vehicleRegistrationDate, setVehicleRegistrationDate] = useState(null);
|
||||
|
||||
// Check List
|
||||
const [checklist, setChecklist] = useState(['']);
|
||||
|
||||
// Additional Information
|
||||
const [note, setNote] = useState('');
|
||||
|
||||
// Drivers list
|
||||
const [drivers, setDrivers] = useState([]);
|
||||
|
||||
// Documents
|
||||
const [selectedMonthlyFile, setSelectedMonthlyFile] = useState();
|
||||
const [selectedYearlyFile, setSelectedYearlyFile] = useState();
|
||||
const [monthlyInspectionDate, setMonthlyInspectionDate] = useState(null);
|
||||
const [yearlyInspectionDate, setYearlyInspectionDate] = useState(null);
|
||||
|
||||
// Repair & Maintenance Record
|
||||
const [repairPartName, setRepairPartName] = useState('');
|
||||
const [repairReplacementDate, setRepairReplacementDate] = useState(null);
|
||||
const [repairMileage, setRepairMileage] = useState('');
|
||||
const [repairQuantity, setRepairQuantity] = useState('');
|
||||
const [repairCost, setRepairCost] = useState('');
|
||||
const [repairLocation, setRepairLocation] = useState('');
|
||||
const [repairReceiptFile, setRepairReceiptFile] = useState(null);
|
||||
const [repairNextReminder, setRepairNextReminder] = useState('');
|
||||
|
||||
useEffect(() => {
|
||||
if (!AuthService.canAddOrEditVechiles()) {
|
||||
window.alert('You haven\'t login yet OR this user does not have access to this page. Please change an Dispatcher or admin account to login.')
|
||||
AuthService.logout();
|
||||
navigate(`/login`);
|
||||
}
|
||||
// Fetch drivers list
|
||||
DriverService.getAllActiveDrivers('driver', 'active').then(data => {
|
||||
setDrivers(data.data || []);
|
||||
}).catch(() => {
|
||||
setDrivers([]);
|
||||
});
|
||||
}, []);
|
||||
const { createVehicle } = vehicleSlice.actions;
|
||||
const [vehicleNumber, setVehicleNumber] = useState();
|
||||
const [tag, setTag] = useState('');
|
||||
const [make, setMake] = useState('');
|
||||
const [vehicleModel, setVehicleModel] = useState('');
|
||||
const [year, setYear] = useState('');
|
||||
const [ezpass, setEzpass] = useState('');
|
||||
const [gps, setGps] = useState('');
|
||||
const [mileage, setMileage] = useState();
|
||||
const [capacity, setCapacity] = useState();
|
||||
const [checklist, setChecklist] = useState(['']);
|
||||
const [hasLiftEquip, setHasLiftEquip] = useState(undefined);
|
||||
const [insuranceExpireOn, setInsuranceExpireOn] = useState(undefined);
|
||||
const [titleRegistrationOn, setTitleRegistrationOn] = useState(undefined);
|
||||
const [emissionTestOn, setEmissionTestOn] = useState(undefined);
|
||||
const [oilChangeMileage, setOilChangeMileage] = useState(undefined);
|
||||
const [oilChangeDate, setOilChangeDate] = useState(undefined);
|
||||
const [vin, setVin] = useState('');
|
||||
const [note, setNote] = useState('');
|
||||
// const [selectedMothlyFile, setSelectedMonthlyFile] = useState();
|
||||
// const [selectedYearlyFile, setSelectedYearlyFile] = useState();
|
||||
// const [monthlyInspectionDate, setMonthlyInspectionDate] = useState();
|
||||
// const [yearlyInspectionDate, setYearlyInspectionDate] = useState();
|
||||
const error = useSelector(selectVehicleError);
|
||||
|
||||
const redirectTo = () => {
|
||||
const params = new URLSearchParams(window.location.search);
|
||||
@ -48,7 +89,6 @@ const CreateVehicle = () => {
|
||||
if (redirect === 'schedule') {
|
||||
navigate(`/trans-routes/schedule`);
|
||||
} else {
|
||||
// navigate(`/trans-routes/dashboard`);
|
||||
redirectToList();
|
||||
}
|
||||
}
|
||||
@ -62,46 +102,111 @@ const CreateVehicle = () => {
|
||||
setChecklist(arr);
|
||||
}
|
||||
|
||||
const saveVechile = () => {
|
||||
const formatDateForBackend = (date) => {
|
||||
if (!date) return '';
|
||||
return moment(date).format('MM/DD/YYYY');
|
||||
}
|
||||
|
||||
const validateVehicle = () => {
|
||||
const errors = [];
|
||||
|
||||
// Required fields validation
|
||||
if (!vehicleNumber || vehicleNumber.trim() === '') {
|
||||
errors.push('Vehicle Number');
|
||||
}
|
||||
if (!tag || tag.trim() === '') {
|
||||
errors.push('License Plate');
|
||||
}
|
||||
|
||||
if (errors.length > 0) {
|
||||
window.alert(`Please fill in the following required fields:\n${errors.join('\n')}`);
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
};
|
||||
|
||||
const saveVehicle = () => {
|
||||
if (!validateVehicle()) {
|
||||
return;
|
||||
}
|
||||
|
||||
const data = {
|
||||
// Basic Information
|
||||
vehicle_number: vehicleNumber,
|
||||
tag,
|
||||
ezpass,
|
||||
gps_tag: gps,
|
||||
mileage,
|
||||
capacity,
|
||||
year,
|
||||
responsible_driver: responsibleDriver?.label || '',
|
||||
responsible_driver_id: responsibleDriver?.value || '',
|
||||
capacity: parseInt(capacity) || 0,
|
||||
mileage: parseInt(mileage) || 0,
|
||||
make,
|
||||
vehicle_model: vehicleModel,
|
||||
status: 'active',
|
||||
checklist,
|
||||
note,
|
||||
year,
|
||||
vin,
|
||||
tag,
|
||||
gps_tag: gps,
|
||||
ezpass,
|
||||
has_lift_equip: hasLiftEquip === 'true',
|
||||
insurance_expire_on: moment(insuranceExpireOn).format('MM/DD/YYYY'),
|
||||
title_registration_on: moment(titleRegistrationOn).format('MM/DD/YYYY'),
|
||||
emission_test_on: moment(emissionTestOn).format('MM/DD/YYYY'),
|
||||
oil_change_date: moment(oilChangeDate).format('MM/DD/YYYY')
|
||||
fuel_type: fuelType,
|
||||
title,
|
||||
title_other: titleOther,
|
||||
|
||||
// Compliance & Deadlines
|
||||
insurance_start_date: formatDateForBackend(insuranceStartDate),
|
||||
vehicle_registration_date: formatDateForBackend(vehicleRegistrationDate),
|
||||
|
||||
// Check List
|
||||
checklist,
|
||||
|
||||
// Additional Information
|
||||
note,
|
||||
|
||||
// System fields
|
||||
status: 'active'
|
||||
};
|
||||
dispatch(createVehicle({data, redirectFun: redirectTo}));
|
||||
}
|
||||
|
||||
// const saveDocument = () => {
|
||||
// if (selectedMothlyFile && monthlyInspectionDate) {
|
||||
// VehicleService.uploadVechileFile(formData, currentVechile.id, currentVehchile.vehicle_number, 'monthlyInspection', monthlyInspectionDate);
|
||||
// }
|
||||
// if (selectedYearlyFile && yearlyInspectionDate) {
|
||||
// VehicleService.uploadVechileFile(formData, currentVechile.id, currentVehchile.vehicle_number, 'yearlyInspection', yearlyInspectionDate);
|
||||
// }
|
||||
// }
|
||||
|
||||
// Custom styles for react-select
|
||||
const selectStyles = {
|
||||
control: (baseStyles) => ({
|
||||
...baseStyles,
|
||||
minWidth: '200px',
|
||||
borderRadius: '8px'
|
||||
}),
|
||||
indicatorSeparator: () => ({
|
||||
display: 'none'
|
||||
}),
|
||||
dropdownIndicator: (baseStyles) => ({
|
||||
...baseStyles,
|
||||
paddingRight: '8px',
|
||||
color: '#333',
|
||||
'&:hover': {
|
||||
color: '#000',
|
||||
},
|
||||
}),
|
||||
valueContainer: (baseStyles) => ({
|
||||
...baseStyles,
|
||||
height: '43px',
|
||||
padding: '0 8px',
|
||||
}),
|
||||
input: (baseStyles) => ({
|
||||
...baseStyles,
|
||||
margin: '0px',
|
||||
padding: '0px',
|
||||
height: '30px',
|
||||
width: '290px'
|
||||
}),
|
||||
singleValue: (baseStyles) => ({
|
||||
...baseStyles,
|
||||
margin: '0px',
|
||||
}),
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className="list row mb-4">
|
||||
<Breadcrumb>
|
||||
<Breadcrumb.Item>Transportation</Breadcrumb.Item>
|
||||
<Breadcrumb.Item active>
|
||||
<Breadcrumb.Item href="/trans-routes/dashboard">Transportation</Breadcrumb.Item>
|
||||
<Breadcrumb.Item href="/vehicles/list">
|
||||
Vehicles Information
|
||||
</Breadcrumb.Item>
|
||||
<Breadcrumb.Item active>
|
||||
@ -109,135 +214,294 @@ const CreateVehicle = () => {
|
||||
</Breadcrumb.Item>
|
||||
</Breadcrumb>
|
||||
<div className="col-md-12 text-primary">
|
||||
<h4>Create New Vechile <button className="btn btn-link btn-sm" onClick={() => {redirectTo()}}>Back</button></h4>
|
||||
<h4>Create New Vehicle <button className="btn btn-link btn-sm" onClick={() => {redirectTo()}}>Back</button></h4>
|
||||
</div>
|
||||
</div>
|
||||
<div className="app-main-content-list-container">
|
||||
<div className="app-main-content-list-container form-page">
|
||||
<div className="app-main-content-list-func-container">
|
||||
<Tabs defaultActiveKey="basicInfo" id="customers-tab">
|
||||
<Tabs defaultActiveKey="basicInfo" id="vehicles-tab">
|
||||
<Tab eventKey="basicInfo" title="Basic Information">
|
||||
<h6 className="text-primary">Vehicle Information</h6>
|
||||
<h6 className="text-primary">Basic Information</h6>
|
||||
<div className="app-main-content-fields-section">
|
||||
<div className="me-4">
|
||||
<div className="field-label">Vehicle Number
|
||||
<span className="required">*</span>
|
||||
<div className="field-label">Vehicle Number <span className="required">*</span></div>
|
||||
<input type="text" placeholder="e.g., 101" value={vehicleNumber} onChange={e => setVehicleNumber(e.target.value)}/>
|
||||
</div>
|
||||
<input type="number" placeholder="e.g.,1" value={vehicleNumber || ''} onChange={e => setVehicleNumber(e.target.value)}/>
|
||||
<div className="me-4">
|
||||
<div className="field-label">Responsible Driver</div>
|
||||
<Select
|
||||
value={responsibleDriver}
|
||||
onChange={setResponsibleDriver}
|
||||
options={[{value: '', label: ''}, ...drivers.map(driver => ({
|
||||
value: driver?.id || '',
|
||||
label: driver?.name || '',
|
||||
}))]}
|
||||
styles={selectStyles}
|
||||
placeholder="e.g., John Smith"
|
||||
isClearable
|
||||
/>
|
||||
</div>
|
||||
<div className="me-4">
|
||||
<div className="field-label">Seating Capacity</div>
|
||||
<select value={capacity} onChange={e => setCapacity(e.target.value)}>
|
||||
<option value="">Select...</option>
|
||||
{SEATING_CAPACITY_OPTIONS.map(opt => (
|
||||
<option key={opt.value} value={opt.value}>{opt.label}</option>
|
||||
))}
|
||||
</select>
|
||||
</div>
|
||||
<div className="me-4">
|
||||
<div className="field-label">Mileage</div>
|
||||
<input type="number" placeholder="e.g., 48000" value={mileage} onChange={e => setMileage(e.target.value)}/>
|
||||
</div>
|
||||
<div className="me-4"><div className="field-label">Seating Capacity</div><input type="number" value={capacity || ''} placeholder="e.g.,12" onChange={e => setCapacity(e.target.value)}/></div>
|
||||
<div className="me-4"><div className="field-label">Mileage</div><input type="number" value={mileage || ''} placeholder="e.g.,48000" onChange={e => setMileage(e.target.value)}/></div>
|
||||
<div className="me-4"><div className="field-label">Make</div><input type="text" value={make || ''} placeholder="e.g.,Ford" onChange={e => setMake(e.target.value)}/></div>
|
||||
<div className="me-4"><div className="field-label">Model</div><input type="text" value={vehicleModel || ''} placeholder="e.g.,T350" onChange={e => setVehicleModel(e.target.value)}/></div>
|
||||
</div>
|
||||
<div className="app-main-content-fields-section">
|
||||
<div className="me-4"><div className="field-label">Year</div><input type="text" value={year || ''} placeholder="e.g.,2016" onChange={e => setYear(e.target.value)}/></div>
|
||||
<div className="me-4"><div className="field-label">Vin Number</div><input type="text" value={vin || ''} placeholder="e.g, 1FBAX2CM9KA34959" onChange={e => setVin(e.target.value)}/></div>
|
||||
<div className="me-4"><div className="field-label">Licence Plate</div><input type="text" value={tag || ''} placeholder="e.g.,91579HT" onChange={e => setTag(e.target.value)}/></div>
|
||||
<div className="me-4"><div className="field-label">GPS ID</div><input type="text" value={gps || ''} placeholder="e.g.,609671" onChange={e => setGps(e.target.value)}/></div>
|
||||
<div className="me-4"><div className="field-label">EZPass</div><input type="text" value={ezpass || ''} placeholder="e.g.,NY12345" onChange={e => setEzpass(e.target.value)}/></div>
|
||||
<div className="me-4">
|
||||
<div className="field-label">Make</div>
|
||||
<input type="text" placeholder="e.g., Ford" value={make} onChange={e => setMake(e.target.value)}/>
|
||||
</div>
|
||||
<div className="me-4">
|
||||
<div className="field-label">Model</div>
|
||||
<input type="text" placeholder="e.g., T350" value={vehicleModel} onChange={e => setVehicleModel(e.target.value)}/>
|
||||
</div>
|
||||
<div className="me-4">
|
||||
<div className="field-label">Year</div>
|
||||
<input type="text" placeholder="e.g., 2018" value={year} onChange={e => setYear(e.target.value)}/>
|
||||
</div>
|
||||
<div className="me-4">
|
||||
<div className="field-label">VIN Number</div>
|
||||
<input type="text" placeholder="e.g., 1FBAX2CM9KKA34959" value={vin} onChange={e => setVin(e.target.value)}/>
|
||||
</div>
|
||||
</div>
|
||||
<div className="app-main-content-fields-section">
|
||||
<div className="me-4">
|
||||
<div className="field-label">License Plate <span className="required">*</span></div>
|
||||
<input type="text" placeholder="e.g., 91579HT" value={tag} onChange={e => setTag(e.target.value)}/>
|
||||
</div>
|
||||
<div className="me-4">
|
||||
<div className="field-label">GPS ID</div>
|
||||
<input type="text" placeholder="e.g., 609671" value={gps} onChange={e => setGps(e.target.value)}/>
|
||||
</div>
|
||||
<div className="me-4">
|
||||
<div className="field-label">E-ZPass</div>
|
||||
<input type="text" placeholder="e.g., NY12345" value={ezpass} onChange={e => setEzpass(e.target.value)}/>
|
||||
</div>
|
||||
<div className="me-4">
|
||||
<div className="field-label">Lift Equipped</div>
|
||||
<select value={hasLiftEquip} onChange={e => setHasLiftEquip(e.target.value)}>
|
||||
<option value=""></option>
|
||||
<option value="true">Yes</option>
|
||||
<option value="false">No</option>
|
||||
<option value="">Select...</option>
|
||||
{Object.keys(LIFT_EQUIPPED).map(key => (
|
||||
<option key={key} value={LIFT_EQUIPPED[key]}>{LIFT_EQUIPPED_TEXT[LIFT_EQUIPPED[key]]}</option>
|
||||
))}
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<h6 className="text-primary">Vehicle Maintenance & Compliance</h6>
|
||||
<div className="app-main-content-fields-section">
|
||||
<div className="me-4">
|
||||
<div className="field-label">Last Oil Change Date <span className="field-blurb float-right">1-month due cycle </span></div>
|
||||
<DatePicker selected={oilChangeDate} onChange={(v) => setOilChangeDate(v)} />
|
||||
<div className="field-label">Fuel Type</div>
|
||||
<select value={fuelType} onChange={e => setFuelType(e.target.value)}>
|
||||
<option value="">Select...</option>
|
||||
{Object.keys(FUEL_TYPE).map(key => (
|
||||
<option key={key} value={FUEL_TYPE[key]}>{FUEL_TYPE_TEXT[FUEL_TYPE[key]]}</option>
|
||||
))}
|
||||
</select>
|
||||
</div>
|
||||
<div className="me-4">
|
||||
<div className="field-label">Last Emissions Inspection Date <span className="field-blurb float-right">1-year due cycle </span></div>
|
||||
<DatePicker selected={emissionTestOn} onChange={(v) => setEmissionTestOn(v)} />
|
||||
<div className="field-label">Title</div>
|
||||
<select value={title} onChange={e => setTitle(e.target.value)}>
|
||||
<option value="">Select...</option>
|
||||
{Object.keys(VEHICLE_TITLE).map(key => (
|
||||
<option key={key} value={VEHICLE_TITLE[key]}>{VEHICLE_TITLE_TEXT[VEHICLE_TITLE[key]]}</option>
|
||||
))}
|
||||
</select>
|
||||
</div>
|
||||
{title === 'other' && (
|
||||
<div className="me-4">
|
||||
<div className="field-label">Insurance Expiration Date <span className="field-blurb float-right">1-year due cycle </span></div>
|
||||
<DatePicker selected={insuranceExpireOn} onChange={(v) => setInsuranceExpireOn(v)} />
|
||||
<div className="field-label">Title (Other)</div>
|
||||
<input type="text" placeholder="Please specify..." value={titleOther} onChange={e => setTitleOther(e.target.value)}/>
|
||||
</div>
|
||||
<div className="me-4">
|
||||
<div className="field-label">Title Registration Date <span className="field-blurb float-right">1-year due cycle </span></div>
|
||||
<DatePicker selected={titleRegistrationOn} onChange={(v) => setTitleRegistrationOn(v)} />
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
<h6 className="text-primary">Check List</h6>
|
||||
<div className="app-main-content-fields-section column">
|
||||
{checklist.map((item, index) => (<div className="mb-4" key={index}><input type="text" value={item} onChange={(e) => setChecklist([...checklist].map((a, index1) => {if (index1 === index) {return e.target.value;} return a;}))}/>
|
||||
{checklist.map((item, index) => (
|
||||
<div className="mb-4" key={index}>
|
||||
<input type="text" placeholder="e.g., Tire pressure" value={item} onChange={(e) => setChecklist([...checklist].map((a, index1) => {if (index1 === index) {return e.target.value;} return a;}))}/>
|
||||
<button className="btn btn-link btn-sm" onClick={(e) => setChecklist([...checklist].filter((value, index1) => index1 != index))}>Remove</button>
|
||||
</div>))}
|
||||
</div>
|
||||
))}
|
||||
<button className="btn btn-link" onClick={() => addItemToArray()}>+Add New Item</button>
|
||||
</div>
|
||||
|
||||
<h6 className="text-primary">Additional Information</h6>
|
||||
<div className="app-main-content-fields-section">
|
||||
<div className="me-4">
|
||||
<div className="field-label">Note</div>
|
||||
<textarea placeholder="Any Extra Details" value={note || ''} onChange={e => setNote(e.target.value)}/>
|
||||
<div className="field-label">Notes and Attachments</div>
|
||||
<textarea placeholder="e.g., Vehicle assigned to Route A" value={note} onChange={e => setNote(e.target.value)} rows={4} style={{width: '400px'}}/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{error && <div className="col-md-12 mb-4 alert alert-danger" role="alert">
|
||||
{error}
|
||||
</div>}
|
||||
<div className="list row mb-5">
|
||||
<div className="col-md-12 col-sm-12 col-xs-12">
|
||||
<button className="btn btn-default btn-sm float-right" onClick={() => redirectTo()}> Cancel </button>
|
||||
<button className="btn btn-primary btn-sm float-right" onClick={() => saveVechile()}> Save </button>
|
||||
|
||||
<button className="btn btn-primary btn-sm float-right" onClick={() => saveVehicle()}> Save </button>
|
||||
</div>
|
||||
</div>
|
||||
</Tab>
|
||||
{/* <Tab eventKey="documents" title="Documents">
|
||||
<h6 className="text-primary">Yearly Vehicle Inspection</h6>
|
||||
|
||||
<Tab eventKey="complianceDeadlines" title="Compliance & Deadlines">
|
||||
<h6 className="text-primary">Compliance & Deadlines</h6>
|
||||
<div className="app-main-content-fields-section">
|
||||
<div className="me-4">
|
||||
<div className="field-label">Vehicle Inspection Date<span className="required">*</span></div>
|
||||
<DatePicker selected={yearlyInspectionDate} onChange={(v) => setYearlyInspectionDate(v)} />
|
||||
<div className="field-label">Insurance Expiration Date</div>
|
||||
<DatePicker
|
||||
selected={insuranceStartDate}
|
||||
onChange={(date) => setInsuranceStartDate(date)}
|
||||
dateFormat="MM/dd/yyyy"
|
||||
placeholderText="e.g., 03/01/2024"
|
||||
className="form-control"
|
||||
/>
|
||||
</div>
|
||||
<div className="me-4">
|
||||
<div className="field-label">Yearly Vehicle Inspection Sheet<span className="required">*</span></div>
|
||||
<label className="custom-file-upload">
|
||||
<Upload width={20} color={"#fff"} className="me-2"></Upload> Upload Files
|
||||
<input
|
||||
type="file"
|
||||
onChange={(e) => setSelectedYearlyFile(e.target.files[0])}
|
||||
<div className="field-label">Vehicle Registration Date</div>
|
||||
<DatePicker
|
||||
selected={vehicleRegistrationDate}
|
||||
onChange={(date) => setVehicleRegistrationDate(date)}
|
||||
dateFormat="MM/dd/yyyy"
|
||||
placeholderText="e.g., 03/01/2024"
|
||||
className="form-control"
|
||||
/>
|
||||
</label>
|
||||
<div className="file-name">{ selectedYearlyFile && selectedYearlyFile?.name }</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<h6 className="text-primary">Monthly Vehicle Inspection</h6>
|
||||
<div className="app-main-content-fields-section">
|
||||
<div className="me-4">
|
||||
<div className="field-label">Vehicle Inspection Date <span className="required">*</span></div>
|
||||
<DatePicker selected={monthlyInspectionDate} onChange={(v) => setMonthlyInspectionDate(v)} />
|
||||
</div>
|
||||
<div className="me-4">
|
||||
<div className="field-label">Monthly Vehicle Inspection Sheet<span className="required">*</span></div>
|
||||
<label className="custom-file-upload">
|
||||
<Upload width={20} color={"#fff"} className="me-2"></Upload> Upload Files
|
||||
<input
|
||||
type="file"
|
||||
onChange={(e) => setSelectedMonthlyFile(e.target.files[0])}
|
||||
/>
|
||||
</label>
|
||||
<div className="file-name">{ selectedMothlyFile && selectedMothlyFile?.name }</div>
|
||||
</div>
|
||||
</div>
|
||||
{error && <div className="col-md-12 mb-4 alert alert-danger" role="alert">
|
||||
{error}
|
||||
</div>}
|
||||
<div className="list row mb-5">
|
||||
<div className="col-md-12 col-sm-12 col-xs-12">
|
||||
<button className="btn btn-default btn-sm float-right" onClick={() => redirectTo()}> Cancel </button>
|
||||
<button className="btn btn-primary btn-sm float-right" onClick={() => saveDocuments()}> Save </button>
|
||||
|
||||
<button className="btn btn-primary btn-sm float-right" onClick={() => saveVehicle()}> Save </button>
|
||||
</div>
|
||||
</div>
|
||||
</Tab>
|
||||
|
||||
<Tab eventKey="documents" title="Documents & Records">
|
||||
<h6 className="text-primary">Yearly Inspection</h6>
|
||||
<div className="app-main-content-fields-section">
|
||||
<div className="me-4">
|
||||
<div className="field-label">Yearly Inspection Date</div>
|
||||
<DatePicker
|
||||
selected={yearlyInspectionDate}
|
||||
onChange={(date) => setYearlyInspectionDate(date)}
|
||||
dateFormat="MM/dd/yyyy"
|
||||
placeholderText="e.g., 03/01/2024"
|
||||
className="form-control"
|
||||
/>
|
||||
</div>
|
||||
<div className="me-4">
|
||||
<div className="field-label">Yearly Inspection File</div>
|
||||
<label className="custom-file-upload">
|
||||
<Upload width={20} color={"#fff"} className="me-2"></Upload> Upload
|
||||
<input type="file" onChange={(e) => setSelectedYearlyFile(e.target.files[0])}/>
|
||||
</label>
|
||||
<div className="file-name">{selectedYearlyFile?.name}</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<h6 className="text-primary">Monthly Inspection</h6>
|
||||
<div className="app-main-content-fields-section">
|
||||
<div className="me-4">
|
||||
<div className="field-label">Monthly Inspection Date</div>
|
||||
<DatePicker
|
||||
selected={monthlyInspectionDate}
|
||||
onChange={(date) => setMonthlyInspectionDate(date)}
|
||||
dateFormat="MM/dd/yyyy"
|
||||
placeholderText="e.g., 03/01/2024"
|
||||
className="form-control"
|
||||
/>
|
||||
</div>
|
||||
<div className="me-4">
|
||||
<div className="field-label">Monthly Inspection File</div>
|
||||
<label className="custom-file-upload">
|
||||
<Upload width={20} color={"#fff"} className="me-2"></Upload> Upload
|
||||
<input type="file" onChange={(e) => setSelectedMonthlyFile(e.target.files[0])}/>
|
||||
</label>
|
||||
<div className="file-name">{selectedMonthlyFile?.name}</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<h6 className="text-primary">Repair & Maintenance Record</h6>
|
||||
<div className="app-main-content-fields-section">
|
||||
<div className="me-4">
|
||||
<div className="field-label">Part Name</div>
|
||||
<select value={repairPartName} onChange={e => setRepairPartName(e.target.value)}>
|
||||
<option value="">Select...</option>
|
||||
{Object.keys(REPAIR_PART_NAME).map(key => (
|
||||
<option key={key} value={REPAIR_PART_NAME[key]}>{REPAIR_PART_NAME_TEXT[REPAIR_PART_NAME[key]]}</option>
|
||||
))}
|
||||
</select>
|
||||
</div>
|
||||
<div className="me-4">
|
||||
<div className="field-label">Replacement Date</div>
|
||||
<DatePicker
|
||||
selected={repairReplacementDate}
|
||||
onChange={(date) => setRepairReplacementDate(date)}
|
||||
dateFormat="MM/dd/yyyy"
|
||||
placeholderText="e.g., 03/01/2024"
|
||||
className="form-control"
|
||||
/>
|
||||
</div>
|
||||
<div className="me-4">
|
||||
<div className="field-label">Mileage at Replacement</div>
|
||||
<input type="text" placeholder="e.g., 48,000" value={repairMileage} onChange={e => setRepairMileage(e.target.value)}/>
|
||||
</div>
|
||||
<div className="me-4">
|
||||
<div className="field-label">Quantity</div>
|
||||
<select value={repairQuantity} onChange={e => setRepairQuantity(e.target.value)}>
|
||||
<option value="">Select...</option>
|
||||
{QUANTITY_OPTIONS.map(opt => (
|
||||
<option key={opt.value} value={opt.value}>{opt.label}</option>
|
||||
))}
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div className="app-main-content-fields-section">
|
||||
<div className="me-4">
|
||||
<div className="field-label">Cost</div>
|
||||
<input type="text" placeholder="e.g., $250.00" value={repairCost} onChange={e => setRepairCost(e.target.value)}/>
|
||||
</div>
|
||||
<div className="me-4">
|
||||
<div className="field-label">Location</div>
|
||||
<input type="text" placeholder="e.g., Rockville Auto Center" value={repairLocation} onChange={e => setRepairLocation(e.target.value)}/>
|
||||
</div>
|
||||
<div className="me-4">
|
||||
<div className="field-label">Receipt Upload</div>
|
||||
<label className="custom-file-upload">
|
||||
<Upload width={20} color={"#fff"} className="me-2"></Upload> Upload
|
||||
<input type="file" onChange={(e) => setRepairReceiptFile(e.target.files[0])}/>
|
||||
</label>
|
||||
<div className="file-name">{repairReceiptFile?.name}</div>
|
||||
</div>
|
||||
<div className="me-4">
|
||||
<div className="field-label">Next Replacement Reminder</div>
|
||||
<input type="text" placeholder="e.g., 78,000" value={repairNextReminder} onChange={e => setRepairNextReminder(e.target.value)}/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{error && <div className="col-md-12 mb-4 alert alert-danger" role="alert">
|
||||
{error}
|
||||
</div>}
|
||||
<div className="list row mb-5">
|
||||
<div className="col-md-12 col-sm-12 col-xs-12">
|
||||
<button className="btn btn-default btn-sm float-right" onClick={() => redirectTo()}> Cancel </button>
|
||||
<button className="btn btn-primary btn-sm float-right" onClick={() => saveVehicle()}> Save </button>
|
||||
</div>
|
||||
</div>
|
||||
</Tab>
|
||||
<Tab eventKey="Repair Records" title="Repair Records">
|
||||
Coming soon...
|
||||
</Tab> */}
|
||||
</Tabs>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@ -1,49 +1,81 @@
|
||||
import React, {useEffect, useState} from "react";
|
||||
import { useSelector,useDispatch } from "react-redux";
|
||||
import { useNavigate, useParams } from "react-router-dom";
|
||||
import { useSelector, useDispatch } from "react-redux";
|
||||
import { useNavigate, useParams, useSearchParams } from "react-router-dom";
|
||||
import { vehicleSlice, selectVehicleError } from "./../../store";
|
||||
import { AuthService, VehicleRepairService, VehicleService } from "../../services";
|
||||
import { AuthService, VehicleRepairService, VehicleService, DriverService } from "../../services";
|
||||
import { Archive, Upload } from "react-bootstrap-icons";
|
||||
import { Spinner, Breadcrumb, BreadcrumbItem, Tabs, Tab } from "react-bootstrap";
|
||||
import { Spinner, Breadcrumb, BreadcrumbItem, Tabs, Tab, Modal, Button } from "react-bootstrap";
|
||||
import DatePicker from "react-datepicker";
|
||||
import Select from 'react-select';
|
||||
import moment from 'moment';
|
||||
|
||||
import {
|
||||
SEATING_CAPACITY_OPTIONS,
|
||||
FUEL_TYPE, FUEL_TYPE_TEXT,
|
||||
VEHICLE_TITLE, VEHICLE_TITLE_TEXT,
|
||||
LIFT_EQUIPPED, LIFT_EQUIPPED_TEXT,
|
||||
REPAIR_PART_NAME, REPAIR_PART_NAME_TEXT,
|
||||
QUANTITY_OPTIONS
|
||||
} from "../../shared";
|
||||
|
||||
const UpdateVehicle = () => {
|
||||
const navigate = useNavigate();
|
||||
const dispatch = useDispatch();
|
||||
const params = useParams();
|
||||
const [searchParams] = useSearchParams();
|
||||
const [activeTab, setActiveTab] = useState(searchParams.get('tab') || 'basicInfo');
|
||||
const vehicles = useSelector((state) => state.vehicles && state.vehicles.vehicles);
|
||||
const currentVehicle = vehicles.find(item => item.id === params.id ) || undefined;
|
||||
const { updateVehicle, deleteVehicle, fetchAllVehicles } = vehicleSlice.actions;
|
||||
const [vehicleNumber, setVehicleNumber] = useState();
|
||||
const [tag, setTag] = useState('');
|
||||
const error = useSelector(selectVehicleError);
|
||||
|
||||
// Basic Information
|
||||
const [vehicleNumber, setVehicleNumber] = useState('');
|
||||
const [responsibleDriver, setResponsibleDriver] = useState(null);
|
||||
const [capacity, setCapacity] = useState('');
|
||||
const [mileage, setMileage] = useState('');
|
||||
const [make, setMake] = useState('');
|
||||
const [vehicleModel, setVehicleModel] = useState('');
|
||||
const [year, setYear] = useState('');
|
||||
const [ezpass, setEzpass] = useState('');
|
||||
const [gps, setGps] = useState('');
|
||||
const [mileage, setMileage] = useState();
|
||||
const [capacity, setCapacity] = useState();
|
||||
const [checklist, setChecklist] = useState(['']);
|
||||
const [hasLiftEquip, setHasLiftEquip] = useState(undefined);
|
||||
const [insuranceExpireOn, setInsuranceExpireOn] = useState(undefined);
|
||||
const [titleRegistrationOn, setTitleRegistrationOn] = useState(undefined);
|
||||
const [emissionTestOn, setEmissionTestOn] = useState(undefined);
|
||||
const [oilChangeMileage, setOilChangeMileage] = useState(undefined);
|
||||
const [oilChangeDate, setOilChangeDate] = useState(undefined);
|
||||
const [vin, setVin] = useState('');
|
||||
const [tag, setTag] = useState('');
|
||||
const [gps, setGps] = useState('');
|
||||
const [ezpass, setEzpass] = useState('');
|
||||
const [hasLiftEquip, setHasLiftEquip] = useState('');
|
||||
const [fuelType, setFuelType] = useState('');
|
||||
const [title, setTitle] = useState('');
|
||||
const [titleOther, setTitleOther] = useState('');
|
||||
|
||||
// Compliance & Deadlines
|
||||
const [insuranceStartDate, setInsuranceStartDate] = useState(null);
|
||||
const [vehicleRegistrationDate, setVehicleRegistrationDate] = useState(null);
|
||||
|
||||
// Check List
|
||||
const [checklist, setChecklist] = useState(['']);
|
||||
|
||||
// Additional Information
|
||||
const [note, setNote] = useState('');
|
||||
const [selectedMothlyFile, setSelectedMonthlyFile] = useState();
|
||||
|
||||
// Drivers list
|
||||
const [drivers, setDrivers] = useState([]);
|
||||
|
||||
// Documents
|
||||
const [selectedMonthlyFile, setSelectedMonthlyFile] = useState();
|
||||
const [selectedYearlyFile, setSelectedYearlyFile] = useState();
|
||||
const [monthlyInspectionDate, setMonthlyInspectionDate] = useState();
|
||||
const [yearlyInspectionDate, setYearlyInspectionDate] = useState();
|
||||
const [repairDate, setRepairDate] = useState();
|
||||
const [repairDescription, setRepairDescription] = useState('');
|
||||
const [repairPrice, setRepairPrice] = useState('');
|
||||
const [monthlyInspectionDate, setMonthlyInspectionDate] = useState(null);
|
||||
const [yearlyInspectionDate, setYearlyInspectionDate] = useState(null);
|
||||
|
||||
// Repair & Maintenance Record
|
||||
const [repairPartName, setRepairPartName] = useState('');
|
||||
const [repairReplacementDate, setRepairReplacementDate] = useState(null);
|
||||
const [repairMileage, setRepairMileage] = useState('');
|
||||
const [repairQuantity, setRepairQuantity] = useState('');
|
||||
const [repairCost, setRepairCost] = useState('');
|
||||
const [repairLocation, setRepairLocation] = useState('');
|
||||
const error = useSelector(selectVehicleError);
|
||||
const [selectedRepairFile, setSelectedRepairFile] = useState();
|
||||
const [repairReceiptFile, setRepairReceiptFile] = useState(null);
|
||||
const [repairNextReminder, setRepairNextReminder] = useState('');
|
||||
|
||||
// Modal
|
||||
const [showDeleteModal, setShowDeleteModal] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
if (!AuthService.canAddOrEditVechiles()) {
|
||||
@ -54,30 +86,49 @@ const UpdateVehicle = () => {
|
||||
if (!currentVehicle) {
|
||||
dispatch(fetchAllVehicles());
|
||||
}
|
||||
// Fetch drivers list
|
||||
DriverService.getAllActiveDrivers('driver', 'active').then(data => {
|
||||
setDrivers(data.data || []);
|
||||
}).catch(() => {
|
||||
setDrivers([]);
|
||||
});
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
if (currentVehicle) {
|
||||
setVehicleNumber(currentVehicle.vehicle_number);
|
||||
setTag(currentVehicle.tag);
|
||||
setMake(currentVehicle.make);
|
||||
setVehicleModel(currentVehicle.vehicle_model);
|
||||
setYear(currentVehicle.year);
|
||||
setGps(currentVehicle.gps_tag);
|
||||
setEzpass(currentVehicle.ezpass);
|
||||
setMileage(currentVehicle.mileage);
|
||||
setCapacity(currentVehicle.capacity);
|
||||
setChecklist(currentVehicle.checklist);
|
||||
setHasLiftEquip(currentVehicle.has_lift_equip === true ? 'true': (currentVehicle?.has_lift_equip === false ? 'false' : undefined) );
|
||||
setNote(currentVehicle?.note);
|
||||
setVin(currentVehicle?.vin);
|
||||
setInsuranceExpireOn(currentVehicle.insurance_expire_on && VehicleService.convertToDate(currentVehicle.insurance_expire_on));
|
||||
setTitleRegistrationOn(currentVehicle?.title_registration_on && VehicleService.convertToDate(currentVehicle?.title_registration_on));
|
||||
setEmissionTestOn(currentVehicle.title_registration_on && VehicleService.convertToDate(currentVehicle.title_registration_on));
|
||||
setOilChangeDate(currentVehicle.oil_change_date && VehicleService.convertToDate(currentVehicle.oil_change_date));
|
||||
}
|
||||
// Basic Information
|
||||
setVehicleNumber(currentVehicle.vehicle_number || '');
|
||||
setResponsibleDriver(currentVehicle.responsible_driver ? {
|
||||
value: currentVehicle.responsible_driver_id || '',
|
||||
label: currentVehicle.responsible_driver || ''
|
||||
} : null);
|
||||
setCapacity(currentVehicle.capacity?.toString() || '');
|
||||
setMileage(currentVehicle.mileage?.toString() || '');
|
||||
setMake(currentVehicle.make || '');
|
||||
setVehicleModel(currentVehicle.vehicle_model || '');
|
||||
setYear(currentVehicle.year || '');
|
||||
setVin(currentVehicle.vin || '');
|
||||
setTag(currentVehicle.tag || '');
|
||||
setGps(currentVehicle.gps_tag || '');
|
||||
setEzpass(currentVehicle.ezpass || '');
|
||||
setHasLiftEquip(currentVehicle.has_lift_equip === true ? 'true' : (currentVehicle.has_lift_equip === false ? 'false' : ''));
|
||||
setFuelType(currentVehicle.fuel_type || '');
|
||||
setTitle(currentVehicle.title || '');
|
||||
setTitleOther(currentVehicle.title_other || '');
|
||||
|
||||
}, [currentVehicle])
|
||||
// Compliance & Deadlines
|
||||
setInsuranceStartDate(currentVehicle.insurance_start_date ? VehicleService.convertToDate(currentVehicle.insurance_start_date) :
|
||||
(currentVehicle.insurance_expire_on ? VehicleService.convertToDate(currentVehicle.insurance_expire_on) : null));
|
||||
setVehicleRegistrationDate(currentVehicle.vehicle_registration_date ? VehicleService.convertToDate(currentVehicle.vehicle_registration_date) :
|
||||
(currentVehicle.title_registration_on ? VehicleService.convertToDate(currentVehicle.title_registration_on) : null));
|
||||
|
||||
// Check List
|
||||
setChecklist(currentVehicle.checklist || ['']);
|
||||
|
||||
// Additional Information
|
||||
setNote(currentVehicle.note || '');
|
||||
}
|
||||
}, [currentVehicle]);
|
||||
|
||||
const redirectTo = () => {
|
||||
const params = new URLSearchParams(window.location.search);
|
||||
@ -86,64 +137,113 @@ const UpdateVehicle = () => {
|
||||
navigate(`/trans-routes/schedule`);
|
||||
} else {
|
||||
if (redirect === 'list') {
|
||||
navigate(`/vehicles/list`)
|
||||
navigate(`/vehicles/list`);
|
||||
} else {
|
||||
navigate(`/trans-routes/dashboard`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const redirectToView = () => {
|
||||
navigate(`/vehicles/${params.id}`);
|
||||
}
|
||||
|
||||
const addItemToArray = () => {
|
||||
const arr = [...checklist, ''];
|
||||
setChecklist(arr);
|
||||
}
|
||||
|
||||
const saveVechile = () => {
|
||||
const data = {
|
||||
const formatDateForBackend = (date) => {
|
||||
if (!date) return '';
|
||||
return moment(date).format('MM/DD/YYYY');
|
||||
}
|
||||
|
||||
const buildVehicleData = () => {
|
||||
return {
|
||||
// Basic Information
|
||||
vehicle_number: vehicleNumber,
|
||||
tag,
|
||||
ezpass,
|
||||
gps_tag: gps,
|
||||
mileage,
|
||||
capacity,
|
||||
year,
|
||||
responsible_driver: responsibleDriver?.label || '',
|
||||
responsible_driver_id: responsibleDriver?.value || '',
|
||||
capacity: parseInt(capacity) || 0,
|
||||
mileage: parseInt(mileage) || 0,
|
||||
make,
|
||||
vehicle_model: vehicleModel,
|
||||
status: 'active',
|
||||
checklist,
|
||||
note,
|
||||
year,
|
||||
vin,
|
||||
tag,
|
||||
gps_tag: gps,
|
||||
ezpass,
|
||||
has_lift_equip: hasLiftEquip === 'true',
|
||||
insurance_expire_on: moment(insuranceExpireOn).format('MM/DD/YYYY'),
|
||||
title_registration_on: moment(titleRegistrationOn).format('MM/DD/YYYY'),
|
||||
emission_test_on: moment(emissionTestOn).format('MM/DD/YYYY'),
|
||||
oil_change_date: moment(oilChangeDate).format('MM/DD/YYYY')
|
||||
fuel_type: fuelType,
|
||||
title,
|
||||
title_other: titleOther,
|
||||
|
||||
// Compliance & Deadlines
|
||||
insurance_start_date: formatDateForBackend(insuranceStartDate),
|
||||
vehicle_registration_date: formatDateForBackend(vehicleRegistrationDate),
|
||||
|
||||
// Legacy fields for backward compatibility
|
||||
insurance_expire_on: formatDateForBackend(insuranceStartDate),
|
||||
title_registration_on: formatDateForBackend(vehicleRegistrationDate),
|
||||
|
||||
// Check List
|
||||
checklist,
|
||||
|
||||
// Additional Information
|
||||
note,
|
||||
|
||||
// System fields
|
||||
status: 'active'
|
||||
};
|
||||
dispatch(updateVehicle({id: params.id, data, redirectFun: redirectTo}));
|
||||
};
|
||||
|
||||
const validateVehicle = () => {
|
||||
const errors = [];
|
||||
|
||||
// Required fields validation
|
||||
if (!vehicleNumber || vehicleNumber.trim() === '') {
|
||||
errors.push('Vehicle Number');
|
||||
}
|
||||
if (!tag || tag.trim() === '') {
|
||||
errors.push('License Plate');
|
||||
}
|
||||
|
||||
if (errors.length > 0) {
|
||||
window.alert(`Please fill in the following required fields:\n${errors.join('\n')}`);
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
};
|
||||
|
||||
const saveVehicle = () => {
|
||||
if (!validateVehicle()) {
|
||||
return;
|
||||
}
|
||||
|
||||
const data = buildVehicleData();
|
||||
dispatch(updateVehicle({id: params.id, data, redirectFun: redirectToView}));
|
||||
}
|
||||
|
||||
const triggerShowDeleteModal = () => {
|
||||
setShowDeleteModal(true);
|
||||
}
|
||||
|
||||
const closeDeleteModal = () => {
|
||||
setShowDeleteModal(false);
|
||||
}
|
||||
|
||||
const deactivateVehicle = () => {
|
||||
const data = {
|
||||
vehicle_number: vehicleNumber,
|
||||
tag,
|
||||
ezpass,
|
||||
gps_tag: gps,
|
||||
mileage,
|
||||
capacity,
|
||||
year,
|
||||
make,
|
||||
vehicle_model: vehicleModel,
|
||||
status: 'inactive',
|
||||
checklist
|
||||
};
|
||||
const data = buildVehicleData();
|
||||
data.status = 'inactive';
|
||||
dispatch(deleteVehicle({id: params.id, data}));
|
||||
setShowDeleteModal(false);
|
||||
redirectTo();
|
||||
}
|
||||
|
||||
const saveDocuments = () => {
|
||||
if (selectedMothlyFile && monthlyInspectionDate) {
|
||||
if (selectedMonthlyFile && monthlyInspectionDate) {
|
||||
const monthlyFormData = new FormData();
|
||||
monthlyFormData.append('file', selectedMothlyFile);
|
||||
monthlyFormData.append('file', selectedMonthlyFile);
|
||||
VehicleService.uploadVechileFile(monthlyFormData, currentVehicle.id, currentVehicle.vehicle_number, 'monthlyInspection', monthlyInspectionDate);
|
||||
}
|
||||
if (selectedYearlyFile && yearlyInspectionDate) {
|
||||
@ -157,26 +257,68 @@ const UpdateVehicle = () => {
|
||||
const saveRepair = () => {
|
||||
const data = {
|
||||
vehicle: currentVehicle?.id,
|
||||
repair_date: moment(repairDate).format('MM/DD/YYYY'),
|
||||
repair_description: repairDescription,
|
||||
part_name: repairPartName,
|
||||
repair_date: formatDateForBackend(repairReplacementDate),
|
||||
mileage_at_replacement: repairMileage,
|
||||
quantity: repairQuantity,
|
||||
repair_price: repairCost,
|
||||
repair_location: repairLocation,
|
||||
repair_price: repairPrice
|
||||
}
|
||||
next_replacement_reminder: repairNextReminder
|
||||
};
|
||||
VehicleRepairService.createNewVehicleRepair(data).then(result => {
|
||||
const record = result.data;
|
||||
if (repairReceiptFile) {
|
||||
const formData = new FormData();
|
||||
formData.append('file', selectedRepairFile);
|
||||
VehicleService.uploadVechileFile(formData, currentVehicle.id, record.id, 'repair', repairDate).then(() => redirectTo());
|
||||
})
|
||||
formData.append('file', repairReceiptFile);
|
||||
VehicleService.uploadVechileFile(formData, currentVehicle.id, record.id, 'repair', repairReplacementDate).then(() => redirectTo());
|
||||
} else {
|
||||
redirectTo();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Custom styles for react-select
|
||||
const selectStyles = {
|
||||
control: (baseStyles) => ({
|
||||
...baseStyles,
|
||||
minWidth: '200px',
|
||||
borderRadius: '8px'
|
||||
}),
|
||||
indicatorSeparator: () => ({
|
||||
display: 'none'
|
||||
}),
|
||||
dropdownIndicator: (baseStyles) => ({
|
||||
...baseStyles,
|
||||
paddingRight: '8px',
|
||||
color: '#333',
|
||||
'&:hover': {
|
||||
color: '#000',
|
||||
},
|
||||
}),
|
||||
valueContainer: (baseStyles) => ({
|
||||
...baseStyles,
|
||||
height: '43px',
|
||||
padding: '0 8px',
|
||||
}),
|
||||
input: (baseStyles) => ({
|
||||
...baseStyles,
|
||||
margin: '0px',
|
||||
padding: '0px',
|
||||
height: '30px',
|
||||
width: '290px'
|
||||
}),
|
||||
singleValue: (baseStyles) => ({
|
||||
...baseStyles,
|
||||
margin: '0px',
|
||||
}),
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className="list row mb-4">
|
||||
<Breadcrumb>
|
||||
<Breadcrumb.Item>Transportation</Breadcrumb.Item>
|
||||
<Breadcrumb.Item active>
|
||||
<Breadcrumb.Item href="/trans-routes/dashboard">Transportation</Breadcrumb.Item>
|
||||
<Breadcrumb.Item href="/vehicles/list">
|
||||
Vehicles Information
|
||||
</Breadcrumb.Item>
|
||||
<Breadcrumb.Item active>
|
||||
@ -187,159 +329,296 @@ const UpdateVehicle = () => {
|
||||
<h4>Update Vehicle Information <button className="btn btn-link btn-sm" onClick={() => {redirectTo()}}>Back</button></h4>
|
||||
</div>
|
||||
</div>
|
||||
<div className="app-main-content-list-container">
|
||||
<div className="app-main-content-list-container form-page">
|
||||
<div className="app-main-content-list-func-container">
|
||||
<Tabs defaultActiveKey="basicInfo" id="customers-tab">
|
||||
<Tabs activeKey={activeTab} onSelect={(k) => setActiveTab(k)} id="vehicles-tab">
|
||||
<Tab eventKey="basicInfo" title="Basic Information">
|
||||
<h6 className="text-primary">Vehicle Information</h6>
|
||||
<h6 className="text-primary">Basic Information</h6>
|
||||
<div className="app-main-content-fields-section">
|
||||
<div className="me-4">
|
||||
<div className="field-label">Vehicle Number
|
||||
<span className="required">*</span>
|
||||
<div className="field-label">Vehicle Number <span className="required">*</span></div>
|
||||
<input type="text" placeholder="e.g., 101" value={vehicleNumber} onChange={e => setVehicleNumber(e.target.value)}/>
|
||||
</div>
|
||||
<input type="number" placeholder="e.g.,1" value={vehicleNumber || ''} onChange={e => setVehicleNumber(e.target.value)}/>
|
||||
<div className="me-4">
|
||||
<div className="field-label">Responsible Driver</div>
|
||||
<Select
|
||||
value={responsibleDriver}
|
||||
onChange={setResponsibleDriver}
|
||||
options={[{value: '', label: ''}, ...drivers.map(driver => ({
|
||||
value: driver?.id || '',
|
||||
label: driver?.name || '',
|
||||
}))]}
|
||||
styles={selectStyles}
|
||||
placeholder="e.g., John Smith"
|
||||
isClearable
|
||||
/>
|
||||
</div>
|
||||
<div className="me-4">
|
||||
<div className="field-label">Seating Capacity</div>
|
||||
<select value={capacity} onChange={e => setCapacity(e.target.value)}>
|
||||
<option value="">Select...</option>
|
||||
{SEATING_CAPACITY_OPTIONS.map(opt => (
|
||||
<option key={opt.value} value={opt.value}>{opt.label}</option>
|
||||
))}
|
||||
</select>
|
||||
</div>
|
||||
<div className="me-4">
|
||||
<div className="field-label">Mileage</div>
|
||||
<input type="number" placeholder="e.g., 48000" value={mileage} onChange={e => setMileage(e.target.value)}/>
|
||||
</div>
|
||||
<div className="me-4"><div className="field-label">Seating Capacity</div><input type="number" value={capacity || ''} placeholder="e.g.,12" onChange={e => setCapacity(e.target.value)}/></div>
|
||||
<div className="me-4"><div className="field-label">Mileage</div><input type="number" value={mileage || ''} placeholder="e.g.,48000" onChange={e => setMileage(e.target.value)}/></div>
|
||||
<div className="me-4"><div className="field-label">Make</div><input type="text" value={make || ''} placeholder="e.g.,Ford" onChange={e => setMake(e.target.value)}/></div>
|
||||
<div className="me-4"><div className="field-label">Model</div><input type="text" value={vehicleModel || ''} placeholder="e.g.,T350" onChange={e => setVehicleModel(e.target.value)}/></div>
|
||||
</div>
|
||||
<div className="app-main-content-fields-section">
|
||||
<div className="me-4"><div className="field-label">Year</div><input type="text" value={year || ''} placeholder="e.g.,2016" onChange={e => setYear(e.target.value)}/></div>
|
||||
<div className="me-4"><div className="field-label">Vin Number</div><input type="text" value={vin || ''} placeholder="e.g, 1FBAX2CM9KA34959" onChange={e => setVin(e.target.value)}/></div>
|
||||
<div className="me-4"><div className="field-label">Licence Plate</div><input type="text" value={tag || ''} placeholder="e.g.,91579HT" onChange={e => setTag(e.target.value)}/></div>
|
||||
<div className="me-4"><div className="field-label">GPS ID</div><input type="text" value={gps || ''} placeholder="e.g.,609671" onChange={e => setGps(e.target.value)}/></div>
|
||||
<div className="me-4"><div className="field-label">EZPass</div><input type="text" value={ezpass || ''} placeholder="e.g.,NY12345" onChange={e => setEzpass(e.target.value)}/></div>
|
||||
<div className="me-4">
|
||||
<div className="field-label">Make</div>
|
||||
<input type="text" placeholder="e.g., Ford" value={make} onChange={e => setMake(e.target.value)}/>
|
||||
</div>
|
||||
<div className="me-4">
|
||||
<div className="field-label">Model</div>
|
||||
<input type="text" placeholder="e.g., T350" value={vehicleModel} onChange={e => setVehicleModel(e.target.value)}/>
|
||||
</div>
|
||||
<div className="me-4">
|
||||
<div className="field-label">Year</div>
|
||||
<input type="text" placeholder="e.g., 2018" value={year} onChange={e => setYear(e.target.value)}/>
|
||||
</div>
|
||||
<div className="me-4">
|
||||
<div className="field-label">VIN Number</div>
|
||||
<input type="text" placeholder="e.g., 1FBAX2CM9KKA34959" value={vin} onChange={e => setVin(e.target.value)}/>
|
||||
</div>
|
||||
</div>
|
||||
<div className="app-main-content-fields-section">
|
||||
<div className="me-4">
|
||||
<div className="field-label">License Plate <span className="required">*</span></div>
|
||||
<input type="text" placeholder="e.g., 91579HT" value={tag} onChange={e => setTag(e.target.value)}/>
|
||||
</div>
|
||||
<div className="me-4">
|
||||
<div className="field-label">GPS ID</div>
|
||||
<input type="text" placeholder="e.g., 609671" value={gps} onChange={e => setGps(e.target.value)}/>
|
||||
</div>
|
||||
<div className="me-4">
|
||||
<div className="field-label">E-ZPass</div>
|
||||
<input type="text" placeholder="e.g., NY12345" value={ezpass} onChange={e => setEzpass(e.target.value)}/>
|
||||
</div>
|
||||
<div className="me-4">
|
||||
<div className="field-label">Lift Equipped</div>
|
||||
<select value={hasLiftEquip} onChange={e => setHasLiftEquip(e.target.value)}>
|
||||
<option value=""></option>
|
||||
<option value="true">Yes</option>
|
||||
<option value="false">No</option>
|
||||
<option value="">Select...</option>
|
||||
{Object.keys(LIFT_EQUIPPED).map(key => (
|
||||
<option key={key} value={LIFT_EQUIPPED[key]}>{LIFT_EQUIPPED_TEXT[LIFT_EQUIPPED[key]]}</option>
|
||||
))}
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<h6 className="text-primary">Vehicle Maintenance & Compliance</h6>
|
||||
<div className="app-main-content-fields-section">
|
||||
<div className="me-4">
|
||||
<div className="field-label">Last Oil Change Date <span className="field-blurb float-right">1-month due cycle </span></div>
|
||||
<DatePicker selected={oilChangeDate} onChange={(v) => setOilChangeDate(v)} />
|
||||
<div className="field-label">Fuel Type</div>
|
||||
<select value={fuelType} onChange={e => setFuelType(e.target.value)}>
|
||||
<option value="">Select...</option>
|
||||
{Object.keys(FUEL_TYPE).map(key => (
|
||||
<option key={key} value={FUEL_TYPE[key]}>{FUEL_TYPE_TEXT[FUEL_TYPE[key]]}</option>
|
||||
))}
|
||||
</select>
|
||||
</div>
|
||||
<div className="me-4">
|
||||
<div className="field-label">Last Emissions Inspection Date <span className="field-blurb float-right">1-year due cycle </span></div>
|
||||
<DatePicker selected={emissionTestOn} onChange={(v) => setEmissionTestOn(v)} />
|
||||
<div className="field-label">Title</div>
|
||||
<select value={title} onChange={e => setTitle(e.target.value)}>
|
||||
<option value="">Select...</option>
|
||||
{Object.keys(VEHICLE_TITLE).map(key => (
|
||||
<option key={key} value={VEHICLE_TITLE[key]}>{VEHICLE_TITLE_TEXT[VEHICLE_TITLE[key]]}</option>
|
||||
))}
|
||||
</select>
|
||||
</div>
|
||||
{title === 'other' && (
|
||||
<div className="me-4">
|
||||
<div className="field-label">Insurance Expiration Date <span className="field-blurb float-right">1-year due cycle </span></div>
|
||||
<DatePicker selected={insuranceExpireOn} onChange={(v) => setInsuranceExpireOn(v)} />
|
||||
</div>
|
||||
<div className="me-4">
|
||||
<div className="field-label">Title Registration Date <span className="field-blurb float-right">1-year due cycle </span></div>
|
||||
<DatePicker selected={titleRegistrationOn} onChange={(v) => setTitleRegistrationOn(v)} />
|
||||
<div className="field-label">Title (Other)</div>
|
||||
<input type="text" placeholder="Please specify..." value={titleOther} onChange={e => setTitleOther(e.target.value)}/>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<h6 className="text-primary">Check List</h6>
|
||||
<div className="app-main-content-fields-section column">
|
||||
{checklist.map((item, index) => (<div className="mb-4" key={index}><input type="text" value={item} onChange={(e) => setChecklist([...checklist].map((a, index1) => {if (index1 === index) {return e.target.value;} return a;}))}/>
|
||||
{checklist.map((item, index) => (
|
||||
<div className="mb-4" key={index}>
|
||||
<input type="text" placeholder="e.g., Tire pressure" value={item} onChange={(e) => setChecklist([...checklist].map((a, index1) => {if (index1 === index) {return e.target.value;} return a;}))}/>
|
||||
<button className="btn btn-link btn-sm" onClick={(e) => setChecklist([...checklist].filter((value, index1) => index1 != index))}>Remove</button>
|
||||
</div>))}
|
||||
</div>
|
||||
))}
|
||||
<button className="btn btn-link" onClick={() => addItemToArray()}>+Add New Item</button>
|
||||
</div>
|
||||
|
||||
<h6 className="text-primary">Additional Information</h6>
|
||||
<div className="app-main-content-fields-section">
|
||||
<div className="me-4">
|
||||
<div className="field-label">Note</div>
|
||||
<textarea placeholder="Any Extra Details" value={note || ''} onChange={e => setNote(e.target.value)}/>
|
||||
<div className="field-label">Notes and Attachments</div>
|
||||
<textarea placeholder="e.g., Vehicle assigned to Route A" value={note} onChange={e => setNote(e.target.value)} rows={4} style={{width: '400px'}}/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{error && <div className="col-md-12 mb-4 alert alert-danger" role="alert">
|
||||
{error}
|
||||
</div>}
|
||||
<div className="list row mb-5">
|
||||
<div className="col-md-12 col-sm-12 col-xs-12">
|
||||
<button className="btn btn-default btn-sm float-right" onClick={() => redirectTo()}> Cancel </button>
|
||||
<button className="btn btn-primary btn-sm float-right" onClick={() => saveVechile()}> Save </button>
|
||||
|
||||
<button className="btn btn-danger btn-sm me-2 mb-2" onClick={() => triggerShowDeleteModal()}> Delete </button>
|
||||
<button className="btn btn-primary btn-sm float-right" onClick={() => saveVehicle()}> Save </button>
|
||||
</div>
|
||||
</div>
|
||||
</Tab>
|
||||
<Tab eventKey="documents" title="Documents">
|
||||
<h6 className="text-primary">Yearly Vehicle Inspection</h6>
|
||||
|
||||
<Tab eventKey="complianceDeadlines" title="Compliance & Deadlines">
|
||||
<h6 className="text-primary">Compliance & Deadlines</h6>
|
||||
<div className="app-main-content-fields-section">
|
||||
<div className="me-4">
|
||||
<div className="field-label">Vehicle Inspection Date<span className="required">*</span></div>
|
||||
<DatePicker selected={yearlyInspectionDate} onChange={(v) => setYearlyInspectionDate(v)} />
|
||||
<div className="field-label">Insurance Expiration Date</div>
|
||||
<DatePicker
|
||||
selected={insuranceStartDate}
|
||||
onChange={(date) => setInsuranceStartDate(date)}
|
||||
dateFormat="MM/dd/yyyy"
|
||||
placeholderText="e.g., 03/01/2024"
|
||||
className="form-control"
|
||||
/>
|
||||
</div>
|
||||
<div className="me-4">
|
||||
<div className="field-label">Yearly Vehicle Inspection Sheet<span className="required">*</span></div>
|
||||
<label className="custom-file-upload">
|
||||
<Upload width={20} color={"#fff"} className="me-2"></Upload> Upload Files
|
||||
<input
|
||||
type="file"
|
||||
onChange={(e) => setSelectedYearlyFile(e.target.files[0])}
|
||||
<div className="field-label">Vehicle Registration Date</div>
|
||||
<DatePicker
|
||||
selected={vehicleRegistrationDate}
|
||||
onChange={(date) => setVehicleRegistrationDate(date)}
|
||||
dateFormat="MM/dd/yyyy"
|
||||
placeholderText="e.g., 03/01/2024"
|
||||
className="form-control"
|
||||
/>
|
||||
</label>
|
||||
<div className="file-name">{ selectedYearlyFile && selectedYearlyFile?.name }</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<h6 className="text-primary">Monthly Vehicle Inspection</h6>
|
||||
<div className="app-main-content-fields-section">
|
||||
<div className="me-4">
|
||||
<div className="field-label">Vehicle Inspection Date <span className="required">*</span></div>
|
||||
<DatePicker selected={monthlyInspectionDate} onChange={(v) => setMonthlyInspectionDate(v)} />
|
||||
</div>
|
||||
<div className="me-4">
|
||||
<div className="field-label">Monthly Vehicle Inspection Sheet<span className="required">*</span></div>
|
||||
<label className="custom-file-upload">
|
||||
<Upload width={20} color={"#fff"} className="me-2"></Upload> Upload Files
|
||||
<input
|
||||
type="file"
|
||||
onChange={(e) => setSelectedMonthlyFile(e.target.files[0])}
|
||||
/>
|
||||
</label>
|
||||
<div className="file-name">{ selectedMothlyFile && selectedMothlyFile?.name }</div>
|
||||
</div>
|
||||
</div>
|
||||
{error && <div className="col-md-12 mb-4 alert alert-danger" role="alert">
|
||||
{error}
|
||||
</div>}
|
||||
<div className="list row mb-5">
|
||||
<div className="col-md-12 col-sm-12 col-xs-12">
|
||||
<button className="btn btn-default btn-sm float-right" onClick={() => redirectTo()}> Cancel </button>
|
||||
<button className="btn btn-primary btn-sm float-right" onClick={() => saveDocuments()}> Save </button>
|
||||
|
||||
<button className="btn btn-danger btn-sm me-2 mb-2" onClick={() => triggerShowDeleteModal()}> Delete </button>
|
||||
<button className="btn btn-primary btn-sm float-right" onClick={() => saveVehicle()}> Save </button>
|
||||
</div>
|
||||
</div>
|
||||
</Tab>
|
||||
<Tab eventKey="Repair Records" title="Repair Records">
|
||||
<h6 className="text-primary">Repair Log</h6>
|
||||
|
||||
<Tab eventKey="documents" title="Documents & Records">
|
||||
<h6 className="text-primary">Yearly Inspection</h6>
|
||||
<div className="app-main-content-fields-section">
|
||||
<div className="me-4">
|
||||
<div className="field-label">Repair Date <span className="required">*</span></div>
|
||||
<DatePicker selected={repairDate} onChange={(v) => setRepairDate(v)} />
|
||||
</div>
|
||||
<div className="me-4"><div className="field-label">Cost <span className="required">*</span></div>
|
||||
<input type="text" value={repairPrice || ''} placeholder="e.g.,$75" onChange={e => setRepairPrice(e.target.value)}/>
|
||||
</div>
|
||||
<div className="me-4"><div className="field-label">Repair Location <span className="required">*</span></div>
|
||||
<input type="text" value={repairLocation || ''} placeholder="e.g.,LocalGarage" onChange={e => setRepairLocation(e.target.value)}/>
|
||||
</div>
|
||||
</div>
|
||||
<div className="app-main-content-fields-section">
|
||||
<div className="me-4">
|
||||
<div className="field-label">Description <span className="required">*</span></div>
|
||||
<textarea value={repairDescription || ''} onChange={e => setRepairDescription(e.target.value)}/>
|
||||
</div>
|
||||
<div className="me-4">
|
||||
<div className="field-label">Upload Maintenance Files</div>
|
||||
<label className="custom-file-upload">
|
||||
<Upload width={20} color={"#fff"} className="me-2"></Upload> Upload Files
|
||||
<input
|
||||
type="file"
|
||||
onChange={(e) => setSelectedRepairFile(e.target.files[0])}
|
||||
<div className="field-label">Yearly Inspection Date</div>
|
||||
<DatePicker
|
||||
selected={yearlyInspectionDate}
|
||||
onChange={(date) => setYearlyInspectionDate(date)}
|
||||
dateFormat="MM/dd/yyyy"
|
||||
placeholderText="e.g., 03/01/2024"
|
||||
className="form-control"
|
||||
/>
|
||||
</div>
|
||||
<div className="me-4">
|
||||
<div className="field-label">Yearly Inspection File</div>
|
||||
<label className="custom-file-upload">
|
||||
<Upload width={20} color={"#fff"} className="me-2"></Upload> Upload
|
||||
<input type="file" onChange={(e) => setSelectedYearlyFile(e.target.files[0])}/>
|
||||
</label>
|
||||
<div className="file-name">{ selectedRepairFile && selectedRepairFile?.name }</div>
|
||||
<div className="file-name">{selectedYearlyFile?.name}</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<h6 className="text-primary">Monthly Inspection</h6>
|
||||
<div className="app-main-content-fields-section">
|
||||
<div className="me-4">
|
||||
<div className="field-label">Monthly Inspection Date</div>
|
||||
<DatePicker
|
||||
selected={monthlyInspectionDate}
|
||||
onChange={(date) => setMonthlyInspectionDate(date)}
|
||||
dateFormat="MM/dd/yyyy"
|
||||
placeholderText="e.g., 03/01/2024"
|
||||
className="form-control"
|
||||
/>
|
||||
</div>
|
||||
<div className="me-4">
|
||||
<div className="field-label">Monthly Inspection File</div>
|
||||
<label className="custom-file-upload">
|
||||
<Upload width={20} color={"#fff"} className="me-2"></Upload> Upload
|
||||
<input type="file" onChange={(e) => setSelectedMonthlyFile(e.target.files[0])}/>
|
||||
</label>
|
||||
<div className="file-name">{selectedMonthlyFile?.name}</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="list row mb-3">
|
||||
<div className="col-md-12 col-sm-12 col-xs-12">
|
||||
<button className="btn btn-primary btn-sm" onClick={() => saveDocuments()}> Upload Documents </button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<h6 className="text-primary">Repair & Maintenance Record</h6>
|
||||
<div className="app-main-content-fields-section">
|
||||
<div className="me-4">
|
||||
<div className="field-label">Part Name</div>
|
||||
<select value={repairPartName} onChange={e => setRepairPartName(e.target.value)}>
|
||||
<option value="">Select...</option>
|
||||
{Object.keys(REPAIR_PART_NAME).map(key => (
|
||||
<option key={key} value={REPAIR_PART_NAME[key]}>{REPAIR_PART_NAME_TEXT[REPAIR_PART_NAME[key]]}</option>
|
||||
))}
|
||||
</select>
|
||||
</div>
|
||||
<div className="me-4">
|
||||
<div className="field-label">Replacement Date</div>
|
||||
<DatePicker
|
||||
selected={repairReplacementDate}
|
||||
onChange={(date) => setRepairReplacementDate(date)}
|
||||
dateFormat="MM/dd/yyyy"
|
||||
placeholderText="e.g., 03/01/2024"
|
||||
className="form-control"
|
||||
/>
|
||||
</div>
|
||||
<div className="me-4">
|
||||
<div className="field-label">Mileage at Replacement</div>
|
||||
<input type="text" placeholder="e.g., 48,000" value={repairMileage} onChange={e => setRepairMileage(e.target.value)}/>
|
||||
</div>
|
||||
<div className="me-4">
|
||||
<div className="field-label">Quantity</div>
|
||||
<select value={repairQuantity} onChange={e => setRepairQuantity(e.target.value)}>
|
||||
<option value="">Select...</option>
|
||||
{QUANTITY_OPTIONS.map(opt => (
|
||||
<option key={opt.value} value={opt.value}>{opt.label}</option>
|
||||
))}
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div className="app-main-content-fields-section">
|
||||
<div className="me-4">
|
||||
<div className="field-label">Cost</div>
|
||||
<input type="text" placeholder="e.g., $250.00" value={repairCost} onChange={e => setRepairCost(e.target.value)}/>
|
||||
</div>
|
||||
<div className="me-4">
|
||||
<div className="field-label">Location</div>
|
||||
<input type="text" placeholder="e.g., Rockville Auto Center" value={repairLocation} onChange={e => setRepairLocation(e.target.value)}/>
|
||||
</div>
|
||||
<div className="me-4">
|
||||
<div className="field-label">Receipt Upload</div>
|
||||
<label className="custom-file-upload">
|
||||
<Upload width={20} color={"#fff"} className="me-2"></Upload> Upload
|
||||
<input type="file" onChange={(e) => setRepairReceiptFile(e.target.files[0])}/>
|
||||
</label>
|
||||
<div className="file-name">{repairReceiptFile?.name}</div>
|
||||
</div>
|
||||
<div className="me-4">
|
||||
<div className="field-label">Next Replacement Reminder</div>
|
||||
<input type="text" placeholder="e.g., 78,000" value={repairNextReminder} onChange={e => setRepairNextReminder(e.target.value)}/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{error && <div className="col-md-12 mb-4 alert alert-danger" role="alert">
|
||||
{error}
|
||||
</div>}
|
||||
<div className="list row mb-5">
|
||||
<div className="col-md-12 col-sm-12 col-xs-12">
|
||||
<button className="btn btn-default btn-sm float-right" onClick={() => redirectTo()}> Cancel </button>
|
||||
<button className="btn btn-primary btn-sm float-right" onClick={() => saveRepair()}> Save </button>
|
||||
|
||||
<button className="btn btn-primary btn-sm float-right" onClick={() => saveRepair()}> Save Repair Record </button>
|
||||
</div>
|
||||
</div>
|
||||
</Tab>
|
||||
</Tabs>
|
||||
@ -348,57 +627,22 @@ const UpdateVehicle = () => {
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{/* <div className="list row mb-4">
|
||||
<div className="col-md-12 text-primary">
|
||||
<h5>Update Vehicle <button className="btn btn-link btn-sm" onClick={() => {redirectTo()}}>Back</button></h5>
|
||||
</div>
|
||||
</div>
|
||||
<div className="list row mb-4">
|
||||
<div className="col-md-4 mb-4">
|
||||
<div>Vehicle Number(*):</div> <input type="number" value={vehicleNumber || ''} onChange={e => setVehicleNumber(e.target.value)}/>
|
||||
</div>
|
||||
<div className="col-md-4 mb-4">
|
||||
<div>Tag(*):</div> <input type="text" value={tag || ''} onChange={e => setTag(e.target.value)}/>
|
||||
</div>
|
||||
<div className="col-md-4 mb-4">
|
||||
<div>EZ Pass:</div> <input type="text" value={ezpass || ''} onChange={e => setEzpass(e.target.value)}/>
|
||||
</div>
|
||||
<div className="col-md-4 mb-4">
|
||||
<div>GPS:</div> <input type="text" value={gps || ''} onChange={e => setGps(e.target.value)}/>
|
||||
</div>
|
||||
<div className="col-md-4 mb-4">
|
||||
<div>Make:</div> <input type="text" value={make || ''} onChange={e => setMake(e.target.value)}/>
|
||||
</div>
|
||||
<div className="col-md-4 mb-4">
|
||||
<div>Model:</div> <input type="text" value={vehicleModel || ''} onChange={e => setVehicleModel(e.target.value)}/>
|
||||
</div>
|
||||
<div className="col-md-4 mb-4">
|
||||
<div>Year:</div> <input type="text" value={year || ''} onChange={e => setYear(e.target.value)}/>
|
||||
</div>
|
||||
<div className="col-md-4 mb-4">
|
||||
<div>Mileage(*):</div> <input type="number" value={mileage || ''} onChange={e => setMileage(e.target.value)}/>
|
||||
</div>
|
||||
<div className="col-md-4 mb-4">
|
||||
<div>Capacity(*):</div> <input type="number" value={capacity || ''} onChange={e => setCapacity(e.target.value)}/>
|
||||
</div>
|
||||
<div className="col-md-12 mb-4">
|
||||
<div>Checklist(*):</div>
|
||||
{checklist.map((item, index) => (<div className="mb-4" key={index}><input type="text" value={item} onChange={(e) => setChecklist([...checklist].map((a, index1) => {if (index1 === index) {return e.target.value;} return a;}))}/>
|
||||
<button className="btn btn-link btn-sm" onClick={(e) => setChecklist([...checklist].filter((value, index1) => index1 != index))}>Remove</button>
|
||||
</div>))}
|
||||
<button className="btn btn-link" onClick={() => addItemToArray()}>+Add New Item</button>
|
||||
</div>
|
||||
</div>
|
||||
<div className="list row mb-5">
|
||||
{error && <div className="col-md-12 mb-4 alert alert-danger" role="alert">
|
||||
{error}
|
||||
</div>}
|
||||
<div className="col-md-6 col-sm-6 col-xs-12">
|
||||
<button className="btn btn-primary btn-sm me-2 mb-2" onClick={() => saveVechile()}> Save </button>
|
||||
<button className="btn btn-danger btn-sm me-2 mb-2" onClick={() => deactivateVehicle()}> Delete </button>
|
||||
<button className="btn btn-default btn-sm mb-2" onClick={() => redirectTo()}> Cancel </button>
|
||||
</div>
|
||||
</div> */}
|
||||
<Modal show={showDeleteModal} onHide={() => closeDeleteModal()}>
|
||||
<Modal.Header closeButton>
|
||||
<Modal.Title>Delete Vehicle</Modal.Title>
|
||||
</Modal.Header>
|
||||
<Modal.Body>
|
||||
<div>Are you sure you want to delete this vehicle?</div>
|
||||
</Modal.Body>
|
||||
<Modal.Footer>
|
||||
<Button variant="secondary" onClick={() => closeDeleteModal()}>
|
||||
No
|
||||
</Button>
|
||||
<Button variant="primary" onClick={() => deactivateVehicle()}>
|
||||
Yes
|
||||
</Button>
|
||||
</Modal.Footer>
|
||||
</Modal>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
@ -68,24 +68,24 @@ const VehicleList = () => {
|
||||
useEffect(() => {
|
||||
if (showInactive) {
|
||||
setFilteredVehicles(vehicles && vehicles.filter(item =>
|
||||
(item?.vehicle_number?.toString()?.includes(keyword.toLowerCase()) ||
|
||||
item?.tag?.toLowerCase()?.includes(keyword.toLowerCase()) ||
|
||||
item?.ezpass?.toLowerCase()?.includes(keyword.toLowerCase()) ||
|
||||
item?.gps_tag?.toLowerCase()?.includes(keyword.toLowerCase()) ||
|
||||
item?.make?.toLowerCase()?.includes(keyword.toLowerCase()) ||
|
||||
item?.vehicle_model?.toLowerCase()?.includes(keyword.toLowerCase()) ||
|
||||
item?.year?.toLowerCase()?.includes(keyword.toLowerCase())) &&
|
||||
(item?.vehicle_number?.toString()?.startsWith(keyword.toLowerCase()) ||
|
||||
item?.tag?.toLowerCase()?.startsWith(keyword.toLowerCase()) ||
|
||||
item?.ezpass?.toLowerCase()?.startsWith(keyword.toLowerCase()) ||
|
||||
item?.gps_tag?.toLowerCase()?.startsWith(keyword.toLowerCase()) ||
|
||||
item?.make?.toLowerCase()?.startsWith(keyword.toLowerCase()) ||
|
||||
item?.vehicle_model?.toLowerCase()?.startsWith(keyword.toLowerCase()) ||
|
||||
item?.year?.toLowerCase()?.startsWith(keyword.toLowerCase())) &&
|
||||
item?.status?.toLowerCase() !== 'active'
|
||||
))
|
||||
} else {
|
||||
setFilteredVehicles(vehicles && vehicles.filter(item =>
|
||||
(item?.vehicle_number?.toString()?.includes(keyword.toLowerCase()) ||
|
||||
item?.tag?.toLowerCase()?.includes(keyword.toLowerCase()) ||
|
||||
item?.ezpass?.toLowerCase()?.includes(keyword.toLowerCase()) ||
|
||||
item?.gps_tag?.toLowerCase()?.includes(keyword.toLowerCase()) ||
|
||||
item?.make?.toLowerCase()?.includes(keyword.toLowerCase()) ||
|
||||
item?.vehicle_model?.toLowerCase()?.includes(keyword.toLowerCase()) ||
|
||||
item?.year?.toLowerCase()?.includes(keyword.toLowerCase())) &&
|
||||
(item?.vehicle_number?.toString()?.startsWith(keyword.toLowerCase()) ||
|
||||
item?.tag?.toLowerCase()?.startsWith(keyword.toLowerCase()) ||
|
||||
item?.ezpass?.toLowerCase()?.startsWith(keyword.toLowerCase()) ||
|
||||
item?.gps_tag?.toLowerCase()?.startsWith(keyword.toLowerCase()) ||
|
||||
item?.make?.toLowerCase()?.startsWith(keyword.toLowerCase()) ||
|
||||
item?.vehicle_model?.toLowerCase()?.startsWith(keyword.toLowerCase()) ||
|
||||
item?.year?.toLowerCase()?.startsWith(keyword.toLowerCase())) &&
|
||||
item?.status?.toLowerCase() === 'active'
|
||||
))
|
||||
}
|
||||
@ -203,7 +203,7 @@ const VehicleList = () => {
|
||||
<>
|
||||
<div className="list row mb-4">
|
||||
<Breadcrumb>
|
||||
<Breadcrumb.Item>Transportation</Breadcrumb.Item>
|
||||
<Breadcrumb.Item href="/trans-routes/dashboard">Transportation</Breadcrumb.Item>
|
||||
<Breadcrumb.Item active>
|
||||
Vehicle Information
|
||||
</Breadcrumb.Item>
|
||||
@ -214,7 +214,7 @@ const VehicleList = () => {
|
||||
</h4>
|
||||
</div>
|
||||
</div>
|
||||
<div className="app-main-content-list-container">
|
||||
<div className="app-main-content-list-container list-page">
|
||||
<div className="app-main-content-list-func-container">
|
||||
<Tabs defaultActiveKey="activeVehicles" id="vehicles-tab" onSelect={(k) => showArchive(k)}>
|
||||
<Tab eventKey="activeVehicles" title="Active Vehicles">
|
||||
|
||||
@ -1,18 +1,23 @@
|
||||
import React, {useState, useEffect} from "react";
|
||||
import { useNavigate, useParams } from "react-router-dom";
|
||||
import { useSelector,useDispatch } from "react-redux";
|
||||
import { useNavigate, useParams, useSearchParams } from "react-router-dom";
|
||||
import { useSelector, useDispatch } from "react-redux";
|
||||
import { AuthService, VehicleRepairService, VehicleService } from "../../services";
|
||||
import { vehicleSlice, selectVehicleError } from "./../../store";
|
||||
import { Spinner, Breadcrumb, BreadcrumbItem, Tabs, Tab } from "react-bootstrap";
|
||||
import { Download, PencilSquare, Archive } from "react-bootstrap-icons";
|
||||
import moment from "moment";
|
||||
import { Export } from "../../shared/components";
|
||||
import {
|
||||
FUEL_TYPE_TEXT,
|
||||
VEHICLE_TITLE_TEXT,
|
||||
REPAIR_PART_NAME_TEXT
|
||||
} from "../../shared";
|
||||
|
||||
const ViewVehicle = () => {
|
||||
const navigate = useNavigate();
|
||||
const dispatch = useDispatch();
|
||||
const urlParams = useParams();
|
||||
const [currentVehicle, setCurrentVehicle] = useState(undefined);
|
||||
const [searchParams] = useSearchParams();
|
||||
const [monthlyDocs, setMonthlyDocs] = useState([]);
|
||||
const [yearlyDocs, setYearlyDocs] = useState([]);
|
||||
const [repairs, setRepairs] = useState([]);
|
||||
@ -27,26 +32,27 @@ const ViewVehicle = () => {
|
||||
const [filteredYearlyDocs, setFilteredYearlyDocs] = useState(yearlyDocs);
|
||||
const [filteredRepairs, setFilteredRepairs] = useState(repairs);
|
||||
const { updateVehicle, deleteVehicle, fetchAllVehicles } = vehicleSlice.actions;
|
||||
const [currentTab, setCurrentTab] = useState('basicInfo')
|
||||
const [currentTab, setCurrentTab] = useState(searchParams.get('tab') || 'basicInfo');
|
||||
const [currentVehicle, setCurrentVehicle] = useState(undefined);
|
||||
|
||||
const redirectTo = () => {
|
||||
navigate(`/vehicles/list`)
|
||||
navigate(`/vehicles/list`);
|
||||
}
|
||||
|
||||
const goToEdit = (id) => {
|
||||
navigate(`/vehicles/edit/${id}?redirect=list`)
|
||||
navigate(`/vehicles/edit/${id}?redirect=list&tab=${currentTab}`);
|
||||
}
|
||||
|
||||
const download = () => {
|
||||
const downloadArr = [...selectedItemsMonthly, ...selectedItemsYearly];
|
||||
downloadArr.forEach((url) => {
|
||||
const a = document.createElement('a');
|
||||
a.href= url;
|
||||
a.href = url;
|
||||
a.download = '';
|
||||
document.body.append(a);
|
||||
a.click();
|
||||
document.body.removeChild(a);
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
const deactivateVehicle = () => {
|
||||
@ -64,7 +70,6 @@ const ViewVehicle = () => {
|
||||
if (prefix) {
|
||||
const arr2 = prefix.split('_');
|
||||
const dateNumber = arr2[arr2.length - 1];
|
||||
|
||||
return dateNumber ? new Date(parseInt(dateNumber)).toLocaleDateString('en-US', {month: '2-digit', day: '2-digit', year: 'numeric'}) : moment().format('MM/DD/YYYY');
|
||||
} else {
|
||||
return moment().format('MM/DD/YYYY');
|
||||
@ -78,7 +83,6 @@ const ViewVehicle = () => {
|
||||
};
|
||||
const getAllRepairs = async (vid) => {
|
||||
const v_repairs = (await VehicleRepairService.getAll(vid)).data;
|
||||
console.log('repairs', v_repairs);
|
||||
setRepairs(v_repairs);
|
||||
}
|
||||
if (!AuthService.canViewVechiles()) {
|
||||
@ -91,7 +95,7 @@ const ViewVehicle = () => {
|
||||
setCurrentVehicle(data.data);
|
||||
getAllDocuments(data.data?.id, data.data?.vehicle_number);
|
||||
getAllRepairs(data.data?.id);
|
||||
})
|
||||
});
|
||||
} else {
|
||||
getAllDocuments(currentVehicle?.id, currentVehicle?.vehicle_number);
|
||||
getAllRepairs(currentVehicle?.id);
|
||||
@ -104,8 +108,14 @@ const ViewVehicle = () => {
|
||||
}, [keyword, yearlyDocs, monthlyDocs]);
|
||||
|
||||
useEffect(() => {
|
||||
setFilteredRepairs(repairs?.filter(item => item?.repair_description?.toLowerCase().includes(keyword.toLowerCase()) || item?.repair_date?.includes(keyword.toLowerCase()) || item?.repair_location?.toLowerCase().includes(keyword.toLowerCase()) || item?.repair_price?.toLowerCase().includes(keyword.toLowerCase()) ));
|
||||
}, [keyword, repairs])
|
||||
setFilteredRepairs(repairs?.filter(item =>
|
||||
item?.repair_description?.toLowerCase().includes(keyword.toLowerCase()) ||
|
||||
item?.repair_date?.includes(keyword.toLowerCase()) ||
|
||||
item?.repair_location?.toLowerCase().includes(keyword.toLowerCase()) ||
|
||||
item?.repair_price?.toLowerCase().includes(keyword.toLowerCase()) ||
|
||||
item?.part_name?.toLowerCase().includes(keyword.toLowerCase())
|
||||
));
|
||||
}, [keyword, repairs]);
|
||||
|
||||
useEffect(() => {
|
||||
const newYearlyDocs = [...yearlyDocs];
|
||||
@ -114,7 +124,7 @@ const ViewVehicle = () => {
|
||||
});
|
||||
setYearlyDocs(
|
||||
sortingMonthly.order === 'asc' ? sortedYearlyDocs : sortedYearlyDocs.reverse()
|
||||
)
|
||||
);
|
||||
}, [sortingYearly]);
|
||||
|
||||
useEffect(() => {
|
||||
@ -124,7 +134,7 @@ const ViewVehicle = () => {
|
||||
});
|
||||
setMonthlyDocs(
|
||||
sortingMonthly.order === 'asc' ? sortedMonthlyDocs : sortedMonthlyDocs.reverse()
|
||||
)
|
||||
);
|
||||
}, [sortingMonthly]);
|
||||
|
||||
useEffect(() => {
|
||||
@ -134,73 +144,41 @@ const ViewVehicle = () => {
|
||||
});
|
||||
setRepairs(
|
||||
sortingRepair.order === 'asc' ? sortedRepairs : sortedRepairs.reverse()
|
||||
)
|
||||
);
|
||||
}, [sortingRepair]);
|
||||
|
||||
// Helper to format Yes/No values
|
||||
const formatYesNo = (value) => {
|
||||
if (value === true) return 'Yes';
|
||||
if (value === false) return 'No';
|
||||
return value || '';
|
||||
};
|
||||
|
||||
const columnsMonthly = [
|
||||
{
|
||||
key: 'name',
|
||||
label: 'Monthly Vehicle Inspection Sheet'
|
||||
},
|
||||
{
|
||||
key: 'inspectionDate',
|
||||
label: 'Vehicle Inspection Date'
|
||||
},
|
||||
{
|
||||
key: 'createdAt',
|
||||
label: 'Date Added'
|
||||
}
|
||||
{ key: 'name', label: 'Monthly Vehicle Inspection Sheet' },
|
||||
{ key: 'inspectionDate', label: 'Vehicle Inspection Date' },
|
||||
{ key: 'createdAt', label: 'Date Added' }
|
||||
];
|
||||
|
||||
const columnsYearly = [
|
||||
{
|
||||
key: 'name',
|
||||
label: 'Yearly Vehicle Inspection Sheet'
|
||||
},
|
||||
{
|
||||
key: 'inspectionDate',
|
||||
label: 'Vehicle Inspection Date'
|
||||
},
|
||||
{
|
||||
key: 'createdAt',
|
||||
label: 'Date Added'
|
||||
}
|
||||
{ key: 'name', label: 'Yearly Vehicle Inspection Sheet' },
|
||||
{ key: 'inspectionDate', label: 'Vehicle Inspection Date' },
|
||||
{ key: 'createdAt', label: 'Date Added' }
|
||||
];
|
||||
|
||||
const columnsRepair = [
|
||||
{
|
||||
key: 'repair_description',
|
||||
label: 'Repair Description',
|
||||
show: true
|
||||
},
|
||||
{
|
||||
key: 'repair_date',
|
||||
label: 'Repair Date',
|
||||
show: true
|
||||
},
|
||||
{
|
||||
key: 'repair_price',
|
||||
label: 'Cost',
|
||||
show: true
|
||||
},
|
||||
{
|
||||
key: 'repair_location',
|
||||
label: 'Repair Location',
|
||||
show: true
|
||||
},
|
||||
{
|
||||
key: 'create_date',
|
||||
label: 'Date Added',
|
||||
show: true
|
||||
}
|
||||
{ key: 'part_name', label: 'Part Name', show: true },
|
||||
{ key: 'repair_date', label: 'Replacement Date', show: true },
|
||||
{ key: 'mileage_at_replacement', label: 'Mileage', show: true },
|
||||
{ key: 'quantity', label: 'Quantity', show: true },
|
||||
{ key: 'repair_price', label: 'Cost', show: true },
|
||||
{ key: 'repair_location', label: 'Location', show: true },
|
||||
{ key: 'next_replacement_reminder', label: 'Next Reminder', show: true },
|
||||
{ key: 'create_date', label: 'Date Added', show: true }
|
||||
];
|
||||
|
||||
const sortTableWithFieldMonthly = (key) => {
|
||||
let newSorting = {
|
||||
key,
|
||||
order: 'asc',
|
||||
}
|
||||
|
||||
let newSorting = { key, order: 'asc' };
|
||||
if (sortingMonthly.key === key && sortingMonthly.order === 'asc') {
|
||||
newSorting = {...newSorting, order: 'desc'};
|
||||
}
|
||||
@ -208,11 +186,7 @@ const ViewVehicle = () => {
|
||||
}
|
||||
|
||||
const sortTableWithFieldYearly = (key) => {
|
||||
let newSorting = {
|
||||
key,
|
||||
order: 'asc',
|
||||
}
|
||||
|
||||
let newSorting = { key, order: 'asc' };
|
||||
if (sortingYearly.key === key && sortingYearly.order === 'asc') {
|
||||
newSorting = {...newSorting, order: 'desc'};
|
||||
}
|
||||
@ -220,11 +194,7 @@ const ViewVehicle = () => {
|
||||
}
|
||||
|
||||
const sortTableWithFieldRepair = (key) => {
|
||||
let newSorting = {
|
||||
key,
|
||||
order: 'asc',
|
||||
}
|
||||
|
||||
let newSorting = { key, order: 'asc' };
|
||||
if (sortingRepair.key === key && sortingRepair.order === 'asc') {
|
||||
newSorting = {...newSorting, order: 'desc'};
|
||||
}
|
||||
@ -252,9 +222,9 @@ const ViewVehicle = () => {
|
||||
const toggleSelectedAllItemsRepair = () => {
|
||||
if (selectedItemsRepair.length !== filteredRepairs.length || filteredRepairs.length === 0) {
|
||||
const newSelectedItems = [...filteredRepairs].map((doc) => doc.id);
|
||||
selectedItemsRepair(newSelectedItems);
|
||||
setSelectedItemsRepair(newSelectedItems);
|
||||
} else {
|
||||
selectedItemsRepair([]);
|
||||
setSelectedItemsRepair([]);
|
||||
}
|
||||
}
|
||||
|
||||
@ -309,7 +279,7 @@ const ViewVehicle = () => {
|
||||
}
|
||||
|
||||
const checkSelectAllRepair = () => {
|
||||
return selectedItemsRepair.length === selectedItemsRepair.length && selectedItemsRepair.length > 0;
|
||||
return selectedItemsRepair.length === filteredRepairs.length && selectedItemsRepair.length > 0;
|
||||
}
|
||||
|
||||
const changeTab = (k) => {
|
||||
@ -320,107 +290,116 @@ const ViewVehicle = () => {
|
||||
setSortingRepair({key: '', order: ''});
|
||||
}
|
||||
|
||||
const tableMonthly = <div className="list row mb-4">
|
||||
const tableMonthly = (
|
||||
<div className="list row mb-4">
|
||||
<div className="col-md-12">
|
||||
<table className="personnel-info-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th className="th-checkbox"><input type="checkbox" checked={checkSelectAllMonthly()} onClick={() => toggleSelectedAllItemsMonthly()}></input></th>
|
||||
<th className="th-index">No.</th>
|
||||
{
|
||||
columnsMonthly.map((column, index) => <th className="sortable-header" key={index}>
|
||||
{columnsMonthly.map((column, index) => (
|
||||
<th className="sortable-header" key={index}>
|
||||
{column.label} <span className="float-right" onClick={() => sortTableWithFieldMonthly(column.key)}><img src={`/images/${getSortingImgMonthly(column.key)}.png`}></img></span>
|
||||
</th>)
|
||||
}
|
||||
</th>
|
||||
))}
|
||||
</tr>
|
||||
|
||||
</thead>
|
||||
<tbody>
|
||||
{
|
||||
filteredMonthlyDocs.map((doc, index) => <tr key={doc.url}>
|
||||
<td className="td-checkbox"><input type="checkbox" checked={selectedItemsMonthly.includes(doc?.url)} onClick={()=>toggleItemMonthly(doc?.url)}/></td>
|
||||
{filteredMonthlyDocs.map((doc, index) => (
|
||||
<tr key={doc.url}>
|
||||
<td className="td-checkbox"><input type="checkbox" checked={selectedItemsMonthly.includes(doc?.url)} onClick={() => toggleItemMonthly(doc?.url)}/></td>
|
||||
<td className="td-index">{index + 1}</td>
|
||||
<td> <PencilSquare size={16} className="clickable me-2" onClick={() => goToEdit(currentVehicle?.id)}></PencilSquare>
|
||||
<a className="btn btn-link btn-sm" href={doc?.url} target="_blank">{doc?.name}</a> </td>
|
||||
<td>
|
||||
<PencilSquare size={16} className="clickable me-2" onClick={() => goToEdit(currentVehicle?.id)}></PencilSquare>
|
||||
<a className="btn btn-link btn-sm" href={doc?.url} target="_blank">{doc?.name}</a>
|
||||
</td>
|
||||
<td>{doc?.inspectionDate}</td>
|
||||
<td>{new Date(doc?.createdAt).toLocaleDateString('en-US', {month: '2-digit', day: '2-digit', year: 'numeric'})}</td>
|
||||
</tr>)
|
||||
}
|
||||
</tr>
|
||||
))}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>;
|
||||
</div>
|
||||
);
|
||||
|
||||
const tableYearly = <div className="list row mb-4">
|
||||
const tableYearly = (
|
||||
<div className="list row mb-4">
|
||||
<div className="col-md-12">
|
||||
<table className="personnel-info-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th className="th-checkbox"><input type="checkbox" checked={checkSelectAllYearly()} onClick={() => toggleSelectedAllItemsYearly()}></input></th>
|
||||
<th className="th-index">No.</th>
|
||||
{
|
||||
columnsYearly.map((column, index) => <th className="sortable-header" key={index}>
|
||||
{columnsYearly.map((column, index) => (
|
||||
<th className="sortable-header" key={index}>
|
||||
{column.label} <span className="float-right" onClick={() => sortTableWithFieldYearly(column.key)}><img src={`/images/${getSortingImgYearly(column.key)}.png`}></img></span>
|
||||
</th>)
|
||||
}
|
||||
</th>
|
||||
))}
|
||||
</tr>
|
||||
|
||||
</thead>
|
||||
<tbody>
|
||||
{
|
||||
filteredYearlyDocs.map((doc, index) => <tr key={doc.url}>
|
||||
<td className="td-checkbox"><input type="checkbox" checked={selectedItemsYearly.includes(doc?.url)} onClick={()=>toggleItemYearly(doc?.url)}/></td>
|
||||
{filteredYearlyDocs.map((doc, index) => (
|
||||
<tr key={doc.url}>
|
||||
<td className="td-checkbox"><input type="checkbox" checked={selectedItemsYearly.includes(doc?.url)} onClick={() => toggleItemYearly(doc?.url)}/></td>
|
||||
<td className="td-index">{index + 1}</td>
|
||||
<td> <PencilSquare size={16} className="clickable me-2" onClick={() => goToEdit(currentVehicle?.id)}></PencilSquare>
|
||||
<a className="btn btn-link btn-sm" href={doc?.url} target="_blank">{doc?.name}</a> </td>
|
||||
<td>
|
||||
<PencilSquare size={16} className="clickable me-2" onClick={() => goToEdit(currentVehicle?.id)}></PencilSquare>
|
||||
<a className="btn btn-link btn-sm" href={doc?.url} target="_blank">{doc?.name}</a>
|
||||
</td>
|
||||
<td>{doc?.inspectionDate}</td>
|
||||
<td>{new Date(doc?.createdAt).toLocaleDateString('en-US', {month: '2-digit', day: '2-digit', year: 'numeric'})}</td>
|
||||
</tr>)
|
||||
}
|
||||
</tr>
|
||||
))}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>;
|
||||
</div>
|
||||
);
|
||||
|
||||
|
||||
const tableRepair = <div className="list row mb-4">
|
||||
const tableRepair = (
|
||||
<div className="list row mb-4">
|
||||
<div className="col-md-12">
|
||||
<table className="personnel-info-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th className="th-checkbox"><input type="checkbox" checked={checkSelectAllRepair()} onClick={() => toggleSelectedAllItemsRepair()}></input></th>
|
||||
<th className="th-index">No.</th>
|
||||
{
|
||||
columnsRepair.map((column, index) => <th className="sortable-header" key={index}>
|
||||
{columnsRepair.map((column, index) => (
|
||||
<th className="sortable-header" key={index}>
|
||||
{column.label} <span className="float-right" onClick={() => sortTableWithFieldRepair(column.key)}><img src={`/images/${getSortingImgRepair(column.key)}.png`}></img></span>
|
||||
</th>)
|
||||
}
|
||||
</th>
|
||||
))}
|
||||
</tr>
|
||||
|
||||
</thead>
|
||||
<tbody>
|
||||
{
|
||||
filteredRepairs?.map((repair, index) => <tr key={repair.id}>
|
||||
<td className="td-checkbox"><input type="checkbox" checked={selectedItemsRepair.includes(repair?.id)} onClick={()=>toggleItemRepair(repair?.id)}/></td>
|
||||
{filteredRepairs?.map((repair, index) => (
|
||||
<tr key={repair.id}>
|
||||
<td className="td-checkbox"><input type="checkbox" checked={selectedItemsRepair.includes(repair?.id)} onClick={() => toggleItemRepair(repair?.id)}/></td>
|
||||
<td className="td-index">{index + 1}</td>
|
||||
<td> {repair?.repair_description}</td>
|
||||
<td>{REPAIR_PART_NAME_TEXT[repair?.part_name] || repair?.part_name || repair?.repair_description}</td>
|
||||
<td>{repair?.repair_date}</td>
|
||||
<td>{repair?.mileage_at_replacement}</td>
|
||||
<td>{repair?.quantity}</td>
|
||||
<td>{repair?.repair_price}</td>
|
||||
<td>{repair?.repair_location}</td>
|
||||
<td>{repair?.next_replacement_reminder}</td>
|
||||
<td>{repair?.create_date ? new Date(repair?.create_date).toLocaleDateString('en-US', {month: '2-digit', day: '2-digit', year: 'numeric'}) : repair?.repair_date}</td>
|
||||
</tr>)
|
||||
}
|
||||
</tr>
|
||||
))}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>;
|
||||
</div>
|
||||
);
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className="list row mb-4">
|
||||
<Breadcrumb>
|
||||
<Breadcrumb.Item>Transportation</Breadcrumb.Item>
|
||||
<Breadcrumb.Item active>
|
||||
<Breadcrumb.Item href="/trans-routes/dashboard">Transportation</Breadcrumb.Item>
|
||||
<Breadcrumb.Item href="/vehicles/list">
|
||||
Vehicles Information
|
||||
</Breadcrumb.Item>
|
||||
<Breadcrumb.Item active>
|
||||
@ -429,18 +408,22 @@ const tableRepair = <div className="list row mb-4">
|
||||
</Breadcrumb>
|
||||
</div>
|
||||
<div className="col-md-12 text-primary">
|
||||
<h4>View Vechile Information <button className="btn btn-link btn-sm" onClick={() => {redirectTo()}}>Back</button></h4>
|
||||
<h4>View Vehicle Information <button className="btn btn-link btn-sm" onClick={() => {redirectTo()}}>Back</button></h4>
|
||||
</div>
|
||||
<div className="app-main-content-list-container">
|
||||
<div className="app-main-content-list-container form-page">
|
||||
<div className="app-main-content-list-func-container">
|
||||
<Tabs defaultActiveKey="basicInfo" id="customers-tab" onSelect={k => changeTab(k)}>
|
||||
<Tabs activeKey={currentTab} id="vehicles-tab" onSelect={k => changeTab(k)}>
|
||||
<Tab eventKey="basicInfo" title="Basic Information">
|
||||
<h6 className="text-primary">Vehicle Information</h6>
|
||||
<h6 className="text-primary">Basic Information</h6>
|
||||
<div className="app-main-content-fields-section">
|
||||
<div className="field-body">
|
||||
<div className="field-label">Vehicle Number</div>
|
||||
<div className="field-value">{currentVehicle?.vehicle_number}</div>
|
||||
</div>
|
||||
<div className="field-body">
|
||||
<div className="field-label">Responsible Driver</div>
|
||||
<div className="field-value">{currentVehicle?.responsible_driver}</div>
|
||||
</div>
|
||||
<div className="field-body">
|
||||
<div className="field-label">Seating Capacity</div>
|
||||
<div className="field-value">{currentVehicle?.capacity}</div>
|
||||
@ -449,6 +432,8 @@ const tableRepair = <div className="list row mb-4">
|
||||
<div className="field-label">Mileage</div>
|
||||
<div className="field-value">{currentVehicle?.mileage}</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="app-main-content-fields-section">
|
||||
<div className="field-body">
|
||||
<div className="field-label">Make</div>
|
||||
<div className="field-value">{currentVehicle?.make}</div>
|
||||
@ -457,87 +442,103 @@ const tableRepair = <div className="list row mb-4">
|
||||
<div className="field-label">Model</div>
|
||||
<div className="field-value">{currentVehicle?.vehicle_model}</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="app-main-content-fields-section">
|
||||
<div className="field-body">
|
||||
<div className="field-label">Year</div>
|
||||
<div className="field-value">{currentVehicle?.year}</div>
|
||||
</div>
|
||||
<div className="field-body">
|
||||
<div className="field-label">Vin Number</div>
|
||||
<div className="field-label">VIN Number</div>
|
||||
<div className="field-value">{currentVehicle?.vin}</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="app-main-content-fields-section">
|
||||
<div className="field-body">
|
||||
<div className="field-label">Licence Plate</div>
|
||||
<div className="field-label">License Plate</div>
|
||||
<div className="field-value">{currentVehicle?.tag}</div>
|
||||
</div>
|
||||
<div className="field-body">
|
||||
<div className="field-label">GPS ID</div>
|
||||
<div className="field-value">{currentVehicle?.gps}</div>
|
||||
<div className="field-value">{currentVehicle?.gps_tag}</div>
|
||||
</div>
|
||||
<div className="field-body">
|
||||
<div className="field-label">EZPass</div>
|
||||
<div className="field-label">E-ZPass</div>
|
||||
<div className="field-value">{currentVehicle?.ezpass}</div>
|
||||
</div>
|
||||
<div className="field-body">
|
||||
<div className="field-label">Lift Equipped</div>
|
||||
<div className="field-value">{currentVehicle?.has_lift_equip === true ? 'Yes' : (currentVehicle?.has_lift_equip === false? 'No' : '')}</div>
|
||||
<div className="field-value">{formatYesNo(currentVehicle?.has_lift_equip)}</div>
|
||||
</div>
|
||||
</div>
|
||||
<h6 className="text-primary">Vehicle Maintenance & Compliance</h6>
|
||||
<div className="app-main-content-fields-section">
|
||||
<div className="field-body">
|
||||
<div className="field-label">Last Oil Change Date <span className="field-blurb float-right">1-month due cycle </span></div>
|
||||
<div className="field-value">{currentVehicle?.oil_change_date}</div>
|
||||
<div className="field-label">Fuel Type</div>
|
||||
<div className="field-value">{FUEL_TYPE_TEXT[currentVehicle?.fuel_type] || currentVehicle?.fuel_type}</div>
|
||||
</div>
|
||||
<div className="field-body">
|
||||
<div className="field-label">Last Emissions Inspection Date <span className="field-blurb float-right">1-year due cycle </span></div>
|
||||
<div className="field-value">{currentVehicle?.emission_test_on}</div>
|
||||
<div className="field-label">Title</div>
|
||||
<div className="field-value">
|
||||
{VEHICLE_TITLE_TEXT[currentVehicle?.title] || currentVehicle?.title}
|
||||
{currentVehicle?.title === 'other' && currentVehicle?.title_other && ` (${currentVehicle?.title_other})`}
|
||||
</div>
|
||||
<div className="field-body">
|
||||
<div className="field-label">Insurance Expiration Date <span className="field-blurb float-right">1-year due cycle </span></div>
|
||||
<div className="field-value">{currentVehicle?.insurance_expire_on}</div>
|
||||
</div>
|
||||
<div className="field-body">
|
||||
<div className="field-label">Title Registration Date <span className="field-blurb float-right">1-year due cycle </span></div>
|
||||
<div className="field-value">{currentVehicle?.title_registration_on}</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<h6 className="text-primary">Check List</h6>
|
||||
<div className="app-main-content-fields-section column">
|
||||
<ul>
|
||||
{currentVehicle?.checklist?.map((item) => <li>{item}</li>)}
|
||||
{currentVehicle?.checklist?.map((item, index) => <li key={index}>{item}</li>)}
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<h6 className="text-primary">Additional Information</h6>
|
||||
<div className="app-main-content-fields-section">
|
||||
<div className="field-body">
|
||||
<div className="field-label">Note</div>
|
||||
<div className="field-label">Notes and Attachments</div>
|
||||
<div className="field-value">{currentVehicle?.note}</div>
|
||||
</div>
|
||||
</div>
|
||||
</Tab>
|
||||
<Tab eventKey="documents" title="Documents">
|
||||
<h6 className="text-primary">Yearly Vehicle Inspection</h6>
|
||||
|
||||
<Tab eventKey="complianceDeadlines" title="Compliance & Deadlines">
|
||||
<h6 className="text-primary">Compliance & Deadlines</h6>
|
||||
<div className="app-main-content-fields-section">
|
||||
<div className="field-body">
|
||||
<div className="field-label">Insurance Expiration Date</div>
|
||||
<div className="field-value">{currentVehicle?.insurance_start_date || currentVehicle?.insurance_expire_on}</div>
|
||||
</div>
|
||||
<div className="field-body">
|
||||
<div className="field-label">Vehicle Registration Date</div>
|
||||
<div className="field-value">{currentVehicle?.vehicle_registration_date || currentVehicle?.title_registration_on}</div>
|
||||
</div>
|
||||
</div>
|
||||
</Tab>
|
||||
|
||||
<Tab eventKey="documents" title="Documents & Records">
|
||||
<h6 className="text-primary">Yearly Inspection</h6>
|
||||
{tableYearly}
|
||||
<h6 className="text-primary">Monthly Vehicle Inspection</h6>
|
||||
<h6 className="text-primary">Monthly Inspection</h6>
|
||||
{tableMonthly}
|
||||
</Tab>
|
||||
<Tab eventKey="repairRecords" title="Repair Records">
|
||||
|
||||
<Tab eventKey="repairRecords" title="Repair & Maintenance">
|
||||
{tableRepair}
|
||||
</Tab>
|
||||
</Tabs>
|
||||
<div className="list-func-panel">
|
||||
{currentTab !== 'basicInfo' && <input className="me-2 with-search-icon" type="text" placeholder="Search" value={keyword} onChange={(e) => setKeyword(e.currentTarget.value)} />}
|
||||
{currentTab === 'documents' && <button className="btn btn-primary" onClick={() => download()}><Download size={16} className="me-2"></Download>Download</button>}
|
||||
{currentTab === 'repairRecords' && <Export
|
||||
{(currentTab === 'documents' || currentTab === 'repairRecords') && (
|
||||
<input className="me-2 with-search-icon" type="text" placeholder="Search" value={keyword} onChange={(e) => setKeyword(e.currentTarget.value)} />
|
||||
)}
|
||||
{currentTab === 'documents' && (
|
||||
<button className="btn btn-primary" onClick={() => download()}><Download size={16} className="me-2"></Download>Download</button>
|
||||
)}
|
||||
{currentTab === 'repairRecords' && (
|
||||
<Export
|
||||
columns={columnsRepair}
|
||||
data={filteredRepairs}
|
||||
filename={`vehicle-${currentVehicle?.vehicle_number}-repairs`}
|
||||
/>}
|
||||
|
||||
/>
|
||||
)}
|
||||
<button className="btn btn-primary ms-2" onClick={() => goToEdit(currentVehicle?.id)}><PencilSquare className="me-2" size={16}></PencilSquare>Edit</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@ -2,7 +2,7 @@ import axios from "axios";
|
||||
export default axios.create({
|
||||
// baseURL: (window.location.hostname.includes('worldshine.mayo.llc') || window.location.hostname.includes('site')) ? "https://worldshine.mayo.llc/api" : ((window.location.hostname.includes('worldshine3.mayo.llc') || window.location.hostname.includes('site3')) ? "https://worldshine3.mayo.llc/api" : "https://worldshine3.mayo.llc/api"),
|
||||
// for Test site only:
|
||||
baseURL: (window.location.hostname.includes('ws1') ||window.location.hostname.includes('localhost') || window.location.hostname.includes('site1')) ? "https://ws1.mayosolution.com/api" : ((window.location.hostname.includes('ws3') || window.location.hostname.includes('site3')) ? "https://ws3.mayosolution.com/api" : "https://ws2.mayosolution.com/api"),
|
||||
baseURL: (window.location.hostname.includes('ws1') ||window.location.hostname.includes('localhost') || window.location.hostname.includes('site1')) ? "https://ws1-tspt.mayosolution.com/api" : ((window.location.hostname.includes('ws3') || window.location.hostname.includes('site3')) ? "https://ws3.mayosolution.com/api" : "https://ws2.mayosolution.com/api"),
|
||||
// baseURL: "http://localhost:8080/api",
|
||||
headers: {
|
||||
"Content-type": "application/json"
|
||||
|
||||
@ -44,6 +44,10 @@ const getAvatar = (filename) => {
|
||||
return http.get(`/files/${filename}`);
|
||||
}
|
||||
|
||||
const getAvatarAsBlob = (filename) => {
|
||||
return http.get(`/files/${filename}`, { responseType: 'blob' });
|
||||
}
|
||||
|
||||
const deleteFile = (data) => {
|
||||
return http.post(`/files/delete`, data);
|
||||
}
|
||||
@ -69,6 +73,7 @@ export const CustomerService = {
|
||||
getAllActiveCustomers,
|
||||
uploadAvatar,
|
||||
getAvatar,
|
||||
getAvatarAsBlob,
|
||||
deleteFile,
|
||||
createNewCustomer,
|
||||
updateCustomer,
|
||||
|
||||
@ -82,7 +82,8 @@ const getTransportationInfo = (eventsList, item, timeDocs = []) => {
|
||||
}
|
||||
}
|
||||
|
||||
const interpreterLevelOptions = [
|
||||
// Language Support options
|
||||
const languageSupportOptions = [
|
||||
{
|
||||
value: 'Checkin',
|
||||
label: 'Checkin'
|
||||
@ -97,22 +98,30 @@ const interpreterLevelOptions = [
|
||||
},
|
||||
{
|
||||
value: 'Office',
|
||||
label: 'Office inperson'
|
||||
label: 'Office in person'
|
||||
},
|
||||
{
|
||||
value: 'Office(Phone)',
|
||||
label: 'Office byphone'
|
||||
label: 'Office by phone'
|
||||
},
|
||||
{
|
||||
value: 'Nurse',
|
||||
label: 'Nurse'
|
||||
}
|
||||
];
|
||||
|
||||
const colorOptions = [
|
||||
// Legacy alias for backward compatibility
|
||||
const interpreterLevelOptions = languageSupportOptions;
|
||||
|
||||
// Label options for Medical Events
|
||||
const labelOptions = [
|
||||
{
|
||||
value: 'red',
|
||||
label: 'EyesOn'
|
||||
label: 'Eyes-On'
|
||||
},
|
||||
{
|
||||
value: 'brown',
|
||||
label: 'ByOwn',
|
||||
value: 'pink',
|
||||
label: 'Self-Transport',
|
||||
},
|
||||
{
|
||||
value: 'green',
|
||||
@ -120,15 +129,15 @@ const colorOptions = [
|
||||
},
|
||||
{
|
||||
value: 'blue',
|
||||
label: 'Default for ByCenter'
|
||||
label: 'Default for By Center'
|
||||
},
|
||||
{
|
||||
value: 'black',
|
||||
label: 'Cient Does Not Need to Go'
|
||||
label: 'Medication Pickup Only'
|
||||
},
|
||||
{
|
||||
value: 'purple',
|
||||
label: 'Dropoff Only',
|
||||
label: 'Drop-Off Only',
|
||||
},
|
||||
{
|
||||
value: 'gray',
|
||||
@ -136,21 +145,104 @@ const colorOptions = [
|
||||
},
|
||||
{
|
||||
value: 'orange',
|
||||
label: 'Pickup Only'
|
||||
label: 'Pick-Up Only'
|
||||
},
|
||||
{
|
||||
value: 'brown',
|
||||
label: 'Translation By Center'
|
||||
}
|
||||
];
|
||||
|
||||
// Activity color options
|
||||
const activityColorOptions = [
|
||||
{
|
||||
value: 'red',
|
||||
label: 'Classes'
|
||||
},
|
||||
{
|
||||
value: 'pink',
|
||||
label: 'Translate(center)'
|
||||
label: 'Games'
|
||||
},
|
||||
{
|
||||
value: 'green',
|
||||
label: 'Events'
|
||||
},
|
||||
{
|
||||
value: 'blue',
|
||||
label: 'Outings'
|
||||
},
|
||||
{
|
||||
value: 'purple',
|
||||
label: 'Personal Care'
|
||||
},
|
||||
{
|
||||
value: 'brown',
|
||||
label: 'Care Activities'
|
||||
}
|
||||
];
|
||||
|
||||
// Incident/Attendance Notes color options (always blue)
|
||||
const incidentColorOptions = [
|
||||
{
|
||||
value: 'blue',
|
||||
label: 'Attendance Note'
|
||||
}
|
||||
];
|
||||
|
||||
// Meal Plan color options
|
||||
const mealPlanColorOptions = [
|
||||
{
|
||||
value: 'brown',
|
||||
label: 'Breakfast'
|
||||
},
|
||||
{
|
||||
value: 'green',
|
||||
label: 'Lunch'
|
||||
},
|
||||
{
|
||||
value: 'red',
|
||||
label: 'Snack'
|
||||
}
|
||||
];
|
||||
|
||||
// Legacy alias for backward compatibility
|
||||
const colorOptions = labelOptions;
|
||||
|
||||
// Transportation Support options
|
||||
const transportationTypeOptions = [
|
||||
{
|
||||
value: 'by own',
|
||||
label: 'ByOwn'
|
||||
},
|
||||
{
|
||||
value: 'televisit',
|
||||
label: 'Televisit'
|
||||
},
|
||||
{
|
||||
value: 'client does not need to go',
|
||||
label: 'Client Does Not need to Go'
|
||||
},
|
||||
{
|
||||
value: 'dropoff only',
|
||||
label: 'DropOff Only'
|
||||
},
|
||||
{
|
||||
value: 'pickup only',
|
||||
label: 'Pickup Only'
|
||||
},
|
||||
{
|
||||
value: 'Center Transportation',
|
||||
label: 'By Center Transportation'
|
||||
}
|
||||
];
|
||||
|
||||
|
||||
|
||||
//const baseUrl = (window.location.hostname.includes('ws1') ||window.location.hostname.includes('localhost') || window.location.hostname.includes('site1')) ? "https://ws1.mayosolution.com/api" : ((window.location.hostname.includes('ws3') || window.location.hostname.includes('site3')) ? "https://ws3.mayosolution.com/api" : "https://ws2.mayosolution.com/api")
|
||||
const baseUrl = (window.location.hostname.includes('worldshine.mayo.llc') ||window.location.hostname.includes('worldshine1') || window.location.hostname.includes('site1')) ? "https://worldshine.mayo.llc/api" : ((window.location.hostname.includes('worldshine3') || window.location.hostname.includes('site3')) ? "https://worldshine3.mayo.llc/api" : "https://worldshine2.mayo.llc/api")
|
||||
const baseUrl = (window.location.hostname.includes('ws1') ||window.location.hostname.includes('localhost') || window.location.hostname.includes('site1')) ? "https://ws1-tspt.mayosolution.com/api" : ((window.location.hostname.includes('ws3') || window.location.hostname.includes('site3')) ? "https://ws3.mayosolution.com/api" : "https://ws2.mayosolution.com/api")
|
||||
// const baseUrl = (window.location.hostname.includes('worldshine.mayo.llc') ||window.location.hostname.includes('worldshine1') || window.location.hostname.includes('site1')) ? "https://worldshine.mayo.llc/api" : ((window.location.hostname.includes('worldshine3') || window.location.hostname.includes('site3')) ? "https://worldshine3.mayo.llc/api" : "https://worldshine2.mayo.llc/api")
|
||||
|
||||
//const site = (window.location.hostname.includes('ws1') ||window.location.hostname.includes('localhost') || window.location.hostname.includes('site1')) ? 1 : ((window.location.hostname.includes('ws3') || window.location.hostname.includes('site3')) ? 3 : 2)
|
||||
const site = (window.location.hostname.includes('worldshine.mayo.llc') ||window.location.hostname.includes('worldshine1') || window.location.hostname.includes('site1')) ? 1 : ((window.location.hostname.includes('worldshine3') || window.location.hostname.includes('site3')) ? 3 : 2)
|
||||
const site = (window.location.hostname.includes('ws1') ||window.location.hostname.includes('localhost') || window.location.hostname.includes('site1')) ? 1 : ((window.location.hostname.includes('ws3') || window.location.hostname.includes('site3')) ? 3 : 2)
|
||||
//const site = (window.location.hostname.includes('worldshine.mayo.llc') ||window.location.hostname.includes('worldshine1') || window.location.hostname.includes('site1')) ? 1 : ((window.location.hostname.includes('worldshine3') || window.location.hostname.includes('site3')) ? 3 : 2)
|
||||
|
||||
const generatePdf = (data) => {
|
||||
window.open(`${baseUrl}/docs/get-pdfs?docTemplateName=med_notification&inputData=${encodeURIComponent(JSON.stringify(data))}`);
|
||||
@ -185,6 +277,14 @@ export const EventsService = {
|
||||
getTimeData,
|
||||
getByCustomer,
|
||||
site,
|
||||
// New option names
|
||||
languageSupportOptions,
|
||||
labelOptions,
|
||||
activityColorOptions,
|
||||
incidentColorOptions,
|
||||
mealPlanColorOptions,
|
||||
transportationTypeOptions,
|
||||
// Legacy aliases for backward compatibility
|
||||
interpreterLevelOptions,
|
||||
colorOptions
|
||||
};
|
||||
|
||||
@ -1,4 +1,5 @@
|
||||
import http from "../http-common";
|
||||
|
||||
const getAll = (type) => {
|
||||
const params = {};
|
||||
if (type) {
|
||||
@ -12,7 +13,6 @@ const createNewResource = (data) => {
|
||||
return http.post('/resources', data);
|
||||
};
|
||||
|
||||
|
||||
const updateResource = (id, data) => {
|
||||
return http.put(`/resources/${id}`, data);
|
||||
}
|
||||
@ -38,7 +38,7 @@ const resourceOptionList = [
|
||||
'Botox Therapy',
|
||||
'Breast Surgery',
|
||||
'Cardiology',
|
||||
'Cardiovascular ',
|
||||
'Cardiovascular',
|
||||
'Colon & Rectal Surgery',
|
||||
'Dentist',
|
||||
'Dermatology',
|
||||
@ -60,7 +60,7 @@ const resourceOptionList = [
|
||||
'Infectious disease',
|
||||
'Medical Center',
|
||||
'Lab',
|
||||
'Modified Barium Swallow (MBS) Study ',
|
||||
'Modified Barium Swallow (MBS) Study',
|
||||
'Medical Supply',
|
||||
'Nephrology',
|
||||
'Neuro Surgeon',
|
||||
|
||||
@ -19,6 +19,7 @@ export * from './LabelService';
|
||||
export * from './SeatingService';
|
||||
export * from './AttendanceNoteService';
|
||||
export * from './CarouselService';
|
||||
export * from './DailyRoutesTemplateService';
|
||||
|
||||
// Utility functions
|
||||
export const parseDateFromBackend = (dateString) => {
|
||||
|
||||
@ -2,7 +2,7 @@ import React, { useState } from "react";
|
||||
import { Dropdown } from "react-bootstrap";
|
||||
import { Download } from "react-bootstrap-icons";
|
||||
|
||||
const Export = ({ columns, data, filename = "export" }) => {
|
||||
const Export = ({ columns, data, filename = "export", customActions = [] }) => {
|
||||
const [showExportDropdown, setShowExportDropdown] = useState(false);
|
||||
const [exportColumns, setExportColumns] = useState(
|
||||
columns.map(col => ({ ...col, show: true }))
|
||||
@ -117,6 +117,21 @@ const Export = ({ columns, data, filename = "export" }) => {
|
||||
<h6>Export Options</h6>
|
||||
<div className="app-main-content-fields-section margin-sm dropdown-container">
|
||||
<div className="me-4">
|
||||
{customActions.length > 0 && (
|
||||
<>
|
||||
{customActions.map((action, index) => (
|
||||
<button
|
||||
key={index}
|
||||
className="btn btn-primary btn-sm mb-2"
|
||||
style={{ width: '100%' }}
|
||||
onClick={action.onClick}
|
||||
>
|
||||
{action.label}
|
||||
</button>
|
||||
))}
|
||||
<hr style={{ margin: '8px 0', borderColor: '#ddd' }} />
|
||||
</>
|
||||
)}
|
||||
<div style={{ maxHeight: '200px', overflowY: 'auto', marginBottom: '15px' }}>
|
||||
<h6 style={{ fontSize: '14px', marginBottom: '10px' }}>Select Columns:</h6>
|
||||
{exportColumns.map((column) => (
|
||||
|
||||
@ -14,24 +14,247 @@ export const PICKUP_STATUS_TEXT = {
|
||||
|
||||
export const CUSTOMER_TYPE = {
|
||||
MEMBER: 'member',
|
||||
VOLUNTEER: 'volunteer',
|
||||
SELF_PAY: 'selfPayMember',
|
||||
VISITOR: 'visitor',
|
||||
TRANSFERRED: 'transferred',
|
||||
DISCHARED: 'discharged',
|
||||
DECEASED: 'deceased'
|
||||
VISITOR: 'visitor'
|
||||
}
|
||||
|
||||
export const CUSTOMER_TYPE_TEXT = {
|
||||
member: 'Member',
|
||||
volunteer: 'Volunteer',
|
||||
selfPayMember: 'Self-Pay Member',
|
||||
visitor: 'Visitor',
|
||||
transferred: 'Transferred',
|
||||
discharged: 'Discharged',
|
||||
deceased: 'Deceased'
|
||||
deceased: 'Deceased',
|
||||
discharged: 'Discharged'
|
||||
}
|
||||
|
||||
export const PROGRAM_TYPE = {
|
||||
AMDC: 'amdc',
|
||||
SENIOR_PLUS: 'seniorPlus'
|
||||
}
|
||||
|
||||
export const PROGRAM_TYPE_TEXT = {
|
||||
amdc: 'AMDC',
|
||||
seniorPlus: 'Senior Plus'
|
||||
}
|
||||
|
||||
export const PAY_SOURCE = {
|
||||
PRIVATE_PAY: 'privatePay',
|
||||
MEDICAID: 'medicaid',
|
||||
QMB: 'qmb',
|
||||
VETERAN: 'veteran',
|
||||
OTHER: 'other'
|
||||
}
|
||||
|
||||
export const PAY_SOURCE_TEXT = {
|
||||
privatePay: 'Private Pay',
|
||||
medicaid: 'Medicaid',
|
||||
qmb: 'QMB',
|
||||
veteran: 'Veteran',
|
||||
other: 'Other-Please Specify'
|
||||
}
|
||||
|
||||
export const LEGAL_SEX = {
|
||||
FEMALE: 'female',
|
||||
MALE: 'male'
|
||||
}
|
||||
|
||||
export const LEGAL_SEX_TEXT = {
|
||||
female: 'Female',
|
||||
male: 'Male'
|
||||
}
|
||||
|
||||
export const MARITAL_STATUS = {
|
||||
MARRIED: 'married',
|
||||
WIDOWED: 'widowed',
|
||||
SINGLE: 'single',
|
||||
DIVORCED: 'divorced'
|
||||
}
|
||||
|
||||
export const MARITAL_STATUS_TEXT = {
|
||||
married: 'Married',
|
||||
widowed: 'Widowed',
|
||||
single: 'Single',
|
||||
divorced: 'Divorced'
|
||||
}
|
||||
|
||||
export const IMMIGRATION_STATUS = {
|
||||
US_CITIZEN: 'usCitizen',
|
||||
GREEN_CARD_HOLDER: 'greenCardHolder',
|
||||
PERMANENT_RESIDENT: 'permanentResident',
|
||||
OTHER: 'other'
|
||||
}
|
||||
|
||||
export const IMMIGRATION_STATUS_TEXT = {
|
||||
usCitizen: 'U.S. Citizen',
|
||||
greenCardHolder: 'Green Card Holder',
|
||||
permanentResident: 'Permanent Resident',
|
||||
other: 'Other-Please Specify'
|
||||
}
|
||||
|
||||
export const LANGUAGE_OPTIONS = [
|
||||
{ value: 'english', label: 'English' },
|
||||
{ value: 'mandarin', label: 'Mandarin' },
|
||||
{ value: 'cantonese', label: 'Cantonese' },
|
||||
{ value: 'vietnamese', label: 'Vietnamese' },
|
||||
{ value: 'korean', label: 'Korean' },
|
||||
{ value: 'thai', label: 'Thai' },
|
||||
{ value: 'burmese', label: 'Burmese' },
|
||||
{ value: 'french', label: 'French' },
|
||||
{ value: 'haitianCreole', label: 'Haitian Creole' },
|
||||
{ value: 'hindi', label: 'Hindi' },
|
||||
{ value: 'persian', label: 'Persian' },
|
||||
{ value: 'spanish', label: 'Spanish' },
|
||||
{ value: 'urdu', label: 'Urdu' },
|
||||
{ value: 'other', label: 'Other-Please Specify' }
|
||||
]
|
||||
|
||||
export const STATE_OPTIONS = {
|
||||
MARYLAND: 'maryland',
|
||||
VIRGINIA: 'virginia'
|
||||
}
|
||||
|
||||
export const STATE_OPTIONS_TEXT = {
|
||||
maryland: 'Maryland',
|
||||
virginia: 'Virginia'
|
||||
}
|
||||
|
||||
export const EMERGENCY_CONTACT_RELATIONSHIP = {
|
||||
SON: 'son',
|
||||
DAUGHTER: 'daughter',
|
||||
SPOUSE: 'spouse',
|
||||
FRIEND: 'friend',
|
||||
CAREGIVER: 'caregiver',
|
||||
OTHER: 'other'
|
||||
}
|
||||
|
||||
export const EMERGENCY_CONTACT_RELATIONSHIP_TEXT = {
|
||||
son: 'Son',
|
||||
daughter: 'Daughter',
|
||||
spouse: 'Spouse',
|
||||
friend: 'Friend',
|
||||
caregiver: 'Caregiver',
|
||||
other: 'Other-Please Specify'
|
||||
}
|
||||
|
||||
export const EMERGENCY_CONTACT_ROLE_OPTIONS = [
|
||||
{ value: 'nextOfKin', label: 'Next of Kin' },
|
||||
{ value: 'emergencyContact', label: 'Emergency contact' },
|
||||
{ value: 'payor', label: 'Payor' },
|
||||
{ value: 'primaryCaregiver', label: 'Primary Caregiver' },
|
||||
{ value: 'careProvider', label: 'Care Provider' },
|
||||
{ value: 'caseManager', label: 'Case Manager' },
|
||||
{ value: 'powerOfAttorney', label: 'Power of Attorney' },
|
||||
{ value: 'serviceCoordinator', label: 'Service Coordinator' },
|
||||
{ value: 'friend', label: 'Friend' },
|
||||
{ value: 'guardianOrPoaForHealthcare', label: 'Guardian or PoA for Healthcare' },
|
||||
{ value: 'healthcareProxy', label: 'Healthcare Proxy' }
|
||||
]
|
||||
|
||||
export const DAYS_OF_WEEK_OPTIONS = [
|
||||
{ value: 'monday', label: 'Monday' },
|
||||
{ value: 'tuesday', label: 'Tuesday' },
|
||||
{ value: 'wednesday', label: 'Wednesday' },
|
||||
{ value: 'thursday', label: 'Thursday' },
|
||||
{ value: 'friday', label: 'Friday' },
|
||||
{ value: 'saturday', label: 'Saturday' },
|
||||
{ value: 'sunday', label: 'Sunday' }
|
||||
]
|
||||
|
||||
// Referral Source - Single Choice with categories
|
||||
export const REFERRAL_SOURCE = {
|
||||
// Returning
|
||||
TRANSFER_WORLDSHINE: 'transferWorldshine',
|
||||
TRANSFER_AMDC: 'transferAmdc',
|
||||
REJOIN: 'rejoin',
|
||||
// Referral
|
||||
FAMILY_FRIENDS: 'familyFriends',
|
||||
HEALTHCARE_PROVIDER: 'healthcareProvider',
|
||||
SOCIAL_WORKER: 'socialWorker',
|
||||
SUPPORT_PLANNER: 'supportPlanner',
|
||||
// Online & Media Source
|
||||
ONLINE_SEARCH: 'onlineSearch',
|
||||
FACEBOOK: 'facebook',
|
||||
INSTAGRAM: 'instagram',
|
||||
REDNOTE: 'rednote',
|
||||
YOUTUBE: 'youtube',
|
||||
WECHAT: 'wechat',
|
||||
NEWSPAPER: 'newspaper',
|
||||
NEXTDOOR: 'nextdoor',
|
||||
// Event
|
||||
EVENT: 'event',
|
||||
// N/A
|
||||
NA: 'na',
|
||||
// Other
|
||||
OTHER: 'other'
|
||||
}
|
||||
|
||||
export const REFERRAL_SOURCE_TEXT = {
|
||||
// Returning
|
||||
transferWorldshine: 'Transfer from Another Worldshine Center',
|
||||
transferAmdc: 'Transfer from Another AMDC Center',
|
||||
rejoin: 'Rejoin',
|
||||
// Referral
|
||||
familyFriends: 'Family and Friends',
|
||||
healthcareProvider: 'Healthcare Provider Referral',
|
||||
socialWorker: 'Social Worker Referral',
|
||||
supportPlanner: 'Support Planner',
|
||||
// Online & Media Source
|
||||
onlineSearch: 'Online Search',
|
||||
facebook: 'Facebook',
|
||||
instagram: 'Instagram',
|
||||
rednote: 'RedNote',
|
||||
youtube: 'YouTube',
|
||||
wechat: 'WeChat Official Account',
|
||||
newspaper: 'Newspaper',
|
||||
nextdoor: 'Nextdoor',
|
||||
// Event
|
||||
event: 'Event',
|
||||
// N/A
|
||||
na: 'N/A',
|
||||
// Other
|
||||
other: 'Other-Please Specify'
|
||||
}
|
||||
|
||||
// Referral Source grouped options for display
|
||||
export const REFERRAL_SOURCE_GROUPED = [
|
||||
{
|
||||
label: 'Returning',
|
||||
options: [
|
||||
{ value: 'transferWorldshine', label: 'Transfer from Another Worldshine Center' },
|
||||
{ value: 'transferAmdc', label: 'Transfer from Another AMDC Center' },
|
||||
{ value: 'rejoin', label: 'Rejoin' }
|
||||
]
|
||||
},
|
||||
{
|
||||
label: 'Referral',
|
||||
options: [
|
||||
{ value: 'familyFriends', label: 'Family and Friends' },
|
||||
{ value: 'healthcareProvider', label: 'Healthcare Provider Referral' },
|
||||
{ value: 'socialWorker', label: 'Social Worker Referral' },
|
||||
{ value: 'supportPlanner', label: 'Support Planner' }
|
||||
]
|
||||
},
|
||||
{
|
||||
label: 'Online & Media Source',
|
||||
options: [
|
||||
{ value: 'onlineSearch', label: 'Online Search' },
|
||||
{ value: 'facebook', label: 'Facebook' },
|
||||
{ value: 'instagram', label: 'Instagram' },
|
||||
{ value: 'rednote', label: 'RedNote' },
|
||||
{ value: 'youtube', label: 'YouTube' },
|
||||
{ value: 'wechat', label: 'WeChat Official Account' },
|
||||
{ value: 'newspaper', label: 'Newspaper' },
|
||||
{ value: 'nextdoor', label: 'Nextdoor' }
|
||||
]
|
||||
},
|
||||
{
|
||||
label: 'Other',
|
||||
options: [
|
||||
{ value: 'event', label: 'Event' },
|
||||
{ value: 'na', label: 'N/A' },
|
||||
{ value: 'other', label: 'Other-Please Specify' }
|
||||
]
|
||||
}
|
||||
]
|
||||
|
||||
export const CUSTOMER_JOIN_REASON = {
|
||||
FRIEND_FAMILY_REFERRAL: 'friendFamilyReferral',
|
||||
SOCIAL_WORKER_REFERRAL: 'socialWorkerReferral',
|
||||
@ -50,16 +273,180 @@ export const CUSTOMER_JOIN_REASON_TEXT = {
|
||||
|
||||
export const CUSTOMER_DISCHARGE_REASON = {
|
||||
ABSENT_OVER_30: 'absentOver30',
|
||||
TRANSFERRED_TO_ASSISTED_LIVING: 'TransferredToAssignedLiving',
|
||||
DECEASED: 'Deceased',
|
||||
EVENT: 'Event',
|
||||
OTHER: 'Other'
|
||||
TRANSFER_WORLDSHINE: 'transferWorldshine',
|
||||
TRANSFER_AMDC: 'transferAmdc',
|
||||
TRANSFER_OTHER_FACILITY: 'transferOtherFacility',
|
||||
DECEASED: 'deceased',
|
||||
HOSPITALIZED: 'hospitalized',
|
||||
MOVING_OUT_SERVICE_AREA: 'movingOutServiceArea',
|
||||
NO_LONGER_ELIGIBLE: 'noLongerEligible',
|
||||
NA: 'na',
|
||||
OTHER: 'other'
|
||||
}
|
||||
|
||||
export const CUSTOMER_DISCHARGE_REASON_TEXT = {
|
||||
absentOver30: 'Absent for Over 30 Days',
|
||||
TransferredToAssignedLiving: 'Transferred to Assisted Living',
|
||||
Deceased: 'Deceased',
|
||||
Event: 'Event',
|
||||
Other: 'Other'
|
||||
transferWorldshine: 'Transfer to Another Worldshine Center',
|
||||
transferAmdc: 'Transfer to Another AMDC Center',
|
||||
transferOtherFacility: 'Transfer to Another Type of Facility',
|
||||
deceased: 'Deceased',
|
||||
hospitalized: 'Hospitalized',
|
||||
movingOutServiceArea: 'Moving out of the Service Area',
|
||||
noLongerEligible: 'No Longer Eligible for ADS',
|
||||
na: 'N/A',
|
||||
other: 'Other-Please Specify'
|
||||
}
|
||||
|
||||
// Dietary Restrictions - Multi Choice with categories
|
||||
export const DIETARY_RESTRICTIONS_OPTIONS = [
|
||||
// Regular
|
||||
{ value: 'regular', label: 'Regular' },
|
||||
// Meat
|
||||
{ value: 'noRedMeat', label: 'No Red Meat' },
|
||||
{ value: 'noPork', label: 'No Pork' },
|
||||
{ value: 'noBeef', label: 'No Beef' },
|
||||
{ value: 'noLamb', label: 'No Lamb' },
|
||||
{ value: 'noChicken', label: 'No Chicken' },
|
||||
{ value: 'noTurkey', label: 'No Turkey' },
|
||||
{ value: 'noDuck', label: 'No Duck' },
|
||||
// Seafood
|
||||
{ value: 'noFish', label: 'No Fish' },
|
||||
{ value: 'noSeafood', label: 'No Seafood' },
|
||||
{ value: 'noShrimp', label: 'No Shrimp' },
|
||||
{ value: 'noShellfish', label: 'No Shellfish' },
|
||||
// Dairy
|
||||
{ value: 'noDairy', label: 'No Dairy' },
|
||||
{ value: 'noLactose', label: 'No Lactose' },
|
||||
{ value: 'noYoghurt', label: 'No Yoghurt' },
|
||||
{ value: 'noBoiledEgg', label: 'No Boiled Egg' },
|
||||
{ value: 'noEgg', label: 'No Egg' },
|
||||
// Nuts and Snacks
|
||||
{ value: 'noNuts', label: 'No Nuts' },
|
||||
{ value: 'noPeanuts', label: 'No Peanuts' },
|
||||
{ value: 'noCrackers', label: 'No Crackers' },
|
||||
// Vegetables and Fruits
|
||||
{ value: 'noVegetables', label: 'No Vegetables' },
|
||||
{ value: 'noFruits', label: 'No Fruits' },
|
||||
{ value: 'noOrange', label: 'No Orange' },
|
||||
{ value: 'noApple', label: 'No Apple' },
|
||||
{ value: 'noGrapefruit', label: 'No Grapefruit' },
|
||||
{ value: 'noGrapes', label: 'No Grapes' },
|
||||
{ value: 'noCucumber', label: 'No Cucumber' },
|
||||
{ value: 'noCorn', label: 'No Corn' },
|
||||
{ value: 'noPickles', label: 'No Pickles' },
|
||||
{ value: 'noEggplants', label: 'No Eggplants' },
|
||||
{ value: 'noLettuce', label: 'No Lettuce' }
|
||||
]
|
||||
|
||||
// Dietary Restrictions grouped options for display
|
||||
export const DIETARY_RESTRICTIONS_GROUPED = [
|
||||
{
|
||||
label: 'Regular',
|
||||
options: [
|
||||
{ value: 'regular', label: 'Regular' }
|
||||
]
|
||||
},
|
||||
{
|
||||
label: 'Meat',
|
||||
options: [
|
||||
{ value: 'noRedMeat', label: 'No Red Meat' },
|
||||
{ value: 'noPork', label: 'No Pork' },
|
||||
{ value: 'noBeef', label: 'No Beef' },
|
||||
{ value: 'noLamb', label: 'No Lamb' },
|
||||
{ value: 'noChicken', label: 'No Chicken' },
|
||||
{ value: 'noTurkey', label: 'No Turkey' },
|
||||
{ value: 'noDuck', label: 'No Duck' }
|
||||
]
|
||||
},
|
||||
{
|
||||
label: 'Seafood',
|
||||
options: [
|
||||
{ value: 'noFish', label: 'No Fish' },
|
||||
{ value: 'noSeafood', label: 'No Seafood' },
|
||||
{ value: 'noShrimp', label: 'No Shrimp' },
|
||||
{ value: 'noShellfish', label: 'No Shellfish' }
|
||||
]
|
||||
},
|
||||
{
|
||||
label: 'Dairy',
|
||||
options: [
|
||||
{ value: 'noDairy', label: 'No Dairy' },
|
||||
{ value: 'noLactose', label: 'No Lactose' },
|
||||
{ value: 'noYoghurt', label: 'No Yoghurt' },
|
||||
{ value: 'noBoiledEgg', label: 'No Boiled Egg' },
|
||||
{ value: 'noEgg', label: 'No Egg' }
|
||||
]
|
||||
},
|
||||
{
|
||||
label: 'Nuts and Snacks',
|
||||
options: [
|
||||
{ value: 'noNuts', label: 'No Nuts' },
|
||||
{ value: 'noPeanuts', label: 'No Peanuts' },
|
||||
{ value: 'noCrackers', label: 'No Crackers' }
|
||||
]
|
||||
},
|
||||
{
|
||||
label: 'Vegetables and Fruits',
|
||||
options: [
|
||||
{ value: 'noVegetables', label: 'No Vegetables' },
|
||||
{ value: 'noFruits', label: 'No Fruits' },
|
||||
{ value: 'noOrange', label: 'No Orange' },
|
||||
{ value: 'noApple', label: 'No Apple' },
|
||||
{ value: 'noGrapefruit', label: 'No Grapefruit' },
|
||||
{ value: 'noGrapes', label: 'No Grapes' },
|
||||
{ value: 'noCucumber', label: 'No Cucumber' },
|
||||
{ value: 'noCorn', label: 'No Corn' },
|
||||
{ value: 'noPickles', label: 'No Pickles' },
|
||||
{ value: 'noEggplants', label: 'No Eggplants' },
|
||||
{ value: 'noLettuce', label: 'No Lettuce' }
|
||||
]
|
||||
}
|
||||
]
|
||||
|
||||
export const DIET_TEXTURE = {
|
||||
REGULAR: 'regular',
|
||||
CHOPPED: 'chopped',
|
||||
PUREED: 'pureed',
|
||||
LIQUID: 'liquid'
|
||||
}
|
||||
|
||||
export const DIET_TEXTURE_TEXT = {
|
||||
regular: 'Regular',
|
||||
chopped: 'Chopped',
|
||||
pureed: 'Pureed',
|
||||
liquid: 'Liquid'
|
||||
}
|
||||
|
||||
export const TRANSPORTATION_TYPE = {
|
||||
ROUND_TRIP: 'roundTrip',
|
||||
PICKUP_ONLY: 'pickupOnly',
|
||||
DROPOFF_ONLY: 'dropoffOnly',
|
||||
SELF_TRANSPORT: 'selfTransport'
|
||||
}
|
||||
|
||||
export const TRANSPORTATION_TYPE_TEXT = {
|
||||
roundTrip: 'Round Trip',
|
||||
pickupOnly: 'Pick-up Only',
|
||||
dropoffOnly: 'Drop-off Only',
|
||||
selfTransport: 'Self-Transport'
|
||||
}
|
||||
|
||||
export const YES_NO = {
|
||||
YES: 'yes',
|
||||
NO: 'no'
|
||||
}
|
||||
|
||||
export const YES_NO_TEXT = {
|
||||
yes: 'Yes',
|
||||
no: 'No'
|
||||
}
|
||||
|
||||
export const PREFERRED_TEXT_LANGUAGE = {
|
||||
ENGLISH: 'english',
|
||||
CHINESE: 'chinese'
|
||||
}
|
||||
|
||||
export const PREFERRED_TEXT_LANGUAGE_TEXT = {
|
||||
english: 'English',
|
||||
chinese: 'Chinese'
|
||||
}
|
||||
@ -2,3 +2,5 @@ export * from "./customer.constant";
|
||||
export * from "./route-status.constant";
|
||||
export * from "./employee.constant";
|
||||
export * from "./report.constant";
|
||||
export * from "./vehicle.constant";
|
||||
export * from "./resource.constant";
|
||||
21
server.js
21
server.js
@ -12,10 +12,10 @@ const employeeUploadBasePath = '/www/wwwroot/upload/';
|
||||
app.use('/files', express.static(employeeUploadBasePath));
|
||||
app.use(cors(corsOptions));
|
||||
// parse requests of content-type - application/json
|
||||
app.use(bodyParser.json());
|
||||
app.use(bodyParser.json({ limit: '50mb' }));
|
||||
app.use(express.static(path));
|
||||
// parse requests of content-type - application/x-www-form-urlencoded
|
||||
app.use(bodyParser.urlencoded({ extended: true }));
|
||||
app.use(bodyParser.urlencoded({ limit: '50mb', extended: true }));
|
||||
const db = require('./app/models');
|
||||
db.mongoose
|
||||
.connect(db.url, {
|
||||
@ -72,6 +72,18 @@ app.get('/trans-routes/route-signature', function (req,res) {
|
||||
app.get('/trans-routes/templates/edit/:id', function (req,res) {
|
||||
res.sendFile(path + "index.html");
|
||||
});
|
||||
app.get('/trans-routes/daily-templates/list', function (req,res) {
|
||||
res.sendFile(path + "index.html");
|
||||
});
|
||||
app.get('/trans-routes/daily-templates/view/:id', function (req,res) {
|
||||
res.sendFile(path + "index.html");
|
||||
});
|
||||
app.get('/trans-routes/daily-templates/:id/view-route/:routeId', function (req,res) {
|
||||
res.sendFile(path + "index.html");
|
||||
});
|
||||
app.get('/trans-routes/daily-templates/:id/update-route/:routeId', function (req,res) {
|
||||
res.sendFile(path + "index.html");
|
||||
});
|
||||
app.get('/employees', function (req,res) {
|
||||
res.sendFile(path + "index.html");
|
||||
});
|
||||
@ -201,6 +213,9 @@ app.get('/seating', function (req,res) {
|
||||
app.get('/center-calendar', function (req,res) {
|
||||
res.sendFile(path + "index.html");
|
||||
});
|
||||
app.get('/meal-status', function (req,res) {
|
||||
res.sendFile(path + "index.html");
|
||||
});
|
||||
app.get('/info-screen', function (req,res) {
|
||||
res.sendFile(path + "index.html");
|
||||
});
|
||||
@ -233,6 +248,8 @@ require("./app/routes/seating.routes")(app);
|
||||
require("./app/routes/label.routes")(app);
|
||||
require("./app/routes/attendance-note.routes")(app);
|
||||
require("./app/routes/carousel.routes")(app);
|
||||
require("./app/routes/fingerprint-attendance.routes")(app);
|
||||
require("./app/routes/daily-routes-template.routes")(app);
|
||||
|
||||
require("./app/scheduler/reminderScheduler");
|
||||
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user