Merge pull request #167 from LD-Reborn/155-feature-add-locations-front-end-crud

155 feature add locations front end crud
This commit is contained in:
LD50
2025-10-25 16:40:44 +02:00
committed by GitHub
5 changed files with 331 additions and 40 deletions

View File

@@ -26,8 +26,8 @@ public class LocationsController : Controller
return list;
}
[HttpGet("Create")]
public async Task<bool> Create(LocationsCreateRequestModel model)
[HttpPost("Create")]
public async Task<bool> Create([FromBody]LocationsCreateRequestModel model)
{
try
{
@@ -67,7 +67,7 @@ public class LocationsController : Controller
}
[HttpPost("Update")]
public async Task<bool> Update(LocationsModifyRequestModel requestModel)
public async Task<bool> Update([FromBody]LocationsModifyRequestModel requestModel)
{
if (requestModel is null)
{

View File

@@ -1,6 +1,7 @@
namespace Berufsschule_HAM.Models;
using System.Text.Json;
using System.Text.Json.Serialization;
using Berufsschule_HAM.Exceptions;
public class LocationModel
{
@@ -22,8 +23,11 @@ public class LocationModel
}
public class LocationsDescription
{
[JsonPropertyName("Location")]
public string? Location { get; set; }
[JsonPropertyName("RoomNumber")]
public string? RoomNumber { get; set; }
[JsonPropertyName("Seat")]
public string? Seat { get; set; }
}

View File

@@ -1,13 +1,18 @@
using System.Text.Json.Serialization;
namespace Berufsschule_HAM.Models;
public class LocationsCreateRequestModel
{
[JsonPropertyName("Description")]
public required LocationsDescription LocationsDescription { get; set; }
}
public class LocationsModifyRequestModel
{
[JsonPropertyName("Location")]
public required string Location { get; set; }
[JsonPropertyName("Description")]
public required LocationsDescription Description { get; set; }
}

View File

@@ -19,4 +19,46 @@
<data name="Create location" xml:space="preserve">
<value>Ort anlegen</value>
</data>
<data name="Location ID" xml:space="preserve">
<value>Ort ID</value>
</data>
<data name="Location name" xml:space="preserve">
<value>Ort Name</value>
</data>
<data name="Room number" xml:space="preserve">
<value>Raumnummer</value>
</data>
<data name="Seat" xml:space="preserve">
<value>Sitz</value>
</data>
<data name="Action" xml:space="preserve">
<value>Aktion</value>
</data>
<data name="Edit" xml:space="preserve">
<value>Anpassen</value>
</data>
<data name="Delete" xml:space="preserve">
<value>Löschen</value>
</data>
<data name="Location deleted successfully" xml:space="preserve">
<value>Ort erfolgreich gelöscht</value>
</data>
<data name="Unknown error" xml:space="preserve">
<value>Unbekannter Fehler</value>
</data>
<data name="Error contacting server" xml:space="preserve">
<value>Server konnte nicht erreicht werden</value>
</data>
<data name="Location updated successfully" xml:space="preserve">
<value>Ort wurde erfolgreich angepasst</value>
</data>
<data name="Location update failed" xml:space="preserve">
<value>Ort konnte nicht angepasst werden</value>
</data>
<data name="Location created successfully" xml:space="preserve">
<value>Ort wurde erfolgreich erstellt</value>
</data>
<data name="Location creation failed" xml:space="preserve">
<value>Erstellung des Ortes ist fehlgeschlagen</value>
</data>
</root>

View File

@@ -12,7 +12,7 @@
<div class="mb-4 d-flex flex-wrap gap-2">
<button class="btn btn-primary">@T["Create location"]</button>
<button class="btn btn-primary" data-bs-toggle="modal" data-bs-target="#createModal">@T["Create location"]</button>
</div>
@@ -20,11 +20,11 @@
<table class="table table-striped align-middle">
<thead>
<tr>
<th>User</th>
<th>Asset ID</th>
<th>Asset Name</th>
<th>Location</th>
<th>Action</th>
<th>@T["Location ID"]</th>
<th>@T["Location name"]</th>
<th>@T["Room number"]</th>
<th>@T["Seat"]</th>
<th>@T["Action"]</th>
</tr>
</thead>
<tbody>
@@ -38,12 +38,20 @@
<td>@locationTableViewModel.Seat</td>
<td>
<div class="d-flex gap-2">
<button class="btn btn-sm btn-primary">Update</button>
<button class="btn btn-sm btn-warning btn-edit"
data-location-id="@locationTableViewModel.LocationID"
data-location-name="@locationTableViewModel.LocationName"
data-room-number="@locationTableViewModel.RoomNumber"
data-seat="@locationTableViewModel.Seat"
data-bs-toggle="modal"
data-bs-target="#editModal">
@T["Edit"]
</button>
<button class="btn btn-sm btn-danger btn-delete"
data-location-id="@locationTableViewModel.LocationID"
data-bs-toggle="modal"
data-bs-target="#deleteModal">
🗑️ Delete
@T["Delete"]
</button>
</div>
</td>
@@ -77,6 +85,35 @@
</div>
<script defer>
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;
}
// 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());
}
</script>
<script>
document.addEventListener('DOMContentLoaded', () => {
const deleteModal = document.getElementById('deleteModal');
@@ -124,40 +161,243 @@
row.classList.add('table-danger');
setTimeout(() => row.remove(), 300);
showToast('Location deleted successfully', 'success');
showToast('@T["Location deleted successfully"]', 'success');
} else {
showToast(`${result.reason}: ${result.exception || 'Unknown error'}`, 'danger');
showToast(`${result.reason}: ${result.exception || '@T["Unknown error"]'}`, 'danger');
}
} catch (error) {
console.error(error);
showToast('Error contacting server', 'danger');
showToast('@T["Error contacting server"]', 'danger');
}
});
});
</script>
<!-- Location Edit Modal -->
<div class="modal fade" id="editModal" tabindex="-1" aria-labelledby="editModalLabel" aria-hidden="true">
<div class="modal-dialog modal-dialog-centered">
<div class="modal-content">
<div class="modal-header bg-warning text-dark">
<h5 class="modal-title" id="editModalLabel">Edit Location</h5>
<button type="button" class="btn-close btn-close-white" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
<form id="editForm">
<div class="modal-body">
<input type="hidden" id="editLocationId" name="LocationID">
<div class="mb-3">
<label for="editLocationName" class="form-label">Location Name</label>
<input type="text" class="form-control" id="editLocationName" name="LocationName" required>
</div>
<div class="mb-3">
<label for="editRoomNumber" class="form-label">Room Number</label>
<input type="text" class="form-control" id="editRoomNumber" name="RoomNumber">
</div>
<div class="mb-3">
<label for="editSeat" class="form-label">Seat</label>
<input type="text" class="form-control" id="editSeat" name="Seat">
</div>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Cancel</button>
<button type="submit" class="btn btn-warning">Save changes</button>
</div>
</form>
</div>
</div>
</div>
<script>
document.addEventListener('DOMContentLoaded', () => {
// --- EDIT MODAL ---
const editModal = document.getElementById('editModal');
const editForm = document.getElementById('editForm');
editModal.addEventListener('show.bs.modal', event => {
const button = event.relatedTarget;
const id = button.getAttribute('data-location-id');
const name = button.getAttribute('data-location-name');
const room = button.getAttribute('data-room-number');
const seat = button.getAttribute('data-seat');
// Set values in the modal form
editForm.querySelector('#editLocationId').value = id;
editForm.querySelector('#editLocationName').value = name;
editForm.querySelector('#editRoomNumber').value = room;
editForm.querySelector('#editSeat').value = seat;
});
editForm.addEventListener('submit', async e => {
e.preventDefault();
const data = {
Location: editForm.editLocationId.value,
Description:
{
Location: editLocationName.value,
RoomNumber: editForm.editRoomNumber.value,
Seat: editForm.editSeat.value
}
};
try {
const response = await fetch('/Locations/Update', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Accept': 'application/json'
},
body: JSON.stringify(data)
});
const result = await response.json();
if (result) {
const btn = document.querySelector(`button[data-location-id="${data.Location}"]`);
const row = btn.closest('tr');
let slugifiedLocationID = `${data.Description.Location}-${data.Description.RoomNumber}-${data.Description.Seat}`
.toLowerCase()
.replace(/[^a-z0-9-]/g, '-')
.replace(/-+/g, '-')
.replace(/^-|-$/g, '');
btn.setAttribute("data-location-id", slugifiedLocationID);
btn.setAttribute("data-location-name", data.Description.Location);
btn.setAttribute("data-room-number", data.Description.RoomNumber);
btn.setAttribute("data-seat", data.Description.Seat);
row.children[0].textContent = slugifiedLocationID;
row.children[1].textContent = data.Description.Location;
row.children[2].textContent = data.Description.RoomNumber;
row.children[3].textContent = data.Description.Seat;
// Close modal
bootstrap.Modal.getInstance(editModal).hide();
showToast('@T["Location updated successfully"]', 'success');
} else {
showToast(`${result.reason || '@T["Location update failed"]'}`, 'danger');
}
} catch (error) {
console.error(error);
showToast('@T["Error contacting server"]', 'danger');
}
});
});
</script>
<!-- Location Create Modal -->
<div class="modal fade" id="createModal" tabindex="-1" aria-labelledby="createModalLabel" aria-hidden="true">
<div class="modal-dialog modal-dialog-centered">
<div class="modal-content">
<div class="modal-header bg-primary text-white">
<h5 class="modal-title" id="createModalLabel">Create Location</h5>
<button type="button" class="btn-close btn-close-white" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
<form id="createForm">
<div class="modal-body">
<div class="mb-3">
<label for="createLocationName" class="form-label">Location Name</label>
<input type="text" class="form-control" id="createLocationName" name="LocationName" required>
</div>
<div class="mb-3">
<label for="createRoomNumber" class="form-label">Room Number</label>
<input type="text" class="form-control" id="createRoomNumber" name="RoomNumber">
</div>
<div class="mb-3">
<label for="createSeat" class="form-label">Seat</label>
<input type="text" class="form-control" id="createSeat" name="Seat">
</div>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Cancel</button>
<button type="submit" class="btn btn-primary">Create</button>
</div>
</form>
</div>
</div>
</div>
<script>
document.addEventListener('DOMContentLoaded', () => {
// --- CREATE MODAL ---
const createForm = document.getElementById('createForm');
createForm.addEventListener('submit', async e => {
e.preventDefault();
const data = {
Description: {
Location: createForm.createLocationName.value,
RoomNumber: createForm.createRoomNumber.value,
Seat: createForm.createSeat.value
}
};
try {
const response = await fetch('/Locations/Create', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Accept': 'application/json'
},
body: JSON.stringify(data)
});
const result = await response.json();
if (result) {
const newLoc = data.Description;
const slugifiedLocationID = `${newLoc.Location}-${newLoc.RoomNumber}-${newLoc.Seat}`
.toLowerCase()
.replace(/[^a-z0-9-]/g, '-')
.replace(/-+/g, '-')
.replace(/^-|-$/g, '');
// Insert new row into the table dynamically
const tbody = document.querySelector('table tbody');
const newRow = document.createElement('tr');
newRow.innerHTML = `
<td>${slugifiedLocationID}</td>
<td>${newLoc.Location}</td>
<td>${newLoc.RoomNumber}</td>
<td>${newLoc.Seat}</td>
<td>
<div class="d-flex gap-2">
<button class="btn btn-sm btn-warning btn-edit"
data-location-id="${slugifiedLocationID}"
data-location-name="${newLoc.Location}"
data-room-number="${newLoc.RoomNumber}"
data-seat="${newLoc.Seat}"
data-bs-toggle="modal"
data-bs-target="#editModal">
Edit
</button>
<button class="btn btn-sm btn-danger btn-delete"
data-location-id="${slugifiedLocationID}"
data-location-name="${newLoc.Location}"
data-bs-toggle="modal"
data-bs-target="#deleteModal">
Delete
</button>
</div>
</td>
`;
tbody.appendChild(newRow);
// Close modal
bootstrap.Modal.getInstance(document.getElementById('createModal')).hide();
createForm.reset();
showToast('@T["Location created successfully"]', 'success');
} else {
showToast(`${result.reason || '@T["Location creation failed"]'}`, 'danger');
}
} catch (error) {
console.error(error);
showToast('@T["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>