mirror of
https://github.com/LD-Reborn/Berufsschule_HAM.git
synced 2025-12-20 15:01:56 +00:00
Implemented users update button in frontend
This commit is contained in:
@@ -1,6 +1,8 @@
|
||||
@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
|
||||
@{
|
||||
@@ -42,15 +44,29 @@
|
||||
<td>@userTableViewModel.Title</td>
|
||||
<td>@userTableViewModel.Name</td>
|
||||
<td>@userTableViewModel.Surname</td>
|
||||
<td>@userTableViewModel.Workplace</td>
|
||||
<td>@userTableViewModel.Description.Workplace</td>
|
||||
<td>
|
||||
<div class="d-flex gap-2">
|
||||
<button class="btn btn-sm btn-primary">Update</button>
|
||||
<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
|
||||
Delete
|
||||
</button>
|
||||
</div>
|
||||
</td>
|
||||
@@ -84,6 +100,34 @@
|
||||
</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');
|
||||
@@ -115,8 +159,7 @@
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'Accept': 'application/json'
|
||||
}//,
|
||||
//body: JSON.stringify({ id: userId }) // Use this for Post requests with [FromBody] parameters like in /Groups/Update
|
||||
}
|
||||
});
|
||||
|
||||
const result = await response.json();
|
||||
@@ -140,31 +183,281 @@
|
||||
showToast('Error contacting server', 'danger');
|
||||
}
|
||||
});
|
||||
|
||||
// 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>
|
||||
|
||||
<!-- 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>
|
||||
|
||||
Reference in New Issue
Block a user