All update about transportantion and style
This commit is contained in:
parent
e4654be2b9
commit
98d86c0f70
BIN
app/.DS_Store
vendored
BIN
app/.DS_Store
vendored
Binary file not shown.
131
app/controllers/attendance-note.controller.js
Normal file
131
app/controllers/attendance-note.controller.js
Normal 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 || "")
|
||||
});
|
||||
});
|
||||
};
|
||||
@ -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
|
||||
|
||||
123
app/controllers/carousel.controller.js
Normal file
123
app/controllers/carousel.controller.js
Normal 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 || "")
|
||||
});
|
||||
});
|
||||
};
|
||||
@ -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
|
||||
|
||||
@ -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."
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
@ -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;
|
||||
}
|
||||
|
||||
|
||||
40
app/models/attendance-note.model.js
Normal file
40
app/models/attendance-note.model.js
Normal 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;
|
||||
};
|
||||
@ -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 }
|
||||
);
|
||||
|
||||
29
app/models/carousel.model.js
Normal file
29
app/models/carousel.model.js
Normal 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;
|
||||
};
|
||||
@ -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 }
|
||||
);
|
||||
|
||||
@ -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;
|
||||
29
app/routes/attendance-note.routes.js
Normal file
29
app/routes/attendance-note.routes.js
Normal 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);
|
||||
};
|
||||
29
app/routes/carousel.routes.js
Normal file
29
app/routes/carousel.routes.js
Normal 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);
|
||||
};
|
||||
@ -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);
|
||||
|
||||
11
app/scheduler/reminderScheduler.js
Normal file
11
app/scheduler/reminderScheduler.js
Normal 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"
|
||||
})
|
||||
212
app/services/reminderService.js
Normal file
212
app/services/reminderService.js
Normal 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();
|
||||
@ -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"
|
||||
]
|
||||
}
|
||||
@ -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
1
app/views/static/css/main.2c06dda5.css.map
Normal file
1
app/views/static/css/main.2c06dda5.css.map
Normal file
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
3
app/views/static/js/main.cddce86b.js
Normal file
3
app/views/static/js/main.cddce86b.js
Normal file
File diff suppressed because one or more lines are too long
@ -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
|
||||
1
app/views/static/js/main.cddce86b.js.map
Normal file
1
app/views/static/js/main.cddce86b.js.map
Normal file
File diff suppressed because one or more lines are too long
BIN
client/.DS_Store
vendored
BIN
client/.DS_Store
vendored
Binary file not shown.
57
client/package-lock.json
generated
57
client/package-lock.json
generated
@ -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",
|
||||
|
||||
@ -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",
|
||||
|
||||
@ -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/
|
||||
|
||||
@ -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;
|
||||
}
|
||||
@ -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>
|
||||
|
||||
124
client/src/components/admin-view/AdminView.css
Normal file
124
client/src/components/admin-view/AdminView.css
Normal 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;
|
||||
}
|
||||
1388
client/src/components/admin-view/AdminView.js
Normal file
1388
client/src/components/admin-view/AdminView.js
Normal file
File diff suppressed because it is too large
Load Diff
@ -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>
|
||||
|
||||
@ -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">
|
||||
|
||||
@ -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>
|
||||
|
||||
@ -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>
|
||||
|
||||
@ -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">
|
||||
|
||||
53
client/src/components/dashboard/Dashboard.css
Normal file
53
client/src/components/dashboard/Dashboard.css
Normal 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;
|
||||
}
|
||||
317
client/src/components/dashboard/Dashboard.js
Normal file
317
client/src/components/dashboard/Dashboard.js
Normal 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;
|
||||
428
client/src/components/dashboard/DashboardCustomersList.js
Normal file
428
client/src/components/dashboard/DashboardCustomersList.js
Normal 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;
|
||||
@ -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>
|
||||
|
||||
@ -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>
|
||||
|
||||
@ -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>
|
||||
|
||||
@ -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>
|
||||
|
||||
|
||||
@ -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>}
|
||||
|
||||
1424
client/src/components/info-screen/InfoScreen.js
Normal file
1424
client/src/components/info-screen/InfoScreen.js
Normal file
File diff suppressed because it is too large
Load Diff
@ -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>
|
||||
|
||||
@ -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>
|
||||
|
||||
@ -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>
|
||||
|
||||
@ -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>
|
||||
|
||||
@ -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">
|
||||
|
||||
@ -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>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
@ -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>
|
||||
|
||||
@ -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 && (
|
||||
|
||||
@ -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>
|
||||
|
||||
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@ -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()}}}>
|
||||
|
||||
@ -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>
|
||||
|
||||
@ -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>
|
||||
|
||||
@ -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));
|
||||
}
|
||||
|
||||
43
client/src/services/AttendanceNoteService.js
Normal file
43
client/src/services/AttendanceNoteService.js
Normal 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
|
||||
};
|
||||
43
client/src/services/CarouselService.js
Normal file
43
client/src/services/CarouselService.js
Normal 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
|
||||
};
|
||||
@ -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
|
||||
};
|
||||
|
||||
@ -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);
|
||||
};
|
||||
178
client/src/shared/components/Export.js
Normal file
178
client/src/shared/components/Export.js
Normal 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;
|
||||
83
client/src/shared/components/ManageTable.js
Normal file
83
client/src/shared/components/ManageTable.js
Normal 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;
|
||||
2
client/src/shared/components/index.js
Normal file
2
client/src/shared/components/index.js
Normal file
@ -0,0 +1,2 @@
|
||||
export { default as ManageTable } from './ManageTable';
|
||||
export { default as Export } from './Export';
|
||||
@ -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'
|
||||
}
|
||||
@ -1 +1,2 @@
|
||||
export * from "./constants";
|
||||
export * from "./constants";
|
||||
export * from "./components";
|
||||
9
package-lock.json
generated
9
package-lock.json
generated
@ -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",
|
||||
|
||||
@ -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"
|
||||
}
|
||||
|
||||
15
server.js
15
server.js
@ -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;
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user