feat(demo): update prototype to showcase authentic Windows and Android app screens
This commit is contained in:
1 parent
18abb9a6d8
commit
fca3a1f9d7
3 files changed
+473
-1722
No files matched your search
+148
-389
@@ -1,371 +1,149 @@
|
||||
/**
|
||||
* TIS-RMS Interactive Prototype Logic
|
||||
* Powers navigation, device switching, mock data filtering, and simulated workflows.
|
||||
* TIS-RMS Interactive Prototype Showcase
|
||||
* Drives real screen switching between actual screenshots and interactive dialogs.
|
||||
*/
|
||||
|
||||
// ── Mock Data ───────────────────────────────────────────────────
|
||||
const MOCK_STUDENTS = [
|
||||
{ id: 1, lrn: '109876543210', name: 'DELA CRUZ, Juan Santos', grade: 'Grade 10', section: 'Jacinto', sex: 'Male', status: 'Enrolled', age: 16, contact: '0917-123-4567', docs: 4 },
|
||||
{ id: 2, lrn: '109876543211', name: 'SANTOS, Maria Clara Perez', grade: 'Grade 9', section: 'Silang', sex: 'Female', status: 'Enrolled', age: 15, contact: '0918-234-5678', docs: 5 },
|
||||
{ id: 3, lrn: '109876543212', name: 'ALVAREZ, Gabriel Mendoza', grade: 'Grade 7', section: 'Rizal', sex: 'Male', status: 'Enrolled', age: 13, contact: '0919-345-6789', docs: 3 },
|
||||
{ id: 4, lrn: '109876543213', name: 'BAUTISTA, Chloe Anne Flores', grade: 'Grade 8', section: 'Aguinaldo', sex: 'Female', status: 'Enrolled', age: 14, contact: '0920-456-7890', docs: 4 },
|
||||
{ id: 5, lrn: '109876543214', name: 'RAMIREZ, Christian Paul Torres', grade: 'Grade 11', section: 'TVL-ICT', sex: 'Male', status: 'Enrolled', age: 17, contact: '0921-567-8901', docs: 5 },
|
||||
{ id: 6, lrn: '109876543215', name: 'VILLANUEVA, Princess Diane Gomez', grade: 'Grade 12', section: 'STEM A', sex: 'Female', status: 'Enrolled', age: 18, contact: '0922-678-9012', docs: 5 },
|
||||
{ id: 7, lrn: '109876543216', name: 'CASTILLO, Joshua Mark Reyes', grade: 'Grade 10', section: 'Tandang Sora', sex: 'Male', status: 'Enrolled', age: 16, contact: '0923-789-0123', docs: 4 },
|
||||
{ id: 8, lrn: '109876543217', name: 'MERCADO, Angel Mae Lim', grade: 'Grade 7', section: 'Bonifacio', sex: 'Female', status: 'Enrolled', age: 13, contact: '0924-890-1234', docs: 3 },
|
||||
const SCREENS = [
|
||||
{ id: 'dashboard', name: 'Dashboard', icon: '📊' },
|
||||
{ id: 'students', name: 'Students', icon: '👥' },
|
||||
{ id: 'documents', name: 'Documents', icon: '📁' },
|
||||
{ id: 'archives', name: 'Archives', icon: '📦' },
|
||||
{ id: 'reports', name: 'Reports', icon: '📈' },
|
||||
{ id: 'users', name: 'Users', icon: '👤' },
|
||||
{ id: 'history', name: 'History', icon: '🕒' },
|
||||
{ id: 'settings', name: 'Settings', icon: '⚙️' },
|
||||
];
|
||||
|
||||
const MOCK_DOCS = [
|
||||
{ name: 'PSA / NSO Birth Certificate', code: 'DOC-PSA', required: true, status: 'Verified', count: 1420 },
|
||||
{ name: 'SF10 / Form 137 (Permanent Record)', code: 'DOC-SF10', required: true, status: 'Verified', count: 1388 },
|
||||
{ name: 'SF9 / Form 138 (Report Card)', code: 'DOC-SF9', required: true, status: 'Verified', count: 1405 },
|
||||
{ name: 'Certificate of Good Moral Character', code: 'DOC-GMC', required: false, status: 'Complete', count: 1290 },
|
||||
{ name: '2x2 Recent ID Photo', code: 'DOC-PHOTO', required: true, status: 'Verified', count: 1482 },
|
||||
];
|
||||
|
||||
const MOCK_CLASSES = [
|
||||
{ grade: 'Grade 7', section: 'Rizal', adviser: 'Ms. Carmela Diaz', students: 46, max: 50 },
|
||||
{ grade: 'Grade 7', section: 'Bonifacio', adviser: 'Mr. Eduardo Ramos', students: 48, max: 50 },
|
||||
{ grade: 'Grade 8', section: 'Aguinaldo', adviser: 'Mrs. Leah Manalo', students: 45, max: 50 },
|
||||
{ grade: 'Grade 8', section: 'Luna', adviser: 'Mr. Kenneth Ortiz', students: 47, max: 50 },
|
||||
{ grade: 'Grade 9', section: 'Silang', adviser: 'Ms. Joanne Beltran', students: 44, max: 50 },
|
||||
{ grade: 'Grade 10', section: 'Jacinto', adviser: 'Mr. Roderick Tan', students: 49, max: 50 },
|
||||
{ grade: 'Grade 11', section: 'TVL-ICT', adviser: 'Engr. Mark Soriano', students: 42, max: 45 },
|
||||
{ grade: 'Grade 12', section: 'STEM A', adviser: 'Dr. Evelyn Cruz', students: 40, max: 45 },
|
||||
];
|
||||
|
||||
// ── Application State ──────────────────────────────────────────
|
||||
let currentDevice = 'windows';
|
||||
let currentDevice = 'windows'; // 'windows' | 'android'
|
||||
let currentScreen = 'dashboard';
|
||||
let currentTheme = 'light';
|
||||
let studentFilter = 'all';
|
||||
let searchQuery = '';
|
||||
|
||||
// ── Initialization ─────────────────────────────────────────────
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
setupDeviceSwitcher();
|
||||
setupThemeToggle();
|
||||
setupNavigation();
|
||||
renderStudentsTable();
|
||||
renderMobileStudentsList();
|
||||
renderDocsList();
|
||||
renderClassesList();
|
||||
startClock();
|
||||
setupScreenPills();
|
||||
setupKeyboardNavigation();
|
||||
updateView();
|
||||
});
|
||||
|
||||
// ── Device Switcher ────────────────────────────────────────────
|
||||
// ── Device Switcher ───────────────────────────────────────────
|
||||
function setupDeviceSwitcher() {
|
||||
const winBtn = document.getElementById('btn-view-windows');
|
||||
const androidBtn = document.getElementById('btn-view-android');
|
||||
const winFrame = document.getElementById('desktop-frame');
|
||||
const phoneFrame = document.getElementById('phone-frame');
|
||||
|
||||
winBtn.addEventListener('click', () => {
|
||||
currentDevice = 'windows';
|
||||
winBtn.classList.add('active');
|
||||
androidBtn.classList.remove('active');
|
||||
winFrame.style.display = 'flex';
|
||||
phoneFrame.style.display = 'none';
|
||||
syncScreens();
|
||||
document.getElementById('desktop-window').style.display = 'flex';
|
||||
document.getElementById('phone-mockup').style.display = 'none';
|
||||
updateView();
|
||||
});
|
||||
|
||||
androidBtn.addEventListener('click', () => {
|
||||
currentDevice = 'android';
|
||||
androidBtn.classList.add('active');
|
||||
winBtn.classList.remove('active');
|
||||
winFrame.style.display = 'none';
|
||||
phoneFrame.style.display = 'flex';
|
||||
syncScreens();
|
||||
document.getElementById('desktop-window').style.display = 'none';
|
||||
document.getElementById('phone-mockup').style.display = 'flex';
|
||||
updateView();
|
||||
});
|
||||
}
|
||||
|
||||
// ── Theme Toggle ───────────────────────────────────────────────
|
||||
function setupThemeToggle() {
|
||||
const toggleBtn = document.getElementById('theme-toggle-btn');
|
||||
toggleBtn.addEventListener('click', () => {
|
||||
currentTheme = currentTheme === 'light' ? 'dark' : 'light';
|
||||
document.documentElement.setAttribute('data-theme', currentTheme);
|
||||
toggleBtn.innerHTML = currentTheme === 'light'
|
||||
? `<svg width="18" height="18" fill="none" stroke="currentColor" stroke-width="2" viewBox="0 0 24 24"><path d="M21 12.79A9 9 0 1 1 11.21 3 7 7 0 0 0 21 12.79z"></path></svg>`
|
||||
: `<svg width="18" height="18" fill="none" stroke="currentColor" stroke-width="2" viewBox="0 0 24 24"><circle cx="12" cy="12" r="5"></circle><line x1="12" y1="1" x2="12" y2="3"></line><line x1="12" y1="21" x2="12" y2="23"></line><line x1="4.22" y1="4.22" x2="5.64" y2="5.64"></line><line x1="18.36" y1="18.36" x2="19.78" y2="19.78"></line><line x1="1" y1="12" x2="3" y2="12"></line><line x1="21" y1="12" x2="23" y2="12"></line><line x1="4.22" y1="19.78" x2="5.64" y2="18.36"></line><line x1="18.36" y1="5.64" x2="19.78" y2="4.22"></line></svg>`;
|
||||
});
|
||||
}
|
||||
// ── Screen Selector Pills ─────────────────────────────────────
|
||||
function setupScreenPills() {
|
||||
const bar = document.getElementById('screen-selector-bar');
|
||||
if (!bar) return;
|
||||
|
||||
// ── Navigation ─────────────────────────────────────────────────
|
||||
function setupNavigation() {
|
||||
// Desktop sidebar clicks
|
||||
document.querySelectorAll('.win-nav-item').forEach(item => {
|
||||
item.addEventListener('click', () => {
|
||||
const target = item.getAttribute('data-screen');
|
||||
if (target) navigateTo(target);
|
||||
});
|
||||
});
|
||||
|
||||
// Mobile bottom nav clicks
|
||||
document.querySelectorAll('.bottom-nav-item').forEach(item => {
|
||||
item.addEventListener('click', () => {
|
||||
const target = item.getAttribute('data-screen');
|
||||
if (target) navigateTo(target);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function navigateTo(screenId) {
|
||||
currentScreen = screenId;
|
||||
syncScreens();
|
||||
}
|
||||
|
||||
function syncScreens() {
|
||||
// Update desktop sidebar active class
|
||||
document.querySelectorAll('.win-nav-item').forEach(el => {
|
||||
el.classList.toggle('active', el.getAttribute('data-screen') === currentScreen);
|
||||
});
|
||||
|
||||
// Update mobile bottom nav active class
|
||||
document.querySelectorAll('.bottom-nav-item').forEach(el => {
|
||||
el.classList.toggle('active', el.getAttribute('data-screen') === currentScreen);
|
||||
});
|
||||
|
||||
// Update desktop content views
|
||||
document.querySelectorAll('.win-screen-view').forEach(view => {
|
||||
view.style.display = view.id === `win-${currentScreen}` ? 'block' : 'none';
|
||||
});
|
||||
|
||||
// Update mobile content views
|
||||
document.querySelectorAll('.phone-screen-view').forEach(view => {
|
||||
view.style.display = view.id === `phone-${currentScreen}` ? 'block' : 'none';
|
||||
});
|
||||
|
||||
// Update breadcrumb titles
|
||||
const titles = {
|
||||
dashboard: 'Dashboard Overview',
|
||||
students: 'Students Directory',
|
||||
documents: 'Document Requirements',
|
||||
academic: 'Classes & Academic Structure',
|
||||
settings: 'System Settings & About'
|
||||
};
|
||||
|
||||
const winTitle = document.getElementById('win-breadcrumb-title');
|
||||
if (winTitle) winTitle.textContent = titles[currentScreen] || 'Dashboard';
|
||||
|
||||
const phoneTitle = document.getElementById('phone-header-title');
|
||||
if (phoneTitle) phoneTitle.textContent = titles[currentScreen] || 'TIS-RMS';
|
||||
}
|
||||
|
||||
// ── Students Table & Card Rendering ────────────────────────────
|
||||
function renderStudentsTable() {
|
||||
const tbody = document.getElementById('students-table-body');
|
||||
if (!tbody) return;
|
||||
|
||||
const filtered = filterStudentData();
|
||||
tbody.innerHTML = '';
|
||||
|
||||
if (filtered.length === 0) {
|
||||
tbody.innerHTML = `<tr><td colspan="7" style="text-align: center; padding: 32px; color: var(--win-text-muted);">No student records matched your search query.</td></tr>`;
|
||||
return;
|
||||
}
|
||||
|
||||
filtered.forEach(student => {
|
||||
const tr = document.createElement('tr');
|
||||
tr.className = 'student-row-click';
|
||||
tr.innerHTML = `
|
||||
<td style="font-weight: 600;">${student.name}</td>
|
||||
<td style="font-family: monospace; color: var(--win-text-muted);">${student.lrn}</td>
|
||||
<td>${student.grade}</td>
|
||||
<td>${student.section}</td>
|
||||
<td>${student.sex}</td>
|
||||
<td><span class="status-badge badge-enrolled">${student.status}</span></td>
|
||||
<td>
|
||||
<button class="btn-secondary" style="height: 28px; padding: 0 10px; font-size: 11px;" onclick="openStudentModal(${student.id})">
|
||||
View Details
|
||||
</button>
|
||||
</td>
|
||||
`;
|
||||
tr.addEventListener('click', (e) => {
|
||||
if (e.target.tagName !== 'BUTTON') openStudentModal(student.id);
|
||||
});
|
||||
tbody.appendChild(tr);
|
||||
});
|
||||
}
|
||||
|
||||
function renderMobileStudentsList() {
|
||||
const container = document.getElementById('mobile-students-container');
|
||||
if (!container) return;
|
||||
|
||||
const filtered = filterStudentData();
|
||||
container.innerHTML = '';
|
||||
|
||||
if (filtered.length === 0) {
|
||||
container.innerHTML = `<div style="text-align:center; padding: 30px; color: var(--win-text-muted); font-size: 13px;">No student records found.</div>`;
|
||||
return;
|
||||
}
|
||||
|
||||
filtered.forEach(student => {
|
||||
const card = document.createElement('div');
|
||||
card.className = 'mobile-student-card';
|
||||
card.innerHTML = `
|
||||
<div class="m-student-header">
|
||||
<span class="m-student-name">${student.name}</span>
|
||||
<span class="status-badge badge-enrolled">${student.status}</span>
|
||||
</div>
|
||||
<div class="m-student-lrn">LRN: ${student.lrn}</div>
|
||||
<div class="m-student-footer">
|
||||
<span>${student.grade} - ${student.section}</span>
|
||||
<span style="color: var(--primary-green); font-weight: 600;">Details ›</span>
|
||||
</div>
|
||||
`;
|
||||
card.addEventListener('click', () => openStudentModal(student.id));
|
||||
container.appendChild(card);
|
||||
});
|
||||
}
|
||||
|
||||
function filterStudentData() {
|
||||
return MOCK_STUDENTS.filter(s => {
|
||||
const matchesSearch = s.name.toLowerCase().includes(searchQuery.toLowerCase()) ||
|
||||
s.lrn.includes(searchQuery) ||
|
||||
s.section.toLowerCase().includes(searchQuery.toLowerCase());
|
||||
|
||||
if (studentFilter === 'all') return matchesSearch;
|
||||
if (studentFilter === 'male') return matchesSearch && s.sex === 'Male';
|
||||
if (studentFilter === 'female') return matchesSearch && s.sex === 'Female';
|
||||
return matchesSearch && s.grade.toLowerCase().includes(studentFilter.toLowerCase());
|
||||
});
|
||||
}
|
||||
|
||||
function setStudentFilter(filterVal, element) {
|
||||
studentFilter = filterVal;
|
||||
document.querySelectorAll('.filter-chips .chip').forEach(c => c.classList.remove('active'));
|
||||
if (element) element.classList.add('active');
|
||||
renderStudentsTable();
|
||||
renderMobileStudentsList();
|
||||
}
|
||||
|
||||
function onSearchInput(query) {
|
||||
searchQuery = query;
|
||||
renderStudentsTable();
|
||||
renderMobileStudentsList();
|
||||
}
|
||||
|
||||
// ── Documents Screen ───────────────────────────────────────────
|
||||
function renderDocsList() {
|
||||
const winContainer = document.getElementById('docs-grid-windows');
|
||||
const phoneContainer = document.getElementById('docs-list-phone');
|
||||
|
||||
if (winContainer) {
|
||||
winContainer.innerHTML = MOCK_DOCS.map(doc => `
|
||||
<div class="content-card" style="display: flex; justify-content: space-between; align-items: center;">
|
||||
<div>
|
||||
<div style="font-weight: 700; font-size: 14px; margin-bottom: 2px;">${doc.name}</div>
|
||||
<div style="font-size: 12px; color: var(--win-text-muted);">${doc.code} • ${doc.count} Verified Records</div>
|
||||
</div>
|
||||
<div style="display: flex; align-items: center; gap: 12px;">
|
||||
<span class="status-badge badge-complete">${doc.status}</span>
|
||||
<button class="btn-secondary" style="height: 32px; padding: 0 12px;" onclick="openDocPreviewModal('${doc.name}', '${doc.code}')">Preview Scan</button>
|
||||
</div>
|
||||
</div>
|
||||
`).join('');
|
||||
}
|
||||
|
||||
if (phoneContainer) {
|
||||
phoneContainer.innerHTML = MOCK_DOCS.map(doc => `
|
||||
<div class="mobile-student-card" onclick="openDocPreviewModal('${doc.name}', '${doc.code}')">
|
||||
<div class="m-student-header">
|
||||
<span class="m-student-name" style="font-size: 13px;">${doc.name}</span>
|
||||
<span class="status-badge badge-complete">${doc.status}</span>
|
||||
</div>
|
||||
<div class="m-student-footer" style="margin-top: 6px;">
|
||||
<span>${doc.count} submissions</span>
|
||||
<span style="color: var(--primary-green); font-weight: 600;">View ›</span>
|
||||
</div>
|
||||
</div>
|
||||
`).join('');
|
||||
}
|
||||
}
|
||||
|
||||
// ── Academic Screen ────────────────────────────────────────────
|
||||
function renderClassesList() {
|
||||
const winContainer = document.getElementById('classes-grid-windows');
|
||||
const phoneContainer = document.getElementById('classes-list-phone');
|
||||
|
||||
const html = MOCK_CLASSES.map(cls => `
|
||||
<div class="content-card">
|
||||
<div class="card-header" style="margin-bottom: 8px;">
|
||||
<span style="font-size: 12px; font-weight: 700; color: var(--primary-green); text-transform: uppercase;">${cls.grade}</span>
|
||||
<span style="font-size: 11px; font-weight: 600; color: var(--win-text-muted);">${cls.students}/${cls.max} Students</span>
|
||||
</div>
|
||||
<div style="font-size: 16px; font-weight: 700; color: var(--win-text); margin-bottom: 4px;">Section ${cls.section}</div>
|
||||
<div style="font-size: 12px; color: var(--win-text-muted); margin-bottom: 12px;">Adviser: ${cls.adviser}</div>
|
||||
<div class="bar-track" style="height: 6px;">
|
||||
<div class="bar-fill" style="width: ${(cls.students / cls.max) * 100}%;"></div>
|
||||
</div>
|
||||
</div>
|
||||
bar.innerHTML = SCREENS.map(s => `
|
||||
<button class="screen-pill ${s.id === currentScreen ? 'active' : ''}" data-screen="${s.id}" onclick="setScreen('${s.id}')">
|
||||
<span>${s.icon}</span>
|
||||
<span>${s.name}</span>
|
||||
</button>
|
||||
`).join('');
|
||||
|
||||
if (winContainer) winContainer.innerHTML = html;
|
||||
if (phoneContainer) phoneContainer.innerHTML = html;
|
||||
}
|
||||
|
||||
// ── Student Detail Modal ───────────────────────────────────────
|
||||
function openStudentModal(id) {
|
||||
const student = MOCK_STUDENTS.find(s => s.id === id);
|
||||
if (!student) return;
|
||||
|
||||
const modal = document.getElementById('student-modal');
|
||||
const body = document.getElementById('student-modal-body');
|
||||
|
||||
body.innerHTML = `
|
||||
<div style="display: flex; align-items: center; gap: 14px; padding-bottom: 12px; border-bottom: 1px solid var(--win-border);">
|
||||
<div style="width: 52px; height: 52px; border-radius: 50%; background: var(--primary-green); color: #fff; display: flex; align-items: center; justify-content: center; font-size: 20px; font-weight: 700;">
|
||||
${student.name.charAt(0)}
|
||||
</div>
|
||||
<div>
|
||||
<h4 style="font-size: 16px; font-weight: 700;">${student.name}</h4>
|
||||
<span style="font-size: 12px; color: var(--win-text-muted);">LRN: ${student.lrn} • ${student.grade} - ${student.section}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="info-row"><span class="info-label">Gender:</span><span class="info-val">${student.sex}</span></div>
|
||||
<div class="info-row"><span class="info-label">Age:</span><span class="info-val">${student.age} years old</span></div>
|
||||
<div class="info-row"><span class="info-label">Enrollment Status:</span><span class="info-val"><span class="status-badge badge-enrolled">${student.status}</span></span></div>
|
||||
<div class="info-row"><span class="info-label">Parent / Guardian Contact:</span><span class="info-val">${student.contact}</span></div>
|
||||
<div class="info-row"><span class="info-label">School:</span><span class="info-val">Talipan National High School</span></div>
|
||||
|
||||
<div style="margin-top: 10px;">
|
||||
<h5 style="font-size: 13px; font-weight: 700; margin-bottom: 8px;">Submitted Document Requirements</h5>
|
||||
<div style="display: flex; flex-direction: column; gap: 6px;">
|
||||
<div style="display: flex; justify-content: space-between; font-size: 12px; padding: 6px 10px; background: var(--win-surface-subtle); border-radius: 6px;">
|
||||
<span>PSA Birth Certificate</span>
|
||||
<span style="color: var(--primary-green); font-weight: 600;">✓ Verified</span>
|
||||
</div>
|
||||
<div style="display: flex; justify-content: space-between; font-size: 12px; padding: 6px 10px; background: var(--win-surface-subtle); border-radius: 6px;">
|
||||
<span>Form 137 / SF10</span>
|
||||
<span style="color: var(--primary-green); font-weight: 600;">✓ Verified</span>
|
||||
</div>
|
||||
<div style="display: flex; justify-content: space-between; font-size: 12px; padding: 6px 10px; background: var(--win-surface-subtle); border-radius: 6px;">
|
||||
<span>Form 138 / SF9</span>
|
||||
<span style="color: var(--primary-green); font-weight: 600;">✓ Verified</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
modal.classList.add('active');
|
||||
function setScreen(screenId) {
|
||||
currentScreen = screenId;
|
||||
updateView();
|
||||
}
|
||||
|
||||
function openDocPreviewModal(name, code) {
|
||||
const modal = document.getElementById('doc-preview-modal');
|
||||
const title = document.getElementById('doc-preview-title');
|
||||
const codeEl = document.getElementById('doc-preview-code');
|
||||
function updateView() {
|
||||
// Update screen pills
|
||||
document.querySelectorAll('.screen-pill').forEach(pill => {
|
||||
pill.classList.toggle('active', pill.getAttribute('data-screen') === currentScreen);
|
||||
});
|
||||
|
||||
if (title) title.textContent = name;
|
||||
if (codeEl) codeEl.textContent = code;
|
||||
// Windows Desktop Image
|
||||
const winImg = document.getElementById('win-screen-img');
|
||||
if (winImg) {
|
||||
winImg.style.opacity = '0';
|
||||
setTimeout(() => {
|
||||
winImg.src = `screenshots/windows/${currentScreen}.webp`;
|
||||
winImg.style.opacity = '1';
|
||||
}, 120);
|
||||
}
|
||||
|
||||
modal.classList.add('active');
|
||||
// Android Mobile Image
|
||||
const phoneImg = document.getElementById('phone-screen-img');
|
||||
if (phoneImg) {
|
||||
phoneImg.style.opacity = '0';
|
||||
setTimeout(() => {
|
||||
phoneImg.src = `screenshots/android/${currentScreen}.webp`;
|
||||
phoneImg.style.opacity = '1';
|
||||
}, 120);
|
||||
}
|
||||
|
||||
// Update Dynamic Hotspots
|
||||
updateHotspots();
|
||||
}
|
||||
|
||||
// ── Dynamic Hotspots ──────────────────────────────────────────
|
||||
function updateHotspots() {
|
||||
const addStudentHotspot = document.getElementById('hotspot-add-student');
|
||||
const settingsUpdateHotspot = document.getElementById('hotspot-settings-update');
|
||||
|
||||
if (addStudentHotspot) {
|
||||
addStudentHotspot.style.display = currentScreen === 'students' ? 'block' : 'none';
|
||||
}
|
||||
|
||||
if (settingsUpdateHotspot) {
|
||||
settingsUpdateHotspot.style.display = currentScreen === 'settings' ? 'block' : 'none';
|
||||
}
|
||||
}
|
||||
|
||||
// ── Keyboard Shortcuts (1-8 and Left/Right Arrows) ────────────
|
||||
function setupKeyboardNavigation() {
|
||||
document.addEventListener('keydown', (e) => {
|
||||
// 1-8 keys
|
||||
const num = parseInt(e.key);
|
||||
if (!isNaN(num) && num >= 1 && num <= SCREENS.length) {
|
||||
setScreen(SCREENS[num - 1].id);
|
||||
return;
|
||||
}
|
||||
|
||||
// Arrow keys
|
||||
const currentIndex = SCREENS.findIndex(s => s.id === currentScreen);
|
||||
if (e.key === 'ArrowRight') {
|
||||
const nextIndex = (currentIndex + 1) % SCREENS.length;
|
||||
setScreen(SCREENS[nextIndex].id);
|
||||
} else if (e.key === 'ArrowLeft') {
|
||||
const prevIndex = (currentIndex - 1 + SCREENS.length) % SCREENS.length;
|
||||
setScreen(SCREENS[prevIndex].id);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// ── Interactive Modals ─────────────────────────────────────────
|
||||
function openAddStudentModal() {
|
||||
const modal = document.getElementById('add-student-modal');
|
||||
modal.classList.add('active');
|
||||
if (modal) modal.classList.add('active');
|
||||
}
|
||||
|
||||
function openUpdateCheckModal() {
|
||||
const modal = document.getElementById('update-modal');
|
||||
if (modal) {
|
||||
modal.classList.add('active');
|
||||
simulateUpdateProcess();
|
||||
}
|
||||
}
|
||||
|
||||
function closeModal(id) {
|
||||
@@ -373,76 +151,57 @@ function closeModal(id) {
|
||||
if (modal) modal.classList.remove('active');
|
||||
}
|
||||
|
||||
// ── Settings Collapsible Sections ──────────────────────────────
|
||||
function toggleSettingsSection(id) {
|
||||
const body = document.getElementById(`settings-body-${id}`);
|
||||
const arrow = document.getElementById(`settings-arrow-${id}`);
|
||||
if (!body) return;
|
||||
|
||||
const isVisible = body.style.display !== 'none';
|
||||
body.style.display = isVisible ? 'none' : 'flex';
|
||||
if (arrow) {
|
||||
arrow.style.transform = isVisible ? 'rotate(0deg)' : 'rotate(180deg)';
|
||||
}
|
||||
}
|
||||
|
||||
// ── Check For Updates Simulation ───────────────────────────────
|
||||
function simulateUpdateCheck() {
|
||||
const btn = document.getElementById('btn-check-update');
|
||||
const btnText = document.getElementById('btn-check-update-text');
|
||||
const statusBanner = document.getElementById('update-status-banner');
|
||||
// ── Simulated Update Check Workflow ────────────────────────────
|
||||
function simulateUpdateProcess() {
|
||||
const statusEl = document.getElementById('update-modal-status');
|
||||
const actionBtn = document.getElementById('btn-download-action');
|
||||
const toast = document.getElementById('browser-download-toast');
|
||||
const toastText = document.getElementById('toast-filename');
|
||||
const toastFile = document.getElementById('toast-filename');
|
||||
|
||||
if (!btn) return;
|
||||
const isWin = currentDevice === 'windows';
|
||||
const arch = isWin ? 'x64 (64-bit)' : 'ARM64 (64-bit)';
|
||||
const file = isWin ? 'TIS_RMS_Setup_v1.0.24.exe' : 'TIS_RMS_64_v1.0.24.apk';
|
||||
|
||||
btn.disabled = true;
|
||||
btnText.textContent = 'Checking GitHub releases...';
|
||||
statusBanner.style.display = 'none';
|
||||
statusEl.innerHTML = `
|
||||
<div style="display: flex; align-items: center; gap: 10px; color: var(--accent-emerald);">
|
||||
<div style="width: 16px; height: 16px; border: 2px solid var(--accent-emerald); border-top-color: transparent; border-radius: 50%; animation: spin 0.8s linear infinite;"></div>
|
||||
<span>Querying GitHub releases for ${arch}...</span>
|
||||
</div>
|
||||
`;
|
||||
actionBtn.style.display = 'none';
|
||||
|
||||
setTimeout(() => {
|
||||
btn.disabled = false;
|
||||
btnText.textContent = 'Check for Updates';
|
||||
|
||||
// Architecture-matched file simulation
|
||||
const isWin = currentDevice === 'windows';
|
||||
const filename = isWin ? 'TIS_RMS_Setup_v1.0.24.exe' : 'TIS_RMS_64_v1.0.24.apk';
|
||||
const archLabel = isWin ? 'x64 (64-bit)' : 'ARM64 (64-bit)';
|
||||
|
||||
statusBanner.style.display = 'block';
|
||||
statusBanner.innerHTML = `
|
||||
<div style="display: flex; align-items: center; gap: 8px; font-weight: 600; color: var(--primary-green); margin-bottom: 4px;">
|
||||
<svg width="18" height="18" fill="none" stroke="currentColor" stroke-width="2" viewBox="0 0 24 24"><path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"></path><polyline points="7 10 12 15 17 10"></polyline><line x1="12" y1="15" x2="12" y2="3"></line></svg>
|
||||
New Version Detected: v1.0.24 for ${archLabel}
|
||||
</div>
|
||||
<div style="font-size: 12px; color: var(--win-text);">
|
||||
Architecture matched: <strong>${filename}</strong>.<br/>
|
||||
Simulating launching download in device's default browser...
|
||||
statusEl.innerHTML = `
|
||||
<div style="background: rgba(25, 135, 84, 0.15); border: 1px solid rgba(25, 135, 84, 0.35); padding: 12px; border-radius: 8px;">
|
||||
<div style="font-weight: 700; color: var(--accent-emerald); margin-bottom: 4px;">
|
||||
✓ Update Detected: v1.0.24
|
||||
</div>
|
||||
<div style="font-size: 12px; color: #cbd5e1; line-height: 1.5;">
|
||||
Architecture: <strong>${arch}</strong><br/>
|
||||
Matched Asset: <strong>${file}</strong><br/>
|
||||
Opening direct link in default device browser...
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
// Show simulated default browser toast
|
||||
if (toast && toastText) {
|
||||
toastText.textContent = filename;
|
||||
toast.classList.add('active');
|
||||
setTimeout(() => {
|
||||
toast.classList.remove('active');
|
||||
}, 5000);
|
||||
}
|
||||
}, 1200);
|
||||
actionBtn.style.display = 'inline-flex';
|
||||
actionBtn.onclick = () => {
|
||||
closeModal('update-modal');
|
||||
triggerDownloadToast(file);
|
||||
};
|
||||
|
||||
triggerDownloadToast(file);
|
||||
}, 1400);
|
||||
}
|
||||
|
||||
// ── Clock for Android Status Bar ───────────────────────────────
|
||||
function startClock() {
|
||||
function updateTime() {
|
||||
const el = document.getElementById('phone-time');
|
||||
if (!el) return;
|
||||
const now = new Date();
|
||||
let hours = now.getHours();
|
||||
let mins = now.getMinutes();
|
||||
mins = mins < 10 ? '0' + mins : mins;
|
||||
el.textContent = `${hours}:${mins}`;
|
||||
function triggerDownloadToast(filename) {
|
||||
const toast = document.getElementById('browser-download-toast');
|
||||
const toastFile = document.getElementById('toast-filename');
|
||||
if (toast && toastFile) {
|
||||
toastFile.textContent = filename;
|
||||
toast.classList.add('active');
|
||||
setTimeout(() => {
|
||||
toast.classList.remove('active');
|
||||
}, 5000);
|
||||
}
|
||||
updateTime();
|
||||
setInterval(updateTime, 30000);
|
||||
}
|
||||
Reference in new issue
Block a user