This commit is contained in:
Alvin
2025-10-21 20:54:03 +02:00
parent 97c80d7800
commit a5c73ad907
35 changed files with 4899 additions and 0 deletions

108
public/js/add-item.js Normal file
View File

@@ -0,0 +1,108 @@
// Add Item page functionality
// Check authentication
function checkAuth() {
const token = localStorage.getItem('token');
const role = localStorage.getItem('userRole');
if (!token || role !== 'admin') {
window.location.href = '/index.html';
}
}
// Initialize page
function initializePage() {
checkAuth();
setupEventListeners();
displayUserInfo();
setupImagePreview();
}
// Display user info
function displayUserInfo() {
const username = localStorage.getItem('username');
document.getElementById('userInfo').textContent = `Admin: ${username}`;
}
// Set up event listeners
function setupEventListeners() {
document.getElementById('addItemForm').addEventListener('submit', addItem);
document.getElementById('logoutBtn').addEventListener('click', logout);
}
// Handle logout
function logout() {
localStorage.removeItem('token');
localStorage.removeItem('userRole');
localStorage.removeItem('username');
window.location.href = '/index.html';
}
// Set up image preview
function setupImagePreview() {
const imageInput = document.getElementById('itemImage');
const previewContainer = document.getElementById('imagePreview');
const removeButton = document.getElementById('removeImage');
imageInput.addEventListener('change', () => {
const file = imageInput.files[0];
if (file) {
const reader = new FileReader();
reader.onload = (e) => {
previewContainer.innerHTML = `<img src="${e.target.result}" class="img-fluid" alt="Preview">`;
removeButton.style.display = 'block';
};
reader.readAsDataURL(file);
} else {
previewContainer.innerHTML = '';
removeButton.style.display = 'none';
}
});
// Handle remove button click
removeButton.addEventListener('click', () => {
imageInput.value = '';
previewContainer.innerHTML = '';
removeButton.style.display = 'none';
});
}
// Add new item
async function addItem(e) {
e.preventDefault();
const formData = new FormData();
formData.append('name', document.getElementById('itemName').value);
formData.append('location', document.getElementById('itemLocation').value);
formData.append('description', document.getElementById('itemDescription').value);
formData.append('quantity', document.getElementById('itemQuantity').value);
const imageFile = document.getElementById('itemImage').files[0];
if (imageFile) {
formData.append('image', imageFile);
}
try {
const response = await fetch('/api/items', {
method: 'POST',
headers: {
'Authorization': `Bearer ${localStorage.getItem('token')}`
},
body: formData
});
if (response.ok) {
alert('Item added successfully!');
window.location.href = 'admin.html';
} else {
const error = await response.json();
alert(error.message || 'Failed to add item');
}
} catch (error) {
console.error('Error adding item:', error);
alert('Failed to add item');
}
}
// Initialize the page when loaded
document.addEventListener('DOMContentLoaded', initializePage);

View File

View File

@@ -0,0 +1,188 @@
// Admin reservations page functionality
let reservations = [];
// Check authentication
function checkAuth() {
const token = localStorage.getItem('token');
const role = localStorage.getItem('userRole');
if (!token || role !== 'admin') {
window.location.href = '/index.html';
}
}
// Initialize page
async function initializePage() {
checkAuth();
await loadReservations();
setupEventListeners();
displayUserInfo();
}
// Display user info
function displayUserInfo() {
const username = localStorage.getItem('username');
document.getElementById('userInfo').textContent = `Admin: ${username}`;
}
// Load and filter reservations
async function loadReservations() {
try {
const response = await fetch('/api/reservations', {
headers: {
'Authorization': `Bearer ${localStorage.getItem('token')}`
}
});
reservations = await response.json();
filterAndDisplayReservations();
} catch (error) {
console.error('Error loading reservations:', error);
alert('Failed to load reservations');
}
}
// Filter and display reservations
function filterAndDisplayReservations() {
const locationFilter = document.getElementById('locationFilter').value;
const statusFilter = document.getElementById('statusFilter').value;
const filteredReservations = reservations.filter(reservation => {
const locationMatch = locationFilter === 'all' || reservation.location === locationFilter;
const statusMatch = statusFilter === 'all' || reservation.status === statusFilter;
return locationMatch && statusMatch;
});
const reservationsList = document.getElementById('reservationsList');
reservationsList.innerHTML = filteredReservations.map(reservation => `
<tr>
<td><strong>${reservation.studentName}</strong></td>
<td>${reservation.itemName}</td>
<td><span class="badge bg-info">${reservation.quantity || 1}</span></td>
<td>${reservation.location}</td>
<td>${new Date(reservation.reservedDate).toLocaleDateString('en-US', {
year: 'numeric',
month: 'short',
day: 'numeric'
})}</td>
<td><span class="badge reservation-${reservation.status.toLowerCase()}">${reservation.status}</span></td>
<td>
<div class="btn-group btn-group-sm" role="group">
${reservation.status === 'PENDING' ? `
<button class="btn btn-success" onclick="updateReservation('${reservation._id}', 'APPROVED')" title="Approve">
<i class="bi bi-check-lg"></i><span class="btn-text"> Approve</span>
</button>
<button class="btn btn-warning" onclick="updateReservation('${reservation._id}', 'REJECTED')" title="Reject">
<i class="bi bi-x-lg"></i><span class="btn-text"> Reject</span>
</button>
` : reservation.status === 'APPROVED' ? `
<button class="btn btn-info" onclick="updateReservation('${reservation._id}', 'RETURNED')" title="Mark as Returned">
<i class="bi bi-arrow-return-left"></i><span class="btn-text"> Return</span>
</button>
` : reservation.status === 'RETURNED' ? `
<button class="btn btn-secondary" onclick="archiveReservation('${reservation._id}')" title="Archive Reservation">
<i class="bi bi-archive"></i><span class="btn-text"> Archive</span>
</button>
` : ''}
<button class="btn btn-danger" onclick="deleteReservation('${reservation._id}')" title="Delete">
<i class="bi bi-trash"></i><span class="btn-text"> Delete</span>
</button>
</div>
</td>
</tr>
`).join('');
}
// Update reservation status
async function updateReservation(reservationId, status) {
try {
const response = await fetch(`/api/reservations/${reservationId}`, {
method: 'PATCH',
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${localStorage.getItem('token')}`
},
body: JSON.stringify({ status })
});
if (response.ok) {
await loadReservations();
} else {
const error = await response.json();
alert(error.message || 'Failed to update reservation');
}
} catch (error) {
console.error('Error updating reservation:', error);
alert('Failed to update reservation');
}
}
// Delete reservation
async function deleteReservation(reservationId) {
if (!confirm('Are you sure you want to delete this reservation?')) return;
try {
const response = await fetch(`/api/reservations/${reservationId}`, {
method: 'DELETE',
headers: {
'Authorization': `Bearer ${localStorage.getItem('token')}`
}
});
if (response.ok) {
await loadReservations();
} else {
const error = await response.json();
alert(error.message || 'Failed to delete reservation');
}
} catch (error) {
console.error('Error deleting reservation:', error);
alert('Failed to delete reservation');
}
}
// Archive reservation
async function archiveReservation(reservationId) {
if (!confirm('Are you sure you want to archive this reservation? It will be hidden from the list but remain in the database.')) return;
try {
const response = await fetch(`/api/reservations/${reservationId}/archive`, {
method: 'PATCH',
headers: {
'Authorization': `Bearer ${localStorage.getItem('token')}`
}
});
if (response.ok) {
await loadReservations();
// Show success message
const alert = document.createElement('div');
alert.className = 'alert alert-success alert-dismissible fade show';
alert.innerHTML = `
Reservation archived successfully!
<button type="button" class="btn-close" data-bs-dismiss="alert"></button>
`;
const container = document.querySelector('.container');
container.prepend(alert);
setTimeout(() => alert.remove(), 3000);
} else {
const error = await response.json();
alert(error.message || 'Failed to archive reservation');
}
} catch (error) {
console.error('Error archiving reservation:', error);
alert('Failed to archive reservation');
}
}
// Set up event listeners
function setupEventListeners() {
document.getElementById('locationFilter').addEventListener('change', filterAndDisplayReservations);
document.getElementById('statusFilter').addEventListener('change', filterAndDisplayReservations);
document.getElementById('logoutBtn').addEventListener('click', () => {
localStorage.clear();
window.location.href = '/index.html';
});
}
// Initialize the page when DOM is loaded
document.addEventListener('DOMContentLoaded', initializePage);

436
public/js/admin.js Normal file
View File

@@ -0,0 +1,436 @@
// Admin dashboard functionality
let items = [];
let reservations = [];
let currentView = 'grid'; // Default view mode
// Check authentication
function checkAuth() {
const token = localStorage.getItem('token');
const role = localStorage.getItem('userRole');
if (!token || role !== 'admin') {
window.location.href = '/index.html';
}
}
// Initialize page
async function initializePage() {
try {
checkAuth();
await loadItems();
displayUserInfo();
setupEventListeners(); // Move this after displayUserInfo to ensure DOM is ready
console.log('Page initialization complete');
} catch (error) {
console.error('Error during page initialization:', error);
throw error; // Re-throw to be caught by the outer try-catch
}
}
// Display user info
function displayUserInfo() {
const username = localStorage.getItem('username');
document.getElementById('userInfo').textContent = `Admin: ${username}`;
}
// Load items from server
async function loadItems() {
try {
console.log('Loading items...');
const response = await fetch('/api/items', {
headers: {
'Authorization': `Bearer ${localStorage.getItem('token')}`
}
});
const data = await response.json();
console.log('Received items:', data);
if (!response.ok) {
throw new Error(data.message || 'Failed to load items');
}
items = data;
displayItems();
} catch (error) {
console.error('Error loading items:', error);
alert('Failed to load items: ' + error.message);
}
}
// Display items based on current view mode
function displayItems() {
if (currentView === 'grid') {
displayGridView();
} else {
displayListView();
}
}
// Display items in grid view
function displayGridView() {
const itemsGrid = document.getElementById('itemsGrid');
document.getElementById('itemsList').classList.add('d-none');
itemsGrid.classList.remove('d-none');
itemsGrid.innerHTML = items.map(item => `
<div class="col-md-4 col-lg-3">
<div class="card item-card">
<img src="${item.imageUrl || '/images/default-item.png'}" class="item-image" alt="${item.name}">
<div class="card-body">
<h5 class="card-title">${item.name}</h5>
<p class="card-text text-muted">${item.description || 'No description available'}</p>
<p class="card-text">Location: ${item.location}</p>
<p class="card-text">Quantity: ${item.quantity}</p>
<p class="card-text">Reserved: ${item.reserved || 0}</p>
</div>
<div class="card-footer">
<button class="btn btn-sm btn-warning" onclick="editItem('${item._id}')">Edit</button>
<button class="btn btn-sm btn-danger" onclick="deleteItem('${item._id}')">Delete</button>
</div>
</div>
</div>
`).join('');
}
// Display items in list view
function displayListView() {
const itemsList = document.getElementById('itemsList');
const itemsGrid = document.getElementById('itemsGrid');
itemsGrid.classList.add('d-none');
itemsList.classList.remove('d-none');
const itemsListBody = document.getElementById('itemsListBody');
itemsListBody.innerHTML = items.map(item => `
<tr>
<td><img src="${item.imageUrl || '/images/default-item.png'}" class="item-thumbnail" alt="${item.name}"></td>
<td>${item.name}</td>
<td>${item.description || 'No description available'}</td>
<td>${item.location}</td>
<td>${item.quantity}</td>
<td>${item.reserved || 0}</td>
<td>
<button class="btn btn-sm btn-warning" onclick="editItem('${item._id}')">Edit</button>
<button class="btn btn-sm btn-danger" onclick="deleteItem('${item._id}')">Delete</button>
</td>
</tr>
`).join('');
}
// Load reservations
async function loadReservations() {
try {
const response = await fetch('/api/reservations', {
headers: {
'Authorization': `Bearer ${localStorage.getItem('token')}`
}
});
reservations = await response.json();
displayReservations();
} catch (error) {
console.error('Error loading reservations:', error);
alert('Failed to load reservations');
}
}
// Display reservations in table
function displayReservations() {
const reservationsList = document.getElementById('reservationsList');
reservationsList.innerHTML = reservations.map(reservation => `
<tr>
<td>${reservation.studentName}</td>
<td>${reservation.itemName}</td>
<td>${reservation.location}</td>
<td>${new Date(reservation.reservedDate).toLocaleDateString()}</td>
<td><span class="badge reservation-${reservation.status.toLowerCase()}">${reservation.status}</span></td>
<td>
${reservation.status === 'PENDING' ? `
<button class="btn btn-sm btn-success" onclick="updateReservation('${reservation._id}', 'APPROVED')">Approve</button>
<button class="btn btn-sm btn-danger" onclick="updateReservation('${reservation._id}', 'REJECTED')">Reject</button>
` : ''}
</td>
</tr>
`).join('');
}
// Delete item
async function deleteItem(itemId) {
if (!confirm('Are you sure you want to delete this item?')) return;
try {
const response = await fetch(`/api/items/${itemId}`, {
method: 'DELETE',
headers: {
'Authorization': `Bearer ${localStorage.getItem('token')}`
}
});
if (response.ok) {
await loadItems();
} else {
const error = await response.json();
alert(error.message || 'Failed to delete item');
}
} catch (error) {
console.error('Error deleting item:', error);
alert('Failed to delete item');
}
}
// Update reservation status
async function updateReservation(reservationId, status) {
try {
const response = await fetch(`/api/reservations/${reservationId}`, {
method: 'PATCH',
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${localStorage.getItem('token')}`
},
body: JSON.stringify({ status })
});
if (response.ok) {
await loadReservations();
await loadItems(); // Refresh items to update quantities
} else {
const error = await response.json();
alert(error.message || 'Failed to update reservation');
}
} catch (error) {
console.error('Error updating reservation:', error);
alert('Failed to update reservation');
}
}
// Edit item functionality
let editModal = null;
// Initialize Bootstrap modal when DOM is loaded
function initializeModal() {
const modalElement = document.getElementById('editItemModal');
if (!modalElement) {
console.error('Modal element not found in the DOM');
return;
}
try {
editModal = new bootstrap.Modal(modalElement);
console.log('Modal initialized successfully');
} catch (error) {
console.error('Error initializing modal:', error);
}
}
async function editItem(itemId) {
try {
console.log('Fetching item with ID:', itemId); // Debug log
const response = await fetch(`/api/items/${itemId}`, {
headers: {
'Authorization': `Bearer ${localStorage.getItem('token')}`
}
});
const data = await response.json();
console.log('Response data:', data); // Debug log
if (!response.ok) {
console.error('Server error:', data);
throw new Error(data.message || 'Failed to fetch item');
}
if (!data || !data._id) {
throw new Error('Invalid item data received');
}
document.getElementById('editItemId').value = data._id;
document.getElementById('editItemName').value = data.name;
document.getElementById('editItemDescription').value = data.description || '';
document.getElementById('editItemLocation').value = data.location;
document.getElementById('editItemQuantity').value = data.quantity;
// Show current image if it exists
const imagePreview = document.getElementById('editImagePreview');
const removeButton = document.getElementById('editRemoveImage');
imagePreview.innerHTML = '';
if (data.imageUrl) {
const img = document.createElement('img');
img.src = data.imageUrl;
img.classList.add('modal-item-image');
imagePreview.appendChild(img);
removeButton.style.display = 'block';
imagePreview.dataset.currentImageUrl = data.imageUrl;
} else {
removeButton.style.display = 'none';
imagePreview.dataset.currentImageUrl = '';
}
// Show the modal
if (!editModal) {
console.log('Modal not initialized, attempting to initialize now');
initializeModal();
}
if (editModal) {
editModal.show();
} else {
throw new Error('Could not initialize modal. Please try again.');
}
} catch (error) {
console.error('Error fetching item:', error);
alert('Error loading item details: ' + error.message);
}
}
async function submitEditItem() {
const itemId = document.getElementById('editItemId').value;
const formData = new FormData();
formData.append('name', document.getElementById('editItemName').value);
formData.append('description', document.getElementById('editItemDescription').value);
formData.append('location', document.getElementById('editItemLocation').value);
formData.append('quantity', document.getElementById('editItemQuantity').value);
const imagePreview = document.getElementById('editImagePreview');
const imageFile = document.getElementById('editItemImage').files[0];
const currentImageUrl = imagePreview.dataset.currentImageUrl;
try {
// Handle image update
if (imageFile) {
// Upload new image
const imageFormData = new FormData();
imageFormData.append('image', imageFile);
const uploadResponse = await fetch('/api/upload', {
method: 'POST',
headers: {
'Authorization': `Bearer ${localStorage.getItem('token')}`
},
body: imageFormData
});
if (!uploadResponse.ok) {
throw new Error('Failed to upload image');
}
const { imageUrl } = await uploadResponse.json();
formData.append('imageUrl', imageUrl);
} else if (!currentImageUrl) {
// If no new image and no current image, explicitly set imageUrl to null
formData.append('imageUrl', '');
}
} catch (error) {
console.error('Error uploading image:', error);
alert('Failed to upload image');
return;
}
try {
const response = await fetch(`/api/items/${itemId}`, {
method: 'PUT',
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${localStorage.getItem('token')}`
},
body: JSON.stringify(Object.fromEntries(formData))
});
if (!response.ok) {
throw new Error('Update failed');
}
await loadItems(); // Refresh the items list
bootstrap.Modal.getInstance(document.getElementById('editItemModal')).hide();
alert('Item updated successfully');
} catch (error) {
console.error('Error updating item:', error);
alert('Error updating item');
}
}
// Set up event listeners
function setupEventListeners() {
// Common elements
const logoutBtn = document.getElementById('logoutBtn');
if (logoutBtn) {
logoutBtn.addEventListener('click', () => {
localStorage.clear();
window.location.href = '/index.html';
});
}
// View mode toggle
const viewModeBtns = document.querySelectorAll('.view-mode-btn');
if (viewModeBtns.length > 0) {
viewModeBtns.forEach(btn => {
btn.addEventListener('click', function() {
document.querySelectorAll('.view-mode-btn').forEach(b => b.classList.remove('active'));
this.classList.add('active');
currentView = this.dataset.mode;
displayItems();
});
});
}
// Edit form elements
const editItemImage = document.getElementById('editItemImage');
if (editItemImage) {
editItemImage.addEventListener('change', function(e) {
const file = e.target.files[0];
const imagePreview = document.getElementById('editImagePreview');
const removeButton = document.getElementById('editRemoveImage');
if (file) {
const reader = new FileReader();
reader.onload = function(e) {
imagePreview.innerHTML = '';
const img = document.createElement('img');
img.src = e.target.result;
img.classList.add('modal-item-image');
imagePreview.appendChild(img);
removeButton.style.display = 'block';
};
reader.readAsDataURL(file);
} else {
imagePreview.innerHTML = '';
if (!imagePreview.dataset.currentImageUrl) {
removeButton.style.display = 'none';
}
}
});
}
// Remove image button
const editRemoveImage = document.getElementById('editRemoveImage');
if (editRemoveImage) {
editRemoveImage.addEventListener('click', function() {
const imagePreview = document.getElementById('editImagePreview');
const imageInput = document.getElementById('editItemImage');
const removeButton = document.getElementById('editRemoveImage');
imagePreview.innerHTML = '';
imageInput.value = '';
imagePreview.dataset.currentImageUrl = '';
removeButton.style.display = 'none';
});
}
}
// Initialize the page when DOM is loaded
document.addEventListener('DOMContentLoaded', async () => {
try {
console.log('Initializing page...');
await initializePage();
console.log('Page initialized');
// Ensure modal is initialized after Bootstrap is loaded
setTimeout(() => {
console.log('Initializing modal...');
initializeModal();
console.log('Modal initialized');
}, 100);
} catch (error) {
console.error('Error during page initialization:', error);
alert('Error initializing page: ' + error.message);
}
});

36
public/js/auth.js Normal file
View File

@@ -0,0 +1,36 @@
// Authentication related functions
document.getElementById('loginForm').addEventListener('submit', async (e) => {
e.preventDefault();
const username = document.getElementById('username').value.toLowerCase(); // Convert to lowercase
const password = document.getElementById('password').value;
try {
const response = await fetch('/api/auth/login', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({ username, password })
});
const data = await response.json();
if (response.ok) {
localStorage.setItem('token', data.token);
localStorage.setItem('userRole', data.role);
localStorage.setItem('username', data.username.toLowerCase());
// Redirect based on role
if (data.role === 'admin') {
window.location.href = '/admin.html';
} else {
window.location.href = '/student.html';
}
} else {
alert(data.message || 'Login failed');
}
} catch (error) {
console.error('Login error:', error);
alert('An error occurred during login');
}
});

60
public/js/register.js Normal file
View File

@@ -0,0 +1,60 @@
// Registration form handling
document.getElementById('registrationForm').addEventListener('submit', async (e) => {
e.preventDefault();
const username = document.getElementById('username').value.toLowerCase(); // Convert to lowercase
const email = document.getElementById('email').value;
const password = document.getElementById('password').value;
const confirmPassword = document.getElementById('confirmPassword').value;
// Validate username (only allow alphanumeric and underscores)
const usernameRegex = /^[a-z0-9_]+$/;
if (!usernameRegex.test(username)) {
alert('Username can only contain letters, numbers, and underscores');
return;
}
// Validate password match
if (password !== confirmPassword) {
alert('Passwords do not match!');
return;
}
// Validate email format
const emailRegex = /^\d+@vistacollege\.nl$/;
if (!emailRegex.test(email)) {
alert('Email must be in the format: studentnumber@vistacollege.nl');
return;
}
try {
const response = await fetch('/api/auth/register', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({
username,
email,
password
})
});
const data = await response.json();
if (response.ok) {
// Store the token and user info
localStorage.setItem('token', data.token);
localStorage.setItem('userRole', data.role);
localStorage.setItem('username', data.username.toLowerCase());
// Redirect to student dashboard
window.location.href = '/student.html';
} else {
alert(data.message || 'Registration failed');
}
} catch (error) {
console.error('Registration error:', error);
alert('An error occurred during registration');
}
});

View File

@@ -0,0 +1,175 @@
// Student reservations page functionality
let reservations = [];
// Check authentication
function checkAuth() {
const token = localStorage.getItem('token');
const role = localStorage.getItem('userRole');
if (!token || role !== 'student') {
window.location.href = '/index.html';
}
}
// Initialize page
async function initializePage() {
checkAuth();
await loadReservations();
setupEventListeners();
displayUserInfo();
}
// Display user info
function displayUserInfo() {
const username = localStorage.getItem('username');
document.getElementById('userInfo').textContent = `Student: ${username}`;
}
// Load and filter reservations
async function loadReservations() {
try {
const response = await fetch('/api/reservations/my', {
headers: {
'Authorization': `Bearer ${localStorage.getItem('token')}`
}
});
if (!response.ok) {
const errorData = await response.json();
throw new Error(errorData.message || `HTTP ${response.status}`);
}
reservations = await response.json();
filterAndDisplayReservations();
} catch (error) {
console.error('Error loading reservations:', error);
// Display a user-friendly error message
const reservationsList = document.getElementById('reservationsList');
reservationsList.innerHTML = `
<tr>
<td colspan="6" class="text-center text-muted">
<i class="bi bi-exclamation-triangle"></i>
Failed to load reservations: ${error.message}
<br><small>Please try refreshing the page</small>
</td>
</tr>
`;
}
}
// Filter and display reservations
function filterAndDisplayReservations() {
const locationFilter = document.getElementById('locationFilter').value;
const statusFilter = document.getElementById('statusFilter').value;
const filteredReservations = reservations.filter(reservation => {
const locationMatch = locationFilter === 'all' || reservation.location === locationFilter;
const statusMatch = statusFilter === 'all' || reservation.status === statusFilter;
return locationMatch && statusMatch;
});
const reservationsList = document.getElementById('reservationsList');
reservationsList.innerHTML = filteredReservations.map(reservation => `
<tr>
<td><strong>${reservation.itemName}</strong></td>
<td><span class="badge bg-info">${reservation.quantity || 1}</span></td>
<td>${reservation.location}</td>
<td>${new Date(reservation.reservedDate).toLocaleDateString('en-US', {
year: 'numeric',
month: 'short',
day: 'numeric'
})}</td>
<td><span class="badge reservation-${reservation.status.toLowerCase()}">${reservation.status}</span></td>
<td>
${reservation.status === 'PENDING' ? `
<button class="btn btn-sm btn-danger" onclick="deleteReservation('${reservation._id}')" title="Cancel Reservation">
<i class="bi bi-x-circle"></i> Cancel
</button>
` : reservation.status === 'APPROVED' ? `
<button class="btn btn-sm btn-info" onclick="returnReservation('${reservation._id}')" title="Return Item">
<i class="bi bi-arrow-return-left"></i> Return
</button>
` : reservation.status === 'REJECTED' ? `
<span class="text-muted"><i class="bi bi-x-circle"></i> Rejected</span>
` : reservation.status === 'RETURNED' ? `
<span class="text-success"><i class="bi bi-check-circle"></i> Returned</span>
` : ''}
</td>
</tr>
`).join('');
}
// Delete (cancel) reservation
async function deleteReservation(reservationId) {
if (!confirm('Are you sure you want to cancel this reservation?')) return;
try {
const response = await fetch(`/api/reservations/${reservationId}`, {
method: 'DELETE',
headers: {
'Authorization': `Bearer ${localStorage.getItem('token')}`
}
});
if (response.ok) {
await loadReservations();
} else {
const error = await response.json();
alert(error.message || 'Failed to cancel reservation');
}
} catch (error) {
console.error('Error canceling reservation:', error);
alert('Failed to cancel reservation');
}
}
// Return reservation (mark as returned)
async function returnReservation(reservationId) {
if (!confirm('Are you sure you want to return this item?')) return;
try {
const response = await fetch(`/api/reservations/${reservationId}`, {
method: 'PATCH',
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${localStorage.getItem('token')}`
},
body: JSON.stringify({ status: 'RETURNED' })
});
if (response.ok) {
// Successfully returned, reload reservations
await loadReservations();
// Show success message
const alert = document.createElement('div');
alert.className = 'alert alert-success alert-dismissible fade show mt-3';
alert.innerHTML = `
Item returned successfully!
<button type="button" class="btn-close" data-bs-dismiss="alert"></button>
`;
const container = document.querySelector('.container');
const h2Element = container.querySelector('h2');
h2Element.insertAdjacentElement('afterend', alert);
setTimeout(() => alert.remove(), 3000);
} else {
const error = await response.json();
throw new Error(error.message || 'Failed to return item');
}
} catch (error) {
console.error('Error returning item:', error);
alert(`Failed to return item: ${error.message}`);
}
}
// Set up event listeners
function setupEventListeners() {
document.getElementById('locationFilter').addEventListener('change', filterAndDisplayReservations);
document.getElementById('statusFilter').addEventListener('change', filterAndDisplayReservations);
document.getElementById('logoutBtn').addEventListener('click', () => {
localStorage.clear();
window.location.href = '/index.html';
});
}
// Initialize the page when DOM is loaded
document.addEventListener('DOMContentLoaded', initializePage);

240
public/js/student.js Normal file
View File

@@ -0,0 +1,240 @@
// Student dashboard functionality
let items = [];
// Check authentication
function checkAuth() {
const token = localStorage.getItem('token');
const role = localStorage.getItem('userRole');
if (!token || role !== 'student') {
window.location.href = '/index.html';
}
}
// Initialize page
async function initializePage() {
checkAuth();
await loadItems();
setupEventListeners();
displayUserInfo();
initializeModal();
}
// Display user info
function displayUserInfo() {
const username = localStorage.getItem('username');
document.getElementById('userInfo').textContent = `Student: ${username}`;
}
// Load items from server
async function loadItems() {
try {
const response = await fetch('/api/items', {
headers: {
'Authorization': `Bearer ${localStorage.getItem('token')}`
}
});
items = await response.json();
displayItems();
} catch (error) {
console.error('Error loading items:', error);
alert('Failed to load items');
}
}
let itemDetailsModal = null;
let currentItem = null;
// Initialize Bootstrap modal
function initializeModal() {
const modalElement = document.getElementById('itemDetailsModal');
itemDetailsModal = new bootstrap.Modal(modalElement);
// Set up modal reserve button
document.getElementById('modalReserveButton').addEventListener('click', () => {
if (currentItem) {
const quantity = parseInt(document.getElementById('reserveQuantity').value);
reserveItem(currentItem._id, quantity);
}
});
}
// Show item details in modal
function showItemDetails(item) {
currentItem = item;
const availableQuantity = item.quantity - (item.reserved || 0);
// Set modal content
document.getElementById('modalItemImage').src = item.imageUrl || '/images/default-item.png';
document.getElementById('modalItemName').textContent = item.name;
document.getElementById('modalItemDescription').textContent = item.description || 'No description available';
document.getElementById('modalItemLocation').textContent = item.location;
document.getElementById('modalItemQuantity').textContent = availableQuantity;
// Populate quantity select
const quantitySelect = document.getElementById('reserveQuantity');
quantitySelect.innerHTML = '';
for (let i = 1; i <= availableQuantity; i++) {
const option = document.createElement('option');
option.value = i;
option.textContent = i;
quantitySelect.appendChild(option);
}
// Show/hide reserve button and quantity select based on availability
const reserveButton = document.getElementById('modalReserveButton');
const quantityGroup = document.getElementById('quantitySelectGroup');
if (availableQuantity > 0) {
reserveButton.style.display = 'block';
quantityGroup.style.display = 'block';
reserveButton.disabled = false;
} else {
reserveButton.style.display = 'none';
quantityGroup.style.display = 'none';
}
itemDetailsModal.show();
}
// Track current view mode
let currentViewMode = localStorage.getItem('studentViewMode') || 'grid';
// Display items based on current view mode
function displayItems() {
const locationFilter = document.getElementById('locationFilter').value;
const filteredItems = locationFilter === 'all'
? items
: items.filter(item => item.location === locationFilter);
// Update view mode buttons active state
const viewButtons = document.querySelectorAll('.view-mode-btn');
viewButtons.forEach(btn => {
btn.classList.toggle('active', btn.getAttribute('data-mode') === currentViewMode);
});
// Show/hide appropriate containers
document.getElementById('itemsGrid').classList.toggle('d-none', currentViewMode !== 'grid');
document.getElementById('itemsList').classList.toggle('d-none', currentViewMode !== 'list');
if (currentViewMode === 'grid') {
// Display items in grid view
const gridContainer = document.getElementById('itemsGrid');
gridContainer.innerHTML = filteredItems.map(item => `
<div class="col-md-4 col-lg-3 mb-4">
<div class="card h-100" style="cursor: pointer" onclick='showItemDetails(${JSON.stringify(item).replace(/"/g, '&quot;')})'>
<img src="${item.imageUrl || '/images/default-item.png'}" class="card-img-top" alt="${item.name}" style="height: 200px; object-fit: cover;">
<div class="card-body">
<h5 class="card-title">${item.name}</h5>
<p class="card-text small text-muted">${item.description || 'No description available'}</p>
<p class="card-text">
<small class="text-muted">Location: ${item.location}</small><br>
<small class="text-muted">Available: ${item.quantity - (item.reserved || 0)}</small>
</p>
${item.quantity - (item.reserved || 0) > 0 ?
'<span class="badge bg-success">Available</span>' :
'<span class="badge bg-secondary">Not Available</span>'
}
</div>
</div>
</div>
`).join('');
} else {
// Display items in list view
const itemsListBody = document.getElementById('itemsListBody');
itemsListBody.innerHTML = filteredItems.map(item => `
<tr class="item-row" style="cursor: pointer" onclick="showItemDetails(${JSON.stringify(item).replace(/"/g, '&quot;')})">
<td><img src="${item.imageUrl || '/images/default-item.png'}" class="item-thumbnail" alt="${item.name}"></td>
<td>${item.name}</td>
<td>${item.description || 'No description available'}</td>
<td>${item.location}</td>
<td>${item.quantity - (item.reserved || 0)}</td>
<td>
${item.quantity - (item.reserved || 0) > 0 ?
'<span class="badge bg-success">Available</span>' :
'<span class="badge bg-secondary">Not Available</span>'
}
</td>
</tr>
`).join('');
}
}
// Load user's reservations
async function loadMyReservations() {
try {
const response = await fetch('/api/reservations/my', {
headers: {
'Authorization': `Bearer ${localStorage.getItem('token')}`
}
});
myReservations = await response.json();
displayMyReservations();
} catch (error) {
console.error('Error loading reservations:', error);
alert('Failed to load reservations');
}
}
// Display user's reservations
function displayMyReservations() {
const reservationsList = document.getElementById('reservationsList');
reservationsList.innerHTML = myReservations.map(reservation => `
<tr>
<td>${reservation.itemName}</td>
<td>${reservation.location}</td>
<td>${new Date(reservation.reservedDate).toLocaleDateString()}</td>
<td><span class="badge reservation-${reservation.status.toLowerCase()}">${reservation.status}</span></td>
</tr>
`).join('');
}
// Reserve an item
async function reserveItem(itemId, quantity = 1) {
try {
const response = await fetch('/api/reservations', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${localStorage.getItem('token')}`
},
body: JSON.stringify({ itemId, quantity })
});
if (response.ok) {
await loadItems();
itemDetailsModal.hide();
// Redirect to reservations page after successful reservation
window.location.href = '/student-reservations.html';
} else {
const error = await response.json();
alert(error.message || 'Failed to reserve item');
}
} catch (error) {
console.error('Error reserving item:', error);
alert('Failed to reserve item');
}
}
// Switch view mode
function switchViewMode(mode) {
currentViewMode = mode;
localStorage.setItem('studentViewMode', mode);
displayItems();
}
// Set up event listeners
function setupEventListeners() {
document.getElementById('locationFilter').addEventListener('change', displayItems);
document.getElementById('logoutBtn').addEventListener('click', () => {
localStorage.clear();
window.location.href = '/index.html';
});
// View mode toggle listeners
document.querySelectorAll('.view-mode-btn').forEach(btn => {
btn.addEventListener('click', () => switchViewMode(btn.getAttribute('data-mode')));
});
}
// Initialize the page when DOM is loaded
document.addEventListener('DOMContentLoaded', initializePage);