37970-vm/public/app.js
Flatlogic Bot 33ad70235b v5
2026-01-30 15:36:51 +00:00

233 lines
8.0 KiB
JavaScript

const API_URL = '/api/v1/index.php?request=';
const state = {
user: JSON.parse(localStorage.getItem('user')) || null,
token: localStorage.getItem('token') || null,
};
const routes = {
'/': homePage,
'/login': loginPage,
'/learners': learnersPage,
'/assessments': assessmentsPage,
};
async function init() {
window.addEventListener('hashchange', router);
router();
updateNav();
}
function router() {
const hash = window.location.hash || '#/';
const path = hash.substring(1);
const page = routes[path] || routes['/'];
page();
}
function updateNav() {
const navLinks = document.getElementById('nav-links');
if (!state.token) {
navLinks.innerHTML = `
<li class="nav-item"><a class="nav-link" href="#/login">Login</a></li>
`;
return;
}
navLinks.innerHTML = `
<li class="nav-item"><a class="nav-link" href="#/">Dashboard</a></li>
<li class="nav-item"><a class="nav-link" href="#/learners">Learners</a></li>
<li class="nav-item"><a class="nav-link" href="#/assessments">Assessments</a></li>
<li class="nav-item"><a class="nav-link" href="#" id="logout-btn">Logout</a></li>
`;
document.getElementById('logout-btn').addEventListener('click', (e) => {
e.preventDefault();
logout();
});
}
function logout() {
localStorage.removeItem('token');
localStorage.removeItem('user');
state.token = null;
state.user = null;
window.location.hash = '#/login';
updateNav();
}
async function apiFetch(endpoint, options = {}) {
const url = API_URL + endpoint;
const defaultOptions = {
headers: {
'Content-Type': 'application/json',
...(state.token ? { 'Authorization': `Bearer ${state.token}` } : {})
}
};
const response = await fetch(url, { ...defaultOptions, ...options });
const data = await response.json();
if (response.status === 401) {
logout();
throw new Error('Unauthorized');
}
return data;
}
function render(html) {
document.getElementById('content').innerHTML = html;
}
async function homePage() {
if (!state.token) {
window.location.hash = '#/login';
return;
}
render(`
<div class="p-5 mb-4 bg-white rounded-3 shadow-sm">
<h1 class="display-5 fw-bold">Welcome, ${state.user.email}</h1>
<p class="col-md-8 fs-4">This is the new static frontend for SOMS Platform. Everything is served from an API.</p>
<hr class="my-4">
<div class="row g-4">
<div class="col-md-4">
<div class="card h-100 border-0 shadow-sm">
<div class="card-body">
<h5 class="card-title">Learners</h5>
<p class="card-text">Manage your student records.</p>
<a href="#/learners" class="btn btn-outline-primary">View All</a>
</div>
</div>
</div>
<div class="col-md-4">
<div class="card h-100 border-0 shadow-sm">
<div class="card-body">
<h5 class="card-title">Assessments</h5>
<p class="card-text">Record marks and track progress.</p>
<a href="#/assessments" class="btn btn-outline-primary">Open Hub</a>
</div>
</div>
</div>
</div>
</div>
`);
}
function loginPage() {
render(`
<div class="row justify-content-center">
<div class="col-md-5">
<div class="card shadow-lg border-0">
<div class="card-body p-5">
<h2 class="fw-bold text-center mb-4">Sign In</h2>
<form id="login-form">
<div class="mb-3">
<label class="form-label">Email</label>
<input type="email" id="email" class="form-control" value="admin@sowetohigh.edu.za" required>
</div>
<div class="mb-4">
<label class="form-label">Password</label>
<input type="password" id="password" class="form-control" value="password" required>
</div>
<button type="submit" class="btn btn-primary w-100">Login</button>
</form>
</div>
</div>
</div>
</div>
`);
document.getElementById('login-form').addEventListener('submit', async (e) => {
e.preventDefault();
const email = document.getElementById('email').value;
const password = document.getElementById('password').value;
try {
const data = await apiFetch('/auth/login', {
method: 'POST',
body: JSON.stringify({ email, password })
});
if (data.token) {
localStorage.setItem('token', data.token);
localStorage.setItem('user', JSON.stringify(data.user));
state.token = data.token;
state.user = data.user;
updateNav();
window.location.hash = '#/';
}
} catch (err) {
alert('Login failed: ' + err.message);
}
});
}
async function learnersPage() {
render('<div class="text-center"><div class="spinner-border text-primary"></div></div>');
try {
const learners = await apiFetch('/learners');
let html = `
<div class="d-flex justify-content-between align-items-center mb-4">
<h2>Learners</h2>
<button class="btn btn-primary">Add New</button>
</div>
<div class="table-responsive bg-white p-3 rounded shadow-sm">
<table class="table table-hover">
<thead>
<tr>
<th>Full Name</th>
<th>Grade</th>
<th>Student ID</th>
<th>Actions</th>
</tr>
</thead>
<tbody>
`;
learners.forEach(l => {
html += `
<tr>
<td>${l.full_name}</td>
<td>${l.grade}</td>
<td>${l.student_id}</td>
<td><button class="btn btn-sm btn-outline-secondary">Edit</button></td>
</tr>
`;
});
html += '</tbody></table></div>';
render(html);
} catch (err) {
render('<div class="alert alert-danger">Failed to load learners</div>');
}
}
async function assessmentsPage() {
render('<div class="text-center"><div class="spinner-border text-primary"></div></div>');
try {
const assessments = await apiFetch('/assessments');
let html = `
<div class="d-flex justify-content-between align-items-center mb-4">
<h2>Assessments</h2>
<button class="btn btn-primary">Create New</button>
</div>
<div class="row g-3">
`;
assessments.forEach(a => {
html += `
<div class="col-md-4">
<div class="card shadow-sm border-0">
<div class="card-body">
<h5 class="card-title">${a.title}</h5>
<h6 class="card-subtitle mb-2 text-muted">${a.subject} - ${a.type}</h6>
<button class="btn btn-sm btn-primary">Record Marks</button>
</div>
</div>
</div>
`;
});
html += '</div>';
render(html);
} catch (err) {
render('<div class="alert alert-danger">Failed to load assessments</div>');
}
}
init();