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 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 = {
|
module.exports = {
|
||||||
baseUrl: "mongodb://localhost:27017/",
|
// baseUrl: "mongodb://localhost:27017/",
|
||||||
fileUrl: "https://worldshine.mayo.llc/files/",
|
// fileUrl: "https://worldshine.mayo.llc/files/",
|
||||||
database: "worldshine",
|
// database: "worldshine",
|
||||||
url: devUri,
|
// url: devUri,
|
||||||
|
|
||||||
// on local enable this
|
// on local enable this
|
||||||
// url: localUri,
|
url: localUri,
|
||||||
// baseUrl: "mongodb+srv://new-user-test:Testing123@cluster0.qkzim.mongodb.net/",
|
// baseUrl: "mongodb+srv://new-user-test:Testing123@cluster0.qkzim.mongodb.net/",
|
||||||
// database: "leapbase",
|
baseUrl: "mongodb+srv://liyang1000000:Testing123@juxing.iosxg2f.mongodb.net/",
|
||||||
// fileUrl: "http://localhost:8080/files/",
|
database: "worldshine",
|
||||||
// imgBucket: "photos",
|
fileUrl: "http://localhost:8080/files/",
|
||||||
|
imgBucket: "photos",
|
||||||
};
|
};
|
||||||
@ -47,7 +47,10 @@ exports.createCalendarEvent = (req, res) => {
|
|||||||
event_location: req.body.event_location,
|
event_location: req.body.event_location,
|
||||||
event_prediction_date: req.body.event_prediction_date,
|
event_prediction_date: req.body.event_prediction_date,
|
||||||
event_reminder_type: req.body.event_reminder_type,
|
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
|
// Save event in the database
|
||||||
calendarEvent
|
calendarEvent
|
||||||
|
|||||||
@ -12,16 +12,76 @@ exports.createCustomer = (req, res) => {
|
|||||||
const site = splitSite.findSiteNumber(req);
|
const site = splitSite.findSiteNumber(req);
|
||||||
// Create a Customer
|
// Create a Customer
|
||||||
const customer = new Customer({
|
const customer = new Customer({
|
||||||
|
// Basic Info
|
||||||
username: req.body.username || req.body.email || '',
|
username: req.body.username || req.body.email || '',
|
||||||
name: req.body.name || '',
|
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_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 || '',
|
email: req.body.email || '',
|
||||||
password: req.body.password ? bcrypt.hashSync(req.body.password, 8) : '',
|
phone: req.body.phone || '',
|
||||||
mobile_phone: req.body.mobile_phone || '',
|
mobile_phone: req.body.mobile_phone || '',
|
||||||
home_phone: req.body.home_phone || '',
|
home_phone: req.body.home_phone || '',
|
||||||
phone: req.body.phone || '',
|
|
||||||
language: req.body.language || '',
|
// Address 1
|
||||||
status: 'active',
|
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 || '',
|
address1: req.body.address1 || '',
|
||||||
address2: req.body.address2 || '',
|
address2: req.body.address2 || '',
|
||||||
address3: req.body.address3 || '',
|
address3: req.body.address3 || '',
|
||||||
@ -32,75 +92,130 @@ exports.createCustomer = (req, res) => {
|
|||||||
state1: req.body.state1 || '',
|
state1: req.body.state1 || '',
|
||||||
zip_code1: req.body.zip_code1 || '',
|
zip_code1: req.body.zip_code1 || '',
|
||||||
street_address_2: req.body.street_address_2 || '',
|
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 || '',
|
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 || '',
|
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 || '',
|
street_address_5: req.body.street_address_5 || '',
|
||||||
city5: req.body.city5 || '',
|
apartment: req.body.apartment || '',
|
||||||
state5: req.body.state5 || '',
|
|
||||||
zip_code5: req.body.zip_code5 || '',
|
// Emergency Contact
|
||||||
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 || '',
|
|
||||||
emergency_contact: req.body.emergency_contact || '',
|
emergency_contact: req.body.emergency_contact || '',
|
||||||
emergency_contact2: req.body.emergency_contact2 || '',
|
emergency_contact2: req.body.emergency_contact2 || '',
|
||||||
emergency_contact_name: req.body.emergency_contact_name || '',
|
emergency_contact_name: req.body.emergency_contact_name || '',
|
||||||
emergency_contact_phone: req.body.emergency_contact_phone || '',
|
emergency_contact_phone: req.body.emergency_contact_phone || '',
|
||||||
emergency_contact_relationship: req.body.emergency_contact_relationship || '',
|
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_name: req.body.emergency_contact2_name || '',
|
||||||
emergency_contact2_phone: req.body.emergency_contact2_phone || '',
|
emergency_contact2_phone: req.body.emergency_contact2_phone || '',
|
||||||
emergency_contact2_relationship: req.body.emergency_contact2_relationship || '',
|
emergency_contact2_relationship: req.body.emergency_contact2_relationship || '',
|
||||||
medicare_number: req.body.medicare_number || '',
|
emergency_contact2_relationship_other: req.body.emergency_contact2_relationship_other || '',
|
||||||
medicaid_number: req.body.medicaid_number || '',
|
emergency_contact2_role: req.body.emergency_contact2_role || [],
|
||||||
pharmacy: req.body.pharmacy || '',
|
|
||||||
type: req.body.type || '',
|
// Schedule
|
||||||
avatar: req.body.avatar || '',
|
days_of_week: req.body.days_of_week || [],
|
||||||
special_needs: req.body.special_needs || '',
|
|
||||||
pickup_status: req.body.pickup_status || '',
|
// Admission & Discharge Record
|
||||||
pharmacy_id: req.body.pharmacy_id || '',
|
|
||||||
pin: req.body.pin || '',
|
|
||||||
admission_date: req.body.admission_date || null,
|
admission_date: req.body.admission_date || null,
|
||||||
seating: req.body.seating || '',
|
enrolled_date: req.body.enrolled_date || null,
|
||||||
vehicle_no: req.body.vehicle_no || '',
|
create_by: req.body.create_by || '',
|
||||||
caller: req.body.caller || '',
|
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,
|
discharge_date: req.body.discharge_date || null,
|
||||||
placement: req.body.placement || '',
|
discharge_by: req.body.discharge_by || '',
|
||||||
nickname: req.body.nickname || '',
|
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 || '',
|
table_id: req.body.table_id || '',
|
||||||
groups: req.body.groups || null,
|
seat_number: req.body.seat_number || '',
|
||||||
tags: req.body.tags || null,
|
seating: req.body.seating || '',
|
||||||
roles: req.body.roles || null,
|
transportation_type: req.body.transportation_type || '',
|
||||||
apartment: req.body.apartment || '',
|
consent_to_text_messages: req.body.consent_to_text_messages || '',
|
||||||
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 || '',
|
|
||||||
text_msg_enabled: req.body.text_msg_enabled || false,
|
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 || '',
|
allergy_info: req.body.allergy_info || '',
|
||||||
meal_requirement: req.body.meal_requirement || '',
|
meal_requirement: req.body.meal_requirement || '',
|
||||||
service_requirement: req.body.service_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_due_date: req.body.payment_due_date || '',
|
||||||
payment_status: req.body.payment_status || '',
|
payment_status: req.body.payment_status || '',
|
||||||
join_reason: req.body.join_reason || '',
|
site
|
||||||
discharge_reason: req.body.discharge_reason || ''
|
|
||||||
});
|
});
|
||||||
// Save Customer in the database
|
// Save Customer in the database
|
||||||
customer
|
customer
|
||||||
|
|||||||
@ -14,26 +14,17 @@ exports.createEmployee = (req, res) => {
|
|||||||
// Create a Employee
|
// Create a Employee
|
||||||
const employee = new Employee({
|
const employee = new Employee({
|
||||||
username: req.body.username || req.body.email || '',
|
username: req.body.username || req.body.email || '',
|
||||||
name: req.body.name || '',
|
|
||||||
name_cn: req.body.name_cn || '',
|
name_cn: req.body.name_cn || '',
|
||||||
email: req.body.email || '',
|
email: req.body.email || '',
|
||||||
password: req.body.password ? bcrypt.hashSync(req.body.password, 8) : '',
|
password: req.body.password ? bcrypt.hashSync(req.body.password, 8) : '',
|
||||||
roles: req.body.roles || [],
|
roles: req.body.roles || [],
|
||||||
mobile_phone: req.body.mobile_phone || '',
|
mobile_phone: req.body.mobile_phone || '',
|
||||||
phone: req.body.phone || '',
|
|
||||||
home_phone: req.body.home_phone || '',
|
home_phone: req.body.home_phone || '',
|
||||||
language: req.body.language || '',
|
language: req.body.language || '',
|
||||||
employment_status: req.body.employment_status || '',
|
employment_status: req.body.employment_status || '',
|
||||||
status: req.body.status || 'active',
|
|
||||||
address: req.body.address || '',
|
address: req.body.address || '',
|
||||||
title: req.body.title || '',
|
date_hired: req.body.date_hired || '',
|
||||||
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,
|
|
||||||
driver_capacity: req.body.driver_capacity || null,
|
driver_capacity: req.body.driver_capacity || null,
|
||||||
date_hired: req.body.date_hired || null,
|
|
||||||
create_by: req.body.create_by || '',
|
create_by: req.body.create_by || '',
|
||||||
create_date: new Date(),
|
create_date: new Date(),
|
||||||
edit_by: req.body.edit_by || '',
|
edit_by: req.body.edit_by || '',
|
||||||
@ -41,7 +32,68 @@ exports.createEmployee = (req, res) => {
|
|||||||
note: req.body.note || '',
|
note: req.body.note || '',
|
||||||
tags: req.body.tags || [],
|
tags: req.body.tags || [],
|
||||||
fetch_route_time: req.body.fetch_route_time || null,
|
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
|
// Save Employee in the database
|
||||||
employee
|
employee
|
||||||
@ -67,6 +119,9 @@ exports.getAllEmployees = (req, res) => {
|
|||||||
if (params.role) {
|
if (params.role) {
|
||||||
condition.roles = params.role;
|
condition.roles = params.role;
|
||||||
}
|
}
|
||||||
|
if (params.site) {
|
||||||
|
condition.site = params.site;
|
||||||
|
}
|
||||||
Employee.find(condition)
|
Employee.find(condition)
|
||||||
.then(data => {
|
.then(data => {
|
||||||
res.send(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.
|
// Retrieve all Active Employee from the database.
|
||||||
exports.getAllActiveEmployees = (req, res) => {
|
exports.getAllActiveEmployees = (req, res) => {
|
||||||
var params = req.query;
|
var params = req.query;
|
||||||
@ -86,6 +164,9 @@ exports.getAllActiveEmployees = (req, res) => {
|
|||||||
if (params.roles) {
|
if (params.roles) {
|
||||||
condition.roles = params.roles;
|
condition.roles = params.roles;
|
||||||
}
|
}
|
||||||
|
if (params.site) {
|
||||||
|
condition.site = params.site;
|
||||||
|
}
|
||||||
Employee.find(condition)
|
Employee.find(condition)
|
||||||
.then(data => {
|
.then(data => {
|
||||||
res.send(data);
|
res.send(data);
|
||||||
|
|||||||
@ -10,33 +10,51 @@ exports.createResource = (req, res) => {
|
|||||||
}
|
}
|
||||||
const site = splitSite.findSiteNumber(req);
|
const site = splitSite.findSiteNumber(req);
|
||||||
const resource = new Resource({
|
const resource = new Resource({
|
||||||
name: req.body.name,
|
// Basic Information
|
||||||
name_original: req.body.name_original,
|
name: req.body.name, // Provider name
|
||||||
name_branch: req.body.name_branch,
|
office_name: req.body.office_name || '',
|
||||||
specialty: req.body.specialty,
|
specialty: req.body.specialty,
|
||||||
type: req.body.type, // value may be ['doctor', 'pharmacy' or 'other']
|
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,
|
city: req.body.city,
|
||||||
state: req.body.state,
|
state: req.body.state,
|
||||||
zipcode: req.body.zipcode,
|
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_by: req.body.create_by,
|
||||||
create_date: req.body.create_date || new Date(),
|
create_date: req.body.create_date || new Date(),
|
||||||
parent_id: req.body.parent_id,
|
parent_id: req.body.parent_id,
|
||||||
ext_id: req.body.ext_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,
|
data: req.body.data,
|
||||||
edit_by: req.body.edit_by,
|
edit_by: req.body.edit_by,
|
||||||
edit_date: req.body.edit_date || new Date(),
|
edit_date: req.body.edit_date || new Date(),
|
||||||
images: req.body.images,
|
images: req.body.images,
|
||||||
edit_history: req.body.edit_history,
|
edit_history: req.body.edit_history,
|
||||||
site
|
site
|
||||||
});
|
});
|
||||||
// Save Resource in the database
|
// Save Resource in the database
|
||||||
|
|||||||
@ -10,26 +10,44 @@ exports.createVehicle = (req, res) => {
|
|||||||
const site = splitSite.findSiteNumber(req);
|
const site = splitSite.findSiteNumber(req);
|
||||||
// Create a Vehicle
|
// Create a Vehicle
|
||||||
const vehicle = new Vehicle({
|
const vehicle = new Vehicle({
|
||||||
|
// Basic Information
|
||||||
vehicle_number: req.body.vehicle_number,
|
vehicle_number: req.body.vehicle_number,
|
||||||
tag: req.body.tag || '',
|
responsible_driver: req.body.responsible_driver || '',
|
||||||
ezpass: req.body.ezpass || '',
|
responsible_driver_id: req.body.responsible_driver_id || '',
|
||||||
gps_tag: req.body.gps_tag || '',
|
|
||||||
mileage: req.body.mileage || 0,
|
|
||||||
capacity: req.body.capacity || 0,
|
capacity: req.body.capacity || 0,
|
||||||
|
mileage: req.body.mileage || 0,
|
||||||
make: req.body.make || '',
|
make: req.body.make || '',
|
||||||
vehicle_model: req.body.vehicle_model || '',
|
vehicle_model: req.body.vehicle_model || '',
|
||||||
year: req.body.year || '',
|
year: req.body.year || '',
|
||||||
checklist: req.body.checklist || '',
|
|
||||||
status: 'active',
|
|
||||||
site,
|
|
||||||
has_lift_equip: req.body.has_lift_equip,
|
|
||||||
vin: req.body.vin || '',
|
vin: req.body.vin || '',
|
||||||
note: req.body.note || '',
|
tag: req.body.tag || '',
|
||||||
insurance_expire_on: req.body.insurance_expire_on,
|
gps_tag: req.body.gps_tag || '',
|
||||||
title_registration_on: req.body.title_registration_on,
|
ezpass: req.body.ezpass || '',
|
||||||
emission_test_on: req.body.emission_test_on,
|
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_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
|
// Save Vehicle in the database
|
||||||
|
|||||||
@ -81,7 +81,12 @@ module.exports = mongoose => {
|
|||||||
event_location: String,
|
event_location: String,
|
||||||
event_prediction_date: String,
|
event_prediction_date: String,
|
||||||
event_reminder_type: 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 }
|
{ collection: 'calendar_event', timestamps: true }
|
||||||
);
|
);
|
||||||
|
|||||||
@ -2,33 +2,84 @@ const uniqueValidator = require('mongoose-unique-validator');
|
|||||||
module.exports = mongoose => {
|
module.exports = mongoose => {
|
||||||
var schema = mongoose.Schema(
|
var schema = mongoose.Schema(
|
||||||
{
|
{
|
||||||
|
// Basic Info
|
||||||
username: {
|
username: {
|
||||||
type: String,
|
type: String,
|
||||||
unique: true
|
unique: true
|
||||||
},
|
},
|
||||||
name: String,
|
name: String,
|
||||||
|
firstname: String,
|
||||||
|
middle_name: String,
|
||||||
|
lastname: String,
|
||||||
name_cn: 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: {
|
email: {
|
||||||
type: String,
|
type: String,
|
||||||
unique: true
|
unique: true
|
||||||
},
|
},
|
||||||
parent_id: String,
|
phone: String,
|
||||||
password: String,
|
mobile_phone: String,
|
||||||
care_provider: String,
|
home_phone: String,
|
||||||
emergency_contact: String,
|
|
||||||
emergency_contact2: String,
|
// Address 1
|
||||||
emergency_contact_name: String,
|
address_line_1: String,
|
||||||
emergency_contact_phone: String,
|
address_line_2: String,
|
||||||
emergency_contact_relationship: String,
|
city: String,
|
||||||
emergency_contact2_name: String,
|
state: String,
|
||||||
emergency_contact2_phone: String,
|
zip_code: String,
|
||||||
emergency_contact2_relationship: String,
|
address_note: String,
|
||||||
medicare_number: String,
|
|
||||||
medicaid_number: String,
|
// Address 2
|
||||||
pharmacy: String,
|
address2_line_1: String,
|
||||||
birth_date: String,
|
address2_line_2: String,
|
||||||
firstname: String,
|
city2: String,
|
||||||
lastname: 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,
|
address1: String,
|
||||||
address2: String,
|
address2: String,
|
||||||
address3: String,
|
address3: String,
|
||||||
@ -39,75 +90,171 @@ module.exports = mongoose => {
|
|||||||
state1: String,
|
state1: String,
|
||||||
zip_code1: String,
|
zip_code1: String,
|
||||||
street_address_2: String,
|
street_address_2: String,
|
||||||
city2: String,
|
|
||||||
state2: String,
|
|
||||||
zip_code2: String,
|
|
||||||
street_address_3: String,
|
street_address_3: String,
|
||||||
city3: String,
|
|
||||||
state3: String,
|
|
||||||
zip_code3: String,
|
|
||||||
street_address_4: String,
|
street_address_4: String,
|
||||||
city4: String,
|
|
||||||
state4: String,
|
|
||||||
zip_code4: String,
|
|
||||||
street_address_5: String,
|
street_address_5: String,
|
||||||
city5: String,
|
apartment: String,
|
||||||
state5: String,
|
|
||||||
zip_code5: String,
|
// Emergency Contact
|
||||||
phone: String,
|
emergency_contact: String, // legacy field
|
||||||
mobile_phone: String,
|
emergency_contact2: String, // legacy field
|
||||||
type: String,
|
emergency_contact_name: String,
|
||||||
avatar: String,
|
emergency_contact_phone: String,
|
||||||
special_needs: String,
|
emergency_contact_relationship: String,
|
||||||
note: String,
|
emergency_contact_relationship_other: String,
|
||||||
language: String,
|
emergency_contact_role: [{
|
||||||
status: String,
|
type: String
|
||||||
pickup_status: 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_by: String,
|
||||||
create_date: Date,
|
create_date: Date,
|
||||||
edit_by: String,
|
referral_source: String,
|
||||||
edit_date: Date,
|
referral_source_other: String,
|
||||||
password: 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,
|
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,
|
pin: String,
|
||||||
admission_date: String,
|
|
||||||
home_phone: String,
|
|
||||||
seating: String,
|
|
||||||
vehicle_no: String,
|
vehicle_no: String,
|
||||||
caller: String,
|
caller: String,
|
||||||
|
placement: String,
|
||||||
|
height: String,
|
||||||
|
weight: String,
|
||||||
|
status: String,
|
||||||
roles: [{
|
roles: [{
|
||||||
type: String
|
type: String
|
||||||
}],
|
}],
|
||||||
discharge_date: String,
|
|
||||||
placement: String,
|
|
||||||
nickname: String,
|
|
||||||
table_id: String,
|
|
||||||
salt: String,
|
|
||||||
groups: [{
|
groups: [{
|
||||||
type: String
|
type: String
|
||||||
}],
|
}],
|
||||||
tags: [{
|
tags: [{
|
||||||
type: String
|
type: String
|
||||||
}],
|
}],
|
||||||
api_token: String,
|
|
||||||
data: String,
|
data: String,
|
||||||
title: String,
|
title: String,
|
||||||
apartment: String,
|
|
||||||
private_note: String,
|
|
||||||
site: Number,
|
site: Number,
|
||||||
disability: Boolean,
|
edit_by: String,
|
||||||
height: String,
|
edit_date: Date,
|
||||||
weight: String,
|
|
||||||
gender: String,
|
|
||||||
text_msg_enabled: Boolean,
|
|
||||||
health_condition: String,
|
|
||||||
allergy_info: String,
|
|
||||||
meal_requirement: String,
|
|
||||||
service_requirement: String,
|
|
||||||
payment_due_date: String,
|
payment_due_date: String,
|
||||||
payment_status: String,
|
payment_status: String
|
||||||
join_reason: String,
|
|
||||||
discharge_reason: String
|
|
||||||
},
|
},
|
||||||
{ collection: 'customer', timestamps: true }
|
{ collection: 'customer', timestamps: true }
|
||||||
);
|
);
|
||||||
|
|||||||
@ -1,39 +1,98 @@
|
|||||||
module.exports = mongoose => {
|
module.exports = mongoose => {
|
||||||
var schema = mongoose.Schema(
|
var schema = mongoose.Schema(
|
||||||
{
|
{
|
||||||
username: String,
|
username: String,
|
||||||
name: String,
|
name_cn: String,
|
||||||
name_cn: String,
|
email: String,
|
||||||
email: String,
|
password: String,
|
||||||
password: String,
|
roles: [{
|
||||||
roles: [{
|
type: String
|
||||||
type: String
|
}],
|
||||||
}],
|
mobile_phone: String,
|
||||||
mobile_phone: String,
|
home_phone: String,
|
||||||
phone: String,
|
language: String,
|
||||||
home_phone: String,
|
employment_status: String,
|
||||||
language: String,
|
address: String,
|
||||||
employment_status: String,
|
date_hired: String,
|
||||||
status: String,
|
driver_capacity: Number,
|
||||||
address: String,
|
create_by: String,
|
||||||
title: String,
|
create_date: Date,
|
||||||
title_cn: String,
|
edit_by: String,
|
||||||
firstname: String,
|
edit_date: Date,
|
||||||
lastname: String,
|
note: String,
|
||||||
department: String,
|
tags: [{
|
||||||
birth_date: String,
|
type: String
|
||||||
driver_capacity: Number,
|
}],
|
||||||
date_hired: String,
|
fetch_route_time: Date,
|
||||||
create_by: String,
|
site: Number,
|
||||||
create_date: Date,
|
// new fields added and legacy feilds are used in HR system
|
||||||
edit_by: String,
|
trinet_id: String,
|
||||||
edit_date: Date,
|
name: String,
|
||||||
note: String,
|
firstname: String,
|
||||||
tags: [{
|
middlename: String,
|
||||||
type: String
|
lastname: String,
|
||||||
}],
|
preferred_name: String,
|
||||||
fetch_route_time: Date,
|
ssn: String,
|
||||||
site: Number
|
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 }
|
{ collection: 'employee', timestamps: true }
|
||||||
);
|
);
|
||||||
|
|||||||
@ -32,4 +32,6 @@ db.label = require("./label.model")(mongoose);
|
|||||||
db.seating = require("./seating.model")(mongoose);
|
db.seating = require("./seating.model")(mongoose);
|
||||||
db.attendance_note = require("./attendance-note.model")(mongoose);
|
db.attendance_note = require("./attendance-note.model")(mongoose);
|
||||||
db.carousel = require("./carousel.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;
|
module.exports = db;
|
||||||
@ -5,33 +5,49 @@ module.exports = mongoose => {
|
|||||||
});
|
});
|
||||||
var schema = mongoose.Schema(
|
var schema = mongoose.Schema(
|
||||||
{
|
{
|
||||||
name: String,
|
// Basic Information
|
||||||
name_original: String,
|
name: String, // Provider name (renamed from doctor name)
|
||||||
name_branch: String,
|
office_name: String, // Merged from name_original and name_branch
|
||||||
specialty: String,
|
specialty: String,
|
||||||
type: String, // value may be ['doctor', 'pharmacy' or 'other']
|
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,
|
city: String,
|
||||||
state: String,
|
state: String,
|
||||||
zipcode: 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']
|
status: String, // value might be ['active', 'inactive']
|
||||||
|
|
||||||
|
// System fields
|
||||||
create_by: String,
|
create_by: String,
|
||||||
create_date: Date,
|
create_date: Date,
|
||||||
parent_id: String,
|
parent_id: String,
|
||||||
ext_id: String,
|
ext_id: String,
|
||||||
category: String,
|
category: String,
|
||||||
description: String,
|
|
||||||
contact: String,
|
|
||||||
fax: String,
|
|
||||||
email: String,
|
|
||||||
note: String,
|
|
||||||
data: Object,
|
data: Object,
|
||||||
edit_by: String,
|
edit_by: String,
|
||||||
edit_date: Date,
|
edit_date: Date,
|
||||||
create_by: String,
|
|
||||||
create_date: Date,
|
|
||||||
site: Number,
|
site: Number,
|
||||||
images: [{
|
images: [{
|
||||||
type: String
|
type: String
|
||||||
|
|||||||
@ -2,38 +2,52 @@ const uniqueValidator = require('mongoose-unique-validator');
|
|||||||
module.exports = mongoose => {
|
module.exports = mongoose => {
|
||||||
var schema = mongoose.Schema(
|
var schema = mongoose.Schema(
|
||||||
{
|
{
|
||||||
|
// Basic Information
|
||||||
vehicle_number: {
|
vehicle_number: {
|
||||||
type: Number,
|
type: Number,
|
||||||
required: true,
|
required: true,
|
||||||
},
|
},
|
||||||
|
responsible_driver: String,
|
||||||
|
responsible_driver_id: String,
|
||||||
|
capacity: Number,
|
||||||
|
mileage: Number,
|
||||||
|
make: String,
|
||||||
|
vehicle_model: String,
|
||||||
|
year: String,
|
||||||
|
vin: String,
|
||||||
tag: {
|
tag: {
|
||||||
type: String,
|
type: String,
|
||||||
required: true
|
required: true
|
||||||
},
|
},
|
||||||
ezpass: {
|
gps_tag: String,
|
||||||
type: String,
|
ezpass: 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,
|
|
||||||
has_lift_equip: Boolean,
|
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,
|
insurance_expire_on: String,
|
||||||
title_registration_on: String,
|
title_registration_on: String,
|
||||||
emission_test_on: String,
|
emission_test_on: String,
|
||||||
oil_change_mileage: Number,
|
oil_change_mileage: Number,
|
||||||
oil_change_date: String,
|
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 }
|
{ collection: 'vehicle', timestamps: true }
|
||||||
);
|
);
|
||||||
|
|||||||
@ -10,7 +10,9 @@ module.exports = app => {
|
|||||||
});
|
});
|
||||||
var router = require("express").Router();
|
var router = require("express").Router();
|
||||||
// Retrieve all employee
|
// 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
|
// Create a new employee
|
||||||
router.post("/", employees.createEmployee);
|
router.post("/", employees.createEmployee);
|
||||||
// Update an employee
|
// Update an employee
|
||||||
|
|||||||
BIN
app/views/.DS_Store
vendored
BIN
app/views/.DS_Store
vendored
Binary file not shown.
@ -1,16 +1,16 @@
|
|||||||
{
|
{
|
||||||
"files": {
|
"files": {
|
||||||
"main.css": "/static/css/main.2c06dda5.css",
|
"main.css": "/static/css/main.7fa0ef40.css",
|
||||||
"main.js": "/static/js/main.cddce86b.js",
|
"main.js": "/static/js/main.ea61a51a.js",
|
||||||
"static/js/787.c4e7f8f9.chunk.js": "/static/js/787.c4e7f8f9.chunk.js",
|
"static/js/787.c4e7f8f9.chunk.js": "/static/js/787.c4e7f8f9.chunk.js",
|
||||||
"static/media/landing.png": "/static/media/landing.d4c6072db7a67dff6a78.png",
|
"static/media/landing.png": "/static/media/landing.d4c6072db7a67dff6a78.png",
|
||||||
"index.html": "/index.html",
|
"index.html": "/index.html",
|
||||||
"main.2c06dda5.css.map": "/static/css/main.2c06dda5.css.map",
|
"main.7fa0ef40.css.map": "/static/css/main.7fa0ef40.css.map",
|
||||||
"main.cddce86b.js.map": "/static/js/main.cddce86b.js.map",
|
"main.ea61a51a.js.map": "/static/js/main.ea61a51a.js.map",
|
||||||
"787.c4e7f8f9.chunk.js.map": "/static/js/787.c4e7f8f9.chunk.js.map"
|
"787.c4e7f8f9.chunk.js.map": "/static/js/787.c4e7f8f9.chunk.js.map"
|
||||||
},
|
},
|
||||||
"entrypoints": [
|
"entrypoints": [
|
||||||
"static/css/main.2c06dda5.css",
|
"static/css/main.7fa0ef40.css",
|
||||||
"static/js/main.cddce86b.js"
|
"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;
|
white-space: nowrap;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.btn-warning {
|
||||||
|
height: 35px;
|
||||||
|
font-size: 13px;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
|
||||||
.text-primary {
|
.text-primary {
|
||||||
color: #0066B1;
|
color: #0066B1;
|
||||||
}
|
}
|
||||||
@ -103,7 +109,7 @@ input {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.app-side-bar-container {
|
.app-side-bar-container {
|
||||||
width: 300px;
|
width: 250px;
|
||||||
border-right: 1px solid #ccc;
|
border-right: 1px solid #ccc;
|
||||||
height: 100vh;
|
height: 100vh;
|
||||||
}
|
}
|
||||||
@ -112,7 +118,7 @@ input {
|
|||||||
padding: 16px;
|
padding: 16px;
|
||||||
border-top: 1px solid #ccc;
|
border-top: 1px solid #ccc;
|
||||||
border-right: 1px solid #ccc;
|
border-right: 1px solid #ccc;
|
||||||
width: 300px;
|
width: 250px;
|
||||||
position: absolute;
|
position: absolute;
|
||||||
bottom: 0;
|
bottom: 0;
|
||||||
left: 0;
|
left: 0;
|
||||||
@ -135,7 +141,7 @@ input {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.app-side-bar-menu-container {
|
.app-side-bar-menu-container {
|
||||||
height: calc(100vh - 120px);
|
height: calc(100vh - 150px);
|
||||||
overflow: auto;
|
overflow: auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -166,7 +172,7 @@ input {
|
|||||||
.app-side-bar-logo {
|
.app-side-bar-logo {
|
||||||
padding: 16px;
|
padding: 16px;
|
||||||
border-bottom: 1px solid #ccc;
|
border-bottom: 1px solid #ccc;
|
||||||
width: 300px;
|
width: 250px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.app-layout .collapsed {
|
.app-layout .collapsed {
|
||||||
@ -180,6 +186,8 @@ input {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.app-main-container {
|
.app-main-container {
|
||||||
|
flex: 1;
|
||||||
|
min-width: 0;
|
||||||
padding: 40px;
|
padding: 40px;
|
||||||
height: 100vh;
|
height: 100vh;
|
||||||
overflow: auto;
|
overflow: auto;
|
||||||
@ -198,6 +206,14 @@ input {
|
|||||||
padding: 4px 24px;
|
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 {
|
.app-main-content-list-func-container {
|
||||||
position: relative;
|
position: relative;
|
||||||
}
|
}
|
||||||
@ -227,10 +243,18 @@ input {
|
|||||||
color: #0066B1 !important;
|
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;
|
border-top: 1px solid #ccc;
|
||||||
padding-top: 24px;
|
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 {
|
.app-main-content-list-func-container .list-func-panel {
|
||||||
@ -358,13 +382,20 @@ table tr {
|
|||||||
border: 1px solid white;
|
border: 1px solid white;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
table {
|
||||||
|
width: 100%;
|
||||||
|
table-layout: auto;
|
||||||
|
}
|
||||||
|
|
||||||
table tr th {
|
table tr th {
|
||||||
border: 1px solid white;
|
border: 1px solid white;
|
||||||
padding: 8px 16px;
|
padding: 8px 16px;
|
||||||
font-size: 12px;
|
font-size: 12px;
|
||||||
background-color: #0066B1;
|
background-color: #0066B1;
|
||||||
color: white;
|
color: white;
|
||||||
min-width: 250px;
|
width: fit-content;
|
||||||
|
max-width: 250px;
|
||||||
|
overflow-wrap: break-word;
|
||||||
}
|
}
|
||||||
|
|
||||||
table tr td {
|
table tr td {
|
||||||
@ -372,7 +403,9 @@ table tr td {
|
|||||||
padding: 8px 16px;
|
padding: 8px 16px;
|
||||||
font-size: 12px;
|
font-size: 12px;
|
||||||
color: #333;
|
color: #333;
|
||||||
min-width: 250px;
|
width: fit-content;
|
||||||
|
max-width: 250px;
|
||||||
|
overflow-wrap: break-word;
|
||||||
}
|
}
|
||||||
|
|
||||||
.clickable {
|
.clickable {
|
||||||
@ -381,13 +414,13 @@ table tr td {
|
|||||||
|
|
||||||
.td-checkbox, .th-checkbox {
|
.td-checkbox, .th-checkbox {
|
||||||
width: 30px !important;
|
width: 30px !important;
|
||||||
padding-left: 8px !important;
|
padding-left: 14px !important;
|
||||||
min-width: 5px !important;
|
min-width: 5px !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
.td-index, .th-index {
|
.td-index, .th-index {
|
||||||
width: 30px !important;
|
width: 30px !important;
|
||||||
padding-left: 8px !important;
|
padding-left: 14px !important;
|
||||||
min-width: 5px !important;
|
min-width: 5px !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -413,11 +446,20 @@ table .group td {
|
|||||||
transform: translate(0px, 37px)!important;
|
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 {
|
.app-main-content-fields-section {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: baseline;
|
align-items: baseline;
|
||||||
margin-top: 24px;
|
margin-top: 24px;
|
||||||
margin-bottom: 40px;
|
margin-bottom: 40px;
|
||||||
|
flex-wrap: wrap;
|
||||||
}
|
}
|
||||||
|
|
||||||
.app-main-content-fields-section.flex-end {
|
.app-main-content-fields-section.flex-end {
|
||||||
@ -425,6 +467,7 @@ table .group td {
|
|||||||
align-items: flex-end;
|
align-items: flex-end;
|
||||||
margin-top: 24px;
|
margin-top: 24px;
|
||||||
margin-bottom: 40px;
|
margin-bottom: 40px;
|
||||||
|
flex-wrap: wrap;
|
||||||
}
|
}
|
||||||
|
|
||||||
.app-main-content-fields-section.column {
|
.app-main-content-fields-section.column {
|
||||||
@ -456,6 +499,7 @@ table .group td {
|
|||||||
color: black;
|
color: black;
|
||||||
font-weight: 500;
|
font-weight: 500;
|
||||||
line-height: 2;
|
line-height: 2;
|
||||||
|
white-space: nowrap;
|
||||||
}
|
}
|
||||||
|
|
||||||
.multi-columns-container {
|
.multi-columns-container {
|
||||||
@ -497,12 +541,15 @@ table .group td {
|
|||||||
|
|
||||||
.app-main-content-fields-section.short .field-body {
|
.app-main-content-fields-section.short .field-body {
|
||||||
margin-right: 20px;
|
margin-right: 20px;
|
||||||
|
min-width: 140px;
|
||||||
|
flex: 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
.app-main-content-fields-section.short {
|
.app-main-content-fields-section.short {
|
||||||
margin-bottom: 0;
|
margin-bottom: 0;
|
||||||
margin-top: 12px;
|
margin-top: 12px;
|
||||||
justify-content: space-between;
|
display: flex;
|
||||||
|
gap: 16px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.app-main-content-fields-section.with-function .react-datepicker-wrapper {
|
.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] {
|
.app-main-content-fields-section.dropdown-container input[type=text] {
|
||||||
width: 210px;
|
width: 210px;
|
||||||
height: 35px;
|
height: 45px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.app-main-content-fields-section.dropdown-container input[type=number] {
|
.app-main-content-fields-section.dropdown-container input[type=number] {
|
||||||
width: 210px;
|
width: 210px;
|
||||||
height: 35px;
|
height: 45px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.app-main-content-fields-section.dropdown-container select {
|
.app-main-content-fields-section.dropdown-container select {
|
||||||
width: 210px;
|
width: 210px;
|
||||||
height: 35px;
|
height: 45px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.app-main-content-fields-section.with-function .react-datepicker-wrapper input[type=text] {
|
.app-main-content-fields-section.with-function .react-datepicker-wrapper input[type=text] {
|
||||||
height: 35px;
|
height: 45px;
|
||||||
width: 120px;
|
width: 120px;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -601,8 +648,9 @@ table .group td {
|
|||||||
select {
|
select {
|
||||||
margin-left: 5px;
|
margin-left: 5px;
|
||||||
min-width: 200px;
|
min-width: 200px;
|
||||||
height: 40px;
|
height: 45px;
|
||||||
padding: 5px;
|
padding: 5px;
|
||||||
|
box-sizing: border-box;
|
||||||
}
|
}
|
||||||
|
|
||||||
a {
|
a {
|
||||||
@ -611,11 +659,12 @@ a {
|
|||||||
}
|
}
|
||||||
|
|
||||||
input[type="text"] {
|
input[type="text"] {
|
||||||
height: 30px;
|
height: 45px;
|
||||||
padding-left: 15px;
|
padding-left: 15px;
|
||||||
padding-right: 15px;
|
padding-right: 15px;
|
||||||
border-radius: 8px;
|
border-radius: 8px;
|
||||||
border: 1px solid #ccc;
|
border: 1px solid #ccc;
|
||||||
|
box-sizing: border-box;
|
||||||
}
|
}
|
||||||
|
|
||||||
textarea {
|
textarea {
|
||||||
@ -628,33 +677,54 @@ textarea {
|
|||||||
}
|
}
|
||||||
|
|
||||||
input[type="number"] {
|
input[type="number"] {
|
||||||
height: 30px;
|
height: 45px;
|
||||||
padding-left: 15px;
|
padding-left: 15px;
|
||||||
padding-right: 15px;
|
padding-right: 15px;
|
||||||
border-radius: 8px;
|
border-radius: 8px;
|
||||||
border: 1px solid #ccc;
|
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"] {
|
input[type="email"] {
|
||||||
height: 30px;
|
height: 45px;
|
||||||
padding-left: 15px;
|
padding-left: 15px;
|
||||||
padding-right: 15px;
|
padding-right: 15px;
|
||||||
border-radius: 8px;
|
border-radius: 8px;
|
||||||
border: 1px solid #ccc;
|
border: 1px solid #ccc;
|
||||||
|
/* box-sizing: border-box; */
|
||||||
}
|
}
|
||||||
|
|
||||||
input[type="password"] {
|
input[type="password"] {
|
||||||
height: 30px;
|
height: 45px;
|
||||||
padding-left: 15px;
|
padding-left: 15px;
|
||||||
padding-right: 15px;
|
padding-right: 15px;
|
||||||
border-radius: 8px;
|
border-radius: 8px;
|
||||||
border: 1px solid #ccc;
|
border: 1px solid #ccc;
|
||||||
|
/* box-sizing: border-box; */
|
||||||
}
|
}
|
||||||
|
|
||||||
input[type="checkbox"] {
|
input[type="checkbox"] {
|
||||||
margin-left: 8px;
|
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 {
|
.clickable {
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
}
|
}
|
||||||
@ -702,6 +772,32 @@ input[type="checkbox"] {
|
|||||||
|
|
||||||
.event-list-item-container {
|
.event-list-item-container {
|
||||||
border-radius: 8px;
|
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 {
|
.event-red {
|
||||||
@ -764,6 +860,44 @@ input[type="checkbox"] {
|
|||||||
background-color: #FFD1DC !important;
|
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 {
|
.btn-no-deco {
|
||||||
text-decoration: none!important;
|
text-decoration: none!important;
|
||||||
padding-top: 6px;
|
padding-top: 6px;
|
||||||
@ -994,7 +1128,7 @@ input[type="checkbox"] {
|
|||||||
font-size: 13px;
|
font-size: 13px;
|
||||||
cursor: move;
|
cursor: move;
|
||||||
background: #eee;
|
background: #eee;
|
||||||
margin-left: 60px;
|
margin-left: 65px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.customers-dnd-item-container-absent {
|
.customers-dnd-item-container-absent {
|
||||||
@ -1009,8 +1143,9 @@ input[type="checkbox"] {
|
|||||||
|
|
||||||
.new-customers-dnd-item-container {
|
.new-customers-dnd-item-container {
|
||||||
border: none;
|
border: none;
|
||||||
padding: 0 16px;
|
padding: 0 16px 0 25px;
|
||||||
margin-bottom: 24px;
|
margin-bottom: 24px;
|
||||||
|
margin-top: 16px;
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
font-size: 13px;
|
font-size: 13px;
|
||||||
@ -1024,6 +1159,7 @@ input[type="checkbox"] {
|
|||||||
.stop-index {
|
.stop-index {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
left: 0;
|
left: 0;
|
||||||
|
padding-right: 12px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.customer-dnd-item {
|
.customer-dnd-item {
|
||||||
@ -1053,10 +1189,44 @@ input[type="checkbox"] {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.react-datepicker__input-container input {
|
.react-datepicker__input-container input {
|
||||||
height: 35px;
|
height: 45px;
|
||||||
width: 100%;
|
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 {
|
.admin-nav a {
|
||||||
text-decoration: none !important;
|
text-decoration: none !important;
|
||||||
}
|
}
|
||||||
@ -1413,6 +1583,16 @@ input[type="checkbox"] {
|
|||||||
text-align: center;
|
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 {
|
.guest-name {
|
||||||
text-align: center;
|
text-align: center;
|
||||||
font-size: 10px;
|
font-size: 10px;
|
||||||
@ -1593,66 +1773,199 @@ input[type="checkbox"] {
|
|||||||
width: 100vw;
|
width: 100vw;
|
||||||
height: 100vh;
|
height: 100vh;
|
||||||
z-index: 9998;
|
z-index: 9998;
|
||||||
overflow: auto;
|
overflow: hidden;
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
flex-direction: column;
|
||||||
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;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.fullscreen-mode .app-main-content-list-func-container {
|
.fullscreen-mode .app-main-content-list-func-container {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: center;
|
flex-direction: column;
|
||||||
margin-top: auto;
|
padding: clamp(10px, 2vw, 30px);
|
||||||
|
box-sizing: border-box;
|
||||||
}
|
}
|
||||||
|
|
||||||
.fullscreen-mode .multi-columns-container {
|
.fullscreen-mode .multi-columns-container {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
max-width: 1200px;
|
height: 100%;
|
||||||
margin: 0 auto;
|
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: center;
|
gap: clamp(10px, 1.5vw, 25px);
|
||||||
gap: 20px;
|
flex: 1;
|
||||||
|
min-height: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.fullscreen-mode .column-container {
|
.fullscreen-mode .column-container {
|
||||||
flex: 1;
|
flex: 1;
|
||||||
max-width: 400px;
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
min-width: 0;
|
||||||
|
height: 100%;
|
||||||
|
gap: clamp(8px, 1vw, 16px);
|
||||||
}
|
}
|
||||||
|
|
||||||
.fullscreen-mode .column-card {
|
.fullscreen-mode .column-card {
|
||||||
background-color: rgba(255, 255, 255, 0.8) !important;
|
background-color: rgba(255, 255, 255, 0.9) !important;
|
||||||
backdrop-filter: blur(5px);
|
backdrop-filter: blur(8px);
|
||||||
margin-bottom: 20px;
|
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 {
|
.fullscreen-mode .card {
|
||||||
background-color: rgba(255, 255, 255, 0.8) !important;
|
background-color: rgba(255, 255, 255, 0.9) !important;
|
||||||
backdrop-filter: blur(5px);
|
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 {
|
.fullscreen-hint {
|
||||||
position: fixed;
|
position: fixed;
|
||||||
top: 20px;
|
top: clamp(10px, 1.5vw, 25px);
|
||||||
right: 20px;
|
right: clamp(10px, 1.5vw, 25px);
|
||||||
background-color: rgba(0, 0, 0, 0.7);
|
background-color: rgba(0, 0, 0, 0.75);
|
||||||
color: white;
|
color: white;
|
||||||
padding: 10px 15px;
|
padding: clamp(8px, 1vw, 15px) clamp(12px, 1.5vw, 20px);
|
||||||
border-radius: 5px;
|
border-radius: clamp(4px, 0.5vw, 8px);
|
||||||
z-index: 9999;
|
z-index: 9999;
|
||||||
font-size: 14px;
|
font-size: clamp(11px, 1.2vw, 16px);
|
||||||
animation: fadeIn 0.3s ease-in;
|
animation: fadeIn 0.3s ease-in;
|
||||||
|
backdrop-filter: blur(4px);
|
||||||
}
|
}
|
||||||
|
|
||||||
@keyframes fadeIn {
|
@keyframes fadeIn {
|
||||||
@ -1674,39 +1987,125 @@ input[type="checkbox"] {
|
|||||||
background-attachment: fixed;
|
background-attachment: fixed;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Info Screen Table Overrides for Full Screen */
|
/* Prevent wrapping in fullscreen table cells */
|
||||||
.fullscreen-mode .info-screen-appointments-table {
|
.fullscreen-mode .personnel-info-table {
|
||||||
background-color: rgba(255, 255, 255, 0.9);
|
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);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Responsive adjustments for full screen mode */
|
|
||||||
@media (max-width: 1200px) {
|
@media (max-width: 1200px) {
|
||||||
.fullscreen-mode .multi-columns-container {
|
.fullscreen-mode .column-container:nth-child(2) .row.mb-4 {
|
||||||
max-width: 1000px;
|
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
gap: 15px;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.fullscreen-mode .column-container {
|
.fullscreen-mode .carousel {
|
||||||
max-width: 100%;
|
height: clamp(100px, 12vh, 180px) !important;
|
||||||
width: 100%;
|
}
|
||||||
|
|
||||||
|
.fullscreen-mode .carousel-item img {
|
||||||
|
height: clamp(100px, 12vh, 180px) !important;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@media (max-width: 768px) {
|
@media (max-height: 700px) {
|
||||||
.fullscreen-mode .app-main-content-list-container {
|
.fullscreen-mode .carousel {
|
||||||
padding: 10px;
|
height: clamp(80px, 10vh, 140px) !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
.fullscreen-mode .multi-columns-container {
|
.fullscreen-mode .carousel-item img {
|
||||||
gap: 10px;
|
height: clamp(80px, 10vh, 140px) !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
.fullscreen-mode .column-card {
|
.fullscreen-mode h6 {
|
||||||
margin-bottom: 15px;
|
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 */
|
/* InfoScreen custom classes for moving inline styles to CSS */
|
||||||
|
|
||||||
.info-title {
|
.info-title {
|
||||||
|
|||||||
@ -25,6 +25,12 @@ import Login from "./components/login/Login";
|
|||||||
import RoutesHistory from "./components/trans-routes/RoutesHistory";
|
import RoutesHistory from "./components/trans-routes/RoutesHistory";
|
||||||
import RouteTemplatesList from "./components/trans-routes/RouteTemplatesList";
|
import RouteTemplatesList from "./components/trans-routes/RouteTemplatesList";
|
||||||
import RouteTemplateEdit from "./components/trans-routes/RouteTemplateEdit";
|
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 CreateCustomer from "./components/customers/CreateCustomer";
|
||||||
import CustomersList from "./components/customers/CustomersList";
|
import CustomersList from "./components/customers/CustomersList";
|
||||||
import ViewCustomer from "./components/customers/ViewCustomer";
|
import ViewCustomer from "./components/customers/ViewCustomer";
|
||||||
@ -119,6 +125,11 @@ function App() {
|
|||||||
<Route path="history" element={<RoutesHistory />} />
|
<Route path="history" element={<RoutesHistory />} />
|
||||||
<Route path="templates" element={<RouteTemplatesList />} />
|
<Route path="templates" element={<RouteTemplatesList />} />
|
||||||
<Route path="templates/edit/:id" element={<RouteTemplateEdit />} />
|
<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-signature" element={<RouteSignatureList/>} />
|
||||||
<Route path="route-report-with-signature/:id" element={<RouteReportWithSignature/>} />
|
<Route path="route-report-with-signature/:id" element={<RouteReportWithSignature/>} />
|
||||||
</Route>
|
</Route>
|
||||||
@ -158,6 +169,7 @@ function App() {
|
|||||||
|
|
||||||
<Route path="/seating" element={<Seating />} />
|
<Route path="/seating" element={<Seating />} />
|
||||||
<Route path="/center-calendar" element={<CenterCalendar/>} />
|
<Route path="/center-calendar" element={<CenterCalendar/>} />
|
||||||
|
<Route path="/meal-status" element={<MealStatus />} />
|
||||||
|
|
||||||
<Route path="/medical" element={<Medical />}>
|
<Route path="/medical" element={<Medical />}>
|
||||||
<Route path="" element={<Navigate replace to="index" />} />
|
<Route path="" element={<Navigate replace to="index" />} />
|
||||||
|
|||||||
@ -646,7 +646,7 @@ const AdminView = () => {
|
|||||||
<>
|
<>
|
||||||
<div className="list row mb-4">
|
<div className="list row mb-4">
|
||||||
<Breadcrumb>
|
<Breadcrumb>
|
||||||
<Breadcrumb.Item>Dashboard</Breadcrumb.Item>
|
<Breadcrumb.Item href="/">Dashboard</Breadcrumb.Item>
|
||||||
<Breadcrumb.Item active>
|
<Breadcrumb.Item active>
|
||||||
Admin View
|
Admin View
|
||||||
</Breadcrumb.Item>
|
</Breadcrumb.Item>
|
||||||
|
|||||||
@ -660,7 +660,7 @@ const CustomerReport = () => {
|
|||||||
<>
|
<>
|
||||||
<div className="list row mb-4">
|
<div className="list row mb-4">
|
||||||
<Breadcrumb>
|
<Breadcrumb>
|
||||||
<Breadcrumb.Item>Transportation</Breadcrumb.Item>
|
<Breadcrumb.Item href="/trans-routes/dashboard">Transportation</Breadcrumb.Item>
|
||||||
<Breadcrumb.Item active>
|
<Breadcrumb.Item active>
|
||||||
Customer Reports
|
Customer Reports
|
||||||
</Breadcrumb.Item>
|
</Breadcrumb.Item>
|
||||||
|
|||||||
@ -17,6 +17,7 @@ import '@schedule-x/theme-default/dist/calendar.css';
|
|||||||
import { Archive, PencilSquare, Filter } from "react-bootstrap-icons";
|
import { Archive, PencilSquare, Filter } from "react-bootstrap-icons";
|
||||||
import DatePicker from "react-datepicker";
|
import DatePicker from "react-datepicker";
|
||||||
import { vehicleSlice } from "../../store";
|
import { vehicleSlice } from "../../store";
|
||||||
|
import Select from 'react-select';
|
||||||
// import { Scheduler } from "@aldabil/react-scheduler";
|
// import { Scheduler } from "@aldabil/react-scheduler";
|
||||||
|
|
||||||
|
|
||||||
@ -40,6 +41,8 @@ const EventsCalendar = () => {
|
|||||||
const eventModalService = createEventModalPlugin();
|
const eventModalService = createEventModalPlugin();
|
||||||
const eventRecurrence = createEventRecurrencePlugin();
|
const eventRecurrence = createEventRecurrencePlugin();
|
||||||
const [groupedEvents, setGroupedEvents] = useState(new Map());
|
const [groupedEvents, setGroupedEvents] = useState(new Map());
|
||||||
|
const [currentRangeStart, setCurrentRangeStart] = useState(null);
|
||||||
|
const [currentRangeEnd, setCurrentRangeEnd] = useState(null);
|
||||||
const [showCreationModal, setShowCreationModal] = useState(false);
|
const [showCreationModal, setShowCreationModal] = useState(false);
|
||||||
const [newEventStartDateTime, setNewEventStartDateTime] = useState(new Date());
|
const [newEventStartDateTime, setNewEventStartDateTime] = useState(new Date());
|
||||||
const [newEventEndDateTime, setNewEventEndDateTime] = useState(new Date())
|
const [newEventEndDateTime, setNewEventEndDateTime] = useState(new Date())
|
||||||
@ -59,6 +62,65 @@ const EventsCalendar = () => {
|
|||||||
const [vehicles, setVehicles] = useState([]);
|
const [vehicles, setVehicles] = useState([]);
|
||||||
const [employees, setEmployees] = 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 = {
|
const eventTypeMap = {
|
||||||
medicalCalendar: 'medical',
|
medicalCalendar: 'medical',
|
||||||
activitiesCalendar: 'activity',
|
activitiesCalendar: 'activity',
|
||||||
@ -81,34 +143,57 @@ const EventsCalendar = () => {
|
|||||||
events: events,
|
events: events,
|
||||||
plugins: [eventModalService, eventsServicePlugin, eventRecurrence],
|
plugins: [eventModalService, eventsServicePlugin, eventRecurrence],
|
||||||
callbacks: {
|
callbacks: {
|
||||||
onSelectedDateUpdate(date) {
|
onRangeUpdate(range) {
|
||||||
setFromDate(new Date(new Date(date).getFullYear(), new Date(date).getMonth(), 1));
|
console.log('new calendar range start date', range.start);
|
||||||
setToDate(new Date(new Date(date).getFullYear(), new Date(date).getMonth() + 1, 0));
|
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) {
|
onClickDate(date) {
|
||||||
|
|
||||||
|
|
||||||
setNewEventStartDateTime(new Date(date))
|
setNewEventStartDateTime(new Date(date))
|
||||||
setNewEventEndDateTime(new Date(date));
|
setNewEventEndDateTime(new Date(date));
|
||||||
setShowCreationModal(true);
|
setShowCreationModal(true);
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
},
|
},
|
||||||
onClickDateTime(dateTime) {
|
onClickDateTime(dateTime) {
|
||||||
|
|
||||||
setNewEventStartDateTime(new Date(dateTime.replace(' ', 'T')));
|
setNewEventStartDateTime(new Date(dateTime.replace(' ', 'T')));
|
||||||
setNewEventEndDateTime(new Date(dateTime.replace(' ', 'T')));
|
setNewEventEndDateTime(new Date(dateTime.replace(' ', 'T')));
|
||||||
setShowCreationModal(true);
|
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 getGroupedEvents = () => {
|
||||||
const eventsDateMap = new Map();
|
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');
|
const dateString = moment(eventItem.start_time).format('MMM Do, YYYY');
|
||||||
if (eventsDateMap.has(dateString)) {
|
if (eventsDateMap.has(dateString)) {
|
||||||
eventsDateMap.set(dateString, [...eventsDateMap.get(dateString), eventItem]);
|
eventsDateMap.set(dateString, [...eventsDateMap.get(dateString), eventItem]);
|
||||||
@ -134,7 +219,7 @@ const EventsCalendar = () => {
|
|||||||
EmployeeService.getAllEmployees().then((data) => {
|
EmployeeService.getAllEmployees().then((data) => {
|
||||||
setEmployees(data.data);
|
setEmployees(data.data);
|
||||||
});
|
});
|
||||||
CustomerService.getAllCustomers().then((data) => {
|
CustomerService.getAllActiveCustomers().then((data) => {
|
||||||
setCustomers(data.data);
|
setCustomers(data.data);
|
||||||
});
|
});
|
||||||
ResourceService.getAll().then((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.endTime = item?.start_time? `${moment(new Date(item?.end_time)).format('YYYY-MM-DD HH:mm')}` : '' ;
|
||||||
item.fasting = item?.data?.fasting || '';
|
item.fasting = item?.data?.fasting || '';
|
||||||
item.transportation = item?.link_event_name || '';
|
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.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')}`);
|
item.end = item?.stop_time? `${moment(new Date(item?.stop_time)).format('YYYY-MM-DD HH:mm')}` : (item?.start_time? `${moment(item?.start_time).format('YYYY-MM-DD HH:mm')}` : `${moment().format('YYYY-MM-DD HH:mm')}`);
|
||||||
const transportationInfo = EventsService.getTransportationInfo(allEvents, item, timeData);
|
const transportationInfo = EventsService.getTransportationInfo(allEvents, item, timeData);
|
||||||
@ -203,12 +289,15 @@ const EventsCalendar = () => {
|
|||||||
}
|
}
|
||||||
}, [customers, resources, timeData, currentTab, allEvents, showDeletedItems])
|
}, [customers, resources, timeData, currentTab, allEvents, showDeletedItems])
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (events && calendar) {
|
if (events && calendar) {
|
||||||
|
console.log("CenterCalendar useEffect - events:", events.length, "range:", currentRangeStart, "to", currentRangeEnd);
|
||||||
calendar?.eventsService?.set(events);
|
calendar?.eventsService?.set(events);
|
||||||
setGroupedEvents(getGroupedEvents());
|
setGroupedEvents(getGroupedEvents());
|
||||||
}
|
}
|
||||||
}, [events]);
|
}, [events, currentRangeStart, currentRangeEnd]);
|
||||||
|
|
||||||
|
|
||||||
const redirectToAdmin = () => {
|
const redirectToAdmin = () => {
|
||||||
@ -303,13 +392,23 @@ const EventsCalendar = () => {
|
|||||||
const customComponents = {
|
const customComponents = {
|
||||||
eventModal: ({calendarEvent}) => {
|
eventModal: ({calendarEvent}) => {
|
||||||
return <>
|
return <>
|
||||||
|
|
||||||
<div className="sx__event-modal__title">{currentTab === 'medicalCalendar' ? calendarEvent?.customer : calendarEvent?.title}</div>
|
<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">{`${calendarEvent?.start}`}</div>
|
||||||
<div className="sx__event-modal__time">
|
<div className="sx__event-modal__time" style={{ display: 'flex', gap: '12px', marginTop: '8px' }}>
|
||||||
{currentTab === 'medicalCalendar' && <PencilSquare size={16} onClick={() => goToEdit(calendarEvent?.id)} className="me-4"></PencilSquare>}
|
<PencilSquare
|
||||||
<Archive size={16} onClick={() =>{disableEvent(calendarEvent?.id)}}></Archive> </div>
|
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">
|
const calendarView = <div className="multi-columns-container">
|
||||||
<div className="column-container" style={{'minWidth': '1000px'}}>
|
<div className="column-container" style={{'minWidth': '1000px'}}>
|
||||||
{calendar && <ScheduleXCalendar customComponents={customComponents} calendarApp={calendar} />}
|
{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>
|
||||||
<div className="column-container">
|
<div className="column-container">
|
||||||
<div className="column-card">
|
<div className="column-card" style={{ maxHeight: '800px', overflowY: 'auto', overflowX: 'hidden', padding: '16px' }}>
|
||||||
<h6 className="text-primary me-4">List</h6>
|
<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) => {
|
Array.from(groupedEvents?.keys())?.map((key) => {
|
||||||
return <>
|
return <div key={key}>
|
||||||
<h6 className="text-primary me-2">{key}</h6>
|
<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
|
||||||
<div className="event-item-flex">
|
key={eventItem.id}
|
||||||
<div className="sx__month-agenda-event__title">{currentTab === 'medicalCalendar' ? eventItem.customer : eventItem.title}</div>
|
className={`event-${eventItem.color || 'primary'} mb-3 event-list-item-container`}
|
||||||
<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>
|
onClick={() => currentTab === 'medicalCalendar' && goToView(eventItem.id)}
|
||||||
</div>
|
style={{ cursor: currentTab === 'medicalCalendar' ? 'pointer' : 'default', padding: '8px 12px', borderRadius: '4px' }}
|
||||||
<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>}
|
{/* Medical Events */}
|
||||||
{eventItem?.event_prediction_date && <div className="sx__event-modal__time with-padding">{ `Deadline: ${moment(eventItem?.event_prediction_date).format('MM/DD/YYYY')}`}</div>}
|
{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" 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>
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
</div>
|
</div>
|
||||||
@ -383,54 +579,162 @@ const handleClose = () => {
|
|||||||
setNewEventTargetType('');
|
setNewEventTargetType('');
|
||||||
setShowCreationModal(false);
|
setShowCreationModal(false);
|
||||||
setNewEventEndDateTime(undefined);
|
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 handleSave = () => {
|
||||||
const data = {
|
const userName = localStorage.getItem('user') && JSON.parse(localStorage.getItem('user'))?.name;
|
||||||
title: newEventTitle,
|
|
||||||
description: newEventDescription,
|
let data = {
|
||||||
type: newEventType,
|
type: newEventType,
|
||||||
department: newEventDepartment,
|
|
||||||
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',
|
status: 'active',
|
||||||
create_by: localStorage.getItem('user') && JSON.parse(localStorage.getItem('user'))?.name,
|
create_by: userName,
|
||||||
edit_by: localStorage.getItem('user') && JSON.parse(localStorage.getItem('user'))?.name,
|
edit_by: userName,
|
||||||
edit_date: new Date(),
|
edit_date: new Date(),
|
||||||
create_date: new Date(),
|
create_date: new Date(),
|
||||||
edit_history: [{ employee: localStorage.getItem('user') && JSON.parse(localStorage.getItem('user'))?.name, 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,
|
||||||
|
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.createNewEvent(data).then(() => {
|
||||||
EventsService.getAllEvents({ from: EventsService.formatDate(fromDate), to: EventsService.formatDate(toDate) }).then((data) => {
|
EventsService.getAllEvents({ from: EventsService.formatDate(fromDate), to: EventsService.formatDate(toDate) }).then((data) => {
|
||||||
setAllEvents(data.data);
|
setAllEvents(data.data);
|
||||||
setShowCreationModal(false);
|
handleClose();
|
||||||
setNewEventDescription('');
|
|
||||||
setNewEventTitle('');
|
|
||||||
setNewEventLocation('');
|
|
||||||
setNewEventSource(undefined);
|
|
||||||
setNewEventTarget(undefined);
|
|
||||||
setNewEventStartDateTime(undefined);
|
|
||||||
setNewEventType('');
|
|
||||||
})
|
})
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 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 (
|
return (
|
||||||
<>
|
<>
|
||||||
<div className="list row mb-4">
|
<div className="list row mb-4">
|
||||||
<Breadcrumb>
|
<Breadcrumb>
|
||||||
<Breadcrumb.Item>General</Breadcrumb.Item>
|
<Breadcrumb.Item href="/">General</Breadcrumb.Item>
|
||||||
<Breadcrumb.Item active>
|
<Breadcrumb.Item active>
|
||||||
Calendar
|
Calendar
|
||||||
</Breadcrumb.Item>
|
</Breadcrumb.Item>
|
||||||
@ -451,7 +755,7 @@ const handleSave = () => {
|
|||||||
<Tab eventKey="activitiesCalendar" title="Activities">
|
<Tab eventKey="activitiesCalendar" title="Activities">
|
||||||
|
|
||||||
</Tab>
|
</Tab>
|
||||||
<Tab eventKey="incidentsCalendar" title="Important Notes And Incidents">
|
<Tab eventKey="incidentsCalendar" title="Attendance">
|
||||||
|
|
||||||
</Tab>
|
</Tab>
|
||||||
<Tab eventKey="mealPlanCalendar" title="Meal Plan">
|
<Tab eventKey="mealPlanCalendar" title="Meal Plan">
|
||||||
@ -476,189 +780,283 @@ const handleSave = () => {
|
|||||||
<Dropdown.Menu as={customMenu}/>
|
<Dropdown.Menu as={customMenu}/>
|
||||||
</Dropdown>
|
</Dropdown>
|
||||||
</div>
|
</div>
|
||||||
<Modal show={showCreationModal && currentTab !== 'medicalCalendar'} onHide={handleClose}>
|
<Modal show={showCreationModal} onHide={handleClose} size="sm" dialogClassName="calendar-event-modal">
|
||||||
<Modal.Header closeButton>
|
<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.Header>
|
||||||
<Modal.Body>
|
<Modal.Body>
|
||||||
<div className="app-main-content-fields-section">
|
{/* Medical Appointment Fields */}
|
||||||
<div className="me-4">
|
{currentTab === 'medicalCalendar' && (
|
||||||
<div className="field-label">Title
|
<>
|
||||||
<span className="required">*</span>
|
<div className="mb-3">
|
||||||
</div>
|
<div className="field-label">Customer<span className="required">*</span></div>
|
||||||
<input type="text" placeholder="Briefly Describe activity, incident, menu or reminder" value={newEventTitle || ''} onChange={e => setNewEventTitle(e.target.value)}/>
|
<select className="form-control" value={newEventCustomer} onChange={(e) => setNewEventCustomer(e.target.value)}>
|
||||||
</div>
|
<option value="">Select Customer</option>
|
||||||
</div>
|
{customers.filter(item => item?.status === 'active' && item?.type !== 'discharged').map((item) => <option key={item.id} value={item.id}>{item.name}</option>)}
|
||||||
<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>
|
|
||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
|
<div className="mb-3">
|
||||||
|
<div className="field-label">Provider<span className="required">*</span></div>
|
||||||
|
<select className="form-control" value={newEventResource} onChange={(e) => setNewEventResource(e.target.value)}>
|
||||||
|
<option value="">Select Provider</option>
|
||||||
|
{resources.filter(item => item?.status === 'active').map((item) => <option key={item.id} value={item.id}>{item.name}</option>)}
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
<div className="mb-3">
|
||||||
|
<div className="field-label">Appointment Time<span className="required">*</span></div>
|
||||||
|
<DatePicker
|
||||||
|
className="form-control"
|
||||||
|
selected={newEventStartDateTime}
|
||||||
|
onChange={setNewEventStartDateTime}
|
||||||
|
showTimeSelect
|
||||||
|
timeFormat="HH:mm"
|
||||||
|
timeIntervals={15}
|
||||||
|
dateFormat="MM/dd/yyyy, HH:mm"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div className="mb-3">
|
||||||
|
<div className="field-label">Language Support</div>
|
||||||
|
<select className="form-control" value={newEventInterpreter} onChange={(e) => setNewEventInterpreter(e.target.value)}>
|
||||||
|
<option value="">Select</option>
|
||||||
|
<option value="Checkin">Checkin</option>
|
||||||
|
<option value="Center">Center</option>
|
||||||
|
<option value="Family">Family</option>
|
||||||
|
<option value="Office in person">Office in person</option>
|
||||||
|
<option value="Office by phone">Office by phone</option>
|
||||||
|
<option value="Nurse">Nurse</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
<div className="mb-3">
|
||||||
|
<div className="field-label">Transportation Support</div>
|
||||||
|
<select className="form-control" value={newEventTransMethod} onChange={(e) => setNewEventTransMethod(e.target.value)}>
|
||||||
|
<option value="">Select</option>
|
||||||
|
<option value="ByOwn">Self-Transport</option>
|
||||||
|
<option value="By Center Transportation">By Center Transportation</option>
|
||||||
|
<option value="DropOff Only">Drop-Off Only</option>
|
||||||
|
<option value="Pickup Only">Pick-Up Only</option>
|
||||||
|
<option value="Client Does Not need to Go">Medication Pickup Only</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
<div className="mb-3">
|
||||||
|
<div className="field-label">Label</div>
|
||||||
|
<select className="form-control" value={newEventColor} onChange={(e) => setNewEventColor(e.target.value)}>
|
||||||
|
<option value="">Select</option>
|
||||||
|
{EventsService.labelOptions?.map((item) => <option key={item.value} value={item.value}>{item.label}</option>)}
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
|
||||||
</div>
|
{/* Activity Fields */}
|
||||||
</div>
|
{currentTab === 'activitiesCalendar' && (
|
||||||
<div className="app-main-content-fields-section">
|
<>
|
||||||
<div className="me-4">
|
<div className="mb-3">
|
||||||
<div className="field-label">Start Time
|
<div className="field-label">Activity Name<span className="required">*</span></div>
|
||||||
<span className="required">*</span>
|
<input type="text" className="form-control" placeholder="Enter activity name" value={newEventTitle || ''} onChange={e => setNewEventTitle(e.target.value)}/>
|
||||||
</div>
|
</div>
|
||||||
<DatePicker
|
<div className="mb-3">
|
||||||
selected={newEventStartDateTime}
|
<div className="field-label">Date & Time</div>
|
||||||
onChange={setNewEventStartDateTime}
|
<DatePicker
|
||||||
showTimeInput
|
className="form-control"
|
||||||
timeInputLabel="Time:"
|
selected={newEventStartDateTime}
|
||||||
dateFormat="MM/dd/yyyy, HH:mm"
|
onChange={setNewEventStartDateTime}
|
||||||
></DatePicker>
|
showTimeSelect
|
||||||
</div>
|
timeFormat="HH:mm"
|
||||||
<div className="me-4">
|
timeIntervals={15}
|
||||||
<div className="field-label">End Time
|
dateFormat="MM/dd/yyyy, HH:mm"
|
||||||
<span className="required">*</span>
|
/>
|
||||||
<span className="field-blurb float-right">Make sure end time is later than start time </span>
|
|
||||||
</div>
|
</div>
|
||||||
<DatePicker
|
<div className="mb-3">
|
||||||
selected={newEventEndDateTime}
|
<div className="field-label">Category<span className="required">*</span></div>
|
||||||
onChange={setNewEventEndDateTime}
|
<select className="form-control" value={newActivityCategory} onChange={(e) => setNewActivityCategory(e.target.value)}>
|
||||||
showTimeInput
|
<option value="">Select Category</option>
|
||||||
timeInputLabel="Time:"
|
<option value="red">Classes</option>
|
||||||
dateFormat="MM/dd/yyyy, HH:mm"
|
<option value="pink">Games</option>
|
||||||
></DatePicker>
|
<option value="green">Events</option>
|
||||||
</div>
|
<option value="blue">Outings</option>
|
||||||
</div>
|
<option value="purple">Personal Care</option>
|
||||||
{ currentTab !== 'mealPlanCalendar' && <> <div className="app-main-content-fields-section">
|
<option value="brown">Care Activities</option>
|
||||||
<div className="me-4">
|
</select>
|
||||||
<div className="field-label">Who will we take care of in this event:</div>
|
</div>
|
||||||
<select value={newEventTargetType} onChange={(e) => setNewEventTargetType(e.target.value)}>
|
<div className="mb-3">
|
||||||
<option value=""></option>
|
<div className="field-label">Location</div>
|
||||||
<option value="vehicle">Vehicle</option>
|
<input type="text" className="form-control" placeholder="Enter location" value={newEventLocation || ''} onChange={e => setNewEventLocation(e.target.value)}/>
|
||||||
<option value="customer">Customer</option>
|
</div>
|
||||||
<option value="employee">Employee</option>
|
<div className="mb-3">
|
||||||
<option value="notApplicable">Not Applicable</option>
|
<div className="field-label">Repeat</div>
|
||||||
</select>
|
<select className="form-control" value={newEventRecurring} onChange={(e) => setNewEventRecurring(e.target.value)}>
|
||||||
</div>
|
<option value="">No (One-time)</option>
|
||||||
{newEventTargetType && newEventTargetType !== '' && newEventTargetType !== 'notApplicable' && <div className="me-4">
|
<option value="FREQ=DAILY">Daily</option>
|
||||||
<div className="field-label">Please select a candidate</div>
|
<option value="FREQ=WEEKLY">Weekly</option>
|
||||||
<select value={newEventTarget?.value || ''} onChange={(e) => {
|
<option value="FREQ=MONTHLY">Monthly</option>
|
||||||
const selectedOption = e.target.options[e.target.selectedIndex];
|
<option value="FREQ=YEARLY">Yearly</option>
|
||||||
setNewEventTarget({
|
</select>
|
||||||
value: selectedOption.value,
|
</div>
|
||||||
label: selectedOption.text
|
</>
|
||||||
})
|
)}
|
||||||
}}>
|
|
||||||
<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>
|
{/* Attendance Note Fields */}
|
||||||
</div>}
|
{currentTab === 'incidentsCalendar' && (
|
||||||
</div>
|
<>
|
||||||
|
<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>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
|
||||||
<div className="app-main-content-fields-section">
|
{/* Meal Item Fields */}
|
||||||
<div className="me-4">
|
{currentTab === 'mealPlanCalendar' && (
|
||||||
<div className="field-label">Who will be responsible for this event</div>
|
<>
|
||||||
<select value={newEventSourceType} onChange={(e) => setNewEventSourceType(e.target.value)}>
|
<div className="mb-3">
|
||||||
<option value=""></option>
|
<div className="field-label">Dish Name<span className="required">*</span></div>
|
||||||
<option value="employee">Employee</option>
|
<input type="text" className="form-control" placeholder="Enter dish name" value={newEventTitle || ''} onChange={e => setNewEventTitle(e.target.value)}/>
|
||||||
<option value="resource">Resource</option>
|
</div>
|
||||||
<option value="notApplicable">Not Applicable</option>
|
<div className="mb-3">
|
||||||
</select>
|
<div className="field-label">Meal Type<span className="required">*</span></div>
|
||||||
</div>
|
<select className="form-control" value={newMealType} onChange={(e) => setNewMealType(e.target.value)}>
|
||||||
{newEventSourceType && newEventSourceType !== '' && newEventSourceType !== 'notApplicable' && <div className="me-4">
|
<option value="">Select Meal Type</option>
|
||||||
<div className="field-label">Please select a member</div>
|
<option value="breakfast">Breakfast</option>
|
||||||
<select value={newEventSource?.value || ''} onChange={(e) => {
|
<option value="lunch">Lunch</option>
|
||||||
const selectedOption = e.target.options[e.target.selectedIndex];
|
<option value="snack">Snack</option>
|
||||||
setNewEventSource({
|
</select>
|
||||||
value: selectedOption.value,
|
</div>
|
||||||
label: selectedOption.text
|
<div className="mb-3">
|
||||||
})
|
<div className="field-label">Date</div>
|
||||||
}}>
|
<DatePicker
|
||||||
<option value=""></option>
|
className="form-control"
|
||||||
{
|
selected={newEventStartDateTime}
|
||||||
newEventSourceType === 'resource' && resources.map((item) => <option value={item?.id}>{item?.name}</option>)
|
onChange={setNewEventStartDateTime}
|
||||||
}
|
dateFormat="MM/dd/yyyy"
|
||||||
{
|
/>
|
||||||
newEventSourceType === 'employee' && employees.map((item) => <option value={item?.id}>{item?.name}</option>)
|
</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>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
|
||||||
</select>
|
{/* Important Dates Fields */}
|
||||||
</div>}
|
{currentTab === 'reminderDatesCalendar' && (
|
||||||
</div> </>}
|
<>
|
||||||
<div className="app-main-content-fields-section">
|
<div className="mb-3">
|
||||||
{currentTab === 'reminderDatesCalendar' && <div className="me-4">
|
<div className="field-label">Title<span className="required">*</span></div>
|
||||||
<div className="field-label">Reminder Type
|
<select className="form-control" value={newReminderTitleCategory} onChange={(e) => {
|
||||||
|
setNewReminderTitleCategory(e.target.value);
|
||||||
|
setNewReminderAssociatedEntity(null); // Reset when category changes
|
||||||
|
}}>
|
||||||
|
<option value="">Select Title</option>
|
||||||
|
<optgroup label="Member">
|
||||||
|
<option value="birthday">Birthday</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>
|
</div>
|
||||||
<select value={newEventReminderType} onChange={(e) => setNewEventReminderType(e.target.value)}>
|
{newReminderTitleCategory && (
|
||||||
<option value=""></option>
|
<div className="mb-3">
|
||||||
<option value="birthday">Birthday</option>
|
<div className="field-label">
|
||||||
<option value="membership">Membership Renew</option>
|
{['birthday', 'adcaps_completion', 'center_qualification_expiration'].includes(newReminderTitleCategory)
|
||||||
<option value="payment">Customer Payment Due Date</option>
|
? 'Associated Person'
|
||||||
<option value="insurance_renew">Customer Insurance Renew</option>
|
: 'Associated Vehicle'}<span className="required">*</span>
|
||||||
<option value="insurance_expire">Vehicle Insurance Renew</option>
|
</div>
|
||||||
<option value="title_expire">Vehicle Title Registration</option>
|
<Select
|
||||||
<option value="emission_test">Vehicle Emission Test</option>
|
value={newReminderAssociatedEntity}
|
||||||
<option value="oil_change">Vehicle Oil Change</option>
|
onChange={setNewReminderAssociatedEntity}
|
||||||
</select>
|
options={
|
||||||
</div>}
|
['birthday', 'adcaps_completion', 'center_qualification_expiration'].includes(newReminderTitleCategory)
|
||||||
{currentTab === 'activitiesCalendar' && <div className="me-4">
|
? customers.filter(item => item?.status === 'active' && item?.type !== 'discharged').map(c => ({ value: c.id, label: c.name }))
|
||||||
<div className="field-label">Event Location
|
: 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>
|
||||||
<input type="text" placeholder="Type in the location this event gonna happen if applicable" value={newEventLocation || ''} onChange={e => setNewEventLocation(e.target.value)}/>
|
<div className="mb-3">
|
||||||
</div>}
|
<div className="field-label">Category</div>
|
||||||
{currentTab === 'reminderDatesCalendar' && <div className="me-4">
|
<div className="field-value" style={{ padding: '8px 0', color: '#666' }}>
|
||||||
<div className="field-label">If this is reminder which will happen later, please select the accurate Date it gonna happen:
|
{newReminderTitleCategory
|
||||||
|
? (['birthday', 'adcaps_completion', 'center_qualification_expiration'].includes(newReminderTitleCategory) ? 'Member Related' : 'Vehicle Related')
|
||||||
|
: '-'}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<DatePicker selected={newEventFutureDate}
|
<div className="mb-3">
|
||||||
onChange={setNewEventFutureDate} dateFormat="MM/dd/yyyy"/>
|
<div className="field-label">Repeat Cycle</div>
|
||||||
</div>}
|
<select className="form-control" value={newEventRecurring} onChange={(e) => setNewEventRecurring(e.target.value)}>
|
||||||
|
<option value="">No (One-time)</option>
|
||||||
</div>
|
<option value="FREQ=DAILY">Daily</option>
|
||||||
<div className="app-main-content-fields-section">
|
<option value="FREQ=WEEKLY">Weekly</option>
|
||||||
<div className="me-4">
|
<option value="FREQ=MONTHLY">Monthly</option>
|
||||||
<div className="field-label">Color
|
<option value="FREQ=YEARLY">Yearly</option>
|
||||||
|
</select>
|
||||||
</div>
|
</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>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</Modal.Body>
|
</Modal.Body>
|
||||||
<Modal.Footer>
|
<Modal.Footer>
|
||||||
<Button variant="secondary" onClick={handleClose}>
|
<Button variant="secondary" size="sm" onClick={handleClose}>
|
||||||
Close
|
Cancel
|
||||||
</Button>
|
</Button>
|
||||||
<Button variant="primary" onClick={handleSave}>
|
<Button variant="primary" size="sm" onClick={handleSave} disabled={
|
||||||
Save Calendar Item
|
(currentTab === 'medicalCalendar' && (!newEventCustomer || !newEventResource || !newEventStartDateTime)) ||
|
||||||
|
(currentTab === 'activitiesCalendar' && (!newEventTitle || !newActivityCategory)) ||
|
||||||
|
(currentTab === 'incidentsCalendar' && (!newAttendanceCustomer || !newEventStartDateTime)) ||
|
||||||
|
(currentTab === 'mealPlanCalendar' && (!newEventTitle || !newMealType)) ||
|
||||||
|
(currentTab === 'reminderDatesCalendar' && (!newReminderTitleCategory || !newReminderAssociatedEntity))
|
||||||
|
}>
|
||||||
|
Save
|
||||||
</Button>
|
</Button>
|
||||||
</Modal.Footer>
|
</Modal.Footer>
|
||||||
</Modal>
|
</Modal>
|
||||||
|
|||||||
@ -49,7 +49,7 @@ const CenterPhoneList = () => {
|
|||||||
<>
|
<>
|
||||||
<div className="list row mb-4">
|
<div className="list row mb-4">
|
||||||
<Breadcrumb>
|
<Breadcrumb>
|
||||||
<Breadcrumb.Item>General</Breadcrumb.Item>
|
<Breadcrumb.Item href="/">General</Breadcrumb.Item>
|
||||||
<Breadcrumb.Item active>
|
<Breadcrumb.Item active>
|
||||||
Center Phone
|
Center Phone
|
||||||
</Breadcrumb.Item>
|
</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 = () => {
|
const savePhone = () => {
|
||||||
|
if (!validatePhone()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
const data = {
|
const data = {
|
||||||
phone_title: phoneTitle,
|
phone_title: phoneTitle,
|
||||||
phone_number: phoneNumber
|
phone_number: phoneNumber
|
||||||
@ -43,10 +65,13 @@ const CreateCenterPhone = () => {
|
|||||||
<>
|
<>
|
||||||
<div className="list row mb-4">
|
<div className="list row mb-4">
|
||||||
<Breadcrumb>
|
<Breadcrumb>
|
||||||
<Breadcrumb.Item>General</Breadcrumb.Item>
|
<Breadcrumb.Item href="/">General</Breadcrumb.Item>
|
||||||
<Breadcrumb.Item active>
|
<Breadcrumb.Item href="/center-phones/list">
|
||||||
Center Phone
|
Center Phone
|
||||||
</Breadcrumb.Item>
|
</Breadcrumb.Item>
|
||||||
|
<Breadcrumb.Item active>
|
||||||
|
Create Center Phone
|
||||||
|
</Breadcrumb.Item>
|
||||||
</Breadcrumb>
|
</Breadcrumb>
|
||||||
<div className="col-md-12 text-primary">
|
<div className="col-md-12 text-primary">
|
||||||
<h4>
|
<h4>
|
||||||
|
|||||||
@ -35,7 +35,29 @@ const UpdateCenterPhone = () => {
|
|||||||
navigate('/center-phones/list')
|
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 = () => {
|
const savePhone = () => {
|
||||||
|
if (!validatePhone()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
const data = {
|
const data = {
|
||||||
phone_title: phoneTitle,
|
phone_title: phoneTitle,
|
||||||
phone_number: phoneNumber,
|
phone_number: phoneNumber,
|
||||||
@ -48,10 +70,13 @@ const UpdateCenterPhone = () => {
|
|||||||
<>
|
<>
|
||||||
<div className="list row mb-4">
|
<div className="list row mb-4">
|
||||||
<Breadcrumb>
|
<Breadcrumb>
|
||||||
<Breadcrumb.Item>General</Breadcrumb.Item>
|
<Breadcrumb.Item href="/">General</Breadcrumb.Item>
|
||||||
<Breadcrumb.Item active>
|
<Breadcrumb.Item href="/center-phones/list">
|
||||||
Center Phone
|
Center Phone
|
||||||
</Breadcrumb.Item>
|
</Breadcrumb.Item>
|
||||||
|
<Breadcrumb.Item active>
|
||||||
|
Update Center Phone
|
||||||
|
</Breadcrumb.Item>
|
||||||
</Breadcrumb>
|
</Breadcrumb>
|
||||||
<div className="col-md-12 text-primary">
|
<div className="col-md-12 text-primary">
|
||||||
<h4>
|
<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 { customerSlice } from "./../../store";
|
||||||
import { AuthService, CustomerService, EventsService, LabelService } from "../../services";
|
import { AuthService, CustomerService, EventsService, LabelService } from "../../services";
|
||||||
import { CUSTOMER_TYPE, ManageTable, Export } from "../../shared";
|
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";
|
import { Columns, Download, Filter, PencilSquare, PersonSquare, Plus } from "react-bootstrap-icons";
|
||||||
|
|
||||||
|
|
||||||
@ -26,6 +26,12 @@ const CustomersList = () => {
|
|||||||
const [serviceRequirementFilter, setServiceRequirementFilter] = useState('');
|
const [serviceRequirementFilter, setServiceRequirementFilter] = useState('');
|
||||||
const [tagsFilter, setTagsFilter] = useState([]);
|
const [tagsFilter, setTagsFilter] = useState([]);
|
||||||
const [availableLabels, setAvailableLabels] = 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([
|
const [columns, setColumns] = useState([
|
||||||
{
|
{
|
||||||
key: 'name',
|
key: 'name',
|
||||||
@ -126,22 +132,42 @@ const CustomersList = () => {
|
|||||||
navigate(`/login`);
|
navigate(`/login`);
|
||||||
}
|
}
|
||||||
CustomerService.getAllCustomers().then((data) => {
|
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.phone = item?.phone || item?.home_phone || item?.mobile_phone;
|
||||||
item.address = item?.address1 || item?.address2 || item?.address3 || item?.address4|| item?.address5;
|
item.address = item?.address1 || item?.address2 || item?.address3 || item?.address4|| item?.address5;
|
||||||
|
|
||||||
return item;
|
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) => {
|
LabelService.getAll().then((data) => {
|
||||||
setAvailableLabels(data.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(() => {
|
useEffect(() => {
|
||||||
let filtered = customers;
|
let filtered = customers;
|
||||||
|
|
||||||
@ -181,8 +207,47 @@ const CustomersList = () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
setFilteredCustomers(filtered);
|
setFilteredCustomers(filtered);
|
||||||
|
setCurrentPage(1); // Reset to first page when filters change
|
||||||
}, [keyword, customers, showInactive, healthConditionFilter, paymentStatusFilter, serviceRequirementFilter, tagsFilter])
|
}, [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(() => {
|
useEffect(() => {
|
||||||
const newCustomers = [...customers];
|
const newCustomers = [...customers];
|
||||||
const sortedCustomers = sorting.key === '' ? newCustomers : newCustomers.sort((a, b) => {
|
const sortedCustomers = sorting.key === '' ? newCustomers : newCustomers.sort((a, b) => {
|
||||||
@ -377,13 +442,31 @@ const CustomersList = () => {
|
|||||||
|
|
||||||
const table = <div className="list row mb-4">
|
const table = <div className="list row mb-4">
|
||||||
<div className="col-md-12">
|
<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>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
<th className="th-checkbox"><input type="checkbox" checked={checkSelectAll()} onClick={() => toggleSelectedAllItems()}></input></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">No.</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>
|
{column.label} <span className="float-right" onClick={() => sortTableWithField(column.key)}><img src={`/images/${getSortingImg(column.key)}.png`}></img></span>
|
||||||
</th>)
|
</th>)
|
||||||
}
|
}
|
||||||
@ -394,10 +477,71 @@ const CustomersList = () => {
|
|||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
{
|
{
|
||||||
filteredCustomers.map((customer, index) => <tr key={customer.id}>
|
paginatedCustomers.map((customer, index) => {
|
||||||
<td className="td-checkbox"><input type="checkbox" checked={selectedItems.includes(customer.id)} onClick={()=>toggleItem(customer?.id)}/></td>
|
const avatarData = customerAvatars[customer.id];
|
||||||
<td className="td-index">{index + 1}</td>
|
const isLoadingAvatar = loadingAvatarId === customer.id;
|
||||||
{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>}
|
|
||||||
|
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 === 'chinese_name')?.show && <td>{customer?.name_cn}</td>}
|
||||||
{columns.find(col => col.key === 'email')?.show && <td>{customer?.email}</td>}
|
{columns.find(col => col.key === 'email')?.show && <td>{customer?.email}</td>}
|
||||||
{columns.find(col => col.key === 'type')?.show && <td>{customer?.type}</td>}
|
{columns.find(col => col.key === 'type')?.show && <td>{customer?.type}</td>}
|
||||||
@ -432,10 +576,74 @@ const CustomersList = () => {
|
|||||||
|
|
||||||
}
|
}
|
||||||
</td>
|
</td>
|
||||||
</tr>)
|
</tr>
|
||||||
|
})
|
||||||
}
|
}
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</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>
|
||||||
</div>;
|
</div>;
|
||||||
|
|
||||||
@ -521,7 +729,7 @@ const CustomersList = () => {
|
|||||||
</div>}
|
</div>}
|
||||||
<div className="list row mb-4">
|
<div className="list row mb-4">
|
||||||
<Breadcrumb>
|
<Breadcrumb>
|
||||||
<Breadcrumb.Item>General</Breadcrumb.Item>
|
<Breadcrumb.Item href="/">General</Breadcrumb.Item>
|
||||||
<Breadcrumb.Item active>
|
<Breadcrumb.Item active>
|
||||||
Customer Information
|
Customer Information
|
||||||
</Breadcrumb.Item>
|
</Breadcrumb.Item>
|
||||||
@ -534,13 +742,13 @@ const CustomersList = () => {
|
|||||||
</h4>
|
</h4>
|
||||||
</div>
|
</div>
|
||||||
</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">
|
<div className="app-main-content-list-func-container">
|
||||||
<Tabs defaultActiveKey="activeCustomers" id="customers-tab" onSelect={(k) => showArchive(k)}>
|
<Tabs defaultActiveKey="activeCustomers" id="customers-tab" onSelect={(k) => showArchive(k)}>
|
||||||
<Tab eventKey="activeCustomers" title="Active Customers">
|
<Tab eventKey="activeCustomers" title="Active Customers">
|
||||||
{table}
|
{table}
|
||||||
</Tab>
|
</Tab>
|
||||||
<Tab eventKey="archivedCustomers" title="Archived Customers">
|
<Tab eventKey="archivedCustomers" title="Discharge Customers">
|
||||||
{table}
|
{table}
|
||||||
</Tab>
|
</Tab>
|
||||||
</Tabs>
|
</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">
|
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">
|
<table className="personnel-info-table">
|
||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
@ -397,7 +397,7 @@ const DashboardCustomersList = () => {
|
|||||||
<Tab eventKey="activeCustomers" title="Active Customers">
|
<Tab eventKey="activeCustomers" title="Active Customers">
|
||||||
{table}
|
{table}
|
||||||
</Tab>
|
</Tab>
|
||||||
<Tab eventKey="archivedCustomers" title="Archived Customers">
|
<Tab eventKey="archivedCustomers" title="Discharge Customers">
|
||||||
{table}
|
{table}
|
||||||
</Tab>
|
</Tab>
|
||||||
</Tabs>
|
</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 = () => {
|
const saveEventRequest = () => {
|
||||||
|
if (!validateEventRequest()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
const newEventRequest = {
|
const newEventRequest = {
|
||||||
customer_id: currentCustomer?.id,
|
customer_id: currentCustomer?.id,
|
||||||
customer_display: `${currentCustomer?.name} - ${currentCustomer?.name_cn} - ${currentCustomer?.birth_date}`,
|
customer_display: `${currentCustomer?.name} - ${currentCustomer?.name_cn} - ${currentCustomer?.birth_date}`,
|
||||||
@ -112,8 +134,8 @@ const CreateEventRequest = () => {
|
|||||||
<>
|
<>
|
||||||
<div className="list row mb-4">
|
<div className="list row mb-4">
|
||||||
<Breadcrumb>
|
<Breadcrumb>
|
||||||
<Breadcrumb.Item>Medical</Breadcrumb.Item>
|
<Breadcrumb.Item href="/medical/index">Medical</Breadcrumb.Item>
|
||||||
<Breadcrumb.Item active>
|
<Breadcrumb.Item href="/medical/event-request/list">
|
||||||
Appointment Request Information
|
Appointment Request Information
|
||||||
</Breadcrumb.Item>
|
</Breadcrumb.Item>
|
||||||
<Breadcrumb.Item active>
|
<Breadcrumb.Item active>
|
||||||
@ -135,34 +157,42 @@ const CreateEventRequest = () => {
|
|||||||
<span className="required">*</span>
|
<span className="required">*</span>
|
||||||
</div>
|
</div>
|
||||||
<Select styles={{
|
<Select styles={{
|
||||||
control: (baseStyles, state) => ({
|
control: (baseStyles) => ({
|
||||||
...baseStyles,
|
...baseStyles,
|
||||||
width: '350px',
|
width: '350px',
|
||||||
height: '45px',
|
borderRadius: '8px'
|
||||||
'padding-top': 0,
|
|
||||||
'padding-bottom': 0,
|
|
||||||
'margin-top': 0,
|
|
||||||
'margin-bottom': 0
|
|
||||||
}),
|
}),
|
||||||
indicatorSeparator: (baseStyles, state) => ({
|
indicatorSeparator: () => ({
|
||||||
...baseStyles,
|
display: 'none'
|
||||||
width: 0
|
|
||||||
}),
|
}),
|
||||||
indicatorsContainer: (baseStyles) => ({
|
dropdownIndicator: (baseStyles) => ({
|
||||||
...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) => ({
|
placeholder: (baseStyles) => ({
|
||||||
...baseStyles,
|
...baseStyles,
|
||||||
'margin-top': '-10px',
|
fontSize: '13px'
|
||||||
'font-size': '13px'
|
|
||||||
}),
|
}),
|
||||||
singleValue: (baseStyles, state) => ({
|
singleValue: (baseStyles) => ({
|
||||||
...baseStyles,
|
...baseStyles,
|
||||||
'margin-top': '-10px',
|
margin: '0px',
|
||||||
'font-size': '13px'
|
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 || '',
|
value: customer?.id || '',
|
||||||
label: `${customer?.name} - ${customer?.name_cn} - ${customer?.birth_date}` || ''
|
label: `${customer?.name} - ${customer?.name_cn} - ${customer?.birth_date}` || ''
|
||||||
}))]}></Select>
|
}))]}></Select>
|
||||||
|
|||||||
@ -305,7 +305,7 @@ const EventRequestList = () => {
|
|||||||
<>
|
<>
|
||||||
<div className="list row mb-4">
|
<div className="list row mb-4">
|
||||||
<Breadcrumb>
|
<Breadcrumb>
|
||||||
<Breadcrumb.Item>Medical</Breadcrumb.Item>
|
<Breadcrumb.Item href="/medical/index">Medical</Breadcrumb.Item>
|
||||||
<Breadcrumb.Item active>
|
<Breadcrumb.Item active>
|
||||||
Appointment Requests Information
|
Appointment Requests Information
|
||||||
</Breadcrumb.Item>
|
</Breadcrumb.Item>
|
||||||
@ -318,7 +318,7 @@ const EventRequestList = () => {
|
|||||||
</h4>
|
</h4>
|
||||||
</div>
|
</div>
|
||||||
</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">
|
<div className="app-main-content-list-func-container">
|
||||||
<Tabs defaultActiveKey="activeRequests" id="requests-tab" onSelect={(k) => showDoneItems(k)}>
|
<Tabs defaultActiveKey="activeRequests" id="requests-tab" onSelect={(k) => showDoneItems(k)}>
|
||||||
<Tab eventKey="activeRequests" title="Active Requests">
|
<Tab eventKey="activeRequests" title="Active Requests">
|
||||||
|
|||||||
@ -51,7 +51,29 @@ const CreateEvent = () => {
|
|||||||
navigate(`/medical/events/edit/${id}?from=create`)
|
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 = () => {
|
const saveEvent = () => {
|
||||||
|
if (!validateEvent()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
console.log('customer', currentCustomer);
|
console.log('customer', currentCustomer);
|
||||||
// console.log('resource', currentResource);
|
// console.log('resource', currentResource);
|
||||||
|
|
||||||
@ -137,8 +159,8 @@ const CreateEvent = () => {
|
|||||||
<>
|
<>
|
||||||
<div className="list row mb-4">
|
<div className="list row mb-4">
|
||||||
<Breadcrumb>
|
<Breadcrumb>
|
||||||
<Breadcrumb.Item>Medical</Breadcrumb.Item>
|
<Breadcrumb.Item href="/medical/index">Medical</Breadcrumb.Item>
|
||||||
<Breadcrumb.Item active>
|
<Breadcrumb.Item href="/medical/events/list">
|
||||||
Medical Appointment Information
|
Medical Appointment Information
|
||||||
</Breadcrumb.Item>
|
</Breadcrumb.Item>
|
||||||
<Breadcrumb.Item active>
|
<Breadcrumb.Item active>
|
||||||
@ -150,54 +172,62 @@ const CreateEvent = () => {
|
|||||||
</div>
|
</div>
|
||||||
</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">
|
<div className="app-main-content-list-func-container">
|
||||||
<Tabs defaultActiveKey="eventClientInfo" id="event-tab">
|
<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="multi-columns-container">
|
||||||
<div className="column-container">
|
<div className="column-container">
|
||||||
<div className="column-card">
|
<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="app-main-content-fields-section">
|
||||||
<div className="me-4">
|
<div className="me-4">
|
||||||
<div className="field-label">Client
|
<div className="field-label">Customer
|
||||||
<span className="required">*</span>
|
<span className="required">*</span>
|
||||||
</div>
|
</div>
|
||||||
<Select styles={{
|
<Select styles={{
|
||||||
control: (baseStyles, state) => ({
|
control: (baseStyles) => ({
|
||||||
...baseStyles,
|
...baseStyles,
|
||||||
width: '350px',
|
width: '350px',
|
||||||
height: '45px',
|
borderRadius: '8px'
|
||||||
'padding-top': 0,
|
|
||||||
'padding-bottom': 0,
|
|
||||||
'margin-top': 0,
|
|
||||||
'margin-bottom': 0
|
|
||||||
}),
|
}),
|
||||||
indicatorSeparator: (baseStyles, state) => ({
|
indicatorSeparator: () => ({
|
||||||
...baseStyles,
|
display: 'none'
|
||||||
width: 0
|
|
||||||
}),
|
}),
|
||||||
indicatorsContainer: (baseStyles) => ({
|
dropdownIndicator: (baseStyles) => ({
|
||||||
...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) => ({
|
placeholder: (baseStyles) => ({
|
||||||
...baseStyles,
|
...baseStyles,
|
||||||
'margin-top': '-10px',
|
fontSize: '13px'
|
||||||
'font-size': '13px'
|
|
||||||
}),
|
}),
|
||||||
singleValue: (baseStyles, state) => ({
|
singleValue: (baseStyles) => ({
|
||||||
...baseStyles,
|
...baseStyles,
|
||||||
'margin-top': '-10px',
|
margin: '0px',
|
||||||
'font-size': '13px'
|
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 || '',
|
value: customer?.id || '',
|
||||||
label: customer?.name || ''
|
label: customer?.name || ''
|
||||||
}))]}></Select>
|
}))]}></Select>
|
||||||
</div>
|
</div>
|
||||||
<div className="me-4">
|
<div className="me-4">
|
||||||
<div className="field-label">Start Time
|
<div className="field-label">Appointment Time
|
||||||
<span className="required">*</span>
|
<span className="required">*</span>
|
||||||
</div>
|
</div>
|
||||||
<DatePicker
|
<DatePicker
|
||||||
@ -221,10 +251,10 @@ const CreateEvent = () => {
|
|||||||
<div className="column-card">
|
<div className="column-card">
|
||||||
{
|
{
|
||||||
currentCustomer && <>
|
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="app-main-content-fields-section short">
|
||||||
<div className="field-body">
|
<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 className="field-value">{currentCustomer?.name || ''}</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="field-body">
|
<div className="field-body">
|
||||||
|
|||||||
@ -37,6 +37,42 @@ const EventsCalendar = () => {
|
|||||||
const eventsServicePlugin = createEventsServicePlugin();
|
const eventsServicePlugin = createEventsServicePlugin();
|
||||||
const eventModalService = createEventModalPlugin();
|
const eventModalService = createEventModalPlugin();
|
||||||
const [groupedEvents, setGroupedEvents] = useState(new Map());
|
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({
|
const calendar = useCalendarApp({
|
||||||
views: [createViewMonthGrid(), createViewDay(), createViewMonthAgenda(), createViewWeek()],
|
views: [createViewMonthGrid(), createViewDay(), createViewMonthAgenda(), createViewWeek()],
|
||||||
@ -52,21 +88,47 @@ const EventsCalendar = () => {
|
|||||||
events: events,
|
events: events,
|
||||||
plugins: [eventModalService, eventsServicePlugin],
|
plugins: [eventModalService, eventsServicePlugin],
|
||||||
callbacks: {
|
callbacks: {
|
||||||
// onRangeUpdate(range) {
|
onRangeUpdate(range) {
|
||||||
// console.log('new range', range.start);
|
console.log('new calendar range start date', range.start);
|
||||||
// console.log('new range end', range.end);
|
console.log('new calendar range end date', range.end);
|
||||||
// },
|
setCurrentRangeStart(range.start);
|
||||||
onSelectedDateUpdate(date) {
|
setCurrentRangeEnd(range.end);
|
||||||
setFromDate(new Date(new Date(date).getFullYear(), new Date(date).getMonth(), 1));
|
// Update fromDate/toDate for API fetching based on the range
|
||||||
setToDate(new Date(new Date(date).getFullYear(), new Date(date).getMonth() + 1, 0));
|
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 getGroupedEvents = () => {
|
||||||
const eventsDateMap = new Map();
|
const eventsDateMap = new Map();
|
||||||
console.log('events', events);
|
const filteredEvents = getFilteredEvents();
|
||||||
for (const eventItem of events) {
|
for (const eventItem of filteredEvents) {
|
||||||
const dateString = moment(eventItem.start_time).format('MMM Do, YYYY');
|
const dateString = moment(eventItem.start_time).format('MMM Do, YYYY');
|
||||||
if (eventsDateMap.has(dateString)) {
|
if (eventsDateMap.has(dateString)) {
|
||||||
eventsDateMap.set(dateString, [...eventsDateMap.get(dateString), eventItem]);
|
eventsDateMap.set(dateString, [...eventsDateMap.get(dateString), eventItem]);
|
||||||
@ -76,7 +138,6 @@ const EventsCalendar = () => {
|
|||||||
eventsDateMap.set(dateString, value);
|
eventsDateMap.set(dateString, value);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
console.log('eventsMap', eventsDateMap);
|
|
||||||
return eventsDateMap;
|
return eventsDateMap;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -87,7 +148,7 @@ const EventsCalendar = () => {
|
|||||||
AuthService.logout();
|
AuthService.logout();
|
||||||
navigate(`/login`);
|
navigate(`/login`);
|
||||||
}
|
}
|
||||||
CustomerService.getAllCustomers().then((data) => {
|
CustomerService.getAllActiveCustomers().then((data) => {
|
||||||
setCustomers(data.data);
|
setCustomers(data.data);
|
||||||
});
|
});
|
||||||
ResourceService.getAll().then((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.endTime = item?.start_time? `${moment(new Date(item?.end_time)).format('hh:mm A')}` : '' ;
|
||||||
item.fasting = item?.data?.fasting || '';
|
item.fasting = item?.data?.fasting || '';
|
||||||
item.transportation = item?.link_event_name || '';
|
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.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.end = item.stop_time? `${moment(new Date(item?.stop_time)).format('YYYY-MM-DD HH:mm')}` : '';
|
||||||
const transportationInfo = EventsService.getTransportationInfo(data.data, item, timeData);
|
const transportationInfo = EventsService.getTransportationInfo(data.data, item, timeData);
|
||||||
@ -140,16 +202,15 @@ const EventsCalendar = () => {
|
|||||||
})}
|
})}
|
||||||
}, [fromDate, toDate, customers, resources, timeData, showDeletedItems]);
|
}, [fromDate, toDate, customers, resources, timeData, showDeletedItems]);
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (events && calendar) {
|
if (events && calendar) {
|
||||||
// events?.forEach((item) => {
|
console.log("EventsCalendar useEffect - events:", events.length, "range:", currentRangeStart, "to", currentRangeEnd);
|
||||||
// if (!calendar.eventsService.getAll()?.find(evItem => evItem.id === item.id)) {
|
|
||||||
// calendar.eventsService.add(item)
|
|
||||||
// } } );
|
|
||||||
calendar?.eventsService?.set(events);
|
calendar?.eventsService?.set(events);
|
||||||
setGroupedEvents(getGroupedEvents());
|
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.endTime = item?.start_time? `${moment(new Date(item?.end_time)).format('hh:mm A')}` : '' ;
|
||||||
item.fasting = item?.data?.fasting || '';
|
item.fasting = item?.data?.fasting || '';
|
||||||
item.transportation = item?.link_event_name || '';
|
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.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.end = item.stop_time? `${moment(new Date(item?.stop_time)).format('YYYY-MM-DD HH:mm')}` : '';
|
||||||
item._options = { additionalClasses: [`event-${item?.color || 'primary'}`]};
|
item._options = { additionalClasses: [`event-${item?.color || 'primary'}`]};
|
||||||
@ -280,7 +341,7 @@ const EventsCalendar = () => {
|
|||||||
<>
|
<>
|
||||||
<div className="list row mb-4">
|
<div className="list row mb-4">
|
||||||
<Breadcrumb>
|
<Breadcrumb>
|
||||||
<Breadcrumb.Item>Medical</Breadcrumb.Item>
|
<Breadcrumb.Item href="/medical/index">Medical</Breadcrumb.Item>
|
||||||
<Breadcrumb.Item active>
|
<Breadcrumb.Item active>
|
||||||
Medical Event Calendar
|
Medical Event Calendar
|
||||||
</Breadcrumb.Item>
|
</Breadcrumb.Item>
|
||||||
@ -301,22 +362,26 @@ const EventsCalendar = () => {
|
|||||||
{calendar && <ScheduleXCalendar customComponents={customComponents} calendarApp={calendar} />}
|
{calendar && <ScheduleXCalendar customComponents={customComponents} calendarApp={calendar} />}
|
||||||
</div>
|
</div>
|
||||||
<div className="column-container">
|
<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>
|
<h6 className="text-primary me-4">List</h6>
|
||||||
{
|
{
|
||||||
Array.from(groupedEvents?.keys())?.map((key) => {
|
Array.from(groupedEvents?.keys())?.map((key) => {
|
||||||
return <>
|
return <div key={key}>
|
||||||
<h6 className="text-primary me-2">{key}</h6>
|
<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="event-item-flex">
|
||||||
<div className="sx__month-agenda-event__title">{eventItem.customer}</div>
|
<div className="sx__month-agenda-event__title">{`${moment(eventItem?.start_time).format('hh:mm A')}: ${formatFullName(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>
|
</div>
|
||||||
<div className="sx__event-modal__time with-padding">{`provider: ${eventItem?.doctor}`}</div>
|
<div className="sx__event-modal__time with-padding">{`provider: ${eventItem?.doctor}`}</div>
|
||||||
</div>)
|
</div>)
|
||||||
}
|
}
|
||||||
</>
|
</div>
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@ -13,7 +13,9 @@ const EventsList = () => {
|
|||||||
const [customers, setCustomers] = useState([]);
|
const [customers, setCustomers] = useState([]);
|
||||||
const [resources, setResources] = useState([]);
|
const [resources, setResources] = useState([]);
|
||||||
const [selectedDate, setSelectedDate] = useState(new Date());
|
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 [selectedItems, setSelectedItems] = useState([]);
|
||||||
const [showTransportationModal, setShowTransportationModal] = useState(false);
|
const [showTransportationModal, setShowTransportationModal] = useState(false);
|
||||||
const [driver, setDriver] = useState(null);
|
const [driver, setDriver] = useState(null);
|
||||||
@ -45,7 +47,7 @@ const EventsList = () => {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
key: 'doctor',
|
key: 'doctor',
|
||||||
label: 'Doctor',
|
label: 'Provider',
|
||||||
show: true
|
show: true
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -60,7 +62,7 @@ const EventsList = () => {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
key: 'translation',
|
key: 'translation',
|
||||||
label: 'Translation',
|
label: 'Language Support',
|
||||||
show: true
|
show: true
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -70,7 +72,7 @@ const EventsList = () => {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
key: 'needId',
|
key: 'needId',
|
||||||
label: 'Need ID',
|
label: 'ID Needed',
|
||||||
show: true
|
show: true
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -80,17 +82,17 @@ const EventsList = () => {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
key: 'startTime',
|
key: 'startTime',
|
||||||
label: 'Start Time',
|
label: 'Appointment Time',
|
||||||
show: true
|
show: true
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
key: 'fasting',
|
key: 'fasting',
|
||||||
label: 'Fasting',
|
label: 'Fasting Required',
|
||||||
show: true
|
show: true
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
key: 'transportation',
|
key: 'transportation',
|
||||||
label: 'Driver',
|
label: 'Transportation Support',
|
||||||
show: true
|
show: true
|
||||||
}
|
}
|
||||||
]);
|
]);
|
||||||
@ -100,6 +102,39 @@ const EventsList = () => {
|
|||||||
return currentCustomer?.disability || event?.data?.disability?.toLowerCase() === 'yes' || false;
|
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(() => {
|
useEffect(() => {
|
||||||
if (!AuthService.canAccessLegacySystem()) {
|
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.')
|
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(() => {
|
useEffect(() => {
|
||||||
if (customers?.length > 0 && resources?.length>0) {
|
if (customers?.length > 0 && resources?.length>0) {
|
||||||
EventsService.getAllEvents({ date: EventsService.formatDate(selectedDate) }).then((data) => {
|
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.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.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 || '');
|
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.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;
|
item.transMethod = item?.data?.trans_method;
|
||||||
return item;
|
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'))
|
setTransportationOptionsList(data.data.filter((item) => item.type === 'transportation' && item.status === 'active'))
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@ -147,15 +186,40 @@ const EventsList = () => {
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
useEffect(() => {
|
// Apply multi-column sorting
|
||||||
const newEvents = [...events];
|
// After all custom sorting rules, always sort by language (empty first)
|
||||||
const sortedEvents = sorting.key === '' ? newEvents : newEvents.sort((a, b) => {
|
const applyMultiColumnSort = (eventsArray, rules) => {
|
||||||
return a[sorting.key].localeCompare(b[sorting.key]);
|
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()
|
|
||||||
)
|
useEffect(() => {
|
||||||
}, [sorting]);
|
if (events.length === 0) return;
|
||||||
|
|
||||||
|
const sortedEvents = applyMultiColumnSort(events, sortingRules);
|
||||||
|
setEvents(sortedEvents);
|
||||||
|
}, [sortingRules]);
|
||||||
|
|
||||||
|
|
||||||
const redirectToAdmin = () => {
|
const redirectToAdmin = () => {
|
||||||
@ -199,24 +263,46 @@ const EventsList = () => {
|
|||||||
setShowDeletedItems(value === 'archivedEvents');
|
setShowDeletedItems(value === 'archivedEvents');
|
||||||
// Recover all filters
|
// Recover all filters
|
||||||
// setKeyword('');
|
// setKeyword('');
|
||||||
setSorting({key: '', order: ''});
|
setSortingRules([]);
|
||||||
setSelectedItems([]);
|
setSelectedItems([]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Get the sort indicator for a column (shows priority number if multi-column)
|
||||||
const getSortingImg = (key) => {
|
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';
|
||||||
}
|
}
|
||||||
|
|
||||||
const sortTableWithField = (key) => {
|
// Get the sort priority number for display (1, 2, 3, etc.)
|
||||||
let newSorting = {
|
const getSortPriority = (key) => {
|
||||||
key,
|
const ruleIndex = sortingRules.findIndex(rule => rule.key === key);
|
||||||
order: 'asc',
|
return ruleIndex === -1 ? null : ruleIndex + 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (sorting.key === key && sorting.order === 'asc') {
|
// Multi-column sort: clicking a column adds it to sort rules or toggles its order
|
||||||
newSorting = {...newSorting, order: 'desc'};
|
// Behavior: asc → desc → remove from sorting
|
||||||
|
const sortTableWithField = (key) => {
|
||||||
|
const existingIndex = sortingRules.findIndex(rule => rule.key === key);
|
||||||
|
|
||||||
|
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 = () => {
|
const toggleSelectedAllItems = () => {
|
||||||
@ -308,13 +394,10 @@ const EventsList = () => {
|
|||||||
item.transMethod = item?.data?.trans_method;
|
item.transMethod = item?.data?.trans_method;
|
||||||
return item;
|
return item;
|
||||||
}).filter(item => item.type === 'medical' && item.confirmed);
|
}).filter(item => item.type === 'medical' && item.confirmed);
|
||||||
const newEvents = [...eventsResults];
|
|
||||||
const sortedEvents = sorting.key === '' ? newEvents : newEvents.sort((a, b) => {
|
// Apply current sorting rules (or default if none)
|
||||||
return a[sorting.key].localeCompare(b[sorting.key]);
|
const sortedEvents = applyMultiColumnSort(eventsResults, sortingRules);
|
||||||
});
|
setEvents(sortedEvents);
|
||||||
setEvents(
|
|
||||||
sorting.order === 'asc' ? sortedEvents : sortedEvents.reverse()
|
|
||||||
)
|
|
||||||
setTransportationOptionsList(data.data.filter((item) => item.type === 'transportation' && item.status === 'active'));
|
setTransportationOptionsList(data.data.filter((item) => item.type === 'transportation' && item.status === 'active'));
|
||||||
closePanel();
|
closePanel();
|
||||||
})
|
})
|
||||||
@ -349,13 +432,10 @@ const EventsList = () => {
|
|||||||
item.transMethod = item?.data?.trans_method;
|
item.transMethod = item?.data?.trans_method;
|
||||||
return item;
|
return item;
|
||||||
}).filter(item => item.type === 'medical' && item.confirmed);
|
}).filter(item => item.type === 'medical' && item.confirmed);
|
||||||
const newEvents = [...eventsResults];
|
|
||||||
const sortedEvents = sorting.key === '' ? newEvents : newEvents.sort((a, b) => {
|
// Apply current sorting rules (or default if none)
|
||||||
return a[sorting.key].localeCompare(b[sorting.key]);
|
const sortedEvents = applyMultiColumnSort(eventsResults, sortingRules);
|
||||||
});
|
setEvents(sortedEvents);
|
||||||
setEvents(
|
|
||||||
sorting.order === 'asc' ? sortedEvents : sortedEvents.reverse()
|
|
||||||
)
|
|
||||||
// setTransportationOptionsList(data.data.filter((item) => item.type === 'transportation' && item.status === 'active'));
|
// setTransportationOptionsList(data.data.filter((item) => item.type === 'transportation' && item.status === 'active'));
|
||||||
closePanel();
|
closePanel();
|
||||||
})
|
})
|
||||||
@ -373,7 +453,11 @@ const EventsList = () => {
|
|||||||
<th className="th-index">No.</th>
|
<th className="th-index">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}>
|
||||||
{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>)
|
||||||
}
|
}
|
||||||
<th>Customer Date of Birth</th>
|
<th>Customer Date of Birth</th>
|
||||||
@ -413,7 +497,7 @@ const EventsList = () => {
|
|||||||
<>
|
<>
|
||||||
<div className="list row mb-4">
|
<div className="list row mb-4">
|
||||||
<Breadcrumb>
|
<Breadcrumb>
|
||||||
<Breadcrumb.Item>Medical</Breadcrumb.Item>
|
<Breadcrumb.Item href="/medical/index">Medical</Breadcrumb.Item>
|
||||||
<Breadcrumb.Item active>
|
<Breadcrumb.Item active>
|
||||||
Appointment Information
|
Appointment Information
|
||||||
</Breadcrumb.Item>
|
</Breadcrumb.Item>
|
||||||
@ -426,7 +510,7 @@ const EventsList = () => {
|
|||||||
</h4>
|
</h4>
|
||||||
</div>
|
</div>
|
||||||
</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">
|
<div className="app-main-content-list-func-container">
|
||||||
<Tabs defaultActiveKey="activeEvents" id="requests-tab" onSelect={(k) => showDeleted(k)}>
|
<Tabs defaultActiveKey="activeEvents" id="requests-tab" onSelect={(k) => showDeleted(k)}>
|
||||||
<Tab eventKey="activeEvents" title="Active Appointments">
|
<Tab eventKey="activeEvents" title="Active Appointments">
|
||||||
|
|||||||
@ -375,7 +375,8 @@ const EventsMultipleList = () => {
|
|||||||
control: (baseStyles, state) => ({
|
control: (baseStyles, state) => ({
|
||||||
...baseStyles,
|
...baseStyles,
|
||||||
width: '210px',
|
width: '210px',
|
||||||
height: '35px',
|
height: '45px',
|
||||||
|
minHeight: '45px',
|
||||||
'padding-top': 0,
|
'padding-top': 0,
|
||||||
'padding-bottom': 0,
|
'padding-bottom': 0,
|
||||||
'margin-top': 0,
|
'margin-top': 0,
|
||||||
@ -383,23 +384,27 @@ const EventsMultipleList = () => {
|
|||||||
}),
|
}),
|
||||||
indicatorSeparator: (baseStyles, state) => ({
|
indicatorSeparator: (baseStyles, state) => ({
|
||||||
...baseStyles,
|
...baseStyles,
|
||||||
width: 0
|
display: 'none'
|
||||||
|
}),
|
||||||
|
dropdownIndicator: (baseStyles) => ({
|
||||||
|
...baseStyles,
|
||||||
|
paddingRight: '12px'
|
||||||
}),
|
}),
|
||||||
indicatorsContainer: (baseStyles) => ({
|
indicatorsContainer: (baseStyles) => ({
|
||||||
...baseStyles,
|
...baseStyles,
|
||||||
'margin-top': '-20px'
|
'margin-top': '-5px'
|
||||||
}),
|
}),
|
||||||
placeholder: (baseStyles) => ({
|
placeholder: (baseStyles) => ({
|
||||||
...baseStyles,
|
...baseStyles,
|
||||||
'margin-top': '-20px',
|
'margin-top': '-5px',
|
||||||
'font-size': '13px'
|
'font-size': '13px'
|
||||||
}),
|
}),
|
||||||
singleValue: (baseStyles, state) => ({
|
singleValue: (baseStyles, state) => ({
|
||||||
...baseStyles,
|
...baseStyles,
|
||||||
'margin-top': '-20px',
|
'margin-top': '-5px',
|
||||||
'font-size': '13px'
|
'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 || '',
|
value: resource?.id || '',
|
||||||
label: resource?.name || '',
|
label: resource?.name || '',
|
||||||
}))]}></Select>
|
}))]}></Select>
|
||||||
@ -412,7 +417,8 @@ const EventsMultipleList = () => {
|
|||||||
control: (baseStyles, state) => ({
|
control: (baseStyles, state) => ({
|
||||||
...baseStyles,
|
...baseStyles,
|
||||||
width: '210px',
|
width: '210px',
|
||||||
height: '25px',
|
height: '45px',
|
||||||
|
minHeight: '45px',
|
||||||
'padding-top': 0,
|
'padding-top': 0,
|
||||||
'padding-bottom': 0,
|
'padding-bottom': 0,
|
||||||
'margin-top': 0,
|
'margin-top': 0,
|
||||||
@ -420,11 +426,15 @@ const EventsMultipleList = () => {
|
|||||||
}),
|
}),
|
||||||
indicatorSeparator: (baseStyles, state) => ({
|
indicatorSeparator: (baseStyles, state) => ({
|
||||||
...baseStyles,
|
...baseStyles,
|
||||||
width: 0
|
display: 'none'
|
||||||
|
}),
|
||||||
|
dropdownIndicator: (baseStyles) => ({
|
||||||
|
...baseStyles,
|
||||||
|
paddingRight: '12px'
|
||||||
}),
|
}),
|
||||||
indicatorsContainer: (baseStyles) => ({
|
indicatorsContainer: (baseStyles) => ({
|
||||||
...baseStyles,
|
...baseStyles,
|
||||||
'margin-top': '-20px'
|
'margin-top': '-5px'
|
||||||
}),
|
}),
|
||||||
placeholder: (baseStyles) => ({
|
placeholder: (baseStyles) => ({
|
||||||
...baseStyles,
|
...baseStyles,
|
||||||
@ -458,7 +468,7 @@ const EventsMultipleList = () => {
|
|||||||
<>
|
<>
|
||||||
<div className="list row mb-4 noprint">
|
<div className="list row mb-4 noprint">
|
||||||
<Breadcrumb>
|
<Breadcrumb>
|
||||||
<Breadcrumb.Item>Medical</Breadcrumb.Item>
|
<Breadcrumb.Item href="/medical/index">Medical</Breadcrumb.Item>
|
||||||
<Breadcrumb.Item active>
|
<Breadcrumb.Item active>
|
||||||
Appointment Information (Multi Days)
|
Appointment Information (Multi Days)
|
||||||
</Breadcrumb.Item>
|
</Breadcrumb.Item>
|
||||||
@ -472,7 +482,7 @@ const EventsMultipleList = () => {
|
|||||||
</div>
|
</div>
|
||||||
</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">
|
<div className="app-main-content-list-func-container">
|
||||||
<Tabs defaultActiveKey="activeEvents" id="requests-tab" onSelect={(k) => showDeleted(k)}>
|
<Tabs defaultActiveKey="activeEvents" id="requests-tab" onSelect={(k) => showDeleted(k)}>
|
||||||
<Tab eventKey="activeEvents" title="Active Appointments">
|
<Tab eventKey="activeEvents" title="Active Appointments">
|
||||||
@ -485,10 +495,6 @@ const EventsMultipleList = () => {
|
|||||||
<div className="field-label">To</div>
|
<div className="field-label">To</div>
|
||||||
<DatePicker selected={toDate} onChange={(v) => {setToDate(v); setSelectedItems([]);}} />
|
<DatePicker selected={toDate} onChange={(v) => {setToDate(v); setSelectedItems([]);}} />
|
||||||
</div>
|
</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>
|
</div>
|
||||||
{table('active')}
|
{table('active')}
|
||||||
</Tab>
|
</Tab>
|
||||||
@ -502,10 +508,6 @@ const EventsMultipleList = () => {
|
|||||||
<div className="field-label">To</div>
|
<div className="field-label">To</div>
|
||||||
<DatePicker selected={toDate} onChange={(v) => {setToDate(v); setSelectedItems([]);}} />
|
<DatePicker selected={toDate} onChange={(v) => {setToDate(v); setSelectedItems([]);}} />
|
||||||
</div>
|
</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>
|
</div>
|
||||||
{table('inactive')}
|
{table('inactive')}
|
||||||
</Tab>
|
</Tab>
|
||||||
@ -531,6 +533,12 @@ const EventsMultipleList = () => {
|
|||||||
columns={columns}
|
columns={columns}
|
||||||
data={filteredEvents.filter(event => event.status === (showDeletedItems ? 'inactive' : 'active'))}
|
data={filteredEvents.filter(event => event.status === (showDeletedItems ? 'inactive' : 'active'))}
|
||||||
filename="events-multiple"
|
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>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
import React, {useState, useEffect} from "react";
|
import React, {useState, useEffect} from "react";
|
||||||
// import { useDispatch } from "react-redux";
|
// 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 { customerSlice } from "./../../store";
|
||||||
import { AuthService, CustomerService, EventsService, ResourceService } from "../../services";
|
import { AuthService, CustomerService, EventsService, ResourceService } from "../../services";
|
||||||
import Select from 'react-select';
|
import Select from 'react-select';
|
||||||
@ -11,6 +11,8 @@ import { Spinner, Breadcrumb, BreadcrumbItem, Tabs, Tab } from "react-bootstrap"
|
|||||||
const UpdateEvent = () => {
|
const UpdateEvent = () => {
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
const urlParams = useParams();
|
const urlParams = useParams();
|
||||||
|
const [searchParams] = useSearchParams();
|
||||||
|
const [activeTab, setActiveTab] = useState(searchParams.get('tab') || 'eventInfo');
|
||||||
const [currentEvent, setCurrentEvent] = useState(undefined);
|
const [currentEvent, setCurrentEvent] = useState(undefined);
|
||||||
const [medicalResource, setMedicalResource] = useState(undefined);
|
const [medicalResource, setMedicalResource] = useState(undefined);
|
||||||
const [currentResource, setCurrentResource] = useState(undefined);
|
const [currentResource, setCurrentResource] = useState(undefined);
|
||||||
@ -70,7 +72,32 @@ const UpdateEvent = () => {
|
|||||||
navigate(`/medical/events/${urlParams.id}`);
|
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 = () => {
|
const saveEvent = () => {
|
||||||
|
if (!validateEvent()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
let newEventDataClientAndResource = {};
|
let newEventDataClientAndResource = {};
|
||||||
let newEventClientAndResource = {};
|
let newEventClientAndResource = {};
|
||||||
if (currentCustomer) {
|
if (currentCustomer) {
|
||||||
@ -266,8 +293,8 @@ const UpdateEvent = () => {
|
|||||||
<>
|
<>
|
||||||
<div className="list row mb-4">
|
<div className="list row mb-4">
|
||||||
<Breadcrumb>
|
<Breadcrumb>
|
||||||
<Breadcrumb.Item>Medical</Breadcrumb.Item>
|
<Breadcrumb.Item href="/medical/index">Medical</Breadcrumb.Item>
|
||||||
<Breadcrumb.Item active>
|
<Breadcrumb.Item href="/medical/events/list">
|
||||||
Medical Appointment Information
|
Medical Appointment Information
|
||||||
</Breadcrumb.Item>
|
</Breadcrumb.Item>
|
||||||
<Breadcrumb.Item active>
|
<Breadcrumb.Item active>
|
||||||
@ -279,54 +306,62 @@ const UpdateEvent = () => {
|
|||||||
</div>
|
</div>
|
||||||
</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">
|
<div className="app-main-content-list-func-container">
|
||||||
<Tabs defaultActiveKey="eventInfo" id="event-tab">
|
<Tabs activeKey={activeTab} onSelect={(k) => setActiveTab(k)} id="event-tab">
|
||||||
{ !hideCreateFields && <Tab eventKey="eventClientInfo" title="Appointment Client And Time Information">
|
{ !hideCreateFields && <Tab eventKey="eventClientInfo" title="Appointment Customer And Time Information">
|
||||||
<div className="multi-columns-container">
|
<div className="multi-columns-container">
|
||||||
<div className="column-container">
|
<div className="column-container">
|
||||||
<div className="column-card">
|
<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="app-main-content-fields-section">
|
||||||
<div className="me-4">
|
<div className="me-4">
|
||||||
<div className="field-label">Client
|
<div className="field-label">Customer
|
||||||
<span className="required">*</span>
|
<span className="required">*</span>
|
||||||
</div>
|
</div>
|
||||||
<Select styles={{
|
<Select styles={{
|
||||||
control: (baseStyles, state) => ({
|
control: (baseStyles) => ({
|
||||||
...baseStyles,
|
...baseStyles,
|
||||||
width: '350px',
|
width: '350px',
|
||||||
height: '45px',
|
borderRadius: '8px'
|
||||||
'padding-top': 0,
|
|
||||||
'padding-bottom': 0,
|
|
||||||
'margin-top': 0,
|
|
||||||
'margin-bottom': 0
|
|
||||||
}),
|
}),
|
||||||
indicatorSeparator: (baseStyles, state) => ({
|
indicatorSeparator: () => ({
|
||||||
...baseStyles,
|
display: 'none'
|
||||||
width: 0
|
|
||||||
}),
|
}),
|
||||||
indicatorsContainer: (baseStyles) => ({
|
dropdownIndicator: (baseStyles) => ({
|
||||||
...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) => ({
|
placeholder: (baseStyles) => ({
|
||||||
...baseStyles,
|
...baseStyles,
|
||||||
'margin-top': '-10px',
|
fontSize: '13px'
|
||||||
'font-size': '13px'
|
|
||||||
}),
|
}),
|
||||||
singleValue: (baseStyles, state) => ({
|
singleValue: (baseStyles) => ({
|
||||||
...baseStyles,
|
...baseStyles,
|
||||||
'margin-top': '-10px',
|
margin: '0px',
|
||||||
'font-size': '13px'
|
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 || '',
|
value: customer?.id || '',
|
||||||
label: customer?.name || ''
|
label: customer?.name || ''
|
||||||
}))]}></Select>
|
}))]}></Select>
|
||||||
</div>
|
</div>
|
||||||
<div className="me-4">
|
<div className="me-4">
|
||||||
<div className="field-label">Start Time
|
<div className="field-label">Appointment Time
|
||||||
<span className="required">*</span>
|
<span className="required">*</span>
|
||||||
</div>
|
</div>
|
||||||
<DatePicker
|
<DatePicker
|
||||||
@ -350,10 +385,10 @@ const UpdateEvent = () => {
|
|||||||
<div className="column-card">
|
<div className="column-card">
|
||||||
{
|
{
|
||||||
currentCustomer && <>
|
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="app-main-content-fields-section short">
|
||||||
<div className="field-body">
|
<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 className="field-value">{currentCustomer?.name || ''}</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="field-body">
|
<div className="field-body">
|
||||||
@ -392,23 +427,15 @@ const UpdateEvent = () => {
|
|||||||
<h6 className="text-primary">Appointment Details</h6>
|
<h6 className="text-primary">Appointment Details</h6>
|
||||||
<div className="app-main-content-fields-section">
|
<div className="app-main-content-fields-section">
|
||||||
<div className="me-4">
|
<div className="me-4">
|
||||||
<div className="field-label">Resource
|
<div className="field-label">Provider
|
||||||
<span className="required">*</span>
|
<span className="required">*</span>
|
||||||
</div>
|
</div>
|
||||||
{currentResource ?
|
{currentResource ?
|
||||||
(<><span>{currentResource?.name}</span> <span><button className="btn btn-link btn-sm me-2 mb-2" onClick={()=> setShowResourceModal(true)}>Update</button></span></>) :
|
(<><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>)}
|
(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>
|
|
||||||
<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)}/>
|
|
||||||
</div>
|
</div>
|
||||||
<div className="me-4">
|
<div className="me-4">
|
||||||
<div className="field-label">Tag
|
<div className="field-label">Label
|
||||||
<span className="required">*</span>
|
<span className="required">*</span>
|
||||||
</div>
|
</div>
|
||||||
<select value={color} onChange={e => setColor(e.target.value)}>
|
<select value={color} onChange={e => setColor(e.target.value)}>
|
||||||
@ -451,7 +478,7 @@ const UpdateEvent = () => {
|
|||||||
</div>
|
</div>
|
||||||
<div className="app-main-content-fields-section">
|
<div className="app-main-content-fields-section">
|
||||||
<div className="me-4">
|
<div className="me-4">
|
||||||
<div className="field-label">Need Id
|
<div className="field-label">ID Needed
|
||||||
</div>
|
</div>
|
||||||
<select value={needId} onChange={e => setNeedId(e.target.value)}>
|
<select value={needId} onChange={e => setNeedId(e.target.value)}>
|
||||||
<option value=""></option>
|
<option value=""></option>
|
||||||
@ -483,16 +510,13 @@ const UpdateEvent = () => {
|
|||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
<div className="me-4">
|
<div className="me-4">
|
||||||
<div className="field-label">Transportation Needed
|
<div className="field-label">Transportation Support
|
||||||
</div>
|
</div>
|
||||||
<select value={transMethod} onChange={e => setTransMethod(e.target.value)}>
|
<select value={transMethod} onChange={e => setTransMethod(e.target.value)}>
|
||||||
<option value=""></option>
|
<option value=""></option>
|
||||||
<option value="by own">By Own</option>
|
{
|
||||||
<option value="televisit">Televisit</option>
|
EventsService.transportationTypeOptions?.map((item) => <option key={item?.value} value={item?.value}>{item?.label}</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>
|
|
||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -563,7 +587,7 @@ const UpdateEvent = () => {
|
|||||||
</div>
|
</div>
|
||||||
<Modal show={showResourceModal} fullscreen={'xxl-down'} onHide={() => setShowResourceModal(false)}>
|
<Modal show={showResourceModal} fullscreen={'xxl-down'} onHide={() => setShowResourceModal(false)}>
|
||||||
<Modal.Header closeButton>
|
<Modal.Header closeButton>
|
||||||
<Modal.Title>Select the Resource</Modal.Title>
|
<Modal.Title>Select the Provider</Modal.Title>
|
||||||
</Modal.Header>
|
</Modal.Header>
|
||||||
<Modal.Body>
|
<Modal.Body>
|
||||||
<div className="app-main-content-fields-section">
|
<div className="app-main-content-fields-section">
|
||||||
@ -572,7 +596,7 @@ const UpdateEvent = () => {
|
|||||||
<input type="text" value={keyword} onChange={e => setKeyword(e.target.value)}/>
|
<input type="text" value={keyword} onChange={e => setKeyword(e.target.value)}/>
|
||||||
</div>
|
</div>
|
||||||
<div className="mb-4 me-4">
|
<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)}>
|
<select value={resourceType} onChange={e => setResourceType(e.target.value)}>
|
||||||
<option value=""></option>
|
<option value=""></option>
|
||||||
<option value="doctor">Doctor</option>
|
<option value="doctor">Doctor</option>
|
||||||
|
|||||||
@ -1,130 +1,207 @@
|
|||||||
import React, {useState, useEffect} from "react";
|
import React, {useState, useEffect} from "react";
|
||||||
// import { useDispatch } from "react-redux";
|
import { PencilSquare } from "react-bootstrap-icons";
|
||||||
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 { AuthService, CustomerService, EventsService, ResourceService } from "../../services";
|
||||||
|
import { Breadcrumb, Tabs, Tab } from "react-bootstrap";
|
||||||
|
|
||||||
const ViewEvent = () => {
|
const ViewEvent = () => {
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
const urlParams = useParams();
|
const urlParams = useParams();
|
||||||
|
const [searchParams] = useSearchParams();
|
||||||
|
const [activeTab, setActiveTab] = useState(searchParams.get('tab') || 'eventInfo');
|
||||||
const [currentEvent, setCurrentEvent] = useState(undefined);
|
const [currentEvent, setCurrentEvent] = useState(undefined);
|
||||||
const [resources, setResources] = useState([]);
|
const [resources, setResources] = useState([]);
|
||||||
const [customers, setCustomers] = useState([]);
|
const [customers, setCustomers] = useState([]);
|
||||||
const params = new URLSearchParams(window.location.search);
|
const params = new URLSearchParams(window.location.search);
|
||||||
|
|
||||||
const redirectToCalendar = () => {
|
const redirectToCalendar = () => {
|
||||||
navigate(`/medical/events/calendar`);
|
navigate(`/medical/events/calendar`);
|
||||||
}
|
}
|
||||||
|
|
||||||
const redirectTo = (id) => {
|
const redirectTo = () => {
|
||||||
navigate(`/medical/events/list`);
|
navigate(`/medical/events/list`);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const goToEdit = (id) => {
|
||||||
|
navigate(`/medical/events/edit/${id}?tab=${activeTab}`);
|
||||||
|
}
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!AuthService.canAccessLegacySystem()) {
|
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.')
|
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();
|
AuthService.logout();
|
||||||
navigate(`/login`);
|
navigate(`/login`);
|
||||||
}
|
}
|
||||||
Promise.all([ResourceService.getAll(), CustomerService.getAllCustomers()]).then(([resourcesData, customersData]) => {
|
Promise.all([ResourceService.getAll(), CustomerService.getAllActiveCustomers()]).then(([resourcesData, customersData]) => {
|
||||||
setResources(resourcesData.data);
|
setResources(resourcesData.data);
|
||||||
setCustomers(customersData.data);
|
setCustomers(customersData.data);
|
||||||
if (!currentEvent) {
|
if (!currentEvent) {
|
||||||
EventsService.getEvent(urlParams.id).then(eventData => {
|
EventsService.getEvent(urlParams.id).then(eventData => {
|
||||||
setCurrentEvent(eventData.data);
|
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 (
|
return (
|
||||||
<>
|
<>
|
||||||
<div className="list row">
|
<div className="list row mb-4">
|
||||||
<div className="col-md-12 col-xs-12">
|
<Breadcrumb>
|
||||||
<div className="list row mb-4">
|
<Breadcrumb.Item href="/medical/index">Medical</Breadcrumb.Item>
|
||||||
<div className="col-md-12 text-primary">
|
<Breadcrumb.Item href="/medical/events/list">
|
||||||
<h5>View Medical Event Details<button className="btn btn-link btn-sm" onClick={() => {redirectTo()}}>Back</button></h5>
|
Medical Events
|
||||||
</div>
|
</Breadcrumb.Item>
|
||||||
</div>
|
<Breadcrumb.Item active>
|
||||||
<div className="list row mb-4">
|
View Event Details
|
||||||
<div className="col-md-6 mb-4">
|
</Breadcrumb.Item>
|
||||||
<div>Resource:</div>
|
</Breadcrumb>
|
||||||
{ currentEvent?.data?.resource ? (resources.find(r => r.id === currentEvent?.data?.resource)?.name || currentEvent?.data?.resource_name) : currentEvent?.data?.resource_name}
|
<div className="col-md-12 text-primary">
|
||||||
</div>
|
<h4>View Medical Event Details <button className="btn btn-link btn-sm" onClick={() => {redirectTo()}}>Back</button></h4>
|
||||||
<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>
|
|
||||||
<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>
|
|
||||||
{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>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<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="field-body">
|
||||||
|
<div className="field-label">Customer</div>
|
||||||
|
<div className="field-value">{getCustomerName()}</div>
|
||||||
|
</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>
|
||||||
|
</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',
|
name: 'Transportation Schedule',
|
||||||
link: '/trans-routes/dashboard',
|
link: '/trans-routes/dashboard',
|
||||||
category: '/trans-routes',
|
category: '/trans-routes/dashboard',
|
||||||
roleFunc: AuthService.canViewRoutes
|
roleFunc: AuthService.canViewRoutes
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'Customer Report',
|
name: 'Templates',
|
||||||
link: '/admin/customer-report',
|
link: '/trans-routes/daily-templates/list',
|
||||||
category: '/admin/',
|
category: '/trans-routes/daily-templates',
|
||||||
roleFunc: AuthService.canViewAttendance
|
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',
|
// name: 'Schedule Driver for Appointment',
|
||||||
// link: '#',
|
// link: '#',
|
||||||
@ -171,12 +183,6 @@ const SideMenu = () => {
|
|||||||
category: '/events/calendar',
|
category: '/events/calendar',
|
||||||
roleFunc: AuthService.canAccessLegacySystem
|
roleFunc: AuthService.canAccessLegacySystem
|
||||||
},
|
},
|
||||||
{
|
|
||||||
name: 'Appointment One-Day List',
|
|
||||||
link: '/medical/events/list',
|
|
||||||
category: '/events/list',
|
|
||||||
roleFunc: AuthService.canAccessLegacySystem
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
name: 'Appointment Multi-Days List',
|
name: 'Appointment Multi-Days List',
|
||||||
link: '/medical/events/multiple-list',
|
link: '/medical/events/multiple-list',
|
||||||
@ -191,11 +197,11 @@ const SideMenu = () => {
|
|||||||
collapsed: false,
|
collapsed: false,
|
||||||
roleFunc: () => true,
|
roleFunc: () => true,
|
||||||
subNavs: [
|
subNavs: [
|
||||||
// {
|
{
|
||||||
// name: 'Meal Status',
|
name: 'Meal Status',
|
||||||
// link: '#',
|
link: '/meal-status',
|
||||||
// roleFunc: () => true
|
roleFunc: () => true
|
||||||
// },
|
},
|
||||||
{
|
{
|
||||||
name: 'Seating Chart',
|
name: 'Seating Chart',
|
||||||
link: '/seating',
|
link: '/seating',
|
||||||
|
|||||||
@ -122,9 +122,12 @@ const InfoScreen = () => {
|
|||||||
|
|
||||||
console.log('Attendance note image response:', imageResponse);
|
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
|
// Get the first image file
|
||||||
const imageFile = imageResponse.data.files[0];
|
const imageFile = files[0];
|
||||||
console.log('Attendance note image file:', imageFile);
|
console.log('Attendance note image file:', imageFile);
|
||||||
setAttendanceNote(prev => ({
|
setAttendanceNote(prev => ({
|
||||||
...prev,
|
...prev,
|
||||||
@ -176,12 +179,18 @@ const InfoScreen = () => {
|
|||||||
'carousel_item'
|
'carousel_item'
|
||||||
);
|
);
|
||||||
console.log('Carousel images response:', imagesResponse);
|
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
|
// 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 image URLs:', imageUrls);
|
||||||
console.log('Carousel files array:', imagesResponse.data.files);
|
console.log('Carousel files array:', files);
|
||||||
setCarousel(prev => ({
|
setCarousel(prev => ({
|
||||||
...prev,
|
...prev,
|
||||||
images: imageUrls
|
images: imageUrls
|
||||||
@ -189,6 +198,12 @@ const InfoScreen = () => {
|
|||||||
} else {
|
} else {
|
||||||
console.log('No files found in carousel response');
|
console.log('No files found in carousel response');
|
||||||
console.log('Response structure:', imagesResponse);
|
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) {
|
} catch (imageError) {
|
||||||
console.log('No images found for carousel:', imageError);
|
console.log('No images found for carousel:', imageError);
|
||||||
@ -910,61 +925,50 @@ const InfoScreen = () => {
|
|||||||
{/* Full Screen Content */}
|
{/* Full Screen Content */}
|
||||||
{isFullScreen && (
|
{isFullScreen && (
|
||||||
<div className="multi-columns-container">
|
<div className="multi-columns-container">
|
||||||
|
{/* Column 1 - Appointments */}
|
||||||
<div className="column-container">
|
<div className="column-container">
|
||||||
<div className="column-card" style={{ backgroundColor: 'rgba(255, 255, 255, 0.8)' }}>
|
<div className="column-card">
|
||||||
<h6 className="text-black" style={{ fontSize: '14px', fontWeight: '600' }}>Appointments</h6>
|
<h6 className="text-black fullscreen-title">Appointments</h6>
|
||||||
<div className="app-main-content-fields-section">
|
<table className="personnel-info-table info-screen-appointments-table">
|
||||||
<div className="list row mb-4">
|
<thead>
|
||||||
<div className="col-md-12">
|
<tr>
|
||||||
<table className="personnel-info-table info-screen-appointments-table">
|
<th>Time</th>
|
||||||
<thead>
|
<th>Customer</th>
|
||||||
<tr>
|
<th>Location</th>
|
||||||
<th>Time</th>
|
</tr>
|
||||||
<th>Customer</th>
|
</thead>
|
||||||
<th>Location</th>
|
<tbody>
|
||||||
</tr>
|
{medicalEvents && medicalEvents.length > 0 ? (
|
||||||
</thead>
|
medicalEvents.map((medicalEvent, index) => (
|
||||||
<tbody>
|
<tr key={medicalEvent.id}>
|
||||||
{medicalEvents && medicalEvents.length > 0 ? (
|
<td>{medicalEvent.startTime}</td>
|
||||||
medicalEvents.map((medicalEvent, index) => (
|
<td>{medicalEvent.customer}</td>
|
||||||
<tr key={medicalEvent.id}>
|
<td>{medicalEvent.location}</td>
|
||||||
<td>{medicalEvent.startTime}</td>
|
</tr>
|
||||||
<td>{medicalEvent.customer}</td>
|
))
|
||||||
<td>{medicalEvent.location}</td>
|
) : (
|
||||||
</tr>
|
<tr>
|
||||||
))
|
<td colSpan="3" className="text-center">No medical appointments scheduled for today</td>
|
||||||
) : (
|
</tr>
|
||||||
<tr>
|
)}
|
||||||
<td colSpan="3" className="text-center">No medical appointments scheduled for today</td>
|
</tbody>
|
||||||
</tr>
|
</table>
|
||||||
)}
|
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
{/* Column 2 - Time, Weather, Attendance Note, Gallery */}
|
||||||
<div className="column-container">
|
<div className="column-container">
|
||||||
{/* First Piece - Time and Weather Cards */}
|
{/* Time and Weather Cards Row */}
|
||||||
<div className="row mb-4">
|
<div className="row mb-4 time-weather-row">
|
||||||
{/* Time Card */}
|
{/* Time Card */}
|
||||||
<div className="col-md-6">
|
<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="card-body text-center">
|
||||||
<div className="mb-3">
|
<div className="fullscreen-date-display">
|
||||||
<div className="text-black" style={{ fontSize: '12px' }}>
|
<div className="text-black">{moment(currentTime).format('dddd')}</div>
|
||||||
{moment(currentTime).format('dddd')}
|
<div className="text-black">{moment(currentTime).format('MMM Do, YYYY')}</div>
|
||||||
</div>
|
<h5 className="text-primary mt-2 fullscreen-time">{moment(currentTime).format('HH:mm')}</h5>
|
||||||
<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>
|
</div>
|
||||||
|
|
||||||
{/* CSS Clock */}
|
|
||||||
<div className="clock-container">
|
<div className="clock-container">
|
||||||
<div className="clock">
|
<div className="clock">
|
||||||
<div className="clock-face">
|
<div className="clock-face">
|
||||||
@ -987,78 +991,41 @@ const InfoScreen = () => {
|
|||||||
|
|
||||||
{/* Weather Card */}
|
{/* Weather Card */}
|
||||||
<div className="col-md-6">
|
<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="card-body text-center">
|
||||||
<div className="mb-3">
|
<div className="fullscreen-date-display">
|
||||||
<div className="text-black" style={{ fontSize: '12px' }}>
|
<div className="text-black">New York, NY</div>
|
||||||
New York, NY
|
<div className="text-black">Partly Cloudy</div>
|
||||||
</div>
|
<h5 className="text-primary mt-2 fullscreen-time">22°C</h5>
|
||||||
<div className="text-black">
|
|
||||||
Partly Cloudy
|
|
||||||
</div>
|
|
||||||
<h5 className="text-primary mt-2" style={{ fontSize: '20px', fontWeight: '600' }}>
|
|
||||||
22°C
|
|
||||||
</h5>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Weather Icon */}
|
|
||||||
<div className="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>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Second Piece - AttendanceNote Card */}
|
{/* AttendanceNote Card */}
|
||||||
<div className="mb-4">
|
<div className="attendance-note-wrapper">
|
||||||
<div className="card" style={{borderRadius: '8px', maxWidth: '450px', backgroundColor: 'rgba(255, 255, 255, 0.8)'}}>
|
<div className="card">
|
||||||
<div className="card-body">
|
<div className="card-body">
|
||||||
{/* Header with Logo and Edit Button */}
|
<div className="d-flex justify-content-between align-items-start mb-2">
|
||||||
<div className="d-flex justify-content-between align-items-start mb-3">
|
|
||||||
<div className="d-flex align-items-center">
|
<div className="d-flex align-items-center">
|
||||||
<img src="/images/logo-trans.png" alt="Worldshine Logo" style={{ height: '30px', marginRight: '10px' }} />
|
<img src="/images/logo-trans.png" alt="Worldshine Logo" className="fullscreen-logo" />
|
||||||
<strong className="logo-worldshine" style={{ color: '#0066B1', fontSize: '16px' }}>Worldshine</strong>
|
<strong className="logo-worldshine">Worldshine</strong>
|
||||||
</div>
|
</div>
|
||||||
<Button
|
|
||||||
variant="link"
|
|
||||||
className="p-0"
|
|
||||||
onClick={handleEditClick}
|
|
||||||
style={{ color: '#666' }}
|
|
||||||
>
|
|
||||||
<PencilSquare size={16} />
|
|
||||||
</Button>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Content Layout */}
|
|
||||||
<div className="row">
|
<div className="row">
|
||||||
{/* Left Side - Text Content */}
|
|
||||||
<div className="col-md-8">
|
<div className="col-md-8">
|
||||||
<h5 className="text-primary mb-2">{attendanceNote.slogan}</h5>
|
<h5 className="text-primary mb-2">{attendanceNote.slogan}</h5>
|
||||||
<p className="text-muted" style={{ fontSize: '14px', lineHeight: '1.6' }}>
|
<p className="text-muted fullscreen-intro">{attendanceNote.introduction}</p>
|
||||||
{attendanceNote.introduction}
|
|
||||||
</p>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Right Side - Image */}
|
|
||||||
<div className="col-md-4">
|
<div className="col-md-4">
|
||||||
{attendanceNote.image ? (
|
{attendanceNote.image ? (
|
||||||
<img
|
<img src={attendanceNote.image} alt="Attendance Note" className="img-fluid rounded fullscreen-note-img" />
|
||||||
src={attendanceNote.image}
|
|
||||||
alt="Attendance Note"
|
|
||||||
className="img-fluid rounded"
|
|
||||||
style={{ maxHeight: '120px', width: '100%', objectFit: 'cover' }}
|
|
||||||
/>
|
|
||||||
) : (
|
) : (
|
||||||
<div
|
<div className="d-flex align-items-center justify-content-center fullscreen-placeholder">
|
||||||
className="d-flex align-items-center justify-content-center"
|
|
||||||
style={{
|
|
||||||
height: '120px',
|
|
||||||
backgroundColor: '#f8f9fa',
|
|
||||||
border: '2px dashed #dee2e6',
|
|
||||||
borderRadius: '8px'
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<span className="text-muted">No image</span>
|
<span className="text-muted">No image</span>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
@ -1068,56 +1035,26 @@ const InfoScreen = () => {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Third Piece - Gallery */}
|
{/* Gallery Card */}
|
||||||
<div className="mb-4">
|
<div className="gallery-wrapper">
|
||||||
<div className="card" style={{borderRadius: '8px', maxWidth: '450px', backgroundColor: 'rgba(255, 255, 255, 0.8)'}}>
|
<div className="card">
|
||||||
<div className="card-body">
|
<div className="card-body">
|
||||||
{/* Header with Title and Edit Button */}
|
<h6 className="text-black mb-2 fullscreen-title">Gallery</h6>
|
||||||
<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 */}
|
|
||||||
{carousel.images.length > 0 ? (
|
{carousel.images.length > 0 ? (
|
||||||
<Carousel
|
<Carousel
|
||||||
interval={10000}
|
interval={carousel.images.length > 1 ? 5000 : null}
|
||||||
controls={false}
|
controls={false}
|
||||||
indicators={true}
|
indicators={carousel.images.length > 1}
|
||||||
style={{ height: '200px' }}
|
className="fullscreen-carousel"
|
||||||
>
|
>
|
||||||
{carousel.images.map((image, index) => (
|
{carousel.images.map((image, index) => (
|
||||||
<Carousel.Item key={index}>
|
<Carousel.Item key={index}>
|
||||||
<img
|
<img className="d-block w-100 fullscreen-carousel-img" src={image} alt={`Gallery ${index + 1}`} />
|
||||||
className="d-block w-100"
|
|
||||||
src={image}
|
|
||||||
alt={`Gallery ${index + 1}`}
|
|
||||||
style={{
|
|
||||||
height: '200px',
|
|
||||||
objectFit: 'cover',
|
|
||||||
borderRadius: '4px'
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
</Carousel.Item>
|
</Carousel.Item>
|
||||||
))}
|
))}
|
||||||
</Carousel>
|
</Carousel>
|
||||||
) : (
|
) : (
|
||||||
<div
|
<div className="d-flex align-items-center justify-content-center fullscreen-placeholder">
|
||||||
className="d-flex align-items-center justify-content-center"
|
|
||||||
style={{
|
|
||||||
height: '200px',
|
|
||||||
backgroundColor: '#f8f9fa',
|
|
||||||
border: '2px dashed #dee2e6',
|
|
||||||
borderRadius: '4px'
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<span className="text-muted">No images in gallery</span>
|
<span className="text-muted">No images in gallery</span>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
@ -1125,17 +1062,16 @@ const InfoScreen = () => {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
{/* Column 3 - Activities and Menu */}
|
||||||
<div className="column-container">
|
<div className="column-container">
|
||||||
<div className="column-card mb-4" style={{ backgroundColor: 'rgba(255, 255, 255, 0.8)' }}>
|
<div className="column-card">
|
||||||
<h6 className="text-black mb-3" style={{ fontSize: '14px', fontWeight: '500' }}>Activities</h6>
|
<h6 className="text-black fullscreen-title">Activities</h6>
|
||||||
|
|
||||||
{/* Activities Table */}
|
|
||||||
|
|
||||||
<table className="personnel-info-table info-screen-appointments-table">
|
<table className="personnel-info-table info-screen-appointments-table">
|
||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
<th>Time</th>
|
<th>Time</th>
|
||||||
<th>Activity Name</th>
|
<th>Activity</th>
|
||||||
<th>Location</th>
|
<th>Location</th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
@ -1155,40 +1091,33 @@ const InfoScreen = () => {
|
|||||||
)}
|
)}
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
|
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
<div className="column-card" style={{ backgroundColor: 'rgba(255, 255, 255, 0.8)' }}>
|
<div className="column-card">
|
||||||
{/* Menu Table */}
|
<h6 className="text-black fullscreen-title">Menu</h6>
|
||||||
<div className="list row mb-4">
|
<table className="personnel-info-table info-screen-appointments-table">
|
||||||
<div className="col-md-12">
|
<thead>
|
||||||
<h6 className="text-black mb-3" style={{ fontSize: '14px', fontWeight: '500' }}>Menu</h6>
|
<tr>
|
||||||
<table className="personnel-info-table info-screen-appointments-table">
|
<th>Breakfast</th>
|
||||||
<thead>
|
<th>Lunch</th>
|
||||||
<tr>
|
<th>Snack</th>
|
||||||
<th>Breakfast</th>
|
</tr>
|
||||||
<th>Lunch</th>
|
</thead>
|
||||||
<th>Snack</th>
|
<tbody>
|
||||||
|
{mealEvents && mealEvents.length > 0 ? (
|
||||||
|
mealEvents.map((mealEvent, index) => (
|
||||||
|
<tr key={mealEvent.id}>
|
||||||
|
<td>{mealEvent.breakfast}</td>
|
||||||
|
<td>{mealEvent.lunch}</td>
|
||||||
|
<td>{mealEvent.snack}</td>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
))
|
||||||
<tbody>
|
) : (
|
||||||
{mealEvents && mealEvents.length > 0 ? (
|
<tr>
|
||||||
mealEvents.map((mealEvent, index) => (
|
<td colSpan="3" className="text-center">No meal plan for today</td>
|
||||||
<tr key={mealEvent.id}>
|
</tr>
|
||||||
<td>{mealEvent.breakfast}</td>
|
)}
|
||||||
<td>{mealEvent.lunch}</td>
|
</tbody>
|
||||||
<td>{mealEvent.snack}</td>
|
</table>
|
||||||
</tr>
|
|
||||||
))
|
|
||||||
) : (
|
|
||||||
<tr>
|
|
||||||
<td colSpan="3" className="text-center">No meal plan for today</td>
|
|
||||||
</tr>
|
|
||||||
)}
|
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@ -5,7 +5,7 @@ import { useNavigate } from "react-router-dom";
|
|||||||
const Landing = () => {
|
const Landing = () => {
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
const goToAdmin = () => {
|
const goToAdmin = () => {
|
||||||
navigate('/admin');
|
navigate('/dashboard/dashboard');
|
||||||
}
|
}
|
||||||
|
|
||||||
const getLogoSuffix = () => {
|
const getLogoSuffix = () => {
|
||||||
@ -31,9 +31,9 @@ const Landing = () => {
|
|||||||
<div className="landing-content mb-4">
|
<div className="landing-content mb-4">
|
||||||
<button onClick={() => goToAdmin()} className="btn btn-primary">Center Management Access</button>
|
<button onClick={() => goToAdmin()} className="btn btn-primary">Center Management Access</button>
|
||||||
</div>
|
</div>
|
||||||
<div className="landing-content">
|
{/* <div className="landing-content">
|
||||||
<button className="btn btn-primary">HR Management Access</button>
|
<button className="btn btn-primary">HR Management Access</button>
|
||||||
</div>
|
</div> */}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="landing-img"/>
|
<div className="landing-img"/>
|
||||||
|
|||||||
@ -22,7 +22,29 @@ const CreateMessage = () => {
|
|||||||
navigate(`/messages/list`);
|
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 = () => {
|
const saveMessage = () => {
|
||||||
|
if (!validateMessage()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
const data = {
|
const data = {
|
||||||
message_group: messageGroup,
|
message_group: messageGroup,
|
||||||
message_title: messageTitle,
|
message_title: messageTitle,
|
||||||
@ -38,10 +60,13 @@ const CreateMessage = () => {
|
|||||||
<>
|
<>
|
||||||
<div className="list row mb-4">
|
<div className="list row mb-4">
|
||||||
<Breadcrumb>
|
<Breadcrumb>
|
||||||
<Breadcrumb.Item>General</Breadcrumb.Item>
|
<Breadcrumb.Item href="/">General</Breadcrumb.Item>
|
||||||
<Breadcrumb.Item active>
|
<Breadcrumb.Item href="/messages/list">
|
||||||
Messaging
|
Messaging
|
||||||
</Breadcrumb.Item>
|
</Breadcrumb.Item>
|
||||||
|
<Breadcrumb.Item active>
|
||||||
|
Create Message Template
|
||||||
|
</Breadcrumb.Item>
|
||||||
</Breadcrumb>
|
</Breadcrumb>
|
||||||
<div className="col-md-12 text-primary">
|
<div className="col-md-12 text-primary">
|
||||||
<h4>
|
<h4>
|
||||||
|
|||||||
@ -49,7 +49,7 @@ const MessageList = () => {
|
|||||||
<>
|
<>
|
||||||
<div className="list row mb-4">
|
<div className="list row mb-4">
|
||||||
<Breadcrumb>
|
<Breadcrumb>
|
||||||
<Breadcrumb.Item>General</Breadcrumb.Item>
|
<Breadcrumb.Item href="/">General</Breadcrumb.Item>
|
||||||
<Breadcrumb.Item active>
|
<Breadcrumb.Item active>
|
||||||
Messaging
|
Messaging
|
||||||
</Breadcrumb.Item>
|
</Breadcrumb.Item>
|
||||||
|
|||||||
@ -58,7 +58,7 @@ const MessageTokenEditor = () => {
|
|||||||
<>
|
<>
|
||||||
<div className="list row mb-4">
|
<div className="list row mb-4">
|
||||||
<Breadcrumb>
|
<Breadcrumb>
|
||||||
<Breadcrumb.Item>General</Breadcrumb.Item>
|
<Breadcrumb.Item href="/">General</Breadcrumb.Item>
|
||||||
<Breadcrumb.Item active>
|
<Breadcrumb.Item active>
|
||||||
Messaging
|
Messaging
|
||||||
</Breadcrumb.Item>
|
</Breadcrumb.Item>
|
||||||
|
|||||||
@ -67,8 +67,8 @@ const SendMessage = () => {
|
|||||||
<>
|
<>
|
||||||
<div className="list row mb-4">
|
<div className="list row mb-4">
|
||||||
<Breadcrumb>
|
<Breadcrumb>
|
||||||
<Breadcrumb.Item>General</Breadcrumb.Item>
|
<Breadcrumb.Item href="/">General</Breadcrumb.Item>
|
||||||
<Breadcrumb.Item active>
|
<Breadcrumb.Item href="/messages/list">
|
||||||
Messaging
|
Messaging
|
||||||
</Breadcrumb.Item>
|
</Breadcrumb.Item>
|
||||||
<Breadcrumb.Item active>
|
<Breadcrumb.Item active>
|
||||||
@ -96,69 +96,85 @@ const SendMessage = () => {
|
|||||||
<span className="required">*</span>
|
<span className="required">*</span>
|
||||||
</div>
|
</div>
|
||||||
<Select styles={{
|
<Select styles={{
|
||||||
control: (baseStyles, state) => ({
|
control: (baseStyles) => ({
|
||||||
...baseStyles,
|
...baseStyles,
|
||||||
width: '350px',
|
width: '350px',
|
||||||
height: '45px',
|
borderRadius: '8px'
|
||||||
'padding-top': 0,
|
|
||||||
'padding-bottom': 0,
|
|
||||||
'margin-top': 0,
|
|
||||||
'margin-bottom': 0
|
|
||||||
}),
|
}),
|
||||||
indicatorSeparator: (baseStyles, state) => ({
|
indicatorSeparator: () => ({
|
||||||
...baseStyles,
|
display: 'none'
|
||||||
width: 0
|
|
||||||
}),
|
}),
|
||||||
indicatorsContainer: (baseStyles) => ({
|
dropdownIndicator: (baseStyles) => ({
|
||||||
...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) => ({
|
placeholder: (baseStyles) => ({
|
||||||
...baseStyles,
|
...baseStyles,
|
||||||
'margin-top': '-10px',
|
fontSize: '13px'
|
||||||
'font-size': '13px'
|
|
||||||
}),
|
}),
|
||||||
singleValue: (baseStyles, state) => ({
|
singleValue: (baseStyles) => ({
|
||||||
...baseStyles,
|
...baseStyles,
|
||||||
'margin-top': '-10px',
|
margin: '0px',
|
||||||
'font-size': '13px'
|
fontSize: '13px'
|
||||||
})
|
})
|
||||||
}} value={contactSeniorPhone || ''} onChange={selectedData => onContactSeniorChange(selectedData)} options={[{value: '', label: ''}, ...seniorPhoneList.map(senior => ({
|
}} value={contactSeniorPhone || ''} onChange={selectedData => onContactSeniorChange(selectedData)} options={[{value: '', label: ''}, ...seniorPhoneList.map(senior => ({
|
||||||
value: senior?.mobile_phone || '',
|
value: senior?.mobile_phone || '',
|
||||||
label: `${senior?.name}(${senior?.name_cn}) - ${senior?.mobile_phone}` || '',
|
label: `${senior?.name}(${senior?.name_cn}) - ${senior?.mobile_phone}` || '',
|
||||||
}))]}></Select>
|
}))]}></Select>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="app-main-content-fields-section">
|
<div className="app-main-content-fields-section">
|
||||||
<div className="me-4">
|
<div className="me-4">
|
||||||
<div className="field-label">Select Message Template (Optional)</div>
|
<div className="field-label">Select Message Template (Optional)</div>
|
||||||
<Select styles={{
|
<Select styles={{
|
||||||
control: (baseStyles, state) => ({
|
control: (baseStyles) => ({
|
||||||
...baseStyles,
|
...baseStyles,
|
||||||
width: '350px',
|
width: '350px',
|
||||||
height: '45px',
|
borderRadius: '8px'
|
||||||
'padding-top': 0,
|
|
||||||
'padding-bottom': 0,
|
|
||||||
'margin-top': 0,
|
|
||||||
'margin-bottom': 0
|
|
||||||
}),
|
}),
|
||||||
indicatorSeparator: (baseStyles, state) => ({
|
indicatorSeparator: () => ({
|
||||||
...baseStyles,
|
display: 'none'
|
||||||
width: 0
|
|
||||||
}),
|
}),
|
||||||
indicatorsContainer: (baseStyles) => ({
|
dropdownIndicator: (baseStyles) => ({
|
||||||
...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) => ({
|
placeholder: (baseStyles) => ({
|
||||||
...baseStyles,
|
...baseStyles,
|
||||||
'margin-top': '-10px',
|
fontSize: '13px'
|
||||||
'font-size': '13px'
|
|
||||||
}),
|
}),
|
||||||
singleValue: (baseStyles, state) => ({
|
singleValue: (baseStyles) => ({
|
||||||
...baseStyles,
|
...baseStyles,
|
||||||
'margin-top': '-10px',
|
margin: '0px',
|
||||||
'font-size': '13px'
|
fontSize: '13px'
|
||||||
})
|
})
|
||||||
}} value={messageTemplate || ''} onChange={selectedData => {setMessageTemplate(selectedData); setMessageText(selectedData.value)}} options={[{value: '', label: ''}, ...messageTempateList.map(template => ({
|
}} value={messageTemplate || ''} onChange={selectedData => {setMessageTemplate(selectedData); setMessageText(selectedData.value)}} options={[{value: '', label: ''}, ...messageTempateList.map(template => ({
|
||||||
value: template.message_body || '',
|
value: template.message_body || '',
|
||||||
|
|||||||
@ -77,7 +77,7 @@ const SentMessageList = () => {
|
|||||||
<>
|
<>
|
||||||
<div className="list row mb-4">
|
<div className="list row mb-4">
|
||||||
<Breadcrumb>
|
<Breadcrumb>
|
||||||
<Breadcrumb.Item>General</Breadcrumb.Item>
|
<Breadcrumb.Item href="/">General</Breadcrumb.Item>
|
||||||
<Breadcrumb.Item active>
|
<Breadcrumb.Item active>
|
||||||
Messaging
|
Messaging
|
||||||
</Breadcrumb.Item>
|
</Breadcrumb.Item>
|
||||||
|
|||||||
@ -41,7 +41,29 @@ const UpdateMessage = () => {
|
|||||||
navigate(`/messages/list`);
|
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 = () => {
|
const saveMessage = () => {
|
||||||
|
if (!validateMessage()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
const data = {
|
const data = {
|
||||||
message_group: messageGroup,
|
message_group: messageGroup,
|
||||||
message_title: messageTitle,
|
message_title: messageTitle,
|
||||||
@ -57,10 +79,13 @@ const UpdateMessage = () => {
|
|||||||
<>
|
<>
|
||||||
<div className="list row mb-4">
|
<div className="list row mb-4">
|
||||||
<Breadcrumb>
|
<Breadcrumb>
|
||||||
<Breadcrumb.Item>General</Breadcrumb.Item>
|
<Breadcrumb.Item href="/">General</Breadcrumb.Item>
|
||||||
<Breadcrumb.Item active>
|
<Breadcrumb.Item href="/messages/list">
|
||||||
Messaging
|
Messaging
|
||||||
</Breadcrumb.Item>
|
</Breadcrumb.Item>
|
||||||
|
<Breadcrumb.Item active>
|
||||||
|
Update Message Template
|
||||||
|
</Breadcrumb.Item>
|
||||||
</Breadcrumb>
|
</Breadcrumb>
|
||||||
<div className="col-md-12 text-primary">
|
<div className="col-md-12 text-primary">
|
||||||
<h4>
|
<h4>
|
||||||
|
|||||||
@ -1,77 +1,109 @@
|
|||||||
import React, {useState, useEffect} from "react";
|
import React, {useState, useEffect} from "react";
|
||||||
import { useNavigate, useParams } from "react-router-dom";
|
import { useNavigate, useParams } from "react-router-dom";
|
||||||
import { AuthService, ResourceService } from "../../services";
|
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 } from "react-bootstrap";
|
||||||
|
import { RESOURCE_TYPE_OPTIONS, RESOURCE_SPECIALTY_OPTIONS } from "../../shared/constants";
|
||||||
|
|
||||||
const CreateResource = () => {
|
const CreateResource = () => {
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
const urlParams = useParams();
|
const urlParams = useParams();
|
||||||
const [name, setName] = useState('');
|
|
||||||
const [originalName, setOriginalName] = useState('');
|
// Basic Information
|
||||||
const [branchName, setBranchName] = useState('');
|
const [name, setName] = useState(''); // Provider name
|
||||||
const [specialty, setSpecialty] = useState('');
|
const [officeName, setOfficeName] = useState('');
|
||||||
|
const [specialty, setSpecialty] = useState('');
|
||||||
const [type, setType] = useState('');
|
const [type, setType] = useState('');
|
||||||
const [category, setCategory] = useState('');
|
|
||||||
const [description, setDescription] = useState('');
|
// Contact Information
|
||||||
const [color, setColor] = useState('');
|
const [phone, setPhone] = useState(''); // Office Phone Number
|
||||||
const [address, setAddress] = useState('');
|
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 [city, setCity] = useState('');
|
||||||
const [state, setState] = useState('');
|
const [state, setState] = useState('');
|
||||||
const [zipcode, setZipcode] = useState('');
|
const [zipcode, setZipcode] = useState('');
|
||||||
const [contact, setContact] = useState('');
|
|
||||||
const [phone, setPhone] = useState('');
|
// Additional Information
|
||||||
const [dataObject, setDataObject] = useState(undefined);
|
|
||||||
const [note, setNote] = useState('');
|
const [note, setNote] = useState('');
|
||||||
const [fax, setFax] = useState('');
|
|
||||||
const [status, setStatus] = useState('active');
|
|
||||||
const [email, setEmail] = useState('');
|
|
||||||
|
|
||||||
|
const redirectTo = () => {
|
||||||
const redirectTo = (id) => {
|
|
||||||
navigate(`/medical/resources/list`);
|
navigate(`/medical/resources/list`);
|
||||||
}
|
}
|
||||||
|
|
||||||
const redirectToView = (id) => {
|
const redirectToView = (id) => {
|
||||||
navigate(`/medical/resources/${id}`);
|
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 = () => {
|
const saveResource = () => {
|
||||||
|
if (!validateResource()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const combinedAddress = `${addressLine1}${addressLine2 ? ', ' + addressLine2 : ''}, ${city}, ${state} ${zipcode}`.trim();
|
||||||
|
|
||||||
const newResource = {
|
const newResource = {
|
||||||
parent_id: '5eee3552b02fac3d4acfd5ea',
|
parent_id: '5eee3552b02fac3d4acfd5ea',
|
||||||
ext_id: '',
|
ext_id: '',
|
||||||
data: dataObject? JSON.parse(dataObject) : {},
|
data: {},
|
||||||
name,
|
|
||||||
description,
|
// Basic Information
|
||||||
note,
|
name, // Provider
|
||||||
name_original: originalName,
|
office_name: officeName,
|
||||||
name_branch: branchName,
|
name_original: officeName, // Legacy field
|
||||||
specialty,
|
specialty,
|
||||||
type,
|
type,
|
||||||
|
|
||||||
|
// Contact Information
|
||||||
|
phone, // Office Phone Number
|
||||||
|
contact, // Secondary Phone Number
|
||||||
|
fax, // Fax Number
|
||||||
email,
|
email,
|
||||||
category,
|
|
||||||
color,
|
// Address (split fields)
|
||||||
address,
|
address_line_1: addressLine1,
|
||||||
state,
|
address_line_2: addressLine2,
|
||||||
city,
|
city,
|
||||||
|
state,
|
||||||
zipcode,
|
zipcode,
|
||||||
contact,
|
address: combinedAddress, // Legacy field
|
||||||
phone,
|
|
||||||
fax,
|
// Additional Information
|
||||||
status,
|
note,
|
||||||
|
|
||||||
|
// System fields
|
||||||
|
status: 'active',
|
||||||
create_by: localStorage.getItem('user') && JSON.parse(localStorage.getItem('user'))?.name,
|
create_by: localStorage.getItem('user') && JSON.parse(localStorage.getItem('user'))?.name,
|
||||||
create_date: new Date(),
|
create_date: new Date(),
|
||||||
edit_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(),
|
edit_date: new Date(),
|
||||||
edit_history:[{ employee: localStorage.getItem('user') && JSON.parse(localStorage.getItem('user'))?.name, date: new Date() }]
|
edit_history: [{ employee: localStorage.getItem('user') && JSON.parse(localStorage.getItem('user'))?.name, date: new Date() }]
|
||||||
}
|
};
|
||||||
|
|
||||||
console.log('new Resource', newResource);
|
|
||||||
|
|
||||||
ResourceService.createNewResource(newResource).then(data => redirectToView(data?.data?.id));
|
ResourceService.createNewResource(newResource).then(data => redirectToView(data?.data?.id));
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!AuthService.canAccessLegacySystem()) {
|
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.')
|
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 (
|
return (
|
||||||
<>
|
<>
|
||||||
<div className="list row mb-4">
|
<div className="list row mb-4">
|
||||||
<Breadcrumb>
|
<Breadcrumb>
|
||||||
<Breadcrumb.Item>Medical</Breadcrumb.Item>
|
<Breadcrumb.Item href="/medical/index">Medical</Breadcrumb.Item>
|
||||||
<Breadcrumb.Item active>
|
<Breadcrumb.Item href="/medical/resources/list">
|
||||||
Provider Information
|
Provider Information
|
||||||
</Breadcrumb.Item>
|
</Breadcrumb.Item>
|
||||||
<Breadcrumb.Item active>
|
<Breadcrumb.Item active>
|
||||||
@ -94,126 +125,103 @@ const CreateResource = () => {
|
|||||||
</Breadcrumb.Item>
|
</Breadcrumb.Item>
|
||||||
</Breadcrumb>
|
</Breadcrumb>
|
||||||
<div className="col-md-12 text-primary">
|
<div className="col-md-12 text-primary">
|
||||||
<h4>Create New Provider <button className="btn btn-link btn-sm" onClick={() => {redirectTo()}}>Back</button></h4>
|
<h4>Create New Provider <button className="btn btn-link btn-sm" onClick={() => {redirectTo()}}>Back</button></h4>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="app-main-content-list-container">
|
<div className="app-main-content-list-container">
|
||||||
<div className="app-main-content-list-func-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">
|
<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="app-main-content-fields-section">
|
||||||
<div className="me-4">
|
<div className="me-4">
|
||||||
<div className="field-label">Doctor Name
|
<div className="field-label">Provider <span className="required">*</span></div>
|
||||||
<span className="required">*</span>
|
<input placeholder="e.g., Dr. Cao Qing" type="text" value={name} onChange={e => setName(e.target.value)}/>
|
||||||
</div>
|
</div>
|
||||||
<input placeholder="e.g.,Cao Qing" type="text" value={name || ''} onChange={e => setName(e.target.value)}/>
|
|
||||||
</div>
|
|
||||||
<div className="me-4">
|
<div className="me-4">
|
||||||
<div className="field-label">Office Name
|
<div className="field-label">Office Name</div>
|
||||||
</div>
|
<input type="text" placeholder="e.g., Silver Spring Family Medicine Clinic" value={officeName} onChange={e => setOfficeName(e.target.value)}/>
|
||||||
<input type="text" placeholder="e.g.,Silver Spring Family Medicine Clinic" value={originalName || ''} onChange={e => setOriginalName(e.target.value)}/>
|
</div>
|
||||||
</div>
|
|
||||||
<div className="me-4">
|
<div className="me-4">
|
||||||
<div className="field-label">Branch Name
|
<div className="field-label">Type</div>
|
||||||
</div>
|
<select value={type} onChange={e => setType(e.target.value)}>
|
||||||
<input type="text" placeholder="e.g.,Silver Spring" value={branchName || ''} onChange={e => setBranchName(e.target.value)}/>
|
<option value="">Select...</option>
|
||||||
</div>
|
{RESOURCE_TYPE_OPTIONS.map((item, index) => (
|
||||||
<div className="me-4">
|
<option key={index} value={item.value}>{item.label}</option>
|
||||||
<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>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
<div className="me-4">
|
<div className="me-4">
|
||||||
<div className="field-label">Type
|
<div className="field-label">Specialty</div>
|
||||||
</div>
|
<select value={specialty} onChange={e => setSpecialty(e.target.value)}>
|
||||||
<select value={type} onChange={e => setType(e.target.value)}>
|
<option value="">Select...</option>
|
||||||
<option value="doctor">Doctor</option>
|
{RESOURCE_SPECIALTY_OPTIONS.map((item, index) => (
|
||||||
<option value="pharmacy">Pharmacy</option>
|
<option key={index} value={item.value}>{item.label}</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>
|
|
||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<h6 className="text-primary">Contact Information</h6>
|
<h6 className="text-primary">Contact Information</h6>
|
||||||
<div className="app-main-content-fields-section">
|
<div className="app-main-content-fields-section">
|
||||||
<div className="me-4">
|
<div className="me-4">
|
||||||
<div className="field-label">Office Phone Number
|
<div className="field-label">Office Phone Number <span className="required">*</span></div>
|
||||||
<span className="required">*</span>
|
<input placeholder="e.g., 240-463-1098" type="text" value={phone} onChange={e => setPhone(e.target.value)}/>
|
||||||
</div>
|
</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="me-4">
|
||||||
<div className="field-label">Contact
|
<div className="field-label">Secondary Phone Number</div>
|
||||||
</div>
|
<input type="text" placeholder="e.g., 240-463-1698" value={contact} onChange={e => setContact(e.target.value)}/>
|
||||||
{/* <textarea value={contact || ''} onChange={e => setContact(e.target.value)}/> */}
|
</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="me-4">
|
||||||
<div className="field-label">Fax Number
|
<div className="field-label">Fax Number</div>
|
||||||
</div>
|
<input type="text" placeholder="e.g., 240-463-1698" value={fax} onChange={e => setFax(e.target.value)}/>
|
||||||
<input type="text" placement="e.g.,240-463-1698" value={fax || ''} onChange={e => setFax(e.target.value)}/>
|
</div>
|
||||||
</div>
|
|
||||||
<div className="me-4">
|
<div className="me-4">
|
||||||
<div className="field-label">Email
|
<div className="field-label">Email</div>
|
||||||
</div>
|
<input type="email" placeholder="e.g., example@gmail.com" value={email} onChange={e => setEmail(e.target.value)}/>
|
||||||
<input type="email" placement="e.g.,example@gmail.com" value={email || ''} onChange={e => setEmail(e.target.value)}/>
|
</div>
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<h6 className="text-primary">Provider Address</h6>
|
<h6 className="text-primary">Provider Address</h6>
|
||||||
<div className="app-main-content-fields-section">
|
<div className="app-main-content-fields-section">
|
||||||
<div className="me-4">
|
<div className="me-4">
|
||||||
<div className="field-label">Address Line 1
|
<div className="field-label">Address Line 1 <span className="required">*</span></div>
|
||||||
<span className="required">*</span>
|
<input type="text" placeholder="e.g., 555 Cloverly Forest Dr" value={addressLine1} onChange={e => setAddressLine1(e.target.value)}/>
|
||||||
</div>
|
</div>
|
||||||
<input type="text" placement="e.g.,555 Cloverly Forest Dr" value={address || ''} onChange={e => setAddress(e.target.value)}/>
|
|
||||||
</div>
|
|
||||||
<div className="me-4">
|
<div className="me-4">
|
||||||
<div className="field-label">City
|
<div className="field-label">Address Line 2</div>
|
||||||
<span className="required">*</span>
|
<input type="text" placeholder="e.g., Suite 200" value={addressLine2} onChange={e => setAddressLine2(e.target.value)}/>
|
||||||
</div>
|
</div>
|
||||||
<input type="text" placement="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>
|
|
||||||
<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>
|
|
||||||
</div>
|
</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 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" placeholder="e.g., 20850" value={zipcode} onChange={e => setZipcode(e.target.value)}/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<h6 className="text-primary">Additional Information</h6>
|
<h6 className="text-primary">Additional Information</h6>
|
||||||
<div className="app-main-content-fields-section">
|
<div className="app-main-content-fields-section">
|
||||||
<div className="me-4">
|
<div className="me-4">
|
||||||
<div className="field-label">Note
|
<div className="field-label">Note</div>
|
||||||
</div>
|
<textarea
|
||||||
<textarea value={note || ''} onChange={e => setNote(e.target.value)}/>
|
placeholder="e.g., Preferred provider for cardiology referrals"
|
||||||
</div>
|
value={note}
|
||||||
</div>
|
onChange={e => setNote(e.target.value)}
|
||||||
<div className="app-main-content-fields-section">
|
rows={4}
|
||||||
<div className="me-4">
|
style={{width: '400px'}}
|
||||||
<div className="field-label">Description
|
/>
|
||||||
</div>
|
</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>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="list row mb-5">
|
<div className="list row mb-5">
|
||||||
<div className="col-md-12 col-sm-12 col-xs-12">
|
<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-default btn-sm float-right" onClick={() => redirectTo()}> Cancel </button>
|
||||||
@ -224,14 +232,6 @@ const CreateResource = () => {
|
|||||||
</Tabs>
|
</Tabs>
|
||||||
</div>
|
</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> */}
|
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@ -22,8 +22,13 @@ const ResourcesList = () => {
|
|||||||
const [currentPage, setCurrentPage] = useState(1);
|
const [currentPage, setCurrentPage] = useState(1);
|
||||||
const [columns, setColumns] = useState([
|
const [columns, setColumns] = useState([
|
||||||
{
|
{
|
||||||
key: 'display_name',
|
key: 'name',
|
||||||
label: 'Name',
|
label: 'Provider',
|
||||||
|
show: true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'office_name',
|
||||||
|
label: 'Office Name',
|
||||||
show: true
|
show: true
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -36,20 +41,20 @@ const ResourcesList = () => {
|
|||||||
label: 'Specialty',
|
label: 'Specialty',
|
||||||
show: true
|
show: true
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
key: 'phone',
|
||||||
|
label: 'Office Phone Number',
|
||||||
|
show: true
|
||||||
|
},
|
||||||
{
|
{
|
||||||
key: 'email',
|
key: 'email',
|
||||||
label: 'Email',
|
label: 'Email',
|
||||||
show: true
|
show: true
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
key: 'address',
|
key: 'display_address',
|
||||||
label: 'Address',
|
label: 'Address',
|
||||||
show: true
|
show: true
|
||||||
},
|
|
||||||
{
|
|
||||||
key: 'display_contact',
|
|
||||||
label: 'Contact',
|
|
||||||
show: true
|
|
||||||
}
|
}
|
||||||
]);
|
]);
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@ -71,7 +76,19 @@ const ResourcesList = () => {
|
|||||||
return a[sorting.key]?.localeCompare(b[sorting.key]);
|
return a[sorting.key]?.localeCompare(b[sorting.key]);
|
||||||
});
|
});
|
||||||
const results = sorting.order === 'asc' ? sortedResources : sortedResources.reverse();
|
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));
|
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]);
|
}, [resources, itemOffset, keyword, itemsPerPage, showDeletedItems, sorting]);
|
||||||
|
|
||||||
@ -194,13 +211,14 @@ const ResourcesList = () => {
|
|||||||
{
|
{
|
||||||
currentItems?.map((resource, index) => <tr key={resource?.id}>
|
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-checkbox"><input type="checkbox" checked={selectedItems.includes(resource.id)} onClick={()=>toggleItem(resource?.id)}/></td>
|
||||||
<td className="td-index">{index + 1}</td>
|
<td className="td-index">{itemOffset + 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 === '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 === 'type')?.show && <td>{resource?.type}</td>}
|
{columns.find(col => col.key === 'office_name')?.show && <td>{resource?.office_name || '-'}</td>}
|
||||||
{columns.find(col => col.key === 'specialty')?.show && <td>{resource?.specialty}</td>}
|
{columns.find(col => col.key === 'type')?.show && <td>{resource?.type || '-'}</td>}
|
||||||
{columns.find(col => col.key === 'email')?.show && <td>{resource?.email}</td>}
|
{columns.find(col => col.key === 'specialty')?.show && <td>{resource?.specialty || '-'}</td>}
|
||||||
{columns.find(col => col.key === 'address')?.show && <td>{resource?.address}</td>}
|
{columns.find(col => col.key === 'phone')?.show && <td>{resource?.phone || '-'}</td>}
|
||||||
{columns.find(col => col.key === 'display_contact')?.show && <td>{resource?.display_contact}</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>)
|
</tr>)
|
||||||
}
|
}
|
||||||
</tbody>
|
</tbody>
|
||||||
@ -249,7 +267,7 @@ const ResourcesList = () => {
|
|||||||
<>
|
<>
|
||||||
<div className="list row mb-4">
|
<div className="list row mb-4">
|
||||||
<Breadcrumb>
|
<Breadcrumb>
|
||||||
<Breadcrumb.Item>Medical</Breadcrumb.Item>
|
<Breadcrumb.Item href="/medical/index">Medical</Breadcrumb.Item>
|
||||||
<Breadcrumb.Item active>
|
<Breadcrumb.Item active>
|
||||||
Provider Information
|
Provider Information
|
||||||
</Breadcrumb.Item>
|
</Breadcrumb.Item>
|
||||||
@ -260,15 +278,15 @@ const ResourcesList = () => {
|
|||||||
</h4>
|
</h4>
|
||||||
</div>
|
</div>
|
||||||
</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">
|
<div className="app-main-content-list-func-container">
|
||||||
<Tabs defaultActiveKey="activeProviders" id="provider-tab" onSelect={(k) => showArchive(k)}>
|
<Tabs defaultActiveKey="activeProviders" id="provider-tab" onSelect={(k) => showArchive(k)}>
|
||||||
<Tab eventKey="activeProviders" title="Active Providers">
|
<Tab eventKey="activeProviders" title="Active Providers">
|
||||||
{table}
|
{table}
|
||||||
</Tab>
|
</Tab>
|
||||||
<Tab eventKey="archivedProviders" title="Archived Providers">
|
{/* <Tab eventKey="archivedProviders" title="Archived Providers">
|
||||||
{table}
|
{table}
|
||||||
</Tab>
|
</Tab> */}
|
||||||
</Tabs>
|
</Tabs>
|
||||||
<div className="list-func-panel">
|
<div className="list-func-panel">
|
||||||
<input className="me-2 with-search-icon" type="text" placeholder="Search" value={keyword} onChange={(e) => setKeyword(e.currentTarget.value)} />
|
<input className="me-2 with-search-icon" type="text" placeholder="Search" value={keyword} onChange={(e) => setKeyword(e.currentTarget.value)} />
|
||||||
|
|||||||
@ -2,75 +2,129 @@ import React, {useState, useEffect} from "react";
|
|||||||
import { useNavigate, useParams } from "react-router-dom";
|
import { useNavigate, useParams } from "react-router-dom";
|
||||||
import { AuthService, ResourceService } from "../../services";
|
import { AuthService, ResourceService } from "../../services";
|
||||||
import { Archive } from "react-bootstrap-icons";
|
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 UpdateResource = () => {
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
const urlParams = useParams();
|
const urlParams = useParams();
|
||||||
const [currentResource, setCurrentResource] = useState(undefined);
|
const [currentResource, setCurrentResource] = useState(undefined);
|
||||||
const [name, setName] = useState('');
|
|
||||||
const [originalName, setOriginalName] = useState('');
|
// Basic Information
|
||||||
const [specialty, setSpecialty] = useState('');
|
const [name, setName] = useState(''); // Provider name
|
||||||
|
const [officeName, setOfficeName] = useState('');
|
||||||
|
const [specialty, setSpecialty] = useState('');
|
||||||
const [type, setType] = useState('');
|
const [type, setType] = useState('');
|
||||||
const [category, setCategory] = useState('');
|
|
||||||
const [description, setDescription] = useState('');
|
// Contact Information
|
||||||
const [color, setColor] = useState('');
|
const [phone, setPhone] = useState(''); // Office Phone Number
|
||||||
const [address, setAddress] = useState('');
|
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 [city, setCity] = useState('');
|
||||||
const [state, setState] = useState('');
|
const [state, setState] = useState('');
|
||||||
const [zipcode, setZipcode] = useState('');
|
const [zipcode, setZipcode] = useState('');
|
||||||
const [contact, setContact] = useState('');
|
|
||||||
const [phone, setPhone] = useState('');
|
// Additional Information
|
||||||
const [dataObject, setDataObject] = useState(undefined);
|
|
||||||
const [note, setNote] = useState('');
|
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 = () => {
|
||||||
|
|
||||||
const redirectTo = (id) => {
|
|
||||||
navigate(`/medical/resources/list`);
|
navigate(`/medical/resources/list`);
|
||||||
}
|
}
|
||||||
|
|
||||||
const redirectToView = () => {
|
const redirectToView = () => {
|
||||||
navigate(`/medical/resources/${urlParams.id}`);
|
navigate(`/medical/resources/${urlParams.id}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
const saveResource = () => {
|
const validateResource = () => {
|
||||||
const newResource = {
|
const errors = [];
|
||||||
...currentResource,
|
|
||||||
data: JSON.parse(dataObject),
|
// Required fields validation
|
||||||
name,
|
if (!name || name.trim() === '') {
|
||||||
description,
|
errors.push('Provider');
|
||||||
note,
|
}
|
||||||
name_original: originalName,
|
if (!phone || phone.trim() === '') {
|
||||||
name_branch: branchName,
|
errors.push('Office Phone Number');
|
||||||
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() }]
|
|
||||||
}
|
}
|
||||||
|
|
||||||
console.log('new Resource', newResource);
|
if (errors.length > 0) {
|
||||||
|
window.alert(`Please fill in the following required fields:\n${errors.join('\n')}`);
|
||||||
ResourceService.updateResource(urlParams.id, newResource).then(data => redirectToView());
|
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(() => {
|
useEffect(() => {
|
||||||
if (!AuthService.canAccessLegacySystem()) {
|
if (!AuthService.canAccessLegacySystem()) {
|
||||||
@ -80,39 +134,41 @@ const UpdateResource = () => {
|
|||||||
}
|
}
|
||||||
ResourceService.getResource(urlParams.id).then(resourceData => {
|
ResourceService.getResource(urlParams.id).then(resourceData => {
|
||||||
setCurrentResource(resourceData.data);
|
setCurrentResource(resourceData.data);
|
||||||
})
|
});
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (currentResource) {
|
if (currentResource) {
|
||||||
setName(currentResource?.name);
|
// Basic Information
|
||||||
setOriginalName(currentResource?.name_original);
|
setName(currentResource?.name || '');
|
||||||
setBranchName(currentResource?.name_branch);
|
setOfficeName(currentResource?.office_name || currentResource?.name_original || '');
|
||||||
setSpecialty(currentResource?.specialty);
|
setSpecialty(currentResource?.specialty || '');
|
||||||
setType(currentResource?.type);
|
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 || {}));
|
|
||||||
}
|
|
||||||
|
|
||||||
|
// 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]);
|
}, [currentResource]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<div className="list row mb-4">
|
<div className="list row mb-4">
|
||||||
<Breadcrumb>
|
<Breadcrumb>
|
||||||
<Breadcrumb.Item>Medical</Breadcrumb.Item>
|
<Breadcrumb.Item href="/medical/index">Medical</Breadcrumb.Item>
|
||||||
<Breadcrumb.Item active>
|
<Breadcrumb.Item href="/medical/resources/list">
|
||||||
Provider Information
|
Provider Information
|
||||||
</Breadcrumb.Item>
|
</Breadcrumb.Item>
|
||||||
<Breadcrumb.Item active>
|
<Breadcrumb.Item active>
|
||||||
@ -125,143 +181,129 @@ const UpdateResource = () => {
|
|||||||
</div>
|
</div>
|
||||||
<div className="app-main-content-list-container">
|
<div className="app-main-content-list-container">
|
||||||
<div className="app-main-content-list-func-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">
|
<Tab eventKey="providerInfo" title="Provider Information">
|
||||||
<h6 className="text-primary">Basic Information</h6>
|
<h6 className="text-primary">Basic Information</h6>
|
||||||
<div className="app-main-content-fields-section">
|
<div className="app-main-content-fields-section">
|
||||||
<div className="me-4">
|
<div className="me-4">
|
||||||
<div className="field-label">Doctor Name
|
<div className="field-label">Provider <span className="required">*</span></div>
|
||||||
<span className="required">*</span>
|
<input placeholder="e.g., Dr. Cao Qing" type="text" value={name} onChange={e => setName(e.target.value)}/>
|
||||||
</div>
|
|
||||||
<input placeholder="e.g.,Cao Qing" type="text" value={name || ''} onChange={e => setName(e.target.value)}/>
|
|
||||||
</div>
|
</div>
|
||||||
<div className="me-4">
|
<div className="me-4">
|
||||||
<div className="field-label">Office Name
|
<div className="field-label">Office Name</div>
|
||||||
</div>
|
<input type="text" placeholder="e.g., Silver Spring Family Medicine Clinic" value={officeName} onChange={e => setOfficeName(e.target.value)}/>
|
||||||
<input type="text" placeholder="e.g.,Silver Spring Family Medicine Clinic" value={originalName || ''} onChange={e => setOriginalName(e.target.value)}/>
|
|
||||||
</div>
|
</div>
|
||||||
<div className="me-4">
|
<div className="me-4">
|
||||||
<div className="field-label">Branch Name
|
<div className="field-label">Type</div>
|
||||||
</div>
|
<select value={type} onChange={e => setType(e.target.value)}>
|
||||||
<input type="text" placeholder="e.g.,Silver Spring" value={branchName || ''} onChange={e => setBranchName(e.target.value)}/>
|
<option value="">Select...</option>
|
||||||
</div>
|
{RESOURCE_TYPE_OPTIONS.map((item, index) => (
|
||||||
<div className="me-4">
|
<option key={index} value={item.value}>{item.label}</option>
|
||||||
<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>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
<div className="me-4">
|
<div className="me-4">
|
||||||
<div className="field-label">Type
|
<div className="field-label">Specialty</div>
|
||||||
</div>
|
<select value={specialty} onChange={e => setSpecialty(e.target.value)}>
|
||||||
<select value={type} onChange={e => setType(e.target.value)}>
|
<option value="">Select...</option>
|
||||||
<option value="doctor">Doctor</option>
|
{RESOURCE_SPECIALTY_OPTIONS.map((item, index) => (
|
||||||
<option value="pharmacy">Pharmacy</option>
|
<option key={index} value={item.value}>{item.label}</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>
|
|
||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<h6 className="text-primary">Contact Information</h6>
|
<h6 className="text-primary">Contact Information</h6>
|
||||||
<div className="app-main-content-fields-section">
|
<div className="app-main-content-fields-section">
|
||||||
<div className="me-4">
|
<div className="me-4">
|
||||||
<div className="field-label">Office Phone Number
|
<div className="field-label">Office Phone Number <span className="required">*</span></div>
|
||||||
<span className="required">*</span>
|
<input placeholder="e.g., 240-463-1098" type="text" value={phone} onChange={e => setPhone(e.target.value)}/>
|
||||||
</div>
|
|
||||||
<input placeholder="e.g.,240-463-1098" type="text" value={phone || ''} onChange={e => setPhone(e.target.value)}/>
|
|
||||||
</div>
|
</div>
|
||||||
<div className="me-4">
|
<div className="me-4">
|
||||||
<div className="field-label">Contact
|
<div className="field-label">Secondary Phone Number</div>
|
||||||
</div>
|
<input type="text" placeholder="e.g., 240-463-1698" value={contact} onChange={e => setContact(e.target.value)}/>
|
||||||
{/* <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>
|
</div>
|
||||||
<div className="me-4">
|
<div className="me-4">
|
||||||
<div className="field-label">Fax Number
|
<div className="field-label">Fax Number</div>
|
||||||
</div>
|
<input type="text" placeholder="e.g., 240-463-1698" value={fax} onChange={e => setFax(e.target.value)}/>
|
||||||
<input type="text" placement="e.g.,240-463-1698" value={fax || ''} onChange={e => setFax(e.target.value)}/>
|
|
||||||
</div>
|
</div>
|
||||||
<div className="me-4">
|
<div className="me-4">
|
||||||
<div className="field-label">Email
|
<div className="field-label">Email</div>
|
||||||
</div>
|
<input type="email" placeholder="e.g., example@gmail.com" value={email} onChange={e => setEmail(e.target.value)}/>
|
||||||
<input type="email" placement="e.g.,example@gmail.com" value={email || ''} onChange={e => setEmail(e.target.value)}/>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<h6 className="text-primary">Provider Address</h6>
|
<h6 className="text-primary">Provider Address</h6>
|
||||||
<div className="app-main-content-fields-section">
|
<div className="app-main-content-fields-section">
|
||||||
<div className="me-4">
|
<div className="me-4">
|
||||||
<div className="field-label">Address Line 1
|
<div className="field-label">Address Line 1 <span className="required">*</span></div>
|
||||||
<span className="required">*</span>
|
<input type="text" placeholder="e.g., 555 Cloverly Forest Dr" value={addressLine1} onChange={e => setAddressLine1(e.target.value)}/>
|
||||||
</div>
|
|
||||||
<input type="text" placement="e.g.,555 Cloverly Forest Dr" value={address || ''} onChange={e => setAddress(e.target.value)}/>
|
|
||||||
</div>
|
</div>
|
||||||
<div className="me-4">
|
<div className="me-4">
|
||||||
<div className="field-label">City
|
<div className="field-label">Address Line 2</div>
|
||||||
<span className="required">*</span>
|
<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="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>
|
|
||||||
<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>
|
</div>
|
||||||
</div>
|
</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 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" placeholder="e.g., 20850" value={zipcode} onChange={e => setZipcode(e.target.value)}/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<h6 className="text-primary">Additional Information</h6>
|
<h6 className="text-primary">Additional Information</h6>
|
||||||
<div className="app-main-content-fields-section">
|
<div className="app-main-content-fields-section">
|
||||||
<div className="me-4">
|
<div className="me-4">
|
||||||
<div className="field-label">Note
|
<div className="field-label">Note</div>
|
||||||
</div>
|
<textarea
|
||||||
<textarea value={note || ''} onChange={e => setNote(e.target.value)}/>
|
placeholder="e.g., Preferred provider for cardiology referrals"
|
||||||
</div>
|
value={note}
|
||||||
</div>
|
onChange={e => setNote(e.target.value)}
|
||||||
<div className="app-main-content-fields-section">
|
rows={4}
|
||||||
<div className="me-4">
|
style={{width: '400px'}}
|
||||||
<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>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="list row mb-5">
|
<div className="list row mb-5">
|
||||||
<div className="col-md-12 col-sm-12 col-xs-12">
|
<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-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>
|
<button className="btn btn-primary btn-sm float-right" onClick={() => saveResource()}> Save </button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</Tab>
|
</Tab>
|
||||||
</Tabs>
|
</Tabs>
|
||||||
<div className="list-func-panel">
|
<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>
|
||||||
</div>
|
</div>
|
||||||
|
<Modal show={showDeleteModal} onHide={() => closeDeleteModal()}>
|
||||||
{/* <div className="col-md-4 mb-4">
|
<Modal.Header closeButton>
|
||||||
<div>Status:</div>
|
<Modal.Title>Delete Provider</Modal.Title>
|
||||||
<select value={status} onChange={e => setStatus(e.target.value)}>
|
</Modal.Header>
|
||||||
<option value="active">Active</option>
|
<Modal.Body>
|
||||||
<option value="inactive">Inactive</option>
|
<div>Are you sure you want to delete this provider?</div>
|
||||||
</select>
|
</Modal.Body>
|
||||||
</div> */}
|
<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 React, {useState, useEffect} from "react";
|
||||||
// import { useDispatch } from "react-redux";
|
|
||||||
import { useNavigate, useParams } from "react-router-dom";
|
import { useNavigate, useParams } from "react-router-dom";
|
||||||
// import { customerSlice } from "./../../store";
|
|
||||||
import { AuthService, ResourceService } from "../../services";
|
import { AuthService, ResourceService } from "../../services";
|
||||||
import { Spinner, Breadcrumb, BreadcrumbItem, Tabs, Tab } from "react-bootstrap";
|
import { Spinner, Breadcrumb, BreadcrumbItem, Tabs, Tab } from "react-bootstrap";
|
||||||
import { Download, Pencil, Archive } from "react-bootstrap-icons";
|
import { Download, Pencil, Archive } from "react-bootstrap-icons";
|
||||||
|
import { RESOURCE_TYPE_TEXT } from "../../shared/constants";
|
||||||
|
|
||||||
const ViewResource = () => {
|
const ViewResource = () => {
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
const urlParams = useParams();
|
const urlParams = useParams();
|
||||||
const [currentResource, setCurrentResource] = useState(undefined);
|
const [currentResource, setCurrentResource] = useState(undefined);
|
||||||
|
|
||||||
const params = new URLSearchParams(window.location.search);
|
|
||||||
|
|
||||||
const redirectTo = () => {
|
const redirectTo = () => {
|
||||||
navigate(`/medical/resources/list`);
|
navigate(`/medical/resources/list`);
|
||||||
}
|
}
|
||||||
|
|
||||||
const goToEdit = (id) => {
|
const goToEdit = (id) => {
|
||||||
navigate(`/medical/resources/edit/${id}`)
|
navigate(`/medical/resources/edit/${id}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
const deactivateResource = (id) => {
|
const deactivateResource = (id) => {
|
||||||
const data = {
|
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(() => {
|
ResourceService.disableResource(id, data).then(() => {
|
||||||
redirectTo();
|
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(() => {
|
useEffect(() => {
|
||||||
@ -38,15 +56,15 @@ const ViewResource = () => {
|
|||||||
}
|
}
|
||||||
ResourceService.getResource(urlParams.id).then(resourceData => {
|
ResourceService.getResource(urlParams.id).then(resourceData => {
|
||||||
setCurrentResource(resourceData.data);
|
setCurrentResource(resourceData.data);
|
||||||
})
|
});
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<div className="list row mb-4">
|
<div className="list row mb-4">
|
||||||
<Breadcrumb>
|
<Breadcrumb>
|
||||||
<Breadcrumb.Item>Medical</Breadcrumb.Item>
|
<Breadcrumb.Item href="/medical/index">Medical</Breadcrumb.Item>
|
||||||
<Breadcrumb.Item active>
|
<Breadcrumb.Item href="/medical/resources/list">
|
||||||
Provider Information
|
Provider Information
|
||||||
</Breadcrumb.Item>
|
</Breadcrumb.Item>
|
||||||
<Breadcrumb.Item active>
|
<Breadcrumb.Item active>
|
||||||
@ -54,83 +72,89 @@ const ViewResource = () => {
|
|||||||
</Breadcrumb.Item>
|
</Breadcrumb.Item>
|
||||||
</Breadcrumb>
|
</Breadcrumb>
|
||||||
<div className="col-md-12 text-primary">
|
<div className="col-md-12 text-primary">
|
||||||
<h4>View Provider Information <button className="btn btn-link btn-sm" onClick={() => {redirectTo()}}>Back</button></h4>
|
<h4>View Provider Information <button className="btn btn-link btn-sm" onClick={() => {redirectTo()}}>Back</button></h4>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="app-main-content-list-container">
|
<div className="app-main-content-list-container">
|
||||||
<div className="app-main-content-list-func-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">
|
<Tab eventKey="providerInfo" title="Provider Information">
|
||||||
<h6 className="text-primary">Basic Information</h6>
|
<h6 className="text-primary">Basic Information</h6>
|
||||||
<div className="app-main-content-fields-section">
|
<div className="app-main-content-fields-section">
|
||||||
<div className="field-body">
|
<div className="field-body">
|
||||||
<div className="field-label">Name</div>
|
<div className="field-label">Provider</div>
|
||||||
<div className="field-value">{currentResource?.name}</div>
|
<div className="field-value">{currentResource?.name || '-'}</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="field-body">
|
<div className="field-body">
|
||||||
<div className="field-label">Office Name</div>
|
<div className="field-label">Office Name</div>
|
||||||
<div className="field-value">{currentResource?.name_original}</div>
|
<div className="field-value">{currentResource?.office_name || 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>
|
</div>
|
||||||
<div className="field-body">
|
<div className="field-body">
|
||||||
<div className="field-label">Specialty</div>
|
<div className="field-label">Specialty</div>
|
||||||
<div className="field-value">{currentResource?.specialty}</div>
|
<div className="field-value">{currentResource?.specialty || '-'}</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="field-body">
|
<div className="field-body">
|
||||||
<div className="field-label">Type</div>
|
<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>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<h6 className="text-primary">Contact Information</h6>
|
<h6 className="text-primary">Contact Information</h6>
|
||||||
<div className="app-main-content-fields-section">
|
<div className="app-main-content-fields-section">
|
||||||
<div className="field-body">
|
<div className="field-body">
|
||||||
<div className="field-label">Office Phone Number</div>
|
<div className="field-label">Office Phone Number</div>
|
||||||
<div className="field-value">{currentResource?.phone}</div>
|
<div className="field-value">{currentResource?.phone || '-'}</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="field-body">
|
<div className="field-body">
|
||||||
<div className="field-label">Contact</div>
|
<div className="field-label">Secondary Phone Number</div>
|
||||||
<div className="field-value">{currentResource?.contact}</div>
|
<div className="field-value">{currentResource?.contact || '-'}</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="field-body">
|
<div className="field-body">
|
||||||
<div className="field-label">Fax Number</div>
|
<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>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<h6 className="text-primary">Provider Address</h6>
|
<h6 className="text-primary">Provider Address</h6>
|
||||||
<div className="app-main-content-fields-section">
|
<div className="app-main-content-fields-section">
|
||||||
<div className="field-body">
|
<div className="field-body">
|
||||||
<div className="field-label">Address</div>
|
<div className="field-label">Address Line 1</div>
|
||||||
<div className="field-value">{`${currentResource?.address}, ${currentResource?.city}, ${currentResource?.state}, ${currentResource?.zipcode}`}</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>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<h6 className="text-primary">Additional Information</h6>
|
<h6 className="text-primary">Additional Information</h6>
|
||||||
<div className="app-main-content-fields-section">
|
<div className="app-main-content-fields-section">
|
||||||
<div className="field-body">
|
<div className="field-body">
|
||||||
<div className="field-label">Note</div>
|
<div className="field-label">Note</div>
|
||||||
<div className="field-value">{currentResource?.note}</div>
|
<div className="field-value">{currentResource?.note || currentResource?.description || '-'}</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>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</Tab>
|
</Tab>
|
||||||
</Tabs>
|
</Tabs>
|
||||||
<div className="list-func-panel">
|
<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={() => 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 me-2" onClick={() => deactivateResource(currentResource?.id)}><Archive size={16} className="me-2"></Archive>Archive</button>
|
||||||
{/* <button className="btn btn-primary"><Download size={16} className="me-2"></Download>Download</button> */}
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
|
||||||
const CircularTable = ({tableNumber, guests = []}) => {
|
const CircularTable = ({tableNumber, guests = [], inCenterCustomerIds = [], onSeatClick}) => {
|
||||||
const getPositions = () => {
|
const getPositions = () => {
|
||||||
const positions = [];
|
const positions = [];
|
||||||
const seatCount = 8;
|
const seatCount = 8;
|
||||||
@ -18,6 +18,12 @@ const CircularTable = ({tableNumber, guests = []}) => {
|
|||||||
return positions;
|
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 seatPositions = getPositions();
|
||||||
// const defaultNames = ['Guest 1', 'Guest 2', 'Guest 3', 'Guest 4', 'Guest 5', 'Guest 6', 'Guest 7', 'Guest 8' ];
|
// 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'];
|
const defaultSeatNumber = ['A', 'B', 'C', 'D', 'E', 'F', 'G', 'H'];
|
||||||
@ -32,10 +38,31 @@ const CircularTable = ({tableNumber, guests = []}) => {
|
|||||||
<div className="table-number">{tableNumber}</div>
|
<div className="table-number">{tableNumber}</div>
|
||||||
|
|
||||||
{seatPositions.map((pos, index) => {
|
{seatPositions.map((pos, index) => {
|
||||||
return (<div className="seat-container" key={index} style={{transform: `translate(${pos.x}px, ${pos.y}px)`}}>
|
const guest = guests[index];
|
||||||
<div className="seat-circle" style={guests[index]?.label?.label_color && { background: guests[index]?.label?.label_color}}>{defaultSeatNumber[index]}</div>
|
const isInCenter = isCustomerInCenter(guest?.customerId);
|
||||||
<div className="guest-name">{guests[index]?.customerName}</div>
|
const hasCustomer = guest?.customerId && guest?.customerName;
|
||||||
</div>)
|
|
||||||
|
// 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 ${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>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@ -29,8 +29,10 @@ const BreakfastSection = ({transRoutes, breakfastRecords, sectionName, confimHas
|
|||||||
}
|
}
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const routeCustomers = TransRoutesService.getAllInCenterCustomersFromRoutes(transRoutes, breakfastRecords);
|
if (transRoutes && transRoutes.length > 0) {
|
||||||
setCustomers(routeCustomers);
|
const routeCustomers = TransRoutesService.getAllInCenterCustomersFromRoutes(transRoutes, breakfastRecords || []);
|
||||||
|
setCustomers(routeCustomers);
|
||||||
|
}
|
||||||
}, [breakfastRecords, transRoutes]);
|
}, [breakfastRecords, transRoutes]);
|
||||||
|
|
||||||
|
|
||||||
@ -51,20 +53,20 @@ const BreakfastSection = ({transRoutes, breakfastRecords, sectionName, confimHas
|
|||||||
<th>Change Breakfast Status</th>
|
<th>Change Breakfast Status</th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
{
|
{
|
||||||
breakfastRecords?.length >0 && customers?.map((customer) => (
|
customers?.map((customer) => (
|
||||||
<tr className={customer?.has_breakfast ? 'light-green' : 'red'}>
|
<tr key={customer?.customer_id} className={customer?.has_breakfast ? 'light-green' : 'red'}>
|
||||||
<td>{customer?.customer_name}</td>
|
<td>{customer?.customer_name}</td>
|
||||||
<td>{customer?.has_breakfast ? 'Yes': 'No'}</td>
|
<td>{customer?.has_breakfast ? 'Yes': 'No'}</td>
|
||||||
<td>
|
<td>
|
||||||
{!customer?.has_breakfast && <button className="btn btn-link btn-sm" onClick={() => confimHasBreakfast(customer)}>Confirm Customer Has breakfast</button>}
|
{!customer?.has_breakfast && <button className="btn btn-link btn-sm" onClick={() => confimHasBreakfast(customer)}>Confirm Customer Has breakfast</button>}
|
||||||
{customer?.has_breakfast && <button className="btn btn-link btn-sm" onClick={() => removeBreakfastRecord(customer?.customer_id)}>Mark Customer NOT have breakfast</button>}
|
{customer?.has_breakfast && <button className="btn btn-link btn-sm" onClick={() => removeBreakfastRecord(customer?.customer_id)}>Mark Customer NOT have breakfast</button>}
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@ -79,13 +79,36 @@ const CreateRoute = () => {
|
|||||||
navigate(`/trans-routes/templates${params.get('type')?`?type=${params.get('type')}`: ''}${params.get('date')? `&date=${params.get('date')}` : ''}`);
|
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 = () => {
|
const saveRoute = () => {
|
||||||
if (!disableSave) {
|
if (!disableSave) {
|
||||||
|
if (!validateRoute()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
setDisableSave(true);
|
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();
|
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();
|
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') {
|
if (params.get('date') === 'tomorrow') {
|
||||||
@ -175,10 +198,13 @@ const CreateRoute = () => {
|
|||||||
<>
|
<>
|
||||||
<div className="list row mb-4">
|
<div className="list row mb-4">
|
||||||
<Breadcrumb>
|
<Breadcrumb>
|
||||||
<Breadcrumb.Item>Transportation</Breadcrumb.Item>
|
<Breadcrumb.Item href="/">Transportation</Breadcrumb.Item>
|
||||||
<Breadcrumb.Item active>
|
<Breadcrumb.Item href="/trans-routes/dashboard">
|
||||||
Transportation Routes
|
Transportation Routes
|
||||||
</Breadcrumb.Item>
|
</Breadcrumb.Item>
|
||||||
|
<Breadcrumb.Item active>
|
||||||
|
Create New Route
|
||||||
|
</Breadcrumb.Item>
|
||||||
</Breadcrumb>
|
</Breadcrumb>
|
||||||
<div className="col-md-12 text-primary">
|
<div className="col-md-12 text-primary">
|
||||||
<h4>
|
<h4>
|
||||||
@ -187,7 +213,7 @@ const CreateRoute = () => {
|
|||||||
</div>
|
</div>
|
||||||
</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">
|
<div className="app-main-content-list-func-container">
|
||||||
<Tabs defaultActiveKey="routeOverview" id="route-view-tab">
|
<Tabs defaultActiveKey="routeOverview" id="route-view-tab">
|
||||||
<Tab eventKey="routeOverview" title="Route Information">
|
<Tab eventKey="routeOverview" title="Route Information">
|
||||||
|
|||||||
@ -29,8 +29,10 @@ const LunchSection = ({transRoutes, lunchRecords, sectionName, confirmHasLunch,
|
|||||||
}
|
}
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const routeCustomers = TransRoutesService.getAllInCenterCustomersFromRoutesForLunch(transRoutes, lunchRecords);
|
if (transRoutes && transRoutes.length > 0) {
|
||||||
setCustomers(routeCustomers);
|
const routeCustomers = TransRoutesService.getAllInCenterCustomersFromRoutesForLunch(transRoutes, lunchRecords || []);
|
||||||
|
setCustomers(routeCustomers);
|
||||||
|
}
|
||||||
}, [lunchRecords, transRoutes]);
|
}, [lunchRecords, transRoutes]);
|
||||||
|
|
||||||
|
|
||||||
@ -51,20 +53,20 @@ const LunchSection = ({transRoutes, lunchRecords, sectionName, confirmHasLunch,
|
|||||||
<th>Change Lunch Status</th>
|
<th>Change Lunch Status</th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
{
|
{
|
||||||
lunchRecords?.length >0 && customers?.map((customer) => (
|
customers?.map((customer) => (
|
||||||
<tr className={customer?.has_lunch ? 'light-green' : 'red'}>
|
<tr key={customer?.customer_id} className={customer?.has_lunch ? 'light-green' : 'red'}>
|
||||||
<td>{customer?.customer_name}</td>
|
<td>{customer?.customer_name}</td>
|
||||||
<td>{customer?.has_lunch ? 'Yes': 'No'}</td>
|
<td>{customer?.has_lunch ? 'Yes': 'No'}</td>
|
||||||
<td>
|
<td>
|
||||||
{!customer?.has_lunch && <button className="btn btn-link btn-sm" onClick={() => confirmHasLunch(customer)}>Confirm Customer Has Lunch</button>}
|
{!customer?.has_lunch && <button className="btn btn-link btn-sm" onClick={() => confirmHasLunch(customer)}>Confirm Customer Has Lunch</button>}
|
||||||
{customer?.has_lunch && <button className="btn btn-link btn-sm" onClick={() => removeLunchRecord(customer?.customer_id)}>Mark Customer NOT have lunch</button>}
|
{customer?.has_lunch && <button className="btn btn-link btn-sm" onClick={() => removeLunchRecord(customer?.customer_id)}>Mark Customer NOT have lunch</button>}
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@ -494,29 +494,97 @@ const PersonnelInfoTable = ({transRoutes, showCompletedInfo,
|
|||||||
|
|
||||||
const saveBulkUpdate = () => {
|
const saveBulkUpdate = () => {
|
||||||
const routeId = transRoutes[0]?.id;
|
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) {
|
if (routeId) {
|
||||||
let requestBody = transRoutes.find((route) => route.id === routeId);
|
let requestBody = transRoutes.find((route) => route.id === routeId);
|
||||||
const dateStr = requestBody?.schedule_date || '';
|
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) => {
|
const updatedCustomerList = requestBody.route_customer_list.map((item) => {
|
||||||
// Skip customers who are Unscheduled Absent or Scheduled Absent
|
// Skip customers who are Unscheduled Absent or Scheduled Absent
|
||||||
if (item.customer_pickup_status === PICKUP_STATUS.UNSCHEDULE_ABSENT ||
|
// Only skip if status is explicitly set to one of these values (not undefined)
|
||||||
item.customer_pickup_status === PICKUP_STATUS.SCHEDULE_ABSENT) {
|
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;
|
return item;
|
||||||
}
|
}
|
||||||
|
|
||||||
let updatedItem = { ...item };
|
let updatedItem = { ...item };
|
||||||
|
|
||||||
if (bulkEnterCenterTime && bulkEnterCenterTime !== '') {
|
// Debug logging for each customer
|
||||||
updatedItem.customer_enter_center_time = combineDateAndTime(dateStr, bulkEnterCenterTime);
|
console.log(`Processing customer ${item.customer_id} (${item.customer_name})`);
|
||||||
updatedItem.customer_route_status = PERSONAL_ROUTE_STATUS.IN_CENTER;
|
|
||||||
|
// 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 !== '') {
|
// Check if bulkLeaveCenterTime is a valid time string (HH:mm format)
|
||||||
updatedItem.customer_leave_center_time = combineDateAndTime(dateStr, bulkLeaveCenterTime);
|
console.log('Checking bulkLeaveCenterTime:', {
|
||||||
updatedItem.customer_route_status = PERSONAL_ROUTE_STATUS.LEFT_CENTER;
|
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 !== '') {
|
if (bulkCustomerRouteStatus && bulkCustomerRouteStatus !== '') {
|
||||||
updatedItem.customer_route_status = 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'});
|
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 };
|
let finalParams = { id: routeId, data: requestBody };
|
||||||
if (scheduleDate) {
|
if (scheduleDate) {
|
||||||
finalParams = Object.assign({}, finalParams, {dateText: moment(scheduleDate).format('MM/DD/YYYY'), fromSchedule: true})
|
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')) {
|
if (dateStr !== '' && dateStr !== moment().format('MM/DD/YYYY')) {
|
||||||
finalParams = Object.assign({}, finalParams, {dateText: dateStr})
|
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));
|
dispatch(updateRoute(finalParams));
|
||||||
} else {
|
} else {
|
||||||
window.alert('Fail to update Route: no route Id found.')
|
window.alert('Fail to update Route: no route Id found.')
|
||||||
@ -625,8 +728,8 @@ const PersonnelInfoTable = ({transRoutes, showCompletedInfo,
|
|||||||
<th>Type</th>
|
<th>Type</th>
|
||||||
{!showCompletedInfo &&<th>Route Type</th>}
|
{!showCompletedInfo &&<th>Route Type</th>}
|
||||||
<th>Pick Up Time</th>
|
<th>Pick Up Time</th>
|
||||||
{!showCompletedInfo && (<th>Enter Center Time</th>)}
|
<th>Enter Center Time</th>
|
||||||
{!showCompletedInfo && (<th>Leave Center Time</th>)}
|
<th>Leave Center Time</th>
|
||||||
<th>Drop Off Time</th>
|
<th>Drop Off Time</th>
|
||||||
{showCompletedInfo && (<th>Schedule Absent</th>)}
|
{showCompletedInfo && (<th>Schedule Absent</th>)}
|
||||||
{showCompletedInfo && (<th>Schedule Absent Note</th>)}
|
{showCompletedInfo && (<th>Schedule Absent Note</th>)}
|
||||||
@ -697,8 +800,8 @@ const PersonnelInfoTable = ({transRoutes, showCompletedInfo,
|
|||||||
{ customer.routeType}
|
{ customer.routeType}
|
||||||
</td>}
|
</td>}
|
||||||
<td>{customer.customer_pickup_time && new Date(customer.customer_pickup_time).toLocaleTimeString('en-US', {hour12: false, hour: '2-digit', minute: '2-digit'})}</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>}
|
<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_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>
|
<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>
|
{showCompletedInfo && (<td>
|
||||||
{ customer.customer_pickup_status === PICKUP_STATUS.SCHEDULE_ABSENT ? 'Yes' : "No" }
|
{ customer.customer_pickup_status === PICKUP_STATUS.SCHEDULE_ABSENT ? 'Yes' : "No" }
|
||||||
@ -749,8 +852,8 @@ const PersonnelInfoTable = ({transRoutes, showCompletedInfo,
|
|||||||
{ customerItem.routeType}
|
{ customerItem.routeType}
|
||||||
</td>}
|
</td>}
|
||||||
<td>{customerItem.customer_pickup_time && new Date(customerItem.customer_pickup_time).toLocaleTimeString('en-US', {hour12: false})}</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>}
|
<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_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>
|
<td>{customerItem.customer_dropoff_time && new Date(customerItem.customer_dropoff_time).toLocaleTimeString('en-US', {hour12: false})}</td>
|
||||||
{showCompletedInfo && (<td>
|
{showCompletedInfo && (<td>
|
||||||
{ customerItem.customer_pickup_status === PICKUP_STATUS.SCHEDULE_ABSENT ? 'Yes' : "No" }
|
{ customerItem.customer_pickup_status === PICKUP_STATUS.SCHEDULE_ABSENT ? 'Yes' : "No" }
|
||||||
@ -806,8 +909,8 @@ const PersonnelInfoTable = ({transRoutes, showCompletedInfo,
|
|||||||
{ customer.routeType}
|
{ customer.routeType}
|
||||||
</td>}
|
</td>}
|
||||||
<td>{customer.customer_pickup_time && new Date(customer.customer_pickup_time).toLocaleTimeString('en-US', {hour12: false})}</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>}
|
<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_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>
|
<td>{customer.customer_dropoff_time && new Date(customer.customer_dropoff_time).toLocaleTimeString('en-US', {hour12: false})}</td>
|
||||||
{showCompletedInfo && (<td>
|
{showCompletedInfo && (<td>
|
||||||
{ customer.customer_pickup_status === PICKUP_STATUS.SCHEDULE_ABSENT ? 'Yes' : "No" }
|
{ 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 { useNavigate} from "react-router-dom";
|
||||||
import { ROUTE_STATUS, PICKUP_STATUS } from "./../../shared";
|
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 navigate = useNavigate();
|
||||||
const params = new URLSearchParams(window.location.search);
|
const params = new URLSearchParams(window.location.search);
|
||||||
const scheduleDate = params.get('dateSchedule');
|
const scheduleDate = params.get('dateSchedule');
|
||||||
const handleOnClick = () => {
|
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}`);
|
navigate(`/trans-routes/${transRoute.id}?dateSchedule=${scheduleDate}`);
|
||||||
} else {
|
} else {
|
||||||
navigate(`/trans-routes/${transRoute.id}`);
|
navigate(`/trans-routes/${transRoute.id}`);
|
||||||
@ -72,22 +75,24 @@ const RouteCard = ({transRoute, drivers, vehicles, driver, vehicle, routeIndex})
|
|||||||
return routeStatusData;
|
return routeStatusData;
|
||||||
}
|
}
|
||||||
|
|
||||||
const routeStatus = getRouteStatus(transRoute);
|
const routeStatus = isTemplate ? { text: '', className: '' } : getRouteStatus(transRoute);
|
||||||
|
|
||||||
return (
|
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 className="route-card-title">{`#${routeIndex+1} `}{transRoute.name}</div>
|
||||||
<div>{drivers.find((driver) => driver.id === transRoute.driver)?.name}</div>
|
<div>{drivers.find((driver) => driver.id === transRoute.driver)?.name}</div>
|
||||||
<div>{`${transRoute?.route_customer_list?.length} Participants`}</div>
|
<div>{`${transRoute?.route_customer_list?.length} Participants`}</div>
|
||||||
<div>{vehicles.find((vehicle) => vehicle.id === transRoute.vehicle)?.tag}</div>
|
<div>{vehicles.find((vehicle) => vehicle.id === transRoute.vehicle)?.tag}</div>
|
||||||
<div className="list row">
|
{!isTemplate && (
|
||||||
<div className="col-md-12 card-status">
|
<div className="list row">
|
||||||
<div className="float-end">{routeStatus?.text}</div>
|
<div className="col-md-12 card-status">
|
||||||
<div className={`${routeStatus.className==='purple' ? '' : routeStatus.className} float-end`} />
|
<div className="float-end">{routeStatus?.text}</div>
|
||||||
|
<div className={`${routeStatus.className==='purple' ? '' : routeStatus.className} float-end`} />
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
)}
|
||||||
</div>)
|
</div>)
|
||||||
}
|
}
|
||||||
{
|
{
|
||||||
|
|||||||
@ -11,6 +11,7 @@ import { GripVertical, Pencil, RecordCircleFill, XSquare } from 'react-bootstrap
|
|||||||
|
|
||||||
const ItemTypes = {
|
const ItemTypes = {
|
||||||
CARD: 'card',
|
CARD: 'card',
|
||||||
|
UNASSIGNED_CUSTOMER: 'unassigned_customer',
|
||||||
};
|
};
|
||||||
|
|
||||||
const Card = ({ content, index, moveCard }) => {
|
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 [customers, setCustomers] = useState([]);
|
||||||
|
const [initializedRouteId, setInitializedRouteId] = useState(null);
|
||||||
const [showAddPersonnelModal, setShowAddPersonnelModal] = useState(false);
|
const [showAddPersonnelModal, setShowAddPersonnelModal] = useState(false);
|
||||||
const [showAddAptGroupModal, setShowAddAptGroupModal] = useState(false);
|
const [showAddAptGroupModal, setShowAddAptGroupModal] = useState(false);
|
||||||
const [showEditAptGroupModal, setShowEditAptGroupModal] = useState(false);
|
const [showEditAptGroupModal, setShowEditAptGroupModal] = useState(false);
|
||||||
@ -101,16 +103,38 @@ const RouteCustomerEditor = ({currentRoute, setNewCustomerList = (a) => {}, view
|
|||||||
const [pageCount, setPageCount] = useState(0);
|
const [pageCount, setPageCount] = useState(0);
|
||||||
const itemsPerPage = 10;
|
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(() => {
|
useEffect(() => {
|
||||||
// Fetch items from another resources.
|
// Fetch items from another resources.
|
||||||
const endOffset = itemOffset + itemsPerPage;
|
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));
|
const assignedIds = getAssignedCustomerIds();
|
||||||
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));
|
// Filter out customers already in the route
|
||||||
}, [customerOptions, itemOffset, customerFilter, lastNameFilter]);
|
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 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(
|
console.log(
|
||||||
`User requested page number ${event.selected}, which is offset ${newOffset}`
|
`User requested page number ${event.selected}, which is offset ${newOffset}`
|
||||||
);
|
);
|
||||||
@ -129,7 +153,14 @@ const RouteCustomerEditor = ({currentRoute, setNewCustomerList = (a) => {}, view
|
|||||||
setLastNameFilter(undefined);
|
setLastNameFilter(undefined);
|
||||||
if (customerOptions.length === 0) {
|
if (customerOptions.length === 0) {
|
||||||
CustomerService.getAllActiveCustomers().then((data) => {
|
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);
|
setShowAddPersonnelModal(true);
|
||||||
@ -146,7 +177,14 @@ const RouteCustomerEditor = ({currentRoute, setNewCustomerList = (a) => {}, view
|
|||||||
setLastNameFilter(undefined);
|
setLastNameFilter(undefined);
|
||||||
if (customerOptions.length === 0) {
|
if (customerOptions.length === 0) {
|
||||||
CustomerService.getAllActiveCustomers().then((data) => {
|
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);
|
setShowAddAptGroupModal(true);
|
||||||
@ -167,7 +205,14 @@ const RouteCustomerEditor = ({currentRoute, setNewCustomerList = (a) => {}, view
|
|||||||
setLastNameFilter(undefined);
|
setLastNameFilter(undefined);
|
||||||
if (customerOptions.length === 0) {
|
if (customerOptions.length === 0) {
|
||||||
CustomerService.getAllActiveCustomers().then((data) => {
|
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);
|
setNewGroupAddress(group.customers[0].customer_group_address);
|
||||||
@ -279,6 +324,52 @@ const RouteCustomerEditor = ({currentRoute, setNewCustomerList = (a) => {}, view
|
|||||||
setNewRouteCustomerList([]);
|
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 = () => {
|
const addAptGroup = () => {
|
||||||
if (checkGroupRequiredField()) {
|
if (checkGroupRequiredField()) {
|
||||||
const result = [].concat(customers).concat([{
|
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(() => {
|
useEffect(() => {
|
||||||
setCustomers(getRouteCustomersWithGroups());
|
if (currentRoute?.id && currentRoute.id !== initializedRouteId) {
|
||||||
}, [currentRoute])
|
setCustomers(getRouteCustomersWithGroups());
|
||||||
|
setInitializedRouteId(currentRoute.id);
|
||||||
|
}
|
||||||
|
}, [currentRoute?.id, initializedRouteId])
|
||||||
const getRouteCustomersWithGroups = () => {
|
const getRouteCustomersWithGroups = () => {
|
||||||
const customerList = currentRoute?.route_customer_list?.map(item => Object.assign({}, item, {routeType: currentRoute.type, routeId: currentRoute.id}));
|
const customerList = currentRoute?.route_customer_list?.map(item => Object.assign({}, item, {routeType: currentRoute.type, routeId: currentRoute.id}));
|
||||||
const result = {};
|
const result = {};
|
||||||
@ -353,10 +449,16 @@ const RouteCustomerEditor = ({currentRoute, setNewCustomerList = (a) => {}, view
|
|||||||
}
|
}
|
||||||
|
|
||||||
const deleteCustomer = (id) => {
|
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));
|
setCustomers(customers.filter((customer) => customer.customer_id !== id));
|
||||||
}
|
}
|
||||||
|
|
||||||
const deleteGroup = (index) => {
|
const deleteGroup = (index) => {
|
||||||
|
if (!window.confirm('Are you sure you want to remove this group from the route?')) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
const arr = [].concat(customers);
|
const arr = [].concat(customers);
|
||||||
arr.splice(index, 1);
|
arr.splice(index, 1);
|
||||||
setCustomers(arr);
|
setCustomers(arr);
|
||||||
@ -393,7 +495,8 @@ const RouteCustomerEditor = ({currentRoute, setNewCustomerList = (a) => {}, view
|
|||||||
};
|
};
|
||||||
|
|
||||||
const ItemsGroup = ({ currentItems }) => {
|
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">
|
(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)}/>
|
<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>
|
<div>
|
||||||
@ -424,6 +527,13 @@ const RouteCustomerEditor = ({currentRoute, setNewCustomerList = (a) => {}, view
|
|||||||
return count;
|
return count;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Expose addCustomerFromDrop via callback
|
||||||
|
useEffect(() => {
|
||||||
|
if (onAddCustomer && typeof onAddCustomer === 'function') {
|
||||||
|
onAddCustomer(addCustomerFromDrop);
|
||||||
|
}
|
||||||
|
}, [addCustomerFromDrop, onAddCustomer]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const result = [];
|
const result = [];
|
||||||
for (const item of customers) {
|
for (const item of customers) {
|
||||||
@ -440,11 +550,43 @@ const RouteCustomerEditor = ({currentRoute, setNewCustomerList = (a) => {}, view
|
|||||||
setNewCustomerList(result);
|
setNewCustomerList(result);
|
||||||
}, [customers])
|
}, [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 (
|
||||||
|
<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 (
|
return (
|
||||||
<DndProvider backend={HTML5Backend}>
|
<>
|
||||||
{ !viewMode && <h6 class="text-primary">Customers Assigned ({getCurrentAssignedNumber()})</h6>}
|
{ !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 && <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) => {
|
{customers.map((item, index) => {
|
||||||
if (item?.customers) {
|
if (item?.customers) {
|
||||||
return <Card key={index} index={index} moveCard={reorderItems} content={(<div className="customers-dnd-item-container">
|
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="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 className="stop-index"><span>{`Stop ${customers?.length+1}`}</span><RecordCircleFill size={16} color={"#ccc"} className="ms-2"></RecordCircleFill> </div>
|
||||||
<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 mb-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={() => openAddAptGroupModal()}> + Add Apt Group </button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
</div>}
|
</CustomersDropZone>}
|
||||||
{
|
{
|
||||||
viewMode && <div className="customers-container mb-4">
|
viewMode && <div className="customers-container mb-4">
|
||||||
{customers.map((item, index) => {
|
{customers.map((item, index) => {
|
||||||
@ -718,7 +860,7 @@ const RouteCustomerEditor = ({currentRoute, setNewCustomerList = (a) => {}, view
|
|||||||
</Button>
|
</Button>
|
||||||
</Modal.Footer>
|
</Modal.Footer>
|
||||||
</Modal>
|
</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 { useSelector,useDispatch } from "react-redux";
|
||||||
import { useParams, useNavigate } from "react-router-dom";
|
import { useParams, useNavigate } from "react-router-dom";
|
||||||
import { selectAllRoutes, transRoutesSlice, vehicleSlice, selectTomorrowAllRoutes, selectAllActiveDrivers, selectAllActiveVehicles, selectHistoryRoutes } from "./../../store";
|
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 moment from 'moment';
|
||||||
import { Archive, GripVertical } from "react-bootstrap-icons";
|
import { Archive, GripVertical } from "react-bootstrap-icons";
|
||||||
import { PERSONAL_ROUTE_STATUS } from "../../shared";
|
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 RouteEdit = () => {
|
||||||
const params = useParams();
|
const params = useParams();
|
||||||
@ -38,6 +41,7 @@ const RouteEdit = () => {
|
|||||||
const [currentRoute, setCurrentRoute] = useState(undefined);
|
const [currentRoute, setCurrentRoute] = useState(undefined);
|
||||||
const [allCustomers, setAllCustomers] = useState([]);
|
const [allCustomers, setAllCustomers] = useState([]);
|
||||||
const [unassignedCustomers, setUnassignedCustomers] = useState([]);
|
const [unassignedCustomers, setUnassignedCustomers] = useState([]);
|
||||||
|
const [addCustomerToRoute, setAddCustomerToRoute] = useState(null);
|
||||||
const paramsQuery = new URLSearchParams(window.location.search);
|
const paramsQuery = new URLSearchParams(window.location.search);
|
||||||
const scheduleDate = paramsQuery.get('dateSchedule');
|
const scheduleDate = paramsQuery.get('dateSchedule');
|
||||||
const editSection = paramsQuery.get('editSection')
|
const editSection = paramsQuery.get('editSection')
|
||||||
@ -54,17 +58,43 @@ const RouteEdit = () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const softDeleteCurrentRoute = () => {
|
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']})
|
const data = Object.assign({}, currentRoute, {status: ['disabled']})
|
||||||
dispatch(updateRoute({ id: currentRoute?.id, data, callback: redirectToDashboard }));
|
dispatch(updateRoute({ id: currentRoute?.id, data, callback: redirectToDashboard }));
|
||||||
// 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 = () => {
|
const updateCurrentRoute = () => {
|
||||||
try {
|
try {
|
||||||
if (!routeName || routeName === '') { setErrorMessage('Route Name is Required'); return;}
|
if (!validateRoute()) {
|
||||||
if (!newRouteType || newRouteType === '') {setErrorMessage('Route Type is Required'); return;}
|
return;
|
||||||
if (!newDriver || newDriver === '') {setErrorMessage('Driver is Required'); return;}
|
}
|
||||||
if (!newVehicle || newVehicle === '') {setErrorMessage('Vehicle is Required'); return;}
|
|
||||||
let data = Object.assign({}, currentRoute, {name: routeName, driver: newDriver, vehicle: newVehicle, type: newRouteType, route_customer_list: newCustomerList});
|
let data = Object.assign({}, currentRoute, {name: routeName, driver: newDriver, vehicle: newVehicle, type: newRouteType, route_customer_list: newCustomerList});
|
||||||
if (estimatedStartTime && estimatedStartTime !== '') {
|
if (estimatedStartTime && estimatedStartTime !== '') {
|
||||||
data = Object.assign({}, data, {estimated_start_time: combineDateAndTime(currentRoute.schedule_date, 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
|
// Filter out customers that are not assigned to any route
|
||||||
return customers.filter(customer =>
|
// Also exclude discharged customers (type is 'discharged' or name contains 'discharged')
|
||||||
customer.status === 'active' && !assignedCustomerIds.has(customer.id)
|
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(() => {
|
useEffect(() => {
|
||||||
@ -180,16 +216,22 @@ const RouteEdit = () => {
|
|||||||
|
|
||||||
const routeDate = currentRoute.schedule_date;
|
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 = [
|
const sameDateRoutes = [
|
||||||
...allRoutes.filter(route => route.schedule_date === routeDate),
|
...allRoutes.filter(route => route.schedule_date === routeDate && route.id !== currentRoute.id),
|
||||||
...tomorrowRoutes.filter(route => route.schedule_date === routeDate),
|
...tomorrowRoutes.filter(route => route.schedule_date === routeDate && route.id !== currentRoute.id),
|
||||||
...historyRoutes.filter(route => route.schedule_date === routeDate)
|
...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);
|
setUnassignedCustomers(unassigned);
|
||||||
}, [allCustomers, allRoutes, tomorrowRoutes, historyRoutes, currentRoute]);
|
}, [allCustomers, allRoutes, tomorrowRoutes, historyRoutes, currentRoute, newCustomerList]);
|
||||||
|
|
||||||
// useEffect(() => {
|
// useEffect(() => {
|
||||||
// if (currentRoute) {
|
// if (currentRoute) {
|
||||||
@ -203,14 +245,41 @@ const RouteEdit = () => {
|
|||||||
// setErrorMessage(undefined);
|
// setErrorMessage(undefined);
|
||||||
// }, [currentRoute])
|
// }, [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 (
|
return (
|
||||||
<>
|
<>
|
||||||
<div className="list row mb-4">
|
<div className="list row mb-4">
|
||||||
<Breadcrumb>
|
<Breadcrumb>
|
||||||
<Breadcrumb.Item>Transportation</Breadcrumb.Item>
|
<Breadcrumb.Item href="/">Transportation</Breadcrumb.Item>
|
||||||
<Breadcrumb.Item active>
|
<Breadcrumb.Item href="/trans-routes/dashboard">
|
||||||
Transportation Routes
|
Transportation Routes
|
||||||
</Breadcrumb.Item>
|
</Breadcrumb.Item>
|
||||||
|
<Breadcrumb.Item active>
|
||||||
|
Edit Route
|
||||||
|
</Breadcrumb.Item>
|
||||||
</Breadcrumb>
|
</Breadcrumb>
|
||||||
<div className="col-md-12 text-primary">
|
<div className="col-md-12 text-primary">
|
||||||
<h4>
|
<h4>
|
||||||
@ -219,7 +288,7 @@ const RouteEdit = () => {
|
|||||||
</div>
|
</div>
|
||||||
</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">
|
<div className="app-main-content-list-func-container">
|
||||||
<Tabs defaultActiveKey="routeOverview" id="route-view-tab">
|
<Tabs defaultActiveKey="routeOverview" id="route-view-tab">
|
||||||
<Tab eventKey="routeOverview" title="Route Information">
|
<Tab eventKey="routeOverview" title="Route Information">
|
||||||
@ -342,7 +411,7 @@ const RouteEdit = () => {
|
|||||||
{
|
{
|
||||||
newDriver && newDriver !== '' && <div className="column-card">
|
newDriver && newDriver !== '' && <div className="column-card">
|
||||||
<h6 className="text-primary">Driver Information</h6>
|
<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="app-main-content-fields-section short">
|
||||||
<div className="field-body">
|
<div className="field-body">
|
||||||
<div className="field-label">Driver Name</div>
|
<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 className="field-value">{drivers.find(item => item.id === newDriver)?.title}</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="field-body">
|
<div className="field-body">
|
||||||
<div className="field-label">Job Status</div>
|
<div className="field-label">Job Type</div>
|
||||||
<div className="field-value">{vehicles.find(item => item.id === newVehicle)?.employment_status}</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>
|
</div>
|
||||||
<div className="app-main-content-fields-section short">
|
<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-body">
|
||||||
<div className="field-label">Phone Number</div>
|
<div className="field-label">Phone Number</div>
|
||||||
<div className="field-value">{drivers.find(item => item.id === newDriver)?.phone}</div>
|
<div className="field-value">{drivers.find(item => item.id === newDriver)?.phone}</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="field-body">
|
<div className="field-body">
|
||||||
<div className="field-label">Email</div>
|
<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>
|
||||||
|
<div className="field-body"></div>
|
||||||
|
<div className="field-body"></div>
|
||||||
|
<div className="field-body"></div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
@ -383,67 +453,63 @@ const RouteEdit = () => {
|
|||||||
</div> }
|
</div> }
|
||||||
|
|
||||||
{
|
{
|
||||||
editSection === 'assignment' && <div className="multi-columns-container">
|
editSection === 'assignment' && <DndProvider backend={HTML5Backend}>
|
||||||
<div className="column-container">
|
<div className="multi-columns-container">
|
||||||
<div className="column-card adjust">
|
<div className="column-container">
|
||||||
<div className="col-md-12 mb-4">
|
<div className="column-card adjust" style={{paddingRight: '30px'}}>
|
||||||
<RouteCustomerEditor
|
<div className="col-md-12 mb-4">
|
||||||
currentRoute={currentRoute ? {
|
<RouteCustomerEditor
|
||||||
...currentRoute,
|
currentRoute={currentRoute ? {
|
||||||
route_customer_list: currentRoute.route_customer_list?.filter(
|
...currentRoute,
|
||||||
customer => customer?.customer_route_status !== PERSONAL_ROUTE_STATUS.SCHEDULED_ABSENT
|
route_customer_list: currentRoute.route_customer_list?.filter(
|
||||||
) || []
|
customer => customer?.customer_route_status !== PERSONAL_ROUTE_STATUS.SCHEDULED_ABSENT
|
||||||
} : undefined}
|
) || []
|
||||||
setNewCustomerList={setNewCustomerList}
|
} : undefined}
|
||||||
/>
|
setNewCustomerList={setNewCustomerList}
|
||||||
|
onAddCustomer={(addFn) => setAddCustomerToRoute(() => addFn)}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
<div className="column-container">
|
||||||
<div className="column-container">
|
<div className="column-card adjust">
|
||||||
<div className="column-card adjust">
|
<h6 className="text-primary">Scheduled Absences ({currentRoute?.route_customer_list?.filter(item => item?.customer_route_status === PERSONAL_ROUTE_STATUS.SCHEDULED_ABSENT)?.length || 0})</h6>
|
||||||
<h6 className="text-primary">Scheduled Absences ({currentRoute?.route_customer_list?.filter(item => item?.customer_route_status === PERSONAL_ROUTE_STATUS.SCHEDULED_ABSENT)?.length || 0})</h6>
|
<div className="customers-container mb-4">
|
||||||
<div className="customers-container mb-4">
|
{
|
||||||
{
|
currentRoute?.route_customer_list.filter(customer => customer?.customer_route_status === PERSONAL_ROUTE_STATUS.SCHEDULED_ABSENT)?.map((abItem) => {
|
||||||
currentRoute?.route_customer_list.filter(customer => customer?.customer_route_status === PERSONAL_ROUTE_STATUS.SCHEDULED_ABSENT)?.map((abItem) => {
|
return <div key={abItem.customer_id} className="customers-dnd-item-container-absent">
|
||||||
return <div className="customers-dnd-item-container-absent">
|
<GripVertical className="me-4" size={14}></GripVertical>
|
||||||
<GripVertical className="me-4" size={14}></GripVertical>
|
<div className="customer-dnd-item">
|
||||||
<div className="customer-dnd-item">
|
<span>{abItem.customer_name} </span>
|
||||||
<span>{abItem.customer_name} </span>
|
<small className="me-2">{abItem.customer_address}</small>
|
||||||
<small className="me-2">{abItem.customer_address}</small>
|
<small className="me-2">{abItem.customer_pickup_status}</small>
|
||||||
<small className="me-2">{abItem.customer_pickup_status}</small>
|
</div>
|
||||||
</div>
|
|
||||||
|
|
||||||
</div>
|
|
||||||
})
|
|
||||||
}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div className="column-container">
|
|
||||||
<div className="column-card adjust">
|
|
||||||
<h6 className="text-primary">Unassigned Customers ({unassignedCustomers?.length || 0})</h6>
|
|
||||||
<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>
|
||||||
</div>
|
})
|
||||||
})
|
}
|
||||||
}
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="column-container">
|
||||||
|
<div className="column-card adjust">
|
||||||
|
<h6 className="text-primary">Unassigned Customers ({unassignedCustomers?.length || 0})</h6>
|
||||||
|
<div className="customers-container mb-4">
|
||||||
|
{
|
||||||
|
unassignedCustomers?.map((customer) => {
|
||||||
|
return <DraggableUnassignedCustomer key={customer.id} customer={customer} />
|
||||||
|
})
|
||||||
|
}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</DndProvider>
|
||||||
}
|
}
|
||||||
</Tab>
|
</Tab>
|
||||||
</Tabs>
|
</Tabs>
|
||||||
<div className="list-func-panel">
|
<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>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@ -76,6 +76,13 @@ const RouteReportWithSignature = () => {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
}, [currentRoute]);
|
}, [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 (
|
return (
|
||||||
<>
|
<>
|
||||||
<style>
|
<style>
|
||||||
@ -85,9 +92,50 @@ const RouteReportWithSignature = () => {
|
|||||||
min-width: auto !important;
|
min-width: auto !important;
|
||||||
width: auto !important;
|
width: auto !important;
|
||||||
max-width: none !important;
|
max-width: none !important;
|
||||||
|
border: 1px solid #333;
|
||||||
|
padding: 4px 8px;
|
||||||
|
font-size: 12px;
|
||||||
}
|
}
|
||||||
.route-report-table {
|
.route-report-table {
|
||||||
table-layout: auto !important;
|
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>
|
</style>
|
||||||
@ -96,116 +144,210 @@ const RouteReportWithSignature = () => {
|
|||||||
<button className="btn btn-link btn-sm" onClick={() => directToView()}>Back</button>
|
<button className="btn btn-link btn-sm" onClick={() => directToView()}>Back</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
|
||||||
<div className="float-end" style={{'background': '#eee', padding: '12px 8px', width: '50px', height: '50px', textAlign: 'center'}}>
|
|
||||||
<span>{routeIndex}</span>
|
|
||||||
</div>
|
|
||||||
</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>
|
|
||||||
<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>
|
|
||||||
<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>
|
|
||||||
<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>
|
|
||||||
|
|
||||||
|
{/* 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 style={{ flex: 1 }}>
|
||||||
|
<strong>Vehicle (车号):</strong> {currentVehicle?.vehicle_number}
|
||||||
|
</div>
|
||||||
|
<div style={{ flex: 1 }}>
|
||||||
|
<strong>Date (日期):</strong> {currentRoute?.schedule_date && moment(currentRoute?.schedule_date).format('MM/DD/YYYY')}
|
||||||
|
</div>
|
||||||
|
<div style={{ background: '#eee', padding: '8px 12px', fontWeight: 'bold' }}>
|
||||||
|
{routeIndex}
|
||||||
|
</div>
|
||||||
</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>}
|
|
||||||
|
|
||||||
|
{/* 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>
|
||||||
<div className="col-md-6 col-sm-12 mb-4">
|
<div style={{ flex: 1 }}>
|
||||||
{/* Manager's Signature (经理签字): {directorSignature && <span className="mb-2">{directorSignature && <img width="100px" src={`data:image/jpg;base64, ${directorSignature}`}/>}</span>} */}
|
<strong>Manager's Signature (经理签字):</strong>
|
||||||
Manager's Signature: <img width="100px" src="/images/signature.jpeg"/>
|
<img width="100px" src="/images/signature.jpeg" style={{ marginLeft: '16px' }} alt="Manager Signature" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
{/* Main Customer Table */}
|
||||||
<div className="list row">
|
<div className="list row">
|
||||||
<div className="col-md-12 mb-4">
|
<div className="col-md-12 mb-4">
|
||||||
<table className="personnel-info-table route-report-table" style={{
|
<table className="personnel-info-table route-report-table" style={{ tableLayout: 'auto', width: '100%' }}>
|
||||||
tableLayout: 'auto',
|
|
||||||
width: '100%'
|
|
||||||
}}>
|
|
||||||
<thead>
|
<thead>
|
||||||
|
{/* English Header Row */}
|
||||||
<tr>
|
<tr>
|
||||||
<th style={{ minWidth: 'auto !important', width: 'auto !important', maxWidth: 'none !important' }}>No.</th>
|
<th rowSpan={2} style={{ verticalAlign: 'middle' }}>No.<span className="bilingual-label">序号</span></th>
|
||||||
<th style={{ minWidth: 'auto !important', width: 'auto !important', maxWidth: 'none !important' }}>Name</th>
|
<th rowSpan={2} style={{ verticalAlign: 'middle' }}>Name<span className="bilingual-label">姓名</span></th>
|
||||||
<th style={{ minWidth: 'auto !important', width: 'auto !important', maxWidth: 'none !important' }}>Phone</th>
|
<th rowSpan={2} style={{ verticalAlign: 'middle' }}>Phone<span className="bilingual-label">联系电话</span></th>
|
||||||
<th style={{ minWidth: 'auto !important', width: 'auto !important', maxWidth: 'none !important' }}>Address</th>
|
<th rowSpan={2} style={{ verticalAlign: 'middle' }}>Address<span className="bilingual-label">地址</span></th>
|
||||||
<th style={{ minWidth: 'auto !important', width: 'auto !important', maxWidth: 'none !important' }}>Unit</th>
|
<th rowSpan={2} style={{ verticalAlign: 'middle' }}>Unit<span className="bilingual-label">房间号</span></th>
|
||||||
<th colSpan={2} style={{ minWidth: 'auto !important', width: 'auto !important', maxWidth: 'none !important' }}>Pick-Up</th>
|
<th colSpan={2} style={{ textAlign: 'center' }}>Attendance<span className="bilingual-label">出勤</span></th>
|
||||||
<th style={{ minWidth: 'auto !important', width: 'auto !important', maxWidth: 'none !important' }}>Pick-Up</th>
|
<th rowSpan={2} style={{ verticalAlign: 'middle' }}>Pick-Up<span className="bilingual-label">接到时间</span></th>
|
||||||
<th style={{ minWidth: 'auto !important', width: 'auto !important', maxWidth: 'none !important' }}>Arrival</th>
|
<th rowSpan={2} style={{ verticalAlign: 'middle' }}>Arrival<span className="bilingual-label">抵达中心</span></th>
|
||||||
<th style={{ minWidth: 'auto !important', width: 'auto !important', maxWidth: 'none !important' }}>Departure</th>
|
<th rowSpan={2} style={{ verticalAlign: 'middle' }}>Departure<span className="bilingual-label">离开中心</span></th>
|
||||||
<th style={{ minWidth: 'auto !important', width: 'auto !important', maxWidth: 'none !important' }}>Drop-Off</th>
|
<th rowSpan={2} style={{ verticalAlign: 'middle' }}>Drop-Off<span className="bilingual-label">送达时间</span></th>
|
||||||
<th style={{ minWidth: 'auto !important', width: 'auto !important', maxWidth: 'none !important' }}>Notice</th>
|
<th rowSpan={2} style={{ verticalAlign: 'middle' }}>Notice<span className="bilingual-label">备注</span></th>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<th style={{ minWidth: 'auto !important', width: 'auto !important', maxWidth: 'none !important' }}></th>
|
<th style={{ textAlign: 'center' }}>Y</th>
|
||||||
<th style={{ minWidth: 'auto !important', width: 'auto !important', maxWidth: 'none !important' }}></th>
|
<th style={{ textAlign: 'center' }}>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' }}>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>
|
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
{
|
{
|
||||||
currentRoute?.route_customer_list?.map((customer, index) => {
|
currentRoute?.route_customer_list?.map((customer, index) => {
|
||||||
const relativeRouteCustomer = getInboundOrOutboundRouteCustomer(customer?.customer_id);
|
const relativeRouteCustomer = getInboundOrOutboundRouteCustomer(customer?.customer_id);
|
||||||
// console.log('cust', relativeRouteCustomer);
|
|
||||||
const otherRouteWithThisCustomer = getOtherRouteWithThisCustomer(customer?.customer_id);
|
const otherRouteWithThisCustomer = getOtherRouteWithThisCustomer(customer?.customer_id);
|
||||||
// console.log('other', otherRouteWithThisCustomer)
|
|
||||||
const customerInOtherRoute = otherRouteWithThisCustomer?.route_customer_list?.find(cust => cust?.customer_id === customer?.customer_id);
|
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);
|
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) &&
|
||||||
<td>{customer?.customer_name}</td>
|
![PERSONAL_ROUTE_STATUS.SCHEDULED_ABSENT, PERSONAL_ROUTE_STATUS.UNEXPECTED_ABSENT].includes(customer?.customer_route_status);
|
||||||
<td>{customer?.customer_phone}</td>
|
|
||||||
<td>{customer?.customer_address_override || customer?.customer_address}</td>
|
return (
|
||||||
<td></td>
|
<tr key={index}>
|
||||||
<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 style={{ textAlign: 'center' }}>{index + 1}</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_name}</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_phone}</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_address_override || customer?.customer_address}</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></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 style={{ textAlign: 'center' }}>{isAttending ? '✓' : ''}</td>
|
||||||
<td>
|
<td style={{ textAlign: 'center' }}>{!isAttending ? '✗' : ''}</td>
|
||||||
{customer?.customer_type !== CUSTOMER_TYPE.MEMBER && <div>{ CUSTOMER_TYPE_TEXT[customer?.customer_type]}</div> }
|
<td style={{ textAlign: 'center' }}>
|
||||||
{ !relativeRouteCustomer && otherRouteWithThisCustomer && (
|
{customer?.customer_pickup_time
|
||||||
<div>
|
? new Date(customer?.customer_pickup_time).toLocaleTimeString('en-US', {hour12: false, hour: '2-digit', minute: '2-digit'})
|
||||||
{`${currentRoute?.type === 'inbound' ? 'Switch to Route ' : 'Switch from Route '} ${otherRouteWithThisCustomer?.name}`}
|
: (relativeRouteCustomer?.customer_pickup_time
|
||||||
</div>
|
? 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'})
|
||||||
{
|
: ''))}
|
||||||
customer?.customer_route_status === PERSONAL_ROUTE_STATUS.SKIP_DROPOFF && otherOutboundRoute && (
|
</td>
|
||||||
<div>
|
<td style={{ textAlign: 'center' }}>
|
||||||
{`Switch to Route ${otherOutboundRoute?.name}`}
|
{customer?.customer_enter_center_time
|
||||||
</div>
|
? 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'})
|
||||||
</td>
|
: (customerInOtherRoute?.customer_enter_center_time
|
||||||
</tr>)
|
? 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>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
</div>
|
</div>
|
||||||
</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">
|
<div className="list row mb-4">
|
||||||
<Breadcrumb>
|
<Breadcrumb>
|
||||||
<Breadcrumb.Item>Transportation</Breadcrumb.Item>
|
<Breadcrumb.Item href="/">Transportation</Breadcrumb.Item>
|
||||||
<Breadcrumb.Item active>
|
<Breadcrumb.Item href="/trans-routes/dashboard">
|
||||||
Transportation Routes
|
Transportation Routes
|
||||||
</Breadcrumb.Item>
|
</Breadcrumb.Item>
|
||||||
|
<Breadcrumb.Item active>
|
||||||
|
View Route
|
||||||
|
</Breadcrumb.Item>
|
||||||
</Breadcrumb>
|
</Breadcrumb>
|
||||||
<div className="col-md-12 text-primary">
|
<div className="col-md-12 text-primary">
|
||||||
<h4>
|
<h4>
|
||||||
@ -100,7 +103,7 @@ const RouteView = () => {
|
|||||||
</h4>
|
</h4>
|
||||||
</div>
|
</div>
|
||||||
</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">
|
<div className="app-main-content-list-func-container">
|
||||||
<Tabs defaultActiveKey="routeOverview" id="route-view-tab">
|
<Tabs defaultActiveKey="routeOverview" id="route-view-tab">
|
||||||
<Tab eventKey="routeOverview" title="Route Information">
|
<Tab eventKey="routeOverview" title="Route Information">
|
||||||
|
|||||||
@ -1,15 +1,12 @@
|
|||||||
import React, { useState, useEffect } from "react";
|
import React, { useState, useEffect } from "react";
|
||||||
import { useSelector, useDispatch } from "react-redux";
|
import { useSelector, useDispatch } from "react-redux";
|
||||||
import { useNavigate, useSearchParams } from "react-router-dom";
|
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 RoutesSection from "./RoutesSection";
|
||||||
import PersonnelSection from "./PersonnelSection";
|
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 { 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 moment from 'moment';
|
||||||
import LunchSection from "./LunchSection";
|
|
||||||
import SnackSection from "./SnackSection";
|
|
||||||
import DatePicker from "react-datepicker";
|
import DatePicker from "react-datepicker";
|
||||||
import { CalendarWeek, ClockHistory, Copy, Download, Eraser, Plus, Clock, Send, Filter, CalendarCheck, Check } from "react-bootstrap-icons";
|
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";
|
import { Breadcrumb, Tabs, Tab, Dropdown, Spinner, Modal, Button } from "react-bootstrap";
|
||||||
@ -19,7 +16,7 @@ import RouteCustomerTable from "./RouteCustomerTable";
|
|||||||
const RoutesDashboard = () => {
|
const RoutesDashboard = () => {
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
const dispatch = useDispatch();
|
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 inboundRoutes = useSelector(selectInboundRoutes);
|
||||||
const outboundRoutes = useSelector(selectOutboundRoutes);
|
const outboundRoutes = useSelector(selectOutboundRoutes);
|
||||||
const tmrInboundRoutes = useSelector(selectTomorrowInboundRoutes);
|
const tmrInboundRoutes = useSelector(selectTomorrowInboundRoutes);
|
||||||
@ -31,10 +28,8 @@ const RoutesDashboard = () => {
|
|||||||
const allTomorrowRoutes = useSelector(selectTomorrowAllRoutes);
|
const allTomorrowRoutes = useSelector(selectTomorrowAllRoutes);
|
||||||
const drivers = useSelector(selectAllActiveDrivers);
|
const drivers = useSelector(selectAllActiveDrivers);
|
||||||
const vehicles = useSelector(selectAllActiveVehicles);
|
const vehicles = useSelector(selectAllActiveVehicles);
|
||||||
const breakfastRecords = useSelector(selectAllBreakfastRecords);
|
|
||||||
const lunchRecords = useSelector(selectAllLunchRecords);
|
|
||||||
const snackRecords = useSelector(selectAllSnackRecords);
|
|
||||||
const [showSyncCustomersLoading, setShowSyncCustomersLoading] = useState(false);
|
const [showSyncCustomersLoading, setShowSyncCustomersLoading] = useState(false);
|
||||||
|
const [hasAutoSynced, setHasAutoSynced] = useState(false);
|
||||||
const [keyword, setKeyword] = useState('');
|
const [keyword, setKeyword] = useState('');
|
||||||
|
|
||||||
const [directorSignature, setDirectorSignature] = useState(undefined);
|
const [directorSignature, setDirectorSignature] = useState(undefined);
|
||||||
@ -76,6 +71,16 @@ const RoutesDashboard = () => {
|
|||||||
const [customerTableId, setCustomerTableId] = useState('');
|
const [customerTableId, setCustomerTableId] = useState('');
|
||||||
const [routeTypeFilter, setRouteTypeFilter] = 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 params = new URLSearchParams(window.location.search);
|
||||||
const scheduleDate = params.get('dateSchedule');
|
const scheduleDate = params.get('dateSchedule');
|
||||||
@ -152,6 +157,13 @@ const RoutesDashboard = () => {
|
|||||||
setDirectorSignature(data?.data)
|
setDirectorSignature(data?.data)
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Fetch all daily routes templates
|
||||||
|
DailyRoutesTemplateService.getAll().then((response) => {
|
||||||
|
setTemplates(response.data || []);
|
||||||
|
}).catch(err => {
|
||||||
|
console.error('Error fetching templates:', err);
|
||||||
|
});
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@ -188,6 +200,12 @@ const RoutesDashboard = () => {
|
|||||||
setRoutesForShowing(allRoutes);
|
setRoutesForShowing(allRoutes);
|
||||||
setRoutesInboundForShowing(inboundRoutes);
|
setRoutesInboundForShowing(inboundRoutes);
|
||||||
setRoutesOutboundForShowing(processRoutesForAbsentCustomers(inboundRoutes, outboundRoutes));
|
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 {
|
} else {
|
||||||
if (dateSelected > new Date()) {
|
if (dateSelected > new Date()) {
|
||||||
setRoutesForShowing(allTomorrowRoutes);
|
setRoutesForShowing(allTomorrowRoutes);
|
||||||
@ -240,6 +258,7 @@ const RoutesDashboard = () => {
|
|||||||
setDateSelected(new Date());
|
setDateSelected(new Date());
|
||||||
setOriginDateSelected(undefined);
|
setOriginDateSelected(undefined);
|
||||||
setTargetedDateSelected(undefined);
|
setTargetedDateSelected(undefined);
|
||||||
|
setHasAutoSynced(false); // Reset auto-sync flag when changing tabs
|
||||||
// setSelectedDriver(undefined);
|
// setSelectedDriver(undefined);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -537,6 +556,12 @@ const RoutesDashboard = () => {
|
|||||||
}
|
}
|
||||||
setTimeout(dispatch(fetchAllRoutes()), 10000);
|
setTimeout(dispatch(fetchAllRoutes()), 10000);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const handleCleanAllRoutesStatus = () => {
|
||||||
|
if (window.confirm('Are you sure you want to do this? This cannot be undone.')) {
|
||||||
|
cleanAllRoutesStatus();
|
||||||
|
}
|
||||||
|
}
|
||||||
const syncCustomersInfo = () => {
|
const syncCustomersInfo = () => {
|
||||||
setShowSyncCustomersLoading(true);
|
setShowSyncCustomersLoading(true);
|
||||||
CustomerService.getAllCustomers().then(data => {
|
CustomerService.getAllCustomers().then(data => {
|
||||||
@ -772,6 +797,11 @@ const RoutesDashboard = () => {
|
|||||||
|
|
||||||
const customMenuDate = React.forwardRef(
|
const customMenuDate = React.forwardRef(
|
||||||
({ children, style, className, 'aria-labelledby': labeledBy }, ref) => {
|
({ children, style, className, 'aria-labelledby': labeledBy }, ref) => {
|
||||||
|
const handleDateChange = (v) => {
|
||||||
|
setDateSelected(v);
|
||||||
|
setShowDateDropdown(false);
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
ref={ref}
|
ref={ref}
|
||||||
@ -781,14 +811,8 @@ const RoutesDashboard = () => {
|
|||||||
>
|
>
|
||||||
<div className="app-main-content-fields-section margin-sm dropdown-container">
|
<div className="app-main-content-fields-section margin-sm dropdown-container">
|
||||||
<div className="me-4">
|
<div className="me-4">
|
||||||
<div className="field-label">Select Date to Start</div>
|
<div className="field-label">Select Date to View Report</div>
|
||||||
<DatePicker selected={dateSelected} onChange={(v) => setDateSelected(v)} />
|
<DatePicker selected={dateSelected} onChange={handleDateChange} />
|
||||||
</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>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -866,89 +890,163 @@ const RoutesDashboard = () => {
|
|||||||
setShowSignatureRequestLoading(false);
|
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());
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
const confirmHasLunch = (customer) => {
|
// Save template modal handlers
|
||||||
TransRoutesService.createLunchRecords({
|
const openSaveTemplateModal = () => {
|
||||||
customer_id: customer?.customer_id,
|
setShowSaveTemplateModal(true);
|
||||||
customer_name: customer?.customer_name,
|
setTemplateName('');
|
||||||
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());
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
const confirmHasSnack = (customer) => {
|
const closeSaveTemplateModal = () => {
|
||||||
TransRoutesService.createSnackRecords({
|
setShowSaveTemplateModal(false);
|
||||||
customer_id: customer?.customer_id,
|
setTemplateName('');
|
||||||
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());
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
const removeBreakfastRecord = (customer_id) => {
|
const saveRoutesAsTemplate = () => {
|
||||||
const breakfast = breakfastRecords.find(b => b.customer_id === customer_id)?.id;
|
if (!templateName || templateName.trim() === '') {
|
||||||
TransRoutesService.deleteBreakfastRecords(breakfast).then(() => {
|
alert('Please enter a template name');
|
||||||
dispatch(fetchAllBreakfastRecords());
|
return;
|
||||||
})
|
}
|
||||||
}
|
|
||||||
const removeLunchRecord = (customer_id) => {
|
setSavingTemplate(true);
|
||||||
const lunch = lunchRecords.find(b => b.customer_id === customer_id)?.id;
|
|
||||||
TransRoutesService.deleteLunchRecords(lunch).then(() => {
|
// Get the current date string in MM/DD/YYYY format
|
||||||
dispatch(fetchAllLunchRecords());
|
const templateDate = getDateString(dateSelected);
|
||||||
})
|
|
||||||
}
|
// Get current user from localStorage
|
||||||
const removeSnackRecord = (customer_id) => {
|
const currentUser = localStorage.getItem('user') ? JSON.parse(localStorage.getItem('user'))?.name : '';
|
||||||
const snack = snackRecords.find(b => b.customer_id === customer_id)?.id;
|
|
||||||
TransRoutesService.deleteSnackRecords(snack).then(() => {
|
// Clean the routes data - remove all date fields and status
|
||||||
dispatch(fetchAllSnackRecords());
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
setApplyingTemplate(true);
|
||||||
|
|
||||||
|
// Find the selected template
|
||||||
|
const selectedTemplate = templates.find(t => t.id === selectedTemplateId);
|
||||||
|
if (!selectedTemplate) {
|
||||||
|
alert('Template not found');
|
||||||
|
setApplyingTemplate(false);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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)}));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch(err => {
|
||||||
|
setApplyingTemplate(false);
|
||||||
|
setErrorMessage(err.message || 'Failed to apply template');
|
||||||
|
setTimeout(() => setErrorMessage(undefined), 5000);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<div className="list row mb-4">
|
<div className="list row mb-4">
|
||||||
<Breadcrumb>
|
<Breadcrumb>
|
||||||
<Breadcrumb.Item>Transportation</Breadcrumb.Item>
|
<Breadcrumb.Item href="/">Transportation</Breadcrumb.Item>
|
||||||
<Breadcrumb.Item active>
|
<Breadcrumb.Item active>
|
||||||
Transportation Routes
|
Transportation Routes
|
||||||
</Breadcrumb.Item>
|
</Breadcrumb.Item>
|
||||||
</Breadcrumb>
|
</Breadcrumb>
|
||||||
<div className="col-md-12 text-primary">
|
<div className="col-md-12 text-primary">
|
||||||
<h4>
|
<h4>
|
||||||
All Routes
|
All Routes - {moment(dateSelected).format('MM/DD/YYYY (dddd)')}
|
||||||
</h4>
|
</h4>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -958,38 +1056,65 @@ const RoutesDashboard = () => {
|
|||||||
<Tab eventKey="allRoutesOverview" title="All Routes Overview">
|
<Tab eventKey="allRoutesOverview" title="All Routes Overview">
|
||||||
{(!dateSelected || getDateString(dateSelected) === getDateString(new Date())) && <div className="app-main-content-fields-section with-function">
|
{(!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={() => 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={() => 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={()=> 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={() => 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 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>
|
<button className="btn btn-primary" onClick={() => generateRouteReport()}><Download size={16} className="me-2"></Download>Export Route Report</button>
|
||||||
</div>}
|
</div>}
|
||||||
{(dateSelected && dateSelected > new Date()) && <div className="app-main-content-fields-section with-function">
|
{(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" 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>
|
<button type="button" className="btn btn-primary btn-sm" onClick={()=> triggerShowDeleteModal()}><Eraser size={16} className="me-2"></Eraser> Clean All Routes</button>
|
||||||
</div>}
|
</div>}
|
||||||
{ (dateSelected <= new Date() || !dateSelected) && <div className="list row">
|
{ (dateSelected <= new Date() || !dateSelected) && <div className="list row">
|
||||||
{
|
{
|
||||||
showCopyDateTargetLoading ? <><Spinner></Spinner></> : <>
|
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">
|
<div className="col-md-12 mb-4">
|
||||||
<RoutesSection transRoutes={routesInboundForShowing} drivers={drivers} vehicles={vehicles} sectionName="Inbound Routes"/>
|
<RoutesSection transRoutes={routesInboundForShowing} drivers={drivers} vehicles={vehicles} sectionName="Inbound Routes"/>
|
||||||
</div>
|
</div>
|
||||||
<div className="col-md-12 mb-4">
|
<div className="col-md-12 mb-4">
|
||||||
<RoutesSection transRoutes={routesOutboundForShowing} drivers={drivers} vehicles={vehicles} sectionName="Outbound Routes"/>
|
<RoutesSection transRoutes={routesOutboundForShowing} drivers={drivers} vehicles={vehicles} sectionName="Outbound Routes"/>
|
||||||
</div>
|
</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>
|
<span className="visually-hidden">Loading...</span>
|
||||||
</Spinner></div>}
|
</Spinner></div>}
|
||||||
{!isLoading && <>
|
{!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">
|
<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"/>
|
<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>
|
</div>
|
||||||
@ -1034,6 +1198,7 @@ const RoutesDashboard = () => {
|
|||||||
}
|
}
|
||||||
</Tab>
|
</Tab>
|
||||||
<Tab eventKey="allRoutesSignature" title="All Routes Signature">
|
<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">
|
<table className="personnel-info-table me-4">
|
||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
@ -1160,7 +1325,7 @@ const RoutesDashboard = () => {
|
|||||||
autoClose={false}
|
autoClose={false}
|
||||||
>
|
>
|
||||||
<Dropdown.Toggle variant="primary">
|
<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.Toggle>
|
||||||
<Dropdown.Menu as={customMenuOriginDate}/>
|
<Dropdown.Menu as={customMenuOriginDate}/>
|
||||||
</Dropdown>
|
</Dropdown>
|
||||||
@ -1184,7 +1349,7 @@ const RoutesDashboard = () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
{ currentTab === 'allRoutesSignature' && <>
|
{ currentTab === 'allRoutesSignature' && <>
|
||||||
<input className="me-2 with-search-icon" type="text" placeholder="Search" value={keyword} onChange={(e) => setKeyword(e.currentTarget.value)} />
|
|
||||||
<Dropdown
|
<Dropdown
|
||||||
key={'signature-date'}
|
key={'signature-date'}
|
||||||
id="signature-date"
|
id="signature-date"
|
||||||
@ -1263,6 +1428,36 @@ const RoutesDashboard = () => {
|
|||||||
</Button>
|
</Button>
|
||||||
</Modal.Footer>
|
</Modal.Footer>
|
||||||
</Modal>
|
</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>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|||||||
@ -263,9 +263,9 @@ const RoutesSchedule = () => {
|
|||||||
<div className="col-lg-3 col-md-6 col-sm-6 col-xs-12 mb-4">
|
<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>
|
<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>
|
||||||
<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>
|
<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">
|
<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>
|
<button type="button" className="btn btn-primary btn-sm" onClick={()=> triggerShowDeleteModal()}>Clean All Routes</button>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@ -7,7 +7,7 @@ import { CUSTOMER_TYPE, PERSONAL_ROUTE_STATUS, PICKUP_STATUS } from "../../share
|
|||||||
import { AuthService } from "../../services";
|
import { AuthService } from "../../services";
|
||||||
import moment from 'moment';
|
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 [showCopyModal, setShowCopyModal] = useState(false);
|
||||||
const [routesOptions, setRouteOptions] = useState([]);
|
const [routesOptions, setRouteOptions] = useState([]);
|
||||||
const [newRoute, setNewRoute] = useState(undefined);
|
const [newRoute, setNewRoute] = useState(undefined);
|
||||||
@ -107,7 +107,7 @@ const RoutesSection = ({transRoutes, copyList, sectionName, drivers, vehicles, c
|
|||||||
transRoutes && (<>
|
transRoutes && (<>
|
||||||
<div className="all-routes-container">
|
<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>
|
</div>
|
||||||
</>)
|
</>)
|
||||||
|
|||||||
@ -29,8 +29,10 @@ const SnackSection = ({transRoutes, snackRecords, sectionName, confirmHasSnack,
|
|||||||
}
|
}
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const routeCustomers = TransRoutesService.getAllInCenterCustomersFromRoutesForSnack(transRoutes, snackRecords);
|
if (transRoutes && transRoutes.length > 0) {
|
||||||
setCustomers(routeCustomers);
|
const routeCustomers = TransRoutesService.getAllInCenterCustomersFromRoutesForSnack(transRoutes, snackRecords || []);
|
||||||
|
setCustomers(routeCustomers);
|
||||||
|
}
|
||||||
}, [snackRecords, transRoutes]);
|
}, [snackRecords, transRoutes]);
|
||||||
|
|
||||||
|
|
||||||
@ -51,20 +53,20 @@ const SnackSection = ({transRoutes, snackRecords, sectionName, confirmHasSnack,
|
|||||||
<th>Change Snack Status</th>
|
<th>Change Snack Status</th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
{
|
{
|
||||||
snackRecords?.length >0 && customers?.map((customer) => (
|
customers?.map((customer) => (
|
||||||
<tr className={customer?.has_snack ? 'light-green' : 'red'}>
|
<tr key={customer?.customer_id} className={customer?.has_snack ? 'light-green' : 'red'}>
|
||||||
<td>{customer?.customer_name}</td>
|
<td>{customer?.customer_name}</td>
|
||||||
<td>{customer?.has_snack ? 'Yes': 'No'}</td>
|
<td>{customer?.has_snack ? 'Yes': 'No'}</td>
|
||||||
<td>
|
<td>
|
||||||
{!customer?.has_snack && <button className="btn btn-link btn-sm" onClick={() => confirmHasSnack(customer)}>Confirm Customer Has snack</button>}
|
{!customer?.has_snack && <button className="btn btn-link btn-sm" onClick={() => confirmHasSnack(customer)}>Confirm Customer Has snack</button>}
|
||||||
{customer?.has_snack && <button className="btn btn-link btn-sm" onClick={() => removeSnackRecord(customer?.customer_id)}>Mark Customer NOT have snack</button>}
|
{customer?.has_snack && <button className="btn btn-link btn-sm" onClick={() => removeSnackRecord(customer?.customer_id)}>Mark Customer NOT have snack</button>}
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@ -1,60 +1,100 @@
|
|||||||
import React, {useEffect, useState} from "react";
|
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 { useNavigate } from "react-router-dom";
|
||||||
import { vehicleSlice, selectVehicleError } from "./../../store";
|
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 { Spinner, Breadcrumb, BreadcrumbItem, Tabs, Tab } from "react-bootstrap";
|
||||||
|
import { Upload } from "react-bootstrap-icons";
|
||||||
import DatePicker from "react-datepicker";
|
import DatePicker from "react-datepicker";
|
||||||
|
import Select from 'react-select';
|
||||||
import moment from 'moment';
|
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 CreateVehicle = () => {
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
const dispatch = useDispatch();
|
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(() => {
|
useEffect(() => {
|
||||||
if (!AuthService.canAddOrEditVechiles()) {
|
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.')
|
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();
|
AuthService.logout();
|
||||||
navigate(`/login`);
|
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 redirectTo = () => {
|
||||||
const params = new URLSearchParams(window.location.search);
|
const params = new URLSearchParams(window.location.search);
|
||||||
const redirect = params.get('redirect');
|
const redirect = params.get('redirect');
|
||||||
if (redirect === 'schedule') {
|
if (redirect === 'schedule') {
|
||||||
navigate(`/trans-routes/schedule`);
|
navigate(`/trans-routes/schedule`);
|
||||||
} else {
|
} else {
|
||||||
// navigate(`/trans-routes/dashboard`);
|
redirectToList();
|
||||||
redirectToList();
|
}
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const redirectToList = () => {
|
const redirectToList = () => {
|
||||||
navigate('/vehicles/list')
|
navigate('/vehicles/list')
|
||||||
}
|
}
|
||||||
|
|
||||||
const addItemToArray = () => {
|
const addItemToArray = () => {
|
||||||
@ -62,185 +102,409 @@ const CreateVehicle = () => {
|
|||||||
setChecklist(arr);
|
setChecklist(arr);
|
||||||
}
|
}
|
||||||
|
|
||||||
const saveVechile = () => {
|
const formatDateForBackend = (date) => {
|
||||||
const data = {
|
if (!date) return '';
|
||||||
vehicle_number: vehicleNumber,
|
return moment(date).format('MM/DD/YYYY');
|
||||||
tag,
|
}
|
||||||
ezpass,
|
|
||||||
gps_tag: gps,
|
|
||||||
mileage,
|
|
||||||
capacity,
|
|
||||||
year,
|
|
||||||
make,
|
|
||||||
vehicle_model: vehicleModel,
|
|
||||||
status: 'active',
|
|
||||||
checklist,
|
|
||||||
note,
|
|
||||||
vin,
|
|
||||||
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')
|
|
||||||
};
|
|
||||||
dispatch(createVehicle({data, redirectFun: redirectTo}));
|
|
||||||
}
|
|
||||||
|
|
||||||
// const saveDocument = () => {
|
const validateVehicle = () => {
|
||||||
// if (selectedMothlyFile && monthlyInspectionDate) {
|
const errors = [];
|
||||||
// VehicleService.uploadVechileFile(formData, currentVechile.id, currentVehchile.vehicle_number, 'monthlyInspection', monthlyInspectionDate);
|
|
||||||
// }
|
|
||||||
// if (selectedYearlyFile && yearlyInspectionDate) {
|
|
||||||
// VehicleService.uploadVechileFile(formData, currentVechile.id, currentVehchile.vehicle_number, 'yearlyInspection', yearlyInspectionDate);
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
|
|
||||||
|
// 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,
|
||||||
|
responsible_driver: responsibleDriver?.label || '',
|
||||||
|
responsible_driver_id: responsibleDriver?.value || '',
|
||||||
|
capacity: parseInt(capacity) || 0,
|
||||||
|
mileage: parseInt(mileage) || 0,
|
||||||
|
make,
|
||||||
|
vehicle_model: vehicleModel,
|
||||||
|
year,
|
||||||
|
vin,
|
||||||
|
tag,
|
||||||
|
gps_tag: gps,
|
||||||
|
ezpass,
|
||||||
|
has_lift_equip: hasLiftEquip === 'true',
|
||||||
|
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}));
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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 (
|
return (
|
||||||
<>
|
<>
|
||||||
<div className="list row mb-4">
|
<div className="list row mb-4">
|
||||||
<Breadcrumb>
|
<Breadcrumb>
|
||||||
<Breadcrumb.Item>Transportation</Breadcrumb.Item>
|
<Breadcrumb.Item href="/trans-routes/dashboard">Transportation</Breadcrumb.Item>
|
||||||
<Breadcrumb.Item active>
|
<Breadcrumb.Item href="/vehicles/list">
|
||||||
Vehicles Information
|
Vehicles Information
|
||||||
</Breadcrumb.Item>
|
</Breadcrumb.Item>
|
||||||
<Breadcrumb.Item active>
|
<Breadcrumb.Item active>
|
||||||
Create New Vehicle
|
Create New Vehicle
|
||||||
</Breadcrumb.Item>
|
</Breadcrumb.Item>
|
||||||
</Breadcrumb>
|
</Breadcrumb>
|
||||||
<div className="col-md-12 text-primary">
|
<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>
|
</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">
|
<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">
|
<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="app-main-content-fields-section">
|
||||||
<div className="me-4">
|
<div className="me-4">
|
||||||
<div className="field-label">Vehicle Number
|
<div className="field-label">Vehicle Number <span className="required">*</span></div>
|
||||||
<span className="required">*</span>
|
<input type="text" placeholder="e.g., 101" value={vehicleNumber} onChange={e => setVehicleNumber(e.target.value)}/>
|
||||||
</div>
|
</div>
|
||||||
<input type="number" placeholder="e.g.,1" value={vehicleNumber || ''} onChange={e => setVehicleNumber(e.target.value)}/>
|
<div className="me-4">
|
||||||
</div>
|
<div className="field-label">Responsible Driver</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>
|
<Select
|
||||||
<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>
|
value={responsibleDriver}
|
||||||
<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>
|
onChange={setResponsibleDriver}
|
||||||
<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>
|
options={[{value: '', label: ''}, ...drivers.map(driver => ({
|
||||||
</div>
|
value: driver?.id || '',
|
||||||
<div className="app-main-content-fields-section">
|
label: driver?.name || '',
|
||||||
<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>
|
styles={selectStyles}
|
||||||
<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>
|
placeholder="e.g., John Smith"
|
||||||
<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>
|
isClearable
|
||||||
<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>
|
||||||
|
<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>
|
||||||
|
<div className="app-main-content-fields-section">
|
||||||
|
<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>
|
<div className="field-label">Lift Equipped</div>
|
||||||
<select value={hasLiftEquip} onChange={e => setHasLiftEquip(e.target.value)}>
|
<select value={hasLiftEquip} onChange={e => setHasLiftEquip(e.target.value)}>
|
||||||
<option value=""></option>
|
<option value="">Select...</option>
|
||||||
<option value="true">Yes</option>
|
{Object.keys(LIFT_EQUIPPED).map(key => (
|
||||||
<option value="false">No</option>
|
<option key={key} value={LIFT_EQUIPPED[key]}>{LIFT_EQUIPPED_TEXT[LIFT_EQUIPPED[key]]}</option>
|
||||||
</select>
|
))}
|
||||||
|
</select>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<h6 className="text-primary">Vehicle Maintenance & Compliance</h6>
|
<div className="app-main-content-fields-section">
|
||||||
<div className="app-main-content-fields-section">
|
<div className="me-4">
|
||||||
<div className="me-4">
|
<div className="field-label">Fuel Type</div>
|
||||||
<div className="field-label">Last Oil Change Date <span className="field-blurb float-right">1-month due cycle </span></div>
|
<select value={fuelType} onChange={e => setFuelType(e.target.value)}>
|
||||||
<DatePicker selected={oilChangeDate} onChange={(v) => setOilChangeDate(v)} />
|
<option value="">Select...</option>
|
||||||
</div>
|
{Object.keys(FUEL_TYPE).map(key => (
|
||||||
<div className="me-4">
|
<option key={key} value={FUEL_TYPE[key]}>{FUEL_TYPE_TEXT[FUEL_TYPE[key]]}</option>
|
||||||
<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)} />
|
</select>
|
||||||
</div>
|
</div>
|
||||||
<div className="me-4">
|
<div className="me-4">
|
||||||
<div className="field-label">Insurance Expiration Date <span className="field-blurb float-right">1-year due cycle </span></div>
|
<div className="field-label">Title</div>
|
||||||
<DatePicker selected={insuranceExpireOn} onChange={(v) => setInsuranceExpireOn(v)} />
|
<select value={title} onChange={e => setTitle(e.target.value)}>
|
||||||
</div>
|
<option value="">Select...</option>
|
||||||
<div className="me-4">
|
{Object.keys(VEHICLE_TITLE).map(key => (
|
||||||
<div className="field-label">Title Registration Date <span className="field-blurb float-right">1-year due cycle </span></div>
|
<option key={key} value={VEHICLE_TITLE[key]}>{VEHICLE_TITLE_TEXT[VEHICLE_TITLE[key]]}</option>
|
||||||
<DatePicker selected={titleRegistrationOn} onChange={(v) => setTitleRegistrationOn(v)} />
|
))}
|
||||||
</div>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
|
{title === 'other' && (
|
||||||
|
<div className="me-4">
|
||||||
|
<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" 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>
|
||||||
|
))}
|
||||||
|
<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">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>
|
||||||
|
|
||||||
<h6 className="text-primary">Check List</h6>
|
{error && <div className="col-md-12 mb-4 alert alert-danger" role="alert">
|
||||||
<div className="app-main-content-fields-section column">
|
{error}
|
||||||
{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;}))}/>
|
</div>}
|
||||||
<button className="btn btn-link btn-sm" onClick={(e) => setChecklist([...checklist].filter((value, index1) => index1 != index))}>Remove</button>
|
<div className="list row mb-5">
|
||||||
</div>))}
|
<div className="col-md-12 col-sm-12 col-xs-12">
|
||||||
<button className="btn btn-link" onClick={() => addItemToArray()}>+Add New Item</button>
|
<button className="btn btn-default btn-sm float-right" onClick={() => redirectTo()}> Cancel </button>
|
||||||
</div>
|
<button className="btn btn-primary btn-sm float-right" onClick={() => saveVehicle()}> Save </button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</Tab>
|
||||||
|
|
||||||
<h6 className="text-primary">Additional Information</h6>
|
<Tab eventKey="complianceDeadlines" title="Compliance & Deadlines">
|
||||||
<div className="app-main-content-fields-section">
|
<h6 className="text-primary">Compliance & Deadlines</h6>
|
||||||
<div className="me-4">
|
<div className="app-main-content-fields-section">
|
||||||
<div className="field-label">Note</div>
|
<div className="me-4">
|
||||||
<textarea placeholder="Any Extra Details" value={note || ''} onChange={e => setNote(e.target.value)}/>
|
<div className="field-label">Insurance Expiration Date</div>
|
||||||
</div>
|
<DatePicker
|
||||||
</div>
|
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">Vehicle Registration Date</div>
|
||||||
|
<DatePicker
|
||||||
|
selected={vehicleRegistrationDate}
|
||||||
|
onChange={(date) => setVehicleRegistrationDate(date)}
|
||||||
|
dateFormat="MM/dd/yyyy"
|
||||||
|
placeholderText="e.g., 03/01/2024"
|
||||||
|
className="form-control"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
{error && <div className="col-md-12 mb-4 alert alert-danger" role="alert">
|
{error && <div className="col-md-12 mb-4 alert alert-danger" role="alert">
|
||||||
{error}
|
{error}
|
||||||
</div>}
|
</div>}
|
||||||
<div className="col-md-12 col-sm-12 col-xs-12">
|
<div className="list row mb-5">
|
||||||
<button className="btn btn-default btn-sm float-right" onClick={() => redirectTo()}> Cancel </button>
|
<div className="col-md-12 col-sm-12 col-xs-12">
|
||||||
<button className="btn btn-primary btn-sm float-right" onClick={() => saveVechile()}> Save </button>
|
<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>
|
||||||
|
|
||||||
</div>
|
<Tab eventKey="documents" title="Documents & Records">
|
||||||
</Tab>
|
<h6 className="text-primary">Yearly Inspection</h6>
|
||||||
{/* <Tab eventKey="documents" title="Documents">
|
<div className="app-main-content-fields-section">
|
||||||
<h6 className="text-primary">Yearly Vehicle Inspection</h6>
|
<div className="me-4">
|
||||||
<div className="app-main-content-fields-section">
|
<div className="field-label">Yearly Inspection Date</div>
|
||||||
<div className="me-4">
|
<DatePicker
|
||||||
<div className="field-label">Vehicle Inspection Date<span className="required">*</span></div>
|
selected={yearlyInspectionDate}
|
||||||
<DatePicker selected={yearlyInspectionDate} onChange={(v) => setYearlyInspectionDate(v)} />
|
onChange={(date) => setYearlyInspectionDate(date)}
|
||||||
</div>
|
dateFormat="MM/dd/yyyy"
|
||||||
<div className="me-4">
|
placeholderText="e.g., 03/01/2024"
|
||||||
<div className="field-label">Yearly Vehicle Inspection Sheet<span className="required">*</span></div>
|
className="form-control"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div className="me-4">
|
||||||
|
<div className="field-label">Yearly Inspection File</div>
|
||||||
<label className="custom-file-upload">
|
<label className="custom-file-upload">
|
||||||
<Upload width={20} color={"#fff"} className="me-2"></Upload> Upload Files
|
<Upload width={20} color={"#fff"} className="me-2"></Upload> Upload
|
||||||
<input
|
<input type="file" onChange={(e) => setSelectedYearlyFile(e.target.files[0])}/>
|
||||||
type="file"
|
|
||||||
onChange={(e) => setSelectedYearlyFile(e.target.files[0])}
|
|
||||||
/>
|
|
||||||
</label>
|
</label>
|
||||||
<div className="file-name">{ selectedYearlyFile && selectedYearlyFile?.name }</div>
|
<div className="file-name">{selectedYearlyFile?.name}</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<h6 className="text-primary">Monthly Vehicle Inspection</h6>
|
<h6 className="text-primary">Monthly Inspection</h6>
|
||||||
<div className="app-main-content-fields-section">
|
<div className="app-main-content-fields-section">
|
||||||
<div className="me-4">
|
<div className="me-4">
|
||||||
<div className="field-label">Vehicle Inspection Date <span className="required">*</span></div>
|
<div className="field-label">Monthly Inspection Date</div>
|
||||||
<DatePicker selected={monthlyInspectionDate} onChange={(v) => setMonthlyInspectionDate(v)} />
|
<DatePicker
|
||||||
</div>
|
selected={monthlyInspectionDate}
|
||||||
<div className="me-4">
|
onChange={(date) => setMonthlyInspectionDate(date)}
|
||||||
<div className="field-label">Monthly Vehicle Inspection Sheet<span className="required">*</span></div>
|
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">
|
<label className="custom-file-upload">
|
||||||
<Upload width={20} color={"#fff"} className="me-2"></Upload> Upload Files
|
<Upload width={20} color={"#fff"} className="me-2"></Upload> Upload
|
||||||
<input
|
<input type="file" onChange={(e) => setSelectedMonthlyFile(e.target.files[0])}/>
|
||||||
type="file"
|
|
||||||
onChange={(e) => setSelectedMonthlyFile(e.target.files[0])}
|
|
||||||
/>
|
|
||||||
</label>
|
</label>
|
||||||
<div className="file-name">{ selectedMothlyFile && selectedMothlyFile?.name }</div>
|
<div className="file-name">{selectedMonthlyFile?.name}</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<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>
|
|
||||||
|
|
||||||
</div>
|
<h6 className="text-primary">Repair & Maintenance Record</h6>
|
||||||
</Tab>
|
<div className="app-main-content-fields-section">
|
||||||
<Tab eventKey="Repair Records" title="Repair Records">
|
<div className="me-4">
|
||||||
Coming soon...
|
<div className="field-label">Part Name</div>
|
||||||
</Tab> */}
|
<select value={repairPartName} onChange={e => setRepairPartName(e.target.value)}>
|
||||||
</Tabs>
|
<option value="">Select...</option>
|
||||||
</div>
|
{Object.keys(REPAIR_PART_NAME).map(key => (
|
||||||
</div>
|
<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>
|
||||||
|
</Tabs>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@ -1,49 +1,81 @@
|
|||||||
import React, {useEffect, useState} from "react";
|
import React, {useEffect, useState} from "react";
|
||||||
import { useSelector,useDispatch } from "react-redux";
|
import { useSelector, useDispatch } from "react-redux";
|
||||||
import { useNavigate, useParams } from "react-router-dom";
|
import { useNavigate, useParams, useSearchParams } from "react-router-dom";
|
||||||
import { vehicleSlice, selectVehicleError } from "./../../store";
|
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 { 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 DatePicker from "react-datepicker";
|
||||||
|
import Select from 'react-select';
|
||||||
import moment from 'moment';
|
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 UpdateVehicle = () => {
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
const dispatch = useDispatch();
|
const dispatch = useDispatch();
|
||||||
const params = useParams();
|
const params = useParams();
|
||||||
|
const [searchParams] = useSearchParams();
|
||||||
|
const [activeTab, setActiveTab] = useState(searchParams.get('tab') || 'basicInfo');
|
||||||
const vehicles = useSelector((state) => state.vehicles && state.vehicles.vehicles);
|
const vehicles = useSelector((state) => state.vehicles && state.vehicles.vehicles);
|
||||||
const currentVehicle = vehicles.find(item => item.id === params.id ) || undefined;
|
const currentVehicle = vehicles.find(item => item.id === params.id ) || undefined;
|
||||||
const { updateVehicle, deleteVehicle, fetchAllVehicles } = vehicleSlice.actions;
|
const { updateVehicle, deleteVehicle, fetchAllVehicles } = 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 [repairDate, setRepairDate] = useState();
|
|
||||||
const [repairDescription, setRepairDescription] = useState('');
|
|
||||||
const [repairPrice, setRepairPrice] = useState('');
|
|
||||||
const [repairLocation, setRepairLocation] = useState('');
|
|
||||||
const error = useSelector(selectVehicleError);
|
const error = useSelector(selectVehicleError);
|
||||||
const [selectedRepairFile, setSelectedRepairFile] = useState();
|
|
||||||
|
// 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('');
|
||||||
|
|
||||||
|
// Modal
|
||||||
|
const [showDeleteModal, setShowDeleteModal] = useState(false);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!AuthService.canAddOrEditVechiles()) {
|
if (!AuthService.canAddOrEditVechiles()) {
|
||||||
@ -54,43 +86,66 @@ const UpdateVehicle = () => {
|
|||||||
if (!currentVehicle) {
|
if (!currentVehicle) {
|
||||||
dispatch(fetchAllVehicles());
|
dispatch(fetchAllVehicles());
|
||||||
}
|
}
|
||||||
|
// Fetch drivers list
|
||||||
|
DriverService.getAllActiveDrivers('driver', 'active').then(data => {
|
||||||
|
setDrivers(data.data || []);
|
||||||
|
}).catch(() => {
|
||||||
|
setDrivers([]);
|
||||||
|
});
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (currentVehicle) {
|
if (currentVehicle) {
|
||||||
setVehicleNumber(currentVehicle.vehicle_number);
|
// Basic Information
|
||||||
setTag(currentVehicle.tag);
|
setVehicleNumber(currentVehicle.vehicle_number || '');
|
||||||
setMake(currentVehicle.make);
|
setResponsibleDriver(currentVehicle.responsible_driver ? {
|
||||||
setVehicleModel(currentVehicle.vehicle_model);
|
value: currentVehicle.responsible_driver_id || '',
|
||||||
setYear(currentVehicle.year);
|
label: currentVehicle.responsible_driver || ''
|
||||||
setGps(currentVehicle.gps_tag);
|
} : null);
|
||||||
setEzpass(currentVehicle.ezpass);
|
setCapacity(currentVehicle.capacity?.toString() || '');
|
||||||
setMileage(currentVehicle.mileage);
|
setMileage(currentVehicle.mileage?.toString() || '');
|
||||||
setCapacity(currentVehicle.capacity);
|
setMake(currentVehicle.make || '');
|
||||||
setChecklist(currentVehicle.checklist);
|
setVehicleModel(currentVehicle.vehicle_model || '');
|
||||||
setHasLiftEquip(currentVehicle.has_lift_equip === true ? 'true': (currentVehicle?.has_lift_equip === false ? 'false' : undefined) );
|
setYear(currentVehicle.year || '');
|
||||||
setNote(currentVehicle?.note);
|
setVin(currentVehicle.vin || '');
|
||||||
setVin(currentVehicle?.vin);
|
setTag(currentVehicle.tag || '');
|
||||||
setInsuranceExpireOn(currentVehicle.insurance_expire_on && VehicleService.convertToDate(currentVehicle.insurance_expire_on));
|
setGps(currentVehicle.gps_tag || '');
|
||||||
setTitleRegistrationOn(currentVehicle?.title_registration_on && VehicleService.convertToDate(currentVehicle?.title_registration_on));
|
setEzpass(currentVehicle.ezpass || '');
|
||||||
setEmissionTestOn(currentVehicle.title_registration_on && VehicleService.convertToDate(currentVehicle.title_registration_on));
|
setHasLiftEquip(currentVehicle.has_lift_equip === true ? 'true' : (currentVehicle.has_lift_equip === false ? 'false' : ''));
|
||||||
setOilChangeDate(currentVehicle.oil_change_date && VehicleService.convertToDate(currentVehicle.oil_change_date));
|
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 redirectTo = () => {
|
||||||
const params = new URLSearchParams(window.location.search);
|
const params = new URLSearchParams(window.location.search);
|
||||||
const redirect = params.get('redirect');
|
const redirect = params.get('redirect');
|
||||||
if (redirect === 'schedule') {
|
if (redirect === 'schedule') {
|
||||||
navigate(`/trans-routes/schedule`);
|
navigate(`/trans-routes/schedule`);
|
||||||
} else {
|
} else {
|
||||||
if (redirect === 'list') {
|
if (redirect === 'list') {
|
||||||
navigate(`/vehicles/list`)
|
navigate(`/vehicles/list`);
|
||||||
} else {
|
} else {
|
||||||
navigate(`/trans-routes/dashboard`);
|
navigate(`/trans-routes/dashboard`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const redirectToView = () => {
|
||||||
|
navigate(`/vehicles/${params.id}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
const addItemToArray = () => {
|
const addItemToArray = () => {
|
||||||
@ -98,85 +153,172 @@ const UpdateVehicle = () => {
|
|||||||
setChecklist(arr);
|
setChecklist(arr);
|
||||||
}
|
}
|
||||||
|
|
||||||
const saveVechile = () => {
|
const formatDateForBackend = (date) => {
|
||||||
const data = {
|
if (!date) return '';
|
||||||
vehicle_number: vehicleNumber,
|
return moment(date).format('MM/DD/YYYY');
|
||||||
tag,
|
}
|
||||||
ezpass,
|
|
||||||
gps_tag: gps,
|
const buildVehicleData = () => {
|
||||||
mileage,
|
return {
|
||||||
capacity,
|
// Basic Information
|
||||||
year,
|
vehicle_number: vehicleNumber,
|
||||||
make,
|
responsible_driver: responsibleDriver?.label || '',
|
||||||
vehicle_model: vehicleModel,
|
responsible_driver_id: responsibleDriver?.value || '',
|
||||||
status: 'active',
|
capacity: parseInt(capacity) || 0,
|
||||||
checklist,
|
mileage: parseInt(mileage) || 0,
|
||||||
|
make,
|
||||||
|
vehicle_model: vehicleModel,
|
||||||
|
year,
|
||||||
|
vin,
|
||||||
|
tag,
|
||||||
|
gps_tag: gps,
|
||||||
|
ezpass,
|
||||||
|
has_lift_equip: hasLiftEquip === 'true',
|
||||||
|
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,
|
note,
|
||||||
vin,
|
|
||||||
has_lift_equip: hasLiftEquip === 'true',
|
// System fields
|
||||||
insurance_expire_on: moment(insuranceExpireOn).format('MM/DD/YYYY'),
|
status: 'active'
|
||||||
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')
|
|
||||||
};
|
const validateVehicle = () => {
|
||||||
dispatch(updateVehicle({id: params.id, data, redirectFun: redirectTo}));
|
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 deactivateVehicle = () => {
|
||||||
const data = {
|
const data = buildVehicleData();
|
||||||
vehicle_number: vehicleNumber,
|
data.status = 'inactive';
|
||||||
tag,
|
|
||||||
ezpass,
|
|
||||||
gps_tag: gps,
|
|
||||||
mileage,
|
|
||||||
capacity,
|
|
||||||
year,
|
|
||||||
make,
|
|
||||||
vehicle_model: vehicleModel,
|
|
||||||
status: 'inactive',
|
|
||||||
checklist
|
|
||||||
};
|
|
||||||
dispatch(deleteVehicle({id: params.id, data}));
|
dispatch(deleteVehicle({id: params.id, data}));
|
||||||
|
setShowDeleteModal(false);
|
||||||
redirectTo();
|
redirectTo();
|
||||||
}
|
}
|
||||||
|
|
||||||
const saveDocuments = () => {
|
const saveDocuments = () => {
|
||||||
if (selectedMothlyFile && monthlyInspectionDate) {
|
if (selectedMonthlyFile && monthlyInspectionDate) {
|
||||||
const monthlyFormData = new FormData();
|
const monthlyFormData = new FormData();
|
||||||
monthlyFormData.append('file', selectedMothlyFile);
|
monthlyFormData.append('file', selectedMonthlyFile);
|
||||||
VehicleService.uploadVechileFile(monthlyFormData, currentVehicle.id, currentVehicle.vehicle_number, 'monthlyInspection', monthlyInspectionDate);
|
VehicleService.uploadVechileFile(monthlyFormData, currentVehicle.id, currentVehicle.vehicle_number, 'monthlyInspection', monthlyInspectionDate);
|
||||||
}
|
}
|
||||||
if (selectedYearlyFile && yearlyInspectionDate) {
|
if (selectedYearlyFile && yearlyInspectionDate) {
|
||||||
const yearlyFormData = new FormData();
|
const yearlyFormData = new FormData();
|
||||||
yearlyFormData.append('file', selectedYearlyFile);
|
yearlyFormData.append('file', selectedYearlyFile);
|
||||||
VehicleService.uploadVechileFile(yearlyFormData, currentVehicle.id, currentVehicle.vehicle_number, 'yearlyInspection', yearlyInspectionDate);
|
VehicleService.uploadVechileFile(yearlyFormData, currentVehicle.id, currentVehicle.vehicle_number, 'yearlyInspection', yearlyInspectionDate);
|
||||||
}
|
}
|
||||||
redirectTo();
|
redirectTo();
|
||||||
}
|
}
|
||||||
|
|
||||||
const saveRepair = () => {
|
const saveRepair = () => {
|
||||||
const data = {
|
const data = {
|
||||||
vehicle: currentVehicle?.id,
|
vehicle: currentVehicle?.id,
|
||||||
repair_date: moment(repairDate).format('MM/DD/YYYY'),
|
part_name: repairPartName,
|
||||||
repair_description: repairDescription,
|
repair_date: formatDateForBackend(repairReplacementDate),
|
||||||
repair_location: repairLocation,
|
mileage_at_replacement: repairMileage,
|
||||||
repair_price: repairPrice
|
quantity: repairQuantity,
|
||||||
}
|
repair_price: repairCost,
|
||||||
VehicleRepairService.createNewVehicleRepair(data).then(result => {
|
repair_location: repairLocation,
|
||||||
const record = result.data;
|
next_replacement_reminder: repairNextReminder
|
||||||
const formData = new FormData();
|
};
|
||||||
formData.append('file', selectedRepairFile);
|
VehicleRepairService.createNewVehicleRepair(data).then(result => {
|
||||||
VehicleService.uploadVechileFile(formData, currentVehicle.id, record.id, 'repair', repairDate).then(() => redirectTo());
|
const record = result.data;
|
||||||
})
|
if (repairReceiptFile) {
|
||||||
}
|
const formData = new FormData();
|
||||||
|
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 (
|
return (
|
||||||
<>
|
<>
|
||||||
<div className="list row mb-4">
|
<div className="list row mb-4">
|
||||||
<Breadcrumb>
|
<Breadcrumb>
|
||||||
<Breadcrumb.Item>Transportation</Breadcrumb.Item>
|
<Breadcrumb.Item href="/trans-routes/dashboard">Transportation</Breadcrumb.Item>
|
||||||
<Breadcrumb.Item active>
|
<Breadcrumb.Item href="/vehicles/list">
|
||||||
Vehicles Information
|
Vehicles Information
|
||||||
</Breadcrumb.Item>
|
</Breadcrumb.Item>
|
||||||
<Breadcrumb.Item active>
|
<Breadcrumb.Item active>
|
||||||
@ -184,221 +326,323 @@ const UpdateVehicle = () => {
|
|||||||
</Breadcrumb.Item>
|
</Breadcrumb.Item>
|
||||||
</Breadcrumb>
|
</Breadcrumb>
|
||||||
<div className="col-md-12 text-primary">
|
<div className="col-md-12 text-primary">
|
||||||
<h4>Update Vehicle Information <button className="btn btn-link btn-sm" onClick={() => {redirectTo()}}>Back</button></h4>
|
<h4>Update Vehicle Information <button className="btn btn-link btn-sm" onClick={() => {redirectTo()}}>Back</button></h4>
|
||||||
</div>
|
</div>
|
||||||
</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">
|
<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">
|
<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="app-main-content-fields-section">
|
||||||
<div className="me-4">
|
<div className="me-4">
|
||||||
<div className="field-label">Vehicle Number
|
<div className="field-label">Vehicle Number <span className="required">*</span></div>
|
||||||
<span className="required">*</span>
|
<input type="text" placeholder="e.g., 101" value={vehicleNumber} onChange={e => setVehicleNumber(e.target.value)}/>
|
||||||
</div>
|
</div>
|
||||||
<input type="number" placeholder="e.g.,1" value={vehicleNumber || ''} onChange={e => setVehicleNumber(e.target.value)}/>
|
<div className="me-4">
|
||||||
</div>
|
<div className="field-label">Responsible Driver</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>
|
<Select
|
||||||
<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>
|
value={responsibleDriver}
|
||||||
<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>
|
onChange={setResponsibleDriver}
|
||||||
<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>
|
options={[{value: '', label: ''}, ...drivers.map(driver => ({
|
||||||
</div>
|
value: driver?.id || '',
|
||||||
<div className="app-main-content-fields-section">
|
label: driver?.name || '',
|
||||||
<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>
|
styles={selectStyles}
|
||||||
<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>
|
placeholder="e.g., John Smith"
|
||||||
<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>
|
isClearable
|
||||||
<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>
|
||||||
|
<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>
|
||||||
|
<div className="app-main-content-fields-section">
|
||||||
|
<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>
|
<div className="field-label">Lift Equipped</div>
|
||||||
<select value={hasLiftEquip} onChange={e => setHasLiftEquip(e.target.value)}>
|
<select value={hasLiftEquip} onChange={e => setHasLiftEquip(e.target.value)}>
|
||||||
<option value=""></option>
|
<option value="">Select...</option>
|
||||||
<option value="true">Yes</option>
|
{Object.keys(LIFT_EQUIPPED).map(key => (
|
||||||
<option value="false">No</option>
|
<option key={key} value={LIFT_EQUIPPED[key]}>{LIFT_EQUIPPED_TEXT[LIFT_EQUIPPED[key]]}</option>
|
||||||
</select>
|
))}
|
||||||
|
</select>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<h6 className="text-primary">Vehicle Maintenance & Compliance</h6>
|
<div className="app-main-content-fields-section">
|
||||||
<div className="app-main-content-fields-section">
|
<div className="me-4">
|
||||||
<div className="me-4">
|
<div className="field-label">Fuel Type</div>
|
||||||
<div className="field-label">Last Oil Change Date <span className="field-blurb float-right">1-month due cycle </span></div>
|
<select value={fuelType} onChange={e => setFuelType(e.target.value)}>
|
||||||
<DatePicker selected={oilChangeDate} onChange={(v) => setOilChangeDate(v)} />
|
<option value="">Select...</option>
|
||||||
</div>
|
{Object.keys(FUEL_TYPE).map(key => (
|
||||||
<div className="me-4">
|
<option key={key} value={FUEL_TYPE[key]}>{FUEL_TYPE_TEXT[FUEL_TYPE[key]]}</option>
|
||||||
<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)} />
|
</select>
|
||||||
</div>
|
</div>
|
||||||
<div className="me-4">
|
<div className="me-4">
|
||||||
<div className="field-label">Insurance Expiration Date <span className="field-blurb float-right">1-year due cycle </span></div>
|
<div className="field-label">Title</div>
|
||||||
<DatePicker selected={insuranceExpireOn} onChange={(v) => setInsuranceExpireOn(v)} />
|
<select value={title} onChange={e => setTitle(e.target.value)}>
|
||||||
</div>
|
<option value="">Select...</option>
|
||||||
<div className="me-4">
|
{Object.keys(VEHICLE_TITLE).map(key => (
|
||||||
<div className="field-label">Title Registration Date <span className="field-blurb float-right">1-year due cycle </span></div>
|
<option key={key} value={VEHICLE_TITLE[key]}>{VEHICLE_TITLE_TEXT[VEHICLE_TITLE[key]]}</option>
|
||||||
<DatePicker selected={titleRegistrationOn} onChange={(v) => setTitleRegistrationOn(v)} />
|
))}
|
||||||
</div>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
<h6 className="text-primary">Check List</h6>
|
{title === 'other' && (
|
||||||
<div className="app-main-content-fields-section column">
|
<div className="me-4">
|
||||||
{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;}))}/>
|
<div className="field-label">Title (Other)</div>
|
||||||
<button className="btn btn-link btn-sm" onClick={(e) => setChecklist([...checklist].filter((value, index1) => index1 != index))}>Remove</button>
|
<input type="text" placeholder="Please specify..." value={titleOther} onChange={e => setTitleOther(e.target.value)}/>
|
||||||
</div>))}
|
</div>
|
||||||
<button className="btn btn-link" onClick={() => addItemToArray()}>+Add New Item</button>
|
)}
|
||||||
</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" 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>
|
||||||
|
))}
|
||||||
|
<button className="btn btn-link" onClick={() => addItemToArray()}>+Add New Item</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
<h6 className="text-primary">Additional Information</h6>
|
<h6 className="text-primary">Additional Information</h6>
|
||||||
<div className="app-main-content-fields-section">
|
<div className="app-main-content-fields-section">
|
||||||
<div className="me-4">
|
<div className="me-4">
|
||||||
<div className="field-label">Note</div>
|
<div className="field-label">Notes and Attachments</div>
|
||||||
<textarea placeholder="Any Extra Details" value={note || ''} onChange={e => setNote(e.target.value)}/>
|
<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="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>
|
|
||||||
|
|
||||||
</div>
|
|
||||||
</Tab>
|
|
||||||
<Tab eventKey="documents" title="Documents">
|
|
||||||
<h6 className="text-primary">Yearly 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={yearlyInspectionDate} onChange={(v) => setYearlyInspectionDate(v)} />
|
|
||||||
</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])}
|
|
||||||
/>
|
|
||||||
</label>
|
|
||||||
<div className="file-name">{ selectedYearlyFile && selectedYearlyFile?.name }</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<h6 className="text-primary">Monthly Vehicle Inspection</h6>
|
{error && <div className="col-md-12 mb-4 alert alert-danger" role="alert">
|
||||||
<div className="app-main-content-fields-section">
|
{error}
|
||||||
<div className="me-4">
|
</div>}
|
||||||
<div className="field-label">Vehicle Inspection Date <span className="required">*</span></div>
|
<div className="list row mb-5">
|
||||||
<DatePicker selected={monthlyInspectionDate} onChange={(v) => setMonthlyInspectionDate(v)} />
|
<div className="col-md-12 col-sm-12 col-xs-12">
|
||||||
</div>
|
<button className="btn btn-default btn-sm float-right" onClick={() => redirectTo()}> Cancel </button>
|
||||||
<div className="me-4">
|
<button className="btn btn-danger btn-sm me-2 mb-2" onClick={() => triggerShowDeleteModal()}> Delete </button>
|
||||||
<div className="field-label">Monthly Vehicle Inspection Sheet<span className="required">*</span></div>
|
<button className="btn btn-primary btn-sm float-right" onClick={() => saveVehicle()}> Save </button>
|
||||||
<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>
|
||||||
</div>
|
</div>
|
||||||
<div className="col-md-12 col-sm-12 col-xs-12">
|
</Tab>
|
||||||
<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>
|
|
||||||
|
|
||||||
</div>
|
<Tab eventKey="complianceDeadlines" title="Compliance & Deadlines">
|
||||||
</Tab>
|
<h6 className="text-primary">Compliance & Deadlines</h6>
|
||||||
<Tab eventKey="Repair Records" title="Repair Records">
|
<div className="app-main-content-fields-section">
|
||||||
<h6 className="text-primary">Repair Log</h6>
|
<div className="me-4">
|
||||||
<div className="app-main-content-fields-section">
|
<div className="field-label">Insurance Expiration Date</div>
|
||||||
<div className="me-4">
|
<DatePicker
|
||||||
<div className="field-label">Repair Date <span className="required">*</span></div>
|
selected={insuranceStartDate}
|
||||||
<DatePicker selected={repairDate} onChange={(v) => setRepairDate(v)} />
|
onChange={(date) => setInsuranceStartDate(date)}
|
||||||
</div>
|
dateFormat="MM/dd/yyyy"
|
||||||
<div className="me-4"><div className="field-label">Cost <span className="required">*</span></div>
|
placeholderText="e.g., 03/01/2024"
|
||||||
<input type="text" value={repairPrice || ''} placeholder="e.g.,$75" onChange={e => setRepairPrice(e.target.value)}/>
|
className="form-control"
|
||||||
</div>
|
/>
|
||||||
<div className="me-4"><div className="field-label">Repair Location <span className="required">*</span></div>
|
</div>
|
||||||
<input type="text" value={repairLocation || ''} placeholder="e.g.,LocalGarage" onChange={e => setRepairLocation(e.target.value)}/>
|
<div className="me-4">
|
||||||
</div>
|
<div className="field-label">Vehicle Registration Date</div>
|
||||||
</div>
|
<DatePicker
|
||||||
<div className="app-main-content-fields-section">
|
selected={vehicleRegistrationDate}
|
||||||
<div className="me-4">
|
onChange={(date) => setVehicleRegistrationDate(date)}
|
||||||
<div className="field-label">Description <span className="required">*</span></div>
|
dateFormat="MM/dd/yyyy"
|
||||||
<textarea value={repairDescription || ''} onChange={e => setRepairDescription(e.target.value)}/>
|
placeholderText="e.g., 03/01/2024"
|
||||||
</div>
|
className="form-control"
|
||||||
<div className="me-4">
|
/>
|
||||||
<div className="field-label">Upload Maintenance Files</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-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 & 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">
|
<label className="custom-file-upload">
|
||||||
<Upload width={20} color={"#fff"} className="me-2"></Upload> Upload Files
|
<Upload width={20} color={"#fff"} className="me-2"></Upload> Upload
|
||||||
<input
|
<input type="file" onChange={(e) => setSelectedYearlyFile(e.target.files[0])}/>
|
||||||
type="file"
|
|
||||||
onChange={(e) => setSelectedRepairFile(e.target.files[0])}
|
|
||||||
/>
|
|
||||||
</label>
|
</label>
|
||||||
<div className="file-name">{ selectedRepairFile && selectedRepairFile?.name }</div>
|
<div className="file-name">{selectedYearlyFile?.name}</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<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>
|
|
||||||
|
|
||||||
</div>
|
<h6 className="text-primary">Monthly Inspection</h6>
|
||||||
</Tab>
|
<div className="app-main-content-fields-section">
|
||||||
</Tabs>
|
<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 Repair Record </button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</Tab>
|
||||||
|
</Tabs>
|
||||||
<div className="list-func-panel">
|
<div className="list-func-panel">
|
||||||
<button className="btn btn-primary" onClick={() => deactivateVehicle()}><Archive size={16} className="me-2"></Archive>Archive</button>
|
<button className="btn btn-primary" onClick={() => deactivateVehicle()}><Archive size={16} className="me-2"></Archive>Archive</button>
|
||||||
</div>
|
</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>
|
</div>
|
||||||
<div className="list row mb-4">
|
<Modal show={showDeleteModal} onHide={() => closeDeleteModal()}>
|
||||||
<div className="col-md-4 mb-4">
|
<Modal.Header closeButton>
|
||||||
<div>Vehicle Number(*):</div> <input type="number" value={vehicleNumber || ''} onChange={e => setVehicleNumber(e.target.value)}/>
|
<Modal.Title>Delete Vehicle</Modal.Title>
|
||||||
</div>
|
</Modal.Header>
|
||||||
<div className="col-md-4 mb-4">
|
<Modal.Body>
|
||||||
<div>Tag(*):</div> <input type="text" value={tag || ''} onChange={e => setTag(e.target.value)}/>
|
<div>Are you sure you want to delete this vehicle?</div>
|
||||||
</div>
|
</Modal.Body>
|
||||||
<div className="col-md-4 mb-4">
|
<Modal.Footer>
|
||||||
<div>EZ Pass:</div> <input type="text" value={ezpass || ''} onChange={e => setEzpass(e.target.value)}/>
|
<Button variant="secondary" onClick={() => closeDeleteModal()}>
|
||||||
</div>
|
No
|
||||||
<div className="col-md-4 mb-4">
|
</Button>
|
||||||
<div>GPS:</div> <input type="text" value={gps || ''} onChange={e => setGps(e.target.value)}/>
|
<Button variant="primary" onClick={() => deactivateVehicle()}>
|
||||||
</div>
|
Yes
|
||||||
<div className="col-md-4 mb-4">
|
</Button>
|
||||||
<div>Make:</div> <input type="text" value={make || ''} onChange={e => setMake(e.target.value)}/>
|
</Modal.Footer>
|
||||||
</div>
|
</Modal>
|
||||||
<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> */}
|
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@ -68,24 +68,24 @@ const VehicleList = () => {
|
|||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (showInactive) {
|
if (showInactive) {
|
||||||
setFilteredVehicles(vehicles && vehicles.filter(item =>
|
setFilteredVehicles(vehicles && vehicles.filter(item =>
|
||||||
(item?.vehicle_number?.toString()?.includes(keyword.toLowerCase()) ||
|
(item?.vehicle_number?.toString()?.startsWith(keyword.toLowerCase()) ||
|
||||||
item?.tag?.toLowerCase()?.includes(keyword.toLowerCase()) ||
|
item?.tag?.toLowerCase()?.startsWith(keyword.toLowerCase()) ||
|
||||||
item?.ezpass?.toLowerCase()?.includes(keyword.toLowerCase()) ||
|
item?.ezpass?.toLowerCase()?.startsWith(keyword.toLowerCase()) ||
|
||||||
item?.gps_tag?.toLowerCase()?.includes(keyword.toLowerCase()) ||
|
item?.gps_tag?.toLowerCase()?.startsWith(keyword.toLowerCase()) ||
|
||||||
item?.make?.toLowerCase()?.includes(keyword.toLowerCase()) ||
|
item?.make?.toLowerCase()?.startsWith(keyword.toLowerCase()) ||
|
||||||
item?.vehicle_model?.toLowerCase()?.includes(keyword.toLowerCase()) ||
|
item?.vehicle_model?.toLowerCase()?.startsWith(keyword.toLowerCase()) ||
|
||||||
item?.year?.toLowerCase()?.includes(keyword.toLowerCase())) &&
|
item?.year?.toLowerCase()?.startsWith(keyword.toLowerCase())) &&
|
||||||
item?.status?.toLowerCase() !== 'active'
|
item?.status?.toLowerCase() !== 'active'
|
||||||
))
|
))
|
||||||
} else {
|
} else {
|
||||||
setFilteredVehicles(vehicles && vehicles.filter(item =>
|
setFilteredVehicles(vehicles && vehicles.filter(item =>
|
||||||
(item?.vehicle_number?.toString()?.includes(keyword.toLowerCase()) ||
|
(item?.vehicle_number?.toString()?.startsWith(keyword.toLowerCase()) ||
|
||||||
item?.tag?.toLowerCase()?.includes(keyword.toLowerCase()) ||
|
item?.tag?.toLowerCase()?.startsWith(keyword.toLowerCase()) ||
|
||||||
item?.ezpass?.toLowerCase()?.includes(keyword.toLowerCase()) ||
|
item?.ezpass?.toLowerCase()?.startsWith(keyword.toLowerCase()) ||
|
||||||
item?.gps_tag?.toLowerCase()?.includes(keyword.toLowerCase()) ||
|
item?.gps_tag?.toLowerCase()?.startsWith(keyword.toLowerCase()) ||
|
||||||
item?.make?.toLowerCase()?.includes(keyword.toLowerCase()) ||
|
item?.make?.toLowerCase()?.startsWith(keyword.toLowerCase()) ||
|
||||||
item?.vehicle_model?.toLowerCase()?.includes(keyword.toLowerCase()) ||
|
item?.vehicle_model?.toLowerCase()?.startsWith(keyword.toLowerCase()) ||
|
||||||
item?.year?.toLowerCase()?.includes(keyword.toLowerCase())) &&
|
item?.year?.toLowerCase()?.startsWith(keyword.toLowerCase())) &&
|
||||||
item?.status?.toLowerCase() === 'active'
|
item?.status?.toLowerCase() === 'active'
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
@ -203,7 +203,7 @@ const VehicleList = () => {
|
|||||||
<>
|
<>
|
||||||
<div className="list row mb-4">
|
<div className="list row mb-4">
|
||||||
<Breadcrumb>
|
<Breadcrumb>
|
||||||
<Breadcrumb.Item>Transportation</Breadcrumb.Item>
|
<Breadcrumb.Item href="/trans-routes/dashboard">Transportation</Breadcrumb.Item>
|
||||||
<Breadcrumb.Item active>
|
<Breadcrumb.Item active>
|
||||||
Vehicle Information
|
Vehicle Information
|
||||||
</Breadcrumb.Item>
|
</Breadcrumb.Item>
|
||||||
@ -214,7 +214,7 @@ const VehicleList = () => {
|
|||||||
</h4>
|
</h4>
|
||||||
</div>
|
</div>
|
||||||
</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">
|
<div className="app-main-content-list-func-container">
|
||||||
<Tabs defaultActiveKey="activeVehicles" id="vehicles-tab" onSelect={(k) => showArchive(k)}>
|
<Tabs defaultActiveKey="activeVehicles" id="vehicles-tab" onSelect={(k) => showArchive(k)}>
|
||||||
<Tab eventKey="activeVehicles" title="Active Vehicles">
|
<Tab eventKey="activeVehicles" title="Active Vehicles">
|
||||||
|
|||||||
@ -1,18 +1,23 @@
|
|||||||
import React, {useState, useEffect} from "react";
|
import React, {useState, useEffect} from "react";
|
||||||
import { useNavigate, useParams } from "react-router-dom";
|
import { useNavigate, useParams, useSearchParams } from "react-router-dom";
|
||||||
import { useSelector,useDispatch } from "react-redux";
|
import { useSelector, useDispatch } from "react-redux";
|
||||||
import { AuthService, VehicleRepairService, VehicleService } from "../../services";
|
import { AuthService, VehicleRepairService, VehicleService } from "../../services";
|
||||||
import { vehicleSlice, selectVehicleError } from "./../../store";
|
import { vehicleSlice, selectVehicleError } from "./../../store";
|
||||||
import { Spinner, Breadcrumb, BreadcrumbItem, Tabs, Tab } from "react-bootstrap";
|
import { Spinner, Breadcrumb, BreadcrumbItem, Tabs, Tab } from "react-bootstrap";
|
||||||
import { Download, PencilSquare, Archive } from "react-bootstrap-icons";
|
import { Download, PencilSquare, Archive } from "react-bootstrap-icons";
|
||||||
import moment from "moment";
|
import moment from "moment";
|
||||||
import { Export } from "../../shared/components";
|
import { Export } from "../../shared/components";
|
||||||
|
import {
|
||||||
|
FUEL_TYPE_TEXT,
|
||||||
|
VEHICLE_TITLE_TEXT,
|
||||||
|
REPAIR_PART_NAME_TEXT
|
||||||
|
} from "../../shared";
|
||||||
|
|
||||||
const ViewVehicle = () => {
|
const ViewVehicle = () => {
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
const dispatch = useDispatch();
|
const dispatch = useDispatch();
|
||||||
const urlParams = useParams();
|
const urlParams = useParams();
|
||||||
const [currentVehicle, setCurrentVehicle] = useState(undefined);
|
const [searchParams] = useSearchParams();
|
||||||
const [monthlyDocs, setMonthlyDocs] = useState([]);
|
const [monthlyDocs, setMonthlyDocs] = useState([]);
|
||||||
const [yearlyDocs, setYearlyDocs] = useState([]);
|
const [yearlyDocs, setYearlyDocs] = useState([]);
|
||||||
const [repairs, setRepairs] = useState([]);
|
const [repairs, setRepairs] = useState([]);
|
||||||
@ -23,36 +28,37 @@ const ViewVehicle = () => {
|
|||||||
const [selectedItemsMonthly, setSelectedItemsMonthly] = useState([]);
|
const [selectedItemsMonthly, setSelectedItemsMonthly] = useState([]);
|
||||||
const [selectedItemsYearly, setSelectedItemsYearly] = useState([]);
|
const [selectedItemsYearly, setSelectedItemsYearly] = useState([]);
|
||||||
const [selectedItemsRepair, setSelectedItemsRepair] = useState([]);
|
const [selectedItemsRepair, setSelectedItemsRepair] = useState([]);
|
||||||
const [filteredMonthlyDocs, setFilteredMonthlyDocs] = useState(monthlyDocs);
|
const [filteredMonthlyDocs, setFilteredMonthlyDocs] = useState(monthlyDocs);
|
||||||
const [filteredYearlyDocs, setFilteredYearlyDocs] = useState(yearlyDocs);
|
const [filteredYearlyDocs, setFilteredYearlyDocs] = useState(yearlyDocs);
|
||||||
const [filteredRepairs, setFilteredRepairs] = useState(repairs);
|
const [filteredRepairs, setFilteredRepairs] = useState(repairs);
|
||||||
const { updateVehicle, deleteVehicle, fetchAllVehicles } = vehicleSlice.actions;
|
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 = () => {
|
const redirectTo = () => {
|
||||||
navigate(`/vehicles/list`)
|
navigate(`/vehicles/list`);
|
||||||
}
|
}
|
||||||
|
|
||||||
const goToEdit = (id) => {
|
const goToEdit = (id) => {
|
||||||
navigate(`/vehicles/edit/${id}?redirect=list`)
|
navigate(`/vehicles/edit/${id}?redirect=list&tab=${currentTab}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
const download = () => {
|
const download = () => {
|
||||||
const downloadArr = [...selectedItemsMonthly, ...selectedItemsYearly];
|
const downloadArr = [...selectedItemsMonthly, ...selectedItemsYearly];
|
||||||
downloadArr.forEach((url) => {
|
downloadArr.forEach((url) => {
|
||||||
const a = document.createElement('a');
|
const a = document.createElement('a');
|
||||||
a.href= url;
|
a.href = url;
|
||||||
a.download = '';
|
a.download = '';
|
||||||
document.body.append(a);
|
document.body.append(a);
|
||||||
a.click();
|
a.click();
|
||||||
document.body.removeChild(a);
|
document.body.removeChild(a);
|
||||||
})
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
const deactivateVehicle = () => {
|
const deactivateVehicle = () => {
|
||||||
const data = {
|
const data = {
|
||||||
status: 'inactive'
|
status: 'inactive'
|
||||||
};
|
};
|
||||||
dispatch(deleteVehicle({id: urlParams.id, data}));
|
dispatch(deleteVehicle({id: urlParams.id, data}));
|
||||||
redirectTo();
|
redirectTo();
|
||||||
}
|
}
|
||||||
@ -64,7 +70,6 @@ const ViewVehicle = () => {
|
|||||||
if (prefix) {
|
if (prefix) {
|
||||||
const arr2 = prefix.split('_');
|
const arr2 = prefix.split('_');
|
||||||
const dateNumber = arr2[arr2.length - 1];
|
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');
|
return dateNumber ? new Date(parseInt(dateNumber)).toLocaleDateString('en-US', {month: '2-digit', day: '2-digit', year: 'numeric'}) : moment().format('MM/DD/YYYY');
|
||||||
} else {
|
} else {
|
||||||
return moment().format('MM/DD/YYYY');
|
return moment().format('MM/DD/YYYY');
|
||||||
@ -78,7 +83,6 @@ const ViewVehicle = () => {
|
|||||||
};
|
};
|
||||||
const getAllRepairs = async (vid) => {
|
const getAllRepairs = async (vid) => {
|
||||||
const v_repairs = (await VehicleRepairService.getAll(vid)).data;
|
const v_repairs = (await VehicleRepairService.getAll(vid)).data;
|
||||||
console.log('repairs', v_repairs);
|
|
||||||
setRepairs(v_repairs);
|
setRepairs(v_repairs);
|
||||||
}
|
}
|
||||||
if (!AuthService.canViewVechiles()) {
|
if (!AuthService.canViewVechiles()) {
|
||||||
@ -91,7 +95,7 @@ const ViewVehicle = () => {
|
|||||||
setCurrentVehicle(data.data);
|
setCurrentVehicle(data.data);
|
||||||
getAllDocuments(data.data?.id, data.data?.vehicle_number);
|
getAllDocuments(data.data?.id, data.data?.vehicle_number);
|
||||||
getAllRepairs(data.data?.id);
|
getAllRepairs(data.data?.id);
|
||||||
})
|
});
|
||||||
} else {
|
} else {
|
||||||
getAllDocuments(currentVehicle?.id, currentVehicle?.vehicle_number);
|
getAllDocuments(currentVehicle?.id, currentVehicle?.vehicle_number);
|
||||||
getAllRepairs(currentVehicle?.id);
|
getAllRepairs(currentVehicle?.id);
|
||||||
@ -104,8 +108,14 @@ const ViewVehicle = () => {
|
|||||||
}, [keyword, yearlyDocs, monthlyDocs]);
|
}, [keyword, yearlyDocs, monthlyDocs]);
|
||||||
|
|
||||||
useEffect(() => {
|
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()) ));
|
setFilteredRepairs(repairs?.filter(item =>
|
||||||
}, [keyword, repairs])
|
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(() => {
|
useEffect(() => {
|
||||||
const newYearlyDocs = [...yearlyDocs];
|
const newYearlyDocs = [...yearlyDocs];
|
||||||
@ -114,7 +124,7 @@ const ViewVehicle = () => {
|
|||||||
});
|
});
|
||||||
setYearlyDocs(
|
setYearlyDocs(
|
||||||
sortingMonthly.order === 'asc' ? sortedYearlyDocs : sortedYearlyDocs.reverse()
|
sortingMonthly.order === 'asc' ? sortedYearlyDocs : sortedYearlyDocs.reverse()
|
||||||
)
|
);
|
||||||
}, [sortingYearly]);
|
}, [sortingYearly]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@ -124,7 +134,7 @@ const ViewVehicle = () => {
|
|||||||
});
|
});
|
||||||
setMonthlyDocs(
|
setMonthlyDocs(
|
||||||
sortingMonthly.order === 'asc' ? sortedMonthlyDocs : sortedMonthlyDocs.reverse()
|
sortingMonthly.order === 'asc' ? sortedMonthlyDocs : sortedMonthlyDocs.reverse()
|
||||||
)
|
);
|
||||||
}, [sortingMonthly]);
|
}, [sortingMonthly]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@ -134,73 +144,41 @@ const ViewVehicle = () => {
|
|||||||
});
|
});
|
||||||
setRepairs(
|
setRepairs(
|
||||||
sortingRepair.order === 'asc' ? sortedRepairs : sortedRepairs.reverse()
|
sortingRepair.order === 'asc' ? sortedRepairs : sortedRepairs.reverse()
|
||||||
)
|
);
|
||||||
}, [sortingRepair]);
|
}, [sortingRepair]);
|
||||||
|
|
||||||
|
// Helper to format Yes/No values
|
||||||
|
const formatYesNo = (value) => {
|
||||||
|
if (value === true) return 'Yes';
|
||||||
|
if (value === false) return 'No';
|
||||||
|
return value || '';
|
||||||
|
};
|
||||||
|
|
||||||
const columnsMonthly = [
|
const columnsMonthly = [
|
||||||
{
|
{ key: 'name', label: 'Monthly Vehicle Inspection Sheet' },
|
||||||
key: 'name',
|
{ key: 'inspectionDate', label: 'Vehicle Inspection Date' },
|
||||||
label: 'Monthly Vehicle Inspection Sheet'
|
{ key: 'createdAt', label: 'Date Added' }
|
||||||
},
|
|
||||||
{
|
|
||||||
key: 'inspectionDate',
|
|
||||||
label: 'Vehicle Inspection Date'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
key: 'createdAt',
|
|
||||||
label: 'Date Added'
|
|
||||||
}
|
|
||||||
];
|
];
|
||||||
|
|
||||||
const columnsYearly = [
|
const columnsYearly = [
|
||||||
{
|
{ key: 'name', label: 'Yearly Vehicle Inspection Sheet' },
|
||||||
key: 'name',
|
{ key: 'inspectionDate', label: 'Vehicle Inspection Date' },
|
||||||
label: 'Yearly Vehicle Inspection Sheet'
|
{ key: 'createdAt', label: 'Date Added' }
|
||||||
},
|
|
||||||
{
|
|
||||||
key: 'inspectionDate',
|
|
||||||
label: 'Vehicle Inspection Date'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
key: 'createdAt',
|
|
||||||
label: 'Date Added'
|
|
||||||
}
|
|
||||||
];
|
];
|
||||||
|
|
||||||
const columnsRepair = [
|
const columnsRepair = [
|
||||||
{
|
{ key: 'part_name', label: 'Part Name', show: true },
|
||||||
key: 'repair_description',
|
{ key: 'repair_date', label: 'Replacement Date', show: true },
|
||||||
label: 'Repair Description',
|
{ key: 'mileage_at_replacement', label: 'Mileage', show: true },
|
||||||
show: true
|
{ key: 'quantity', label: 'Quantity', show: true },
|
||||||
},
|
{ key: 'repair_price', label: 'Cost', show: true },
|
||||||
{
|
{ key: 'repair_location', label: 'Location', show: true },
|
||||||
key: 'repair_date',
|
{ key: 'next_replacement_reminder', label: 'Next Reminder', show: true },
|
||||||
label: 'Repair Date',
|
{ key: 'create_date', label: 'Date Added', show: true }
|
||||||
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
|
|
||||||
}
|
|
||||||
];
|
];
|
||||||
|
|
||||||
const sortTableWithFieldMonthly = (key) => {
|
const sortTableWithFieldMonthly = (key) => {
|
||||||
let newSorting = {
|
let newSorting = { key, order: 'asc' };
|
||||||
key,
|
|
||||||
order: 'asc',
|
|
||||||
}
|
|
||||||
|
|
||||||
if (sortingMonthly.key === key && sortingMonthly.order === 'asc') {
|
if (sortingMonthly.key === key && sortingMonthly.order === 'asc') {
|
||||||
newSorting = {...newSorting, order: 'desc'};
|
newSorting = {...newSorting, order: 'desc'};
|
||||||
}
|
}
|
||||||
@ -208,11 +186,7 @@ const ViewVehicle = () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const sortTableWithFieldYearly = (key) => {
|
const sortTableWithFieldYearly = (key) => {
|
||||||
let newSorting = {
|
let newSorting = { key, order: 'asc' };
|
||||||
key,
|
|
||||||
order: 'asc',
|
|
||||||
}
|
|
||||||
|
|
||||||
if (sortingYearly.key === key && sortingYearly.order === 'asc') {
|
if (sortingYearly.key === key && sortingYearly.order === 'asc') {
|
||||||
newSorting = {...newSorting, order: 'desc'};
|
newSorting = {...newSorting, order: 'desc'};
|
||||||
}
|
}
|
||||||
@ -220,11 +194,7 @@ const ViewVehicle = () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const sortTableWithFieldRepair = (key) => {
|
const sortTableWithFieldRepair = (key) => {
|
||||||
let newSorting = {
|
let newSorting = { key, order: 'asc' };
|
||||||
key,
|
|
||||||
order: 'asc',
|
|
||||||
}
|
|
||||||
|
|
||||||
if (sortingRepair.key === key && sortingRepair.order === 'asc') {
|
if (sortingRepair.key === key && sortingRepair.order === 'asc') {
|
||||||
newSorting = {...newSorting, order: 'desc'};
|
newSorting = {...newSorting, order: 'desc'};
|
||||||
}
|
}
|
||||||
@ -252,13 +222,13 @@ const ViewVehicle = () => {
|
|||||||
const toggleSelectedAllItemsRepair = () => {
|
const toggleSelectedAllItemsRepair = () => {
|
||||||
if (selectedItemsRepair.length !== filteredRepairs.length || filteredRepairs.length === 0) {
|
if (selectedItemsRepair.length !== filteredRepairs.length || filteredRepairs.length === 0) {
|
||||||
const newSelectedItems = [...filteredRepairs].map((doc) => doc.id);
|
const newSelectedItems = [...filteredRepairs].map((doc) => doc.id);
|
||||||
selectedItemsRepair(newSelectedItems);
|
setSelectedItemsRepair(newSelectedItems);
|
||||||
} else {
|
} else {
|
||||||
selectedItemsRepair([]);
|
setSelectedItemsRepair([]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const toggleItemYearly = (id) => {
|
const toggleItemYearly = (id) => {
|
||||||
if (selectedItemsYearly.includes(id)) {
|
if (selectedItemsYearly.includes(id)) {
|
||||||
const newSelectedItems = [...selectedItemsYearly].filter((item) => item !== id);
|
const newSelectedItems = [...selectedItemsYearly].filter((item) => item !== id);
|
||||||
setSelectedItemsYearly(newSelectedItems);
|
setSelectedItemsYearly(newSelectedItems);
|
||||||
@ -268,7 +238,7 @@ const ViewVehicle = () => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const toggleItemMonthly = (id) => {
|
const toggleItemMonthly = (id) => {
|
||||||
if (selectedItemsMonthly.includes(id)) {
|
if (selectedItemsMonthly.includes(id)) {
|
||||||
const newSelectedItems = [...selectedItemsMonthly].filter((item) => item !== id);
|
const newSelectedItems = [...selectedItemsMonthly].filter((item) => item !== id);
|
||||||
setSelectedItemsMonthly(newSelectedItems);
|
setSelectedItemsMonthly(newSelectedItems);
|
||||||
@ -278,7 +248,7 @@ const ViewVehicle = () => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const toggleItemRepair = (id) => {
|
const toggleItemRepair = (id) => {
|
||||||
if (selectedItemsRepair.includes(id)) {
|
if (selectedItemsRepair.includes(id)) {
|
||||||
const newSelectedItems = [...selectedItemsRepair].filter((item) => item !== id);
|
const newSelectedItems = [...selectedItemsRepair].filter((item) => item !== id);
|
||||||
setSelectedItemsRepair(newSelectedItems);
|
setSelectedItemsRepair(newSelectedItems);
|
||||||
@ -309,7 +279,7 @@ const ViewVehicle = () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const checkSelectAllRepair = () => {
|
const checkSelectAllRepair = () => {
|
||||||
return selectedItemsRepair.length === selectedItemsRepair.length && selectedItemsRepair.length > 0;
|
return selectedItemsRepair.length === filteredRepairs.length && selectedItemsRepair.length > 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
const changeTab = (k) => {
|
const changeTab = (k) => {
|
||||||
@ -320,107 +290,116 @@ const ViewVehicle = () => {
|
|||||||
setSortingRepair({key: '', order: ''});
|
setSortingRepair({key: '', order: ''});
|
||||||
}
|
}
|
||||||
|
|
||||||
const tableMonthly = <div className="list row mb-4">
|
const tableMonthly = (
|
||||||
<div className="col-md-12">
|
<div className="list row mb-4">
|
||||||
<table className="personnel-info-table">
|
<div className="col-md-12">
|
||||||
<thead>
|
<table className="personnel-info-table">
|
||||||
<tr>
|
<thead>
|
||||||
<th className="th-checkbox"><input type="checkbox" checked={checkSelectAllMonthly()} onClick={() => toggleSelectedAllItemsMonthly()}></input></th>
|
<tr>
|
||||||
<th className="th-index">No.</th>
|
<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) => (
|
||||||
{column.label} <span className="float-right" onClick={() => sortTableWithFieldMonthly(column.key)}><img src={`/images/${getSortingImgMonthly(column.key)}.png`}></img></span>
|
<th className="sortable-header" key={index}>
|
||||||
</th>)
|
{column.label} <span className="float-right" onClick={() => sortTableWithFieldMonthly(column.key)}><img src={`/images/${getSortingImgMonthly(column.key)}.png`}></img></span>
|
||||||
}
|
</th>
|
||||||
</tr>
|
))}
|
||||||
|
</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>
|
||||||
|
<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>{doc?.inspectionDate}</td>
|
||||||
|
<td>{new Date(doc?.createdAt).toLocaleDateString('en-US', {month: '2-digit', day: '2-digit', year: 'numeric'})}</td>
|
||||||
|
</tr>
|
||||||
|
))}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
|
||||||
</thead>
|
const tableYearly = (
|
||||||
<tbody>
|
<div className="list row mb-4">
|
||||||
{
|
<div className="col-md-12">
|
||||||
filteredMonthlyDocs.map((doc, index) => <tr key={doc.url}>
|
<table className="personnel-info-table">
|
||||||
<td className="td-checkbox"><input type="checkbox" checked={selectedItemsMonthly.includes(doc?.url)} onClick={()=>toggleItemMonthly(doc?.url)}/></td>
|
<thead>
|
||||||
<td className="td-index">{index + 1}</td>
|
<tr>
|
||||||
<td> <PencilSquare size={16} className="clickable me-2" onClick={() => goToEdit(currentVehicle?.id)}></PencilSquare>
|
<th className="th-checkbox"><input type="checkbox" checked={checkSelectAllYearly()} onClick={() => toggleSelectedAllItemsYearly()}></input></th>
|
||||||
<a className="btn btn-link btn-sm" href={doc?.url} target="_blank">{doc?.name}</a> </td>
|
<th className="th-index">No.</th>
|
||||||
<td>{doc?.inspectionDate}</td>
|
{columnsYearly.map((column, index) => (
|
||||||
<td>{new Date(doc?.createdAt).toLocaleDateString('en-US', {month: '2-digit', day: '2-digit', year: 'numeric'})}</td>
|
<th className="sortable-header" key={index}>
|
||||||
</tr>)
|
{column.label} <span className="float-right" onClick={() => sortTableWithFieldYearly(column.key)}><img src={`/images/${getSortingImgYearly(column.key)}.png`}></img></span>
|
||||||
}
|
</th>
|
||||||
</tbody>
|
))}
|
||||||
</table>
|
</tr>
|
||||||
</div>
|
</thead>
|
||||||
</div>;
|
<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>
|
||||||
|
<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>{doc?.inspectionDate}</td>
|
||||||
|
<td>{new Date(doc?.createdAt).toLocaleDateString('en-US', {month: '2-digit', day: '2-digit', year: 'numeric'})}</td>
|
||||||
|
</tr>
|
||||||
|
))}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
|
||||||
const tableYearly = <div className="list row mb-4">
|
const tableRepair = (
|
||||||
<div className="col-md-12">
|
<div className="list row mb-4">
|
||||||
<table className="personnel-info-table">
|
<div className="col-md-12">
|
||||||
<thead>
|
<table className="personnel-info-table">
|
||||||
<tr>
|
<thead>
|
||||||
<th className="th-checkbox"><input type="checkbox" checked={checkSelectAllYearly()} onClick={() => toggleSelectedAllItemsYearly()}></input></th>
|
<tr>
|
||||||
<th className="th-index">No.</th>
|
<th className="th-checkbox"><input type="checkbox" checked={checkSelectAllRepair()} onClick={() => toggleSelectedAllItemsRepair()}></input></th>
|
||||||
{
|
<th className="th-index">No.</th>
|
||||||
columnsYearly.map((column, index) => <th className="sortable-header" key={index}>
|
{columnsRepair.map((column, index) => (
|
||||||
{column.label} <span className="float-right" onClick={() => sortTableWithFieldYearly(column.key)}><img src={`/images/${getSortingImgYearly(column.key)}.png`}></img></span>
|
<th className="sortable-header" key={index}>
|
||||||
</th>)
|
{column.label} <span className="float-right" onClick={() => sortTableWithFieldRepair(column.key)}><img src={`/images/${getSortingImgRepair(column.key)}.png`}></img></span>
|
||||||
}
|
</th>
|
||||||
</tr>
|
))}
|
||||||
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
{
|
{filteredRepairs?.map((repair, index) => (
|
||||||
filteredYearlyDocs.map((doc, index) => <tr key={doc.url}>
|
<tr key={repair.id}>
|
||||||
<td className="td-checkbox"><input type="checkbox" checked={selectedItemsYearly.includes(doc?.url)} onClick={()=>toggleItemYearly(doc?.url)}/></td>
|
<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 className="td-index">{index + 1}</td>
|
||||||
<td> <PencilSquare size={16} className="clickable me-2" onClick={() => goToEdit(currentVehicle?.id)}></PencilSquare>
|
<td>{REPAIR_PART_NAME_TEXT[repair?.part_name] || repair?.part_name || repair?.repair_description}</td>
|
||||||
<a className="btn btn-link btn-sm" href={doc?.url} target="_blank">{doc?.name}</a> </td>
|
<td>{repair?.repair_date}</td>
|
||||||
<td>{doc?.inspectionDate}</td>
|
<td>{repair?.mileage_at_replacement}</td>
|
||||||
<td>{new Date(doc?.createdAt).toLocaleDateString('en-US', {month: '2-digit', day: '2-digit', year: 'numeric'})}</td>
|
<td>{repair?.quantity}</td>
|
||||||
</tr>)
|
<td>{repair?.repair_price}</td>
|
||||||
}
|
<td>{repair?.repair_location}</td>
|
||||||
</tbody>
|
<td>{repair?.next_replacement_reminder}</td>
|
||||||
</table>
|
<td>{repair?.create_date ? new Date(repair?.create_date).toLocaleDateString('en-US', {month: '2-digit', day: '2-digit', year: 'numeric'}) : repair?.repair_date}</td>
|
||||||
</div>
|
</tr>
|
||||||
</div>;
|
))}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
const tableRepair = <div className="list row mb-4">
|
</div>
|
||||||
<div className="col-md-12">
|
</div>
|
||||||
<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}>
|
|
||||||
{column.label} <span className="float-right" onClick={() => sortTableWithFieldRepair(column.key)}><img src={`/images/${getSortingImgRepair(column.key)}.png`}></img></span>
|
|
||||||
</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>
|
|
||||||
<td className="td-index">{index + 1}</td>
|
|
||||||
<td> {repair?.repair_description}</td>
|
|
||||||
<td>{repair?.repair_date}</td>
|
|
||||||
<td>{repair?.repair_price}</td>
|
|
||||||
<td>{repair?.repair_location}</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>)
|
|
||||||
}
|
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
</div>
|
|
||||||
</div>;
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<div className="list row mb-4">
|
<div className="list row mb-4">
|
||||||
<Breadcrumb>
|
<Breadcrumb>
|
||||||
<Breadcrumb.Item>Transportation</Breadcrumb.Item>
|
<Breadcrumb.Item href="/trans-routes/dashboard">Transportation</Breadcrumb.Item>
|
||||||
<Breadcrumb.Item active>
|
<Breadcrumb.Item href="/vehicles/list">
|
||||||
Vehicles Information
|
Vehicles Information
|
||||||
</Breadcrumb.Item>
|
</Breadcrumb.Item>
|
||||||
<Breadcrumb.Item active>
|
<Breadcrumb.Item active>
|
||||||
@ -429,18 +408,22 @@ const tableRepair = <div className="list row mb-4">
|
|||||||
</Breadcrumb>
|
</Breadcrumb>
|
||||||
</div>
|
</div>
|
||||||
<div className="col-md-12 text-primary">
|
<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>
|
||||||
<div className="app-main-content-list-container">
|
<div className="app-main-content-list-container form-page">
|
||||||
<div className="app-main-content-list-func-container">
|
<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">
|
<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="app-main-content-fields-section">
|
||||||
<div className="field-body">
|
<div className="field-body">
|
||||||
<div className="field-label">Vehicle Number</div>
|
<div className="field-label">Vehicle Number</div>
|
||||||
<div className="field-value">{currentVehicle?.vehicle_number}</div>
|
<div className="field-value">{currentVehicle?.vehicle_number}</div>
|
||||||
</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-body">
|
||||||
<div className="field-label">Seating Capacity</div>
|
<div className="field-label">Seating Capacity</div>
|
||||||
<div className="field-value">{currentVehicle?.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-label">Mileage</div>
|
||||||
<div className="field-value">{currentVehicle?.mileage}</div>
|
<div className="field-value">{currentVehicle?.mileage}</div>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="app-main-content-fields-section">
|
||||||
<div className="field-body">
|
<div className="field-body">
|
||||||
<div className="field-label">Make</div>
|
<div className="field-label">Make</div>
|
||||||
<div className="field-value">{currentVehicle?.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-label">Model</div>
|
||||||
<div className="field-value">{currentVehicle?.vehicle_model}</div>
|
<div className="field-value">{currentVehicle?.vehicle_model}</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
<div className="app-main-content-fields-section">
|
|
||||||
<div className="field-body">
|
<div className="field-body">
|
||||||
<div className="field-label">Year</div>
|
<div className="field-label">Year</div>
|
||||||
<div className="field-value">{currentVehicle?.year}</div>
|
<div className="field-value">{currentVehicle?.year}</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="field-body">
|
<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 className="field-value">{currentVehicle?.vin}</div>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="app-main-content-fields-section">
|
||||||
<div className="field-body">
|
<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 className="field-value">{currentVehicle?.tag}</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="field-body">
|
<div className="field-body">
|
||||||
<div className="field-label">GPS ID</div>
|
<div className="field-label">GPS ID</div>
|
||||||
<div className="field-value">{currentVehicle?.gps}</div>
|
<div className="field-value">{currentVehicle?.gps_tag}</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="field-body">
|
<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 className="field-value">{currentVehicle?.ezpass}</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="field-body">
|
<div className="field-body">
|
||||||
<div className="field-label">Lift Equipped</div>
|
<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>
|
||||||
</div>
|
</div>
|
||||||
<h6 className="text-primary">Vehicle Maintenance & Compliance</h6>
|
|
||||||
<div className="app-main-content-fields-section">
|
<div className="app-main-content-fields-section">
|
||||||
<div className="field-body">
|
<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-label">Fuel Type</div>
|
||||||
<div className="field-value">{currentVehicle?.oil_change_date}</div>
|
<div className="field-value">{FUEL_TYPE_TEXT[currentVehicle?.fuel_type] || currentVehicle?.fuel_type}</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="field-body">
|
<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-label">Title</div>
|
||||||
<div className="field-value">{currentVehicle?.emission_test_on}</div>
|
<div className="field-value">
|
||||||
</div>
|
{VEHICLE_TITLE_TEXT[currentVehicle?.title] || currentVehicle?.title}
|
||||||
<div className="field-body">
|
{currentVehicle?.title === 'other' && currentVehicle?.title_other && ` (${currentVehicle?.title_other})`}
|
||||||
<div className="field-label">Insurance Expiration Date <span className="field-blurb float-right">1-year due cycle </span></div>
|
</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>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<h6 className="text-primary">Check List</h6>
|
<h6 className="text-primary">Check List</h6>
|
||||||
<div className="app-main-content-fields-section column">
|
<div className="app-main-content-fields-section column">
|
||||||
<ul>
|
<ul>
|
||||||
{currentVehicle?.checklist?.map((item) => <li>{item}</li>)}
|
{currentVehicle?.checklist?.map((item, index) => <li key={index}>{item}</li>)}
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<h6 className="text-primary">Additional Information</h6>
|
<h6 className="text-primary">Additional Information</h6>
|
||||||
<div className="app-main-content-fields-section">
|
<div className="app-main-content-fields-section">
|
||||||
<div className="field-body">
|
<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 className="field-value">{currentVehicle?.note}</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</Tab>
|
</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}
|
{tableYearly}
|
||||||
<h6 className="text-primary">Monthly Vehicle Inspection</h6>
|
<h6 className="text-primary">Monthly Inspection</h6>
|
||||||
{tableMonthly}
|
{tableMonthly}
|
||||||
</Tab>
|
</Tab>
|
||||||
<Tab eventKey="repairRecords" title="Repair Records">
|
|
||||||
{tableRepair}
|
<Tab eventKey="repairRecords" title="Repair & Maintenance">
|
||||||
</Tab>
|
{tableRepair}
|
||||||
|
</Tab>
|
||||||
</Tabs>
|
</Tabs>
|
||||||
<div className="list-func-panel">
|
<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' || currentTab === 'repairRecords') && (
|
||||||
{currentTab === 'documents' && <button className="btn btn-primary" onClick={() => download()}><Download size={16} className="me-2"></Download>Download</button>}
|
<input className="me-2 with-search-icon" type="text" placeholder="Search" value={keyword} onChange={(e) => setKeyword(e.currentTarget.value)} />
|
||||||
{currentTab === 'repairRecords' && <Export
|
)}
|
||||||
columns={columnsRepair}
|
{currentTab === 'documents' && (
|
||||||
data={filteredRepairs}
|
<button className="btn btn-primary" onClick={() => download()}><Download size={16} className="me-2"></Download>Download</button>
|
||||||
filename={`vehicle-${currentVehicle?.vehicle_number}-repairs`}
|
)}
|
||||||
/>}
|
{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>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@ -2,7 +2,7 @@ import axios from "axios";
|
|||||||
export default axios.create({
|
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"),
|
// 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:
|
// 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",
|
// baseURL: "http://localhost:8080/api",
|
||||||
headers: {
|
headers: {
|
||||||
"Content-type": "application/json"
|
"Content-type": "application/json"
|
||||||
|
|||||||
@ -44,6 +44,10 @@ const getAvatar = (filename) => {
|
|||||||
return http.get(`/files/${filename}`);
|
return http.get(`/files/${filename}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const getAvatarAsBlob = (filename) => {
|
||||||
|
return http.get(`/files/${filename}`, { responseType: 'blob' });
|
||||||
|
}
|
||||||
|
|
||||||
const deleteFile = (data) => {
|
const deleteFile = (data) => {
|
||||||
return http.post(`/files/delete`, data);
|
return http.post(`/files/delete`, data);
|
||||||
}
|
}
|
||||||
@ -69,6 +73,7 @@ export const CustomerService = {
|
|||||||
getAllActiveCustomers,
|
getAllActiveCustomers,
|
||||||
uploadAvatar,
|
uploadAvatar,
|
||||||
getAvatar,
|
getAvatar,
|
||||||
|
getAvatarAsBlob,
|
||||||
deleteFile,
|
deleteFile,
|
||||||
createNewCustomer,
|
createNewCustomer,
|
||||||
updateCustomer,
|
updateCustomer,
|
||||||
|
|||||||
@ -82,7 +82,8 @@ const getTransportationInfo = (eventsList, item, timeDocs = []) => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const interpreterLevelOptions = [
|
// Language Support options
|
||||||
|
const languageSupportOptions = [
|
||||||
{
|
{
|
||||||
value: 'Checkin',
|
value: 'Checkin',
|
||||||
label: 'Checkin'
|
label: 'Checkin'
|
||||||
@ -97,22 +98,30 @@ const interpreterLevelOptions = [
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
value: 'Office',
|
value: 'Office',
|
||||||
label: 'Office inperson'
|
label: 'Office in person'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
value: 'Office(Phone)',
|
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',
|
value: 'red',
|
||||||
label: 'EyesOn'
|
label: 'Eyes-On'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
value: 'brown',
|
value: 'pink',
|
||||||
label: 'ByOwn',
|
label: 'Self-Transport',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
value: 'green',
|
value: 'green',
|
||||||
@ -120,15 +129,15 @@ const colorOptions = [
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
value: 'blue',
|
value: 'blue',
|
||||||
label: 'Default for ByCenter'
|
label: 'Default for By Center'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
value: 'black',
|
value: 'black',
|
||||||
label: 'Cient Does Not Need to Go'
|
label: 'Medication Pickup Only'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
value: 'purple',
|
value: 'purple',
|
||||||
label: 'Dropoff Only',
|
label: 'Drop-Off Only',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
value: 'gray',
|
value: 'gray',
|
||||||
@ -136,21 +145,104 @@ const colorOptions = [
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
value: 'orange',
|
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',
|
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('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 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('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('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) => {
|
const generatePdf = (data) => {
|
||||||
window.open(`${baseUrl}/docs/get-pdfs?docTemplateName=med_notification&inputData=${encodeURIComponent(JSON.stringify(data))}`);
|
window.open(`${baseUrl}/docs/get-pdfs?docTemplateName=med_notification&inputData=${encodeURIComponent(JSON.stringify(data))}`);
|
||||||
@ -185,6 +277,14 @@ export const EventsService = {
|
|||||||
getTimeData,
|
getTimeData,
|
||||||
getByCustomer,
|
getByCustomer,
|
||||||
site,
|
site,
|
||||||
|
// New option names
|
||||||
|
languageSupportOptions,
|
||||||
|
labelOptions,
|
||||||
|
activityColorOptions,
|
||||||
|
incidentColorOptions,
|
||||||
|
mealPlanColorOptions,
|
||||||
|
transportationTypeOptions,
|
||||||
|
// Legacy aliases for backward compatibility
|
||||||
interpreterLevelOptions,
|
interpreterLevelOptions,
|
||||||
colorOptions
|
colorOptions
|
||||||
};
|
};
|
||||||
|
|||||||
@ -1,4 +1,5 @@
|
|||||||
import http from "../http-common";
|
import http from "../http-common";
|
||||||
|
|
||||||
const getAll = (type) => {
|
const getAll = (type) => {
|
||||||
const params = {};
|
const params = {};
|
||||||
if (type) {
|
if (type) {
|
||||||
@ -9,10 +10,9 @@ const getAll = (type) => {
|
|||||||
|
|
||||||
const createNewResource = (data) => {
|
const createNewResource = (data) => {
|
||||||
data.status = 'active';
|
data.status = 'active';
|
||||||
return http.post('/resources', data);
|
return http.post('/resources', data);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
const updateResource = (id, data) => {
|
const updateResource = (id, data) => {
|
||||||
return http.put(`/resources/${id}`, data);
|
return http.put(`/resources/${id}`, data);
|
||||||
}
|
}
|
||||||
@ -38,7 +38,7 @@ const resourceOptionList = [
|
|||||||
'Botox Therapy',
|
'Botox Therapy',
|
||||||
'Breast Surgery',
|
'Breast Surgery',
|
||||||
'Cardiology',
|
'Cardiology',
|
||||||
'Cardiovascular ',
|
'Cardiovascular',
|
||||||
'Colon & Rectal Surgery',
|
'Colon & Rectal Surgery',
|
||||||
'Dentist',
|
'Dentist',
|
||||||
'Dermatology',
|
'Dermatology',
|
||||||
@ -60,7 +60,7 @@ const resourceOptionList = [
|
|||||||
'Infectious disease',
|
'Infectious disease',
|
||||||
'Medical Center',
|
'Medical Center',
|
||||||
'Lab',
|
'Lab',
|
||||||
'Modified Barium Swallow (MBS) Study ',
|
'Modified Barium Swallow (MBS) Study',
|
||||||
'Medical Supply',
|
'Medical Supply',
|
||||||
'Nephrology',
|
'Nephrology',
|
||||||
'Neuro Surgeon',
|
'Neuro Surgeon',
|
||||||
|
|||||||
@ -19,6 +19,7 @@ export * from './LabelService';
|
|||||||
export * from './SeatingService';
|
export * from './SeatingService';
|
||||||
export * from './AttendanceNoteService';
|
export * from './AttendanceNoteService';
|
||||||
export * from './CarouselService';
|
export * from './CarouselService';
|
||||||
|
export * from './DailyRoutesTemplateService';
|
||||||
|
|
||||||
// Utility functions
|
// Utility functions
|
||||||
export const parseDateFromBackend = (dateString) => {
|
export const parseDateFromBackend = (dateString) => {
|
||||||
|
|||||||
@ -2,7 +2,7 @@ import React, { useState } from "react";
|
|||||||
import { Dropdown } from "react-bootstrap";
|
import { Dropdown } from "react-bootstrap";
|
||||||
import { Download } from "react-bootstrap-icons";
|
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 [showExportDropdown, setShowExportDropdown] = useState(false);
|
||||||
const [exportColumns, setExportColumns] = useState(
|
const [exportColumns, setExportColumns] = useState(
|
||||||
columns.map(col => ({ ...col, show: true }))
|
columns.map(col => ({ ...col, show: true }))
|
||||||
@ -117,6 +117,21 @@ const Export = ({ columns, data, filename = "export" }) => {
|
|||||||
<h6>Export Options</h6>
|
<h6>Export Options</h6>
|
||||||
<div className="app-main-content-fields-section margin-sm dropdown-container">
|
<div className="app-main-content-fields-section margin-sm dropdown-container">
|
||||||
<div className="me-4">
|
<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' }}>
|
<div style={{ maxHeight: '200px', overflowY: 'auto', marginBottom: '15px' }}>
|
||||||
<h6 style={{ fontSize: '14px', marginBottom: '10px' }}>Select Columns:</h6>
|
<h6 style={{ fontSize: '14px', marginBottom: '10px' }}>Select Columns:</h6>
|
||||||
{exportColumns.map((column) => (
|
{exportColumns.map((column) => (
|
||||||
|
|||||||
@ -14,24 +14,247 @@ export const PICKUP_STATUS_TEXT = {
|
|||||||
|
|
||||||
export const CUSTOMER_TYPE = {
|
export const CUSTOMER_TYPE = {
|
||||||
MEMBER: 'member',
|
MEMBER: 'member',
|
||||||
VOLUNTEER: 'volunteer',
|
VISITOR: 'visitor'
|
||||||
SELF_PAY: 'selfPayMember',
|
|
||||||
VISITOR: 'visitor',
|
|
||||||
TRANSFERRED: 'transferred',
|
|
||||||
DISCHARED: 'discharged',
|
|
||||||
DECEASED: 'deceased'
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export const CUSTOMER_TYPE_TEXT = {
|
export const CUSTOMER_TYPE_TEXT = {
|
||||||
member: 'Member',
|
member: 'Member',
|
||||||
volunteer: 'Volunteer',
|
|
||||||
selfPayMember: 'Self-Pay Member',
|
|
||||||
visitor: 'Visitor',
|
visitor: 'Visitor',
|
||||||
transferred: 'Transferred',
|
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 = {
|
export const CUSTOMER_JOIN_REASON = {
|
||||||
FRIEND_FAMILY_REFERRAL: 'friendFamilyReferral',
|
FRIEND_FAMILY_REFERRAL: 'friendFamilyReferral',
|
||||||
SOCIAL_WORKER_REFERRAL: 'socialWorkerReferral',
|
SOCIAL_WORKER_REFERRAL: 'socialWorkerReferral',
|
||||||
@ -50,16 +273,180 @@ export const CUSTOMER_JOIN_REASON_TEXT = {
|
|||||||
|
|
||||||
export const CUSTOMER_DISCHARGE_REASON = {
|
export const CUSTOMER_DISCHARGE_REASON = {
|
||||||
ABSENT_OVER_30: 'absentOver30',
|
ABSENT_OVER_30: 'absentOver30',
|
||||||
TRANSFERRED_TO_ASSISTED_LIVING: 'TransferredToAssignedLiving',
|
TRANSFER_WORLDSHINE: 'transferWorldshine',
|
||||||
DECEASED: 'Deceased',
|
TRANSFER_AMDC: 'transferAmdc',
|
||||||
EVENT: 'Event',
|
TRANSFER_OTHER_FACILITY: 'transferOtherFacility',
|
||||||
OTHER: 'Other'
|
DECEASED: 'deceased',
|
||||||
|
HOSPITALIZED: 'hospitalized',
|
||||||
|
MOVING_OUT_SERVICE_AREA: 'movingOutServiceArea',
|
||||||
|
NO_LONGER_ELIGIBLE: 'noLongerEligible',
|
||||||
|
NA: 'na',
|
||||||
|
OTHER: 'other'
|
||||||
}
|
}
|
||||||
|
|
||||||
export const CUSTOMER_DISCHARGE_REASON_TEXT = {
|
export const CUSTOMER_DISCHARGE_REASON_TEXT = {
|
||||||
absentOver30: 'Absent for Over 30 Days',
|
absentOver30: 'Absent for Over 30 Days',
|
||||||
TransferredToAssignedLiving: 'Transferred to Assisted Living',
|
transferWorldshine: 'Transfer to Another Worldshine Center',
|
||||||
Deceased: 'Deceased',
|
transferAmdc: 'Transfer to Another AMDC Center',
|
||||||
Event: 'Event',
|
transferOtherFacility: 'Transfer to Another Type of Facility',
|
||||||
Other: 'Other'
|
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 "./route-status.constant";
|
||||||
export * from "./employee.constant";
|
export * from "./employee.constant";
|
||||||
export * from "./report.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('/files', express.static(employeeUploadBasePath));
|
||||||
app.use(cors(corsOptions));
|
app.use(cors(corsOptions));
|
||||||
// parse requests of content-type - application/json
|
// parse requests of content-type - application/json
|
||||||
app.use(bodyParser.json());
|
app.use(bodyParser.json({ limit: '50mb' }));
|
||||||
app.use(express.static(path));
|
app.use(express.static(path));
|
||||||
// parse requests of content-type - application/x-www-form-urlencoded
|
// 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');
|
const db = require('./app/models');
|
||||||
db.mongoose
|
db.mongoose
|
||||||
.connect(db.url, {
|
.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) {
|
app.get('/trans-routes/templates/edit/:id', function (req,res) {
|
||||||
res.sendFile(path + "index.html");
|
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) {
|
app.get('/employees', function (req,res) {
|
||||||
res.sendFile(path + "index.html");
|
res.sendFile(path + "index.html");
|
||||||
});
|
});
|
||||||
@ -201,6 +213,9 @@ app.get('/seating', function (req,res) {
|
|||||||
app.get('/center-calendar', function (req,res) {
|
app.get('/center-calendar', function (req,res) {
|
||||||
res.sendFile(path + "index.html");
|
res.sendFile(path + "index.html");
|
||||||
});
|
});
|
||||||
|
app.get('/meal-status', function (req,res) {
|
||||||
|
res.sendFile(path + "index.html");
|
||||||
|
});
|
||||||
app.get('/info-screen', function (req,res) {
|
app.get('/info-screen', function (req,res) {
|
||||||
res.sendFile(path + "index.html");
|
res.sendFile(path + "index.html");
|
||||||
});
|
});
|
||||||
@ -233,6 +248,8 @@ require("./app/routes/seating.routes")(app);
|
|||||||
require("./app/routes/label.routes")(app);
|
require("./app/routes/label.routes")(app);
|
||||||
require("./app/routes/attendance-note.routes")(app);
|
require("./app/routes/attendance-note.routes")(app);
|
||||||
require("./app/routes/carousel.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");
|
require("./app/scheduler/reminderScheduler");
|
||||||
|
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user