All update about transportantion and style

This commit is contained in:
Yang Li 2025-07-04 14:13:25 -04:00
parent e4654be2b9
commit 98d86c0f70
76 changed files with 7477 additions and 693 deletions

BIN
.DS_Store vendored

Binary file not shown.

BIN
app/.DS_Store vendored

Binary file not shown.

View File

@ -0,0 +1,131 @@
const { splitSite } = require("../middlewares");
const db = require("../models");
const AttendanceNote = db.attendance_note;
// Create a new Attendance Note
exports.createNewAttendanceNote = (req, res) => {
// Validate request
if (!req.body.slogan || !req.body.introduction) {
res.status(400).send({ message: "Slogan and introduction can not be empty!" });
return;
}
const site = splitSite.findSiteNumber(req);
// Create an Attendance Note
const attendanceNote = new AttendanceNote({
slogan: req.body.slogan,
introduction: req.body.introduction,
image: req.body.image || '',
status: req.body.status || 'active',
site,
create_date: new Date(),
create_by: req.body.create_by || '',
update_date: new Date(),
update_by: req.body.update_by || ''
});
// Save Attendance Note in the database
attendanceNote
.save(attendanceNote)
.then(data => {
res.send(data);
})
.catch(err => {
res.status(500).send({
message:
err.message || "Some error occurred while creating the Attendance Note."
});
});
};
// Retrieve all Attendance Notes from the database
exports.getAllAttendanceNotes = (req, res) => {
var condition = {};
// Filter by status if provided
if (req.query.status) {
condition.status = req.query.status;
}
condition = splitSite.splitSiteGet(req, condition);
AttendanceNote.find(condition)
.then(data => {
res.send(data);
})
.catch(err => {
res.status(500).send({
message:
err.message || "Some error occurred while retrieving Attendance Notes."
});
});
};
// Get One Attendance Note by Id
exports.getAttendanceNote = (req, res) => {
const id = req.params.id;
AttendanceNote.findById(id)
.then(data => {
if (!data)
res.status(404).send({ message: "Not found Attendance Note with id " + id });
else res.send(data);
})
.catch(err => {
res
.status(500)
.send({ message: "Error retrieving Attendance Note with id=" + id });
});
};
// Update an Attendance Note by the id in the request
exports.updateAttendanceNote = (req, res) => {
if (!req.body) {
return res.status(400).send({
message: "Data to update can not be empty!"
});
}
const id = req.params.id;
// Add update tracking
const updateData = {
...req.body,
update_date: new Date(),
update_by: req.body.update_by || ''
};
AttendanceNote.findByIdAndUpdate(id, updateData, { useFindAndModify: false })
.then(data => {
if (!data) {
res.status(404).send({
message: `Cannot update Attendance Note with id=${id}. Maybe Attendance Note was not found!`
});
} else res.send({ success: true, message: "Attendance Note was updated successfully." });
})
.catch(err => {
res.status(500).send({
success: false,
message: "Error updating Attendance Note with id=" + id + ": " + (err.message || "")
});
});
};
// Delete an Attendance Note by id
exports.deleteAttendanceNote = (req, res) => {
const id = req.params.id;
AttendanceNote.findByIdAndRemove(id)
.then(data => {
if (!data) {
res.status(404).send({
message: `Cannot delete Attendance Note with id=${id}. Maybe Attendance Note was not found!`
});
} else {
res.send({
message: "Attendance Note was deleted successfully!"
});
}
})
.catch(err => {
res.status(500).send({
message: "Could not delete Attendance Note with id=" + id + ": " + (err.message || "")
});
});
};

View File

@ -45,7 +45,9 @@ exports.createCalendarEvent = (req, res) => {
edit_history: req.body.edit_history,
site,
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,
rrule: req.body.rrule
});
// Save event in the database
calendarEvent

View File

@ -0,0 +1,123 @@
const { splitSite } = require("../middlewares");
const db = require("../models");
const Carousel = db.carousel;
// Create a new Carousel
exports.createNewCarousel = (req, res) => {
const site = splitSite.findSiteNumber(req);
// Create a Carousel
const carousel = new Carousel({
status: req.body.status || 'active',
site,
create_date: new Date(),
create_by: req.body.create_by || '',
update_date: new Date(),
update_by: req.body.update_by || ''
});
// Save Carousel in the database
carousel
.save(carousel)
.then(data => {
res.send(data);
})
.catch(err => {
res.status(500).send({
message:
err.message || "Some error occurred while creating the Carousel."
});
});
};
// Retrieve all Carousels from the database
exports.getAllCarousels = (req, res) => {
var condition = {};
// Filter by status if provided
if (req.query.status) {
condition.status = req.query.status;
}
condition = splitSite.splitSiteGet(req, condition);
Carousel.find(condition)
.then(data => {
res.send(data);
})
.catch(err => {
res.status(500).send({
message:
err.message || "Some error occurred while retrieving Carousels."
});
});
};
// Get One Carousel by Id
exports.getCarousel = (req, res) => {
const id = req.params.id;
Carousel.findById(id)
.then(data => {
if (!data)
res.status(404).send({ message: "Not found Carousel with id " + id });
else res.send(data);
})
.catch(err => {
res
.status(500)
.send({ message: "Error retrieving Carousel with id=" + id });
});
};
// Update a Carousel by the id in the request
exports.updateCarousel = (req, res) => {
if (!req.body) {
return res.status(400).send({
message: "Data to update can not be empty!"
});
}
const id = req.params.id;
// Add update tracking
const updateData = {
...req.body,
update_date: new Date(),
update_by: req.body.update_by || ''
};
Carousel.findByIdAndUpdate(id, updateData, { useFindAndModify: false })
.then(data => {
if (!data) {
res.status(404).send({
message: `Cannot update Carousel with id=${id}. Maybe Carousel was not found!`
});
} else res.send({ success: true, message: "Carousel was updated successfully." });
})
.catch(err => {
res.status(500).send({
success: false,
message: "Error updating Carousel with id=" + id + ": " + (err.message || "")
});
});
};
// Delete a Carousel by id
exports.deleteCarousel = (req, res) => {
const id = req.params.id;
Carousel.findByIdAndRemove(id)
.then(data => {
if (!data) {
res.status(404).send({
message: `Cannot delete Carousel with id=${id}. Maybe Carousel was not found!`
});
} else {
res.send({
message: "Carousel was deleted successfully!"
});
}
})
.catch(err => {
res.status(500).send({
message: "Could not delete Carousel with id=" + id + ": " + (err.message || "")
});
});
};

View File

@ -27,6 +27,26 @@ exports.createCustomer = (req, res) => {
address3: req.body.address3 || '',
address4: req.body.address4 || '',
address5: req.body.address5 || '',
street_address_1: req.body.street_address_1 || '',
city1: req.body.city1 || '',
state1: req.body.state1 || '',
zip_code1: req.body.zip_code1 || '',
street_address_2: req.body.street_address_2 || '',
city2: req.body.city2 || '',
state2: req.body.state2 || '',
zip_code2: req.body.zip_code2 || '',
street_address_3: req.body.street_address_3 || '',
city3: req.body.city3 || '',
state3: req.body.state3 || '',
zip_code3: req.body.zip_code3 || '',
street_address_4: req.body.street_address_4 || '',
city4: req.body.city4 || '',
state4: req.body.state4 || '',
zip_code4: req.body.zip_code4 || '',
street_address_5: req.body.street_address_5 || '',
city5: req.body.city5 || '',
state5: req.body.state5 || '',
zip_code5: req.body.zip_code5 || '',
firstname: req.body.firstname || '',
lastname: req.body.lastname || '',
birth_date: req.body.birth_date || null,
@ -37,6 +57,13 @@ exports.createCustomer = (req, res) => {
note: req.body.note || '',
care_provider: req.body.care_provider || '',
emergency_contact: req.body.emergency_contact || '',
emergency_contact2: req.body.emergency_contact2 || '',
emergency_contact_name: req.body.emergency_contact_name || '',
emergency_contact_phone: req.body.emergency_contact_phone || '',
emergency_contact_relationship: req.body.emergency_contact_relationship || '',
emergency_contact2_name: req.body.emergency_contact2_name || '',
emergency_contact2_phone: req.body.emergency_contact2_phone || '',
emergency_contact2_relationship: req.body.emergency_contact2_relationship || '',
medicare_number: req.body.medicare_number || '',
medicaid_number: req.body.medicaid_number || '',
pharmacy: req.body.pharmacy || '',
@ -65,7 +92,15 @@ exports.createCustomer = (req, res) => {
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,
allergy_info: req.body.allergy_info || '',
meal_requirement: req.body.meal_requirement || '',
service_requirement: req.body.service_requirement || '',
payment_due_date: req.body.payment_due_date || '',
payment_status: req.body.payment_status || '',
join_reason: req.body.join_reason || '',
discharge_reason: req.body.discharge_reason || ''
});
// Save Customer in the database
customer

View File

@ -254,3 +254,39 @@ exports.deleteRoute= (req, res) => {
});
});
};
// Get all routes on and after today
exports.getAllRoutesOnAndAfterToday = (req, res) => {
const today = new Date();
const todayString = `${String(today.getMonth() + 1).padStart(2, '0')}/${String(today.getDate()).padStart(2, '0')}/${today.getFullYear()}`;
var condition = {
status: { "$ne": 'disabled' }
};
condition = splitSite.splitSiteGet(req, condition);
RoutePath.find(condition)
.then(data => {
// Filter routes with schedule_date on or after today
const filteredRoutes = data.filter(route => {
if (!route.schedule_date) return false;
// Convert MM/DD/YYYY to Date object for comparison
const [month, day, year] = route.schedule_date.split('/');
const routeDate = new Date(parseInt(year), parseInt(month) - 1, parseInt(day));
// Set today's time to start of day for fair comparison
const todayStart = new Date(today.getFullYear(), today.getMonth(), today.getDate());
return routeDate >= todayStart;
});
res.send(filteredRoutes);
})
.catch(err => {
res.status(500).send({
message:
err.message || "Some error occurred while retrieving Routes."
});
});
};

View File

@ -25,14 +25,12 @@ const findSiteNumber = (req) => {
const splitSiteGet = (req, queryParams) => {
const site = findSiteNumber(req);
const newQueryParams = { ...queryParams, site: site };
console.log('query', newQueryParams);
return newQueryParams;
}
const splitSitePost = (req, postBody) => {
const site = findSiteNumber(req);
const newPostBody = { ...postBody, site: site };
console.log('post', newPostBody);
return newPostBody;
}

View File

@ -0,0 +1,40 @@
module.exports = mongoose => {
var schema = mongoose.Schema(
{
slogan: {
type: String,
required: true
},
introduction: {
type: String,
required: true
},
image: {
type: String
},
status: {
type: String,
default: 'active'
},
site: Number,
create_date: {
type: Date,
default: Date.now
},
create_by: String,
update_date: {
type: Date,
default: Date.now
},
update_by: String
},
{ collection: 'attendance_note', timestamps: true }
);
schema.method("toJSON", function() {
const { __v, _id, ...object } = this.toObject();
object.id = _id;
return object;
});
const AttendanceNote = mongoose.model("attendance_note", schema);
return AttendanceNote;
};

View File

@ -79,7 +79,9 @@ module.exports = mongoose => {
}],
site: Number,
event_location: String,
event_prediction_date: String
event_prediction_date: String,
event_reminder_type: String,
rrule: String
},
{ collection: 'calendar_event', timestamps: true }
);

View File

@ -0,0 +1,29 @@
module.exports = mongoose => {
var schema = mongoose.Schema(
{
status: {
type: String,
default: 'active'
},
site: Number,
create_date: {
type: Date,
default: Date.now
},
create_by: String,
update_date: {
type: Date,
default: Date.now
},
update_by: String
},
{ collection: 'carousel', timestamps: true }
);
schema.method("toJSON", function() {
const { __v, _id, ...object } = this.toObject();
object.id = _id;
return object;
});
const Carousel = mongoose.model("carousel", schema);
return Carousel;
};

View File

@ -16,6 +16,13 @@ module.exports = mongoose => {
password: String,
care_provider: String,
emergency_contact: String,
emergency_contact2: String,
emergency_contact_name: String,
emergency_contact_phone: String,
emergency_contact_relationship: String,
emergency_contact2_name: String,
emergency_contact2_phone: String,
emergency_contact2_relationship: String,
medicare_number: String,
medicaid_number: String,
pharmacy: String,
@ -27,6 +34,26 @@ module.exports = mongoose => {
address3: String,
address4: String,
address5: String,
street_address_1: String,
city1: String,
state1: String,
zip_code1: String,
street_address_2: String,
city2: String,
state2: String,
zip_code2: String,
street_address_3: String,
city3: String,
state3: String,
zip_code3: String,
street_address_4: String,
city4: String,
state4: String,
zip_code4: String,
street_address_5: String,
city5: String,
state5: String,
zip_code5: String,
phone: String,
mobile_phone: String,
type: String,
@ -72,7 +99,15 @@ module.exports = mongoose => {
height: String,
weight: String,
gender: String,
text_msg_enabled: Boolean
text_msg_enabled: Boolean,
health_condition: String,
allergy_info: String,
meal_requirement: String,
service_requirement: String,
payment_due_date: String,
payment_status: String,
join_reason: String,
discharge_reason: String
},
{ collection: 'customer', timestamps: true }
);

View File

@ -30,4 +30,6 @@ db.snack = require("./snack.model")(mongoose);
db.vehicle_repair = require("./vehicle-repair.model")(mongoose);
db.label = require("./label.model")(mongoose);
db.seating = require("./seating.model")(mongoose);
db.attendance_note = require("./attendance-note.model")(mongoose);
db.carousel = require("./carousel.model")(mongoose);
module.exports = db;

View File

@ -0,0 +1,29 @@
const {authJwt} = require("../middlewares");
module.exports = app => {
const attendanceNotes = require("../controllers/attendance-note.controller.js");
app.use((req, res, next) => {
res.header(
"Access-Control-Allow-Headers",
"x-access-token, Origin, Content-Type, Accept"
);
next();
});
var router = require("express").Router();
// Retrieve all attendance notes
router.get("/", [authJwt.verifyToken], attendanceNotes.getAllAttendanceNotes);
// Create a new attendance note
router.post("/", [authJwt.verifyToken], attendanceNotes.createNewAttendanceNote);
// Get one attendance note by id
router.get('/:id', [authJwt.verifyToken], attendanceNotes.getAttendanceNote);
// Update an attendance note
router.put('/:id', [authJwt.verifyToken], attendanceNotes.updateAttendanceNote);
// Delete an attendance note
router.delete('/:id', [authJwt.verifyToken], attendanceNotes.deleteAttendanceNote);
app.use('/api/attendance-notes', router);
};

View File

@ -0,0 +1,29 @@
const {authJwt} = require("../middlewares");
module.exports = app => {
const carousels = require("../controllers/carousel.controller.js");
app.use((req, res, next) => {
res.header(
"Access-Control-Allow-Headers",
"x-access-token, Origin, Content-Type, Accept"
);
next();
});
var router = require("express").Router();
// Retrieve all carousels
router.get("/", [authJwt.verifyToken], carousels.getAllCarousels);
// Create a new carousel
router.post("/", [authJwt.verifyToken], carousels.createNewCarousel);
// Get one carousel by id
router.get('/:id', [authJwt.verifyToken], carousels.getCarousel);
// Update a carousel
router.put('/:id', [authJwt.verifyToken], carousels.updateCarousel);
// Delete a carousel
router.delete('/:id', [authJwt.verifyToken], carousels.deleteCarousel);
app.use('/api/carousels', router);
};

View File

@ -12,6 +12,7 @@ module.exports = app => {
var router = require("express").Router();
// Retrieve all routes
router.get("/", [authJwt.verifyToken], routePaths.getAllRoutes);
router.get("/on-and-after-today", [authJwt.verifyToken], routePaths.getAllRoutesOnAndAfterToday);
router.get("/:id", [authJwt.verifyToken], routePaths.getRoute);
// Create a new route
router.post("/", [authJwt.verifyToken], routePaths.createRoutePath);

View File

@ -0,0 +1,11 @@
const cron = require('node-cron');
const reminderService = require('../services/reminderService')
cron.schedule('45 23 * * *', async () => {
console.log('Running task at 11:45 PM in Eastern Time');
await reminderService.createCustomerRelatedEvents(30);
await reminderService.createVehicleRelatedEvents(30);
}, {
scheduled: true,
timezone: "America/New_York"
})

View File

@ -0,0 +1,212 @@
const db = require("../models");
const Vehicle = db.vehicle;
const Customer = db.customer;
const CalendarEvent = db.calendar_event;
const moment = require('moment');
const siteMap = {
'ws1': 1,
'worldshine1': 1,
'ws2': 2,
'worldshine2': 2,
'ws3': 3,
'worldshine3': 3,
'worldshine4': 4,
'ws4': 4,
'worldshine.mayo.llc': 1
};
const getSite = () => {
console.log('here', __dirname);
let site = 1;
const arr = __dirname.split('/');
for (const key of Object.keys(siteMap)) {
if (arr.includes(key)) {
site = siteMap[key];
break;
}
}
return site;
}
class ReminderService {
parseDate(dateString) {
const [month, day, year] = dateString.split('/').map(Number);
return new Date(year, month-1,day);
}
async createVehicleRelatedEvents(daysAhead = 30) {
const today = new Date();
const futureDate = new Date();
futureDate.setDate(today.getDate() + daysAhead); // check 30 days ahead
const vehicles = await Vehicle.find({status: 'active', site: getSite()});
const allVehiclesCalendarEvent = await CalendarEvent.find({type: 'reminder', status: 'active', site: getSite(), target_type: 'vehicle'});
console.log('vehicles', vehicles);
for (const vehicle of vehicles) {
const insuranceExpireDate = this.parseDate(vehicle.insurance_expire_on);
const titleRegistrationDate = this.parseDate(vehicle.title_registration_on);
const titleExpireDate = new Date(titleRegistrationDate);
titleExpireDate.setFullYear(titleExpireDate.getFullYear() + 1);
const emissionTestDate = this.parseDate(vehicle.emission_test_on);
const emissionTestExpireDate = new Date(emissionTestDate);
emissionTestExpireDate.setFullYear(titleExpireDate.getFullYear() + 1);
const newDate = new Date(insuranceExpireDate);
newDate.setDate(newDate.getDate() - 15);
const newDate1 = new Date(emissionTestExpireDate);
newDate1.setDate(newDate.getDate() - 15);
const newDate2 = new Date(titleExpireDate);
newDate2.setDate(newDate.getDate() - 15);
if ( insuranceExpireDate >= today && insuranceExpireDate < futureDate) {
if (!allVehiclesCalendarEvent.find(item => item.event_reminder_type === 'insurance_expire' && item.target_uuid === vehicle.id)) {
const calendarEvent = new CalendarEvent({
title: `Vehicle ${vehicle?.vehicle_number} (${vehicle?.tag})'s Insurance would expire in 15 days`,
description: `Vehicle ${vehicle?.vehicle_number} (${vehicle?.tag})'s Insurance would expire in 15 day, please check whether it is renewed properly`,
type: 'reminder',
start_time: newDate,
stop_time: new Date(newDate.getTime() + 10*60*1000),
color: 'brown',
target_type: 'vehicle',
target_uuid: vehicle.id,
target_name: vechile.vehicle_number,
event_reminder_type: 'insurance_expire',
status: 'active',
site: getSite(),
edit_date: new Date(),
create_date: new Date(),
create_by: 'system',
edit_by: 'system',
edit_history: [{employee: 'system', date: new Date()}]
})
await calendarEvent.save(calendarEvent);
}
}
if ( emissionTestExpireDate >= today && emissionTestExpireDate < futureDate) {
if (!allVehiclesCalendarEvent.find(item => item.event_reminder_type === 'emission_test' && item.target_uuid === vehicle.id)) {
const calendarEvent = new CalendarEvent({
title: `Vehicle ${vehicle?.vehicle_number} (${vehicle?.tag})'s Emission Test would expire in 15 days`,
description: `Vehicle ${vehicle?.vehicle_number} (${vehicle?.tag})'s Emission Test would expire in 15 day, please check whether it is renewed properly`,
type: 'reminder',
start_time: newDate1,
stop_time: new Date(newDate1.getTime() + 10*60*1000),
color: 'brown',
target_type: 'vehicle',
target_uuid: vehicle.id,
target_name: vechile.vehicle_number,
event_reminder_type: 'emission_test',
status: 'active',
site: getSite(),
edit_date: new Date(),
create_date: new Date(),
create_by: 'system',
edit_by: 'system',
edit_history: [{employee: 'system', date: new Date()}]
})
await calendarEvent.save(calendarEvent);
}
}
if ( titleExpireDate >= today && titleExpireDate < futureDate) {
if (!allVehiclesCalendarEvent.find(item => item.event_reminder_type === 'title_expire' && item.target_uuid === vehicle.id)) {
const calendarEvent = new CalendarEvent({
title: `Vehicle ${vehicle?.vehicle_number} (${vehicle?.tag})'s Title would expire in 15 days`,
description: `Vehicle ${vehicle?.vehicle_number} (${vehicle?.tag})'s Title would expire in 15 day, please check whether it is renewed properly`,
type: 'reminder',
start_time: newDate2,
stop_time: new Date(newDate2.getTime() + 10*60*1000),
color: 'brown',
target_type: 'vehicle',
target_uuid: vehicle.id,
target_name: vechile.vehicle_number,
event_reminder_type: 'title_expire',
status: 'active',
site: getSite(),
edit_date: new Date(),
create_date: new Date(),
create_by: 'system',
edit_by: 'system',
edit_history: [{employee: 'system', date: new Date()}]
})
await calendarEvent.save(calendarEvent);
}
}
}
}
async createCustomerRelatedEvents(daysAhead = 30) {
const today = new Date();
const upcomingDates = [];
// Calculate dates for the next 'daysAhead' days
for (let i=0; i<=daysAhead; i++) {
const targetDate = new Date(today);
targetDate.setDate(today.getDate() + i);
upcomingDates.push(targetDate)
}
const allCustomers = await Customer.find({ site: getSite()});
console.log('allCustomers', allCustomers);
const allExistingBirthdayReminders = await CalendarEvent.find({ type: 'reminder', status: 'active', rrule: 'FREQ=YEARLY', event_reminder_type: 'birthday', site: getSite() });
const allExistingPaymentReminders = await CalendarEvent.find({ type: 'reminder', status: 'active', event_reminder_type: 'payment', site: getSite() });
for (const customer of allCustomers) {
const [month, day, year] = customer?.birth_date?.split('/').map(Number) || [-1, -1, -1];
const [month1, day1, year1] = customer?.payment_due_date?.split('/').map(Number) || [-1, -1, -1];
for (const targetDate of upcomingDates) {
if (month === targetDate.getMonth() + 1 && day === targetDate.getDate()) {
console.log('targetDate found', targetDate);
console.log('are you sure you find', allExistingBirthdayReminders.find((reminder) => reminder.target_uuid === customer?.id));
if (!allExistingBirthdayReminders.find((reminder) => reminder.target_uuid === customer?.id)) {
console.log(123123);
const calendarEvent = new CalendarEvent({
title: `${customer?.name}'s Birthday`,
description: `Happy Birthday to ${customer?.name}`,
type: 'reminder',
start_time: targetDate,
stop_time: new Date(targetDate.getTime() + 10*60*1000),
color: 'orange',
target_type: 'customer',
target_uuid: customer?.id,
target_name: customer?.name,
event_reminder_type: 'birthday',
rrule: 'FREQ=YEARLY',
status: 'active',
site: getSite(),
edit_date: new Date(),
create_date: new Date(),
create_by: 'system',
edit_by: 'system',
edit_history: [{employee: 'system', date: new Date()}]
});
console.log('start to create', calendarEvent);
await calendarEvent.save(calendarEvent);
}
}
if (month1 === targetDate.getMonth() + 1 && day1 === targetDate.getDate()) {
if (!allExistingPaymentReminders.find((reminder) => reminder.target_uuid === customer?.id)) {
const calendarEvent = new CalendarEvent({
title: `${customer?.name}'s Payment Due Date`,
description: `${customer?.name}'s payment would be due on ${customer?.payment_due_date}`,
type: 'reminder',
start_time: targetDate,
stop_time: new Date(targetDate.getTime() + 10*60*1000),
color: 'red',
target_type: 'customer',
target_uuid: customer?.id,
target_name: customer?.name,
event_reminder_type: 'payment',
status: 'active',
site: getSite(),
edit_date: new Date(),
create_date: new Date(),
create_by: 'system',
edit_by: 'system',
edit_history: [{employee: 'system', date: new Date()}]
})
await calendarEvent.save(calendarEvent);
}
}
}
}
}
}
module.exports = new ReminderService();

View File

@ -1,16 +1,16 @@
{
"files": {
"main.css": "/static/css/main.11c89bb3.css",
"main.js": "/static/js/main.c96f6687.js",
"main.css": "/static/css/main.2c06dda5.css",
"main.js": "/static/js/main.cddce86b.js",
"static/js/787.c4e7f8f9.chunk.js": "/static/js/787.c4e7f8f9.chunk.js",
"static/media/landing.png": "/static/media/landing.d4c6072db7a67dff6a78.png",
"index.html": "/index.html",
"main.11c89bb3.css.map": "/static/css/main.11c89bb3.css.map",
"main.c96f6687.js.map": "/static/js/main.c96f6687.js.map",
"main.2c06dda5.css.map": "/static/css/main.2c06dda5.css.map",
"main.cddce86b.js.map": "/static/js/main.cddce86b.js.map",
"787.c4e7f8f9.chunk.js.map": "/static/js/787.c4e7f8f9.chunk.js.map"
},
"entrypoints": [
"static/css/main.11c89bb3.css",
"static/js/main.c96f6687.js"
"static/css/main.2c06dda5.css",
"static/js/main.cddce86b.js"
]
}

View File

@ -1 +1 @@
<!doctype html><html lang="en"><head><meta charset="utf-8"/><link rel="icon" href="/favicon.ico"/><meta name="viewport" content="width=device-width,initial-scale=1"/><meta name="theme-color" content="#000000"/><meta name="description" content="Web site created using create-react-app"/><link rel="apple-touch-icon" href="/logo192.png"/><script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.10/lodash.min.js"></script><link rel="manifest" href="/manifest.json"/><title>Worldshine Transportation</title><script defer="defer" src="/static/js/main.c96f6687.js"></script><link href="/static/css/main.11c89bb3.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.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>

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

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

View File

@ -4,6 +4,20 @@
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

File diff suppressed because one or more lines are too long

BIN
client/.DS_Store vendored

Binary file not shown.

View File

@ -13,6 +13,7 @@
"@emotion/styled": "^11.11.5",
"@reduxjs/toolkit": "^1.8.1",
"@schedule-x/event-modal": "^2.30.0",
"@schedule-x/event-recurrence": "^2.32.0",
"@schedule-x/events-service": "^2.30.0",
"@schedule-x/react": "^2.30.0",
"@schedule-x/theme-default": "^2.30.0",
@ -23,10 +24,12 @@
"axios": "^0.27.2",
"bootstrap": "^5.1.3",
"browser-image-compression": "^2.0.0",
"chart.js": "^4.5.0",
"immutability-helper": "^3.1.1",
"react": "^18.1.0",
"react-bootstrap": "^2.4.0",
"react-bootstrap-icons": "^1.11.5",
"react-chartjs-2": "^5.3.0",
"react-csv": "^2.2.2",
"react-datepicker": "^4.8.0",
"react-datetime-picker": "^3.5.0",
@ -3066,6 +3069,11 @@
"@jridgewell/sourcemap-codec": "^1.4.10"
}
},
"node_modules/@kurkle/color": {
"version": "0.3.4",
"resolved": "https://registry.npmjs.org/@kurkle/color/-/color-0.3.4.tgz",
"integrity": "sha512-M5UknZPHRu3DEDWoipU6sE8PdkZ6Z/S+v4dD+Ke8IaNlpdSQah50lz1KtcFBa2vsdOnwbbnxJwVM4wty6udA5w=="
},
"node_modules/@leichtgewicht/ip-codec": {
"version": "2.0.3",
"resolved": "https://registry.npmjs.org/@leichtgewicht/ip-codec/-/ip-codec-2.0.3.tgz",
@ -3419,6 +3427,11 @@
"preact": "^10.19.2"
}
},
"node_modules/@schedule-x/event-recurrence": {
"version": "2.32.0",
"resolved": "https://registry.npmjs.org/@schedule-x/event-recurrence/-/event-recurrence-2.32.0.tgz",
"integrity": "sha512-6wY54QT76p21+Gq96V75Uj+smxvAmptxkvTcg+V8BKpRuvcUP5XzMwAZgLub2Vi7tbKXLyzmIyl8x9cyVNi/XA=="
},
"node_modules/@schedule-x/events-service": {
"version": "2.30.0",
"resolved": "https://registry.npmjs.org/@schedule-x/events-service/-/events-service-2.30.0.tgz",
@ -6510,6 +6523,17 @@
"node": "*"
}
},
"node_modules/chart.js": {
"version": "4.5.0",
"resolved": "https://registry.npmjs.org/chart.js/-/chart.js-4.5.0.tgz",
"integrity": "sha512-aYeC/jDgSEx8SHWZvANYMioYMZ2KX02W6f6uVfyteuCGcadDLcYVHdfdygsTQkQ4TKn5lghoojAsPj5pu0SnvQ==",
"dependencies": {
"@kurkle/color": "^0.3.0"
},
"engines": {
"pnpm": ">=8"
}
},
"node_modules/check-types": {
"version": "11.1.2",
"resolved": "https://registry.npmjs.org/check-types/-/check-types-11.1.2.tgz",
@ -17095,6 +17119,15 @@
"react-dom": "^16.3.0 || ^17.0.0 || ^18.0.0"
}
},
"node_modules/react-chartjs-2": {
"version": "5.3.0",
"resolved": "https://registry.npmjs.org/react-chartjs-2/-/react-chartjs-2-5.3.0.tgz",
"integrity": "sha512-UfZZFnDsERI3c3CZGxzvNJd02SHjaSJ8kgW1djn65H1KK8rehwTjyrRKOG3VTMG8wtHZ5rgAO5oTHtHi9GCCmw==",
"peerDependencies": {
"chart.js": "^4.1.1",
"react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0"
}
},
"node_modules/react-clock": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/react-clock/-/react-clock-3.1.0.tgz",
@ -23976,6 +24009,11 @@
"@jridgewell/sourcemap-codec": "^1.4.10"
}
},
"@kurkle/color": {
"version": "0.3.4",
"resolved": "https://registry.npmjs.org/@kurkle/color/-/color-0.3.4.tgz",
"integrity": "sha512-M5UknZPHRu3DEDWoipU6sE8PdkZ6Z/S+v4dD+Ke8IaNlpdSQah50lz1KtcFBa2vsdOnwbbnxJwVM4wty6udA5w=="
},
"@leichtgewicht/ip-codec": {
"version": "2.0.3",
"resolved": "https://registry.npmjs.org/@leichtgewicht/ip-codec/-/ip-codec-2.0.3.tgz",
@ -24220,6 +24258,11 @@
"integrity": "sha512-RS/p5/rg/wCooKR5y05orJBJjxf7Ls7/VcGsz5MemJCQCXhu5pbU0g+OVVSlh6tJ+OoKF5frm4lIhXu82gd6/Q==",
"requires": {}
},
"@schedule-x/event-recurrence": {
"version": "2.32.0",
"resolved": "https://registry.npmjs.org/@schedule-x/event-recurrence/-/event-recurrence-2.32.0.tgz",
"integrity": "sha512-6wY54QT76p21+Gq96V75Uj+smxvAmptxkvTcg+V8BKpRuvcUP5XzMwAZgLub2Vi7tbKXLyzmIyl8x9cyVNi/XA=="
},
"@schedule-x/events-service": {
"version": "2.30.0",
"resolved": "https://registry.npmjs.org/@schedule-x/events-service/-/events-service-2.30.0.tgz",
@ -26643,6 +26686,14 @@
"integrity": "sha1-wKHS86cJLgN3S/qD8UwPxXkKhmc=",
"dev": true
},
"chart.js": {
"version": "4.5.0",
"resolved": "https://registry.npmjs.org/chart.js/-/chart.js-4.5.0.tgz",
"integrity": "sha512-aYeC/jDgSEx8SHWZvANYMioYMZ2KX02W6f6uVfyteuCGcadDLcYVHdfdygsTQkQ4TKn5lghoojAsPj5pu0SnvQ==",
"requires": {
"@kurkle/color": "^0.3.0"
}
},
"check-types": {
"version": "11.1.2",
"resolved": "https://registry.npmjs.org/check-types/-/check-types-11.1.2.tgz",
@ -34391,6 +34442,12 @@
"prop-types": "^15.6.0"
}
},
"react-chartjs-2": {
"version": "5.3.0",
"resolved": "https://registry.npmjs.org/react-chartjs-2/-/react-chartjs-2-5.3.0.tgz",
"integrity": "sha512-UfZZFnDsERI3c3CZGxzvNJd02SHjaSJ8kgW1djn65H1KK8rehwTjyrRKOG3VTMG8wtHZ5rgAO5oTHtHi9GCCmw==",
"requires": {}
},
"react-clock": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/react-clock/-/react-clock-3.1.0.tgz",

View File

@ -8,6 +8,7 @@
"@emotion/styled": "^11.11.5",
"@reduxjs/toolkit": "^1.8.1",
"@schedule-x/event-modal": "^2.30.0",
"@schedule-x/event-recurrence": "^2.32.0",
"@schedule-x/events-service": "^2.30.0",
"@schedule-x/react": "^2.30.0",
"@schedule-x/theme-default": "^2.30.0",
@ -18,10 +19,12 @@
"axios": "^0.27.2",
"bootstrap": "^5.1.3",
"browser-image-compression": "^2.0.0",
"chart.js": "^4.5.0",
"immutability-helper": "^3.1.1",
"react": "^18.1.0",
"react-bootstrap": "^2.4.0",
"react-bootstrap-icons": "^1.11.5",
"react-chartjs-2": "^5.3.0",
"react-csv": "^2.2.2",
"react-datepicker": "^4.8.0",
"react-datetime-picker": "^3.5.0",

View File

@ -11,6 +11,7 @@
/>
<link rel="apple-touch-icon" href="%PUBLIC_URL%/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">
<!--
manifest.json provides metadata used when your web app is installed on a
user's mobile device or desktop. See https://developers.google.com/web/fundamentals/web-app-manifest/

View File

@ -843,6 +843,88 @@ input[type="checkbox"] {
color: #000;
}
/* Info Screen Appointments Table - Override minimum width */
.info-screen-appointments-table td {
min-width: auto !important;
max-width: 200px;
word-wrap: break-word;
}
.info-screen-appointments-table th {
min-width: auto !important;
max-width: 200px;
word-wrap: break-word;
}
/* Clock Styles for Info Screen */
.clock-container {
display: flex;
justify-content: center;
align-items: center;
margin-top: 15px;
}
.clock {
width: 80px;
height: 80px;
border: 3px solid #007bff;
border-radius: 50%;
position: relative;
background: white;
}
.clock-face {
width: 100%;
height: 100%;
position: relative;
}
.hand {
position: absolute;
bottom: 50%;
left: 50%;
transform-origin: bottom;
background: #333;
border-radius: 2px;
}
.hour-hand {
width: 3px;
height: 25%;
margin-left: -1.5px;
}
.minute-hand {
width: 2px;
height: 35%;
margin-left: -1px;
}
.second-hand {
width: 1px;
height: 40%;
margin-left: -0.5px;
background: #dc3545;
}
.center-dot {
position: absolute;
top: 50%;
left: 50%;
width: 8px;
height: 8px;
background: #333;
border-radius: 50%;
transform: translate(-50%, -50%);
}
.weather-icon {
display: flex;
justify-content: center;
align-items: center;
margin-top: 15px;
}
.all-routes-container {
display: flex;
flex-wrap: wrap;
@ -1499,7 +1581,230 @@ input[type="checkbox"] {
display: none !important;
}
.container {
margin-left: 0;
margin-right:0;
width: 100% !important;
}
}
/* Full Screen Mode Styles */
.fullscreen-mode {
position: fixed;
top: 0;
left: 0;
width: 100vw;
height: 100vh;
z-index: 9998;
overflow: auto;
display: flex;
align-items: center;
justify-content: center;
}
.fullscreen-mode .app-main-content-list-container {
border: none;
padding: 20px;
min-height: 100vh;
width: 100%;
max-width: 1400px;
margin: 0 auto;
display: flex;
justify-content: center;
}
.fullscreen-mode .app-main-content-list-func-container {
width: 100%;
display: flex;
justify-content: center;
margin-top: auto;
}
.fullscreen-mode .multi-columns-container {
width: 100%;
max-width: 1200px;
margin: 0 auto;
display: flex;
justify-content: center;
gap: 20px;
}
.fullscreen-mode .column-container {
flex: 1;
max-width: 400px;
}
.fullscreen-mode .column-card {
background-color: rgba(255, 255, 255, 0.8) !important;
backdrop-filter: blur(5px);
margin-bottom: 20px;
}
.fullscreen-mode .card {
background-color: rgba(255, 255, 255, 0.8) !important;
backdrop-filter: blur(5px);
}
.fullscreen-hint {
position: fixed;
top: 20px;
right: 20px;
background-color: rgba(0, 0, 0, 0.7);
color: white;
padding: 10px 15px;
border-radius: 5px;
z-index: 9999;
font-size: 14px;
animation: fadeIn 0.3s ease-in;
}
@keyframes fadeIn {
from {
opacity: 0;
transform: translateY(-10px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
/* Background Image Styles */
.background-image-container {
background-size: cover;
background-position: center;
background-repeat: no-repeat;
background-attachment: fixed;
}
/* Info Screen Table Overrides for Full Screen */
.fullscreen-mode .info-screen-appointments-table {
background-color: rgba(255, 255, 255, 0.9);
}
/* Responsive adjustments for full screen mode */
@media (max-width: 1200px) {
.fullscreen-mode .multi-columns-container {
max-width: 1000px;
flex-direction: column;
gap: 15px;
}
.fullscreen-mode .column-container {
max-width: 100%;
width: 100%;
}
}
@media (max-width: 768px) {
.fullscreen-mode .app-main-content-list-container {
padding: 10px;
}
.fullscreen-mode .multi-columns-container {
gap: 10px;
}
.fullscreen-mode .column-card {
margin-bottom: 15px;
}
}
/* InfoScreen custom classes for moving inline styles to CSS */
.info-title {
font-size: 14px;
font-weight: 600;
}
.info-title-500 {
font-size: 14px;
font-weight: 500;
}
.info-logo {
height: 30px;
margin-right: 10px;
}
.info-logo-text {
color: #0066B1;
font-size: 16px;
}
.info-btn-icon {
color: #666;
}
.info-card {
border-radius: 8px;
max-width: 450px;
}
.info-card-gallery {
border-radius: 8px;
max-width: 450px;
}
.info-img-preview {
max-height: 120px;
width: 100%;
object-fit: cover;
}
.info-img-preview-lg {
max-height: 150px;
}
.info-img-preview-gallery {
height: 200px;
object-fit: cover;
border-radius: 4px;
}
.info-img-preview-bg {
max-height: 200px;
max-width: 100%;
}
.info-upload-area {
border: 2px dashed #dee2e6;
background-color: #f8f9fa;
cursor: pointer;
}
.info-upload-area-120 {
height: 120px;
border-radius: 8px;
}
.info-upload-area-200 {
height: 200px;
border-radius: 4px;
}
.info-weather-icon {
font-size: 48px;
color: #ffc107;
}
.info-clock-title {
font-size: 12px;
}
.info-clock-time {
font-size: 20px;
font-weight: 600;
}
.info-gallery-btn {
top: 5px;
right: 5px;
}
.info-gallery-img-thumb {
height: 100px;
width: 100%;
object-fit: cover;
}
.info-gallery-img-thumb-wrap {
position: relative;
}

View File

@ -68,7 +68,10 @@ import Home from "./components/home/home";
import Seating from "./components/seating/Seating";
import CenterCalendar from "./components/center-calendar/CenterCalendar";
import Dashboard from "./components/dashboard/Dashboard";
import AdminView from "./components/admin-view/AdminView";
import InfoScreen from './components/info-screen/InfoScreen';
function App() {
const [showMenu, setShowMenu] = useState(false);
@ -150,6 +153,9 @@ function App() {
<Route path="customer-report" element={<CustomerReport/>} />
</Route>
<Route path="/dashboard/dashboard" element={<Dashboard/>} />
<Route path="/dashboard/admin-view" element={<AdminView/>} />
<Route path="/seating" element={<Seating />} />
<Route path="/center-calendar" element={<CenterCalendar/>} />
@ -171,6 +177,8 @@ function App() {
<Route path="event-request" element={<CreateEventRequest />} />
<Route path="event-request/list" element={<EventRequestList />} />
</Route>
<Route path="/info-screen" element={<InfoScreen />} />
</Route>
</Routes>
</Router>

View File

@ -0,0 +1,124 @@
.admin-view-container {
min-width: 1300px;
}
.admin-view-card {
border-radius: 8px;
border: 1px solid #dee2e6;
}
.admin-view-stats-section {
border-right: 1px solid #dee2e6;
}
.admin-view-stats-label {
color: #000;
font-weight: 600;
}
.admin-view-chart-placeholder {
height: 250px;
display: flex;
align-items: center;
justify-content: center;
background-color: #f8f9fa;
border-radius: 4px;
}
.admin-view-reports-card {
border-radius: 8px;
border: 1px solid #dee2e6;
min-height: 537px;
}
.admin-view-reports-content {
height: 300px;
display: flex;
align-items: center;
justify-content: center;
background-color: #f8f9fa;
border-radius: 4px;
}
.admin-view-second-section-content {
height: 200px;
display: flex;
align-items: center;
justify-content: center;
background-color: #f8f9fa;
border-radius: 4px;
}
/* Week selector styles */
.admin-view-week-selector {
width: auto;
}
/* Chart container styles */
.admin-view-chart-container {
height: 450px;
position: relative;
}
/* Flexible chart container for New Admissions chart */
.admin-view-chart-container-flexible {
min-height: 450px;
height: auto;
position: relative;
}
/* Average display styles */
.admin-view-average-display {
position: absolute;
bottom: 40px;
right: 10px;
background-color: rgba(255, 255, 255, 0.9);
padding: 5px 10px;
font-size: 12px;
font-weight: bold;
}
/* Chart header styles */
.admin-view-chart-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 1rem;
}
.admin-view-chart-title {
margin-bottom: 0;
}
/* Donut chart stats styles */
.admin-view-donut-stats {
position: absolute;
top: 43%;
left: 50%;
transform: translate(-50%, -50%);
text-align: center;
pointer-events: none;
}
/* Legend overflow handling */
.admin-view-chart-container canvas + div {
overflow-x: auto;
overflow-y: hidden;
white-space: nowrap;
}
.admin-view-chart-container canvas + div ul {
white-space: nowrap;
display: flex;
flex-wrap: nowrap;
overflow-x: auto;
padding: 0;
margin: 0;
list-style: none;
}
.admin-view-chart-container canvas + div li {
white-space: nowrap;
flex-shrink: 0;
margin-right: 15px;
}

File diff suppressed because it is too large Load Diff

View File

@ -12,6 +12,7 @@ import {
} from '@schedule-x/calendar';
import { createEventsServicePlugin } from '@schedule-x/events-service';
import { createEventModalPlugin} from '@schedule-x/event-modal';
import { createEventRecurrencePlugin } from "@schedule-x/event-recurrence";
import '@schedule-x/theme-default/dist/calendar.css';
import { Archive, PencilSquare, Filter } from "react-bootstrap-icons";
import DatePicker from "react-datepicker";
@ -37,6 +38,7 @@ const EventsCalendar = () => {
const [showFilterDropdown, setShowFilterDropdown] = useState(false);
const eventsServicePlugin = createEventsServicePlugin();
const eventModalService = createEventModalPlugin();
const eventRecurrence = createEventRecurrencePlugin();
const [groupedEvents, setGroupedEvents] = useState(new Map());
const [showCreationModal, setShowCreationModal] = useState(false);
const [newEventStartDateTime, setNewEventStartDateTime] = useState(new Date());
@ -52,6 +54,8 @@ const EventsCalendar = () => {
const [newEventSourceType, setNewEventSourceType] = useState('');
const [newEventTargetType, setNewEventTargetType] = useState('');
const [newEventFutureDate, setNewEventFutureDate] = useState(undefined);
const [newEventReminderType, setNewEventReminderType] = useState('');
const [newEventRecurring, setNewEventRecurring] = useState(undefined);
const [vehicles, setVehicles] = useState([]);
const [employees, setEmployees] = useState([]);
@ -75,7 +79,7 @@ const EventsCalendar = () => {
skipValidation: true,
selectedDate: moment(new Date()).format('YYYY-MM-DD HH:mm'),
events: events,
plugins: [eventModalService, eventsServicePlugin],
plugins: [eventModalService, eventsServicePlugin, eventRecurrence],
callbacks: {
onSelectedDateUpdate(date) {
setFromDate(new Date(new Date(date).getFullYear(), new Date(date).getMonth(), 1));
@ -397,6 +401,8 @@ const handleSave = () => {
target_name: newEventTarget?.label,
event_location: newEventLocation,
event_prediction_date: newEventFutureDate && moment(newEventFutureDate).format('MM/DD/YYYY'),
event_reminder_type: newEventReminderType,
rrule: newEventRecurring,
status: 'active',
create_by: localStorage.getItem('user') && JSON.parse(localStorage.getItem('user'))?.name,
edit_by: localStorage.getItem('user') && JSON.parse(localStorage.getItem('user'))?.name,
@ -499,6 +505,20 @@ const handleSave = () => {
<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>
</div>
</div>
</div>
<div className="app-main-content-fields-section">
<div className="me-4">
<div className="field-label">Start Time
@ -592,17 +612,35 @@ const handleSave = () => {
</div>}
</div> </>}
<div className="app-main-content-fields-section">
{currentTab === 'reminderDatesCalendar' && <div className="me-4">
<div className="field-label">Reminder Type
</div>
<select value={newEventReminderType} onChange={(e) => setNewEventReminderType(e.target.value)}>
<option value=""></option>
<option value="birthday">Birthday</option>
<option value="membership">Membership Renew</option>
<option value="payment">Customer Payment Due Date</option>
<option value="insurance_renew">Customer Insurance Renew</option>
<option value="insurance_expire">Vehicle Insurance Renew</option>
<option value="title_expire">Vehicle Title Registration</option>
<option value="emission_test">Vehicle Emission Test</option>
<option value="oil_change">Vehicle Oil Change</option>
</select>
</div>}
{currentTab === 'activitiesCalendar' && <div className="me-4">
<div className="field-label">Event Location
</div>
<input type="text" placeholder="Type in the location this event gonna happen if applicable" value={newEventLocation || ''} onChange={e => setNewEventLocation(e.target.value)}/>
</div>}
{currentTab === 'reminderDatesCalendar' && <div className="me-4">
<div className="field-label">If this is reminder, please select the accurate Date it gonna happen:
<div className="field-label">If this is reminder which will happen later, please select the accurate Date it gonna happen:
</div>
<DatePicker selected={newEventFutureDate}
onChange={setNewEventFutureDate} dateFormat="MM/dd/yyyy"/>
</div>}
</div>
<div className="app-main-content-fields-section">
<div className="me-4">
<div className="field-label">Color
</div>

View File

@ -2,9 +2,11 @@ import React, {useState, useEffect} from "react";
import { useDispatch } from "react-redux";
import { useNavigate } from "react-router-dom";
import { customerSlice } from "../../store";
import { AuthService, CustomerService, ResourceService } from "../../services";
import { AuthService, CustomerService, ResourceService, parseDateFromBackend } from "../../services";
import Select from 'react-select';
import { CUSTOMER_TYPE, PICKUP_STATUS, PICKUP_STATUS_TEXT , CUSTOMER_TYPE_TEXT} from "../../shared";
import DatePicker from 'react-datepicker';
import "react-datepicker/dist/react-datepicker.css";
import { CUSTOMER_TYPE, PICKUP_STATUS, PICKUP_STATUS_TEXT , CUSTOMER_TYPE_TEXT, CUSTOMER_JOIN_REASON, CUSTOMER_JOIN_REASON_TEXT, CUSTOMER_DISCHARGE_REASON, CUSTOMER_DISCHARGE_REASON_TEXT} from "../../shared";
import { Spinner, Breadcrumb, BreadcrumbItem, Tabs, Tab } from "react-bootstrap";
import { Upload } from "react-bootstrap-icons";
@ -19,7 +21,7 @@ const CreateCustomer = () => {
const [nameOnId, setNameOnId] = useState('')
const [lastname, setLastname] = useState('');
const [nameCN, setNameCN] = useState('');
const [birthDate, setBirthDate] = useState('');
const [birthDate, setBirthDate] = useState(null);
const [email, setEmail] = useState('');
const [mobilePhone, setMobilePhone] = useState('');
const [phone, setPhone] = useState('');
@ -31,6 +33,27 @@ const CreateCustomer = () => {
const [address3, setAddress3] = useState('');
const [address4, setAddress4] = useState('');
const [address5, setAddress5] = useState('');
const [addressCount, setAddressCount] = useState(1);
const [streetAddress1, setStreetAddress1] = useState('');
const [city1, setCity1] = useState('');
const [state1, setState1] = useState('');
const [zipCode1, setZipCode1] = useState('');
const [streetAddress2, setStreetAddress2] = useState('');
const [city2, setCity2] = useState('');
const [state2, setState2] = useState('');
const [zipCode2, setZipCode2] = useState('');
const [streetAddress3, setStreetAddress3] = useState('');
const [city3, setCity3] = useState('');
const [state3, setState3] = useState('');
const [zipCode3, setZipCode3] = useState('');
const [streetAddress4, setStreetAddress4] = useState('');
const [city4, setCity4] = useState('');
const [state4, setState4] = useState('');
const [zipCode4, setZipCode4] = useState('');
const [streetAddress5, setStreetAddress5] = useState('');
const [city5, setCity5] = useState('');
const [state5, setState5] = useState('');
const [zipCode5, setZipCode5] = useState('');
const [customerType, setCustomerType] = useState('');
const [selectedFile, setSelectedFile] = useState();
const [careProvider, setCareProvider] = useState('');
@ -41,13 +64,20 @@ const CreateCustomer = () => {
const [pickupStatus, setPickupStatus] = useState('');
const [specialNeeds, setSpecialNeeds] = useState('');
const [emergencyContact, setEmergencyContact] = useState('');
const [admissionDate, setAdmissionDate] = useState('');
const [emergencyContactCount, setEmergencyContactCount] = useState(1);
const [emergencyContactName, setEmergencyContactName] = useState('');
const [emergencyContactPhone, setEmergencyContactPhone] = useState('');
const [emergencyContactRelationship, setEmergencyContactRelationship] = useState('');
const [emergencyContact2Name, setEmergencyContact2Name] = useState('');
const [emergencyContact2Phone, setEmergencyContact2Phone] = useState('');
const [emergencyContact2Relationship, setEmergencyContact2Relationship] = useState('');
const [admissionDate, setAdmissionDate] = useState(null);
const [vehicleNo, setVehicleNo] = useState('');
const [note, setNote] = useState('');
const [pin, setPin] = useState('');
const [seating, setSeating] = useState('');
const [caller, setCaller] = useState('');
const [dischargeDate, setDischargeDate] = useState('');
const [dischargeDate, setDischargeDate] = useState(null);
const [placement, setPlacement] = useState('');
const [nickname, setNickname] = useState('');
const [groups, setGroups] = useState('');
@ -62,6 +92,13 @@ const CreateCustomer = () => {
const [heightFeet, setHeightFeet] = useState('');
const [heightInch, setHeightInch] = useState('');
const [textMsgEnabled, setTextMsgEnabled] = useState(false);
const [healthCondition, setHealthCondition] = useState('');
const [allergyInfo, setAllergyInfo] = useState('');
const [serviceRequirement, setServiceRequirement] = useState('');
const [paymentDueDate, setPaymentDueDate] = useState(null);
const [paymentStatus, setPaymentStatus] = useState('');
const [joinReason, setJoinReason] = useState('');
const [dischargeReason, setDischargeReason] = useState('');
const params = new URLSearchParams(window.location.search);
const redirectTo = () => {
navigate('/customers/list');
@ -92,14 +129,34 @@ const CreateCustomer = () => {
type: customerType,
language,
status: 'active',
address1,
address2,
address3,
address4,
address5,
address1: combineAddress(streetAddress1, city1, state1, zipCode1),
address2: combineAddress(streetAddress2, city2, state2, zipCode2),
address3: combineAddress(streetAddress3, city3, state3, zipCode3),
address4: combineAddress(streetAddress4, city4, state4, zipCode4),
address5: combineAddress(streetAddress5, city5, state5, zipCode5),
street_address_1: streetAddress1,
city1: city1,
state1: state1,
zip_code1: zipCode1,
street_address_2: streetAddress2,
city2: city2,
state2: state2,
zip_code2: zipCode2,
street_address_3: streetAddress3,
city3: city3,
state3: state3,
zip_code3: zipCode3,
street_address_4: streetAddress4,
city4: city4,
state4: state4,
zip_code4: zipCode4,
street_address_5: streetAddress5,
city5: city5,
state5: state5,
zip_code5: zipCode5,
firstname,
lastname,
birth_date: birthDate,
birth_date: formatDateForBackend(birthDate),
care_provider: careProvider,
medicare_number: medicareNumber,
medicaid_number: medicaidNumber,
@ -107,10 +164,17 @@ const CreateCustomer = () => {
pharmacy_id: pharmacyId,
pickup_status: pickupStatus,
special_needs: specialNeeds,
emergency_contact: emergencyContact,
admission_date: admissionDate,
emergency_contact: combineEmergencyContact(emergencyContactName, emergencyContactPhone, emergencyContactRelationship),
emergency_contact2: combineEmergencyContact(emergencyContact2Name, emergencyContact2Phone, emergencyContact2Relationship),
emergency_contact_name: emergencyContactName,
emergency_contact_phone: emergencyContactPhone,
emergency_contact_relationship: emergencyContactRelationship,
emergency_contact2_name: emergencyContact2Name,
emergency_contact2_phone: emergencyContact2Phone,
emergency_contact2_relationship: emergencyContact2Relationship,
admission_date: formatDateForBackend(admissionDate),
vehicle_no: vehicleNo,
discharge_date: dischargeDate,
discharge_date: formatDateForBackend(dischargeDate),
create_by: localStorage.getItem('user') && JSON.parse(localStorage.getItem('user'))?.name || '',
edit_by: localStorage.getItem('user') && JSON.parse(localStorage.getItem('user'))?.name || '',
note,
@ -128,7 +192,14 @@ const CreateCustomer = () => {
weight,
gender,
height: `${heightFeet} ft ${heightInch} in`,
text_msg_enabled: textMsgEnabled === 'true' || false
text_msg_enabled: textMsgEnabled === 'true' || false,
health_condition: healthCondition,
allergy_info: allergyInfo,
service_requirement: serviceRequirement,
payment_due_date: formatDateForBackend(paymentDueDate),
payment_status: paymentStatus,
join_reason: joinReason,
discharge_reason: dischargeReason
};
const dataForLegacy = {
username,
@ -148,16 +219,16 @@ const CreateCustomer = () => {
address2,
firstname,
lastname,
birth_date: birthDate,
birth_date: formatDateForBackend(birthDate),
care_provider: careProvider,
medicare_number: medicareNumber,
medicaid_number: medicaidNumber,
pharmacy: pharmacy?.label || '',
pharmacy_id: pharmacyId,
emergency_contact: emergencyContact,
admission_date: admissionDate,
admission_date: formatDateForBackend(admissionDate),
vehicle_no: vehicleNo,
discharge_date: dischargeDate,
discharge_date: formatDateForBackend(dischargeDate),
create_by: localStorage.getItem('user') && JSON.parse(localStorage.getItem('user'))?.name || '',
edit_by: localStorage.getItem('user') && JSON.parse(localStorage.getItem('user'))?.name || '',
note,
@ -186,6 +257,93 @@ const CreateCustomer = () => {
setPharmacyId(selectedPharmacy?.value);
}
const formatDateForBackend = (date) => {
if (!date) return '';
const d = new Date(date);
const month = String(d.getMonth() + 1).padStart(2, '0');
const day = String(d.getDate()).padStart(2, '0');
const year = d.getFullYear();
return `${month}/${day}/${year}`;
}
// Helper function to combine address fields into a single string
const combineAddress = (streetAddress, city, state, zipCode) => {
const parts = [streetAddress, city, state, zipCode].filter(part => part && part.trim());
return parts.join(', ');
};
// Helper function to split address string into components
const splitAddress = (addressString) => {
if (!addressString) return { streetAddress: '', city: '', state: '', zipCode: '' };
const parts = addressString.split(',').map(part => part.trim());
if (parts.length === 1) {
return { streetAddress: parts[0], city: '', state: '', zipCode: '' };
} else if (parts.length === 2) {
return { streetAddress: parts[0], city: parts[1], state: '', zipCode: '' };
} else if (parts.length === 3) {
return { streetAddress: parts[0], city: parts[1], state: parts[2], zipCode: '' };
} else {
return {
streetAddress: parts[0],
city: parts[1],
state: parts[2],
zipCode: parts.slice(3).join(', ')
};
}
};
// Function to add a new address line
const addAddressLine = () => {
if (addressCount < 5) {
setAddressCount(addressCount + 1);
}
};
// Function to remove an address line
const removeAddressLine = (index) => {
if (addressCount > 1) {
setAddressCount(addressCount - 1);
// Clear the fields for the removed address
const clearAddressFields = (num) => {
switch(num) {
case 1: setStreetAddress1(''); setCity1(''); setState1(''); setZipCode1(''); break;
case 2: setStreetAddress2(''); setCity2(''); setState2(''); setZipCode2(''); break;
case 3: setStreetAddress3(''); setCity3(''); setState3(''); setZipCode3(''); break;
case 4: setStreetAddress4(''); setCity4(''); setState4(''); setZipCode4(''); break;
case 5: setStreetAddress5(''); setCity5(''); setState5(''); setZipCode5(''); break;
}
};
clearAddressFields(index);
}
};
// Helper function to combine emergency contact fields into a single string
const combineEmergencyContact = (name, phone, relationship) => {
const parts = [name, phone, relationship].filter(part => part && part.trim());
return parts.join(' ');
};
// Function to add a new emergency contact
const addEmergencyContact = () => {
if (emergencyContactCount < 2) {
setEmergencyContactCount(emergencyContactCount + 1);
}
};
// Function to remove an emergency contact
const removeEmergencyContact = (index) => {
if (emergencyContactCount > 1) {
setEmergencyContactCount(emergencyContactCount - 1);
// Clear the fields for the removed emergency contact
if (index === 2) {
setEmergencyContact2Name('');
setEmergencyContact2Phone('');
setEmergencyContact2Relationship('');
}
}
};
return (
<>
<div className="list row mb-4">
@ -218,15 +376,33 @@ const CreateCustomer = () => {
<div className="app-main-content-fields-section">
<div className="me-4">
<div className="field-label">Admission Date</div>
<input type="text" placeholder="e.g.,01/15/2025" value={admissionDate || ''} onChange={e => setAdmissionDate(e.target.value)}/>
<DatePicker
selected={admissionDate}
onChange={(date) => setAdmissionDate(date)}
dateFormat="MM/dd/yyyy"
placeholderText="MM/DD/YYYY"
className="form-control"
/>
</div>
<div className="me-4">
<div className="field-label">Discharge Date</div>
<input type="text" placeholder="e.g.,01/15/2025" value={dischargeDate || ''} onChange={e => setDischargeDate(e.target.value)}/>
<DatePicker
selected={dischargeDate}
onChange={(date) => setDischargeDate(date)}
dateFormat="MM/dd/yyyy"
placeholderText="MM/DD/YYYY"
className="form-control"
/>
</div>
<div className="me-4">
<div className="field-label">Birth Date</div>
<input type="text" placeholder="e.g.,01/15/2025" value={birthDate || ''} onChange={e => setBirthDate(e.target.value)}/>
<DatePicker
selected={birthDate}
onChange={(date) => setBirthDate(date)}
dateFormat="MM/dd/yyyy"
placeholderText="MM/DD/YYYY"
className="form-control"
/>
</div>
</div>
<div className="app-main-content-fields-section">
@ -284,40 +460,198 @@ const CreateCustomer = () => {
</div>
</div>
{/* We will do Address and Emergency later */}
<h6 className="text-primary">Home Addresses</h6>
<div className="app-main-content-fields-section">
<div className="me-4">
<div className="field-label">Address 1 <span className="required">*</span></div>
<input type="text" value={address1 || ''} onChange={e => setAddress1(e.target.value)}/>
{[1, 2, 3, 4, 5].map((index) => (
index <= addressCount && (
<>
<h6 className="text-secondary mb-0">
Address {index} {index === 1 && <span className="required">*</span>}
{index > 1 && (
<button
type="button"
className="btn btn-sm btn-outline-danger ms-2"
onClick={() => removeAddressLine(index)}
>
Remove
</button>
)}
</h6>
<div key={index} className="app-main-content-fields-section">
<div className="me-4">
<div className="field-label">Street Address</div>
<input
type="text"
placeholder="e.g., 123 Main St"
value={index === 1 ? streetAddress1 : index === 2 ? streetAddress2 : index === 3 ? streetAddress3 : index === 4 ? streetAddress4 : streetAddress5}
onChange={e => {
const value = e.target.value;
switch(index) {
case 1: setStreetAddress1(value); break;
case 2: setStreetAddress2(value); break;
case 3: setStreetAddress3(value); break;
case 4: setStreetAddress4(value); break;
case 5: setStreetAddress5(value); break;
}
}}
/>
</div>
<div className="me-4">
<div className="field-label">City</div>
<input
type="text"
placeholder="e.g., Rockville"
value={index === 1 ? city1 : index === 2 ? city2 : index === 3 ? city3 : index === 4 ? city4 : city5}
onChange={e => {
const value = e.target.value;
switch(index) {
case 1: setCity1(value); break;
case 2: setCity2(value); break;
case 3: setCity3(value); break;
case 4: setCity4(value); break;
case 5: setCity5(value); break;
}
}}
/>
</div>
<div className="me-4">
<div className="field-label">State</div>
<input
type="text"
placeholder="e.g., MD"
value={index === 1 ? state1 : index === 2 ? state2 : index === 3 ? state3 : index === 4 ? state4 : state5}
onChange={e => {
const value = e.target.value;
switch(index) {
case 1: setState1(value); break;
case 2: setState2(value); break;
case 3: setState3(value); break;
case 4: setState4(value); break;
case 5: setState5(value); break;
}
}}
/>
</div>
<div className="me-4">
<div className="field-label">Zip Code</div>
<input
type="text"
placeholder="e.g., 20850"
value={index === 1 ? zipCode1 : index === 2 ? zipCode2 : index === 3 ? zipCode3 : index === 4 ? zipCode4 : zipCode5}
onChange={e => {
const value = e.target.value;
switch(index) {
case 1: setZipCode1(value); break;
case 2: setZipCode2(value); break;
case 3: setZipCode3(value); break;
case 4: setZipCode4(value); break;
case 5: setZipCode5(value); break;
}
}}
/>
</div>
</div>
</>
)
))}
{addressCount < 5 && (
<div className="app-main-content-fields-section">
<button
type="button"
className="btn btn-outline-primary"
onClick={addAddressLine}
>
+ Add Another Address
</button>
</div>
</div>
<div className="app-main-content-fields-section">
<div className="me-4">
<div className="field-label">Address 2</div>
<input type="text" value={address2 || ''} onChange={e => setAddress2(e.target.value)}/>
</div>
<div className="me-4">
<div className="field-label">Address 3</div>
<input type="text" value={address3 || ''} onChange={e => setAddress3(e.target.value)}/>
</div>
<div className="me-4">
<div className="field-label">Address 4</div>
<input type="text" value={address4 || ''} onChange={e => setAddress4(e.target.value)}/>
</div>
<div className="me-4">
<div className="field-label">Address 5</div>
<input type="text" value={address5 || ''} onChange={e => setAddress5(e.target.value)}/>
</div>
</div>
)}
<h6 className="text-primary">Emergency Contact Information</h6>
<div className="app-main-content-fields-section">
<div className="me-4">
<div className="field-label">Emergency Contact <span className="required">*</span> </div>
<input type="text" placeholder="e.g.,John 240-463-1698" className="long" value={emergencyContact || ''} onChange={e => setEmergencyContact(e.target.value)}/>
{[1, 2].map((index) => (
index <= emergencyContactCount && (
<>
<h6 className="text-secondary mb-0">
{index === 1 ? 'Primary Emergency Contact' : 'Emergency Contact 2'}
{index === 1 && <span className="required">*</span>}
{index > 1 && (
<button
type="button"
className="btn btn-sm btn-outline-danger ms-2"
onClick={() => removeEmergencyContact(index)}
>
Remove
</button>
)}
</h6>
<div key={index} className="app-main-content-fields-section">
<div className="me-4">
<div className="field-label">Name</div>
<input
type="text"
placeholder="e.g., John Smith"
value={index === 1 ? emergencyContactName : emergencyContact2Name}
onChange={e => {
const value = e.target.value;
if (index === 1) {
setEmergencyContactName(value);
} else {
setEmergencyContact2Name(value);
}
}}
/>
</div>
<div className="me-4">
<div className="field-label">Phone</div>
<input
type="text"
placeholder="e.g., 240-463-1698"
value={index === 1 ? emergencyContactPhone : emergencyContact2Phone}
onChange={e => {
const value = e.target.value;
if (index === 1) {
setEmergencyContactPhone(value);
} else {
setEmergencyContact2Phone(value);
}
}}
/>
</div>
<div className="me-4">
<div className="field-label">Relationship</div>
<input
type="text"
placeholder="e.g., Spouse, Son, Daughter"
value={index === 1 ? emergencyContactRelationship : emergencyContact2Relationship}
onChange={e => {
const value = e.target.value;
if (index === 1) {
setEmergencyContactRelationship(value);
} else {
setEmergencyContact2Relationship(value);
}
}}
/>
</div>
</div>
</>
)
))}
{emergencyContactCount < 2 && (
<div className="app-main-content-fields-section">
<button
type="button"
className="btn btn-outline-primary"
onClick={addEmergencyContact}
>
+ Add Another Emergency Contact
</button>
</div>
</div>
)}
<h6 className="text-primary">Service Information</h6>
<div className="app-main-content-fields-section">
@ -359,6 +693,70 @@ const CreateCustomer = () => {
<div className="field-label">Special Needs</div>
<input type="text" placeholder="e.g.,Special Needs" value={specialNeeds || ''} onChange={e => setSpecialNeeds(e.target.value)}/>
</div>
<div className="me-4">
<div className="field-label">Health Condition</div>
<select value={healthCondition} onChange={e => setHealthCondition(e.target.value)}>
<option value=""></option>
<option value="diabetes">Diabetes</option>
<option value="1-1">1-1</option>
<option value="rounding list">Rounding List</option>
<option value="MOLST/POA/Advanced Directive">MOLST/POA/Advanced Directive</option>
</select>
</div>
<div className="me-4">
<div className="field-label">Allergy Information</div>
<input type="text" placeholder="e.g.,Peanuts, Shellfish" value={allergyInfo || ''} onChange={e => setAllergyInfo(e.target.value)}/>
</div>
</div>
<div className="app-main-content-fields-section">
<div className="me-4">
<div className="field-label">Service Requirements</div>
<select value={serviceRequirement} onChange={e => setServiceRequirement(e.target.value)}>
<option value=""></option>
<option value="wheelchair">Wheelchair</option>
<option value="special care">Special Care</option>
</select>
</div>
<div className="me-4">
<div className="field-label">Payment Due Date</div>
<DatePicker
selected={paymentDueDate}
onChange={(date) => setPaymentDueDate(date)}
dateFormat="MM/dd/yyyy"
placeholderText="MM/DD/YYYY"
className="form-control"
/>
</div>
<div className="me-4">
<div className="field-label">Payment Status</div>
<select value={paymentStatus} onChange={e => setPaymentStatus(e.target.value)}>
<option value=""></option>
<option value="paid">Paid</option>
<option value="overdue">Overdue</option>
</select>
</div>
<div className="me-4">
<div className="field-label">Join Reason</div>
<select value={joinReason} onChange={e => setJoinReason(e.target.value)}>
<option value=""></option>
{Object.keys(CUSTOMER_JOIN_REASON).map(key => (
<option key={key} value={CUSTOMER_JOIN_REASON[key]}>
{CUSTOMER_JOIN_REASON_TEXT[CUSTOMER_JOIN_REASON[key]]}
</option>
))}
</select>
</div>
<div className="me-4">
<div className="field-label">Discharge Reason</div>
<select value={dischargeReason} onChange={e => setDischargeReason(e.target.value)}>
<option value=""></option>
{Object.keys(CUSTOMER_DISCHARGE_REASON).map(key => (
<option key={key} value={CUSTOMER_DISCHARGE_REASON[key]}>
{CUSTOMER_DISCHARGE_REASON_TEXT[CUSTOMER_DISCHARGE_REASON[key]]}
</option>
))}
</select>
</div>
</div>
<h6 className="text-primary">Additional Information</h6>
<div className="app-main-content-fields-section base-line">

View File

@ -2,9 +2,9 @@ import React, {useState, useEffect} from "react";
import { useDispatch, useSelector } from "react-redux";
import { useNavigate, useParams } from "react-router-dom";
import { customerSlice } from "./../../store";
import { AuthService, CustomerService, EventsService } from "../../services";
import { CUSTOMER_TYPE } from "../../shared";
import { Spinner, Breadcrumb, BreadcrumbItem, Tabs, Tab } from "react-bootstrap";
import { AuthService, CustomerService, EventsService, LabelService } from "../../services";
import { CUSTOMER_TYPE, ManageTable, Export } from "../../shared";
import { Spinner, Breadcrumb, BreadcrumbItem, Tabs, Tab, Dropdown } from "react-bootstrap";
import { Columns, Download, Filter, PencilSquare, PersonSquare, Plus } from "react-bootstrap-icons";
@ -20,6 +20,104 @@ const CustomersList = () => {
const [sorting, setSorting] = useState({key: '', order: ''});
const [selectedItems, setSelectedItems] = useState([]);
const [filteredCustomers, setFilteredCustomers] = useState(customers);
const [showFilterDropdown, setShowFilterDropdown] = useState(false);
const [healthConditionFilter, setHealthConditionFilter] = useState('');
const [paymentStatusFilter, setPaymentStatusFilter] = useState('');
const [serviceRequirementFilter, setServiceRequirementFilter] = useState('');
const [tagsFilter, setTagsFilter] = useState([]);
const [availableLabels, setAvailableLabels] = useState([]);
const [columns, setColumns] = useState([
{
key: 'name',
label:'Name',
show: true
},
{
key: 'chinese_name',
label: 'Preferred Name',
show: true
},
{
key: 'email',
label: 'Email',
show: true
},
{
key: 'type',
label: 'Type',
show: true
},
{
key: 'pickup_status',
label: 'Pickup Status',
show: true
},
{
key: 'birth_date',
label: 'Date of Birth',
show: true
},
{
key: 'gender',
label: 'Gender',
show: true
},
{
key: 'language',
label: 'Language',
show: true
},
{
key: 'medicare_number',
label: 'Medicare Number',
show: true
},
{
key: 'medicaid_number',
label: 'Medicaid Number',
show: true
},
{
key: 'address',
label: 'Address',
show: true
},
{
key: 'phone',
label: 'Phone',
show: true
},
{
key: 'emergency_contact',
label: 'Fasting',
show: true
},
{
key: 'health_condition',
label: 'Health Condition',
show: true
},
{
key: 'payment_status',
label: 'Payment Status',
show: true
},
{
key: 'payment_due_date',
label: 'Payment Due Date',
show: true
},
{
key: 'service_requirement',
label: 'Service Requirement',
show: true
},
{
key: 'tags',
label: 'Tags',
show: true
}
]);
useEffect(() => {
if (!AuthService.canViewCustomers()) {
@ -35,6 +133,9 @@ const CustomersList = () => {
return item;
}).sort((a, b) => a.lastname > b.lastname ? 1: -1));
})
LabelService.getAll().then((data) => {
setAvailableLabels(data.data);
})
// EventsService.getAllEvents({ from: EventsService.formatDate(new Date()), to: '9999-12-31'}).then((data) => {
// console.log('events', data.data)
// setEvents(data.data);
@ -42,12 +143,45 @@ const CustomersList = () => {
}, []);
useEffect(() => {
if (showInactive) {
setFilteredCustomers(customers && customers.filter((item) => item?.name.toLowerCase().includes(keyword.toLowerCase())).filter(item => (item.type === CUSTOMER_TYPE.TRANSFERRED || item.type === CUSTOMER_TYPE.DECEASED || item.type === CUSTOMER_TYPE.DISCHARED) && item.status !== 'active'));
} else {
setFilteredCustomers(customers && customers.filter((item) => item?.name.toLowerCase().includes(keyword.toLowerCase())).filter(item => (item.type !== CUSTOMER_TYPE.TRANSFERRED && item.type!=CUSTOMER_TYPE.DECEASED && item.type!=CUSTOMER_TYPE.DISCHARED) && item.status === 'active'));
let filtered = customers;
// Basic keyword filter
if (keyword) {
filtered = filtered.filter((item) => item?.name.toLowerCase().includes(keyword.toLowerCase()));
}
}, [keyword, customers])
// Active/Inactive filter
if (showInactive) {
filtered = filtered.filter(item => (item.type === CUSTOMER_TYPE.TRANSFERRED || item.type === CUSTOMER_TYPE.DECEASED || item.type === CUSTOMER_TYPE.DISCHARED) && item.status !== 'active');
} else {
filtered = filtered.filter(item => (item.type !== CUSTOMER_TYPE.TRANSFERRED && item.type!=CUSTOMER_TYPE.DECEASED && item.type!=CUSTOMER_TYPE.DISCHARED) && item.status === 'active');
}
// Health condition filter
if (healthConditionFilter) {
filtered = filtered.filter(item => item?.health_condition === healthConditionFilter);
}
// Payment status filter
if (paymentStatusFilter) {
filtered = filtered.filter(item => item?.payment_status === paymentStatusFilter);
}
// Service requirement filter
if (serviceRequirementFilter) {
filtered = filtered.filter(item => item?.service_requirement === serviceRequirementFilter);
}
// Tags filter
if (tagsFilter.length > 0) {
filtered = filtered.filter(item => {
if (!item?.tags || item.tags.length === 0) return false;
return tagsFilter.some(tag => item.tags.includes(tag));
});
}
setFilteredCustomers(filtered);
}, [keyword, customers, showInactive, healthConditionFilter, paymentStatusFilter, serviceRequirementFilter, tagsFilter])
useEffect(() => {
const newCustomers = [...customers];
@ -59,69 +193,6 @@ const CustomersList = () => {
)
}, [sorting]);
const columns = [
{
key: 'name',
label:'Name'
},
{
key: 'chinese_name',
label: 'Preferred Name'
},
{
key: 'email',
label: 'Email'
},
{
key: 'type',
label: 'Type'
},
{
key: 'pickup_status',
label: 'Pickup Status'
},
{
key: 'birth_date',
label: 'Date of Birth'
},
{
key: 'gender',
label: 'Gender'
},
{
key: 'language',
label: 'Language'
},
{
key: 'medicare_number',
label: 'Medicare Number',
},
{
key: 'medicaid_number',
label: 'Medicaid Number'
},
{
key: 'address',
label: 'Address'
},
{
key: 'phone',
label: 'Phone'
},
{
key: 'emergency_contact',
label: 'Fasting'
}
];
const getSortingImg = (key) => {
return sorting.key === key ? (sorting.order === 'asc' ? 'up_arrow' : 'down_arrow') : 'default';
}
@ -170,6 +241,30 @@ const CustomersList = () => {
return selectedItems.length === filteredCustomers.length && selectedItems.length > 0;
}
const cleanFilterAndClose = () => {
setHealthConditionFilter('');
setPaymentStatusFilter('');
setServiceRequirementFilter('');
setTagsFilter([]);
setShowFilterDropdown(false);
}
const FilterAndClose = () => {
setShowFilterDropdown(false);
}
const toggleTagFilter = (tagName) => {
if (tagsFilter.includes(tagName)) {
setTagsFilter(tagsFilter.filter(tag => tag !== tagName));
} else {
setTagsFilter([...tagsFilter, tagName]);
}
}
const handleColumnsChange = (newColumns) => {
setColumns(newColumns);
}
const goToEdit = (id) => {
navigate(`/customers/edit/${id}`)
}
@ -288,7 +383,7 @@ const CustomersList = () => {
<th className="th-checkbox"><input type="checkbox" checked={checkSelectAll()} onClick={() => toggleSelectedAllItems()}></input></th>
<th className="th-index">No.</th>
{
columns.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>
</th>)
}
@ -302,19 +397,24 @@ const CustomersList = () => {
filteredCustomers.map((customer, index) => <tr key={customer.id}>
<td className="td-checkbox"><input type="checkbox" checked={selectedItems.includes(customer.id)} onClick={()=>toggleItem(customer?.id)}/></td>
<td className="td-index">{index + 1}</td>
<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>
<td>{customer?.name_cn}</td>
<td>{customer?.email}</td>
<td>{customer?.type}</td>
<td>{customer?.pickup_status}</td>
<td>{customer?.birth_date}</td>
<td>{customer?.gender}</td>
<td>{customer?.language}</td>
<td>{customer?.medicare_number}</td>
<td>{customer?.medicaid_number}</td>
<td>{customer?.address1 || customer?.address2 || customer?.address3 || customer?.address4 || customer?.address5}</td>
<td>{customer?.phone || customer?.home_phone || customer?.mobile_phone}</td>
<td>{customer?.emergency_contact}</td>
{columns.find(col => col.key === 'name')?.show && <td> {AuthService.canAddOrEditCustomers() && <PencilSquare size={16} className="clickable me-2" onClick={() => goToEdit(customer?.id)}></PencilSquare>} {AuthService.canViewCustomers() && <PersonSquare onClick={() => goToView(customer?.id)} size={16} className="clickable me-2" />} {customer?.name}</td>}
{columns.find(col => col.key === 'chinese_name')?.show && <td>{customer?.name_cn}</td>}
{columns.find(col => col.key === 'email')?.show && <td>{customer?.email}</td>}
{columns.find(col => col.key === 'type')?.show && <td>{customer?.type}</td>}
{columns.find(col => col.key === 'pickup_status')?.show && <td>{customer?.pickup_status}</td>}
{columns.find(col => col.key === 'birth_date')?.show && <td>{customer?.birth_date}</td>}
{columns.find(col => col.key === 'gender')?.show && <td>{customer?.gender}</td>}
{columns.find(col => col.key === 'language')?.show && <td>{customer?.language}</td>}
{columns.find(col => col.key === 'medicare_number')?.show && <td>{customer?.medicare_number}</td>}
{columns.find(col => col.key === 'medicaid_number')?.show && <td>{customer?.medicaid_number}</td>}
{columns.find(col => col.key === 'address')?.show && <td>{customer?.address1 || customer?.address2 || customer?.address3 || customer?.address4 || customer?.address5}</td>}
{columns.find(col => col.key === 'phone')?.show && <td>{customer?.phone || customer?.home_phone || customer?.mobile_phone}</td>}
{columns.find(col => col.key === 'emergency_contact')?.show && <td>{customer?.emergency_contact}</td>}
{columns.find(col => col.key === 'health_condition')?.show && <td>{customer?.health_condition}</td>}
{columns.find(col => col.key === 'payment_status')?.show && <td>{customer?.payment_status}</td>}
{columns.find(col => col.key === 'payment_due_date')?.show && <td>{customer?.payment_due_date}</td>}
{columns.find(col => col.key === 'service_requirement')?.show && <td>{customer?.service_requirement}</td>}
{columns.find(col => col.key === 'tags')?.show && <td>{customer?.tags?.join(', ')}</td>}
<td>
{AuthService.canViewCustomers() && <button className="btn btn-link btn-sm me-2" onClick={() => exportCSV(customer)}>Export Medical Events</button>}
</td>
@ -339,7 +439,79 @@ const CustomersList = () => {
</div>
</div>;
const customFilterMenu = React.forwardRef(
({ children, style, className, 'aria-labelledby': labeledBy }, ref) => {
return (
<div
ref={ref}
style={style}
className={className}
aria-labelledby={labeledBy}
>
<h6>Filter By</h6>
<div className="app-main-content-fields-section margin-sm dropdown-container">
<div className="me-4">
<div className="field-label">Health Condition</div>
<select value={healthConditionFilter} onChange={(e) => setHealthConditionFilter(e.target.value)}>
<option value=""></option>
<option value="diabetes">Diabetes</option>
<option value="1-1">1-1</option>
<option value="rounding list">Rounding List</option>
<option value="MOLST/POA/Advanced Directive">MOLST/POA/Advanced Directive</option>
</select>
</div>
</div>
<div className="app-main-content-fields-section margin-sm dropdown-container">
<div className="me-4">
<div className="field-label">Payment Status</div>
<select value={paymentStatusFilter} onChange={(e) => setPaymentStatusFilter(e.target.value)}>
<option value=""></option>
<option value="paid">Paid</option>
<option value="overdue">Overdue</option>
</select>
</div>
</div>
<div className="app-main-content-fields-section margin-sm dropdown-container">
<div className="me-4">
<div className="field-label">Service Requirement</div>
<select value={serviceRequirementFilter} onChange={(e) => setServiceRequirementFilter(e.target.value)}>
<option value=""></option>
<option value="wheelchair">Wheelchair</option>
<option value="special care">Special Care</option>
</select>
</div>
</div>
<div className="app-main-content-fields-section margin-sm dropdown-container">
<div className="me-4">
<div className="field-label">Tags</div>
<div style={{ maxHeight: '150px', overflowY: 'auto' }}>
{availableLabels.map((label) => (
<div key={label.id} style={{ marginBottom: '5px' }}>
<input
type="checkbox"
id={`tag-${label.id}`}
checked={tagsFilter.includes(label.label_name)}
onChange={() => toggleTagFilter(label.label_name)}
/>
<label htmlFor={`tag-${label.id}`} style={{ marginLeft: '5px' }}>
{label.label_name}
</label>
</div>
))}
</div>
</div>
</div>
<div className="list row">
<div className="col-md-12">
<button className="btn btn-default btn-sm float-right" onClick={() => cleanFilterAndClose()}> Cancel </button>
<button className="btn btn-primary btn-sm float-right" onClick={() => FilterAndClose()}> Filter </button>
</div>
</div>
</div>
);
},
);
return (
<>
{showSpinner && <div className="spinner-overlay">
@ -374,10 +546,31 @@ const CustomersList = () => {
</Tabs>
<div className="list-func-panel">
<input className="me-2 with-search-icon" type="text" placeholder="Search" value={keyword} onChange={(e) => setKeyword(e.currentTarget.value)} />
<button className="btn btn-primary me-2"><Filter size={16} className="me-2"></Filter>Filter</button>
<button className="btn btn-primary me-2"><Columns size={16} className="me-2"></Columns>Manage Table</button>
<Dropdown
key={'filter-customers'}
id="filter-customers"
className="me-2"
show={showFilterDropdown}
onToggle={() => setShowFilterDropdown(!showFilterDropdown)}
autoClose={false}
>
<Dropdown.Toggle variant="primary">
<Filter size={16} className="me-2"></Filter>Filter
</Dropdown.Toggle>
<Dropdown.Menu as={customFilterMenu}/>
</Dropdown>
<ManageTable columns={columns} onColumnsChange={handleColumnsChange} />
<button className="btn btn-primary me-2" onClick={() => goToCreateNew()}><Plus size={16}></Plus>Add New Customer</button>
<button className="btn btn-primary"><Download size={16} className="me-2"></Download>Export</button>
<Export
columns={columns}
data={filteredCustomers.map((customer) => ({
...customer,
address: customer?.address1 || customer?.address2 || customer?.address3 || customer?.address4 || customer?.address5,
phone: customer?.phone || customer?.home_phone || customer?.mobile_phone,
tags: customer?.tags?.join(', ')
}))}
filename="customers"
/>
</div>
</div>
</div>

View File

@ -2,10 +2,12 @@ import React, {useState, useEffect} from "react";
import { useDispatch } from "react-redux";
import { useNavigate, useParams } from "react-router-dom";
import { customerSlice } from "./../../store";
import { AuthService, CustomerService, ResourceService } from "../../services";
import { AuthService, CustomerService, ResourceService, parseDateFromBackend } from "../../services";
import Select from 'react-select';
import DatePicker from 'react-datepicker';
import "react-datepicker/dist/react-datepicker.css";
import { Spinner, Breadcrumb, BreadcrumbItem, Tabs, Tab, Modal, Button } from "react-bootstrap";
import { CUSTOMER_TYPE, PICKUP_STATUS, PICKUP_STATUS_TEXT , CUSTOMER_TYPE_TEXT} from "../../shared";
import { CUSTOMER_TYPE, PICKUP_STATUS, PICKUP_STATUS_TEXT , CUSTOMER_TYPE_TEXT, CUSTOMER_JOIN_REASON, CUSTOMER_JOIN_REASON_TEXT, CUSTOMER_DISCHARGE_REASON, CUSTOMER_DISCHARGE_REASON_TEXT} from "../../shared";
import { Upload } from "react-bootstrap-icons";
const UpdateCustomer = () => {
@ -19,7 +21,7 @@ const UpdateCustomer = () => {
const [firstname, setFirstname] = useState('');
const [lastname, setLastname] = useState('');
const [nameCN, setNameCN] = useState('');
const [birthDate, setBirthDate] = useState('');
const [birthDate, setBirthDate] = useState(null);
const [email, setEmail] = useState('');
const [mobilePhone, setMobilePhone] = useState('');
const [phone, setPhone] = useState('');
@ -31,6 +33,27 @@ const UpdateCustomer = () => {
const [address3, setAddress3] = useState('');
const [address4, setAddress4] = useState('');
const [address5, setAddress5] = useState('');
const [addressCount, setAddressCount] = useState(1);
const [streetAddress1, setStreetAddress1] = useState('');
const [city1, setCity1] = useState('');
const [state1, setState1] = useState('');
const [zipCode1, setZipCode1] = useState('');
const [streetAddress2, setStreetAddress2] = useState('');
const [city2, setCity2] = useState('');
const [state2, setState2] = useState('');
const [zipCode2, setZipCode2] = useState('');
const [streetAddress3, setStreetAddress3] = useState('');
const [city3, setCity3] = useState('');
const [state3, setState3] = useState('');
const [zipCode3, setZipCode3] = useState('');
const [streetAddress4, setStreetAddress4] = useState('');
const [city4, setCity4] = useState('');
const [state4, setState4] = useState('');
const [zipCode4, setZipCode4] = useState('');
const [streetAddress5, setStreetAddress5] = useState('');
const [city5, setCity5] = useState('');
const [state5, setState5] = useState('');
const [zipCode5, setZipCode5] = useState('');
const [customerType, setCustomerType] = useState('');
const [selectedFile, setSelectedFile] = useState();
const [careProvider, setCareProvider] = useState('');
@ -41,13 +64,20 @@ const UpdateCustomer = () => {
const [pickupStatus, setPickupStatus] = useState('');
const [specialNeeds, setSpecialNeeds] = useState('');
const [emergencyContact, setEmergencyContact] = useState('');
const [admissionDate, setAdmissionDate] = useState('');
const [emergencyContactCount, setEmergencyContactCount] = useState(1);
const [emergencyContactName, setEmergencyContactName] = useState('');
const [emergencyContactPhone, setEmergencyContactPhone] = useState('');
const [emergencyContactRelationship, setEmergencyContactRelationship] = useState('');
const [emergencyContact2Name, setEmergencyContact2Name] = useState('');
const [emergencyContact2Phone, setEmergencyContact2Phone] = useState('');
const [emergencyContact2Relationship, setEmergencyContact2Relationship] = useState('');
const [admissionDate, setAdmissionDate] = useState(null);
const [vehicleNo, setVehicleNo] = useState('');
const [note, setNote] = useState('');
const [pin, setPin] = useState('');
const [seating, setSeating] = useState('');
const [caller, setCaller] = useState('');
const [dischargeDate, setDischargeDate] = useState('');
const [dischargeDate, setDischargeDate] = useState(null);
const [placement, setPlacement] = useState('');
const [nickname, setNickname] = useState('');
const [groups, setGroups] = useState('');
@ -63,6 +93,13 @@ const UpdateCustomer = () => {
const [heightFeet, setHeightFeet] = useState('');
const [heightInch, setHeightInch] = useState('');
const [textMsgEnabled, setTextMsgEnabled] = useState(false);
const [healthCondition, setHealthCondition] = useState('');
const [allergyInfo, setAllergyInfo] = useState('');
const [serviceRequirement, setServiceRequirement] = useState('');
const [paymentDueDate, setPaymentDueDate] = useState(null);
const [paymentStatus, setPaymentStatus] = useState('');
const [joinReason, setJoinReason] = useState('');
const [dischargeReason, setDischargeReason] = useState('');
const params = new URLSearchParams(window.location.search);
const redirectTo = () => {
navigate(`/customers/list`);
@ -92,13 +129,100 @@ const UpdateCustomer = () => {
setPharmacyId(selectedPharmacy?.value);
}
const formatDateForBackend = (date) => {
if (!date) return '';
const d = new Date(date);
const month = String(d.getMonth() + 1).padStart(2, '0');
const day = String(d.getDate()).padStart(2, '0');
const year = d.getFullYear();
return `${month}/${day}/${year}`;
}
// Helper function to combine address fields into a single string
const combineAddress = (streetAddress, city, state, zipCode) => {
const parts = [streetAddress, city, state, zipCode].filter(part => part && part.trim());
return parts.join(', ');
};
// Helper function to split address string into components
const splitAddress = (addressString) => {
if (!addressString) return { streetAddress: '', city: '', state: '', zipCode: '' };
const parts = addressString.split(',').map(part => part.trim());
if (parts.length === 1) {
return { streetAddress: parts[0], city: '', state: '', zipCode: '' };
} else if (parts.length === 2) {
return { streetAddress: parts[0], city: parts[1], state: '', zipCode: '' };
} else if (parts.length === 3) {
return { streetAddress: parts[0], city: parts[1], state: parts[2], zipCode: '' };
} else {
return {
streetAddress: parts[0],
city: parts[1],
state: parts[2],
zipCode: parts.slice(3).join(', ')
};
}
};
// Function to add a new address line
const addAddressLine = () => {
if (addressCount < 5) {
setAddressCount(addressCount + 1);
}
};
// Function to remove an address line
const removeAddressLine = (index) => {
if (addressCount > 1) {
setAddressCount(addressCount - 1);
// Clear the fields for the removed address
const clearAddressFields = (num) => {
switch(num) {
case 1: setStreetAddress1(''); setCity1(''); setState1(''); setZipCode1(''); break;
case 2: setStreetAddress2(''); setCity2(''); setState2(''); setZipCode2(''); break;
case 3: setStreetAddress3(''); setCity3(''); setState3(''); setZipCode3(''); break;
case 4: setStreetAddress4(''); setCity4(''); setState4(''); setZipCode4(''); break;
case 5: setStreetAddress5(''); setCity5(''); setState5(''); setZipCode5(''); break;
}
};
clearAddressFields(index);
}
};
// Helper function to combine emergency contact fields into a single string
const combineEmergencyContact = (name, phone, relationship) => {
const parts = [name, phone, relationship].filter(part => part && part.trim());
return parts.join(' ');
};
// Function to add a new emergency contact
const addEmergencyContact = () => {
if (emergencyContactCount < 2) {
setEmergencyContactCount(emergencyContactCount + 1);
}
};
// Function to remove an emergency contact
const removeEmergencyContact = (index) => {
if (emergencyContactCount > 1) {
setEmergencyContactCount(emergencyContactCount - 1);
// Clear the fields for the removed emergency contact
if (index === 2) {
setEmergencyContact2Name('');
setEmergencyContact2Phone('');
setEmergencyContact2Relationship('');
}
}
};
useEffect(() => {
if (currentCustomer) {
setUsername(currentCustomer.username);
setFirstname(currentCustomer.firstname);
setLastname(currentCustomer.lastname);
setNameCN(currentCustomer.name_cn);
setBirthDate(currentCustomer.birth_date);
setBirthDate(parseDateFromBackend(currentCustomer.birth_date));
setRoles(currentCustomer.roles?.join(','));
setEmail(currentCustomer.email);
setMobilePhone(currentCustomer.mobile_phone);
@ -121,8 +245,34 @@ const UpdateCustomer = () => {
setPickupStatus(currentCustomer.pickup_status);
setSpecialNeeds(currentCustomer.setSpecial_needs);
setEmergencyContact(currentCustomer.emergency_contact);
setAdmissionDate(currentCustomer.admission_date);
setDischargeDate(currentCustomer.discharge_date);
// Populate new emergency contact fields
if (currentCustomer.emergency_contact_name || currentCustomer.emergency_contact_phone || currentCustomer.emergency_contact_relationship) {
setEmergencyContactName(currentCustomer.emergency_contact_name || '');
setEmergencyContactPhone(currentCustomer.emergency_contact_phone || '');
setEmergencyContactRelationship(currentCustomer.emergency_contact_relationship || '');
} else if (currentCustomer.emergency_contact) {
// For legacy data, put the entire emergency contact string in the name field
setEmergencyContactName(currentCustomer.emergency_contact);
setEmergencyContactPhone('');
setEmergencyContactRelationship('');
}
if (currentCustomer.emergency_contact2_name || currentCustomer.emergency_contact2_phone || currentCustomer.emergency_contact2_relationship) {
setEmergencyContact2Name(currentCustomer.emergency_contact2_name || '');
setEmergencyContact2Phone(currentCustomer.emergency_contact2_phone || '');
setEmergencyContact2Relationship(currentCustomer.emergency_contact2_relationship || '');
}
// Determine emergency contact count
let emergencyCount = 1;
if (currentCustomer.emergency_contact2_name || currentCustomer.emergency_contact2_phone || currentCustomer.emergency_contact2_relationship) {
emergencyCount = 2;
}
setEmergencyContactCount(emergencyCount);
setAdmissionDate(parseDateFromBackend(currentCustomer.admission_date));
setDischargeDate(parseDateFromBackend(currentCustomer.discharge_date));
setVehicleNo(currentCustomer.vehicle_no);
setPin(currentCustomer.pin);
setSeating(currentCustomer.seating);
@ -139,6 +289,87 @@ const UpdateCustomer = () => {
setTextMsgEnabled(currentCustomer.text_msg_enabled ? 'true': 'false');
setHeightFeet(currentCustomer.height && currentCustomer.height.length > 0? currentCustomer.height.replaceAll(' ', '')?.split('ft')[0] : '');
setHeightInch(currentCustomer.height && currentCustomer.height.length > 0? currentCustomer.height.replaceAll(' ', '')?.split('ft')[1]?.split('in')[0] : '');
setHealthCondition(currentCustomer.health_condition || '');
setAllergyInfo(currentCustomer.allergy_info || '');
setServiceRequirement(currentCustomer.service_requirement || '');
setPaymentDueDate(parseDateFromBackend(currentCustomer.payment_due_date));
setPaymentStatus(currentCustomer.payment_status || '');
setJoinReason(currentCustomer.join_reason || '');
setDischargeReason(currentCustomer.discharge_reason || '');
// Populate new address fields
if (currentCustomer.street_address_1 || currentCustomer.city1 || currentCustomer.state1 || currentCustomer.zip_code1) {
setStreetAddress1(currentCustomer.street_address_1 || '');
setCity1(currentCustomer.city1 || '');
setState1(currentCustomer.state1 || '');
setZipCode1(currentCustomer.zip_code1 || '');
} else if (currentCustomer.address1) {
const addr1 = splitAddress(currentCustomer.address1);
setStreetAddress1(addr1.streetAddress);
setCity1(addr1.city);
setState1(addr1.state);
setZipCode1(addr1.zipCode);
}
if (currentCustomer.street_address_2 || currentCustomer.city2 || currentCustomer.state2 || currentCustomer.zip_code2) {
setStreetAddress2(currentCustomer.street_address_2 || '');
setCity2(currentCustomer.city2 || '');
setState2(currentCustomer.state2 || '');
setZipCode2(currentCustomer.zip_code2 || '');
} else if (currentCustomer.address2) {
const addr2 = splitAddress(currentCustomer.address2);
setStreetAddress2(addr2.streetAddress);
setCity2(addr2.city);
setState2(addr2.state);
setZipCode2(addr2.zipCode);
}
if (currentCustomer.street_address_3 || currentCustomer.city3 || currentCustomer.state3 || currentCustomer.zip_code3) {
setStreetAddress3(currentCustomer.street_address_3 || '');
setCity3(currentCustomer.city3 || '');
setState3(currentCustomer.state3 || '');
setZipCode3(currentCustomer.zip_code3 || '');
} else if (currentCustomer.address3) {
const addr3 = splitAddress(currentCustomer.address3);
setStreetAddress3(addr3.streetAddress);
setCity3(addr3.city);
setState3(addr3.state);
setZipCode3(addr3.zipCode);
}
if (currentCustomer.street_address_4 || currentCustomer.city4 || currentCustomer.state4 || currentCustomer.zip_code4) {
setStreetAddress4(currentCustomer.street_address_4 || '');
setCity4(currentCustomer.city4 || '');
setState4(currentCustomer.state4 || '');
setZipCode4(currentCustomer.zip_code4 || '');
} else if (currentCustomer.address4) {
const addr4 = splitAddress(currentCustomer.address4);
setStreetAddress4(addr4.streetAddress);
setCity4(addr4.city);
setState4(addr4.state);
setZipCode4(addr4.zipCode);
}
if (currentCustomer.street_address_5 || currentCustomer.city5 || currentCustomer.state5 || currentCustomer.zip_code5) {
setStreetAddress5(currentCustomer.street_address_5 || '');
setCity5(currentCustomer.city5 || '');
setState5(currentCustomer.state5 || '');
setZipCode5(currentCustomer.zip_code5 || '');
} else if (currentCustomer.address5) {
const addr5 = splitAddress(currentCustomer.address5);
setStreetAddress5(addr5.streetAddress);
setCity5(addr5.city);
setState5(addr5.state);
setZipCode5(addr5.zipCode);
}
// Determine address count
let count = 1;
if (currentCustomer.address2 || currentCustomer.street_address_2 || currentCustomer.city2 || currentCustomer.state2 || currentCustomer.zip_code2) count = 2;
if (currentCustomer.address3 || currentCustomer.street_address_3 || currentCustomer.city3 || currentCustomer.state3 || currentCustomer.zip_code3) count = 3;
if (currentCustomer.address4 || currentCustomer.street_address_4 || currentCustomer.city4 || currentCustomer.state4 || currentCustomer.zip_code4) count = 4;
if (currentCustomer.address5 || currentCustomer.street_address_5 || currentCustomer.city5 || currentCustomer.state5 || currentCustomer.zip_code5) count = 5;
setAddressCount(count);
}
}, [currentCustomer]);
@ -160,14 +391,34 @@ const UpdateCustomer = () => {
type: customerType,
language,
status: 'inactive',
address1,
address2,
address3,
address4,
address5,
address1: combineAddress(streetAddress1, city1, state1, zipCode1),
address2: combineAddress(streetAddress2, city2, state2, zipCode2),
address3: combineAddress(streetAddress3, city3, state3, zipCode3),
address4: combineAddress(streetAddress4, city4, state4, zipCode4),
address5: combineAddress(streetAddress5, city5, state5, zipCode5),
street_address_1: streetAddress1,
city1: city1,
state1: state1,
zip_code1: zipCode1,
street_address_2: streetAddress2,
city2: city2,
state2: state2,
zip_code2: zipCode2,
street_address_3: streetAddress3,
city3: city3,
state3: state3,
zip_code3: zipCode3,
street_address_4: streetAddress4,
city4: city4,
state4: state4,
zip_code4: zipCode4,
street_address_5: streetAddress5,
city5: city5,
state5: state5,
zip_code5: zipCode5,
firstname,
lastname,
birth_date: birthDate,
birth_date: formatDateForBackend(birthDate),
care_provider: careProvider,
medicare_number: medicareNumber,
medicaid_number: medicaidNumber,
@ -175,10 +426,17 @@ const UpdateCustomer = () => {
pharmacy_id: pharmacyId,
pickup_status: pickupStatus,
special_needs: specialNeeds,
emergency_contact: emergencyContact,
admission_date: admissionDate,
emergency_contact: combineEmergencyContact(emergencyContactName, emergencyContactPhone, emergencyContactRelationship),
emergency_contact2: combineEmergencyContact(emergencyContact2Name, emergencyContact2Phone, emergencyContact2Relationship),
emergency_contact_name: emergencyContactName,
emergency_contact_phone: emergencyContactPhone,
emergency_contact_relationship: emergencyContactRelationship,
emergency_contact2_name: emergencyContact2Name,
emergency_contact2_phone: emergencyContact2Phone,
emergency_contact2_relationship: emergencyContact2Relationship,
admission_date: formatDateForBackend(admissionDate),
vehicle_no: vehicleNo,
discharge_date: dischargeDate,
discharge_date: formatDateForBackend(dischargeDate),
edit_by: localStorage.getItem('user') && JSON.parse(localStorage.getItem('user'))?.name || '',
note,
pin,
@ -195,7 +453,14 @@ const UpdateCustomer = () => {
weight,
gender,
height: ((heightFeet?.length > 0 && heightInch?.length> 0 && `${heightFeet} ft ${heightInch} in`)) || '',
text_msg_enabled: textMsgEnabled === 'true' || false
text_msg_enabled: textMsgEnabled === 'true' || false,
health_condition: healthCondition,
allergy_info: allergyInfo,
service_requirement: serviceRequirement,
payment_due_date: formatDateForBackend(paymentDueDate),
payment_status: paymentStatus,
join_reason: joinReason,
discharge_reason: dischargeReason
};
// const dataForLegacy = {
// username,
@ -266,14 +531,34 @@ const UpdateCustomer = () => {
type: customerType,
language,
status: 'active',
address1,
address2,
address3,
address4,
address5,
address1: combineAddress(streetAddress1, city1, state1, zipCode1),
address2: combineAddress(streetAddress2, city2, state2, zipCode2),
address3: combineAddress(streetAddress3, city3, state3, zipCode3),
address4: combineAddress(streetAddress4, city4, state4, zipCode4),
address5: combineAddress(streetAddress5, city5, state5, zipCode5),
street_address_1: streetAddress1,
city1: city1,
state1: state1,
zip_code1: zipCode1,
street_address_2: streetAddress2,
city2: city2,
state2: state2,
zip_code2: zipCode2,
street_address_3: streetAddress3,
city3: city3,
state3: state3,
zip_code3: zipCode3,
street_address_4: streetAddress4,
city4: city4,
state4: state4,
zip_code4: zipCode4,
street_address_5: streetAddress5,
city5: city5,
state5: state5,
zip_code5: zipCode5,
firstname,
lastname,
birth_date: birthDate,
birth_date: formatDateForBackend(birthDate),
care_provider: careProvider,
medicare_number: medicareNumber,
medicaid_number: medicaidNumber,
@ -281,10 +566,17 @@ const UpdateCustomer = () => {
pharmacy_id: pharmacyId,
pickup_status: pickupStatus,
special_needs: specialNeeds,
emergency_contact: emergencyContact,
admission_date: admissionDate,
emergency_contact: combineEmergencyContact(emergencyContactName, emergencyContactPhone, emergencyContactRelationship),
emergency_contact2: combineEmergencyContact(emergencyContact2Name, emergencyContact2Phone, emergencyContact2Relationship),
emergency_contact_name: emergencyContactName,
emergency_contact_phone: emergencyContactPhone,
emergency_contact_relationship: emergencyContactRelationship,
emergency_contact2_name: emergencyContact2Name,
emergency_contact2_phone: emergencyContact2Phone,
emergency_contact2_relationship: emergencyContact2Relationship,
admission_date: formatDateForBackend(admissionDate),
vehicle_no: vehicleNo,
discharge_date: dischargeDate,
discharge_date: formatDateForBackend(dischargeDate),
edit_by: localStorage.getItem('user') && JSON.parse(localStorage.getItem('user'))?.name || '',
note,
pin,
@ -301,7 +593,14 @@ const UpdateCustomer = () => {
weight,
gender,
height: `${heightFeet} ft ${heightInch} in`,
text_msg_enabled: textMsgEnabled === 'true' || false
text_msg_enabled: textMsgEnabled === 'true' || false,
health_condition: healthCondition,
allergy_info: allergyInfo,
service_requirement: serviceRequirement,
payment_due_date: formatDateForBackend(paymentDueDate),
payment_status: paymentStatus,
join_reason: joinReason,
discharge_reason: dischargeReason
};
// const dataForLegacy = {
// username,
@ -399,15 +698,33 @@ const UpdateCustomer = () => {
<div className="app-main-content-fields-section">
<div className="me-4">
<div className="field-label">Admission Date</div>
<input type="text" placeholder="e.g.,01/15/2025" value={admissionDate || ''} onChange={e => setAdmissionDate(e.target.value)}/>
<DatePicker
selected={admissionDate}
onChange={(date) => setAdmissionDate(date)}
dateFormat="MM/dd/yyyy"
placeholderText="MM/DD/YYYY"
className="form-control"
/>
</div>
<div className="me-4">
<div className="field-label">Discharge Date</div>
<input type="text" placeholder="e.g.,01/15/2025" value={dischargeDate || ''} onChange={e => setDischargeDate(e.target.value)}/>
<DatePicker
selected={dischargeDate}
onChange={(date) => setDischargeDate(date)}
dateFormat="MM/dd/yyyy"
placeholderText="MM/DD/YYYY"
className="form-control"
/>
</div>
<div className="me-4">
<div className="field-label">Birth Date</div>
<input type="text" placeholder="e.g.,01/15/2025" value={birthDate || ''} onChange={e => setBirthDate(e.target.value)}/>
<DatePicker
selected={birthDate}
onChange={(date) => setBirthDate(date)}
dateFormat="MM/dd/yyyy"
placeholderText="MM/DD/YYYY"
className="form-control"
/>
</div>
</div>
<div className="app-main-content-fields-section">
@ -466,39 +783,195 @@ const UpdateCustomer = () => {
</div>
{/* We will do Address and Emergency later */}
{/* We will do Address and Emergency later */}
<h6 className="text-primary">Home Addresses</h6>
<div className="app-main-content-fields-section">
<div className="me-4">
<div className="field-label">Address 1 <span className="required">*</span></div>
<input type="text" value={address1 || ''} onChange={e => setAddress1(e.target.value)}/>
<h6 className="text-primary">
Home Addresses</h6>
{[1, 2, 3, 4, 5].map((index) => (
index <= addressCount && (
<>
<h6 className="text-secondary mb-0">Address {index} {index === 1 && <span className="required">*</span>}
{index > 1 && (
<button
type="button"
className="btn btn-sm btn-outline-danger ms-2"
onClick={() => removeAddressLine(index)}
>
Remove
</button>
)}
</h6>
<div key={index} className="app-main-content-fields-section">
<div className="me-4">
<div className="field-label">Street Address</div>
<input
type="text"
placeholder="e.g., 123 Main St"
value={index === 1 ? streetAddress1 : index === 2 ? streetAddress2 : index === 3 ? streetAddress3 : index === 4 ? streetAddress4 : streetAddress5}
onChange={e => {
const value = e.target.value;
switch(index) {
case 1: setStreetAddress1(value); break;
case 2: setStreetAddress2(value); break;
case 3: setStreetAddress3(value); break;
case 4: setStreetAddress4(value); break;
case 5: setStreetAddress5(value); break;
}
}}
/>
</div>
<div className="me-4">
<div className="field-label">City</div>
<input
type="text"
placeholder="e.g., Rockville"
value={index === 1 ? city1 : index === 2 ? city2 : index === 3 ? city3 : index === 4 ? city4 : city5}
onChange={e => {
const value = e.target.value;
switch(index) {
case 1: setCity1(value); break;
case 2: setCity2(value); break;
case 3: setCity3(value); break;
case 4: setCity4(value); break;
case 5: setCity5(value); break;
}
}}
/>
</div>
<div className="me-4">
<div className="field-label">State</div>
<input
type="text"
placeholder="e.g., MD"
value={index === 1 ? state1 : index === 2 ? state2 : index === 3 ? state3 : index === 4 ? state4 : state5}
onChange={e => {
const value = e.target.value;
switch(index) {
case 1: setState1(value); break;
case 2: setState2(value); break;
case 3: setState3(value); break;
case 4: setState4(value); break;
case 5: setState5(value); break;
}
}}
/>
</div>
<div className="me-4">
<div className="field-label">Zip Code</div>
<input
type="text"
placeholder="e.g., 20850"
value={index === 1 ? zipCode1 : index === 2 ? zipCode2 : index === 3 ? zipCode3 : index === 4 ? zipCode4 : zipCode5}
onChange={e => {
const value = e.target.value;
switch(index) {
case 1: setZipCode1(value); break;
case 2: setZipCode2(value); break;
case 3: setZipCode3(value); break;
case 4: setZipCode4(value); break;
case 5: setZipCode5(value); break;
}
}}
/>
</div>
</div>
</>
)
))}
{addressCount < 5 && (
<div className="app-main-content-fields-section">
<button
type="button"
className="btn btn-outline-primary"
onClick={addAddressLine}
>
+ Add Another Address
</button>
</div>
</div>
<div className="app-main-content-fields-section">
<div className="me-4">
<div className="field-label">Address 2</div>
<input type="text" value={address2 || ''} onChange={e => setAddress2(e.target.value)}/>
</div>
<div className="me-4">
<div className="field-label">Address 3</div>
<input type="text" value={address3 || ''} onChange={e => setAddress3(e.target.value)}/>
</div>
<div className="me-4">
<div className="field-label">Address 4</div>
<input type="text" value={address4 || ''} onChange={e => setAddress4(e.target.value)}/>
</div>
<div className="me-4">
<div className="field-label">Address 5</div>
<input type="text" value={address5 || ''} onChange={e => setAddress5(e.target.value)}/>
</div>
</div>
)}
<h6 className="text-primary">Emergency Contact Information</h6>
<div className="app-main-content-fields-section">
<div className="me-4">
<div className="field-label">Emergency Contact <span className="required">*</span> </div>
<input type="text" placeholder="e.g.,John 240-463-1698" className="long" value={emergencyContact || ''} onChange={e => setEmergencyContact(e.target.value)}/>
{[1, 2].map((index) => (
index <= emergencyContactCount && (
<>
<h6 className="text-secondary mb-0">
{index === 1 ? 'Primary Emergency Contact' : 'Emergency Contact 2'}
{index === 1 && <span className="required">*</span>}
{index > 1 && (
<button
type="button"
className="btn btn-sm btn-outline-danger ms-2"
onClick={() => removeEmergencyContact(index)}
>
Remove
</button>
)}
</h6>
<div key={index} className="app-main-content-fields-section">
<div className="me-4">
<div className="field-label">Name</div>
<input
type="text"
placeholder="e.g., John Smith"
value={index === 1 ? emergencyContactName : emergencyContact2Name}
onChange={e => {
const value = e.target.value;
if (index === 1) {
setEmergencyContactName(value);
} else {
setEmergencyContact2Name(value);
}
}}
/>
</div>
<div className="me-4">
<div className="field-label">Phone</div>
<input
type="text"
placeholder="e.g., 240-463-1698"
value={index === 1 ? emergencyContactPhone : emergencyContact2Phone}
onChange={e => {
const value = e.target.value;
if (index === 1) {
setEmergencyContactPhone(value);
} else {
setEmergencyContact2Phone(value);
}
}}
/>
</div>
<div className="me-4">
<div className="field-label">Relationship</div>
<input
type="text"
placeholder="e.g., Spouse, Son, Daughter"
value={index === 1 ? emergencyContactRelationship : emergencyContact2Relationship}
onChange={e => {
const value = e.target.value;
if (index === 1) {
setEmergencyContactRelationship(value);
} else {
setEmergencyContact2Relationship(value);
}
}}
/>
</div>
</div>
</>
)
))}
{emergencyContactCount < 2 && (
<div className="app-main-content-fields-section">
<button
type="button"
className="btn btn-outline-primary"
onClick={addEmergencyContact}
>
+ Add Another Emergency Contact
</button>
</div>
</div>
)}
<h6 className="text-primary">Service Information</h6>
<div className="app-main-content-fields-section">
<div className="me-4">
@ -539,6 +1012,70 @@ const UpdateCustomer = () => {
<div className="field-label">Special Needs</div>
<input type="text" placeholder="e.g.,Special Needs" value={specialNeeds || ''} onChange={e => setSpecialNeeds(e.target.value)}/>
</div>
<div className="me-4">
<div className="field-label">Health Condition</div>
<select value={healthCondition} onChange={e => setHealthCondition(e.target.value)}>
<option value=""></option>
<option value="diabetes">Diabetes</option>
<option value="1-1">1-1</option>
<option value="rounding list">Rounding List</option>
<option value="MOLST/POA/Advanced Directive">MOLST/POA/Advanced Directive</option>
</select>
</div>
<div className="me-4">
<div className="field-label">Allergy Information</div>
<input type="text" placeholder="e.g.,Peanuts, Shellfish" value={allergyInfo || ''} onChange={e => setAllergyInfo(e.target.value)}/>
</div>
</div>
<div className="app-main-content-fields-section">
<div className="me-4">
<div className="field-label">Service Requirements</div>
<select value={serviceRequirement} onChange={e => setServiceRequirement(e.target.value)}>
<option value=""></option>
<option value="wheelchair">Wheelchair</option>
<option value="special care">Special Care</option>
</select>
</div>
<div className="me-4">
<div className="field-label">Payment Due Date</div>
<DatePicker
selected={paymentDueDate}
onChange={(date) => setPaymentDueDate(date)}
dateFormat="MM/dd/yyyy"
placeholderText="MM/DD/YYYY"
className="form-control"
/>
</div>
<div className="me-4">
<div className="field-label">Payment Status</div>
<select value={paymentStatus} onChange={e => setPaymentStatus(e.target.value)}>
<option value=""></option>
<option value="paid">Paid</option>
<option value="overdue">Overdue</option>
</select>
</div>
<div className="me-4">
<div className="field-label">Join Reason</div>
<select value={joinReason} onChange={e => setJoinReason(e.target.value)}>
<option value=""></option>
{Object.keys(CUSTOMER_JOIN_REASON).map(key => (
<option key={key} value={CUSTOMER_JOIN_REASON[key]}>
{CUSTOMER_JOIN_REASON_TEXT[CUSTOMER_JOIN_REASON[key]]}
</option>
))}
</select>
</div>
<div className="me-4">
<div className="field-label">Discharge Reason</div>
<select value={dischargeReason} onChange={e => setDischargeReason(e.target.value)}>
<option value=""></option>
{Object.keys(CUSTOMER_DISCHARGE_REASON).map(key => (
<option key={key} value={CUSTOMER_DISCHARGE_REASON[key]}>
{CUSTOMER_DISCHARGE_REASON_TEXT[CUSTOMER_DISCHARGE_REASON[key]]}
</option>
))}
</select>
</div>
</div>
<h6 className="text-primary">Additional Information</h6>
<div className="app-main-content-fields-section base-line">
@ -637,191 +1174,6 @@ const UpdateCustomer = () => {
</Tabs>
</div>
</div>
{/* <div className="list row mb-4">
<div className="col-md-12 text-primary">
<h5>Update Customer <button className="btn btn-link btn-sm" onClick={() => {redirectTo()}}>Back</button></h5>
</div>
</div>
<div className="list row mb-4">
<div className="col-md-4 mb-4">
<div>First Name:(*)</div> <input type="text" value={firstname || ''} onChange={e => setFirstname(e.target.value)}/>
</div>
<div className="col-md-4 mb-4">
<div>Last Name:(*)</div> <input type="text" value={lastname || ''} onChange={e => setLastname(e.target.value)}/>
</div>
<div className="col-md-4 mb-4">
<div>Preferred Name (中文姓名):</div> <input type="text" value={nameCN || ''} onChange={e => setNameCN(e.target.value)}/>
</div>
<div className="col-md-4 mb-4">
<div>Email:(*)</div> <input type="email" value={email || ''} onChange={e => setEmail(e.target.value)}/>
</div>
<div className="col-md-4 mb-4">
<div>Care Provider:</div> <input type="text" value={careProvider || ''} onChange={e => setCareProvider(e.target.value)}/>
</div>
<div className="col-md-4 mb-4">
<div>Emergency Contact:</div> <input type="text" value={emergencyContact || ''} onChange={e => setEmergencyContact(e.target.value)}/>
</div>
<div className="col-md-4 mb-4">
<div>Medicare Number:</div> <input type="text" value={medicareNumber || ''} onChange={e => setMedicareNumber(e.target.value)}/>
</div>
<div className="col-md-4 mb-4">
<div>Medicaid Number:</div> <input type="text" value={medicaidNumber || ''} onChange={e => setMedicaidNumber(e.target.value)}/>
</div>
<div className="col-md-4 mb-4">
<div>Pharmacy:</div>
<Select value={pharmacy || ''} onChange={selectedData => onPharmacyChange(selectedData)} options={[{value: '', label: ''}, ...resources.map(resource => ({
value: resource?.id || '',
label: resource?.name || '',
}))]}></Select>
</div>
<div className="col-md-4 mb-4">
<div>Pharmacy ID:</div> <input type="text" value={pharmacyId || ''} onChange={e => setPharmacyId(e.target.value)}/>
</div>
<div className="col-md-4 mb-4">
<div>Address 1:</div> <input type="text" value={address1 || ''} onChange={e => setAddress1(e.target.value)}/>
</div>
<div className="col-md-4 mb-4">
<div>Address 2:</div> <input type="text" value={address2 || ''} onChange={e => setAddress2(e.target.value)}/>
</div>
<div className="col-md-4 mb-4">
<div>Address 3:</div> <input type="text" value={address3 || ''} onChange={e => setAddress3(e.target.value)}/>
</div>
<div className="col-md-4 mb-4">
<div>Address 4:</div> <input type="text" value={address4 || ''} onChange={e => setAddress4(e.target.value)}/>
</div>
<div className="col-md-4 mb-4">
<div>Address 5:</div> <input type="text" value={address5 || ''} onChange={e => setAddress5(e.target.value)}/>
</div>
<div className="col-md-4 mb-4">
<div>Apartment:</div> <input type="text" value={apartment || ''} onChange={e => setApartment(e.target.value)}/>
</div>
<div className="col-md-4 mb-4">
<div>Table Id:</div> <input type="text" value={tableId || ''} onChange={e => setTableId(e.target.value)}/>
</div>
<div className="col-md-4 mb-4">
<div>Customer Type:</div> <select value={customerType} onChange={e => setCustomerType(e.target.value)}>
<option value=""></option>
{
Object.keys(CUSTOMER_TYPE).map((key, index) => <option key={index} value={CUSTOMER_TYPE[key]}> {CUSTOMER_TYPE_TEXT[CUSTOMER_TYPE[key]]}</option>)
}
</select>
</div>
<div className="col-md-4 mb-4">
<div>Pickup Status:</div> <select value={pickupStatus} onChange={e => setPickupStatus(e.target.value)}>
<option value=""></option>
{
Object.keys(PICKUP_STATUS).map((key, index) => <option key={index} value={PICKUP_STATUS[key]}> {PICKUP_STATUS_TEXT[PICKUP_STATUS[key]]}</option>)
}
</select>
</div>
<div className="col-md-4 mb-4">
<div>Upload Avatar (Image size should be less than 500 KB):</div>
<input
type="file"
onChange={(e) => setSelectedFile(e.target.files[0])}
className="form-control-file border"
/>
</div>
<div className="col-md-4 mb-4">
<div>Birth Date (Type in as: MM/DD/YYYY):</div> <input type="text" value={birthDate || ''} onChange={e => setBirthDate(e.target.value)}/>
</div>
<div className="col-md-4 mb-4">
<div>Phone :</div> <input type="text" value={phone || ''} onChange={e => setPhone(e.target.value)}/>
</div>
<div className="col-md-4 mb-4">
<div>Mobile Phone:</div> <input type="text" value={mobilePhone || ''} onChange={e => setMobilePhone(e.target.value)}/>
</div>
<div className="col-md-4 mb-4">
<div>Home Phone:</div> <input type="text" value={homePhone || ''} onChange={e => setHomePhone(e.target.value)}/>
</div>
<div className="col-md-4 mb-4">
<div>Special Needs:</div> <input type="text" value={specialNeeds || ''} onChange={e => setSpecialNeeds(e.target.value)}/>
</div>
<div className="col-md-4 mb-4">
<div>Language(Please use ',' between each language):</div> <input type="text" value={language || ''} onChange={e => setLanguage(e.target.value)}/>
</div>
<div className="col-md-4 mb-4">
<div>Username (not required):</div> <input type="text" value={username || ''} onChange={e => setUsername(e.target.value)}/>
</div>
<div className="col-md-4 mb-4">
<div>Password (not required):</div> <input type="text" value={password || ''} onChange={e => setPassword(e.target.value)}/>
</div>
<div className="col-md-4 mb-4">
<div>Nick Name:</div> <input type="text" value={nickname || ''} onChange={e => setNickname(e.target.value)}/>
</div>
<div className="col-md-4 mb-4">
<div>Note:</div> <input type="text" value={note || ''} onChange={e => setNote(e.target.value)}/>
</div>
<div className="col-md-4 mb-4">
<div>Admission Date(Type in as 'MM/DD/YYYY'):</div> <input type="text" value={admissionDate || ''} onChange={e => setAdmissionDate(e.target.value)}/>
</div>
<div className="col-md-4 mb-4">
<div>Discharge Date(Type in as 'MM/DD/YYYY'):</div> <input type="text" value={dischargeDate || ''} onChange={e => setDischargeDate(e.target.value)}/>
</div>
<div className="col-md-4 mb-4">
<div>Pin:</div> <input type="text" value={pin || ''} onChange={e => setPin(e.target.value)}/>
</div>
<div className="col-md-4 mb-4">
<div>Seating:</div> <input type="text" value={seating || ''} onChange={e => setSeating(e.target.value)}/>
</div>
<div className="col-md-4 mb-4">
<div>Vehicle No:</div> <input type="text" value={vehicleNo || ''} onChange={e => setVehicleNo(e.target.value)}/>
</div>
<div className="col-md-4 mb-4">
<div>Caller:</div> <input type="text" value={caller || ''} onChange={e => setCaller(e.target.value)}/>
</div>
<div className="col-md-4 mb-4">
<div>Placement:</div> <input type="text" value={placement || ''} onChange={e => setPlacement(e.target.value)}/>
</div>
<div className="col-md-4 mb-4">
<div>Groups(Please use ',' between each group):</div> <input type="text" value={groups || ''} onChange={e => setGroups(e.target.value)}/>
</div>
<div className="col-md-4 mb-4">
<div>Tags(Please use ',' between each tags):</div> <input type="text" value={tags || ''} onChange={e => setTags(e.target.value)}/>
</div>
<div className="col-md-4 mb-4">
<div>Roles(Please use ',' between each roles):</div> <input type="text" value={roles || ''} onChange={e => setRoles(e.target.value)}/>
</div>
<div className="col-md-4 mb-4">
<div>Private Note:</div> <input type="text" value={privateNote || ''} onChange={e => setPrivateNote(e.target.value)}/>
</div>
<div className="col-md-4 mb-4">
<div>Disability:</div> <select value={disability} onChange={e => setDisability(e.target.value)}>
<option value="true">Yes</option>
<option value="false">No</option>
</select>
</div>
<div className="col-md-4 mb-4">
<div>Height:</div>
<input type="text" value={heightFeet || ''} onChange={e => setHeightFeet(e.target.value)}/> Ft
<input type="text" value={heightInch || ''} onChange={e => setHeightInch(e.target.value)}/> In
</div>
<div className="col-md-4 mb-4">
<div>Weight:</div> <input type="text" value={weight || ''} onChange={e => setWeight(e.target.value)}/> lb
</div>
<div className="col-md-4 mb-4">
<div>Gender:</div> <select value={gender} onChange={e => setGender(e.target.value)}>
<option value=""></option>
<option value="male">Male</option>
<option value="female">Female</option>
</select>
</div>
<div className="col-md-4 mb-4">
<div>Enable Text Message:</div> <select value={textMsgEnabled} onChange={e => setTextMsgEnabled(e.target.value)}>
<option value="true">Yes</option>
<option value="false">No</option>
</select>
</div>
</div> */}
{/* <div className="list row mb-5">
<div className="col-md-6 col-sm-6 col-xs-12">
<button className="btn btn-primary btn-sm me-2 mb-2" onClick={() => saveCustomer()}> Save </button>
<button className="btn btn-danger btn-sm me-2 mb-2" onClick={() => triggerShowDeleteModal()}> Delete </button>
<button className="btn btn-default btn-sm" onClick={() => redirectTo()}> Cancel </button>
</div>
</div> */}
<Modal show={showDeleteModal} onHide={() => closeDeleteModal()}>
<Modal.Header closeButton>
<Modal.Title>Delete Customer</Modal.Title>

View File

@ -3,6 +3,7 @@ import { PencilSquare } from "react-bootstrap-icons";
import { useNavigate, useParams } from "react-router-dom";
import { AuthService, CustomerService } from "../../services";
import { Spinner, Breadcrumb, BreadcrumbItem, Tabs, Tab } from "react-bootstrap";
import { CUSTOMER_JOIN_REASON_TEXT, CUSTOMER_DISCHARGE_REASON_TEXT } from "../../shared";
const ViewCustomer = () => {
const navigate = useNavigate();
@ -139,6 +140,126 @@ const ViewCustomer = () => {
</div>
</div>
{/* We will do Address and Emergency later */}
<h6 className="text-primary">Home Addresses</h6>
{[1, 2, 3, 4, 5].map((index) => {
const streetAddress = index === 1 ? currentCustomer?.street_address_1 :
index === 2 ? currentCustomer?.street_address_2 :
index === 3 ? currentCustomer?.street_address_3 :
index === 4 ? currentCustomer?.street_address_4 :
currentCustomer?.street_address_5;
const city = index === 1 ? currentCustomer?.city1 :
index === 2 ? currentCustomer?.city2 :
index === 3 ? currentCustomer?.city3 :
index === 4 ? currentCustomer?.city4 :
currentCustomer?.city5;
const state = index === 1 ? currentCustomer?.state1 :
index === 2 ? currentCustomer?.state2 :
index === 3 ? currentCustomer?.state3 :
index === 4 ? currentCustomer?.state4 :
currentCustomer?.state5;
const zipCode = index === 1 ? currentCustomer?.zip_code1 :
index === 2 ? currentCustomer?.zip_code2 :
index === 3 ? currentCustomer?.zip_code3 :
index === 4 ? currentCustomer?.zip_code4 :
currentCustomer?.zip_code5;
const address = index === 1 ? currentCustomer?.address1 :
index === 2 ? currentCustomer?.address2 :
index === 3 ? currentCustomer?.address3 :
index === 4 ? currentCustomer?.address4 :
currentCustomer?.address5;
// Show address if any of the new fields or old address field has data
if (streetAddress || city || state || zipCode || address) {
return (
<>
<h6 className="text-secondary mb-3">Address {index}</h6>
<div key={index} className="app-main-content-fields-section">
{streetAddress && (
<div className="field-body">
<div className="field-label">Street Address</div>
<div className="field-value">{streetAddress}</div>
</div>
)}
{city && (
<div className="field-body">
<div className="field-label">City</div>
<div className="field-value">{city}</div>
</div>
)}
{state && (
<div className="field-body">
<div className="field-label">State</div>
<div className="field-value">{state}</div>
</div>
)}
{zipCode && (
<div className="field-body">
<div className="field-label">Zip Code</div>
<div className="field-value">{zipCode}</div>
</div>
)}
{address && !streetAddress && !city && !state && !zipCode && (
<div className="field-body">
<div className="field-label">Address</div>
<div className="field-value">{address}</div>
</div>
)}
</div>
</>
);
}
return null;
})}
<h6 className="text-primary">Emergency Contact Information</h6>
{[1, 2].map((index) => {
const name = index === 1 ? currentCustomer?.emergency_contact_name : currentCustomer?.emergency_contact2_name;
const phone = index === 1 ? currentCustomer?.emergency_contact_phone : currentCustomer?.emergency_contact2_phone;
const relationship = index === 1 ? currentCustomer?.emergency_contact_relationship : currentCustomer?.emergency_contact2_relationship;
const emergencyContact = index === 1 ? currentCustomer?.emergency_contact : currentCustomer?.emergency_contact2;
// Show emergency contact if any of the new fields or old emergency contact field has data
if (name || phone || relationship || emergencyContact) {
return (
<>
<h6 className="text-secondary mb-3">
{index === 1 ? 'Primary Emergency Contact' : 'Emergency Contact 2'}
</h6>
<div key={index} className="app-main-content-fields-section">
{name && (
<div className="field-body">
<div className="field-label">Name</div>
<div className="field-value">{name}</div>
</div>
)}
{phone && (
<div className="field-body">
<div className="field-label">Phone</div>
<div className="field-value">{phone}</div>
</div>
)}
{relationship && (
<div className="field-body">
<div className="field-label">Relationship</div>
<div className="field-value">{relationship}</div>
</div>
)}
{emergencyContact && !name && !phone && !relationship && (
<div className="field-body">
<div className="field-label">Emergency Contact</div>
<div className="field-value">{emergencyContact}</div>
</div>
)}
</div>
</>
);
}
return null;
})}
<h6 className="text-primary">Service Information</h6>
<div className="app-main-content-fields-section">
<div className="field-body">
@ -168,6 +289,36 @@ const ViewCustomer = () => {
<div className="field-label">Special Needs</div>
<div className="field-value">{currentCustomer?.special_needs}</div>
</div>
<div className="field-body">
<div className="field-label">Health Condition</div>
<div className="field-value">{currentCustomer?.health_condition}</div>
</div>
<div className="field-body">
<div className="field-label">Allergy Information</div>
<div className="field-value">{currentCustomer?.allergy_info}</div>
</div>
</div>
<div className="app-main-content-fields-section">
<div className="field-body">
<div className="field-label">Service Requirements</div>
<div className="field-value">{currentCustomer?.service_requirement}</div>
</div>
<div className="field-body">
<div className="field-label">Payment Due Date</div>
<div className="field-value">{currentCustomer?.payment_due_date}</div>
</div>
<div className="field-body">
<div className="field-label">Payment Status</div>
<div className="field-value">{currentCustomer?.payment_status}</div>
</div>
<div className="field-body">
<div className="field-label">Join Reason</div>
<div className="field-value">{currentCustomer?.join_reason ? CUSTOMER_JOIN_REASON_TEXT[currentCustomer.join_reason] : ''}</div>
</div>
<div className="field-body">
<div className="field-label">Discharge Reason</div>
<div className="field-value">{currentCustomer?.discharge_reason ? CUSTOMER_DISCHARGE_REASON_TEXT[currentCustomer.discharge_reason] : ''}</div>
</div>
</div>
<h6 className="text-primary">Additional Information</h6>
<div className="app-main-content-fields-section base-line">

View File

@ -0,0 +1,53 @@
/* Dashboard component styles */
/* Top cards section */
.dashboard-top-cards {
margin-top: 8px;
}
.dashboard-card {
border-radius: 8px;
}
.dashboard-card-title {
font-size: 1rem;
font-weight: 500;
}
/* Right sidebar styles */
.dashboard-right-sidebar {
margin-top: 8px;
}
.dashboard-column-container {
border-radius: 8px;
}
.dashboard-event-selector {
width: auto;
min-width: 150px;
}
.dashboard-event-date {
font-size: 0.9rem;
}
.dashboard-event-item {
padding: 8px;
border-radius: 4px;
}
.dashboard-event-title {
font-size: 0.85rem;
font-weight: 500;
}
.dashboard-event-time {
font-size: 0.75rem;
color: #666;
}
.dashboard-event-description {
font-size: 0.75rem;
color: #666;
}

View File

@ -0,0 +1,317 @@
import React, { useState, useEffect } from 'react';
import { Breadcrumb, BreadcrumbItem, Card, Row, Col, Dropdown } from 'react-bootstrap';
import { AuthService, EventsService, CustomerService, TransRoutesService, ResourceService } from '../../services';
import DashboardCustomersList from './DashboardCustomersList';
import { CUSTOMER_TYPE, PERSONAL_ROUTE_STATUS } from '../../shared';
import moment from 'moment';
import './Dashboard.css';
const Dashboard = () => {
const [todayAttendance, setTodayAttendance] = useState(0);
const [todayMedicalAppointments, setTodayMedicalAppointments] = useState(0);
const [membersCount, setMembersCount] = useState(0);
const [visitorsCount, setVisitorsCount] = useState(0);
const [medicalPercentage, setMedicalPercentage] = useState(0);
const [medicalTrend, setMedicalTrend] = useState(''); // 'increase' or 'decrease'
const [events, setEvents] = useState([]);
const [allEvents, setAllEvents] = useState([]);
const [groupedEvents, setGroupedEvents] = useState(new Map());
const [selectedEventType, setSelectedEventType] = useState('medical');
const [customers, setCustomers] = useState([]);
const [resources, setResources] = useState([]);
const eventTypes = [
{ value: 'medical', label: 'Medical Appointments' },
{ value: 'activity', label: 'Activities' },
{ value: 'incident', label: 'Important Notes And Incidents' },
{ value: 'meal_plan', label: 'Meal Plan' },
{ value: 'reminder', label: 'Important Dates' }
];
// Fetch today's events for the calendar list
const fetchTodayEvents = async () => {
try {
const today = new Date();
const fromDate = new Date(today.getFullYear(), today.getMonth(), 1);
const toDate = new Date(today.getFullYear(), today.getMonth() + 1, 0);
const eventsData = await EventsService.getAllEvents({
from: EventsService.formatDate(fromDate),
to: EventsService.formatDate(toDate)
});
setAllEvents(eventsData.data);
let processedEvents = [];
// Filter and map events based on selected type
if (selectedEventType === 'medical') {
if (customers?.length > 0 && resources.length > 0) {
const originalEvents = [...eventsData.data];
processedEvents = originalEvents
?.filter(item => item.type === 'medical')
?.map((item) => {
const customerField = item?.data?.customer ? (customers?.find(c => c.id === item?.data?.customer)?.name || item?.data?.client_name || '') : (item?.data?.client_name || '');
const doctorField = item?.data?.resource ? ((resources?.find(r => r.id === item?.data?.resource))?.name || item?.data?.resource_name || '') : (item?.data?.resource_name || '');
item.event_id = item.id;
item.customer = customerField;
item.doctor = doctorField;
item.phone = item?.data?.resource ? ((resources?.find(r => r.id === item?.data?.resource))?.phone || item?.data?.resource_phone || '') : (item?.data?.resource_phone || '');
item.contact = item?.data?.resource? ((resources?.find(r => r.id === item?.data?.resource))?.contact || item?.data?.resource_contact || '') : (item?.data?.resource_contact || '');
item.address = item?.data?.resource ? ((resources?.find(r => r.id === item?.data?.resource))?.address || item?.data?.resource_address || '') : (item?.data?.resource_address || '');
item.translation = item?.data?.interpreter || '';
item.newPatient = item?.data?.new_patient || '';
item.needId = item?.data?.need_id || '';
item.disability = item?.data?.disability || '';
item.startTime = item?.start_time? `${moment(new Date(item?.start_time)).format('YYYY-MM-DD HH:mm')}` : '';
item.endTime = item?.start_time? `${moment(new Date(item?.end_time)).format('YYYY-MM-DD HH:mm')}` : '';
item.fasting = item?.data?.fasting || '';
item.transportation = item?.link_event_name || '';
item.title = `${customerField}, provider: ${doctorField}`;
item.start = item?.start_time? `${moment(new Date(item?.start_time)).format('YYYY-MM-DD HH:mm')}` : `${moment().format('YYYY-MM-DD HH:mm')}`;
item.end = item?.stop_time? `${moment(new Date(item?.stop_time)).format('YYYY-MM-DD HH:mm')}` : (item?.start_time? `${moment(item?.start_time).format('YYYY-MM-DD HH:mm')}` : `${moment().format('YYYY-MM-DD HH:mm')}`);
item.color = item?.color;
item._options = { additionalClasses: [`event-${item?.color || 'primary'}`]};
return item;
})
?.filter(item => item.status === 'active');
}
} else {
const originalEvents = [...eventsData.data];
processedEvents = originalEvents
?.filter(item => item.type === selectedEventType)
?.map(item => ({
...item,
title: item?.title,
start: item?.start_time? `${moment(new Date(item?.start_time)).format('YYYY-MM-DD HH:mm')}` : `${moment().format('YYYY-MM-DD HH:mm')}`,
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')}`),
_options: { additionalClasses: [`event-${item?.color || 'primary'}`]}
}))
?.filter(item => item.status === 'active');
}
setEvents(processedEvents);
// Group events by date
const eventsDateMap = new Map();
processedEvents?.forEach(eventItem => {
const dateString = moment(eventItem.start_time).format('MMM Do, YYYY');
if (eventsDateMap.has(dateString)) {
eventsDateMap.set(dateString, [...eventsDateMap.get(dateString), eventItem]);
} else {
eventsDateMap.set(dateString, [eventItem]);
}
});
setGroupedEvents(eventsDateMap);
} catch (error) {
console.error('Error fetching events:', error);
}
};
// Fetch today's attendance data
const fetchTodayAttendance = async () => {
try {
const today = EventsService.formatDate(new Date());
const routesData = await TransRoutesService.getAll(today);
const inboundRoutes = routesData.data.filter(route => route.type === 'inbound');
let totalAttendance = 0;
let members = 0;
let visitors = 0;
// Get all customers from inbound routes who are not absent
const allCustomers = TransRoutesService.getAllCustomersFromRoutes(inboundRoutes, []);
allCustomers.forEach(customer => {
// Check if customer is not Unexpected Absent or Scheduled Absent
if (customer.customer_route_status !== PERSONAL_ROUTE_STATUS.UNEXPECTED_ABSENT &&
customer.customer_route_status !== PERSONAL_ROUTE_STATUS.SCHEDULED_ABSENT) {
totalAttendance++;
// Count by customer type
if (customer.customer_type === CUSTOMER_TYPE.MEMBER || customer.customer_type === CUSTOMER_TYPE.SELF_PAY) {
members++;
} else if (customer.customer_type === CUSTOMER_TYPE.VISITOR) {
visitors++;
}
}
});
setTodayAttendance(totalAttendance);
setMembersCount(members);
setVisitorsCount(visitors);
} catch (error) {
console.error('Error fetching attendance:', error);
}
};
// Fetch today's and yesterday's medical appointments
const fetchMedicalAppointments = async () => {
try {
const today = EventsService.formatDate(new Date());
const yesterday = EventsService.formatDate(new Date(Date.now() - 24 * 60 * 60 * 1000));
// Get today's medical events
const todayEventsData = await EventsService.getAllEvents({ date: today });
const todayMedicalEvents = todayEventsData.data.filter(event => event.type === 'medical');
// Get yesterday's medical events
const yesterdayEventsData = await EventsService.getAllEvents({ date: yesterday });
const yesterdayMedicalEvents = yesterdayEventsData.data.filter(event => event.type === 'medical');
setTodayMedicalAppointments(todayMedicalEvents.length);
// Calculate percentage change
if (yesterdayMedicalEvents.length > 0) {
const percentageChange = ((todayMedicalEvents.length - yesterdayMedicalEvents.length) / yesterdayMedicalEvents.length) * 100;
setMedicalPercentage(Math.abs(percentageChange));
setMedicalTrend(percentageChange >= 0 ? 'increase' : 'decrease');
} else if (todayMedicalEvents.length > 0) {
setMedicalPercentage(100);
setMedicalTrend('increase');
} else {
setMedicalPercentage(0);
setMedicalTrend('');
}
} catch (error) {
console.error('Error fetching medical appointments:', error);
}
};
useEffect(() => {
fetchTodayAttendance();
fetchMedicalAppointments();
// Fetch customers and resources for event mapping
CustomerService.getAllCustomers().then((data) => {
setCustomers(data.data);
});
ResourceService.getAll().then((data) => {
setResources(data.data);
});
}, []);
// Separate useEffect for events that depends on selectedEventType, customers, and resources
useEffect(() => {
if (customers?.length > 0 && resources?.length > 0) {
fetchTodayEvents();
}
}, [selectedEventType, customers, resources]);
return (
<>
<div className="list row mb-4">
<Breadcrumb>
<Breadcrumb.Item>Dashboard</Breadcrumb.Item>
<Breadcrumb.Item active>
Dashboard
</Breadcrumb.Item>
</Breadcrumb>
<div className="col-md-12 text-primary">
<h4>Dashboard</h4>
</div>
</div>
<div className="app-main-content-list-container">
<div className="row">
{/* Main Section - 3/4 width */}
<div className="col-md-9">
{/* Top Cards */}
<div className="row mb-4 dashboard-top-cards">
<div className="col-md-6">
<Card className="h-100 dashboard-card">
<Card.Body>
<Card.Title className="dashboard-card-title">Today's Attendance</Card.Title>
<Card.Text className="h2 text-primary">
{todayAttendance}
</Card.Text>
<Card.Text className="text-muted">
{membersCount} Members {visitorsCount} Visitors
</Card.Text>
</Card.Body>
</Card>
</div>
<div className="col-md-6">
<Card className="h-100 dashboard-card">
<Card.Body>
<Card.Title className="dashboard-card-title">Today's Medical Appointments</Card.Title>
<Card.Text className="h2 text-primary">
{todayMedicalAppointments}
</Card.Text>
<Card.Text className="text-muted">
{medicalTrend && (
<span className={medicalTrend === 'increase' ? 'text-success' : 'text-danger'}>
{medicalTrend === 'increase' ? '↗' : '↘'} {medicalPercentage.toFixed(1)}% {medicalTrend === 'increase' ? 'increase' : 'decrease'} from yesterday
</span>
)}
{!medicalTrend && 'No change from yesterday'}
</Card.Text>
</Card.Body>
</Card>
</div>
</div>
{/* Customer List Section */}
<div className="row">
<div className="col-12">
<DashboardCustomersList />
</div>
</div>
</div>
{/* Right Side Bar - 1/4 width */}
<div className="col-md-3 dashboard-right-sidebar">
<div className="column-container dashboard-column-container">
<div className="column-card">
<div className="d-flex justify-content-between align-items-center mb-3">
<h6 className="text-primary mb-0">List</h6>
<select
value={selectedEventType}
onChange={(e) => setSelectedEventType(e.target.value)}
className="form-select form-select-sm dashboard-event-selector"
>
{eventTypes.map((eventType) => (
<option key={eventType.value} value={eventType.value}>
{eventType.label}
</option>
))}
</select>
</div>
<div>
{Array.from(groupedEvents?.keys())?.map((key) => {
return (
<div key={key}>
<h6 className="text-primary me-2 dashboard-event-date">{key}</h6>
{groupedEvents.get(key).map((eventItem, index) => (
<div key={index} className={`event-${eventItem.color || 'primary'} mb-3 event-list-item-container dashboard-event-item`}>
<div className="event-item-flex">
<div className="sx__month-agenda-event__title dashboard-event-title">
{selectedEventType === 'medical' ? eventItem.customer || eventItem.title : eventItem.title}
</div>
<div className="sx__event-modal__time dashboard-event-time">
{`${moment(eventItem?.start_time).format('hh:mm A')} ${eventItem?.end_time ? `- ${moment(eventItem?.end_time).format('hh:mm A')}` : ''}`}
</div>
</div>
<div className="sx__event-modal__time with-padding dashboard-event-description">
{selectedEventType === 'medical' ? `Provider: ${eventItem?.doctor || 'N/A'}` : eventItem?.description || 'No description'}
</div>
</div>
))}
</div>
);
})}
{groupedEvents.size === 0 && (
<div className="text-muted text-center py-3">
No {selectedEventType} events for today
</div>
)}
</div>
</div>
</div>
</div>
</div>
</div>
</>
);
};
export default Dashboard;

View File

@ -0,0 +1,428 @@
import React, {useState, useEffect} from "react";
import { useDispatch, useSelector } from "react-redux";
import { useNavigate, useParams } from "react-router-dom";
import { customerSlice } from "./../../store";
import { AuthService, CustomerService, EventsService, LabelService } from "../../services";
import { CUSTOMER_TYPE, ManageTable } from "../../shared";
import { Spinner, Breadcrumb, BreadcrumbItem, Tabs, Tab, Dropdown } from "react-bootstrap";
import { Columns, Download, Filter, PencilSquare, PersonSquare, Plus } from "react-bootstrap-icons";
const DashboardCustomersList = () => {
const navigate = useNavigate();
const dispatch = useDispatch();
const site = EventsService.site;
const [customers, setCustomers] = useState([]);
const [keyword, setKeyword] = useState('');
const [showInactive, setShowInactive] = useState(false);
const [transferMap, setTransferMap] = useState({});
const [showSpinner, setShowSpinner] = useState(false);
const [sorting, setSorting] = useState({key: '', order: ''});
const [selectedItems, setSelectedItems] = useState([]);
const [filteredCustomers, setFilteredCustomers] = useState(customers);
const [showFilterDropdown, setShowFilterDropdown] = useState(false);
const [healthConditionFilter, setHealthConditionFilter] = useState('');
const [paymentStatusFilter, setPaymentStatusFilter] = useState('');
const [serviceRequirementFilter, setServiceRequirementFilter] = useState('');
const [tagsFilter, setTagsFilter] = useState([]);
const [availableLabels, setAvailableLabels] = useState([]);
const [columns, setColumns] = useState([
{
key: 'name',
label:'Name',
show: true
},
{
key: 'chinese_name',
label: 'Preferred Name',
show: true
},
{
key: 'email',
label: 'Email',
show: true
},
{
key: 'type',
label: 'Type',
show: true
},
{
key: 'pickup_status',
label: 'Pickup Status',
show: true
},
{
key: 'birth_date',
label: 'Date of Birth',
show: true
},
{
key: 'gender',
label: 'Gender',
show: true
},
{
key: 'language',
label: 'Language',
show: true
},
{
key: 'medicare_number',
label: 'Medicare Number',
show: true
},
{
key: 'medicaid_number',
label: 'Medicaid Number',
show: true
},
{
key: 'address',
label: 'Address',
show: true
},
{
key: 'phone',
label: 'Phone',
show: true
},
{
key: 'emergency_contact',
label: 'Fasting',
show: true
},
{
key: 'health_condition',
label: 'Health Condition',
show: true
},
{
key: 'payment_status',
label: 'Payment Status',
show: true
},
{
key: 'payment_due_date',
label: 'Payment Due Date',
show: true
},
{
key: 'service_requirement',
label: 'Service Requirement',
show: true
},
{
key: 'tags',
label: 'Tags',
show: true
}
]);
useEffect(() => {
if (!AuthService.canViewCustomers()) {
window.alert('You haven\'t login yet OR this user does not have access to this page. Please change an admin account to login.')
AuthService.logout();
navigate(`/login`);
}
CustomerService.getAllCustomers().then((data) => {
setCustomers(data.data.map((item) =>{
item.phone = item?.phone || item?.home_phone || item?.mobile_phone;
item.address = item?.address1 || item?.address2 || item?.address3 || item?.address4|| item?.address5;
return item;
}).sort((a, b) => a.lastname > b.lastname ? 1: -1));
})
LabelService.getAll().then((data) => {
setAvailableLabels(data.data);
})
}, []);
useEffect(() => {
let filtered = customers;
// Basic keyword filter
if (keyword) {
filtered = filtered.filter((item) => item?.name.toLowerCase().includes(keyword.toLowerCase()));
}
// Active/Inactive filter
if (showInactive) {
filtered = filtered.filter(item => (item.type === CUSTOMER_TYPE.TRANSFERRED || item.type === CUSTOMER_TYPE.DECEASED || item.type === CUSTOMER_TYPE.DISCHARED) && item.status !== 'active');
} else {
filtered = filtered.filter(item => (item.type !== CUSTOMER_TYPE.TRANSFERRED && item.type!=CUSTOMER_TYPE.DECEASED && item.type!=CUSTOMER_TYPE.DISCHARED) && item.status === 'active');
}
// Health condition filter
if (healthConditionFilter) {
filtered = filtered.filter(item => item?.health_condition === healthConditionFilter);
}
// Payment status filter
if (paymentStatusFilter) {
filtered = filtered.filter(item => item?.payment_status === paymentStatusFilter);
}
// Service requirement filter
if (serviceRequirementFilter) {
filtered = filtered.filter(item => item?.service_requirement === serviceRequirementFilter);
}
// Tags filter
if (tagsFilter.length > 0) {
filtered = filtered.filter(item => {
if (!item?.tags || item.tags.length === 0) return false;
return tagsFilter.some(tag => item.tags.includes(tag));
});
}
setFilteredCustomers(filtered);
}, [keyword, customers, showInactive, healthConditionFilter, paymentStatusFilter, serviceRequirementFilter, tagsFilter])
useEffect(() => {
const newCustomers = [...customers];
const sortedCustomers = sorting.key === '' ? newCustomers : newCustomers.sort((a, b) => {
return a[sorting.key]?.localeCompare(b[sorting.key]);
});
setCustomers(
sorting.order === 'asc' ? sortedCustomers : sortedCustomers.reverse()
)
}, [sorting]);
const getSortingImg = (key) => {
return sorting.key === key ? (sorting.order === 'asc' ? 'up_arrow' : 'down_arrow') : 'default';
}
const sortTableWithField = (key) => {
if (sorting.key === key) {
setSorting({key: key, order: sorting.order === 'asc' ? 'desc' : 'asc'});
} else {
setSorting({key: key, order: 'asc'});
}
}
const toggleSelectedAllItems = () => {
if (selectedItems.length === filteredCustomers.length) {
setSelectedItems([]);
} else {
setSelectedItems(filteredCustomers.map(item => item.id));
}
}
const toggleItem = (id) => {
if (selectedItems.includes(id)) {
setSelectedItems(selectedItems.filter(item => item !== id));
} else {
setSelectedItems([...selectedItems, id]);
}
}
const showArchive = (value) => {
setShowInactive(value === 'archivedCustomers');
}
const checkSelectAll = () => {
return selectedItems.length === filteredCustomers.length && filteredCustomers.length > 0;
}
const cleanFilterAndClose = () => {
setHealthConditionFilter('');
setPaymentStatusFilter('');
setServiceRequirementFilter('');
setTagsFilter([]);
setShowFilterDropdown(false);
}
const FilterAndClose = () => {
setShowFilterDropdown(false);
}
const toggleTagFilter = (tagName) => {
if (tagsFilter.includes(tagName)) {
setTagsFilter(tagsFilter.filter(tag => tag !== tagName));
} else {
setTagsFilter([...tagsFilter, tagName]);
}
}
const handleColumnsChange = (newColumns) => {
setColumns(newColumns);
}
const goToEdit = (id) => {
navigate(`/customers/edit/${id}`);
}
const goToCreateNew = () => {
navigate(`/customers`);
}
const setTransferValue = (customerId, site) => {
setTransferMap(prev => ({
...prev,
[customerId]: site
}));
}
const goToView = (id) => {
navigate(`/customers/${id}`);
}
const table = <div className="list row mb-4">
<div className="col-md-12">
<table className="personnel-info-table">
<thead>
<tr>
<th className="th-checkbox"><input type="checkbox" checked={checkSelectAll()} onClick={() => toggleSelectedAllItems()}></input></th>
<th className="th-index">No.</th>
{
columns.filter(col => col.show).map((column, index) => <th className="sortable-header" key={index}>
{column.label} <span className="float-right" onClick={() => sortTableWithField(column.key)}><img src={`/images/${getSortingImg(column.key)}.png`}></img></span>
</th>)
}
</tr>
</thead>
<tbody>
{
filteredCustomers.map((customer, index) => <tr key={customer.id}>
<td className="td-checkbox"><input type="checkbox" checked={selectedItems.includes(customer.id)} onClick={()=>toggleItem(customer?.id)}/></td>
<td className="td-index">{index + 1}</td>
{columns.find(col => col.key === 'name')?.show && <td> {AuthService.canAddOrEditCustomers() && <PencilSquare size={16} className="clickable me-2" onClick={() => goToEdit(customer?.id)}></PencilSquare>} {AuthService.canViewCustomers() && <PersonSquare onClick={() => goToView(customer?.id)} size={16} className="clickable me-2" />} {customer?.name}</td>}
{columns.find(col => col.key === 'chinese_name')?.show && <td>{customer?.name_cn}</td>}
{columns.find(col => col.key === 'email')?.show && <td>{customer?.email}</td>}
{columns.find(col => col.key === 'type')?.show && <td>{customer?.type}</td>}
{columns.find(col => col.key === 'pickup_status')?.show && <td>{customer?.pickup_status}</td>}
{columns.find(col => col.key === 'birth_date')?.show && <td>{customer?.birth_date}</td>}
{columns.find(col => col.key === 'gender')?.show && <td>{customer?.gender}</td>}
{columns.find(col => col.key === 'language')?.show && <td>{customer?.language}</td>}
{columns.find(col => col.key === 'medicare_number')?.show && <td>{customer?.medicare_number}</td>}
{columns.find(col => col.key === 'medicaid_number')?.show && <td>{customer?.medicaid_number}</td>}
{columns.find(col => col.key === 'address')?.show && <td>{customer?.address1 || customer?.address2 || customer?.address3 || customer?.address4 || customer?.address5}</td>}
{columns.find(col => col.key === 'phone')?.show && <td>{customer?.phone || customer?.home_phone || customer?.mobile_phone}</td>}
{columns.find(col => col.key === 'emergency_contact')?.show && <td>{customer?.emergency_contact}</td>}
{columns.find(col => col.key === 'health_condition')?.show && <td>{customer?.health_condition}</td>}
{columns.find(col => col.key === 'payment_status')?.show && <td>{customer?.payment_status}</td>}
{columns.find(col => col.key === 'payment_due_date')?.show && <td>{customer?.payment_due_date}</td>}
{columns.find(col => col.key === 'service_requirement')?.show && <td>{customer?.service_requirement}</td>}
{columns.find(col => col.key === 'tags')?.show && <td>{customer?.tags?.join(', ')}</td>}
</tr>)
}
</tbody>
</table>
</div>
</div>;
const customFilterMenu = React.forwardRef(
({ children, style, className, 'aria-labelledby': labeledBy }, ref) => {
return (
<div
ref={ref}
style={style}
className={className}
aria-labelledby={labeledBy}
>
<h6>Filter By</h6>
<div className="app-main-content-fields-section margin-sm dropdown-container">
<div className="me-4">
<div className="field-label">Health Condition</div>
<select value={healthConditionFilter} onChange={(e) => setHealthConditionFilter(e.target.value)}>
<option value=""></option>
<option value="diabetes">Diabetes</option>
<option value="1-1">1-1</option>
<option value="rounding list">Rounding List</option>
<option value="MOLST/POA/Advanced Directive">MOLST/POA/Advanced Directive</option>
</select>
</div>
</div>
<div className="app-main-content-fields-section margin-sm dropdown-container">
<div className="me-4">
<div className="field-label">Payment Status</div>
<select value={paymentStatusFilter} onChange={(e) => setPaymentStatusFilter(e.target.value)}>
<option value=""></option>
<option value="paid">Paid</option>
<option value="overdue">Overdue</option>
</select>
</div>
</div>
<div className="app-main-content-fields-section margin-sm dropdown-container">
<div className="me-4">
<div className="field-label">Service Requirement</div>
<select value={serviceRequirementFilter} onChange={(e) => setServiceRequirementFilter(e.target.value)}>
<option value=""></option>
<option value="wheelchair">Wheelchair</option>
<option value="special care">Special Care</option>
</select>
</div>
</div>
<div className="app-main-content-fields-section margin-sm dropdown-container">
<div className="me-4">
<div className="field-label">Tags</div>
<div style={{ maxHeight: '150px', overflowY: 'auto' }}>
{availableLabels.map((label) => (
<div key={label.id} style={{ marginBottom: '5px' }}>
<input
type="checkbox"
id={`tag-${label.id}`}
checked={tagsFilter.includes(label.label_name)}
onChange={() => toggleTagFilter(label.label_name)}
/>
<label htmlFor={`tag-${label.id}`} style={{ marginLeft: '5px' }}>
{label.label_name}
</label>
</div>
))}
</div>
</div>
</div>
<div className="list row">
<div className="col-md-12">
<button className="btn btn-default btn-sm float-right" onClick={() => cleanFilterAndClose()}> Cancel </button>
<button className="btn btn-primary btn-sm float-right" onClick={() => FilterAndClose()}> Filter </button>
</div>
</div>
</div>
);
},
);
return (
<>
{showSpinner && <div className="spinner-overlay">
<Spinner animation="border" role="status">
<span className="visually-hidden">Loading...</span>
</Spinner>
</div>}
<div className="app-main-content-list-container">
<div className="app-main-content-list-func-container">
<Tabs defaultActiveKey="activeCustomers" id="customers-tab" onSelect={(k) => showArchive(k)}>
<Tab eventKey="activeCustomers" title="Active Customers">
{table}
</Tab>
<Tab eventKey="archivedCustomers" title="Archived Customers">
{table}
</Tab>
</Tabs>
<div className="list-func-panel">
<input className="me-2 with-search-icon" type="text" placeholder="Search" value={keyword} onChange={(e) => setKeyword(e.currentTarget.value)} />
<Dropdown
key={'filter-customers'}
id="filter-customers"
className="me-2"
show={showFilterDropdown}
onToggle={() => setShowFilterDropdown(!showFilterDropdown)}
autoClose={false}
>
<Dropdown.Toggle variant="primary">
<Filter size={16} className="me-2"></Filter>Filter
</Dropdown.Toggle>
<Dropdown.Menu as={customFilterMenu}/>
</Dropdown>
<ManageTable columns={columns} onColumnsChange={handleColumnsChange} />
{/* Removed Create New Customer button and Export functionality */}
</div>
</div>
</div>
</>
)
};
export default DashboardCustomersList;

View File

@ -3,6 +3,7 @@ import { useNavigate } from "react-router-dom";
import { AuthService, EventsService, CustomerService, ResourceService, EventRequestsService } from "../../services";
import { Spinner, Breadcrumb, BreadcrumbItem, Tabs, Tab } from "react-bootstrap";
import { Columns, Download, Filter, PencilSquare, PersonSquare, Plus } from "react-bootstrap-icons";
import { ManageTable, Export } from "../../shared/components";
const EventRequestList = () => {
const navigate = useNavigate();
@ -14,54 +15,53 @@ const EventRequestList = () => {
const [comments, setComments] = useState({});
const [keyword, setKeyword] = useState('');
const [selectedItems, setSelectedItems] = useState([]);
const columns = [
const [columns, setColumns] = useState([
{
key: 'customer_display',
label:'Patient'
label:'Patient',
show: true
},
{
key: 'resource_display',
label: 'Doctor'
label: 'Doctor',
show: true
},
{
key: 'source',
label: 'Source'
label: 'Source',
show: true
},
{
key: 'type',
label: 'Type'
label: 'Type',
show: true
},
{
key: 'symptom',
label: 'Symptom Or Special Need',
show: true
},
{
key: 'np',
label: 'NP'
label: 'NP',
show: true
},
{
key: 'upload',
label: 'Upload By'
label: 'Upload By',
show: true
},
{
key: 'status',
label: 'Status'
label: 'Status',
show: true
},
{
key: 'create_date',
label: 'Create Date'
label: 'Create Date',
show: true
}
];
]);
useEffect(() => {
if (!AuthService.canAccessLegacySystem()) {
@ -258,7 +258,7 @@ const EventRequestList = () => {
<th className="th-checkbox"><input type="checkbox" checked={checkSelectAll()} onClick={() => toggleSelectedAllItems()}></input></th>
<th className="th-index">No.</th>
{
columns.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>
</th>)
}
@ -274,15 +274,15 @@ const EventRequestList = () => {
eventRequests.filter((item) => filterRequestsFun(item, statusParam, keywordParam) )?.map((eventRequest, index) => <tr key={eventRequest?.id}>
<td className="td-checkbox"><input type="checkbox" checked={selectedItems.includes(eventRequest?.id)} onClick={()=>toggleItem(eventRequest?.id)}/></td>
<td className="td-index">{index + 1}</td>
<td>{eventRequest?.customer_display}</td>
<td>{eventRequest?.resource_display}</td>
<td>{EventRequestsService.sourceList.find((item) => item?.value === eventRequest?.source)?.label || eventRequest?.source}</td>
<td>{eventRequest?.type}</td>
<td>{eventRequest?.symptom}</td>
<td>{eventRequest?.np}</td>
<td>{eventRequest?.upload}</td>
<td>{getStatusLabel(eventRequest?.status)}</td>
<td>{new Date(eventRequest?.create_date).toLocaleDateString()}</td>
{columns.find(col => col.key === 'customer_display')?.show && <td>{eventRequest?.customer_display}</td>}
{columns.find(col => col.key === 'resource_display')?.show && <td>{eventRequest?.resource_display}</td>}
{columns.find(col => col.key === 'source')?.show && <td>{EventRequestsService.sourceList.find((item) => item?.value === eventRequest?.source)?.label || eventRequest?.source}</td>}
{columns.find(col => col.key === 'type')?.show && <td>{eventRequest?.type}</td>}
{columns.find(col => col.key === 'symptom')?.show && <td>{eventRequest?.symptom}</td>}
{columns.find(col => col.key === 'np')?.show && <td>{eventRequest?.np}</td>}
{columns.find(col => col.key === 'upload')?.show && <td>{eventRequest?.upload}</td>}
{columns.find(col => col.key === 'status')?.show && <td>{getStatusLabel(eventRequest?.status)}</td>}
{columns.find(col => col.key === 'create_date')?.show && <td>{new Date(eventRequest?.create_date).toLocaleDateString()}</td>}
<td>{ eventRequest.status === 'done' && eventRequest?.edit_history && eventRequest?.edit_history[eventRequest?.edit_history?.length - 1]?.employee || ''}</td>
<td>{eventRequest?.notes?.map((note) => {
return <p>{`${note?.author}: ${note?.content}`}</p>
@ -331,9 +331,13 @@ const EventRequestList = () => {
<div className="list-func-panel">
<input className="me-2 with-search-icon" type="text" placeholder="Search" value={keyword} onChange={(e) => setKeyword(e.currentTarget.value)} />
<button className="btn btn-primary me-2"><Filter size={16} className="me-2"></Filter>Filter</button>
<button className="btn btn-primary me-2"><Columns size={16} className="me-2"></Columns>Manage Table</button>
<ManageTable columns={columns} onColumnsChange={setColumns} />
<button className="btn btn-primary me-2" onClick={() => goToCreateNew()}><Plus size={16}></Plus>Add New Appointment Request</button>
<button className="btn btn-primary"><Download size={16} className="me-2"></Download>Export</button>
<Export
columns={columns}
data={eventRequests.filter((item) => filterRequestsFun(item, showDone ? 'done': 'active', keyword))}
filename="event-requests"
/>
</div>
</div>
</div>

View File

@ -5,6 +5,7 @@ import DatePicker from "react-datepicker";
import { Spinner, Breadcrumb, BreadcrumbItem, Tabs, Tab, Button, Modal } from "react-bootstrap";
import { Columns, Download, Filter, PencilSquare, PersonSquare, Plus } from "react-bootstrap-icons";
import TimePicker from 'react-time-picker';
import { ManageTable, Export } from "../../shared/components";
const EventsList = () => {
const navigate = useNavigate();
@ -21,75 +22,78 @@ const EventsList = () => {
const [transportOptionsList, setTransportationOptionsList] = useState([]);
const [transportSelected, setTransportSelected] = useState(null);
const [showDeletedItems, setShowDeletedItems] = useState(false);
const columns = [
const [columns, setColumns] = useState([
{
key: 'customer',
label:'Customer'
label:'Customer',
show: true
},
{
key: 'chinese_name',
label: 'Preferred Name'
label: 'Preferred Name',
show: true
},
{
key: 'member_type',
label: 'Member Type'
label: 'Member Type',
show: true
},
{
key: 'eyes_on',
label: 'Eyes-on'
label: 'Eyes-on',
show: true
},
{
key: 'doctor',
label: 'Doctor'
label: 'Doctor',
show: true
},
{
key: 'phone',
label: 'Phone'
label: 'Phone',
show: true
},
{
key: 'address',
label: 'Address'
label: 'Address',
show: true
},
{
key: 'translation',
label: 'Translation'
label: 'Translation',
show: true
},
{
key: 'newPatient',
label: 'New Patient',
show: true
},
{
key: 'needId',
label: 'Need ID'
label: 'Need ID',
show: true
},
{
key: 'disability',
label: 'Disability'
label: 'Disability',
show: true
},
{
key: 'startTime',
label: 'Start Time'
label: 'Start Time',
show: true
},
{
key: 'fasting',
label: 'Fasting'
label: 'Fasting',
show: true
},
{
key: 'transportation',
label: 'Driver'
label: 'Driver',
show: true
}
];
]);
const checkDisability = (customers, event) => {
const currentCustomer = customers?.find(c => c?.id === event?.data?.customer || c?.name === event?.data?.client_name || c?.name === event?.target_name);
@ -368,7 +372,7 @@ const EventsList = () => {
<th className="th-checkbox"><input type="checkbox" checked={checkSelectAll()} onClick={() => toggleSelectedAllItems()}></input></th>
<th className="th-index">No.</th>
{
columns.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>
</th>)
}
@ -381,20 +385,20 @@ const EventsList = () => {
events && events.filter(event => event.status === statusParam).map((medicalEvent, index) => <tr key={medicalEvent.id}>
<td className="td-checkbox"><input type="checkbox" checked={selectedItems.includes(medicalEvent.id)} onClick={()=>toggleItem(medicalEvent?.id)}/></td>
<td className="td-index">{index + 1}</td>
<td>{medicalEvent?.customer}</td>
<td>{medicalEvent?.chinese_name}</td>
<td>{medicalEvent?.member_type}</td>
<td>{medicalEvent?.eyes_on}</td>
<td>{medicalEvent?.doctor}</td>
<td>{medicalEvent?.phone}</td>
<td>{medicalEvent?.address}</td>
<td>{medicalEvent?.translation}</td>
<td>{medicalEvent?.newPatient}</td>
<td>{medicalEvent?.needId}</td>
<td>{medicalEvent?.disability}</td>
<td>{medicalEvent?.startTime}</td>
<td>{medicalEvent?.fasting}</td>
<td>{medicalEvent?.transportation}</td>
{columns.find(col => col.key === 'customer')?.show && <td>{medicalEvent?.customer}</td>}
{columns.find(col => col.key === 'chinese_name')?.show && <td>{medicalEvent?.chinese_name}</td>}
{columns.find(col => col.key === 'member_type')?.show && <td>{medicalEvent?.member_type}</td>}
{columns.find(col => col.key === 'eyes_on')?.show && <td>{medicalEvent?.eyes_on}</td>}
{columns.find(col => col.key === 'doctor')?.show && <td>{medicalEvent?.doctor}</td>}
{columns.find(col => col.key === 'phone')?.show && <td>{medicalEvent?.phone}</td>}
{columns.find(col => col.key === 'address')?.show && <td>{medicalEvent?.address}</td>}
{columns.find(col => col.key === 'translation')?.show && <td>{medicalEvent?.translation}</td>}
{columns.find(col => col.key === 'newPatient')?.show && <td>{medicalEvent?.newPatient}</td>}
{columns.find(col => col.key === 'needId')?.show && <td>{medicalEvent?.needId}</td>}
{columns.find(col => col.key === 'disability')?.show && <td>{medicalEvent?.disability}</td>}
{columns.find(col => col.key === 'startTime')?.show && <td>{medicalEvent?.startTime}</td>}
{columns.find(col => col.key === 'fasting')?.show && <td>{medicalEvent?.fasting}</td>}
{columns.find(col => col.key === 'transportation')?.show && <td>{medicalEvent?.transportation}</td>}
<td>{medicalEvent?.dob}</td>
<td>{medicalEvent?.transMethod}</td>
</tr>)
@ -445,10 +449,14 @@ const EventsList = () => {
</Tabs>
<div className="list-func-panel">
{/* <input className="me-2 with-search-icon" type="text" placeholder="Search" value={keyword} onChange={(e) => setKeyword(e.currentTarget.value)} /> */}
<button className="btn btn-primary me-2"><Filter size={16} className="me-2"></Filter>Filter</button>
<button className="btn btn-primary me-2"><Columns size={16} className="me-2"></Columns>Manage Table</button>
{/* <button className="btn btn-primary me-2"><Filter size={16} className="me-2"></Filter>Filter</button> */}
<ManageTable columns={columns} onColumnsChange={setColumns} />
<button className="btn btn-primary me-2" onClick={() => goToCreateNew()}><Plus size={16}></Plus>Add New Medical Appointment</button>
<button className="btn btn-primary"><Download size={16} className="me-2"></Download>Export</button>
<Export
columns={columns}
data={events.filter(event => event.status === (showDeletedItems ? 'inactive' : 'active'))}
filename="events"
/>
</div>
</div>
</div>

View File

@ -5,6 +5,7 @@ import DatePicker from "react-datepicker";
import Select from 'react-select';
import { Spinner, Breadcrumb, BreadcrumbItem, Tabs, Tab, Button, Modal, DropdownButton, Dropdown } from "react-bootstrap";
import { Columns, Download, Filter, PencilSquare, PersonSquare, Plus } from "react-bootstrap-icons";
import { ManageTable, Export } from "../../shared/components";
const EventsMultipleList = () => {
@ -21,70 +22,73 @@ const EventsMultipleList = () => {
const [filteredEvents, setFilteredEvents] = useState(events);
const [showDeletedItems, setShowDeletedItems] = useState(false);
const [showFilterDropdown, setShowFilterDropdown] = useState(false);
const columns = [
const [columns, setColumns] = useState([
{
key: 'customer',
label:'Customer'
label:'Customer',
show: true
},
{
key: 'chinese_name',
label: 'Preferred Name'
label: 'Preferred Name',
show: true
},
{
key: 'member_type',
label: 'Member Type'
label: 'Member Type',
show: true
},
{
key: 'eyes_on',
label: 'Eyes-on'
label: 'Eyes-on',
show: true
},
{
key: 'doctor',
label: 'Doctor'
label: 'Doctor',
show: true
},
{
key: 'phone',
label: 'Phone'
label: 'Phone',
show: true
},
{
key: 'address',
label: 'Address'
label: 'Address',
show: true
},
{
key: 'translation',
label: 'Translation'
label: 'Translation',
show: true
},
{
key: 'newPatient',
label: 'New Patient',
show: true
},
{
key: 'needId',
label: 'Need ID'
label: 'Need ID',
show: true
},
{
key: 'startTime',
label: 'Start Time'
label: 'Start Time',
show: true
},
{
key: 'transportation',
label: 'Driver'
label: 'Driver',
show: true
},
{
key: 'fasting',
label: 'Fasting'
label: 'Fasting',
show: true
}
];
]);
const checkDisability = (customers, event) => {
const currentCustomer = customers?.find(c => c?.id === event?.data?.customer || c?.name === event?.data?.client_name || c?.name === event?.target_name);
@ -303,7 +307,7 @@ const EventsMultipleList = () => {
<th className="th-checkbox" ><input type="checkbox" checked={checkSelectAll()} onClick={() => toggleSelectedAllItems()}></input></th>
<th className="th-index">No.</th>
{
columns.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>
</th>)
}
@ -318,19 +322,19 @@ const EventsMultipleList = () => {
filteredEvents && filteredEvents.filter(event => event.status === statusParam).map((medicalEvent, index) => <tr key={medicalEvent.id}>
<td className="td-checkbox"><input type="checkbox" checked={selectedItems.includes(medicalEvent.id)} onClick={()=>toggleItem(medicalEvent?.id)}/></td>
<td className="td-index">{index + 1}</td>
<td>{medicalEvent?.customer}</td>
<td>{medicalEvent?.chinese_name}</td>
<td>{medicalEvent?.member_type}</td>
<td>{medicalEvent?.eyes_on}</td>
<td>{medicalEvent?.doctor}</td>
<td>{medicalEvent?.phone}</td>
<td>{medicalEvent?.address}</td>
<td>{medicalEvent?.translation}</td>
<td>{medicalEvent?.newPatient}</td>
<td>{medicalEvent?.needId}</td>
<td>{medicalEvent?.startTime}</td>
<td>{medicalEvent?.transportation}</td>
<td>{medicalEvent?.fasting}</td>
{columns.find(col => col.key === 'customer')?.show && <td>{medicalEvent?.customer}</td>}
{columns.find(col => col.key === 'chinese_name')?.show && <td>{medicalEvent?.chinese_name}</td>}
{columns.find(col => col.key === 'member_type')?.show && <td>{medicalEvent?.member_type}</td>}
{columns.find(col => col.key === 'eyes_on')?.show && <td>{medicalEvent?.eyes_on}</td>}
{columns.find(col => col.key === 'doctor')?.show && <td>{medicalEvent?.doctor}</td>}
{columns.find(col => col.key === 'phone')?.show && <td>{medicalEvent?.phone}</td>}
{columns.find(col => col.key === 'address')?.show && <td>{medicalEvent?.address}</td>}
{columns.find(col => col.key === 'translation')?.show && <td>{medicalEvent?.translation}</td>}
{columns.find(col => col.key === 'newPatient')?.show && <td>{medicalEvent?.newPatient}</td>}
{columns.find(col => col.key === 'needId')?.show && <td>{medicalEvent?.needId}</td>}
{columns.find(col => col.key === 'startTime')?.show && <td>{medicalEvent?.startTime}</td>}
{columns.find(col => col.key === 'transportation')?.show && <td>{medicalEvent?.transportation}</td>}
{columns.find(col => col.key === 'fasting')?.show && <td>{medicalEvent?.fasting}</td>}
<td>{medicalEvent?.dob}</td>
<td>{medicalEvent?.transMethod}</td>
<td>{medicalEvent?.confirmed ? 'Confirmed' : <button className="btn btn-primary btn-sm me-2 ms-2" onClick={() => confirmEvent(medicalEvent?.id)}>Confirm</button>}</td>
@ -521,9 +525,13 @@ const EventsMultipleList = () => {
</Dropdown.Toggle>
<Dropdown.Menu as={customMenu}/>
</Dropdown>
<button className="btn btn-primary me-2"><Columns size={16} className="me-2"></Columns>Manage Table</button>
<ManageTable columns={columns} onColumnsChange={setColumns} />
<button className="btn btn-primary me-2" onClick={() => goToCreateNew()}><Plus size={16}></Plus>Add New Medical Appointment</button>
<button className="btn btn-primary"><Download size={16} className="me-2"></Download>Export</button>
<Export
columns={columns}
data={filteredEvents.filter(event => event.status === (showDeletedItems ? 'inactive' : 'active'))}
filename="events-multiple"
/>
</div>
</div>
</div>

View File

@ -18,7 +18,7 @@ function Layout() {
showMenu && <SideMenu />
}
<div className="app-main-container">
<div className="app-menu-user-profile-container">
<div className="app-menu-user-profile-container noprint">
<Bell size={16} color="#0066B1"/>
<div className="app-menu-user-profile ms-2">
<PersonCircle size={24}/>
@ -27,7 +27,7 @@ function Layout() {
<div className="user-role">{user && JSON.parse(user)?.roles[0]}</div>
</div>
<Dropdown>
<Dropdown.Toggle variant="tertiary" id="user-basic">
<Dropdown.Toggle variant="transparent" id="user-basic">
{/* <ChevronDown size={12} color="#555"></ChevronDown> */}
</Dropdown.Toggle>

View File

@ -12,13 +12,27 @@ const SideMenu = () => {
{
icon: <Grid1x2 color="#777" size={14}/>,
name: 'Dashboard',
link: '#',
roleFunc: () => true
collapsed: false,
roleFunc: () => true,
subNavs: [
{
name: 'Dashboard',
link: '/dashboard/dashboard',
category: '/dashboard/dashboard',
roleFunc: AuthService.canViewRoutes
},
{
name: 'Admin View',
link: '/dashboard/admin-view',
category: '/dashboard/admin-view',
roleFunc: AuthService.canAccessLegacySystem
}
]
},
{
icon: <Display color="#777" size={14}/>,
name: 'Info Screen',
link: '#',
link: '/info-screen',
roleFunc: () => true
},
{
@ -123,7 +137,7 @@ const SideMenu = () => {
{
name: 'Customer Report',
link: '/admin/customer-report',
category: '/admin',
category: '/admin/',
roleFunc: AuthService.canViewAttendance
}
// {
@ -212,7 +226,7 @@ const SideMenu = () => {
return (
<>
<div className={`app-side-bar-container${collapse ? ' collapsed' : ''}`}>
<div className={`app-side-bar-container${collapse ? ' collapsed' : ''} noprint`}>
<div className={`app-side-bar-logo${collapse ? ' collapsed' : ''}`}>
<img src="/images/logo-trans.png" />
{!collapse && <strong className="logo-worldshine">Worldshine</strong>}

File diff suppressed because it is too large Load Diff

View File

@ -93,7 +93,7 @@ const MessageList = () => {
<div className="list-func-panel">
<button className="btn btn-primary me-2" onClick={() => goToCreate()}><Plus size={16}></Plus>Create New Message</button>
<button className="btn btn-primary me-2" onClick={() => goToSendMessage()}><Send size={16} className="me-2"></Send> Send Message</button>
<button className="btn btn-primary"><Download size={16} className="me-2"></Download>Export</button>
{/* <button className="btn btn-primary"><Download size={16} className="me-2"></Download>Export</button> */}
</div>
</div>
</div>

View File

@ -4,6 +4,7 @@ import { AuthService, ResourceService } from "../../services";
import { Spinner, Breadcrumb, BreadcrumbItem, Tabs, Tab } from "react-bootstrap";
import { Columns, Download, Filter, PencilSquare, PersonSquare, Plus } from "react-bootstrap-icons";
import ReactPaginate from 'react-paginate';
import { ManageTable, Export } from "../../shared/components";
const ResourcesList = () => {
const navigate = useNavigate();
@ -19,6 +20,38 @@ const ResourcesList = () => {
const [itemsPerPage, setItemsPerPage] = useState(10);
const [showDeletedItems, setShowDeletedItems] = useState(false);
const [currentPage, setCurrentPage] = useState(1);
const [columns, setColumns] = useState([
{
key: 'display_name',
label: 'Name',
show: true
},
{
key: 'type',
label: 'Type',
show: true
},
{
key: 'specialty',
label: 'Specialty',
show: true
},
{
key: 'email',
label: 'Email',
show: true
},
{
key: 'address',
label: 'Address',
show: true
},
{
key: 'display_contact',
label: 'Contact',
show: true
}
]);
useEffect(() => {
if (!AuthService.canAccessLegacySystem()) {
window.alert('You haven\'t login yet OR this user does not have access to this page. Please change an admin account to login.')
@ -143,33 +176,6 @@ const ResourcesList = () => {
});
}
const columns = [
{
key: 'display_name',
label: 'Name'
},
{
key: 'type',
label: 'Type'
},
{
key: 'specialty',
label: 'Specialty'
},
{
key: 'email',
label: 'Email'
},
{
key: 'address',
label: 'Address'
},
{
key: 'display_contact',
label: 'Contact'
}
]
const table = <div className="list row mb-4">
<div className="col-md-12">
<table className="personnel-info-table">
@ -178,7 +184,7 @@ const ResourcesList = () => {
<th className="th-checkbox"><input type="checkbox" checked={checkSelectAll()} onClick={() => toggleSelectedAllItems()}></input></th>
<th className="th-index">No.</th>
{
columns.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>
</th>)
}
@ -189,12 +195,12 @@ const ResourcesList = () => {
currentItems?.map((resource, index) => <tr key={resource?.id}>
<td className="td-checkbox"><input type="checkbox" checked={selectedItems.includes(resource.id)} onClick={()=>toggleItem(resource?.id)}/></td>
<td className="td-index">{index + 1}</td>
<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>
<td>{resource?.type}</td>
<td>{resource?.specialty}</td>
<td>{resource?.email}</td>
<td>{resource?.address}</td>
<td>{resource?.display_contact}</td>
{columns.find(col => col.key === 'display_name')?.show && <td> {AuthService.canAccessLegacySystem() && <PencilSquare size={16} className="clickable me-2" onClick={() => goToEdit(resource?.id)}></PencilSquare>} {AuthService.canAccessLegacySystem() ? <button className="btn btn-link btn-sm" onClick={() => goToView(resource?.id)}>{resource?.display_name}</button> : resource?.display_name } </td>}
{columns.find(col => col.key === 'type')?.show && <td>{resource?.type}</td>}
{columns.find(col => col.key === 'specialty')?.show && <td>{resource?.specialty}</td>}
{columns.find(col => col.key === 'email')?.show && <td>{resource?.email}</td>}
{columns.find(col => col.key === 'address')?.show && <td>{resource?.address}</td>}
{columns.find(col => col.key === 'display_contact')?.show && <td>{resource?.display_contact}</td>}
</tr>)
}
</tbody>
@ -266,10 +272,14 @@ const ResourcesList = () => {
</Tabs>
<div className="list-func-panel">
<input className="me-2 with-search-icon" type="text" placeholder="Search" value={keyword} onChange={(e) => setKeyword(e.currentTarget.value)} />
<button className="btn btn-primary me-2"><Filter size={16} className="me-2"></Filter>Filter</button>
<button className="btn btn-primary me-2"><Columns size={16} className="me-2"></Columns>Manage Table</button>
{/* <button className="btn btn-primary me-2"><Filter size={16} className="me-2"></Filter>Filter</button> */}
<ManageTable columns={columns} onColumnsChange={setColumns} />
<button className="btn btn-primary me-2" onClick={() => goToCreateNew()}><Plus size={16}></Plus>Add New Providers</button>
<button className="btn btn-primary"><Download size={16} className="me-2"></Download>Export</button>
<Export
columns={columns}
data={currentItems || []}
filename="resources"
/>
</div>
</div>
</div>

View File

@ -130,7 +130,7 @@ const ViewResource = () => {
<div className="list-func-panel">
<button className="btn btn-primary me-2" onClick={() => goToEdit(currentResource?.id)}><Pencil size={16} className="me-2"></Pencil>Edit</button>
<button className="btn btn-primary me-2" onClick={() => deactivateResource()}><Archive size={16} className="me-2"></Archive>Archive</button>
<button className="btn btn-primary"><Download size={16} className="me-2"></Download>Download</button>
{/* <button className="btn btn-primary"><Download size={16} className="me-2"></Download>Download</button> */}
</div>
</div>
</div>

View File

@ -456,7 +456,7 @@ const Seating = () => {
<input className="me-2 with-search-icon" type="text" placeholder="Search" value={keyword} onChange={(e) => setKeyword(e.currentTarget.value)} />
<button className="btn btn-primary me-2"><Filter size={16} className="me-2"></Filter>Filter</button>
<button className="btn btn-primary me-2"><Columns size={16} className="me-2"></Columns>Manage Labels</button>
<button className="btn btn-primary"><Download size={16} className="me-2"></Download>Export</button>
{/* <button className="btn btn-primary"><Download size={16} className="me-2"></Download>Export</button> */}
</div>
</div>
</div>

View File

@ -196,7 +196,7 @@ const CreateRoute = () => {
<div className="column-card">
<h6 className="text-primary">Route Details</h6>
{((params.get('type')==="inbound" && inboundTemplates?.length>0) ||(params.get('type')==="outbound" && outboundTemplates?.length>0)) && <div className="app-main-content-fields-section"><div className="me-4">
{/* {((params.get('type')==="inbound" && inboundTemplates?.length>0) ||(params.get('type')==="outbound" && outboundTemplates?.length>0)) && <div className="app-main-content-fields-section"><div className="me-4">
<div className="field-label">
Use Existed Template
</div>
@ -206,8 +206,8 @@ const CreateRoute = () => {
(params.get('type') === 'inbound' && inboundTemplates?.length>0) ? inboundTemplates.map(template => <option value={template.id} key={template.id}>{template.name}</option>) : ((params.get('type') === 'outbound' && outboundTemplates?.length>0) ? outboundTemplates.map(template => <option value={template.id} key={template.id}>{template.name}</option>): <></>)
}
</select>
{/* <button className="btn btn-link btn-sm" onClick={() => goToTemplateList()}>Manage Route Templates</button> */}
</div></div>}
</div></div>} */}
<div className="app-main-content-fields-section">
<div className="me-4">
@ -246,9 +246,9 @@ const CreateRoute = () => {
</select>
</div>
</div>
<div className="app-main-content-fields-section">
{/* <div className="app-main-content-fields-section">
Save As Template: <input type="checkbox" value={saveAsTemplate} checked={saveAsTemplate === true} onChange={e => setSaveAsTemplate(!saveAsTemplate)}/>
</div>
</div> */}
</div>
<div className="column-card adjust">
<div className="col-md-12 mb-4">

View File

@ -19,6 +19,7 @@ const PersonnelInfoTable = ({transRoutes, showCompletedInfo,
}) => {
const [show, setShow] = useState(false);
const [showGroupEditor, setShowGroupEditor] = useState(false);
const [showBulkUpdateModal, setShowBulkUpdateModal] = useState(false);
const [customerInEdit, setCustomerInEdit] = useState(undefined);
const [customersInEdit, setCustomersInEdit] = useState([]);
// const [statusFilter, setStatusFilter] = useState('');
@ -38,6 +39,10 @@ const PersonnelInfoTable = ({transRoutes, showCompletedInfo,
const [customerAddressOverride, setCustomerAddressOverride] = useState('');
const [customerAddressesList, setCustomerAddressesList] = useState([]);
const [customerNote, setCustomerNote] = useState('');
const [bulkEnterCenterTime, setBulkEnterCenterTime] = useState('');
const [bulkLeaveCenterTime, setBulkLeaveCenterTime] = useState('');
const [bulkCustomerRouteStatus, setBulkCustomerRouteStatus] = useState('');
const [bulkCustomerPickupStatus, setBulkCustomerPickupStatus] = useState('');
const dispatch = useDispatch();
const navigate = useNavigate();
const { updateRoute } = transRoutesSlice.actions;
@ -471,6 +476,73 @@ const PersonnelInfoTable = ({transRoutes, showCompletedInfo,
})
}
const openBulkUpdateModal = () => {
setShowBulkUpdateModal(true);
setBulkEnterCenterTime('');
setBulkLeaveCenterTime('');
setBulkCustomerRouteStatus('');
setBulkCustomerPickupStatus('');
}
const closeBulkUpdateModal = () => {
setShowBulkUpdateModal(false);
setBulkEnterCenterTime('');
setBulkLeaveCenterTime('');
setBulkCustomerRouteStatus('');
setBulkCustomerPickupStatus('');
}
const saveBulkUpdate = () => {
const routeId = transRoutes[0]?.id;
if (routeId) {
let requestBody = transRoutes.find((route) => route.id === routeId);
const dateStr = requestBody?.schedule_date || '';
const updatedCustomerList = requestBody.route_customer_list.map((item) => {
// Skip customers who are Unscheduled Absent or Scheduled Absent
if (item.customer_pickup_status === PICKUP_STATUS.UNSCHEDULE_ABSENT ||
item.customer_pickup_status === PICKUP_STATUS.SCHEDULE_ABSENT) {
return item;
}
let updatedItem = { ...item };
if (bulkEnterCenterTime && bulkEnterCenterTime !== '') {
updatedItem.customer_enter_center_time = combineDateAndTime(dateStr, bulkEnterCenterTime);
updatedItem.customer_route_status = PERSONAL_ROUTE_STATUS.IN_CENTER;
}
if (bulkLeaveCenterTime && bulkLeaveCenterTime !== '') {
updatedItem.customer_leave_center_time = combineDateAndTime(dateStr, bulkLeaveCenterTime);
updatedItem.customer_route_status = PERSONAL_ROUTE_STATUS.LEFT_CENTER;
}
if (bulkCustomerRouteStatus && bulkCustomerRouteStatus !== '') {
updatedItem.customer_route_status = bulkCustomerRouteStatus;
}
if (bulkCustomerPickupStatus && bulkCustomerPickupStatus !== '') {
updatedItem.customer_pickup_status = bulkCustomerPickupStatus;
}
return updatedItem;
});
requestBody = Object.assign({}, requestBody, {route_customer_list: updatedCustomerList, updatedAt: new Date(), updatedBy: 'admin'});
let finalParams = { id: routeId, data: requestBody };
if (scheduleDate) {
finalParams = Object.assign({}, finalParams, {dateText: moment(scheduleDate).format('MM/DD/YYYY'), fromSchedule: true})
}
if (dateStr !== '' && dateStr !== moment().format('MM/DD/YYYY')) {
finalParams = Object.assign({}, finalParams, {dateText: dateStr})
}
dispatch(updateRoute(finalParams));
} else {
window.alert('Fail to update Route: no route Id found.')
}
closeBulkUpdateModal();
}
return (
<>
{!showGroupInfo && (<div className="list row mb-4">
@ -486,7 +558,8 @@ const PersonnelInfoTable = ({transRoutes, showCompletedInfo,
Generate Route Reports
</CSVLink>
{transRoutes[0].type === 'inbound' && <button className="btn btn-primary btn-sm me-2" onClick={() => generateSeniorTimeReport()}>Generate Participants Time Reports</button>}
<button className="btn btn-primary btn-sm" onClick={() => goToReportWithSignature()}>Get Route Report With Signature</button>
<button className="btn btn-primary btn-sm me-2" onClick={() => goToReportWithSignature()}>Get Route Report With Signature</button>
<button className="btn btn-primary btn-sm" onClick={() => openBulkUpdateModal()}>Bulk Update Route Customer Time</button>
</div>
</div>)}
@ -919,6 +992,60 @@ const PersonnelInfoTable = ({transRoutes, showCompletedInfo,
</Button>
</Modal.Footer>
</Modal>
<Modal show={showBulkUpdateModal} onHide={() => closeBulkUpdateModal()}>
<Modal.Header closeButton>
<Modal.Title>Bulk Update Route Customer Time</Modal.Title>
</Modal.Header>
<Modal.Body>
<>
<div className="app-main-content-fields-section">
<div className="me-4">
<div className="field-label">Customer Enter Center Time</div>
<TimePicker disableClock={true} format={'HH:mm'} value={bulkEnterCenterTime} onChange={setBulkEnterCenterTime} />
</div>
<div className="me-4">
<div className="field-label">Customer Leave Center Time</div>
<TimePicker disableClock={true} format={'HH:mm'} value={bulkLeaveCenterTime} onChange={setBulkLeaveCenterTime} />
</div>
</div>
<div className="app-main-content-fields-section">
<div className="me-4">
<div className="field-label">Customer Route Status</div>
<select value={bulkCustomerRouteStatus} onChange={(e) => setBulkCustomerRouteStatus(e.currentTarget.value)}>
<option value=""></option>
{
Object.entries(PERSONAL_ROUTE_STATUS).map((item) => <option key={item[0]} value={item[1]}>
{PERSONAL_ROUTE_STATUS_TEXT[item[1]].text}
</option>)
}
</select>
</div>
<div className="me-4">
<div className="field-label">Customer Pickup Status</div>
<select value={bulkCustomerPickupStatus} onChange={(e) => setBulkCustomerPickupStatus(e.currentTarget.value)}>
<option value=""></option>
{
Object.entries(PICKUP_STATUS).map((item) => <option key={item[0]} value={item[1]}>
{PICKUP_STATUS_TEXT[item[1]]}
</option>)
}
</select>
</div>
</div>
<div className="alert alert-info">
<strong>Note:</strong> This will update all customers in this route who are not Unscheduled Absent or Scheduled Absent.
</div>
</>
</Modal.Body>
<Modal.Footer>
<Button variant="link" size="sm" onClick={() => closeBulkUpdateModal()}>
Cancel
</Button>
<Button variant="primary" size="sm" onClick={() => saveBulkUpdate()}>
Save
</Button>
</Modal.Footer>
</Modal>
</>
);
};

View File

@ -4,7 +4,7 @@ import { useParams, useNavigate } from "react-router-dom";
import { selectAllRoutes, transRoutesSlice, vehicleSlice, selectTomorrowAllRoutes, selectAllActiveDrivers, selectAllActiveVehicles, selectHistoryRoutes } from "./../../store";
import { Modal, Button, Breadcrumb, Tabs, Tab } from "react-bootstrap";
import RouteCustomerEditor from "./RouteCustomerEditor";
import { AuthService, TransRoutesService } from "../../services";
import { AuthService, TransRoutesService, CustomerService } from "../../services";
import TimePicker from 'react-time-picker';
import 'react-time-picker/dist/TimePicker.css';
import moment from 'moment';
@ -36,6 +36,8 @@ const RouteEdit = () => {
const [errorMessage, setErrorMessage] = useState(undefined);
const [estimatedStartTime, setEstimatedStartTime] = useState(undefined);
const [currentRoute, setCurrentRoute] = useState(undefined);
const [allCustomers, setAllCustomers] = useState([]);
const [unassignedCustomers, setUnassignedCustomers] = useState([]);
const paramsQuery = new URLSearchParams(window.location.search);
const scheduleDate = paramsQuery.get('dateSchedule');
const editSection = paramsQuery.get('editSection')
@ -132,6 +134,23 @@ const RouteEdit = () => {
return dateObj;
}
const calculateUnassignedCustomers = (customers, routes) => {
if (!customers || !routes) return [];
// Get all customer IDs that are assigned to any route today
const assignedCustomerIds = new Set();
routes.forEach(route => {
route.route_customer_list?.forEach(customer => {
assignedCustomerIds.add(customer.customer_id);
});
});
// Filter out customers that are not assigned to any route
return customers.filter(customer =>
customer.status === 'active' && !assignedCustomerIds.has(customer.id)
);
}
useEffect(() => {
if (!AuthService.canAddOrEditRoutes()) {
window.alert('You haven\'t login yet OR this user does not have access to this page. Please change an admin account to login.')
@ -148,8 +167,30 @@ const RouteEdit = () => {
setNewCustomerList(data?.data?.route_customer_list);
setErrorMessage(undefined);
})
// Fetch all customers
CustomerService.getAllCustomers().then(data => {
setAllCustomers(data?.data || []);
});
}, []);
// Calculate unassigned customers when allCustomers or routes change
useEffect(() => {
if (!currentRoute?.schedule_date) return;
const routeDate = currentRoute.schedule_date;
// Get routes from the same date as the current route
const sameDateRoutes = [
...allRoutes.filter(route => route.schedule_date === routeDate),
...tomorrowRoutes.filter(route => route.schedule_date === routeDate),
...historyRoutes.filter(route => route.schedule_date === routeDate)
];
const unassigned = calculateUnassignedCustomers(allCustomers, sameDateRoutes);
setUnassignedCustomers(unassigned);
}, [allCustomers, allRoutes, tomorrowRoutes, historyRoutes, currentRoute]);
// useEffect(() => {
// if (currentRoute) {
// setRouteName(currentRoute.name);
@ -346,7 +387,15 @@ const RouteEdit = () => {
<div className="column-container">
<div className="column-card adjust">
<div className="col-md-12 mb-4">
<RouteCustomerEditor currentRoute={currentRoute} setNewCustomerList={setNewCustomerList}></RouteCustomerEditor>
<RouteCustomerEditor
currentRoute={currentRoute ? {
...currentRoute,
route_customer_list: currentRoute.route_customer_list?.filter(
customer => customer?.customer_route_status !== PERSONAL_ROUTE_STATUS.SCHEDULED_ABSENT
) || []
} : undefined}
setNewCustomerList={setNewCustomerList}
/>
</div>
</div>
</div>
@ -370,6 +419,25 @@ const RouteEdit = () => {
</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>
}
</Tab>

View File

@ -26,6 +26,10 @@ const RouteReportWithSignature = () => {
const site = EventsService.site;
// Check if route name is 'by own' (case-insensitive)
const isByOwnRoute = currentRoute?.name?.toLowerCase() === 'by own';
const driverLabel = isByOwnRoute ? 'Staff' : 'Driver';
const navigate = useNavigate();
const getRelatedInboundOutboundRoutesForThisView = (routeType) => {
@ -74,6 +78,19 @@ const RouteReportWithSignature = () => {
}, [currentRoute]);
return (
<>
<style>
{`
.route-report-table th,
.route-report-table td {
min-width: auto !important;
width: auto !important;
max-width: none !important;
}
.route-report-table {
table-layout: auto !important;
}
`}
</style>
<div className="list row noprint">
<div className="col-md-12 text-primary mb-2">
<button className="btn btn-link btn-sm" onClick={() => directToView()}>Back</button>
@ -86,75 +103,62 @@ const RouteReportWithSignature = () => {
</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>
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">
Driver (司机): <span className="report-field-value"><strong>{currentDriver?.name}</strong></span>
{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>
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>
Date: <span className="report-field-value"><strong>{currentRoute?.schedule_date && (new Date(currentRoute?.schedule_date))?.toLocaleDateString()}</strong></span>
</div>
</div>
<div className="list row">
<div className="col-md-6 col-sm-12 mb-4">
Driver'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>}
{driverLabel}'s Signature: {signature && <span className="mb-2 me-4">{signature && <img width="100px" src={`data:image/jpg;base64, ${signature}`}/>}</span>} {currentRoute?.end_time && <span>{new Date(currentRoute?.end_time).toLocaleTimeString('en-US', {hour12: false, hour: '2-digit', minute: '2-digit'})}</span>}
</div>
<div className="col-md-6 col-sm-12 mb-4">
{/* Manager's Signature (经理签字): {directorSignature && <span className="mb-2">{directorSignature && <img width="100px" src={`data:image/jpg;base64, ${directorSignature}`}/>}</span>} */}
Manager's Signature (经理签字): <img width="100px" src="/images/signature.jpeg"/>
Manager's Signature: <img width="100px" src="/images/signature.jpeg"/>
</div>
</div>
<div className="list row">
<div className="col-md-12 mb-4">
<table className="personnel-info-table">
<table className="personnel-info-table route-report-table" style={{
tableLayout: 'auto',
width: '100%'
}}>
<thead>
<tr>
<th>No.</th>
<th>Name</th>
<th>Phone</th>
<th>Address</th>
<th>Room</th>
<th colSpan={2}>Pick-Up</th>
<th>Pick-Up</th>
<th>Arrival</th>
<th>Departure</th>
<th>Drop-Off</th>
<th>RestStop</th>
<th>Notice</th>
<th style={{ minWidth: 'auto !important', width: 'auto !important', maxWidth: 'none !important' }}>No.</th>
<th style={{ minWidth: 'auto !important', width: 'auto !important', maxWidth: 'none !important' }}>Name</th>
<th style={{ minWidth: 'auto !important', width: 'auto !important', maxWidth: 'none !important' }}>Phone</th>
<th style={{ minWidth: 'auto !important', width: 'auto !important', maxWidth: 'none !important' }}>Address</th>
<th style={{ minWidth: 'auto !important', width: 'auto !important', maxWidth: 'none !important' }}>Unit</th>
<th colSpan={2} style={{ minWidth: 'auto !important', width: 'auto !important', maxWidth: 'none !important' }}>Pick-Up</th>
<th style={{ minWidth: 'auto !important', width: 'auto !important', maxWidth: 'none !important' }}>Pick-Up</th>
<th style={{ minWidth: 'auto !important', width: 'auto !important', maxWidth: 'none !important' }}>Arrival</th>
<th style={{ minWidth: 'auto !important', width: 'auto !important', maxWidth: 'none !important' }}>Departure</th>
<th style={{ minWidth: 'auto !important', width: 'auto !important', maxWidth: 'none !important' }}>Drop-Off</th>
<th style={{ minWidth: 'auto !important', width: 'auto !important', maxWidth: 'none !important' }}>Notice</th>
</tr>
<tr>
<th>序号</th>
<th>姓名</th>
<th>联系电话</th>
<th>地址</th>
<th>房间号</th>
<th colSpan={2}>出勤</th>
<th>接到时间</th>
<th>抵达中心</th>
<th>离开中心</th>
<th>送达时间</th>
<th></th>
<th>备注</th>
</tr>
<tr>
<th></th>
<th></th>
<th></th>
<th></th>
<th></th>
<th>Y</th>
<th>N</th>
<th></th>
<th></th>
<th></th>
<th></th>
<th></th>
<th></th>
<th style={{ minWidth: 'auto !important', width: 'auto !important', maxWidth: 'none !important' }}></th>
<th style={{ minWidth: 'auto !important', width: 'auto !important', maxWidth: 'none !important' }}></th>
<th style={{ minWidth: 'auto !important', width: 'auto !important', maxWidth: 'none !important' }}></th>
<th style={{ minWidth: 'auto !important', width: 'auto !important', maxWidth: 'none !important' }}></th>
<th style={{ minWidth: 'auto !important', width: 'auto !important', maxWidth: 'none !important' }}></th>
<th style={{ 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>
</thead>
<tbody>
@ -175,11 +179,10 @@ const RouteReportWithSignature = () => {
<td></td>
<td>{ ![PERSONAL_ROUTE_STATUS.SCHEDULED_ABSENT, PERSONAL_ROUTE_STATUS.UNEXPECTED_ABSENT].includes(relativeRouteCustomer?.customer_route_status) && ![PERSONAL_ROUTE_STATUS.SCHEDULED_ABSENT, PERSONAL_ROUTE_STATUS.UNEXPECTED_ABSENT].includes(customer?.customer_route_status) ? "✓" : ''}</td>
<td>{![PERSONAL_ROUTE_STATUS.SCHEDULED_ABSENT, PERSONAL_ROUTE_STATUS.UNEXPECTED_ABSENT].includes(relativeRouteCustomer?.customer_route_status) && ![PERSONAL_ROUTE_STATUS.SCHEDULED_ABSENT, PERSONAL_ROUTE_STATUS.UNEXPECTED_ABSENT].includes(customer?.customer_route_status) ? "" : 'x'}</td>
<td>{customer?.customer_pickup_time ? new Date(customer?.customer_pickup_time).toLocaleTimeString('en-US', {hour12: false, hour: '2-digit', minute: '2-digit'}) : (relativeRouteCustomer?.customer_pickup_time? new Date(relativeRouteCustomer?.customer_pickup_time).toLocaleTimeString('en-US', {hour12: false, hour: '2-digit', minute: '2-digit'}) : (customerInOtherRoute?.customer_pickup_time ? new Date(customerInOtherRoute?.customer_pickup_time)?.toLocaleTimeString('en-US', {hour12: false, hour: '2-digit', minute: '2-digit'}): ''))}</td>
<td>{customer?.customer_enter_center_time ? new Date(customer?.customer_enter_center_time).toLocaleTimeString('en-US', {hour12: false, hour: '2-digit', minute: '2-digit'}) : (relativeRouteCustomer?.customer_enter_center_time ? new Date(relativeRouteCustomer?.customer_enter_center_time).toLocaleTimeString('en-US', {hour12: false, hour: '2-digit', minute: '2-digit'}) : (customerInOtherRoute?.customer_enter_center_time ? new Date(customerInOtherRoute?.customer_enter_center_time)?.toLocaleTimeString('en-US', {hour12: false, hour: '2-digit', minute: '2-digit'}): ''))}</td>
<td>{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'}) : '')}</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'}) : (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'}) : '')}</td>
<td></td>
<td>{customer?.customer_pickup_time ? new Date(customer?.customer_pickup_time).toLocaleTimeString('en-US', {hour12: false, hour: '2-digit', minute: '2-digit', second: '2-digit'}) : (relativeRouteCustomer?.customer_pickup_time? new Date(relativeRouteCustomer?.customer_pickup_time).toLocaleTimeString('en-US', {hour12: false, hour: '2-digit', minute: '2-digit', second: '2-digit'}) : (customerInOtherRoute?.customer_pickup_time ? new Date(customerInOtherRoute?.customer_pickup_time)?.toLocaleTimeString('en-US', {hour12: false, hour: '2-digit', minute: '2-digit', second: '2-digit'}): ''))}</td>
<td>{customer?.customer_enter_center_time ? new Date(customer?.customer_enter_center_time).toLocaleTimeString('en-US', {hour12: false, hour: '2-digit', minute: '2-digit', second: '2-digit'}) : (relativeRouteCustomer?.customer_enter_center_time ? new Date(relativeRouteCustomer?.customer_enter_center_time).toLocaleTimeString('en-US', {hour12: false, hour: '2-digit', minute: '2-digit', second: '2-digit'}) : (customerInOtherRoute?.customer_enter_center_time ? new Date(customerInOtherRoute?.customer_enter_center_time)?.toLocaleTimeString('en-US', {hour12: false, hour: '2-digit', minute: '2-digit', second: '2-digit'}): ''))}</td>
<td>{customer?.customer_leave_center_time && customer?.customer_route_status !== PERSONAL_ROUTE_STATUS.SKIP_DROPOFF ? new Date(customer?.customer_leave_center_time).toLocaleTimeString('en-US', {hour12: false, hour: '2-digit', minute: '2-digit', second: '2-digit'}) : (relativeRouteCustomer?.customer_leave_center_time && relativeRouteCustomer?.customer_route_status !== PERSONAL_ROUTE_STATUS.SKIP_DROPOFF ? new Date(relativeRouteCustomer?.customer_leave_center_time).toLocaleTimeString('en-US', {hour12: false, hour: '2-digit', minute: '2-digit', second: '2-digit'}) : (customerInOtherRoute?.customer_leave_center_time ? new Date(customerInOtherRoute?.customer_leave_center_time)?.toLocaleTimeString('en-US', {hour12: false, hour: '2-digit', minute: '2-digit', second: '2-digit'}): ''))}</td>
<td>{customer?.customer_dropoff_time && customer?.customer_route_status !== PERSONAL_ROUTE_STATUS.SKIP_DROPOFF ? new Date(customer?.customer_dropoff_time).toLocaleTimeString('en-US', {hour12: false, hour: '2-digit', minute: '2-digit', second: '2-digit'}) : (relativeRouteCustomer?.customer_dropoff_time && relativeRouteCustomer?.customer_route_status !== PERSONAL_ROUTE_STATUS.SKIP_DROPOFF ? new Date(relativeRouteCustomer?.customer_dropoff_time).toLocaleTimeString('en-US', {hour12: false, hour: '2-digit', minute: '2-digit', second: '2-digit'}) : (customerInOtherRoute?.customer_dropoff_time ? new Date(customerInOtherRoute?.customer_dropoff_time)?.toLocaleTimeString('en-US', {hour12: false, hour: '2-digit', minute: '2-digit', second: '2-digit'}): ''))}</td>
<td>
{customer?.customer_type !== CUSTOMER_TYPE.MEMBER && <div>{ CUSTOMER_TYPE_TEXT[customer?.customer_type]}</div> }
{ !relativeRouteCustomer && otherRouteWithThisCustomer && (

View File

@ -190,9 +190,9 @@ const RouteView = () => {
</div>
</Tab>
</Tabs>
<div className="list-func-panel">
{/* <div className="list-func-panel">
<button className="btn btn-primary"><Download size={16} className="me-2"></Download>Export</button>
</div>
</div> */}
</div>
<Modal show={showVehicleDetails} onHide={() => closeModal()}>
<Modal.Header closeButton>

View File

@ -19,7 +19,7 @@ import RouteCustomerTable from "./RouteCustomerTable";
const RoutesDashboard = () => {
const navigate = useNavigate();
const dispatch = useDispatch();
const { fetchAllRoutes, createRoute, fetchAllBreakfastRecords, fetchAllLunchRecords, fetchAllSnackRecords, fetchAllHistoryRoutes, fetchAllTomorrowRoutes } = transRoutesSlice.actions;
const { fetchAllRoutes, createRoute, fetchAllBreakfastRecords, fetchAllLunchRecords, fetchAllSnackRecords, fetchAllHistoryRoutes, fetchAllTomorrowRoutes, updateRoute } = transRoutesSlice.actions;
const inboundRoutes = useSelector(selectInboundRoutes);
const outboundRoutes = useSelector(selectOutboundRoutes);
const tmrInboundRoutes = useSelector(selectTomorrowInboundRoutes);
@ -50,9 +50,10 @@ const RoutesDashboard = () => {
const [showFilterDropdown, setShowFilterDropdown] = useState(false);
const [showDateDropdown, setShowDateDropdown] = useState(false);
const [showSignatureRequestLoading, setShowSignatureRequestLoading] = useState(false);
const [routesForShowing, setRoutesForShowing] = useState(allRoutes);
const [routesForShowing, setRoutesForShowing] = useState(allRoutes);
const [routesInboundForShowing, setRoutesInboundForShowing] = useState(inboundRoutes);
const [routesOutboundForShowing, setRoutesOutboundForShowing] = useState(outboundRoutes);
const [previousAbsentCustomerIds, setPreviousAbsentCustomerIds] = useState(new Set());
const [isLoading, setIsLoading] = useState(false);
const [errorMessage, setErrorMessage] = useState(undefined);
@ -83,6 +84,57 @@ const RoutesDashboard = () => {
return ((date.getMonth() > 8) ? (date.getMonth() + 1) : ('0' + (date.getMonth() + 1))) + '/' + ((date.getDate() > 9) ? date.getDate() : ('0' + date.getDate())) + '/' + date.getFullYear()
}
const processRoutesForAbsentCustomers = (inboundRoutes, outboundRoutes) => {
// Get customers who are absent in inbound routes
const absentCustomerIds = new Set();
inboundRoutes.forEach(inboundRoute => {
inboundRoute.route_customer_list?.forEach(customer => {
if (customer.customer_route_status === PERSONAL_ROUTE_STATUS.SCHEDULED_ABSENT ||
customer.customer_route_status === PERSONAL_ROUTE_STATUS.UNEXPECTED_ABSENT) {
absentCustomerIds.add(customer.customer_id);
}
});
});
// Only update backend if absent customer list has changed
const hasAbsentCustomersChanged =
absentCustomerIds.size !== previousAbsentCustomerIds.size ||
[...absentCustomerIds].some(id => !previousAbsentCustomerIds.has(id)) ||
[...previousAbsentCustomerIds].some(id => !absentCustomerIds.has(id));
if (hasAbsentCustomersChanged) {
// Update outbound routes in backend if customers need to be removed
outboundRoutes.forEach(outboundRoute => {
const filteredCustomerList = outboundRoute.route_customer_list?.filter(customer =>
!absentCustomerIds.has(customer.customer_id)
) || [];
// If customers were removed, update the route in backend
if (filteredCustomerList.length !== outboundRoute.route_customer_list?.length) {
const updatedRoute = {
...outboundRoute,
route_customer_list: filteredCustomerList
};
dispatch(updateRoute({ id: outboundRoute.id, data: updatedRoute }));
}
});
// Update the previous absent customer IDs
setPreviousAbsentCustomerIds(absentCustomerIds);
}
// Remove absent customers from outbound routes for display
const processedOutboundRoutes = outboundRoutes.map(outboundRoute => ({
...outboundRoute,
route_customer_list: outboundRoute.route_customer_list?.filter(customer =>
!absentCustomerIds.has(customer.customer_id)
) || []
}));
return processedOutboundRoutes;
}
useEffect(() => {
if (scheduleDate) {
const [year, month, day] = scheduleDate?.split('-').map(Number);
@ -135,15 +187,15 @@ const RoutesDashboard = () => {
if (!dateSelected || selectedDateString === getDateString(new Date())) {
setRoutesForShowing(allRoutes);
setRoutesInboundForShowing(inboundRoutes);
setRoutesOutboundForShowing(outboundRoutes);
setRoutesOutboundForShowing(processRoutesForAbsentCustomers(inboundRoutes, outboundRoutes));
} else {
if (dateSelected > new Date()) {
setRoutesForShowing(allTomorrowRoutes);
setRoutesInboundForShowing(tmrInboundRoutes);
setRoutesOutboundForShowing(tmrOutboundRoutes);
setRoutesOutboundForShowing(processRoutesForAbsentCustomers(tmrInboundRoutes, tmrOutboundRoutes));
} else {
setRoutesForShowing(allHistoryRoutes);
setRoutesOutboundForShowing(historyOutboundRoutes);
setRoutesOutboundForShowing(processRoutesForAbsentCustomers(historyInboundRoutes, historyOutboundRoutes));
setRoutesInboundForShowing(historyInboundRoutes);
}
}

View File

@ -91,7 +91,7 @@ const RoutesSection = ({transRoutes, copyList, sectionName, drivers, vehicles, c
(sectionName.includes('Inbound') ||
sectionName.includes('Outbound')) &&
(`${seniors?.length} Scheduled ${seniors?.filter(
item => ![PERSONAL_ROUTE_STATUS.UNEXPECTED_ABSENT, PERSONAL_ROUTE_STATUS.SCHEDULED_ABSENT].includes(item?.customer_route_status))?.length} ${ sectionName.includes('Inbound') ? 'Checked In': 'Checked Out'} (${seniors.filter(item => [CUSTOMER_TYPE.MEMBER, CUSTOMER_TYPE.SELF_PAY].includes(item.customer_type))?.length} Members ${seniors.filter(item=> [CUSTOMER_TYPE.VISITOR].includes(item?.customer_type))?.length} Visitors)`)}</span>
item => ![PERSONAL_ROUTE_STATUS.UNEXPECTED_ABSENT, PERSONAL_ROUTE_STATUS.SCHEDULED_ABSENT].includes(item?.customer_route_status))?.length} ${ sectionName.includes('Inbound') ? 'Checked In': 'Checked Out'} (${seniors.filter(item => [CUSTOMER_TYPE.MEMBER, CUSTOMER_TYPE.SELF_PAY].includes(item.customer_type) && ![PERSONAL_ROUTE_STATUS.UNEXPECTED_ABSENT, PERSONAL_ROUTE_STATUS.SCHEDULED_ABSENT].includes(item?.customer_route_status))?.length} Members ${seniors.filter(item=> [CUSTOMER_TYPE.VISITOR].includes(item?.customer_type) && ![PERSONAL_ROUTE_STATUS.UNEXPECTED_ABSENT, PERSONAL_ROUTE_STATUS.SCHEDULED_ABSENT].includes(item?.customer_route_status))?.length} Visitors)`)}</span>
</h6>
{ canAddNew && (
<small className="me-4" onClick={() => { if (routeType) {redirect(routeType)} else {redirect()}}}>

View File

@ -4,6 +4,7 @@ import { useNavigate } from "react-router-dom";
import { AuthService, VehicleService } from "../../services";
import { Spinner, Breadcrumb, BreadcrumbItem, Tabs, Tab } from "react-bootstrap";
import { Columns, Download, Filter, PencilSquare, PersonSquare, Plus } from "react-bootstrap-icons";
import { ManageTable, Export } from "../../shared/components";
const VehicleList = () => {
const navigate = useNavigate();
@ -15,6 +16,43 @@ const VehicleList = () => {
const [selectedItems, setSelectedItems] = useState([]);
const [filteredVehicles, setFilteredVehicles] = useState(vehicles);
const [showInactive, setShowInactive] = useState(false);
const [columns, setColumns] = useState([
{
key: 'vehicle_number',
label: 'Vehicle Number',
show: true
},
{
key: 'tag',
label: 'License Plate',
show: true
},
{
key: 'capacity',
label: 'Seating Capacity',
show: true
},
{
key: 'mileage',
label: 'Mileage',
show: true
},
{
key: 'make',
label: 'Make',
show: true
},
{
key: 'model',
label: 'Model',
show: true
},
{
key: 'year',
label: 'Year',
show: true
}
]);
useEffect(() => {
if (!AuthService.canAddOrEditVechiles()) {
@ -76,37 +114,6 @@ const VehicleList = () => {
navigate(`/vehicles`)
}
const columns = [
{
key: 'vehicle_number',
label: 'Vehicle Number'
},
{
key: 'tag',
label: 'License Plate'
},
{
key: 'capacity',
label: 'Seating Capacity'
},
{
key: 'mileage',
label: 'Mileage'
},
{
key: 'make',
label: 'Make'
},
{
key: 'model',
label: 'Model'
},
{
key: 'year',
label: 'Year'
}
];
const getSortingImg = (key) => {
return sorting.key === key ? (sorting.order === 'asc' ? 'up_arrow' : 'down_arrow') : 'default';
}
@ -164,7 +171,7 @@ const VehicleList = () => {
<th className="th-checkbox"><input type="checkbox" checked={checkSelectAll()} onClick={() => toggleSelectedAllItems()}></input></th>
<th className="th-index">No.</th>
{
columns.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>
</th>)
}
@ -176,13 +183,13 @@ const VehicleList = () => {
filteredVehicles.map((vehicle, index) => <tr key={vehicle.id}>
<td className="td-checkbox"><input type="checkbox" checked={selectedItems.includes(vehicle.id)} onClick={()=>toggleItem(vehicle?.id)}/></td>
<td className="td-index">{index + 1}</td>
<td> {AuthService.canAddOrEditVechiles() && <PencilSquare size={16} className="clickable me-2" onClick={() => goToEdit(vehicle?.id)}></PencilSquare>} {AuthService.canViewVechiles() ? <button className="btn btn-link btn-sm" onClick={() => goToView(vehicle?.id)}>{vehicle?.vehicle_number}</button> : vehicle?.vehicle_number } </td>
<td>{vehicle?.tag}</td>
<td>{vehicle?.capacity}</td>
<td>{vehicle?.mileage}</td>
<td>{vehicle?.make}</td>
<td>{vehicle?.vehicle_model}</td>
<td>{vehicle?.year}</td>
{columns.find(col => col.key === 'vehicle_number')?.show && <td> {AuthService.canAddOrEditVechiles() && <PencilSquare size={16} className="clickable me-2" onClick={() => goToEdit(vehicle?.id)}></PencilSquare>} {AuthService.canViewVechiles() ? <button className="btn btn-link btn-sm" onClick={() => goToView(vehicle?.id)}>{vehicle?.vehicle_number}</button> : vehicle?.vehicle_number } </td>}
{columns.find(col => col.key === 'tag')?.show && <td>{vehicle?.tag}</td>}
{columns.find(col => col.key === 'capacity')?.show && <td>{vehicle?.capacity}</td>}
{columns.find(col => col.key === 'mileage')?.show && <td>{vehicle?.mileage}</td>}
{columns.find(col => col.key === 'make')?.show && <td>{vehicle?.make}</td>}
{columns.find(col => col.key === 'model')?.show && <td>{vehicle?.vehicle_model}</td>}
{columns.find(col => col.key === 'year')?.show && <td>{vehicle?.year}</td>}
</tr>)
}
</tbody>
@ -219,10 +226,14 @@ const VehicleList = () => {
</Tabs>
<div className="list-func-panel">
<input className="me-2 with-search-icon" type="text" placeholder="Search" value={keyword} onChange={(e) => setKeyword(e.currentTarget.value)} />
<button className="btn btn-primary me-2"><Filter size={16} className="me-2"></Filter>Filter</button>
<button className="btn btn-primary me-2"><Columns size={16} className="me-2"></Columns>Manage Table</button>
{/* <button className="btn btn-primary me-2"><Filter size={16} className="me-2"></Filter>Filter</button> */}
<ManageTable columns={columns} onColumnsChange={setColumns} />
<button className="btn btn-primary me-2" onClick={() => goToCreateNew()}><Plus size={16}></Plus>Add New Vehicle</button>
<button className="btn btn-primary"><Download size={16} className="me-2"></Download>Export</button>
<Export
columns={columns}
data={filteredVehicles}
filename="vehicles"
/>
</div>
</div>
</div>

View File

@ -6,6 +6,7 @@ import { vehicleSlice, selectVehicleError } from "./../../store";
import { Spinner, Breadcrumb, BreadcrumbItem, Tabs, Tab } from "react-bootstrap";
import { Download, PencilSquare, Archive } from "react-bootstrap-icons";
import moment from "moment";
import { Export } from "../../shared/components";
const ViewVehicle = () => {
const navigate = useNavigate();
@ -169,23 +170,28 @@ const ViewVehicle = () => {
const columnsRepair = [
{
key: 'repair_description',
label: 'Repair Description'
label: 'Repair Description',
show: true
},
{
key: 'repair_date',
label: 'Repair Date'
label: 'Repair Date',
show: true
},
{
key: 'repair_price',
label: 'Cost'
label: 'Cost',
show: true
},
{
key: 'repair_location',
label: 'Repair Location'
label: 'Repair Location',
show: true
},
{
key: 'create_date',
label: 'Date Added'
label: 'Date Added',
show: true
}
];
@ -526,7 +532,11 @@ const tableRepair = <div className="list row mb-4">
<div className="list-func-panel">
{currentTab !== 'basicInfo' && <input className="me-2 with-search-icon" type="text" placeholder="Search" value={keyword} onChange={(e) => setKeyword(e.currentTarget.value)} />}
{currentTab === 'documents' && <button className="btn btn-primary" onClick={() => download()}><Download size={16} className="me-2"></Download>Download</button>}
{currentTab === 'repairRecords' && <button className="btn btn-primary"><Download size={16} className="me-2"></Download>Export</button>}
{currentTab === 'repairRecords' && <Export
columns={columnsRepair}
data={filteredRepairs}
filename={`vehicle-${currentVehicle?.vehicle_number}-repairs`}
/>}
</div>
</div>

View File

@ -1,8 +1,10 @@
import { takeEvery, all, call, put } from 'redux-saga/effects';
import { customerSlice } from './../store';
import { CustomerService } from './../services';
import { customerSlice, transRoutesSlice } from './../store';
import { CustomerService, TransRoutesService } from './../services';
import moment from 'moment';
const { createCustomer, createCustomerFailure, updateCustomer, updateCustomerFailure, deleteCustomer, deleteCustomerFailure } = customerSlice.actions;
const { fetchAllRoutesSuccess, fetchAllTomorrowRoutesSuccess, fetchAllHisotryRoutesSuccess } = transRoutesSlice.actions;
function* createCustomerSaga(action) {
try {
@ -29,6 +31,78 @@ function* updateCustomerSaga(action) {
if (action.payload.avatar) {
CustomerService.uploadAvatar(action.payload.id, action.payload.avatar)
}
// Find and update routes that contain this customer
try {
const routes = yield call(TransRoutesService.getAllRoutesOnAndAfterToday);
const customerId = action.payload.id;
const updatedCustomerData = action.payload.data;
// Find routes that contain this customer
const routesToUpdate = routes.data.filter(route =>
route.route_customer_list.some(customer => customer.customer_id === customerId)
);
// Update each route with the customer's latest info
for (const route of routesToUpdate) {
const updatedCustomerList = route.route_customer_list.map(customer => {
if (customer.customer_id === customerId) {
return {
...customer,
customer_name: updatedCustomerData.name || customer.customer_name,
customer_address: updatedCustomerData.address1 || updatedCustomerData.address2 || updatedCustomerData.address3 || updatedCustomerData.address4 || updatedCustomerData.address5 || customer.customer_address,
customer_phone: updatedCustomerData.mobile_phone || updatedCustomerData.phone || customer.customer_phone,
customer_special_needs: updatedCustomerData.special_needs || customer.customer_special_needs,
customer_note: updatedCustomerData.note || customer.customer_note,
customer_language: updatedCustomerData.language || customer.customer_language,
customer_type: updatedCustomerData.type || customer.customer_type,
customer_table_id: updatedCustomerData.table_id || customer.customer_table_id,
customer_group: updatedCustomerData.groups ? updatedCustomerData.groups[0] : customer.customer_group
};
}
return customer;
});
const updatedRoute = {
...route,
route_customer_list: updatedCustomerList,
updatedAt: new Date(),
updatedBy: 'admin'
};
yield call(TransRoutesService.updateRoute, route.id, updatedRoute);
}
// Refetch routes to update the store with latest data
if (routesToUpdate.length > 0) {
try {
// Refetch today's routes
const date = new Date();
const dateText = ((date.getMonth() > 8) ? (date.getMonth() + 1) : ('0' + (date.getMonth() + 1))) + '/' + ((date.getDate() > 9) ? date.getDate() : ('0' + date.getDate())) + '/' + date.getFullYear();
const todayRoutes = (yield call(TransRoutesService.getAll, dateText)).data;
yield put(fetchAllRoutesSuccess(todayRoutes));
// Refetch tomorrow's routes
date.setDate(date.getDate() + 1);
const tmrDateText = ((date.getMonth() > 8) ? (date.getMonth() + 1) : ('0' + (date.getMonth() + 1))) + '/' + ((date.getDate() > 9) ? date.getDate() : ('0' + date.getDate())) + '/' + date.getFullYear();
const tomorrowRoutes = (yield call(TransRoutesService.getAll, tmrDateText)).data;
yield put(fetchAllTomorrowRoutesSuccess(tomorrowRoutes));
// Refetch history routes (last 7 days)
const historyDate = new Date();
historyDate.setDate(historyDate.getDate() - 7);
const historyDateText = moment(historyDate).format('MM/DD/YYYY');
const historyRoutes = (yield call(TransRoutesService.getAll, historyDateText)).data;
yield put(fetchAllHisotryRoutesSuccess(historyRoutes));
} catch (refetchError) {
console.error('Error refetching routes:', refetchError);
// Don't fail the customer update if refetch fails
}
}
} catch (routeUpdateError) {
console.error('Error updating routes with customer info:', routeUpdateError);
// Don't fail the customer update if route update fails
}
} catch(ex) {
yield put(updateCustomerFailure(ex));
}

View File

@ -0,0 +1,43 @@
import http from "../http-common";
const getAll = (status) => {
const params = {};
if (status) {
params.status = status;
}
return http.get("/attendance-notes", { params });
};
const getAttendanceNote = (id) => {
return http.get(`/attendance-notes/${id}`);
};
const createNewAttendanceNote = (data) => {
return http.post('/attendance-notes', data);
};
const updateAttendanceNote = (id, data) => {
return http.put(`/attendance-notes/${id}`, data);
};
const deleteAttendanceNote = (id) => {
return http.delete(`/attendance-notes/${id}`);
};
const uploadAttendanceNoteFile = (data, noteId, name, fileType) => {
return http.post(`/files/upload-physical?objectId=${noteId}&name=${name}&fileType=${fileType}&model=attendance_note`, data);
};
const getAllAttendanceNoteFiles = (noteId, name, fileType) => {
return http.get(`/files/uploadedDocs/attendance_note/${noteId}/type/${fileType}/name/${name}`);
};
export const AttendanceNoteService = {
getAll,
getAttendanceNote,
createNewAttendanceNote,
updateAttendanceNote,
deleteAttendanceNote,
uploadAttendanceNoteFile,
getAllAttendanceNoteFiles
};

View File

@ -0,0 +1,43 @@
import http from "../http-common";
const getAll = (status) => {
const params = {};
if (status) {
params.status = status;
}
return http.get("/carousels", { params });
};
const getCarousel = (id) => {
return http.get(`/carousels/${id}`);
};
const createNewCarousel = (data) => {
return http.post('/carousels', data);
};
const updateCarousel = (id, data) => {
return http.put(`/carousels/${id}`, data);
};
const deleteCarousel = (id) => {
return http.delete(`/carousels/${id}`);
};
const uploadCarouselFile = (data, carouselId, name, fileType) => {
return http.post(`/files/upload-physical?objectId=${carouselId}&name=${name}&fileType=${fileType}&model=carousel`, data);
};
const getAllCarouselFiles = (carouselId, name, fileType) => {
return http.get(`/files/uploadedDocs/carousel/${carouselId}/type/${fileType}/name/${name}`);
};
export const CarouselService = {
getAll,
getCarousel,
createNewCarousel,
updateCarousel,
deleteCarousel,
uploadCarouselFile,
getAllCarouselFiles
};

View File

@ -13,6 +13,10 @@ const getAll = (scheduleDate, driverId) => {
});
};
const getAllRoutesOnAndAfterToday = () => {
return http.get('/routes/on-and-after-today');
};
const updateRoute = (id, data) => {
return http.put(`/routes/${id}`, data);
};
@ -133,5 +137,6 @@ export const TransRoutesService = {
createSnackRecords,
deleteSnackRecords,
updateInProgress,
getRoute
getRoute,
getAllRoutesOnAndAfterToday
};

View File

@ -16,4 +16,20 @@ export * from './EventRequestService';
export * from './SignatureRequestService';
export * from './VehicleRepairService';
export * from './LabelService';
export * from './SeatingService';
export * from './SeatingService';
export * from './AttendanceNoteService';
export * from './CarouselService';
// Utility functions
export const parseDateFromBackend = (dateString) => {
if (!dateString) return null;
// Handle MM/DD/YYYY format
if (typeof dateString === 'string' && dateString.includes('/')) {
const [month, day, year] = dateString.split('/');
return new Date(parseInt(year), parseInt(month) - 1, parseInt(day));
}
// Handle other date formats
return new Date(dateString);
};

View File

@ -0,0 +1,178 @@
import React, { useState } from "react";
import { Dropdown } from "react-bootstrap";
import { Download } from "react-bootstrap-icons";
const Export = ({ columns, data, filename = "export" }) => {
const [showExportDropdown, setShowExportDropdown] = useState(false);
const [exportColumns, setExportColumns] = useState(
columns.map(col => ({ ...col, show: true }))
);
const handleColumnToggle = (columnKey) => {
const updatedColumns = exportColumns.map(col =>
col.key === columnKey ? { ...col, show: !col.show } : col
);
setExportColumns(updatedColumns);
};
const handleCancel = () => {
setExportColumns(columns.map(col => ({ ...col, show: true })));
setShowExportDropdown(false);
};
const generateCSV = () => {
const visibleColumns = exportColumns.filter(col => col.show);
const headers = visibleColumns.map(col => col.label).join(',');
const rows = data.map(row =>
visibleColumns.map(col => {
const value = row[col.key];
// Handle values that contain commas by wrapping in quotes
return typeof value === 'string' && value.includes(',') ? `"${value}"` : value;
}).join(',')
).join('\n');
const csvContent = `${headers}\n${rows}`;
downloadFile(csvContent, `${filename}.csv`, 'text/csv');
};
const generateExcel = () => {
const visibleColumns = exportColumns.filter(col => col.show);
const headers = visibleColumns.map(col => col.label);
const rows = data.map(row =>
visibleColumns.map(col => row[col.key])
);
// Create a simple Excel-like CSV with BOM for Excel compatibility
const excelContent = '\ufeff' + [headers, ...rows]
.map(row => row.map(cell =>
typeof cell === 'string' && (cell.includes(',') || cell.includes('"') || cell.includes('\n'))
? `"${cell.replace(/"/g, '""')}"`
: cell
).join(','))
.join('\n');
downloadFile(excelContent, `${filename}.csv`, 'text/csv');
};
const generatePDF = () => {
const visibleColumns = exportColumns.filter(col => col.show);
// Create a simple HTML table for PDF generation
const tableHTML = `
<html>
<head>
<style>
table { border-collapse: collapse; width: 100%; }
th, td { border: 1px solid #ddd; padding: 8px; text-align: left; }
th { background-color: #f2f2f2; }
</style>
</head>
<body>
<table>
<thead>
<tr>
${visibleColumns.map(col => `<th>${col.label}</th>`).join('')}
</tr>
</thead>
<tbody>
${data.map(row => `
<tr>
${visibleColumns.map(col => `<td>${row[col.key] || ''}</td>`).join('')}
</tr>
`).join('')}
</tbody>
</table>
</body>
</html>
`;
// Use browser's print functionality to generate PDF
const printWindow = window.open('', '_blank');
printWindow.document.write(tableHTML);
printWindow.document.close();
printWindow.print();
};
const downloadFile = (content, filename, mimeType) => {
const blob = new Blob([content], { type: mimeType });
const url = URL.createObjectURL(blob);
const link = document.createElement('a');
link.href = url;
link.download = filename;
document.body.appendChild(link);
link.click();
document.body.removeChild(link);
URL.revokeObjectURL(url);
};
const customExportMenu = React.forwardRef(
({ children, style, className, 'aria-labelledby': labeledBy }, ref) => {
return (
<div
ref={ref}
style={style}
className={className}
aria-labelledby={labeledBy}
>
<h6>Export Options</h6>
<div className="app-main-content-fields-section margin-sm dropdown-container">
<div className="me-4">
<div style={{ maxHeight: '200px', overflowY: 'auto', marginBottom: '15px' }}>
<h6 style={{ fontSize: '14px', marginBottom: '10px' }}>Select Columns:</h6>
{exportColumns.map((column) => (
<div key={column.key} style={{ marginBottom: '8px' }}>
<input
type="checkbox"
id={`export-column-${column.key}`}
checked={column.show}
onChange={() => handleColumnToggle(column.key)}
/>
<label htmlFor={`export-column-${column.key}`} style={{ marginLeft: '8px' }}>
{column.label}
</label>
</div>
))}
</div>
</div>
</div>
<div className="list row">
<div className="col-md-12">
<div style={{ display: 'flex', flexDirection: 'column', gap: '8px', marginTop: '15px' }}>
<button className="btn btn-primary btn-sm" style={{ width: '100%' }} onClick={generateCSV}>
Generate CSV
</button>
<button className="btn btn-primary btn-sm" style={{ width: '100%' }} onClick={generateExcel}>
Generate Excel
</button>
<button className="btn btn-primary btn-sm" style={{ width: '100%' }} onClick={generatePDF}>
Generate PDF
</button>
<button className="btn btn-default btn-sm" style={{ width: '100%', marginTop: '8px' }} onClick={handleCancel}>
Cancel
</button>
</div>
</div>
</div>
</div>
);
},
);
return (
<Dropdown
key={'export-dropdown'}
id="export-dropdown"
className="me-2"
show={showExportDropdown}
onToggle={() => setShowExportDropdown(!showExportDropdown)}
autoClose={false}
>
<Dropdown.Toggle variant="primary">
<Download size={16} className="me-2"></Download>Export
</Dropdown.Toggle>
<Dropdown.Menu as={customExportMenu}/>
</Dropdown>
);
};
export default Export;

View File

@ -0,0 +1,83 @@
import React, { useState } from "react";
import { Dropdown } from "react-bootstrap";
import { Columns } from "react-bootstrap-icons";
const ManageTable = ({ columns, onColumnsChange }) => {
const [showManageTableDropdown, setShowManageTableDropdown] = useState(false);
const [tempColumns, setTempColumns] = useState(columns);
const handleColumnToggle = (columnKey) => {
const updatedColumns = tempColumns.map(col =>
col.key === columnKey ? { ...col, show: !col.show } : col
);
setTempColumns(updatedColumns);
};
const handleDone = () => {
onColumnsChange(tempColumns);
setShowManageTableDropdown(false);
};
const handleCancel = () => {
setTempColumns(columns);
setShowManageTableDropdown(false);
};
const customManageTableMenu = React.forwardRef(
({ children, style, className, 'aria-labelledby': labeledBy }, ref) => {
return (
<div
ref={ref}
style={style}
className={className}
aria-labelledby={labeledBy}
>
<h6>Manage Table Columns</h6>
<div className="app-main-content-fields-section margin-sm dropdown-container">
<div className="me-4">
<div style={{ maxHeight: '200px', overflowY: 'auto' }}>
{tempColumns.map((column) => (
<div key={column.key} style={{ marginBottom: '8px' }}>
<input
type="checkbox"
id={`column-${column.key}`}
checked={column.show}
onChange={() => handleColumnToggle(column.key)}
/>
<label htmlFor={`column-${column.key}`} style={{ marginLeft: '8px' }}>
{column.label}
</label>
</div>
))}
</div>
</div>
</div>
<div className="list row">
<div className="col-md-12">
<button className="btn btn-default btn-sm float-right" onClick={handleCancel}> Cancel </button>
<button className="btn btn-primary btn-sm float-right" onClick={handleDone}> Done </button>
</div>
</div>
</div>
);
},
);
return (
<Dropdown
key={'manage-table'}
id="manage-table"
className="me-2"
show={showManageTableDropdown}
onToggle={() => setShowManageTableDropdown(!showManageTableDropdown)}
autoClose={false}
>
<Dropdown.Toggle variant="primary">
<Columns size={16} className="me-2"></Columns>Manage Table
</Dropdown.Toggle>
<Dropdown.Menu as={customManageTableMenu}/>
</Dropdown>
);
};
export default ManageTable;

View File

@ -0,0 +1,2 @@
export { default as ManageTable } from './ManageTable';
export { default as Export } from './Export';

View File

@ -30,4 +30,36 @@ export const CUSTOMER_TYPE_TEXT = {
transferred: 'Transferred',
discharged: 'Discharged',
deceased: 'Deceased'
}
export const CUSTOMER_JOIN_REASON = {
FRIEND_FAMILY_REFERRAL: 'friendFamilyReferral',
SOCIAL_WORKER_REFERRAL: 'socialWorkerReferral',
SOCIAL_MEDIA: 'socialMedia',
EVENT: 'event',
OTHER: 'other'
}
export const CUSTOMER_JOIN_REASON_TEXT = {
friendFamilyReferral: 'Friend or Family Referral',
socialWorkerReferral: 'Social Worker Referral',
socialMedia: 'Social Media (Instagram, etc)',
event: 'Event',
other: 'Other'
}
export const CUSTOMER_DISCHARGE_REASON = {
ABSENT_OVER_30: 'absentOver30',
TRANSFERRED_TO_ASSISTED_LIVING: 'TransferredToAssignedLiving',
DECEASED: 'Deceased',
EVENT: 'Event',
OTHER: 'Other'
}
export const CUSTOMER_DISCHARGE_REASON_TEXT = {
absentOver30: 'Absent for Over 30 Days',
TransferredToAssignedLiving: 'Transferred to Assisted Living',
Deceased: 'Deceased',
Event: 'Event',
Other: 'Other'
}

View File

@ -1 +1,2 @@
export * from "./constants";
export * from "./constants";
export * from "./components";

9
package-lock.json generated
View File

@ -23,6 +23,7 @@
"mongoose-unique-validator": "^3.0.0",
"multer": "^1.4.4",
"multer-gridfs-storage": "^5.0.2",
"node-cron": "^4.1.0",
"pizzip": "^3.1.7",
"xlsx-template": "^1.4.4"
}
@ -2531,6 +2532,14 @@
"node": ">= 0.6"
}
},
"node_modules/node-cron": {
"version": "4.1.0",
"resolved": "https://registry.npmjs.org/node-cron/-/node-cron-4.1.0.tgz",
"integrity": "sha512-OS+3ORu+h03/haS6Di8Qr7CrVs4YaKZZOynZwQpyPZDnR3tqRbwJmuP2gVR16JfhLgyNlloAV1VTrrWlRogCFA==",
"engines": {
"node": ">=6.0.0"
}
},
"node_modules/object-assign": {
"version": "4.1.1",
"resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz",

View File

@ -30,6 +30,7 @@
"mongoose-unique-validator": "^3.0.0",
"multer": "^1.4.4",
"multer-gridfs-storage": "^5.0.2",
"node-cron": "^4.1.0",
"pizzip": "^3.1.7",
"xlsx-template": "^1.4.4"
}

View File

@ -162,7 +162,13 @@ app.get('/medical/event-request/list', function (req,res) {
app.get('/medical/events/edit/:id', function (req,res) {
res.sendFile(path + "index.html");
});
app.get('events/create-from-request', function (req,res) {
app.get('/events/create-from-request', function (req,res) {
res.sendFile(path + "index.html");
});
app.get('/dashboard/dashboard', function (req,res) {
res.sendFile(path + "index.html");
});
app.get('/dashboard/admin-view', function (req,res) {
res.sendFile(path + "index.html");
});
app.get('/medical/events/:id', function (req,res) {
@ -195,6 +201,9 @@ app.get('/seating', function (req,res) {
app.get('/center-calendar', function (req,res) {
res.sendFile(path + "index.html");
});
app.get('/info-screen', function (req,res) {
res.sendFile(path + "index.html");
});
require("./app/routes/user.routes")(app);
require("./app/routes/auth.routes")(app);
require("./app/routes/employee.routes")(app);
@ -222,6 +231,10 @@ require("./app/routes/lunch.routes")(app);
require("./app/routes/vehicle-repair.route")(app);
require("./app/routes/seating.routes")(app);
require("./app/routes/label.routes")(app);
require("./app/routes/attendance-note.routes")(app);
require("./app/routes/carousel.routes")(app);
require("./app/scheduler/reminderScheduler");
// set port, listen for requests
const PORT = process.env.PORT || 8080;