233 lines
8.0 KiB
JavaScript
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();
|