Files
Berufsschule_HAM/src/Views/Home/Users.cshtml

785 lines
37 KiB
Plaintext
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
@using Microsoft.AspNetCore.Mvc.Localization
@using Berufsschule_HAM.Models
@using System.Buffers.Text
@using System.Text.Json;
@using System.Text.Json.Serialization;
@model UsersIndexViewModel
@inject IViewLocalizer T
@{
ViewData["Title"] = T["Users"];
}
<div class="container py-4">
<h2 class="mb-3">@T["Users"]</h2>
<div class="mb-4 d-flex flex-wrap gap-2">
<button class="btn btn-primary" data-bs-toggle="modal" data-bs-target="#createModal">
@T["Create user"]
</button>
</div>
<div class="table-responsive">
<table class="table table-striped align-middle">
<thead>
<tr>
<th style="width: 2rem;"></th>
<th>User ID</th>
<th>title</th>
<th>Name</th>
<th>Surname</th>
<th>Workplace</th>
<th>Action</th>
</tr>
</thead>
<tbody>
@{
foreach (UserTableViewModel userTableViewModel in Model.UserTableViewModels)
{
<tr class="user-row">
<td>
<img class="rounded-circle user-icon" src="data:image/jpeg;base64,@userTableViewModel.JpegPhoto" alt="Photo" style="max-width:300px;" />
</td>
<td>@userTableViewModel.Uid</td>
<td>@userTableViewModel.Title</td>
<td>@userTableViewModel.Name</td>
<td>@userTableViewModel.Surname</td>
<td>@userTableViewModel.Description.Workplace</td>
<td>
<div class="d-flex gap-2">
<button class="btn btn-sm btn-warning"
data-user-id="@userTableViewModel.Uid"
data-user-title="@userTableViewModel.Title"
data-user-name="@userTableViewModel.Name"
data-user-surname="@userTableViewModel.Surname"
data-user-workplace="@userTableViewModel.Description.Workplace"
data-user-birthdate="@userTableViewModel.Description.BirthDate"
data-user-address-city="@userTableViewModel.Description.Address.City"
data-user-address-street="@userTableViewModel.Description.Address.Street"
data-user-address-streetnr="@userTableViewModel.Description.Address.StreetNr"
data-user-groups="@JsonSerializer.Serialize(userTableViewModel.Description.Groups)"
data-bs-toggle="modal"
data-bs-target="#updateModal">
Update
</button>
<button class="btn btn-sm btn-danger btn-delete"
data-user-id="@userTableViewModel.Uid"
data-bs-toggle="modal"
data-bs-target="#deleteModal">
Delete
</button>
</div>
</td>
</tr>
}
}
</tbody>
</table>
</div>
</div>
<!-- User Delete Confirmation Modal -->
<div class="modal fade" id="deleteModal" tabindex="-1" aria-labelledby="deleteModalLabel" aria-hidden="true">
<div class="modal-dialog modal-dialog-centered">
<div class="modal-content">
<div class="modal-header bg-danger text-white">
<h5 class="modal-title" id="deleteModalLabel">Confirm Delete</h5>
<button type="button" class="btn-close btn-close-white" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
<div class="modal-body">
<p>Are you sure you want to delete the user <strong id="userName"></strong> (ID: <span id="userId"></span>)?</p>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Cancel</button>
<form id="deleteForm" method="post" action="">
<button type="submit" class="btn btn-danger">Yes, Delete</button>
</form>
</div>
</div>
</div>
</div>
<script>
// Simple toast helper
function showToast(message, type) {
const toastContainer = document.getElementById('toastContainer') || createToastContainer();
const toast = document.createElement('div');
toast.className = `toast align-items-center text-white bg-${type} border-0`;
toast.role = 'alert';
toast.innerHTML = `
<div class="d-flex">
<div class="toast-body">${message}</div>
<button type="button" class="btn-close btn-close-white me-2 m-auto" data-bs-dismiss="toast"></button>
</div>
`;
toastContainer.appendChild(toast);
const bsToast = new bootstrap.Toast(toast, { delay: 3000 });
bsToast.show();
toast.addEventListener('hidden.bs.toast', () => toast.remove());
}
function createToastContainer() {
const container = document.createElement('div');
container.id = 'toastContainer';
container.className = 'toast-container position-fixed bottom-0 end-0 p-3';
document.body.appendChild(container);
return container;
}
</script>
<script>
document.addEventListener('DOMContentLoaded', () => {
const deleteModal = document.getElementById('deleteModal');
let currentButton = null; // The delete button that opened the modal
deleteModal.addEventListener('show.bs.modal', event => {
currentButton = event.relatedTarget; // Button that triggered the modal
const userId = currentButton.getAttribute('data-user-id');
const userName = currentButton.getAttribute('data-user-name');
deleteModal.querySelector('#userId').textContent = userId;
deleteModal.querySelector('#userName').textContent = userName;
// Store the delete URL for later use
deleteModal.querySelector('#deleteForm').dataset.url = `/Users/Delete?uid=${userId}`;
});
// Handle submit of deleteForm via fetch()
const deleteForm = document.getElementById('deleteForm');
deleteForm.addEventListener('submit', async e => {
e.preventDefault();
const url = deleteForm.dataset.url;
const userId = deleteModal.querySelector('#userId').textContent;
try {
const response = await fetch(url, {
method: 'GET',
headers: {
'Content-Type': 'application/json',
'Accept': 'application/json'
}
});
const result = await response.json();
if (result.success) {
// Close the modal
const modal = bootstrap.Modal.getInstance(deleteModal);
modal.hide();
// Remove the row from the table
const row = currentButton.closest('tr');
row.classList.add('table-danger');
setTimeout(() => row.remove(), 300);
showToast('User deleted successfully', 'success');
} else {
showToast(`❌ ${result.reason}: ${result.exception || 'Unknown error'}`, 'danger');
}
} catch (error) {
console.error(error);
showToast('Error contacting server', 'danger');
}
});
});
</script>
<!-- User Update Modal -->
<div class="modal fade" id="updateModal" tabindex="-1" aria-labelledby="updateModalLabel" aria-hidden="true">
<div class="modal-dialog modal-dialog-centered modal-lg">
<div class="modal-content">
<div class="modal-header bg-warning text-dark">
<h5 class="modal-title" id="updateModalLabel">Update User</h5>
<button type="button" class="btn-close btn-close-white" data-bs-dismiss="modal"></button>
</div>
<div class="modal-body">
<form id="updateForm">
<input type="hidden" id="updateUid" name="Uid" />
<div class="row g-3">
<h6 class="fw-bold">Personal information</h6>
<div class="col-md-6">
<label class="form-label">Title</label>
<input type="text" id="updateTitle" name="Title" class="form-control" />
</div>
<div class="col-md-6">
<label class="form-label">Name</label>
<input type="text" id="updateName" name="Cn" class="form-control" />
</div>
<div class="col-md-6">
<label class="form-label">Surname</label>
<input type="text" id="updateSurname" name="Sn" class="form-control" />
</div>
<div class="col-md-6">
<label class="form-label">Birth date</label>
<input type="text" id="updateBirthdate" name="Description.BirthDate" class="form-control" />
</div>
<div class="col-md-6">
<label class="form-label">City</label>
<input type="text" id="updateAddressCity" name="Description.Address.City" class="form-control" />
</div>
<div class="col-md-6">
<label class="form-label">Street</label>
<input type="text" id="updateAddressStreet" name="Description.Address.Street" class="form-control" />
</div>
<div class="col-md-6">
<label class="form-label">Street Nr.</label>
<input type="text" id="updateAddressStreetNr" name="Description.Address.StreetNr" class="form-control" />
</div>
<hr class="my-3">
<h6 class="fw-bold">Workplace & account</h6>
<div class="col-md-6">
<label class="form-label">Workplace</label>
<input type="text" id="updateWorkplace" name="Description.Workplace" class="form-control" />
</div>
<div class="col-md-6">
<label class="form-label">Groups</label>
<select id="updateGroups" name="Description.Groups" class="form-select" multiple></select>
</div>
<div class="col-md-6">
<label class="form-label">New Password</label>
<input type="password" id="updatePassword" name="UserPassword" class="form-control" />
</div>
<div class="col-md-6">
<label class="form-label">Photo</label>
<input type="file" id="updatePhotoFile" accept="image/*" class="form-control" />
<input type="hidden" id="updateJpegPhoto" name="JpegPhoto" />
<div class="mt-2 text-center">
<img id="updatePhotoPreview" src="" alt="Preview" class="img-thumbnail" style="max-height: 150px; display: none;" />
</div>
</div>
</div>
</form>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Cancel</button>
<button type="submit" form="updateForm" class="btn btn-warning">Save changes</button>
</div>
</div>
</div>
</div>
<script>
function unflatten(obj) {
const result = {};
for (const [key, value] of Object.entries(obj)) {
const parts = key.split(".");
let current = result;
for (let i = 0; i < parts.length; i++) {
const part = parts[i];
if (i === parts.length - 1) {
// Last part — assign value
try {
// Try to parse JSON strings like "[...]" if possible
current[part] = JSON.parse(value);
} catch {
current[part] = value;
}
} else {
// Intermediate part — create object if needed
current[part] = current[part] || {};
current = current[part];
}
}
}
return result;
}
document.addEventListener('DOMContentLoaded', () => {
// --- Update user modal ---
const updateModal = document.getElementById('updateModal');
const updateForm = document.getElementById('updateForm');
let updateButton = null;
updateModal.addEventListener('show.bs.modal', event => {
updateButton = event.relatedTarget;
const userId = updateButton.getAttribute('data-user-id');
const title = updateButton.getAttribute('data-user-title');
const name = updateButton.getAttribute('data-user-name');
const surname = updateButton.getAttribute('data-user-surname');
const workplace = updateButton.getAttribute('data-user-workplace');
const birthdate = updateButton.getAttribute('data-user-birthdate');
const addressCity = updateButton.getAttribute('data-user-address-city');
const addressStreet = updateButton.getAttribute('data-user-address-street');
const addressStreetNr = updateButton.getAttribute('data-user-address-streetnr');
// Fill form fields
updateForm.querySelector('#updateUid').value = userId;
updateForm.querySelector('#updateTitle').value = title || '';
updateForm.querySelector('#updateName').value = name || '';
updateForm.querySelector('#updateSurname').value = surname || '';
updateForm.querySelector('#updateWorkplace').value = workplace || '';
updateForm.querySelector('#updateBirthdate').value = birthdate || '';
updateForm.querySelector('#updateAddressCity').value = addressCity || '';
updateForm.querySelector('#updateAddressStreet').value = addressStreet || '';
updateForm.querySelector('#updateAddressStreetNr').value = addressStreetNr || '';
updateForm.querySelector('#updatePassword').value = '';
updateForm.querySelector('#updateJpegPhoto').value = '';
});
updateForm.addEventListener('submit', async e => {
e.preventDefault();
var dataFromEntries = Object.fromEntries(new FormData(updateForm).entries());
var data = unflatten(dataFromEntries);
data.Description.Groups = Array.from(updateForm.querySelector('#updateGroups').selectedOptions).map(option => option.value);
try {
const response = await fetch('/Users/Update', {
method: 'POST',
headers: { 'Content-Type': 'application/json', 'Accept': 'application/json' },
body: JSON.stringify(data)
});
const result = await response.json();
if (result.Success) {
// Close modal
bootstrap.Modal.getInstance(updateModal).hide();
// Update table row
const row = updateButton.closest('tr');
row.querySelector('td:nth-child(2)').textContent = result.NewUid || '';
row.querySelector('td:nth-child(3)').textContent = data.Title || '';
row.querySelector('td:nth-child(4)').textContent = data.Cn || '';
row.querySelector('td:nth-child(5)').textContent = data.Sn || '';
row.querySelector('td:nth-child(6)').textContent = data.Description?.Workplace || '';
// Update button data attributes, so the next edit uses the new data
updateButton.setAttribute('data-user-id', result.NewUid || '');
updateButton.setAttribute('data-user-title', data.Title || '');
updateButton.setAttribute('data-user-name', data.Cn || '');
updateButton.setAttribute('data-user-surname', data.Sn || '');
updateButton.setAttribute('data-user-workplace', data.Description?.Workplace || '');
updateButton.setAttribute('data-user-groups', JSON.stringify(data.Description?.Groups || []));
if (data.JpegPhoto && data.JpegPhoto.length > 0) {
const img = row.querySelector('td:first-child img');
if (img) {
img.src = `data:image/jpeg;base64,${data.JpegPhoto}`;
img.classList.add('border', 'border-success'); // optional visual feedback
setTimeout(() => img.classList.remove('border', 'border-success'), 1000);
}
}
updateForm.reset();
document.getElementById('updatePhotoPreview').style.display = "none";
showToast('User updated successfully', 'success');
} else {
showToast(`${result.Exception || 'Update failed'}`, 'danger');
}
} catch (err) {
console.error(err);
showToast('Error updating user', 'danger');
}
});
});
// Group select drop-down
document.addEventListener('DOMContentLoaded', () => {
const updateModal = document.getElementById('updateModal');
const updateForm = document.getElementById('updateForm');
const updateGroupsSelect = document.getElementById('updateGroups');
let updateButton = null;
updateModal.addEventListener('show.bs.modal', async event => {
updateButton = event.relatedTarget;
const userId = updateButton.getAttribute('data-user-id');
const title = updateButton.getAttribute('data-user-title');
const name = updateButton.getAttribute('data-user-name');
const surname = updateButton.getAttribute('data-user-surname');
const workplace = updateButton.getAttribute('data-user-workplace');
const groups = JSON.parse(updateButton.getAttribute('data-user-groups'));
// Fill basic fields
updateForm.querySelector('#updateUid').value = userId;
updateForm.querySelector('#updateTitle').value = title || '';
updateForm.querySelector('#updateName').value = name || '';
updateForm.querySelector('#updateSurname').value = surname || '';
updateForm.querySelector('#updateWorkplace').value = workplace || '';
updateForm.querySelector('#updatePassword').value = '';
updateForm.querySelector('#updateJpegPhoto').value = '';
// Fetch available groups
await populateGroupsDropdown(updateGroupsSelect, groups);
// Fetch user's existing groups (you need to provide them via data-attribute or fetch)
const existingGroups = updateButton.getAttribute('data-user-groups');
if (existingGroups) {
const selectedGroups = existingGroups.split(',');
Array.from(updateGroupsSelect.options).forEach(opt => {
opt.selected = selectedGroups.includes(opt.value);
});
}
});
async function populateGroupsDropdown(selectElement, preselectedGroups) {
try {
const res = await fetch('/Groups/Get');
const json = await res.json();
if (!json.success) {
console.warn('Failed to fetch groups', json);
return;
}
selectElement.innerHTML = '';
json.GroupModels.forEach(group => {
const opt = document.createElement('option');
opt.value = group.Cn;
opt.textContent = group.DisplayName || group.Cn;
selectElement.appendChild(opt);
if (preselectedGroups != null && preselectedGroups.includes(group.Cn)) {
setTimeout(() => {
opt.selected = true;
}, 200);
}
});
} catch (err) {
console.error('Error fetching groups', err);
}
}
});
document.addEventListener('DOMContentLoaded', () => {
const photoInput = document.getElementById('updatePhotoFile');
const photoPreview = document.getElementById('updatePhotoPreview');
const photoHidden = document.getElementById('updateJpegPhoto');
photoInput.addEventListener('change', () => {
const file = photoInput.files[0];
if (!file) return;
const reader = new FileReader();
reader.onload = e => {
const base64 = e.target.result.split(',')[1]; // strip data:image/jpeg;base64,
photoHidden.value = base64;
photoPreview.src = e.target.result;
photoPreview.style.display = 'block';
};
reader.readAsDataURL(file);
});
});
</script>
<!-- User Create Modal -->
<div class="modal fade" id="createModal" tabindex="-1" aria-labelledby="createModalLabel" aria-hidden="true">
<div class="modal-dialog modal-dialog-centered modal-lg">
<div class="modal-content">
<div class="modal-header bg-primary text-white">
<h5 class="modal-title" id="createModalLabel">Create User</h5>
<button type="button" class="btn-close btn-close-white" data-bs-dismiss="modal"></button>
</div>
<div class="modal-body">
<form id="createForm">
<div class="row g-3">
<h6 class="fw-bold">Personal information</h6>
<div class="col-md-6">
<label class="form-label">Title</label>
<input type="text" name="Title" class="form-control" />
</div>
<div class="col-md-6">
<label class="form-label">Name</label>
<input type="text" name="Cn" class="form-control" />
</div>
<div class="col-md-6">
<label class="form-label">Surname</label>
<input type="text" name="Sn" class="form-control" />
</div>
<div class="col-md-6">
<label class="form-label">Birth date</label>
<input type="text" name="Description.BirthDate" class="form-control" />
</div>
<div class="col-md-6">
<label class="form-label">City</label>
<input type="text" name="Description.Address.City" class="form-control" />
</div>
<div class="col-md-6">
<label class="form-label">Street</label>
<input type="text" name="Description.Address.Street" class="form-control" />
</div>
<div class="col-md-6">
<label class="form-label">Street Nr.</label>
<input type="text" name="Description.Address.StreetNr" class="form-control" />
</div>
<hr class="my-3">
<h6 class="fw-bold">Workplace & account</h6>
<div class="col-md-6">
<label class="form-label">Workplace</label>
<input type="text" name="Description.Workplace" class="form-control" />
</div>
<div class="col-md-6">
<label class="form-label">Groups</label>
<select id="createGroups" name="Description.Groups" class="form-select" multiple></select>
</div>
<div class="col-md-6">
<label class="form-label">Password</label>
<input type="password" name="UserPassword" class="form-control" />
</div>
<div class="col-md-6">
<label class="form-label">Photo</label>
<input type="file" id="createPhotoFile" accept="image/*" class="form-control" />
<input type="hidden" id="createJpegPhoto" name="JpegPhoto" />
<div class="mt-2 text-center">
<img id="createPhotoPreview" src="" alt="Preview" class="img-thumbnail" style="max-height: 150px; display: none;" />
</div>
</div>
</div>
</form>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Cancel</button>
<button type="submit" form="createForm" class="btn btn-primary">Create</button>
</div>
</div>
</div>
</div>
<script>
document.addEventListener('DOMContentLoaded', () => {
const createModal = document.getElementById('createModal');
const createForm = document.getElementById('createForm');
const createGroupsSelect = document.getElementById('createGroups');
const photoInput = document.getElementById('createPhotoFile');
const photoPreview = document.getElementById('createPhotoPreview');
const photoHidden = document.getElementById('createJpegPhoto');
// Load available groups when modal is shown
createModal.addEventListener('show.bs.modal', async () => {
await populateGroupsDropdown(createGroupsSelect);
createForm.reset();
photoPreview.style.display = 'none';
});
// Handle photo upload preview
photoInput.addEventListener('change', () => {
const file = photoInput.files[0];
if (!file) return;
const reader = new FileReader();
reader.onload = e => {
const base64 = e.target.result.split(',')[1];
photoHidden.value = base64;
photoPreview.src = e.target.result;
photoPreview.style.display = 'block';
};
reader.readAsDataURL(file);
});
// Submit create form
createForm.addEventListener('submit', async e => {
e.preventDefault();
const dataFromEntries = Object.fromEntries(new FormData(createForm).entries());
const data = unflatten(dataFromEntries);
data.Description.Groups = Array.from(createGroupsSelect.selectedOptions).map(o => o.value);
try {
const response = await fetch('/Users/Create', {
method: 'POST',
headers: { 'Content-Type': 'application/json', 'Accept': 'application/json' },
body: JSON.stringify(data)
});
const result = await response.json();
if (result.Success) {
bootstrap.Modal.getInstance(createModal).hide();
showToast('User created successfully', 'success');
// Add new row to table dynamically
const tbody = document.querySelector('table tbody');
const newRow = document.createElement('tr');
newRow.innerHTML = `
<td><img class="rounded-circle user-icon" src="data:image/jpeg;base64,${data.JpegPhoto || ''}" alt="Photo" style="max-width:300px;" /></td>
<td>${result.NewUid || ''}</td>
<td>${data.Title || ''}</td>
<td>${data.Cn || ''}</td>
<td>${data.Sn || ''}</td>
<td>${data.Description?.Workplace || ''}</td>
<td>
<div class="d-flex gap-2">
<button class="btn btn-sm btn-warning"
data-user-id="${result.Uid || ''}"
data-user-title="${data.Title || ''}"
data-user-name="${data.Cn || ''}"
data-user-surname="${data.Sn || ''}"
data-user-workplace="${data.Description?.Workplace || ''}"
data-user-groups='${JSON.stringify(data.Description?.Groups || [])}'
data-bs-toggle="modal"
data-bs-target="#updateModal">
Update
</button>
<button class="btn btn-sm btn-danger btn-delete"
data-user-id="${result.NewUid || ''}"
data-user-name="${data.Cn || ''}"
data-bs-toggle="modal"
data-bs-target="#deleteModal">
Delete
</button>
</div>
</td>
`;
tbody.appendChild(newRow);
} else {
showToast(`${result.Exception || 'Create failed'}`, 'danger');
}
} catch (err) {
console.error(err);
showToast('Error creating user', 'danger');
}
});
async function populateGroupsDropdown(selectElement) {
try {
const res = await fetch('/Groups/Get');
const json = await res.json();
if (!json.success) return;
selectElement.innerHTML = '';
json.GroupModels.forEach(group => {
const opt = document.createElement('option');
opt.value = group.Cn;
opt.textContent = group.DisplayName || group.Cn;
selectElement.appendChild(opt);
});
} catch (err) {
console.error('Error fetching groups', err);
}
}
});
</script>
<!-- User Detail Modal -->
<div class="modal fade" id="detailModal" tabindex="-1" aria-labelledby="detailModalLabel" aria-hidden="true">
<div class="modal-dialog modal-dialog-centered modal-lg">
<div class="modal-content">
<div class="modal-header bg-info text-white">
<h5 class="modal-title" id="detailModalLabel">User Details</h5>
<button type="button" class="btn-close btn-close-white" data-bs-dismiss="modal"></button>
</div>
<div class="modal-body">
<div class="row g-3">
<div class="col-md-4 text-center">
<img id="detailPhoto" class="img-thumbnail rounded-circle" style="max-height:150px;" />
</div>
<div class="col-md-6">
<label class="form-label">@T["User ID"]</label>
<input type="text" class="form-control" id="detailUid" value="" disabled />
</div>
<div class="col-md-6">
<label class="form-label">@T["Title"]</label>
<input type="text" class="form-control" id="detailTitle" value="" disabled />
</div>
<div class="col-md-6">
<label class="form-label">@T["Name"]</label>
<input type="text" class="form-control" id="detailName" value="" disabled />
</div>
<div class="col-md-6">
<label class="form-label">@T["Surname"]</label>
<input type="text" class="form-control" id="detailSurname" value="" disabled />
</div>
<div class="col-md-6">
<label class="form-label">@T["Birth Date"]</label>
<input type="text" class="form-control" id="detailBirthdate" value="" disabled />
</div>
<hr class="my-3">
<h6 class="fw-bold">Address</h6>
<div class="col-md-4">
<label class="form-label">@T["City"]</label>
<input type="text" class="form-control" id="detailCity" value="" disabled />
</div>
<div class="col-md-6">
<label class="form-label">@T["Street"]</label>
<input type="text" class="form-control" id="detailStreet" value="" disabled />
</div>
<div class="col-md-2">
<label class="form-label">@T["Street Nr"]</label>
<input type="text" class="form-control" id="detailStreetNr" value="" disabled />
</div>
<hr class="my-3">
<h6 class="fw-bold">Workplace & account</h6>
<div class="col-md-6">
<label class="form-label">@T["Workplace"]</label>
<input type="text" class="form-control" id="detailWorkplace" value="" disabled />
</div>
<div class="col-md-6">
<label class="form-label">@T["Groups"]</label>
<input type="text" class="form-control" id="detailGroups" value="" disabled />
</div>
</div>
</div>
</div>
</div>
</div>
<script>
document.addEventListener('DOMContentLoaded', () => {
const rows = document.querySelectorAll('table tbody tr');
const detailModalEl = document.getElementById('detailModal');
const detailModal = new bootstrap.Modal(detailModalEl);
rows.forEach(row => {
row.addEventListener('click', (e) => {
// Dont trigger when clicking inside the action buttons
if (e.target.closest('button')) return;
const updateBtn = row.querySelector('.btn-warning[data-user-id]');
if (!updateBtn) return;
// Extract data from update button
const data = {
uid: updateBtn.dataset.userId,
title: updateBtn.dataset.userTitle,
name: updateBtn.dataset.userName,
surname: updateBtn.dataset.userSurname,
workplace: updateBtn.dataset.userWorkplace,
birthdate: updateBtn.dataset.userBirthdate,
city: updateBtn.dataset.userAddressCity,
street: updateBtn.dataset.userAddressStreet,
streetNr: updateBtn.dataset.userAddressStreetnr,
groups: JSON.parse(updateBtn.dataset.userGroups || '[]'),
};
console.log(data);
// Fill modal fields
document.getElementById('detailUid').value = data.uid || '';
document.getElementById('detailTitle').value = data.title || '';
document.getElementById('detailName').value = data.name || '';
document.getElementById('detailSurname').value = data.surname || '';
document.getElementById('detailBirthdate').value = data.birthdate || '';
document.getElementById('detailCity').value = data.city || '';
document.getElementById('detailStreet').value = data.street || '';
document.getElementById('detailStreetNr').value = data.streetNr || '';
document.getElementById('detailWorkplace').value = data.workplace || '';
document.getElementById('detailGroups').value = data.groups.join(', ') || '';
// Photo
const imgEl = row.querySelector('td:first-child img');
const detailPhoto = document.getElementById('detailPhoto');
if (imgEl && imgEl.src.startsWith('data:image')) {
detailPhoto.src = imgEl.src;
detailPhoto.style.display = 'block';
} else {
detailPhoto.style.display = 'none';
}
detailModal.show();
});
});
});
</script>
<style>
.user-row > td:not(:last-child) {
cursor: pointer;
}
.user-row > td {
transition: 0.1s ease;
}
.user-row:has(td:not(:last-child):hover) > td {
background-color: #17a2b8;
}
</style>