mirror of
https://github.com/Alvin-Zilverstand/Challenge_15_Magazijn_App_Maken.git
synced 2026-03-06 11:06:34 +01:00
Implement multilingual support with Dutch and English translations across various pages and functionalities
This commit is contained in:
@@ -12,25 +12,35 @@
|
|||||||
<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 Warehouse - Admin</a>
|
<a class="navbar-brand" href="#" data-translate="school-warehouse">School Magazijn - Admin</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="admin.html">Inventory</a>
|
<a class="nav-link" href="admin.html" data-translate="inventory">Voorraad</a>
|
||||||
</li>
|
</li>
|
||||||
<li class="nav-item">
|
<li class="nav-item">
|
||||||
<a class="nav-link active" href="add-item.html">Add New Item</a>
|
<a class="nav-link active" href="add-item.html" data-translate="add-new-item">Nieuw Artikel Toevoegen</a>
|
||||||
</li>
|
</li>
|
||||||
<li class="nav-item">
|
<li class="nav-item">
|
||||||
<a class="nav-link" href="admin-reservations.html">Reservations</a>
|
<a class="nav-link" href="admin-reservations.html" data-translate="reservations">Reserveringen</a>
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
<div class="navbar-nav ms-auto">
|
<div class="navbar-nav ms-auto">
|
||||||
|
<div class="language-toggle nav-item">
|
||||||
|
<div class="language-switcher">
|
||||||
|
<button class="language-btn active" id="nlBtn" data-lang="nl">
|
||||||
|
<i class="fas fa-flag me-1"></i>Nederlands
|
||||||
|
</button>
|
||||||
|
<button class="language-btn" id="enBtn" data-lang="en">
|
||||||
|
<i class="fas fa-flag me-1"></i>English
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</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">Logout</a>
|
<a class="nav-link" href="#" id="logoutBtn" data-translate="logout">Logout</a>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -41,13 +51,22 @@
|
|||||||
<div class="col-md-8">
|
<div class="col-md-8">
|
||||||
<div class="card">
|
<div class="card">
|
||||||
<div class="card-header">
|
<div class="card-header">
|
||||||
<h3>Add New Item</h3>
|
<h3 data-translate="add-new-item">Nieuw Artikel Toevoegen</h3>
|
||||||
</div>
|
</div>
|
||||||
<div class="card-body">
|
<div class="card-body">
|
||||||
<form id="addItemForm">
|
<form id="addItemForm">
|
||||||
<div class="mb-3">
|
<div class="mb-3">
|
||||||
<label for="itemName" class="form-label">Item Name</label>
|
<label class="form-label">Item Name</label>
|
||||||
<input type="text" class="form-control" id="itemName" required>
|
<div class="row">
|
||||||
|
<div class="col-md-6">
|
||||||
|
<label for="itemNameNl" class="form-label text-muted small">Dutch</label>
|
||||||
|
<input type="text" class="form-control" id="itemNameNl" placeholder="Nederlandse naam" required>
|
||||||
|
</div>
|
||||||
|
<div class="col-md-6">
|
||||||
|
<label for="itemNameEn" class="form-label text-muted small">English</label>
|
||||||
|
<input type="text" class="form-control" id="itemNameEn" placeholder="English name" required>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="mb-3">
|
<div class="mb-3">
|
||||||
<label for="itemLocation" class="form-label">Location</label>
|
<label for="itemLocation" class="form-label">Location</label>
|
||||||
@@ -58,8 +77,17 @@
|
|||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
<div class="mb-3">
|
<div class="mb-3">
|
||||||
<label for="itemDescription" class="form-label">Description</label>
|
<label class="form-label">Description</label>
|
||||||
<textarea class="form-control" id="itemDescription" rows="3"></textarea>
|
<div class="row">
|
||||||
|
<div class="col-md-6">
|
||||||
|
<label for="itemDescriptionNl" class="form-label text-muted small">Dutch</label>
|
||||||
|
<textarea class="form-control" id="itemDescriptionNl" rows="3" placeholder="Nederlandse beschrijving"></textarea>
|
||||||
|
</div>
|
||||||
|
<div class="col-md-6">
|
||||||
|
<label for="itemDescriptionEn" class="form-label text-muted small">English</label>
|
||||||
|
<textarea class="form-control" id="itemDescriptionEn" rows="3" placeholder="English description"></textarea>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="mb-3">
|
<div class="mb-3">
|
||||||
<label for="itemQuantity" class="form-label">Quantity</label>
|
<label for="itemQuantity" class="form-label">Quantity</label>
|
||||||
@@ -87,6 +115,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/add-item.js"></script>
|
<script src="js/add-item.js"></script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
@@ -12,25 +12,35 @@
|
|||||||
<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 - Admin</a>
|
<a class="navbar-brand" href="#" data-translate="school-warehouse">School Magazijn - Admin</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="admin.html">Voorraad</a>
|
<a class="nav-link active" href="admin.html" data-translate="inventory">Voorraad</a>
|
||||||
</li>
|
</li>
|
||||||
<li class="nav-item">
|
<li class="nav-item">
|
||||||
<a class="nav-link" href="add-item.html">Nieuw Artikel Toevoegen</a>
|
<a class="nav-link" href="add-item.html" data-translate="add-new-item">Nieuw Artikel Toevoegen</a>
|
||||||
</li>
|
</li>
|
||||||
<li class="nav-item">
|
<li class="nav-item">
|
||||||
<a class="nav-link" href="admin-reservations.html">Reserveringen</a>
|
<a class="nav-link" href="admin-reservations.html" data-translate="reservations">Reserveringen</a>
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
<div class="navbar-nav ms-auto">
|
<div class="navbar-nav ms-auto">
|
||||||
|
<div class="language-toggle nav-item">
|
||||||
|
<div class="language-switcher">
|
||||||
|
<button class="language-btn active" id="nlBtn" data-lang="nl">
|
||||||
|
<i class="fas fa-flag me-1"></i>Nederlands
|
||||||
|
</button>
|
||||||
|
<button class="language-btn" id="enBtn" data-lang="en">
|
||||||
|
<i class="fas fa-flag me-1"></i>English
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</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>
|
||||||
@@ -160,6 +170,7 @@
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
<script src="js/translations.js"></script>
|
||||||
<script src="js/admin.js"></script>
|
<script src="js/admin.js"></script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
@@ -477,68 +477,82 @@ body {
|
|||||||
color: var(--vista-white);
|
color: var(--vista-white);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Language Toggle Styles */
|
/* Language Toggle Styles - Modern Button Design */
|
||||||
.language-toggle {
|
.language-toggle {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
gap: 0.5rem;
|
|
||||||
margin-right: 1rem;
|
margin-right: 1rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
.language-switch {
|
.language-switcher {
|
||||||
position: relative;
|
display: flex;
|
||||||
display: inline-block;
|
background-color: rgba(255, 255, 255, 0.1);
|
||||||
width: 60px;
|
border-radius: 25px;
|
||||||
height: 28px;
|
padding: 2px;
|
||||||
|
border: 1px solid rgba(255, 255, 255, 0.2);
|
||||||
|
backdrop-filter: blur(10px);
|
||||||
|
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
|
||||||
|
transition: all 0.3s ease;
|
||||||
}
|
}
|
||||||
|
|
||||||
.language-switch input {
|
.language-switcher:hover {
|
||||||
opacity: 0;
|
background-color: rgba(255, 255, 255, 0.15);
|
||||||
width: 0;
|
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
|
||||||
height: 0;
|
transform: translateY(-1px);
|
||||||
}
|
}
|
||||||
|
|
||||||
.language-slider {
|
.language-btn {
|
||||||
position: absolute;
|
background: none;
|
||||||
|
border: none;
|
||||||
|
padding: 8px 16px;
|
||||||
|
margin: 0;
|
||||||
|
border-radius: 20px;
|
||||||
|
color: rgba(255, 255, 255, 0.7);
|
||||||
|
font-weight: 500;
|
||||||
|
font-size: 0.9rem;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
|
transition: all 0.3s ease;
|
||||||
|
position: relative;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.language-btn:hover {
|
||||||
|
color: rgba(255, 255, 255, 0.9);
|
||||||
|
transform: scale(1.05);
|
||||||
|
}
|
||||||
|
|
||||||
|
.language-btn.active {
|
||||||
|
background: linear-gradient(135deg, var(--vista-blue), var(--vista-coral));
|
||||||
|
color: white;
|
||||||
|
font-weight: 600;
|
||||||
|
box-shadow: 0 2px 6px rgba(0, 0, 0, 0.2);
|
||||||
|
}
|
||||||
|
|
||||||
|
.language-btn.active::before {
|
||||||
|
content: '';
|
||||||
|
position: absolute;
|
||||||
top: 0;
|
top: 0;
|
||||||
left: 0;
|
left: 0;
|
||||||
right: 0;
|
right: 0;
|
||||||
bottom: 0;
|
bottom: 0;
|
||||||
background-color: var(--vista-coral);
|
background: linear-gradient(45deg, transparent 30%, rgba(255, 255, 255, 0.1) 50%, transparent 70%);
|
||||||
transition: .4s;
|
animation: shine 2s ease-in-out infinite;
|
||||||
border-radius: 34px;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.language-slider:before {
|
@keyframes shine {
|
||||||
position: absolute;
|
0% { transform: translateX(-100%); }
|
||||||
content: "";
|
50% { transform: translateX(100%); }
|
||||||
height: 20px;
|
100% { transform: translateX(100%); }
|
||||||
width: 20px;
|
|
||||||
left: 4px;
|
|
||||||
bottom: 4px;
|
|
||||||
background-color: white;
|
|
||||||
transition: .4s;
|
|
||||||
border-radius: 50%;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
input:checked + .language-slider {
|
/* Focus styles for accessibility */
|
||||||
background-color: var(--vista-blue);
|
.language-btn:focus {
|
||||||
|
outline: none;
|
||||||
|
box-shadow: 0 0 0 2px rgba(255, 255, 255, 0.5);
|
||||||
}
|
}
|
||||||
|
|
||||||
input:checked + .language-slider:before {
|
.language-btn:active {
|
||||||
transform: translateX(32px);
|
transform: scale(0.98);
|
||||||
}
|
|
||||||
|
|
||||||
.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 for login page */
|
||||||
@@ -552,6 +566,24 @@ input:checked + .language-slider:before {
|
|||||||
margin-right: 0;
|
margin-right: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.language-toggle-container .language-label {
|
.language-switcher.login-page {
|
||||||
color: var(--vista-blue) !important;
|
background-color: rgba(21, 78, 135, 0.1);
|
||||||
|
border: 1px solid rgba(21, 78, 135, 0.2);
|
||||||
|
}
|
||||||
|
|
||||||
|
.language-switcher.login-page:hover {
|
||||||
|
background-color: rgba(21, 78, 135, 0.15);
|
||||||
|
}
|
||||||
|
|
||||||
|
.language-switcher.login-page .language-btn {
|
||||||
|
color: var(--vista-blue);
|
||||||
|
}
|
||||||
|
|
||||||
|
.language-switcher.login-page .language-btn:hover {
|
||||||
|
color: var(--vista-coral);
|
||||||
|
}
|
||||||
|
|
||||||
|
.language-switcher.login-page .language-btn.active {
|
||||||
|
background: linear-gradient(135deg, var(--vista-blue), var(--vista-coral));
|
||||||
|
color: white;
|
||||||
}
|
}
|
||||||
@@ -14,12 +14,14 @@
|
|||||||
<div class="col-md-6">
|
<div class="col-md-6">
|
||||||
<div class="language-toggle-container text-center mb-3">
|
<div class="language-toggle-container text-center mb-3">
|
||||||
<div class="language-toggle">
|
<div class="language-toggle">
|
||||||
<span class="language-label text-dark" id="nlLabel">NL</span>
|
<div class="language-switcher login-page">
|
||||||
<label class="language-switch">
|
<button class="language-btn active" id="nlBtn" data-lang="nl">
|
||||||
<input type="checkbox" id="languageToggle">
|
<i class="fas fa-flag me-1"></i>Nederlands
|
||||||
<span class="language-slider"></span>
|
</button>
|
||||||
</label>
|
<button class="language-btn" id="enBtn" data-lang="en">
|
||||||
<span class="language-label text-dark" id="enLabel">EN</span>
|
<i class="fas fa-flag me-1"></i>English
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="card">
|
<div class="card">
|
||||||
|
|||||||
@@ -72,9 +72,36 @@ async function addItem(e) {
|
|||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
|
|
||||||
const formData = new FormData();
|
const formData = new FormData();
|
||||||
formData.append('name', document.getElementById('itemName').value);
|
|
||||||
|
// Collect multilingual name data
|
||||||
|
const nameData = {
|
||||||
|
nl: document.getElementById('itemNameNl').value.trim(),
|
||||||
|
en: document.getElementById('itemNameEn').value.trim()
|
||||||
|
};
|
||||||
|
|
||||||
|
// Collect multilingual description data
|
||||||
|
const descriptionData = {
|
||||||
|
nl: document.getElementById('itemDescriptionNl').value.trim(),
|
||||||
|
en: document.getElementById('itemDescriptionEn').value.trim()
|
||||||
|
};
|
||||||
|
|
||||||
|
// Validate that at least one language is filled for name
|
||||||
|
if (!nameData.nl && !nameData.en) {
|
||||||
|
alert('Please provide at least one name (Dutch or English)');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// If only one language is provided for name, copy it to the other
|
||||||
|
if (!nameData.nl && nameData.en) {
|
||||||
|
nameData.nl = nameData.en;
|
||||||
|
}
|
||||||
|
if (!nameData.en && nameData.nl) {
|
||||||
|
nameData.en = nameData.nl;
|
||||||
|
}
|
||||||
|
|
||||||
|
formData.append('name', JSON.stringify(nameData));
|
||||||
formData.append('location', document.getElementById('itemLocation').value);
|
formData.append('location', document.getElementById('itemLocation').value);
|
||||||
formData.append('description', document.getElementById('itemDescription').value);
|
formData.append('description', JSON.stringify(descriptionData));
|
||||||
formData.append('quantity', document.getElementById('itemQuantity').value);
|
formData.append('quantity', document.getElementById('itemQuantity').value);
|
||||||
|
|
||||||
const imageFile = document.getElementById('itemImage').files[0];
|
const imageFile = document.getElementById('itemImage').files[0];
|
||||||
|
|||||||
@@ -28,6 +28,14 @@ async function initializePage() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Reload content when language changes
|
||||||
|
function reloadContent() {
|
||||||
|
displayItems();
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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');
|
||||||
@@ -68,11 +76,21 @@ function getFilteredItems() {
|
|||||||
return items;
|
return items;
|
||||||
}
|
}
|
||||||
|
|
||||||
return items.filter(item =>
|
return items.filter(item => {
|
||||||
item.name.toLowerCase().includes(searchTerm) ||
|
if (window.translationManager) {
|
||||||
(item.description && item.description.toLowerCase().includes(searchTerm)) ||
|
const localizedItem = window.translationManager.getLocalizedItem(item);
|
||||||
item.location.toLowerCase().includes(searchTerm)
|
return localizedItem.name.toLowerCase().includes(searchTerm) ||
|
||||||
);
|
(localizedItem.description && localizedItem.description.toLowerCase().includes(searchTerm)) ||
|
||||||
|
item.location.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) ||
|
||||||
|
item.location.toLowerCase().includes(searchTerm);
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// Display items based on current view mode
|
// Display items based on current view mode
|
||||||
@@ -91,13 +109,19 @@ function displayGridView() {
|
|||||||
itemsGrid.classList.remove('d-none');
|
itemsGrid.classList.remove('d-none');
|
||||||
|
|
||||||
const filteredItems = getFilteredItems();
|
const filteredItems = getFilteredItems();
|
||||||
itemsGrid.innerHTML = filteredItems.map(item => `
|
itemsGrid.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">
|
<div class="col-md-4 col-lg-3">
|
||||||
<div class="card item-card">
|
<div class="card item-card">
|
||||||
<img src="${item.imageUrl || '/images/default-item.png'}" class="item-image" alt="${item.name}">
|
<img src="${item.imageUrl || '/images/default-item.png'}" class="item-image" alt="${localizedItem.name}">
|
||||||
<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 text-muted">${item.description || 'No description available'}</p>
|
<p class="card-text text-muted">${localizedItem.description || 'No description available'}</p>
|
||||||
<p class="card-text">Location: ${item.location}</p>
|
<p class="card-text">Location: ${item.location}</p>
|
||||||
<p class="card-text">Quantity: ${item.quantity}</p>
|
<p class="card-text">Quantity: ${item.quantity}</p>
|
||||||
<p class="card-text">Reserved: ${item.reserved || 0}</p>
|
<p class="card-text">Reserved: ${item.reserved || 0}</p>
|
||||||
@@ -108,7 +132,8 @@ function displayGridView() {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
`).join('');
|
`;
|
||||||
|
}).join('');
|
||||||
}
|
}
|
||||||
|
|
||||||
// Display items in list view
|
// Display items in list view
|
||||||
@@ -120,11 +145,17 @@ function displayListView() {
|
|||||||
|
|
||||||
const itemsListBody = document.getElementById('itemsListBody');
|
const itemsListBody = document.getElementById('itemsListBody');
|
||||||
const filteredItems = getFilteredItems();
|
const filteredItems = getFilteredItems();
|
||||||
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>
|
<tr>
|
||||||
<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}</td>
|
<td>${item.quantity}</td>
|
||||||
<td>${item.reserved || 0}</td>
|
<td>${item.reserved || 0}</td>
|
||||||
@@ -133,7 +164,8 @@ function displayListView() {
|
|||||||
<button class="btn btn-sm btn-danger" onclick="deleteItem('${item._id}')">Delete</button>
|
<button class="btn btn-sm btn-danger" onclick="deleteItem('${item._id}')">Delete</button>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
`).join('');
|
`;
|
||||||
|
}).join('');
|
||||||
}
|
}
|
||||||
|
|
||||||
// Load reservations
|
// Load reservations
|
||||||
|
|||||||
@@ -180,27 +180,47 @@ class TranslationManager {
|
|||||||
// Apply saved language preference
|
// Apply saved language preference
|
||||||
this.applyTranslations();
|
this.applyTranslations();
|
||||||
|
|
||||||
// Set up language toggle if it exists
|
// Set up language toggle buttons if they exist
|
||||||
const languageToggle = document.getElementById('languageToggle');
|
const nlBtn = document.getElementById('nlBtn');
|
||||||
if (languageToggle) {
|
const enBtn = document.getElementById('enBtn');
|
||||||
languageToggle.addEventListener('change', (e) => {
|
|
||||||
this.setLanguage(e.target.checked ? 'en' : 'nl');
|
if (nlBtn && enBtn) {
|
||||||
|
nlBtn.addEventListener('click', () => {
|
||||||
|
this.setLanguage('nl');
|
||||||
|
this.updateLanguageButtons();
|
||||||
});
|
});
|
||||||
|
|
||||||
// Set toggle state based on current language (Dutch is default/unchecked)
|
enBtn.addEventListener('click', () => {
|
||||||
languageToggle.checked = this.currentLanguage === 'en';
|
this.setLanguage('en');
|
||||||
this.updateLanguageLabels();
|
this.updateLanguageButtons();
|
||||||
|
});
|
||||||
|
|
||||||
|
// Set initial button states
|
||||||
|
this.updateLanguageButtons();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
updateLanguageButtons() {
|
||||||
|
const nlBtn = document.getElementById('nlBtn');
|
||||||
|
const enBtn = document.getElementById('enBtn');
|
||||||
|
|
||||||
|
if (nlBtn && enBtn) {
|
||||||
|
// Remove active class from both buttons
|
||||||
|
nlBtn.classList.remove('active');
|
||||||
|
enBtn.classList.remove('active');
|
||||||
|
|
||||||
|
// Add active class to current language button
|
||||||
|
if (this.currentLanguage === 'nl') {
|
||||||
|
nlBtn.classList.add('active');
|
||||||
|
} else {
|
||||||
|
enBtn.classList.add('active');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Keep the old method for backward compatibility (in case it's used elsewhere)
|
||||||
updateLanguageLabels() {
|
updateLanguageLabels() {
|
||||||
const nlLabel = document.getElementById('nlLabel');
|
this.updateLanguageButtons();
|
||||||
const enLabel = document.getElementById('enLabel');
|
|
||||||
|
|
||||||
if (nlLabel && enLabel) {
|
|
||||||
nlLabel.classList.toggle('active', this.currentLanguage === 'nl');
|
|
||||||
enLabel.classList.toggle('active', this.currentLanguage === 'en');
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
setLanguage(lang) {
|
setLanguage(lang) {
|
||||||
|
|||||||
@@ -27,12 +27,14 @@
|
|||||||
</ul>
|
</ul>
|
||||||
<div class="navbar-nav ms-auto">
|
<div class="navbar-nav ms-auto">
|
||||||
<div class="language-toggle nav-item">
|
<div class="language-toggle nav-item">
|
||||||
<span class="language-label" id="nlLabel">NL</span>
|
<div class="language-switcher">
|
||||||
<label class="language-switch">
|
<button class="language-btn active" id="nlBtn" data-lang="nl">
|
||||||
<input type="checkbox" id="languageToggle">
|
<i class="fas fa-flag me-1"></i>Nederlands
|
||||||
<span class="language-slider"></span>
|
</button>
|
||||||
</label>
|
<button class="language-btn" id="enBtn" data-lang="en">
|
||||||
<span class="language-label" id="enLabel">EN</span>
|
<i class="fas fa-flag me-1"></i>English
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
</div>
|
</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" data-translate="logout">Uitloggen</a>
|
<a class="nav-link" href="#" id="logoutBtn" data-translate="logout">Uitloggen</a>
|
||||||
|
|||||||
@@ -27,12 +27,14 @@
|
|||||||
</ul>
|
</ul>
|
||||||
<div class="navbar-nav ms-auto">
|
<div class="navbar-nav ms-auto">
|
||||||
<div class="language-toggle nav-item">
|
<div class="language-toggle nav-item">
|
||||||
<span class="language-label" id="nlLabel">NL</span>
|
<div class="language-switcher">
|
||||||
<label class="language-switch">
|
<button class="language-btn active" id="nlBtn" data-lang="nl">
|
||||||
<input type="checkbox" id="languageToggle">
|
<i class="fas fa-flag me-1"></i>Nederlands
|
||||||
<span class="language-slider"></span>
|
</button>
|
||||||
</label>
|
<button class="language-btn" id="enBtn" data-lang="en">
|
||||||
<span class="language-label" id="enLabel">EN</span>
|
<i class="fas fa-flag me-1"></i>English
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
</div>
|
</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" data-translate="logout">Uitloggen</a>
|
<a class="nav-link" href="#" id="logoutBtn" data-translate="logout">Uitloggen</a>
|
||||||
|
|||||||
@@ -53,9 +53,27 @@ router.get('/:id', auth, async (req, res) => {
|
|||||||
// Add new item (admin only)
|
// Add new item (admin only)
|
||||||
router.post('/', auth, adminOnly, upload.single('image'), async (req, res) => {
|
router.post('/', auth, adminOnly, upload.single('image'), async (req, res) => {
|
||||||
try {
|
try {
|
||||||
|
let name, description;
|
||||||
|
|
||||||
|
// Parse multilingual name data
|
||||||
|
try {
|
||||||
|
name = typeof req.body.name === 'string' ? JSON.parse(req.body.name) : req.body.name;
|
||||||
|
} catch (e) {
|
||||||
|
// If parsing fails, assume it's a simple string and create multilingual object
|
||||||
|
name = { nl: req.body.name, en: req.body.name };
|
||||||
|
}
|
||||||
|
|
||||||
|
// Parse multilingual description data
|
||||||
|
try {
|
||||||
|
description = typeof req.body.description === 'string' ? JSON.parse(req.body.description) : req.body.description;
|
||||||
|
} catch (e) {
|
||||||
|
// If parsing fails, assume it's a simple string and create multilingual object
|
||||||
|
description = { nl: req.body.description || '', en: req.body.description || '' };
|
||||||
|
}
|
||||||
|
|
||||||
const itemData = {
|
const itemData = {
|
||||||
name: req.body.name,
|
name: name,
|
||||||
description: req.body.description,
|
description: description,
|
||||||
location: req.body.location,
|
location: req.body.location,
|
||||||
quantity: parseInt(req.body.quantity)
|
quantity: parseInt(req.body.quantity)
|
||||||
};
|
};
|
||||||
@@ -76,9 +94,27 @@ router.post('/', auth, adminOnly, upload.single('image'), async (req, res) => {
|
|||||||
// Update item (admin only)
|
// Update item (admin only)
|
||||||
router.put('/:id', auth, adminOnly, upload.single('image'), async (req, res) => {
|
router.put('/:id', auth, adminOnly, upload.single('image'), async (req, res) => {
|
||||||
try {
|
try {
|
||||||
|
let name, description;
|
||||||
|
|
||||||
|
// Parse multilingual name data
|
||||||
|
try {
|
||||||
|
name = typeof req.body.name === 'string' ? JSON.parse(req.body.name) : req.body.name;
|
||||||
|
} catch (e) {
|
||||||
|
// If parsing fails, assume it's a simple string and create multilingual object
|
||||||
|
name = { nl: req.body.name, en: req.body.name };
|
||||||
|
}
|
||||||
|
|
||||||
|
// Parse multilingual description data
|
||||||
|
try {
|
||||||
|
description = typeof req.body.description === 'string' ? JSON.parse(req.body.description) : req.body.description;
|
||||||
|
} catch (e) {
|
||||||
|
// If parsing fails, assume it's a simple string and create multilingual object
|
||||||
|
description = { nl: req.body.description || '', en: req.body.description || '' };
|
||||||
|
}
|
||||||
|
|
||||||
const updateData = {
|
const updateData = {
|
||||||
name: req.body.name,
|
name: name,
|
||||||
description: req.body.description,
|
description: description,
|
||||||
location: req.body.location,
|
location: req.body.location,
|
||||||
quantity: parseInt(req.body.quantity)
|
quantity: parseInt(req.body.quantity)
|
||||||
};
|
};
|
||||||
|
|||||||
Reference in New Issue
Block a user