// ==UserScript==
// @name NEMT for IOA
// @namespace http://conanluo.com/
// @version v.1.2.2
// @description Fixed New System
// @author Conan
// @match https://provider.nemtplatform.com/*
// @require https://code.jquery.com/jquery-2.2.4.min.js
// @ require https://stackpath.bootstrapcdn.com/bootstrap/3.4.1/js/bootstrap.min.js
// @ resource customCSS https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/css/bootstrap.min.css
// @icon https://www.google.com/s2/favicons?sz=64&domain=nemtplatform.com
// @grant none
// ==/UserScript==
(function($){
const popUrl = "https://work.conanluo.com/popRoute.html?arr=";
const routeColors=["000000","b51548","188225","f3f600","AAAA00","443ea1","b1f9d1","aa77aa","01b4fa","f685e6","e08536","dddddd"];
const wander = [
"Auyeung, Shum K",
"Chan, Suk Ching",
"Chen, Buoy Lan",
"Chiang, Kuo Hsiun",
"Chin, Ang Lin",
"Chu, Pak",
"Chung, Isaac I",
"Hui, Wai Chun Ngai",
"Hwang, Soon",
"Chow, James",
"Lee, Lap Chow",
"Lee, Ngan Chu",
"Lee, Sooja",
"Mosier, Jessica",
"Penry, Paul E",
"Wong, Gam Fee",
"Yuan, Fu Mei"
];
const pmSpecialTime = [1400, 1430, 1500];
const driverInfo = {
"Han Yang Zhou": 0,
"Jie Qian": 1,
// "Rong Tang":1,
"Mauricio Reina": 2,
"Jerry Higgins": 3,
"Mingzhan Li": 4,
"Fook Fung": 5,
"Walter Mejia": 6,
"Zhao Zhong Zheng": 7,
"Jabari Tyler": 8,
"Yingyang Chen": 9,
"Bert Reid": 10,
"Jerald Alejandro": 11,
"Wilson Ochoa": 12
}
let addrs;
//更改title,用地址栏后面的座位title
document.title=location.pathname.replace("/","")
function delay(time){
return new Promise((res,rej)=>{
setTimeout(_=>res(),time)
})
}
//**********隐藏alert 提示*******开始*******
let toastLeftBottom=setInterval(function(){
if($(".toast-top-right").length!=0){
$(".toast-top-right").removeClass("toast-top-right")//.addClass("toast-bottom-left")
clearInterval(toastLeftBottom)
}
},1000)
//**********隐藏alert 提示******结束********
//**********添加nav导航按钮******开始********
let topNav=`
<nav id='topNav' class="conan-added-on form-inline row" style="display: none;">
<button id='routePrt' class='btn btn-primary'>routePrt</button>
<button id='popAmRoute' class='btn btn-danger'>popAmRoute</button>
<button id='popPmRoute' class='btn btn-danger'>popPmRoute</button>
</nav>
`;
let addNavListener=setInterval(function(){
if($("app-side-nav").length!=0){
$("body").prepend(topNav);
//添加监听按钮,显示与隐藏nav,默认隐藏
$("app-side-nav div div div").eq(0).prepend($("app-side-nav div div div").eq(0).html()).find("a").eq(1).hide();//模仿side nav的头的标签建一个隐藏显示conan-added-on的功能
$(".space-y-1").append(`<textarea id="conan-added-on-info" class="ng-pristine ng-valid ng-touched conan-added-on" style="display:none" rows="10"></textarea>`)//添加一个textarea 框负责输入一些资料(例如地址对象,等)
$("app-side-nav a").eq(0).attr("href","javascript:").off("click").click(function(){
$(".conan-added-on").toggle("fast")
return false;
})
//添加am,pm 2个监听
$("#popAmRoute").click(function(){
const $table = $('table');
const tableData = parseTableToData($table);
const result = groupPatientsByDriver(tableData, driverInfo);
let url=popUrl+JSON.stringify(result)
window.open(url+`&rt= AM Route Sheet`)
console.log(JSON.stringify(result));
})
$("#popPmRoute").click(function(){
const $table = $('table');
const tableData = parseTableToData($table,"pm");
const result = groupPatientsByDriver(tableData, driverInfo);
let url=popUrl+JSON.stringify(result)
window.open(url+`&rt= PM Route Sheet`)
console.log(JSON.stringify(result));
})
$("#routePrt").click(function(){
let str=$("#conan-added-on-info").val();
if(str=="") return;
eval(addressFormat(str))
if($("#show_route_color").length==0){
let spanColor=""
for(let i=0;i<routeColors.length;i++){
let x=i;
if(x==11 || x==0) x="Check Which Route"
else x="Route "+x
spanColor+=`<span style="padding:5px 30px; background-color:#${routeColors[i]};${i==5||i==0?"color:white;":""}">${x}</span> `
}
$("main").prepend(`<div id="show_route_color"><div class='panel-primary'><h3 class='panel-heading' style="background:#0d6efd;color:white;margin-bottom:10px">The Color For The Route</h3>${spanColor}</div><br><hr><br></div></div>`)
}
//把route上色
$(".relative.h-full.ng-star-inserted").each(function(){
let $this=$(this);
let add1=addressFormat($this.find(".text-main-secondary").eq(1).text().split(" San Francisco")[0].split(" #")[0].trim())
let add2=addressFormat($this.find(".text-main-secondary").eq(2).text().split(" San Francisco")[0].split(" #")[0].trim())
//console.log($this.find(".text-main-secondary").length)
let tempColor=addrs[add1]?addrs[add1]:(addrs[add2]?addrs[add2]:0)
//console.log(tempColor)
$this.css("background","#"+routeColors[tempColor])
})
function addressFormat(addr){
return addr.replaceAll(" Street"," St").replaceAll(" Avenue"," Ave").replaceAll(" Boulevard"," Blvd")
}
})
//清除添加监听
clearInterval(addNavListener);
}
})
//**********添加nav导航按钮*******结束*******
//*****************处理am, pm Route 表 ********开始******
/**
* 解析表格数据,根据类型(上午或下午)筛选并格式化患者信息
* @param {jQuery} $table - jQuery 选择的表格元素
* @param {string} [type="am"] - 处理类型,"am" 为上午,"pm" 为下午
* @returns {Array} - 包含司机和患者信息的对象数组
*/
function parseTableToData($table, type = "am") {
const data = [];
// 遍历表格 tbody 中的每一行
$table.find('tbody tr').each(function() {
const $cells = $(this).find('td');
// Driver 在第 4 列(索引 3),取第 3 个 div 的文本
const driver = $cells.eq(3).find('div').eq(2).text().trim();
// Patient 在第 12 列(索引 11)
const rawPatient = $cells.eq(11).text().trim();
// 格式化 Patient 名字:首字母大写,其他小写
const patient = rawPatient.toLowerCase().replace(/(^|\s)\w/g, char => char.toUpperCase());
// Pickup Address 在第 14 列(索引 13),用于下午筛选和时间提取
const pickupAddress = $cells.eq(13).text().trim();
const pickupAddressTime = $cells.eq(13).find('div').last().text().trim().slice(-4);
// Dropoff Address 在第 15 列(索引 14),用于上午筛选
const dropoffAddress = $cells.eq(14).text().trim();
// Space Type 在第 8 列(索引 7)
const spaceType = $cells.eq(7).text().trim();
// 时间处理
const timeValue = parseInt(pickupAddressTime, 10);
if (isNaN(timeValue)) return; // 无效时间,跳过
// 检查 Patient 是否在 wander 数组中(忽略大小写)
const isWander = wander.some(w => w.toLowerCase() === rawPatient.toLowerCase());
let isValid = false;
let patientWithTags = patient;
if (type === "am") {
// 上午筛选条件:时间 < 10:30,Dropoff Address 包含 "3575 Geary",Space Type 不含 "DME"
const isBefore1030 = timeValue < 1030;
const is2ndRound = timeValue < 1000;
const hasGeary = dropoffAddress.toLowerCase().includes('3575 geary');
const noDME = !spaceType.toLowerCase().includes('dme');
if (driver && patient && isBefore1030 && hasGeary && noDME) {
isValid = true;
patientWithTags = patient +
(is2ndRound ? "" : " - @@" + pickupAddressTime) +
(!isWander ? "" : "__<b>Warning</b>");
}
} else if (type === "pm") {
// 下午筛选条件:时间 >= 15:30 或在 pmSpecialTime 中,Pickup Address 包含 "3575 Geary",Space Type 不含 "DME"
const isAfter1530OrSpecial = timeValue >= 1530 || pmSpecialTime.includes(timeValue);
const hasGeary = pickupAddress.toLowerCase().includes('3575 geary');
const noDME = !spaceType.toLowerCase().includes('dme');
if (driver && patient && isAfter1530OrSpecial && hasGeary && noDME) {
isValid = true;
const isSpecialTime = pmSpecialTime.includes(timeValue);
patientWithTags = patient +
(isSpecialTime ? " - @@" + pickupAddressTime : "") +
(!isWander ? "" : "__<b>Warning</b>");
}
}
if (isValid) {
data.push({ Driver: driver, Patient: patientWithTags });
}
});
return data;
}
/**
* 按司机分组患者,并按患者名字正序排序
* @param {Array} tableData - 解析后的表格数据,包含司机和患者信息
* @param {Object} driverInfo - 司机信息对象,键为司机名字,值为二维数组索引
* @returns {Array} - 按 driverInfo 索引分配的二维数组,每个子数组包含排序后的患者名字
*/
function groupPatientsByDriver(tableData, driverInfo) {
// Step 1: 按 Driver 分组 Patient
const patientGroups = {};
tableData.forEach(row => {
const driver = row.Driver;
const patient = row.Patient;
if (!patientGroups[driver]) {
patientGroups[driver] = [];
}
patientGroups[driver].push(patient);
});
// Step 2: 对每个司机的 Patient 数组按名字正序排序(忽略大小写)
for (const driver in patientGroups) {
patientGroups[driver].sort((a, b) => a.toLowerCase().localeCompare(b.toLowerCase()));
}
// Step 3: 创建二维数组
const maxIndex = Math.max(...Object.values(driverInfo), -1) + 1;
const result = Array(maxIndex).fill().map(() => []);
for (const [driver, index] of Object.entries(driverInfo)) {
result[index] = patientGroups[driver] || [];
}
return result;
}
//*****************处理am, pm Route 表 ********结束******
//*****************添加数据表功能,排序,隐藏等. ********开始******
// 使用setInterval定期检查新生成的表格
setInterval(function() {
// 只选择不含sortable类且不含owl-dt-calendar-table类的table
$("table").not(".sortable, .owl-dt-calendar-table").each(function() {
let $table = $(this);
// 为当前表格添加sortable类
$table.addClass("sortable");
// 在每个有内容的表头前添加箭头,后添加交叉(排除第一列)
$table.find("thead th").filter(function(index) {
return $(this).text().trim().length > 0 && index !== 0; // 排除第一列
}).each(function(index) {
let originalText = $(this).text().trim();
$(this).attr("style", "cursor: pointer;"); // 添加鼠标pointer样式
$(this).data("original-text", originalText); // 存储原始文本
$(this).data("col-index", index); // 存储原始列索引
$(this).html('<span></span>' + originalText + '<span style="margin-left: 5px; font-size: 12px; cursor: pointer;" class="hide-column">❌</span>');
}).click(function(e) {
// 防止点击交叉符号时触发排序
if ($(e.target).hasClass("hide-column")) {
let colIndex = $(this).data("col-index"); // 使用存储的原始列索引
let table = $(this).closest("table");
// 隐藏当前列(仅视觉隐藏,不删除)
table.find("tr").each(function() {
$(this).children().eq(colIndex+1).hide();
});
// 隐藏表头(仅视觉隐藏)
$(this).hide();
return; // 阻止排序
}
let table = $(this).closest("table");
let headerText = $(this).data("original-text"); // 使用存储的原始文本
let rows = table.find("tbody tr").get();
let isAsc = $(this).data("sort-dir") === "asc";
let colIndex = $(this).data("col-index"); // 使用存储的原始列索引
// 切换排序方向
$(this).data("sort-dir", isAsc ? "desc" : "asc");
// 重置所有未隐藏的表头(排除第一列和已隐藏的th)
$(this).closest("tr").find("th").filter(function(index) {
return index !== 0 && $(this).is(":visible"); // 只处理未隐藏的th
}).each(function() {
let text = $(this).data("original-text");
$(this).attr("style", "cursor: pointer;"); // 保持鼠标pointer样式
$(this).html('<span></span>' + text + '<span style="margin-left: 5px; font-size: 12px; cursor: pointer;" class="hide-column">❌</span>');
});
// 当前表头显示升序/降序箭头
$(this).html('<span>' + (isAsc ? "⬇️" : "⬆️") + '</span>' + headerText + '<span style="margin-left: 5px; font-size: 12px; cursor: pointer;" class="hide-column">❌</span>');
// 动态判断列类型
let isNumber = headerText.toLowerCase().includes("miles") || headerText.match(/\b(number|count|distance|size)\b/i);
let isDate = headerText.toLowerCase().includes("time") || headerText.toLowerCase().includes("date");
// 排序逻辑
rows.sort((b, a) => {
let valA = $(a).children("td").eq(colIndex).text().trim();
let valB = $(b).children("td").eq(colIndex).text().trim();
if (isNumber) {
valA = parseFloat(valA.replace(/[^0-9.-]+/g, "")) || 0;
valB = parseFloat(valB.replace(/[^0-9.-]+/g, "")) || 0;
} else if (isDate) {
valA = new Date(valA).getTime() || 0;
valB = new Date(valB).getTime() || 0;
} else {
valA = valA.toLowerCase();
valB = valB.toLowerCase();
}
return (valA < valB ? 1 : -1) * (isAsc ? -1 : 1);
});
// 移动行
let tbody = table.find("tbody");
rows.forEach(row => tbody.append(row));
});
// 添加恢复隐藏列的功能(在表格后添加一个恢复按钮)
if (!$table.next().hasClass("restore-columns")) {
$table.after('<button class="restore-columns" style="margin: 10px; padding: 5px 10px; cursor: pointer;">恢复隐藏列</button>');
$table.next(".restore-columns").click(function() {
let table = $(this).prev("table");
table.find("tr").each(function() {
$(this).children().show(); // 显示所有单元格
});
table.find("thead th").each(function(index) {
if (index === 0) return; // 跳过第一列
let text = $(this).data("original-text");
$(this).attr("style", "cursor: pointer;");
$(this).html('<span></span>' + text + '<span style="margin-left: 5px; font-size: 12px; cursor: pointer;" class="hide-column">❌</span>');
$(this).show(); // 显示所有表头(除第一列外)
});
table.find("th").data("sort-dir", null); // 重置排序状态
});
}
});
}, 1000); // 每1秒检查一次
//*****************添加数据表功能,排序,隐藏等. ********结束******
/* 修改药用的....
function updateMed(){
(function() {
'use strict';
const waitForElement = (selector, callback, maxAttempts = 20, interval = 500) => {
let attempts = 0;
const tryFind = () => {
const element = document.querySelector(selector);
if (element) {
callback(element);
} else if (attempts < maxAttempts) {
attempts++;
setTimeout(tryFind, interval);
}
};
tryFind();
};
waitForElement('#preScheduleTime', (input) => {
input.value = '05/19/2025 1700';
input.dispatchEvent(new Event('input', { bubbles: true }));
input.dispatchEvent(new Event('change', { bubbles: true }));
});
waitForElement('#preAppointmentTime', (input) => {
input.value = '05/19/2025 1730';
input.dispatchEvent(new Event('input', { bubbles: true }));
input.dispatchEvent(new Event('change', { bubbles: true }));
});
waitForElement('#displayName', (input) => {
const newValue = input.value + '💊';
input.value = newValue;
input.dispatchEvent(new Event('input', { bubbles: true }));
input.dispatchEvent(new Event('change', { bubbles: true }));
});
})();
// 获取指定 div 内的 button 并触发点击事件
$('div.border-t.border-main-base.flex.flex-wrap.justify-end.py-2.px-4.bg-container-base.gap-2 button').click();
}
/////监听每1秒执行一次
updating=setInterval(function(){
if($(".flex-1.flex.flex-col.bg-main-base-100.overflow-hidden").length>0&&$(".flex-1.flex.flex-col.bg-main-base-100.overflow-hidden.edited").length==0){
$(".flex-1.flex.flex-col.bg-main-base-100.overflow-hidden").addClass("edited")
updateMed();
console.log("Update:"+$("#displayName").val())
}
},1000)
//////////清除监听
clearInterval(updating)
*/
})($);