Automatically check course names, provides a structured progress for discussions and assignments, also saves progress locally.
// ==UserScript==
// @name Tutorial Online Progress Widget
// @namespace http://tampermonkey.net/
// @version 2
// @description Automatically check course names, provides a structured progress for discussions and assignments, also saves progress locally.
// @author deoffuscated
// @match https://elearning.ut.ac.id/*
// @grant none
// @license MIT
// ==/UserScript==
(function() {
'use strict';
const APP_NAME = "Tutorial Online Progress Widget";
const WIDGET_ICON = 'https://suopmkm.ut.ac.id/uo/statics/logo.png';
const STORAGE_DATA_KEY = 'tuton_progress_checklist';
const STORAGE_COURSES_KEY = 'tuton_course_cache_list';
const STORAGE_AUTODETECT_KEY = 'tuton_auto_detect_enabled';
const STATE_KEY = 'tuton_widget_minimized_state';
const defaultCourses = ["CONTOH MATA KULIAH"];
const colLabels = [
"DISKUSI 1", "DISKUSI 2", "DISKUSI 3", "DISKUSI 4",
"DISKUSI 5", "DISKUSI 6", "DISKUSI 7", "DISKUSI 8",
"TUGAS 1", "TUGAS 2", "TUGAS 3"
];
const script = document.createElement('script');
script.src = 'https://cdn.jsdelivr.net/npm/[email protected]/dist/confetti.browser.min.js';
document.head.appendChild(script);
function loadCachedCourses() {
const stored = localStorage.getItem(STORAGE_COURSES_KEY);
return stored ? JSON.parse(stored) : defaultCourses;
}
function saveCachedCourses(courses) {
localStorage.setItem(STORAGE_COURSES_KEY, JSON.stringify(courses));
}
function loadProgressData() { return JSON.parse(localStorage.getItem(STORAGE_DATA_KEY) || '{}'); }
function saveProgressData(data) { localStorage.setItem(STORAGE_DATA_KEY, JSON.stringify(data)); }
function loadWidgetState() { return localStorage.getItem(STATE_KEY) === 'true'; }
function saveWidgetState(isMin) { localStorage.setItem(STATE_KEY, isMin); }
function loadAutoDetectState() {
const val = localStorage.getItem(STORAGE_AUTODETECT_KEY);
return val === null ? true : val === 'true';
}
function saveAutoDetectState(isEnabled) {
localStorage.setItem(STORAGE_AUTODETECT_KEY, isEnabled);
}
let courseList = loadCachedCourses();
let progressData = loadProgressData();
let isAutoDetectEnabled = loadAutoDetectState();
function scanForCourses() {
if (!isAutoDetectEnabled) return;
const courseElements = document.querySelectorAll('.coursename .multiline');
if (courseElements.length > 0) {
const scannedNames = Array.from(courseElements).map(el => {
let name = el.textContent.trim();
return name.replace(/\s+\d+$/, '');
});
let isUpdated = false;
scannedNames.forEach(name => {
if (!courseList.includes(name)) {
courseList.push(name);
isUpdated = true;
}
});
if (isUpdated) {
console.log(`[${APP_NAME}] Mata kuliah baru ditambahkan (Auto-Detect).`);
saveCachedCourses(courseList);
const mainPanel = document.getElementById('ut-main-panel');
if (mainPanel) {
reRenderTable();
renderSettingsList();
}
}
}
}
const observer = new MutationObserver((mutations) => {
if (isAutoDetectEnabled && document.querySelector('.dashboard-card-deck')) {
scanForCourses();
}
});
observer.observe(document.body, { childList: true, subtree: true });
const style = document.createElement('style');
style.innerHTML = `
:root {
--glass-bg: rgba(255, 255, 255, 0.95);
--primary-color: #1859BC;
--accent-tugas: #e67e22;
--success-color: #2ecc71;
--danger-color: #c0392b;
--text-dark: #1a202c;
--text-light: #4a5568;
--border-color: rgba(0, 0, 0, 0.1);
}
#ut-helper-wrapper {
position: fixed; z-index: 10000;
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
transition: all 0.4s cubic-bezier(0.25, 0.8, 0.25, 1);
}
#ut-helper-wrapper.minimized { bottom: 30px; right: 30px; }
#ut-helper-wrapper.expanded { bottom: 30px; right: 30px; }
/* TRIGGER BUTTON */
#ut-widget-trigger {
width: 55px; height: 55px; border-radius: 50%;
background: rgba(255, 255, 255, 0.9);
box-shadow: 0 4px 15px rgba(0,0,0,0.2);
display: flex; align-items: center; justify-content: center;
cursor: pointer; border: 2px solid var(--primary-color);
padding: 8px; box-sizing: border-box;
transition: transform 0.2s;
}
#ut-widget-trigger:hover { transform: scale(1.05); }
#ut-widget-trigger img { width: 100%; height: 100%; object-fit: contain; pointer-events: none; }
/* MAIN PANEL */
#ut-main-panel {
background: var(--glass-bg);
backdrop-filter: blur(12px); -webkit-backdrop-filter: blur(12px);
border: 1px solid rgba(255,255,255,0.4);
box-shadow: 0 8px 32px rgba(0,0,0,0.15);
border-radius: 12px; padding: 15px;
display: none; flex-direction: column;
min-width: 400px; max-width: 95vw;
animation: slideUp 0.3s ease-out;
max-height: 80vh; overflow-y: auto;
}
@keyframes slideUp { from { opacity: 0; transform: translateY(20px); } to { opacity: 1; transform: translateY(0); } }
/* HEADER */
.ut-header { display: flex; justify-content: space-between; align-items: center; margin-bottom: 10px; }
.ut-title { font-weight: 700; color: var(--text-dark); font-size: 15px; display: flex; align-items: center; }
.ut-title img { margin-right: 10px; height: 24px; }
.ut-controls { display: flex; gap: 8px; }
.ut-icon-btn {
cursor: pointer; color: var(--text-dark);
width: 28px; height: 28px; border-radius: 50%;
display: flex; align-items: center; justify-content: center;
transition: background 0.2s;
}
.ut-icon-btn:hover { background: rgba(0,0,0,0.1); }
.ut-icon-btn.danger:hover { color: red; background: rgba(255,0,0,0.1); }
/* TABS */
.ut-nav-tabs { display: flex; background: rgba(0,0,0,0.05); padding: 4px; border-radius: 8px; margin-bottom: 10px; gap: 5px; }
.ut-tab-item {
flex: 1; text-align: center; padding: 6px; font-size: 12px; font-weight: 600;
cursor: pointer; border-radius: 6px; color: var(--text-light); transition: 0.2s;
}
.ut-tab-item:hover { background: rgba(255,255,255,0.5); }
.ut-tab-item.active { background: #fff; color: var(--primary-color); box-shadow: 0 2px 5px rgba(0,0,0,0.1); }
/* TABLE */
.ut-table-container { overflow-x: auto; }
.ut-table { width: 100%; border-collapse: collapse; margin-bottom: 5px; }
.ut-table tr { border-bottom: 1px solid var(--border-color); }
.ut-table tr:hover:not(:first-child) { background-color: rgba(24, 89, 188, 0.05); }
.ut-table td { padding: 6px 4px; text-align: center; vertical-align: middle; }
.ut-table.mode-diskusi .type-tugas { display: none; }
.ut-table.mode-tugas .type-diskusi { display: none; }
.col-head { font-size: 10px; font-weight: 800; color: var(--primary-color); border-bottom: 2px solid var(--primary-color); }
.col-head-tugas { font-size: 10px; font-weight: 800; color: var(--accent-tugas); border-bottom: 2px solid var(--accent-tugas); }
.bg-tugas { background-color: rgba(230, 126, 34, 0.05); }
.row-label { font-size: 11px; font-weight: 700; text-align: left !important; min-width: 120px; max-width: 180px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.btn-reset { cursor: pointer; font-size: 10px; font-weight: 900; color: var(--danger-color); border-bottom: 2px solid var(--primary-color) !important; }
/* CHECKBOXES & PROGRESS */
.ut-chk-wrap { display: inline-block; position: relative; cursor: pointer; width: 16px; height: 16px; top: 2px; }
.ut-chk-wrap.disabled { opacity: 0.3; pointer-events: none; filter: grayscale(1); }
.ut-chk-wrap input { opacity: 0; width: 0; height: 0; }
.checkmark { position: absolute; top: 0; left: 0; height: 16px; width: 16px; background-color: rgba(255,255,255,0.6); border: 2px solid #718096; border-radius: 4px; }
.ut-chk-wrap:hover .checkmark { border-color: var(--primary-color); }
.ut-chk-wrap input:checked ~ .checkmark { background-color: var(--primary-color); border-color: var(--primary-color); }
.chk-tugas input:checked ~ .checkmark { background-color: var(--accent-tugas); border-color: var(--accent-tugas); }
.checkmark:after { content: ""; position: absolute; display: none; left: 4px; top: 1px; width: 3px; height: 8px; border: solid white; border-width: 0 2px 2px 0; transform: rotate(45deg); }
.ut-chk-wrap input:checked ~ .checkmark:after { display: block; }
.prog-cont { margin-top: 10px; }
.prog-bg { background: rgba(0,0,0,0.1); border-radius: 20px; height: 6px; width: 100%; overflow: hidden; }
.prog-fill { height: 100%; background: var(--success-color); width: 0%; transition: width 0.5s; border-radius: 20px; }
.prog-text { font-size: 10px; text-align: right; margin-top: 4px; color: var(--text-dark); font-weight: 600; }
/* SETTINGS PANEL */
#ut-settings-view { display: none; flex-direction: column; gap: 10px; padding: 5px; }
.st-setting-row { display: flex; justify-content: space-between; align-items: center; background: rgba(255,255,255,0.5); padding: 8px; border-radius: 6px; margin-bottom: 5px; border: 1px solid rgba(0,0,0,0.05); }
.st-input-group { display: flex; gap: 5px; }
.st-input { flex: 1; padding: 8px; border: 1px solid #ddd; border-radius: 6px; font-size: 12px; }
.st-btn { padding: 8px 12px; border: none; border-radius: 6px; font-size: 12px; font-weight: 600; cursor: pointer; color: white; }
.st-btn-add { background: var(--success-color); }
.st-btn-back { background: var(--text-light); width: 100%; margin-top: 10px; }
.st-course-list { max-height: 200px; overflow-y: auto; border: 1px solid #eee; border-radius: 6px; margin-top: 5px; }
.st-course-item { display: flex; justify-content: space-between; align-items: center; padding: 8px; border-bottom: 1px solid #eee; background: white; font-size: 12px; }
.st-course-item:last-child { border-bottom: none; }
.st-del-btn { color: var(--danger-color); cursor: pointer; font-weight: bold; padding: 2px 6px; }
.st-del-btn:hover { background: rgba(192, 57, 43, 0.1); border-radius: 4px; }
.st-lbl { font-size: 12px; font-weight: 700; color: var(--text-dark); margin-top: 5px; }
.hidden { display: none !important; }
/* TOGGLE SWITCH CSS */
.ut-switch { position: relative; display: inline-block; width: 34px; height: 20px; }
.ut-switch input { opacity: 0; width: 0; height: 0; }
.ut-slider { position: absolute; cursor: pointer; top: 0; left: 0; right: 0; bottom: 0; background-color: #ccc; transition: .4s; border-radius: 20px; }
.ut-slider:before { position: absolute; content: ""; height: 14px; width: 14px; left: 3px; bottom: 3px; background-color: white; transition: .4s; border-radius: 50%; }
input:checked + .ut-slider { background-color: var(--primary-color); }
input:checked + .ut-slider:before { transform: translateX(14px); }
/* MODAL */
#ut-modal-overlay { position: fixed; top: 0; left: 0; width: 100%; height: 100%; background: rgba(0,0,0,0.4); z-index: 10001; display: none; justify-content: center; align-items: center; }
#ut-modal-box { background: white; padding: 25px; border-radius: 12px; width: 300px; text-align: center; box-shadow: 0 20px 50px rgba(0,0,0,0.3); }
.ut-modal-btns { display: flex; gap: 10px; margin-top: 20px; }
.ut-btn { flex: 1; border: none; padding: 10px; border-radius: 6px; cursor: pointer; font-weight: 600; }
.btn-cancel { background: #edf2f7; } .btn-ok { background: var(--danger-color); color: white; }
`;
document.head.appendChild(style);
function initUI() {
const wrapper = document.createElement('div');
wrapper.id = 'ut-helper-wrapper';
const widgetTrigger = document.createElement('div');
widgetTrigger.id = 'ut-widget-trigger';
widgetTrigger.innerHTML = `<img src="${WIDGET_ICON}" alt="UT Helper">`;
widgetTrigger.title = 'Buka Progress Widget';
const mainPanel = document.createElement('div');
mainPanel.id = 'ut-main-panel';
mainPanel.innerHTML = `
<div class="ut-header">
<span class="ut-title"><img src="${WIDGET_ICON}" alt="icon"> Tutorial Online Progress Widget</span>
<div class="ut-controls">
<div class="ut-icon-btn" id="ut-btn-settings" title="Pengaturan">
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="3"></circle><path d="M19.4 15a1.65 1.65 0 0 0 .33 1.82l.06.06a2 2 0 0 1 0 2.83 2 2 0 0 1-2.83 0l-.06-.06a1.65 1.65 0 0 0-1.82-.33 1.65 1.65 0 0 0-1 1.51V21a2 2 0 0 1-2 2 2 2 0 0 1-2-2v-.09A1.65 1.65 0 0 0 9 19.4a1.65 1.65 0 0 0-1.82.33l-.06.06a2 2 0 0 1-2.83 0 2 2 0 0 1 0-2.83l.06-.06a1.65 1.65 0 0 0 .33-1.82 1.65 1.65 0 0 0-1.51-1H3a2 2 0 0 1-2-2 2 2 0 0 1 2-2h.09A1.65 1.65 0 0 0 4.6 9a1.65 1.65 0 0 0-.33-1.82l-.06-.06a2 2 0 0 1 0-2.83 2 2 0 0 1 2.83 0l.06.06a1.65 1.65 0 0 0 1.82.33H9a1.65 1.65 0 0 0 1-1.51V3a2 2 0 0 1 2-2 2 2 0 0 1 2 2v.09a1.65 1.65 0 0 0 1 1.51 1.65 1.65 0 0 0 1.82-.33l.06-.06a2 2 0 0 1 2.83 0 2 2 0 0 1 0 2.83l-.06.06a1.65 1.65 0 0 0-.33 1.82V9a1.65 1.65 0 0 0 1.51 1H21a2 2 0 0 1 2 2 2 2 0 0 1-2 2h-.09a1.65 1.65 0 0 0-1.51 1z"></path></svg>
</div>
<div class="ut-icon-btn danger" id="ut-btn-minimize" title="Tutup">
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><line x1="18" y1="6" x2="6" y2="18"></line><line x1="6" y1="6" x2="18" y2="18"></line></svg>
</div>
</div>
</div>
<!-- VIEW: CHECKLIST (DEFAULT) -->
<div id="ut-checklist-view">
<div class="ut-nav-tabs">
<div class="ut-tab-item active" data-mode="mode-all">Semua</div>
<div class="ut-tab-item" data-mode="mode-diskusi">Diskusi</div>
<div class="ut-tab-item" data-mode="mode-tugas">Tugas</div>
</div>
<div id="ut-table-wrapper" class="ut-table-container">
<!-- Table generated by JS -->
</div>
<div class="prog-cont">
<div class="prog-bg"><div class="prog-fill" id="ut-prog-bar"></div></div>
<div class="prog-text" id="ut-prog-lbl">0% Selesai</div>
</div>
</div>
<!-- VIEW: SETTINGS -->
<div id="ut-settings-view">
<div class="st-setting-row">
<span style="font-size:12px; font-weight:600; color:var(--text-dark);">Auto-Detect Mata Kuliah</span>
<label class="ut-switch">
<input type="checkbox" id="st-auto-detect-toggle" ${isAutoDetectEnabled ? 'checked' : ''}>
<span class="ut-slider"></span>
</label>
</div>
<div class="st-lbl">Tambah Mata Kuliah Manual</div>
<div class="st-input-group">
<input type="text" id="st-course-input" class="st-input" placeholder="Misal: Bahasa Inggris Niaga...">
<button id="st-add-btn" class="st-btn st-btn-add">Tambah</button>
</div>
<div class="st-lbl">Daftar Mata Kuliah (Aktif)</div>
<div class="st-course-list" id="st-course-list-container">
<!-- List generated by JS -->
</div>
<button id="st-back-btn" class="st-btn st-btn-back">Kembali ke Progress</button>
</div>
`;
wrapper.appendChild(widgetTrigger);
wrapper.appendChild(mainPanel);
document.body.appendChild(wrapper);
const modalOverlay = document.createElement('div');
modalOverlay.id = 'ut-modal-overlay';
modalOverlay.innerHTML = `
<div id="ut-modal-box">
<div style="font-size:16px; font-weight:700; margin-bottom:5px;">Reset Checklist?</div>
<div style="font-size:13px; color:#666;">Semua progress akan dihapus.</div>
<div class="ut-modal-btns">
<button class="ut-btn btn-cancel" id="ut-modal-cancel">Batal</button>
<button class="ut-btn btn-ok" id="ut-modal-ok">Hapus</button>
</div>
</div>
`;
document.body.appendChild(modalOverlay);
let isMinimized = loadWidgetState();
window.reRenderTable = function() {
const container = document.getElementById('ut-table-wrapper');
let tableHTML = `<table class="ut-table mode-all" id="ut-checklist-table">`;
tableHTML += `<tr><td class="btn-reset" id="ut-btn-reset" title="Reset Semua">RESET</td>`;
colLabels.forEach((l, index) => {
const isTugas = index >= 8;
const headerClass = isTugas ? 'col-head-tugas' : 'col-head';
const typeClass = isTugas ? 'type-tugas' : 'type-diskusi';
tableHTML += `<td class="${headerClass} ${typeClass}">${l}</td>`;
});
tableHTML += `</tr>`;
courseList.forEach(row => {
const cleanRow = row.replace(/[^a-zA-Z0-9]/g, '');
tableHTML += `<tr><td class="row-label" title="${row}">${row}</td>`;
for(let i=1; i<=11; i++) {
const isTugas = i >= 9;
const typeClass = isTugas ? 'type-tugas' : 'type-diskusi';
const bgClass = isTugas ? 'bg-tugas' : '';
const chkClass = isTugas ? 'ut-chk-wrap chk-tugas' : 'ut-chk-wrap';
tableHTML += `<td class="${bgClass} ${typeClass}"><label class="${chkClass}"><input type="checkbox" data-id="${cleanRow}_${i}"><span class="checkmark"></span></label></td>`;
}
tableHTML += `</tr>`;
});
tableHTML += `</table>`;
container.innerHTML = tableHTML;
const tableEl = document.getElementById('ut-checklist-table');
const chks = tableEl.querySelectorAll('input[type="checkbox"]');
chks.forEach(chk => {
const id = chk.getAttribute('data-id');
if (progressData[id]) chk.checked = true;
chk.addEventListener('change', (e) => {
progressData[id] = e.target.checked;
saveProgressData(progressData);
applyRules(true);
});
});
document.getElementById('ut-btn-reset').addEventListener('click', () => {
modalOverlay.style.display = 'flex';
});
const activeTab = document.querySelector('.ut-tab-item.active');
if(activeTab) {
const mode = activeTab.getAttribute('data-mode');
tableEl.className = `ut-table ${mode}`;
}
applyRules(false);
};
function calculateProgress(isUserAction) {
const allChks = document.querySelectorAll('#ut-checklist-table input[type="checkbox"]');
const total = allChks.length;
if (total === 0) return;
const checked = Array.from(allChks).filter(c => c.checked).length;
const pct = Math.round((checked / total) * 100);
document.getElementById('ut-prog-bar').style.width = `${pct}%`;
document.getElementById('ut-prog-lbl').innerText = `${pct}% Selesai`;
if (pct === 100 && isUserAction) {
if (typeof confetti === 'function') confetti({ particleCount: 100, spread: 70, origin: { y: 0.6 }, zIndex: 10002 });
}
}
function applyRules(isUserAction) {
courseList.forEach(row => {
const cleanRow = row.replace(/[^a-zA-Z0-9]/g, '');
for (let i = 1; i < 8; i++) toggleLinkedCheck(`${cleanRow}_${i}`, `${cleanRow}_${i+1}`);
toggleLinkedCheck(`${cleanRow}_3`, `${cleanRow}_9`);
toggleLinkedCheck(`${cleanRow}_5`, `${cleanRow}_10`);
toggleLinkedCheck(`${cleanRow}_7`, `${cleanRow}_11`);
});
saveProgressData(progressData);
calculateProgress(isUserAction);
}
function toggleLinkedCheck(srcId, targetId) {
const src = document.querySelector(`input[data-id="${srcId}"]`);
const tgt = document.querySelector(`input[data-id="${targetId}"]`);
if(src && tgt) {
const wrap = tgt.closest('.ut-chk-wrap');
if(src.checked) {
tgt.disabled = false;
wrap.classList.remove('disabled');
} else {
tgt.disabled = true;
wrap.classList.add('disabled');
if(tgt.checked) { tgt.checked = false; progressData[targetId] = false; }
}
}
}
window.renderSettingsList = function() {
const listCont = document.getElementById('st-course-list-container');
listCont.innerHTML = '';
courseList.forEach((c, idx) => {
const item = document.createElement('div');
item.className = 'st-course-item';
item.innerHTML = `<span>${c}</span> <span class="st-del-btn" data-idx="${idx}">Hapus</span>`;
listCont.appendChild(item);
});
document.querySelectorAll('.st-del-btn').forEach(btn => {
btn.addEventListener('click', (e) => {
const idx = e.target.getAttribute('data-idx');
courseList.splice(idx, 1);
saveCachedCourses(courseList);
renderSettingsList();
});
});
};
function toTitleCase(str) {
return str.toLowerCase().split(' ').map(word => word.charAt(0).toUpperCase() + word.slice(1)).join(' ');
}
document.getElementById('st-add-btn').addEventListener('click', () => {
const input = document.getElementById('st-course-input');
let rawVal = input.value.trim();
if (!rawVal) return;
const val = toTitleCase(rawVal);
if(val && !courseList.includes(val)) {
courseList.push(val);
saveCachedCourses(courseList);
input.value = '';
renderSettingsList();
} else if (courseList.includes(val)) {
alert('Mata kuliah sudah ada!');
}
});
document.getElementById('st-auto-detect-toggle').addEventListener('change', (e) => {
isAutoDetectEnabled = e.target.checked;
saveAutoDetectState(isAutoDetectEnabled);
if(isAutoDetectEnabled) {
scanForCourses();
}
});
const viewChecklist = document.getElementById('ut-checklist-view');
const viewSettings = document.getElementById('ut-settings-view');
document.getElementById('ut-btn-settings').addEventListener('click', () => {
viewChecklist.classList.add('hidden');
viewSettings.classList.remove('hidden');
viewSettings.style.display = 'flex';
renderSettingsList();
});
document.getElementById('st-back-btn').addEventListener('click', () => {
viewSettings.classList.add('hidden');
viewSettings.style.display = 'none';
viewChecklist.classList.remove('hidden');
reRenderTable();
});
const tabItems = document.querySelectorAll('.ut-tab-item');
tabItems.forEach(tab => {
tab.addEventListener('click', () => {
tabItems.forEach(t => t.classList.remove('active'));
tab.classList.add('active');
const mode = tab.getAttribute('data-mode');
document.getElementById('ut-checklist-table').className = `ut-table ${mode}`;
});
});
function updateWidgetState() {
if (isMinimized) {
wrapper.className = 'minimized'; mainPanel.style.display = 'none'; widgetTrigger.style.display = 'flex';
} else {
wrapper.className = 'expanded'; mainPanel.style.display = 'flex'; widgetTrigger.style.display = 'none';
}
saveWidgetState(isMinimized);
}
widgetTrigger.addEventListener('click', () => { isMinimized = false; updateWidgetState(); });
document.getElementById('ut-btn-minimize').addEventListener('click', () => { isMinimized = true; updateWidgetState(); });
const closeModal = () => { modalOverlay.style.display = 'none'; };
document.getElementById('ut-modal-cancel').addEventListener('click', closeModal);
document.getElementById('ut-modal-ok').addEventListener('click', () => {
progressData = {};
saveProgressData(progressData);
reRenderTable();
closeModal();
});
updateWidgetState();
reRenderTable();
}
scanForCourses();
initUI();
})();