mirror of
https://github.com/Alvin-Zilverstand/Challenge_15_Magazijn_App_Maken.git
synced 2026-03-06 02:56:41 +01:00
Implement multilingual support with Dutch and English translations across the application
This commit is contained in:
@@ -2,12 +2,24 @@ const mongoose = require('mongoose');
|
||||
|
||||
const itemSchema = new mongoose.Schema({
|
||||
name: {
|
||||
type: String,
|
||||
required: true
|
||||
en: {
|
||||
type: String,
|
||||
required: true
|
||||
},
|
||||
nl: {
|
||||
type: String,
|
||||
required: true
|
||||
}
|
||||
},
|
||||
description: {
|
||||
type: String,
|
||||
default: ''
|
||||
en: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
nl: {
|
||||
type: String,
|
||||
default: ''
|
||||
}
|
||||
},
|
||||
location: {
|
||||
type: String,
|
||||
|
||||
@@ -476,3 +476,82 @@ body {
|
||||
border-color: var(--vista-coral);
|
||||
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;
|
||||
}
|
||||
@@ -12,23 +12,33 @@
|
||||
<div class="container mt-5">
|
||||
<div class="row justify-content-center">
|
||||
<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-header">
|
||||
<h2 class="text-center">Inloggen</h2>
|
||||
<h2 class="text-center" data-translate="login">Inloggen</h2>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<form id="loginForm">
|
||||
<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>
|
||||
</div>
|
||||
<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>
|
||||
</div>
|
||||
<div class="text-center">
|
||||
<button type="submit" class="btn btn-primary">Inloggen</button>
|
||||
<p class="mt-3">Nieuwe student? <a href="register.html">Registreer hier</a></p>
|
||||
<button type="submit" class="btn btn-primary" data-translate="login">Inloggen</button>
|
||||
<p class="mt-3"><span data-translate="no-account">Geen account?</span> <a href="register.html" data-translate="register">Registreer hier</a></p>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
@@ -37,6 +47,7 @@
|
||||
</div>
|
||||
</div>
|
||||
<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>
|
||||
</body>
|
||||
</html>
|
||||
@@ -20,6 +20,14 @@ async function initializePage() {
|
||||
startAutoRefresh();
|
||||
}
|
||||
|
||||
// Reload content when language changes
|
||||
function reloadContent() {
|
||||
displayReservations();
|
||||
}
|
||||
|
||||
// Make reloadContent available globally for translation manager
|
||||
window.reloadContent = reloadContent;
|
||||
|
||||
// Display user info
|
||||
function displayUserInfo() {
|
||||
const username = localStorage.getItem('username');
|
||||
|
||||
@@ -21,6 +21,15 @@ async function initializePage() {
|
||||
startAutoRefresh();
|
||||
}
|
||||
|
||||
// Reload content when language changes
|
||||
function reloadContent() {
|
||||
displayItems();
|
||||
loadMyReservations();
|
||||
}
|
||||
|
||||
// Make reloadContent available globally for translation manager
|
||||
window.reloadContent = reloadContent;
|
||||
|
||||
// Display user info
|
||||
function displayUserInfo() {
|
||||
const username = localStorage.getItem('username');
|
||||
@@ -112,12 +121,20 @@ function displayItems() {
|
||||
filteredItems = filteredItems.filter(item => item.location === locationFilter);
|
||||
}
|
||||
|
||||
// Apply search filter
|
||||
// Apply search filter with translation support
|
||||
if (searchTerm.trim() !== '') {
|
||||
filteredItems = filteredItems.filter(item =>
|
||||
item.name.toLowerCase().includes(searchTerm) ||
|
||||
(item.description && item.description.toLowerCase().includes(searchTerm))
|
||||
);
|
||||
filteredItems = filteredItems.filter(item => {
|
||||
if (window.translationManager) {
|
||||
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
|
||||
@@ -133,13 +150,19 @@ function displayItems() {
|
||||
if (currentViewMode === 'grid') {
|
||||
// Display items in grid view
|
||||
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="card h-100" style="cursor: pointer" onclick='showItemDetails(${JSON.stringify(item).replace(/"/g, '"')})'>
|
||||
<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">
|
||||
<h5 class="card-title">${item.name}</h5>
|
||||
<p class="card-text small text-muted">${item.description || 'No description available'}</p>
|
||||
<h5 class="card-title">${localizedItem.name}</h5>
|
||||
<p class="card-text small text-muted">${localizedItem.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>
|
||||
@@ -151,15 +174,22 @@ function displayItems() {
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`).join('');
|
||||
`;
|
||||
}).join('');
|
||||
} else {
|
||||
// Display items in list view
|
||||
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, '"')})">
|
||||
<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><img src="${item.imageUrl || '/images/default-item.png'}" class="item-thumbnail" alt="${localizedItem.name}"></td>
|
||||
<td>${localizedItem.name}</td>
|
||||
<td>${localizedItem.description || 'No description available'}</td>
|
||||
<td>${item.location}</td>
|
||||
<td>${item.quantity - (item.reserved || 0)}</td>
|
||||
<td>
|
||||
@@ -169,7 +199,8 @@ function displayItems() {
|
||||
}
|
||||
</td>
|
||||
</tr>
|
||||
`).join('');
|
||||
`;
|
||||
}).join('');
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
293
public/js/translations.js
Normal file
293
public/js/translations.js
Normal 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;
|
||||
@@ -12,29 +12,37 @@
|
||||
<body>
|
||||
<nav class="navbar navbar-expand-lg navbar-dark bg-dark">
|
||||
<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">
|
||||
<span class="navbar-toggler-icon"></span>
|
||||
</button>
|
||||
<div class="collapse navbar-collapse" id="navbarNav">
|
||||
<ul class="navbar-nav">
|
||||
<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 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>
|
||||
</ul>
|
||||
<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>
|
||||
<a class="nav-link" href="#" id="logoutBtn">Uitloggen</a>
|
||||
<a class="nav-link" href="#" id="logoutBtn" data-translate="logout">Uitloggen</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</nav>
|
||||
|
||||
<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-header">
|
||||
@@ -97,6 +105,7 @@
|
||||
</div>
|
||||
|
||||
<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>
|
||||
</body>
|
||||
</html>
|
||||
@@ -3,7 +3,7 @@
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<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 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">
|
||||
@@ -12,22 +12,30 @@
|
||||
<body>
|
||||
<nav class="navbar navbar-expand-lg navbar-dark bg-dark">
|
||||
<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">
|
||||
<span class="navbar-toggler-icon"></span>
|
||||
</button>
|
||||
<div class="collapse navbar-collapse" id="navbarNav">
|
||||
<ul class="navbar-nav">
|
||||
<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 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>
|
||||
</ul>
|
||||
<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>
|
||||
<a class="nav-link" href="#" id="logoutBtn">Uitloggen</a>
|
||||
<a class="nav-link" href="#" id="logoutBtn" data-translate="logout">Uitloggen</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -36,20 +44,20 @@
|
||||
<div class="container mt-4">
|
||||
<div class="row mb-4">
|
||||
<div class="col-auto">
|
||||
<h2>Beschikbare Artikelen</h2>
|
||||
<h2 data-translate="available-items">Beschikbare Artikelen</h2>
|
||||
</div>
|
||||
<div class="col-md-4">
|
||||
<div class="input-group search-input-group">
|
||||
<span class="input-group-text"><i class="bi bi-search"></i></span>
|
||||
<input type="text" class="form-control" id="searchInput" placeholder="Zoek artikelen...">
|
||||
<button class="btn btn-outline-secondary" type="button" id="clearSearch" title="Zoekopdracht wissen">
|
||||
<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" data-translate="clear-search">
|
||||
<i class="bi bi-x-lg"></i>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-auto ms-auto">
|
||||
<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="Maastricht">Maastricht</option>
|
||||
<option value="Sittard">Sittard</option>
|
||||
@@ -58,10 +66,10 @@
|
||||
<div class="col-auto">
|
||||
<div class="btn-group">
|
||||
<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 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>
|
||||
</div>
|
||||
</div>
|
||||
@@ -78,12 +86,12 @@
|
||||
<table class="table table-striped">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Afbeelding</th>
|
||||
<th>Artikelnaam</th>
|
||||
<th>Beschrijving</th>
|
||||
<th>Locatie</th>
|
||||
<th>Beschikbare Hoeveelheid</th>
|
||||
<th>Actie</th>
|
||||
<th data-translate="image">Afbeelding</th>
|
||||
<th data-translate="item-name">Artikelnaam</th>
|
||||
<th data-translate="description">Beschrijving</th>
|
||||
<th data-translate="location">Locatie</th>
|
||||
<th data-translate="quantity-available">Beschikbare Hoeveelheid</th>
|
||||
<th data-translate="action">Actie</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody id="itemsListBody">
|
||||
@@ -133,6 +141,7 @@
|
||||
</div>
|
||||
|
||||
<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>
|
||||
</body>
|
||||
</html>
|
||||
48
seed.js
48
seed.js
@@ -28,33 +28,69 @@ async function seedDatabase() {
|
||||
await User.create({
|
||||
username: 'student',
|
||||
password: studentPassword,
|
||||
email: '123456@vistacollege.nl',
|
||||
role: 'student'
|
||||
});
|
||||
|
||||
// Create some test items
|
||||
// Create some test items with multilingual support
|
||||
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',
|
||||
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',
|
||||
quantity: 3
|
||||
},
|
||||
{
|
||||
name: 'Microscope',
|
||||
name: {
|
||||
en: 'Microscope',
|
||||
nl: 'Microscoop'
|
||||
},
|
||||
description: {
|
||||
en: 'Digital microscope for laboratory work',
|
||||
nl: 'Digitale microscoop voor laboratoriumwerk'
|
||||
},
|
||||
location: 'Sittard',
|
||||
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',
|
||||
quantity: 10
|
||||
},
|
||||
{
|
||||
name: 'Camera',
|
||||
name: {
|
||||
en: 'Camera',
|
||||
nl: 'Camera'
|
||||
},
|
||||
description: {
|
||||
en: 'Professional DSLR camera for photography courses',
|
||||
nl: 'Professionele spiegelreflexcamera voor fotografiecursussen'
|
||||
},
|
||||
location: 'Maastricht',
|
||||
quantity: 2
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user