Greasy Fork is available in English.
Library - TOT Batch module for Taskonator with presets and Vue.js
Tätä skriptiä ei tulisi asentaa suoraan. Se on kirjasto muita skriptejä varten sisällytettäväksi metadirektiivillä // @require https://update.greasyfork.org/scripts/575279/1807562/Taskonator%20-%20TOT%20Batch.js.
// ==UserScript==
// @name Taskonator - TOT Batch
// @name:pl Taskonator - TOT Batch
// @namespace http://tampermonkey.net/
// @version 4.0.0
// @description Library - TOT Batch module for Taskonator with presets and Vue.js
// @description:pl Biblioteka - Modul batch TOT z presetami dla Taskonatora
// @author @sulkpiot
// @license MIT
// ==/UserScript==
/* ================================================================
TASKONATOR MODULE: TOT Batch v4.0
Tabela batch TOT na stronach /employee/*
Wymaga Vue.js 2.x (ładowanego przez Loader @require)
================================================================ */
(function () {
'use strict';
function waitForCore(cb) {
if (window.Taskonator) return cb(window.Taskonator);
let tries = 0;
const iv = setInterval(() => {
tries++;
if (window.Taskonator) { clearInterval(iv); cb(window.Taskonator); }
else if (tries > 60) { clearInterval(iv); console.error('[TOTBatch] Core not found'); }
}, 250);
}
waitForCore(function (Core) {
Core.ModuleRegistry.register('TOTBatch', initTOTBatch);
});
function initTOTBatch(Core) {
const DEFAULT_PRESETS = [
{ name: "V-Returns Ship", processLabel: "V-Returns Support", functionName: "V-Returns Ship", color: "#3b82f6" },
{ name: "Lead/PA", processLabel: "Transfer Out Lead/PA", functionName: "Transfer Out Lead/PA", color: "#8b5cf6" },
{ name: "Janitorial", processLabel: "Facility", functionName: "Facility Janitorial", color: "#10b981" },
{ name: "UIS Waterspider", processLabel: "Container Move", functionName: "UIS Flat Waterspider", color: "#f59e0b" },
{ name: "Trans Out Overflow", processLabel: "Transfer Out", functionName: "Trans Out Overflow", color: "#ef4444" },
{ name: "Dock Crew", processLabel: "Transfer Out Dock", functionName: "TransferOut DockCrew", color: "#ec4899" }
];
// ─── Inject CSS ──────────────────────────────────────────
const stEl = document.createElement('style');
stEl.textContent = `[v-cloak]{display:none}.tot-message{color:#2f855a;margin:5px 0;font-weight:bold;padding:5px 10px;border-radius:3px;background-color:#f0fff4;border:1px solid #c6f6d5}.tot-message2{color:#2a4365;margin:5px 0;font-weight:bold;font-size:14px;padding:5px 10px;background-color:#ebf8ff;border-radius:3px;border:1px solid #bee3f8}.preset-container{margin:8px 0;display:flex;flex-wrap:wrap;gap:5px;align-items:center;justify-content:center}.preset-btn{padding:6px 14px;border:1px solid #cbd5e0;border-radius:5px;background-color:#ebf8ff;color:#2a4365;font-weight:bold;font-size:13px;cursor:pointer;transition:all .15s ease}.preset-btn:hover{background-color:#bee3f8;border-color:#90cdf4;transform:translateY(-1px)}.preset-btn.active{color:white;box-shadow:0 2px 4px rgba(0,0,0,0.2)}.preset-label{font-size:12px;color:#718096;font-weight:bold;margin-right:5px}.save-preset-btn,.settings-btn{padding:6px 14px;border:2px dashed #a0aec0;border-radius:5px;background-color:#f7fafc;color:#718096;font-size:13px;cursor:pointer;transition:all .15s ease}.save-preset-btn:hover{border-color:#68d391;background-color:#f0fff4;color:#2f855a}.settings-btn{border-style:solid;border-color:#a0aec0;font-size:15px;padding:4px 10px}.settings-btn:hover{background-color:#edf2f7}.settings-overlay{position:fixed;top:0;left:0;right:0;bottom:0;background:rgba(0,0,0,0.5);z-index:99998;display:flex;align-items:center;justify-content:center}.settings-panel{background:white;border-radius:12px;box-shadow:0 20px 60px rgba(0,0,0,0.3);width:680px;max-height:85vh;overflow-y:auto;z-index:99999}.settings-header{background:#2d3748;color:white;padding:16px 24px;border-radius:12px 12px 0 0;display:flex;justify-content:space-between;align-items:center;position:sticky;top:0;z-index:1}.settings-header h3{margin:0;font-size:16px}.settings-close{background:none;border:none;color:white;font-size:22px;cursor:pointer;opacity:.7}.settings-close:hover{opacity:1}.settings-body{padding:20px 24px}.settings-section{margin-bottom:20px}.settings-section h4{margin:0 0 10px;font-size:13px;text-transform:uppercase;color:#718096;letter-spacing:.5px}.preset-row{display:flex;align-items:center;gap:8px;padding:8px 10px;border:1px solid #e2e8f0;border-radius:6px;margin-bottom:6px;background:#fafbfc}.preset-row input[type="text"]{padding:5px 8px;border:1px solid #cbd5e0;border-radius:4px;font-size:12px}.preset-row input[type="color"]{width:32px;height:28px;border:1px solid #cbd5e0;border-radius:4px;cursor:pointer}.preset-row .field-name{width:110px;font-weight:bold}.preset-row .field-proc{width:140px}.preset-row .field-func{width:150px}.preset-row-label{font-size:10px;color:#a0aec0;display:block}.move-btn,.del-btn{border:none;border-radius:4px;cursor:pointer;font-size:14px;width:28px;height:28px;display:flex;align-items:center;justify-content:center}.move-btn{background:#edf2f7;color:#4a5568}.move-btn:hover{background:#e2e8f0}.move-btn:disabled{opacity:.3}.del-btn{background:#fff5f5;color:#e53e3e}.del-btn:hover{background:#fed7d7}.settings-actions{display:flex;flex-wrap:wrap;gap:8px;padding:16px 24px;border-top:1px solid #e2e8f0;background:#f7fafc;border-radius:0 0 12px 12px}.action-btn{padding:8px 16px;border:1px solid #cbd5e0;border-radius:6px;font-size:12px;font-weight:bold;cursor:pointer;background:white;color:#4a5568}.action-btn:hover{background:#edf2f7}.action-btn.primary{background:#3b82f6;color:white;border-color:#2563eb}.action-btn.primary:hover{background:#2563eb}.action-btn.danger{color:#e53e3e;border-color:#feb2b2}.action-btn.danger:hover{background:#fff5f5}.action-spacer{flex-grow:1}.import-area{width:100%;height:80px;font-family:monospace;font-size:11px;padding:8px;border:1px solid #cbd5e0;border-radius:6px;margin-top:8px;resize:vertical;box-sizing:border-box}.import-area:focus{outline:none;border-color:#3b82f6}.preset-count{font-size:12px;color:#a0aec0;margin-left:8px}`;
document.head.appendChild(stEl);
// ─── Parse editable segments ─────────────────────────────
var editables = document.querySelectorAll('.time-segment.editable');
var ea = [];
for (var i = 0; i < editables.length; i++) {
var parent = editables[i].parentNode;
if (parent && parent.hasAttribute("onclick") && parent.attributes.onclick.nodeValue.includes("firePopup")) {
ea.push(editables[i]);
}
}
var parseFire = function (args) {
return eval(args.replace('firePopup(', '[').replace(');', ']').replace(/\t/g, '').replace(/\s/g, ''));
};
var totParams = ea.map(ed => parseFire(ed.parentNode.attributes.onclick.nodeValue));
window.globalThat = { totParams };
function sublist(code, cb) {
jediClient.getAllLaborFunctionsForLaborProcessId({
ServiceName: 'FCLMJobEntryDomainInformationService',
data: { laborProcessId: code },
Method: 'GetAllLaborFunctionsForLaborProcessId',
success: cb
});
}
window.globalThat.sublist = sublist;
// ─── Mount Vue Instance ──────────────────────────────────
var contentPanel = document.getElementById('content-panel') || document.body;
var root = document.createElement('div');
root.id = 'tot-batch-root';
contentPanel.appendChild(root);
window.vueInstance = new Vue({
el: '#tot-batch-root',
created() { this.updateTotalDuration(); this.loadPresets(); },
data: {
totParams: totParams.map(p => { let np = [...p]; np[6] = false; return np; }),
processOptions: [], selectedLaborProcess: -1,
functionOptions: [{ laborFunctionId: -1, laborFunctionName: 'Choose Function' }],
selectedLaborFunction: -1, message: '', message2: '', submittedlist: [],
now: Date.now(), lastCodedProcess: '', lastCodedFunction: '',
loadLastCoded: false, presets: [], activePreset: null,
showSettings: false, showImport: false, importText: '', editPresets: []
},
mounted() { this.$nextTick(() => { this.updateTotalDuration(); }); },
watch: {
selectedLaborProcess: { handler() { this.newSubList(); }, immediate: true },
selectedLaborFunction: { handler() { const m2 = this.message2; this.$nextTick(() => { this.message2 = m2; }); } },
totParams: { handler() { this.$nextTick(() => { this.updateTotalDuration(); }); }, deep: true, immediate: true }
},
computed: {
displayMessage() { return this.message || ''; },
displayMessage2() { return this.message2 || ''; },
presetCount() { return this.presets.length; }
},
methods: {
formatTaskName(n) { return n || ''; },
safeSet(o, i, v) { if (o && typeof i !== 'undefined') this.$set(o, i, v); },
formatDateTime(s) { if (!s || s.length === 0) return "(current)"; return s.substring(0, 10) + ' ' + s.substring(10); },
handleCheckboxChange(r, c) { this.safeSet(this.totParams[r], 6, c); this.$nextTick(() => { this.updateTotalDuration(); }); },
newSubList() {
if (window.location.pathname.includes("ppaTimeDetails")) {
if (typeof processes !== 'undefined') {
var sl = this.processOptions.filter(x => x.value == this.selectedLaborProcess)[0];
if (sl && processes[sl.label]) {
var fl = processes[sl.label].attributes.job_role.sort();
this.functionOptions = [this.functionOptions[0]];
fl.forEach(x => this.functionOptions.push({ laborFunctionId: x, laborFunctionName: x }));
if (this.loadLastCoded) { var ff = this.functionOptions.find(o => o.laborFunctionName === this.lastCodedFunction); if (ff) this.selectedLaborFunction = ff.laborFunctionId.toString(); this.loadLastCoded = false; }
}
}
} else {
this.seekFunctions(false);
return sublist(this.selectedLaborProcess, (result) => {
if (result && result.laborFunctions) { this.functionOptions = result.laborFunctions.sort((a, b) => (a.laborFunctionName > b.laborFunctionName) ? 1 : -1); this.seekFunctions(true); }
});
}
},
updateTotalDuration() { var total = 0; this.totParams.forEach(bar => { if (bar[6] === true || bar[6] === 'true' || bar[6] === 1) total += this.getDuration(bar[1], bar[3]); }); this.$set(this, 'message2', 'Total selected: ' + (Math.round(total * 10) / 10) + ' minutes'); },
fireTots() { window.localStorage.setItem("totProcess", this.selectedLaborProcess); window.localStorage.setItem("totFunction", this.selectedLaborFunction); var ts = this.totParams.map((t, i) => ({ ...t, index: i })).filter(t => t[6] === true || t[6] === 'true'); if (this.selectedLaborProcess > 0 && (this.selectedLaborFunction.length == 0 || this.selectedLaborFunction > 0 || (window.location.pathname.includes("ppa") && this.selectedLaborFunction.length > 0))) { this.submitTot(ts); this.message = "Submitting batch..."; } else { this.message = "Check process and function options before submitting!"; } },
submitTot(tots) {
const cp = this.selectedLaborProcess, cf = this.selectedLaborFunction;
const gv = (id, d) => { const el = document.getElementById(id); return el ? el.value : d; };
const fd = { empId: gv("employeeId", ""), whId: gv("warehouseId", ""), startDate: gv("startDate", ""), startHour: gv("startHour", ""), startMinute: gv("startMinute", ""), endDate: gv("endDate", ""), endHour: gv("endHour", ""), endMinute: gv("endMinute", "") };
tots.forEach(tot => {
var enc = encodeURIComponent; const pp = tot[4] || '', pr = tot[5] || '';
var line = "startDate=" + enc(fd.startDate) + "&startHour=" + enc(fd.startHour) + "&startMinute=" + enc(fd.startMinute) + "&endDate=" + enc(fd.endDate) + "&endHour=" + enc(fd.endHour) + "&endMinute=" + enc(fd.endMinute) + "&employeeId=" + enc(fd.empId) + "&warehouseId=" + enc(fd.whId) + "&newLaborProcessId=" + enc(cp) + "&newLaborFunctionId=" + enc(cf) + "&laborFuncStartTime=" + enc(tot[1]) + "&laborFuncEndTime=" + enc(tot[3]) + "&previousLaborProcess=" + enc(pp) + "&previousJobRole=" + enc(pr.replace(/ /g, '+'));
if (window.location.pathname.includes("ppa")) { line = line.replace("warehouseId", "oldWarehouseId"); var loc = line.search("&newLaborProcessId"); line = line.slice(0, loc) + "&warehouseId=" + enc(fd.whId) + line.slice(loc); loc = line.search("&newLaborFunctionId"); line = line.slice(0, loc) + "&newJobRole=" + cf.replace(/ /g, '+'); }
var form = document.querySelector('form'); var actionUrl = form ? form.action : window.location.href;
$.ajax({ url: actionUrl, type: 'POST', data: line, success: (response) => { const index = tot.index !== undefined ? tot.index : this.totParams.indexOf(tot); this.processResponse(response, index, cp, cf); }, error: (x, s, error) => { this.message = "Error: " + error; } });
});
},
loadLastCodedBar() {
try {
var es = Array.from(document.querySelectorAll('.time-segment.editable'));
if (es.length > 0) {
var segs = es.slice(0, -1); var edited = segs.filter(s => s.closest('.function-seg.edited') !== null);
if (edited.length > 0) {
var ts = edited[edited.length - 1];
if (ts && ts.parentNode) {
const oa = ts.parentNode.getAttribute('onclick');
if (oa) {
const params = this.parseFire(oa);
if (params && params.length >= 6) {
const nlcp = params[4].replace(/\s+/g, ''), nlcf = params[5].replace(/\s+/g, '');
const po = this.processOptions.find(p => p.label.replace(/\s+/g, '') === nlcp);
if (po) {
this.selectedLaborProcess = po.value;
setTimeout(() => { const fo = this.functionOptions.find(f => f.laborFunctionName.replace(/\s+/g, '') === nlcf); if (fo) { this.selectedLaborFunction = fo.laborFunctionId; this.message = 'Loaded: ' + po.label + ' - ' + fo.laborFunctionName; } else { this.message = "Found process but couldn't match function"; } }, 500);
} else { this.message = "Couldn't match process name"; }
}
}
}
} else { this.message = "No previously edited tasks found"; }
}
} catch (e) { this.message = "Error loading previous task"; }
},
processResponse(response, totIndex, procId, funcId) {
try {
if (totIndex === undefined || !this.totParams[totIndex]) totIndex = this.totParams.findIndex(t => t[6] === true || t[6] === 'true');
if (totIndex === -1 || totIndex === undefined) { this.message = "Task updated successfully"; return; }
var po = this.processOptions.find(x => x.value == procId), fo = this.functionOptions.find(x => x.laborFunctionId == Number(funcId));
if (po && fo) {
this.$set(this.totParams[totIndex], 4, po.label); this.$set(this.totParams[totIndex], 5, fo.laborFunctionName);
if (!this.submittedlist.includes(totIndex)) this.submittedlist.push(totIndex);
const m2 = this.message2; this.$nextTick(() => { this.message2 = m2; });
setTimeout(() => { const cb = document.querySelector('.ui-dialog-titlebar-close'); if (cb) cb.click(); }, 1000);
this.message = "Task updated successfully"; setTimeout(() => { window.location.reload(); }, 2000);
} else { this.message = "Task updated successfully"; }
} catch (e) { this.message = "Task updated successfully"; setTimeout(() => { window.location.reload(); }, 2000); }
},
seekFunctions(isDone) { if (!isDone) { this.functionOptions = [{ laborFunctionId: -1, laborFunctionName: '-= Getting New Functions =-' }]; this.message = "Getting Functions..."; } else { this.message = ""; var sf = null; if (!this.loadLastCoded) { if (this.selectedLaborProcess == window.localStorage.getItem("totProcess")) sf = window.localStorage.getItem("totFunction"); } else { var fo = this.functionOptions.find(o => o.laborFunctionName === this.lastCodedFunction); if (fo) sf = fo.laborFunctionId.toString(); } if (sf != null) this.selectedLaborFunction = sf; this.loadLastCoded = false; } },
getDuration(d1, d2) { d1 = new Date(d1); d2 = d2.length > 0 ? new Date(d2) : new Date(this.now); return Math.abs((d2 - d1) / 60000); },
allTotDuration() { var total = 0; this.totParams.forEach(bar => (total += this.getDuration(bar[1], bar[3]))); return total > 0 ? " " + (Math.round(total * 10) / 10) + "m in " + this.totParams.length + " bars:" : " No editable time."; },
selectAllBars() { this.totParams.forEach((t, i) => { this.$set(this.totParams[i], 6, true); }); this.$nextTick(() => { this.updateTotalDuration(); }); },
parseFire(args) { return eval(args.replace('firePopup(', '[').replace(');', ']').replace(/\t/g, '').replace(/\s/g, '')); },
loadPresets() { var saved = JSON.parse(window.localStorage.getItem("totPresets") || "null"); this.presets = saved || JSON.parse(JSON.stringify(DEFAULT_PRESETS)); },
savePresets() { window.localStorage.setItem("totPresets", JSON.stringify(this.presets)); },
applyPreset(preset) {
this.activePreset = preset.name; this.message = "\u23f3 Loading: " + preset.name + "...";
var np = preset.processLabel.replace(/\s+/g, '').toLowerCase();
var po = this.processOptions.find(p => p.label.replace(/\s+/g, '').toLowerCase() === np);
if (!po) { this.message = "\u274c Process not found: " + preset.processLabel; this.activePreset = null; return; }
this.selectedLaborProcess = po.value;
var wf = (retries) => { retries = retries || 0; setTimeout(() => { var nf = preset.functionName.replace(/\s+/g, '').toLowerCase(); var fo = this.functionOptions.find(f => f.laborFunctionName.replace(/\s+/g, '').toLowerCase() === nf); if (fo) { this.selectedLaborFunction = fo.laborFunctionId; this.message = "\u2705 " + preset.name + " ready!"; } else if (retries < 15) { wf(retries + 1); } else { this.message = "\u26a0\ufe0f Function '" + preset.functionName + "' not found."; } }, 300); }; wf(0);
},
saveCurrentAsPreset() { if (this.selectedLaborProcess == -1 || this.selectedLaborFunction == -1) { this.message = "Select a process and function first!"; return; } var po = this.processOptions.find(x => x.value == this.selectedLaborProcess); var fo = this.functionOptions.find(x => x.laborFunctionId == this.selectedLaborFunction); if (!po || !fo) { this.message = "Could not identify current selection."; return; } var exists = this.presets.find(p => p.processLabel.replace(/\s+/g, '').toLowerCase() === po.label.replace(/\s+/g, '').toLowerCase() && p.functionName.replace(/\s+/g, '').toLowerCase() === fo.laborFunctionName.replace(/\s+/g, '').toLowerCase()); if (exists) { this.message = "Preset '" + exists.name + "' already exists!"; return; } var name = prompt("Enter button name:", fo.laborFunctionName); if (!name) return; var colors = ["#3b82f6", "#10b981", "#f59e0b", "#ef4444", "#8b5cf6", "#ec4899", "#06b6d4", "#84cc16"]; this.presets.push({ name, processLabel: po.label, functionName: fo.laborFunctionName, color: colors[this.presets.length % colors.length] }); this.savePresets(); this.message = "\u2705 Preset '" + name + "' saved!"; },
openSettings() { this.editPresets = JSON.parse(JSON.stringify(this.presets)); this.showImport = false; this.importText = ""; this.showSettings = true; },
closeSettings() { this.showSettings = false; },
saveSettings() { this.presets = JSON.parse(JSON.stringify(this.editPresets)); this.savePresets(); this.showSettings = false; this.message = "\u2705 Settings saved!"; },
addEmptyPreset() { var colors = ["#3b82f6", "#10b981", "#f59e0b", "#ef4444", "#8b5cf6", "#ec4899", "#06b6d4", "#84cc16"]; this.editPresets.push({ name: "New Preset", processLabel: "", functionName: "", color: colors[this.editPresets.length % colors.length] }); },
removeEditPreset(index) { this.editPresets.splice(index, 1); },
movePreset(index, direction) { var ni = index + direction; if (ni < 0 || ni >= this.editPresets.length) return; var temp = this.editPresets.splice(index, 1)[0]; this.editPresets.splice(ni, 0, temp); },
resetToDefaults() { if (!confirm("Reset all presets to defaults?")) return; this.editPresets = JSON.parse(JSON.stringify(DEFAULT_PRESETS)); },
exportPresets() { this.importText = JSON.stringify(this.editPresets, null, 2); this.showImport = true; this.$nextTick(() => { var ta = document.querySelector('.import-area'); if (ta) { ta.select(); document.execCommand('copy'); } }); this.message = "\ud83d\udccb Presets copied!"; },
toggleImport() { this.showImport = !this.showImport; if (this.showImport) this.importText = ""; },
doImport() { try { var parsed = JSON.parse(this.importText); if (!Array.isArray(parsed)) throw new Error("Not an array"); parsed.forEach(p => { if (!p.name || !p.processLabel || !p.functionName) throw new Error("Missing fields"); if (!p.color) p.color = "#3b82f6"; }); this.editPresets = parsed; this.showImport = false; this.message = "\u2705 Imported " + parsed.length + " presets."; } catch (e) { alert("Invalid JSON: " + e.message); } }
},
template: `<div v-cloak><div><h3>Hi!{{allTotDuration()}}</h3><table align="center"><tbody><tr v-for="(tot,totRow) in totParams" :key="totRow"><td v-for="(attr,index) of tot" :key="index"><template v-if="index==6"><input type="checkbox" :checked="Boolean(tot[index])" @change="handleCheckboxChange(totRow,$event.target.checked)"></template><template v-else-if="index>6"></template><template v-else-if="index==0||index==2">{{formatDateTime(tot[index])}}</template><template v-else-if="index==1"></template><template v-else-if="index==3">{{Math.round(getDuration(tot[1],tot[3])).toString()+"m"}}</template><template v-else-if="index==4||index==5">{{formatTaskName(attr)}}</template><template v-else>{{attr}}</template></td></tr></tbody></table><div class="preset-container"><span class="preset-label">\u26a1 Quick Tasks:</span><button v-for="(preset,pIdx) in presets" :key="'p-'+pIdx" class="preset-btn" :class="{active:activePreset===preset.name}" :style="activePreset===preset.name?'background-color:'+preset.color+';border-color:'+preset.color+';color:white;':'border-left:3px solid '+preset.color" :title="preset.processLabel+' \\u2192 '+preset.functionName" @click="applyPreset(preset)">{{preset.name}}</button><button class="save-preset-btn" @click="saveCurrentAsPreset()" title="Save current selection as preset">+ Save Current</button><button class="settings-btn" @click="openSettings()" title="Customize presets">\u2699\ufe0f</button></div><div><select v-model="selectedLaborProcess"><option v-for="process in processOptions" :value="process.value" :key="process.value">{{process.label}}</option></select><select v-model="selectedLaborFunction"><option v-for="func in functionOptions" :value="func.laborFunctionId" :key="func.laborFunctionId">{{func.laborFunctionName}}</option></select></div><button @click="selectAllBars()">Select All</button><button @click="loadLastCodedBar()">Prev. Task</button><button @click="fireTots()">Submit</button><div class="tot-summary-section"><div class="tot-message" v-if="displayMessage">{{displayMessage}}</div><div class="tot-message2" v-if="displayMessage2">{{displayMessage2}}</div></div><div class="settings-overlay" v-if="showSettings" @click.self="closeSettings()"><div class="settings-panel"><div class="settings-header"><h3>\u2699\ufe0f Preset Settings <span class="preset-count">({{editPresets.length}} presets)</span></h3><button class="settings-close" @click="closeSettings()">\u2715</button></div><div class="settings-body"><div class="settings-section"><h4>Your Presets</h4><div v-if="editPresets.length===0" style="color:#a0aec0;text-align:center;padding:20px;">No presets yet.</div><div v-for="(ep,epIdx) in editPresets" :key="'ep-'+epIdx" class="preset-row"><input type="color" v-model="ep.color" title="Button color"><div><span class="preset-row-label">Name</span><input type="text" v-model="ep.name" class="field-name" placeholder="Button name"></div><div><span class="preset-row-label">Process</span><input type="text" v-model="ep.processLabel" class="field-proc" placeholder="Process name"></div><div><span class="preset-row-label">Function</span><input type="text" v-model="ep.functionName" class="field-func" placeholder="Function name"></div><button class="move-btn" :disabled="epIdx===0" @click="movePreset(epIdx,-1)" title="Move up">\u25b2</button><button class="move-btn" :disabled="epIdx===editPresets.length-1" @click="movePreset(epIdx,1)" title="Move down">\u25bc</button><button class="del-btn" @click="removeEditPreset(epIdx)" title="Delete">\ud83d\uddd1</button></div></div><div class="settings-section" v-if="showImport"><h4>\ud83d\udccb Import / Export JSON</h4><textarea class="import-area" v-model="importText" placeholder="Paste JSON here..."></textarea><div style="margin-top:8px;"><button class="action-btn primary" @click="doImport()">\ud83d\udce5 Import</button></div></div></div><div class="settings-actions"><button class="action-btn success" @click="addEmptyPreset()">+ Add Preset</button><button class="action-btn" @click="exportPresets()">\ud83d\udce4 Export</button><button class="action-btn" @click="toggleImport()">\ud83d\udce5 Import</button><button class="action-btn danger" @click="resetToDefaults()">\ud83d\udd04 Reset</button><span class="action-spacer"></span><button class="action-btn" @click="closeSettings()">Cancel</button><button class="action-btn primary" @click="saveSettings()">\ud83d\udcbe Save</button></div></div></div></div></div>`
}).$mount(root);
// ─── Populate process options from existing select ───────
var processSelect = document.getElementById('newLaborProcessId') || document.querySelector('select[name="newLaborProcessId"]') || document.querySelector('select');
if (processSelect && processSelect.options) {
window.vueInstance.processOptions = Array.from(processSelect.options).map(o => ({ value: o.value, label: o.text }));
} else {
window.vueInstance.processOptions = [{ value: -1, label: 'Choose Process' }];
}
var sp = window.localStorage.getItem("totProcess");
if (sp != null) window.vueInstance.selectedLaborProcess = sp;
// ─── Update title with total duration ────────────────────
var total = 0;
var titleEls = document.getElementsByClassName("title");
var title = titleEls.length > 0 ? titleEls[0] : document.querySelector('h1,h2,h3') || document.querySelector('.ganttChart');
if (title) {
window.vueInstance.totParams.forEach(bar => (total += window.vueInstance.getDuration(bar[1], bar[3])));
if (title.innerText) title.innerText = title.innerText + "(" + Math.round(total) + "m)";
}
console.log('\u2705 TOT Batch module initialized');
} // end initTOTBatch
})();