Implement multilingual support with Dutch and English translations across the application

This commit is contained in:
Alvin
2025-11-04 11:35:45 +01:00
parent 278e9353f9
commit 1d5b007577
9 changed files with 540 additions and 52 deletions

View File

@@ -2,13 +2,25 @@ const mongoose = require('mongoose');
const itemSchema = new mongoose.Schema({ const itemSchema = new mongoose.Schema({
name: { name: {
en: {
type: String, type: String,
required: true required: true
}, },
nl: {
type: String,
required: true
}
},
description: { description: {
en: {
type: String, type: String,
default: '' default: ''
}, },
nl: {
type: String,
default: ''
}
},
location: { location: {
type: String, type: String,
enum: ['Heerlen', 'Maastricht', 'Sittard'], enum: ['Heerlen', 'Maastricht', 'Sittard'],

View File

@@ -476,3 +476,82 @@ body {
border-color: var(--vista-coral); border-color: var(--vista-coral);
color: var(--vista-white); color: var(--vista-white);
} }
/* Language Toggle Styles */
.language-toggle {
display: flex;
align-items: center;
gap: 0.5rem;
margin-right: 1rem;
}
.language-switch {
position: relative;
display: inline-block;
width: 60px;
height: 28px;
}
.language-switch input {
opacity: 0;
width: 0;
height: 0;
}
.language-slider {
position: absolute;
cursor: pointer;
top: 0;
left: 0;
right: 0;
bottom: 0;
background-color: var(--vista-coral);
transition: .4s;
border-radius: 34px;
}
.language-slider:before {
position: absolute;
content: "";
height: 20px;
width: 20px;
left: 4px;
bottom: 4px;
background-color: white;
transition: .4s;
border-radius: 50%;
}
input:checked + .language-slider {
background-color: var(--vista-blue);
}
input:checked + .language-slider:before {
transform: translateX(32px);
}
.language-label {
color: var(--vista-white);
font-weight: 500;
min-width: 20px;
text-align: center;
}
.language-label.active {
font-weight: bold;
}
/* Language toggle for login page */
.language-toggle-container {
display: flex;
justify-content: center;
align-items: center;
}
.language-toggle-container .language-toggle {
margin-right: 0;
}
.language-toggle-container .language-label {
color: var(--vista-blue) !important;
}

View File

@@ -12,23 +12,33 @@
<div class="container mt-5"> <div class="container mt-5">
<div class="row justify-content-center"> <div class="row justify-content-center">
<div class="col-md-6"> <div class="col-md-6">
<div class="language-toggle-container text-center mb-3">
<div class="language-toggle">
<span class="language-label text-dark" id="nlLabel">NL</span>
<label class="language-switch">
<input type="checkbox" id="languageToggle">
<span class="language-slider"></span>
</label>
<span class="language-label text-dark" id="enLabel">EN</span>
</div>
</div>
<div class="card"> <div class="card">
<div class="card-header"> <div class="card-header">
<h2 class="text-center">Inloggen</h2> <h2 class="text-center" data-translate="login">Inloggen</h2>
</div> </div>
<div class="card-body"> <div class="card-body">
<form id="loginForm"> <form id="loginForm">
<div class="mb-3"> <div class="mb-3">
<label for="username" class="form-label">Gebruikersnaam</label> <label for="username" class="form-label" data-translate="username">Gebruikersnaam</label>
<input type="text" class="form-control" id="username" required> <input type="text" class="form-control" id="username" required>
</div> </div>
<div class="mb-3"> <div class="mb-3">
<label for="password" class="form-label">Wachtwoord</label> <label for="password" class="form-label" data-translate="password">Wachtwoord</label>
<input type="password" class="form-control" id="password" required> <input type="password" class="form-control" id="password" required>
</div> </div>
<div class="text-center"> <div class="text-center">
<button type="submit" class="btn btn-primary">Inloggen</button> <button type="submit" class="btn btn-primary" data-translate="login">Inloggen</button>
<p class="mt-3">Nieuwe student? <a href="register.html">Registreer hier</a></p> <p class="mt-3"><span data-translate="no-account">Geen account?</span> <a href="register.html" data-translate="register">Registreer hier</a></p>
</div> </div>
</form> </form>
</div> </div>
@@ -37,6 +47,7 @@
</div> </div>
</div> </div>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/js/bootstrap.bundle.min.js"></script> <script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/js/bootstrap.bundle.min.js"></script>
<script src="js/translations.js"></script>
<script src="js/auth.js"></script> <script src="js/auth.js"></script>
</body> </body>
</html> </html>

View File

@@ -20,6 +20,14 @@ async function initializePage() {
startAutoRefresh(); startAutoRefresh();
} }
// Reload content when language changes
function reloadContent() {
displayReservations();
}
// Make reloadContent available globally for translation manager
window.reloadContent = reloadContent;
// Display user info // Display user info
function displayUserInfo() { function displayUserInfo() {
const username = localStorage.getItem('username'); const username = localStorage.getItem('username');

View File

@@ -21,6 +21,15 @@ async function initializePage() {
startAutoRefresh(); startAutoRefresh();
} }
// Reload content when language changes
function reloadContent() {
displayItems();
loadMyReservations();
}
// Make reloadContent available globally for translation manager
window.reloadContent = reloadContent;
// Display user info // Display user info
function displayUserInfo() { function displayUserInfo() {
const username = localStorage.getItem('username'); const username = localStorage.getItem('username');
@@ -112,12 +121,20 @@ function displayItems() {
filteredItems = filteredItems.filter(item => item.location === locationFilter); filteredItems = filteredItems.filter(item => item.location === locationFilter);
} }
// Apply search filter // Apply search filter with translation support
if (searchTerm.trim() !== '') { if (searchTerm.trim() !== '') {
filteredItems = filteredItems.filter(item => filteredItems = filteredItems.filter(item => {
item.name.toLowerCase().includes(searchTerm) || if (window.translationManager) {
(item.description && item.description.toLowerCase().includes(searchTerm)) const localizedItem = window.translationManager.getLocalizedItem(item);
); return localizedItem.name.toLowerCase().includes(searchTerm) ||
(localizedItem.description && localizedItem.description.toLowerCase().includes(searchTerm));
} else {
// Fallback for when translation manager isn't loaded yet (default to Dutch)
const name = item.name?.nl || item.name?.en || item.name || '';
const description = item.description?.nl || item.description?.en || item.description || '';
return name.toLowerCase().includes(searchTerm) || description.toLowerCase().includes(searchTerm);
}
});
} }
// Update view mode buttons active state // Update view mode buttons active state
@@ -133,13 +150,19 @@ function displayItems() {
if (currentViewMode === 'grid') { if (currentViewMode === 'grid') {
// Display items in grid view // Display items in grid view
const gridContainer = document.getElementById('itemsGrid'); const gridContainer = document.getElementById('itemsGrid');
gridContainer.innerHTML = filteredItems.map(item => ` gridContainer.innerHTML = filteredItems.map(item => {
const localizedItem = window.translationManager ? window.translationManager.getLocalizedItem(item) : {
name: item.name?.nl || item.name?.en || item.name || 'Onbekend Artikel',
description: item.description?.nl || item.description?.en || item.description || ''
};
return `
<div class="col-md-4 col-lg-3 mb-4"> <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;')})'> <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;"> <img src="${item.imageUrl || '/images/default-item.png'}" class="card-img-top" alt="${localizedItem.name}" style="height: 200px; object-fit: cover;">
<div class="card-body"> <div class="card-body">
<h5 class="card-title">${item.name}</h5> <h5 class="card-title">${localizedItem.name}</h5>
<p class="card-text small text-muted">${item.description || 'No description available'}</p> <p class="card-text small text-muted">${localizedItem.description || 'No description available'}</p>
<p class="card-text"> <p class="card-text">
<small class="text-muted">Location: ${item.location}</small><br> <small class="text-muted">Location: ${item.location}</small><br>
<small class="text-muted">Available: ${item.quantity - (item.reserved || 0)}</small> <small class="text-muted">Available: ${item.quantity - (item.reserved || 0)}</small>
@@ -151,15 +174,22 @@ function displayItems() {
</div> </div>
</div> </div>
</div> </div>
`).join(''); `;
}).join('');
} else { } else {
// Display items in list view // Display items in list view
const itemsListBody = document.getElementById('itemsListBody'); const itemsListBody = document.getElementById('itemsListBody');
itemsListBody.innerHTML = filteredItems.map(item => ` itemsListBody.innerHTML = filteredItems.map(item => {
const localizedItem = window.translationManager ? window.translationManager.getLocalizedItem(item) : {
name: item.name?.nl || item.name?.en || item.name || 'Onbekend Artikel',
description: item.description?.nl || item.description?.en || item.description || ''
};
return `
<tr class="item-row" style="cursor: pointer" onclick="showItemDetails(${JSON.stringify(item).replace(/"/g, '&quot;')})"> <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><img src="${item.imageUrl || '/images/default-item.png'}" class="item-thumbnail" alt="${localizedItem.name}"></td>
<td>${item.name}</td> <td>${localizedItem.name}</td>
<td>${item.description || 'No description available'}</td> <td>${localizedItem.description || 'No description available'}</td>
<td>${item.location}</td> <td>${item.location}</td>
<td>${item.quantity - (item.reserved || 0)}</td> <td>${item.quantity - (item.reserved || 0)}</td>
<td> <td>
@@ -169,7 +199,8 @@ function displayItems() {
} }
</td> </td>
</tr> </tr>
`).join(''); `;
}).join('');
} }
} }

293
public/js/translations.js Normal file
View File

@@ -0,0 +1,293 @@
// Translation utility for the warehouse management system
const translations = {
en: {
// Navigation
'school-warehouse': 'School Warehouse',
'available-items': 'Available Items',
'my-reservations': 'My Reservations',
'logout': 'Logout',
'inventory': 'Inventory',
'add-new-item': 'Add New Item',
'reservations': 'Reservations',
// Page titles
'warehouse-dashboard-admin': 'Warehouse Dashboard - Admin',
'warehouse-dashboard-student': 'Warehouse Dashboard - Student',
'my-reservations-student': 'My Reservations - Student',
'login': 'Login',
'register': 'Register',
// Forms
'username': 'Username',
'password': 'Password',
'remember-me': 'Remember Me',
'no-account': "Don't have an account?",
'student-registration': 'Student Registration',
'confirm-password': 'Confirm Password',
'already-account': 'Already have an account?',
// Search and filters
'search-items': 'Search items...',
'search-reservations': 'Search reservations...',
'clear-search': 'Clear search',
'all-locations': 'All Locations',
'all-status': 'All Status',
'filter-reservations': 'Filter Reservations',
'location': 'Location',
'status': 'Status',
'search': 'Search',
// Table headers
'image': 'Image',
'item-name': 'Item Name',
'description': 'Description',
'quantity': 'Quantity',
'quantity-available': 'Quantity Available',
'action': 'Action',
'actions': 'Actions',
'reserved-date': 'Reserved Date',
// View modes
'grid': 'Grid',
'list': 'List',
// Status values
'pending': 'Pending',
'approved': 'Approved',
'rejected': 'Rejected',
'return-pending': 'Return Pending',
'returned': 'Returned',
// Buttons and actions
'reserve': 'Reserve',
'cancel': 'Cancel',
'return': 'Return',
'approve': 'Approve',
'reject': 'Reject',
'edit': 'Edit',
'delete': 'Delete',
// Messages
'loading': 'Loading...',
'loading-items': 'Loading items...',
'failed-load-items': 'Failed to load items',
'failed-load-reservations': 'Failed to load reservations',
'failed-reserve-item': 'Failed to reserve item',
'failed-cancel-reservation': 'Failed to cancel reservation',
'failed-request-return': 'Failed to request return',
'confirm-cancel-reservation': 'Are you sure you want to cancel this reservation?',
'confirm-return-request': 'Are you sure you want to request return for this item? An admin will need to approve the return.',
'return-request-success': 'Return requested successfully! An admin will review your request.',
// Management
'inventory-management': 'Inventory Management',
'reservation-management': 'Reservation Management'
},
nl: {
// Navigation
'school-warehouse': 'School Magazijn',
'available-items': 'Beschikbare Artikelen',
'my-reservations': 'Mijn Reserveringen',
'logout': 'Uitloggen',
'inventory': 'Voorraad',
'add-new-item': 'Nieuw Artikel Toevoegen',
'reservations': 'Reserveringen',
// Page titles
'warehouse-dashboard-admin': 'Magazijn Dashboard - Admin',
'warehouse-dashboard-student': 'Magazijn Dashboard - Student',
'my-reservations-student': 'Mijn Reserveringen - Student',
'login': 'Inloggen',
'register': 'Registreren',
// Forms
'username': 'Gebruikersnaam',
'password': 'Wachtwoord',
'remember-me': 'Onthoud mij',
'no-account': 'Geen account?',
'student-registration': 'Student Registratie',
'confirm-password': 'Bevestig Wachtwoord',
'already-account': 'Al een account?',
// Search and filters
'search-items': 'Zoek artikelen...',
'search-reservations': 'Zoek reserveringen...',
'clear-search': 'Zoekopdracht wissen',
'all-locations': 'Alle Locaties',
'all-status': 'Alle Statussen',
'filter-reservations': 'Filter Reserveringen',
'location': 'Locatie',
'status': 'Status',
'search': 'Zoeken',
// Table headers
'image': 'Afbeelding',
'item-name': 'Artikelnaam',
'description': 'Beschrijving',
'quantity': 'Hoeveelheid',
'quantity-available': 'Beschikbare Hoeveelheid',
'action': 'Actie',
'actions': 'Acties',
'reserved-date': 'Reserveringsdatum',
// View modes
'grid': 'Raster',
'list': 'Lijst',
// Status values
'pending': 'In Behandeling',
'approved': 'Goedgekeurd',
'rejected': 'Afgewezen',
'return-pending': 'Retour in Behandeling',
'returned': 'Geretourneerd',
// Buttons and actions
'reserve': 'Reserveren',
'cancel': 'Annuleren',
'return': 'Retourneren',
'approve': 'Goedkeuren',
'reject': 'Afwijzen',
'edit': 'Bewerken',
'delete': 'Verwijderen',
// Messages
'loading': 'Laden...',
'loading-items': 'Artikelen laden...',
'failed-load-items': 'Kon artikelen niet laden',
'failed-load-reservations': 'Kon reserveringen niet laden',
'failed-reserve-item': 'Kon artikel niet reserveren',
'failed-cancel-reservation': 'Kon reservering niet annuleren',
'failed-request-return': 'Kon retour niet aanvragen',
'confirm-cancel-reservation': 'Weet je zeker dat je deze reservering wilt annuleren?',
'confirm-return-request': 'Weet je zeker dat je retour wilt aanvragen voor dit artikel? Een admin moet de retour goedkeuren.',
'return-request-success': 'Retour succesvol aangevraagd! Een admin zal je verzoek beoordelen.',
// Management
'inventory-management': 'Voorraadbeheer',
'reservation-management': 'Reserveringsbeheer'
}
};
// Translation manager class
class TranslationManager {
constructor() {
this.currentLanguage = localStorage.getItem('language') || 'nl'; // Default to Dutch
this.init();
}
init() {
// Apply saved language preference
this.applyTranslations();
// Set up language toggle if it exists
const languageToggle = document.getElementById('languageToggle');
if (languageToggle) {
languageToggle.addEventListener('change', (e) => {
this.setLanguage(e.target.checked ? 'en' : 'nl');
});
// Set toggle state based on current language (Dutch is default/unchecked)
languageToggle.checked = this.currentLanguage === 'en';
this.updateLanguageLabels();
}
}
updateLanguageLabels() {
const nlLabel = document.getElementById('nlLabel');
const enLabel = document.getElementById('enLabel');
if (nlLabel && enLabel) {
nlLabel.classList.toggle('active', this.currentLanguage === 'nl');
enLabel.classList.toggle('active', this.currentLanguage === 'en');
}
}
setLanguage(lang) {
if (lang !== 'en' && lang !== 'nl') {
console.warn('Unsupported language:', lang);
return;
}
this.currentLanguage = lang;
localStorage.setItem('language', lang);
this.applyTranslations();
this.updateLanguageLabels();
// Reload dynamic content if function exists
if (window.reloadContent) {
window.reloadContent();
}
}
getLanguage() {
return this.currentLanguage;
}
translate(key) {
const translation = translations[this.currentLanguage]?.[key];
if (!translation) {
console.warn(`Translation missing for key: ${key} in language: ${this.currentLanguage}`);
// Fallback to Dutch first, then English, then key itself
return translations['nl']?.[key] || translations['en']?.[key] || key;
}
return translation;
}
applyTranslations() {
// Update all elements with data-translate attribute
document.querySelectorAll('[data-translate]').forEach(element => {
const key = element.getAttribute('data-translate');
const translation = this.translate(key);
if (element.tagName === 'INPUT' && element.type === 'submit') {
element.value = translation;
} else if (element.tagName === 'INPUT' && (element.type === 'text' || element.type === 'password')) {
element.placeholder = translation;
} else if (element.hasAttribute('title')) {
element.title = translation;
} else {
element.textContent = translation;
}
});
// Update page title if it has data-translate
const titleElement = document.querySelector('title[data-translate]');
if (titleElement) {
const key = titleElement.getAttribute('data-translate');
document.title = this.translate(key);
}
}
// Get translated item data based on current language
getLocalizedItem(item) {
return {
...item,
name: item.name?.[this.currentLanguage] || item.name?.nl || item.name?.en || item.name || 'Unknown Item',
description: item.description?.[this.currentLanguage] || item.description?.nl || item.description?.en || item.description || ''
};
}
// Get translated status text
getStatusText(status) {
const statusMap = {
'PENDING': 'pending',
'APPROVED': 'approved',
'REJECTED': 'rejected',
'RETURN_PENDING': 'return-pending',
'RETURNED': 'returned'
};
return this.translate(statusMap[status] || status.toLowerCase());
}
}
// Initialize translation manager when DOM is loaded
let translationManager;
document.addEventListener('DOMContentLoaded', () => {
translationManager = new TranslationManager();
});
// Export for use in other files
window.TranslationManager = TranslationManager;
window.translationManager = translationManager;

View File

@@ -12,29 +12,37 @@
<body> <body>
<nav class="navbar navbar-expand-lg navbar-dark bg-dark"> <nav class="navbar navbar-expand-lg navbar-dark bg-dark">
<div class="container"> <div class="container">
<a class="navbar-brand" href="#">School Magazijn</a> <a class="navbar-brand" href="#" data-translate="school-warehouse">School Magazijn</a>
<button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarNav"> <button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarNav">
<span class="navbar-toggler-icon"></span> <span class="navbar-toggler-icon"></span>
</button> </button>
<div class="collapse navbar-collapse" id="navbarNav"> <div class="collapse navbar-collapse" id="navbarNav">
<ul class="navbar-nav"> <ul class="navbar-nav">
<li class="nav-item"> <li class="nav-item">
<a class="nav-link" href="student.html">Beschikbare Artikelen</a> <a class="nav-link" href="student.html" data-translate="available-items">Beschikbare Artikelen</a>
</li> </li>
<li class="nav-item"> <li class="nav-item">
<a class="nav-link active" href="student-reservations.html">Mijn Reserveringen</a> <a class="nav-link active" href="student-reservations.html" data-translate="my-reservations">Mijn Reserveringen</a>
</li> </li>
</ul> </ul>
<div class="navbar-nav ms-auto"> <div class="navbar-nav ms-auto">
<div class="language-toggle nav-item">
<span class="language-label" id="nlLabel">NL</span>
<label class="language-switch">
<input type="checkbox" id="languageToggle">
<span class="language-slider"></span>
</label>
<span class="language-label" id="enLabel">EN</span>
</div>
<span class="nav-item nav-link text-light" id="userInfo"></span> <span class="nav-item nav-link text-light" id="userInfo"></span>
<a class="nav-link" href="#" id="logoutBtn">Uitloggen</a> <a class="nav-link" href="#" id="logoutBtn" data-translate="logout">Uitloggen</a>
</div> </div>
</div> </div>
</div> </div>
</nav> </nav>
<div class="container mt-4"> <div class="container mt-4">
<h2 class="mb-4">Mijn Reserveringen</h2> <h2 class="mb-4" data-translate="my-reservations">Mijn Reserveringen</h2>
<div class="card"> <div class="card">
<div class="card-header"> <div class="card-header">
@@ -97,6 +105,7 @@
</div> </div>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/js/bootstrap.bundle.min.js"></script> <script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/js/bootstrap.bundle.min.js"></script>
<script src="js/translations.js"></script>
<script src="js/student-reservations.js"></script> <script src="js/student-reservations.js"></script>
</body> </body>
</html> </html>

View File

@@ -3,7 +3,7 @@
<head> <head>
<meta charset="UTF-8"> <meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Magazijn Dashboard - Student</title> <title data-translate="warehouse-dashboard-student">Magazijn Dashboard - Student</title>
<link rel="icon" type="image/svg+xml" href="/favicon.svg"> <link rel="icon" type="image/svg+xml" href="/favicon.svg">
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/css/bootstrap.min.css" rel="stylesheet"> <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/css/bootstrap.min.css" rel="stylesheet">
<link href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.7.2/font/bootstrap-icons.css" rel="stylesheet"> <link href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.7.2/font/bootstrap-icons.css" rel="stylesheet">
@@ -12,22 +12,30 @@
<body> <body>
<nav class="navbar navbar-expand-lg navbar-dark bg-dark"> <nav class="navbar navbar-expand-lg navbar-dark bg-dark">
<div class="container"> <div class="container">
<a class="navbar-brand" href="#">School Magazijn</a> <a class="navbar-brand" href="#" data-translate="school-warehouse">School Magazijn</a>
<button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarNav"> <button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarNav">
<span class="navbar-toggler-icon"></span> <span class="navbar-toggler-icon"></span>
</button> </button>
<div class="collapse navbar-collapse" id="navbarNav"> <div class="collapse navbar-collapse" id="navbarNav">
<ul class="navbar-nav"> <ul class="navbar-nav">
<li class="nav-item"> <li class="nav-item">
<a class="nav-link active" href="student.html">Beschikbare Artikelen</a> <a class="nav-link active" href="student.html" data-translate="available-items">Beschikbare Artikelen</a>
</li> </li>
<li class="nav-item"> <li class="nav-item">
<a class="nav-link" href="student-reservations.html">Mijn Reserveringen</a> <a class="nav-link" href="student-reservations.html" data-translate="my-reservations">Mijn Reserveringen</a>
</li> </li>
</ul> </ul>
<div class="navbar-nav ms-auto"> <div class="navbar-nav ms-auto">
<div class="language-toggle nav-item">
<span class="language-label" id="nlLabel">NL</span>
<label class="language-switch">
<input type="checkbox" id="languageToggle">
<span class="language-slider"></span>
</label>
<span class="language-label" id="enLabel">EN</span>
</div>
<span class="nav-item nav-link text-light" id="userInfo"></span> <span class="nav-item nav-link text-light" id="userInfo"></span>
<a class="nav-link" href="#" id="logoutBtn">Uitloggen</a> <a class="nav-link" href="#" id="logoutBtn" data-translate="logout">Uitloggen</a>
</div> </div>
</div> </div>
</div> </div>
@@ -36,20 +44,20 @@
<div class="container mt-4"> <div class="container mt-4">
<div class="row mb-4"> <div class="row mb-4">
<div class="col-auto"> <div class="col-auto">
<h2>Beschikbare Artikelen</h2> <h2 data-translate="available-items">Beschikbare Artikelen</h2>
</div> </div>
<div class="col-md-4"> <div class="col-md-4">
<div class="input-group search-input-group"> <div class="input-group search-input-group">
<span class="input-group-text"><i class="bi bi-search"></i></span> <span class="input-group-text"><i class="bi bi-search"></i></span>
<input type="text" class="form-control" id="searchInput" placeholder="Zoek artikelen..."> <input type="text" class="form-control" id="searchInput" placeholder="Zoek artikelen..." data-translate="search-items">
<button class="btn btn-outline-secondary" type="button" id="clearSearch" title="Zoekopdracht wissen"> <button class="btn btn-outline-secondary" type="button" id="clearSearch" title="Zoekopdracht wissen" data-translate="clear-search">
<i class="bi bi-x-lg"></i> <i class="bi bi-x-lg"></i>
</button> </button>
</div> </div>
</div> </div>
<div class="col-auto ms-auto"> <div class="col-auto ms-auto">
<select class="form-select me-2" id="locationFilter"> <select class="form-select me-2" id="locationFilter">
<option value="all">Alle Locaties</option> <option value="all" data-translate="all-locations">Alle Locaties</option>
<option value="Heerlen">Heerlen</option> <option value="Heerlen">Heerlen</option>
<option value="Maastricht">Maastricht</option> <option value="Maastricht">Maastricht</option>
<option value="Sittard">Sittard</option> <option value="Sittard">Sittard</option>
@@ -58,10 +66,10 @@
<div class="col-auto"> <div class="col-auto">
<div class="btn-group"> <div class="btn-group">
<button type="button" class="btn btn-outline-primary view-mode-btn active" data-mode="grid"> <button type="button" class="btn btn-outline-primary view-mode-btn active" data-mode="grid">
<i class="bi bi-grid"></i> Raster <i class="bi bi-grid"></i> <span data-translate="grid">Raster</span>
</button> </button>
<button type="button" class="btn btn-outline-primary view-mode-btn" data-mode="list"> <button type="button" class="btn btn-outline-primary view-mode-btn" data-mode="list">
<i class="bi bi-list"></i> Lijst <i class="bi bi-list"></i> <span data-translate="list">Lijst</span>
</button> </button>
</div> </div>
</div> </div>
@@ -78,12 +86,12 @@
<table class="table table-striped"> <table class="table table-striped">
<thead> <thead>
<tr> <tr>
<th>Afbeelding</th> <th data-translate="image">Afbeelding</th>
<th>Artikelnaam</th> <th data-translate="item-name">Artikelnaam</th>
<th>Beschrijving</th> <th data-translate="description">Beschrijving</th>
<th>Locatie</th> <th data-translate="location">Locatie</th>
<th>Beschikbare Hoeveelheid</th> <th data-translate="quantity-available">Beschikbare Hoeveelheid</th>
<th>Actie</th> <th data-translate="action">Actie</th>
</tr> </tr>
</thead> </thead>
<tbody id="itemsListBody"> <tbody id="itemsListBody">
@@ -133,6 +141,7 @@
</div> </div>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/js/bootstrap.bundle.min.js"></script> <script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/js/bootstrap.bundle.min.js"></script>
<script src="js/translations.js"></script>
<script src="js/student.js"></script> <script src="js/student.js"></script>
</body> </body>
</html> </html>

48
seed.js
View File

@@ -28,33 +28,69 @@ async function seedDatabase() {
await User.create({ await User.create({
username: 'student', username: 'student',
password: studentPassword, password: studentPassword,
email: '123456@vistacollege.nl',
role: 'student' role: 'student'
}); });
// Create some test items // Create some test items with multilingual support
const items = [ const items = [
{ {
name: 'Laptop', name: {
en: 'Laptop',
nl: 'Laptop'
},
description: {
en: 'High-performance laptop for programming and design work',
nl: 'Krachtige laptop voor programmeren en ontwerpwerk'
},
location: 'Heerlen', location: 'Heerlen',
quantity: 5 quantity: 5
}, },
{ {
name: 'Projector', name: {
en: 'Projector',
nl: 'Beamer'
},
description: {
en: 'HD projector for presentations and lectures',
nl: 'HD-beamer voor presentaties en lezingen'
},
location: 'Maastricht', location: 'Maastricht',
quantity: 3 quantity: 3
}, },
{ {
name: 'Microscope', name: {
en: 'Microscope',
nl: 'Microscoop'
},
description: {
en: 'Digital microscope for laboratory work',
nl: 'Digitale microscoop voor laboratoriumwerk'
},
location: 'Sittard', location: 'Sittard',
quantity: 4 quantity: 4
}, },
{ {
name: 'Tablet', name: {
en: 'Tablet',
nl: 'Tablet'
},
description: {
en: 'Portable tablet for mobile learning and presentations',
nl: 'Draagbare tablet voor mobiel leren en presentaties'
},
location: 'Heerlen', location: 'Heerlen',
quantity: 10 quantity: 10
}, },
{ {
name: 'Camera', name: {
en: 'Camera',
nl: 'Camera'
},
description: {
en: 'Professional DSLR camera for photography courses',
nl: 'Professionele spiegelreflexcamera voor fotografiecursussen'
},
location: 'Maastricht', location: 'Maastricht',
quantity: 2 quantity: 2
} }