Fixed Asset delete wrong HTTP method, Added Asset creation in /Home/Assets

This commit is contained in:
2025-10-10 10:22:16 +02:00
parent 0baa92c267
commit 2b5527a760
2 changed files with 243 additions and 42 deletions

View File

@@ -38,7 +38,7 @@ public class AssetsController : Controller
} }
[HttpPost("Create")] [HttpPost("Create")]
public async Task<AssetsCreateResponseModel> Create(AssetsCreateRequestModel assetModel) public async Task<AssetsCreateResponseModel> Create([FromBody]AssetsCreateRequestModel assetModel)
{ {
AssetsCreateResponseModel result; AssetsCreateResponseModel result;
if (assetModel is null) if (assetModel is null)

View File

@@ -12,7 +12,9 @@
<div class="mb-4 d-flex flex-wrap gap-2"> <div class="mb-4 d-flex flex-wrap gap-2">
<button class="btn btn-outline-primary">@T["Create asset"]</button> <button class="btn btn-outline-primary" data-bs-toggle="modal" data-bs-target="#createAssetModal">
@T["Create asset"]
</button>
</div> </div>
@@ -20,11 +22,11 @@
<table class="table table-striped align-middle"> <table class="table table-striped align-middle">
<thead> <thead>
<tr> <tr>
<th>User</th> <th>@T["Owner"]</th>
<th>Asset ID</th> <th>@T["Asset ID"]</th>
<th>Asset Name</th> <th>@T["Asset Name"]</th>
<th>Location</th> <th>@T["Location"]</th>
<th>Action</th> <th>@T["Action"]</th>
</tr> </tr>
</thead> </thead>
<tbody> <tbody>
@@ -104,7 +106,7 @@
try { try {
const response = await fetch(url, { const response = await fetch(url, {
method: 'GET', method: 'DELETE',
headers: { headers: {
'Content-Type': 'application/json', 'Content-Type': 'application/json',
'Accept': 'application/json' 'Accept': 'application/json'
@@ -161,45 +163,244 @@
} }
}); });
</script> </script>
@* <script>
<!-- Asset Create Modal -->
<div class="modal fade" id="createAssetModal" tabindex="-1" aria-labelledby="createAssetModalLabel" aria-hidden="true">
<div class="modal-dialog modal-lg modal-dialog-centered">
<div class="modal-content">
<div class="modal-header bg-primary text-white">
<h5 class="modal-title" id="createAssetModalLabel">@T["Create Asset"]</h5>
<button type="button" class="btn-close btn-close-white" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
<form id="createAssetForm">
<div class="modal-body">
<div class="row g-3">
<!-- Basic Info -->
<div class="col-md-6">
<label class="form-label">@T["Asset ID (Cn)"] *</label>
<input type="text" class="form-control" name="Cn" required />
</div>
<div class="col-md-6">
<label class="form-label">@T["Name"]</label>
<input type="text" class="form-control" name="Name" />
</div>
<div class="col-md-6">
<label class="form-label">@T["Location"]</label>
<input type="text" class="form-control" name="Location" />
</div>
<div class="col-md-6">
<label class="form-label">@T["Owner"]</label>
<input type="text" class="form-control" name="Owner" />
</div>
<div class="col-md-6">
<label class="form-label">@T["Serial Number"]</label>
<input type="text" class="form-control" name="SerialNumber" />
</div>
<hr class="my-3" />
<!-- Description Section -->
<h6 class="fw-bold">@T["Description"]</h6>
<div class="col-md-6">
<label class="form-label">@T["Type"]</label>
<input type="text" class="form-control" name="Description.Type" />
</div>
<div class="col-md-6">
<label class="form-label">@T["Make"]</label>
<input type="text" class="form-control" name="Description.Make" />
</div>
<div class="col-md-6">
<label class="form-label">@T["Model"]</label>
<input type="text" class="form-control" name="Description.Model" />
</div>
<!-- Attributes Section -->
<div class="col-12 mt-3">
<div class="d-flex justify-content-between align-items-center mb-2">
<h6 class="fw-bold mb-0">@T["Attributes"]</h6>
<button type="button" class="btn btn-sm btn-outline-success" id="addAttributeBtn">
@T["Add Attribute"]
</button>
</div>
<div id="attributesContainer" class="d-flex flex-column gap-2">
<!-- Dynamic attribute rows will appear here -->
</div>
</div>
<hr class="my-3" />
<!-- Purchase Info -->
<h6 class="fw-bold">@T["Purchase Information"]</h6>
<div class="col-md-6">
<label class="form-label">@T["Purchase Date"]</label>
<input type="date" class="form-control" name="Description.Purchase.PurchaseDate" />
</div>
<div class="col-md-6">
<label class="form-label">@T["Purchase Value"]</label>
<input type="text" class="form-control" name="Description.Purchase.PurchaseValue" />
</div>
<div class="col-md-6">
<label class="form-label">@T["Purchased At"]</label>
<input type="text" class="form-control" name="Description.Purchase.PurchasedAt" />
</div>
<div class="col-md-6">
<label class="form-label">@T["Purchased By"]</label>
<input type="text" class="form-control" name="Description.Purchase.PurchasedBy" />
</div>
</div>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">@T["Cancel"]</button>
<button type="submit" class="btn btn-primary">@T["Create"]</button>
</div>
</form>
</div>
</div>
</div>
<script>
document.addEventListener('DOMContentLoaded', () => { document.addEventListener('DOMContentLoaded', () => {
alert("DEBUG@1"); const createForm = document.getElementById('createAssetForm');
const deleteModal = document.getElementById('deleteModal'); const createModalEl = document.getElementById('createAssetModal');
deleteModal.addEventListener('show.bs.modal', event => {
alert("DEBUG@2");
const button = event.relatedTarget; // Button that triggered the modal
const assetId = button.getAttribute('data-asset-id');
const assetName = button.getAttribute('data-asset-name');
// Update modal content createForm.addEventListener('submit', async (e) => {
deleteModal.querySelector('#assetId').textContent = assetId; e.preventDefault();
deleteModal.querySelector('#assetName').textContent = assetName;
// Set the form action dynamically const formData = new FormData(createForm);
const form = deleteModal.querySelector('#deleteForm'); const jsonData = {};
form.action = `/Assets/Delete/${assetId}`; // <-- adjust controller/action as needed
// Convert form data into nested JSON for AssetsCreateRequestModel
for (const [key, value] of formData.entries()) {
if (!value) continue;
const keys = key.split('.');
let target = jsonData;
for (let i = 0; i < keys.length - 1; i++) {
target[keys[i]] = target[keys[i]] || {};
target = target[keys[i]];
}
target[keys[keys.length - 1]] = value;
}
try {
const response = await fetch('/Assets/Create', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Accept': 'application/json'
},
body: JSON.stringify(jsonData)
});
const result = await response.json();
if (result.success) {
const modal = bootstrap.Modal.getInstance(createModalEl);
modal.hide();
createForm.reset();
showToast('✅ Asset created successfully', 'success');
// Optional: reload page or dynamically add new row
// location.reload();
} else {
showToast(`❌ ${result.reason || 'Error creating asset'}`, 'danger');
}
} catch (error) {
console.error(error);
showToast('Error contacting server', 'danger');
}
}); });
}); });
</script> *@ </script>
<!-- <script>
/* Handle the attributes list for asset creation*/
document.addEventListener('DOMContentLoaded', () => {
const attributesContainer = document.getElementById('attributesContainer');
const addAttributeBtn = document.getElementById('addAttributeBtn');
In case someone wants to reread this: // Add a new attribute row
addAttributeBtn.addEventListener('click', () => {
const row = document.createElement('div');
row.className = 'd-flex gap-2 align-items-center attribute-row';
row.innerHTML = `
<input type="text" class="form-control" placeholder="Attribute name" data-attr-name />
<input type="text" class="form-control" placeholder="Attribute value" data-attr-value />
<button type="button" class="btn btn-outline-danger btn-sm btn-remove-attribute">✖</button>
`;
attributesContainer.appendChild(row);
});
<div class="text-center"> // Delegate delete button click
<h1 class="display-4">Crashcourse zu "MVC"</h1> attributesContainer.addEventListener('click', (e) => {
<p>Die HTML, die grad gerendert wird, liegt in /Views/Home/ - dort sollte auch das Login später rein (bspw. als Login.cshtml)</p> if (e.target.classList.contains('btn-remove-attribute')) {
<p>Die Web-App folgt der MVC Struktur. D.h. man hat ein Model, ein View und einen Controller. (Wobei dieses View hier hat kein Model. Ist also mehr oder weniger optional.)</p> e.target.closest('.attribute-row').remove();
<h2>McDonalds Vergleich:</h2> }
<p>Der Controller ist der Kassierer; der nimmt die Informationen entgegen, verarbeitet diese und setzt eine Bestellnotiz zusammen.</p> });
<p>Das Model ist die Bestellnotiz, die zur Küche weitergegeben wird mit den Informationen, wie es zubereitet werden muss.</p>
<p>Der View ist der Angestellte, der die Burger zusammensetzt; er kocht nichts und macht nichts groß kompliziertes, sondern tut nur die Teile in den Burger und gibt das Ergebnis zum Kassierer (d.h. Controller).</p> // 🔧 Hook into the existing submit handler to include attributes
<h2>Normale Erklärung:</h2> const createForm = document.getElementById('createAssetForm');
<p>Ein Controller ist zwingend erforderlich und stellt den Endpoint unter einer Route bereit (d.h. das, was man im Browser eingibt, bspw. "/Home/Index")</p> const originalHandler = createForm.onsubmit;
<p>Im Controller von diesem View ("/Controllers/HomeControllers.cs") findet man ein simples "return View();", was dafür sorgt, dass einfach diese .cshtml returned wird.</p> createForm.addEventListener('submit', (e) => {
<p>("Warum diese .cshtml?" - Bestimmt er selber anhand der Controller Route. Die Controller Route setzt er zusammen aus dem Namen der Klasse (wenn oberhalb der Klasse "[Route("[controller]")]" steht) und dem, was man vor eine Methode schreibt, bspw. "[HttpGet("Index")]" -> "/Home/Index" und "[HttpGet("/")]" -> "/".)</p> // Lets patch the JSON generation just before sending
<p>Wenn man Informationen vom Controller an das View übergeben will, tut man das mit einem Model; bspw. "return View(new AssetViewModel { cn = assetName, id = assetId });" </p> const formData = new FormData(createForm);
<p>Ein Model ist ganz einfach eine Klasse, wie man sie kennt.</p> const jsonData = {};
<p>Logik kommt in den Controller und das HTML kommt in das View (... in der Praxis kommt ins View öfters mal auch bissl mehr Logik rein. Lässt sich nicht verhindern.)</p>
</div> for (const [key, value] of formData.entries()) {
--> if (!value) continue;
const keys = key.split('.');
let target = jsonData;
for (let i = 0; i < keys.length - 1; i++) {
target[keys[i]] = target[keys[i]] || {};
target = target[keys[i]];
}
target[keys[keys.length - 1]] = value;
}
// Add Attributes as Dictionary<string,string>
const attributes = {};
document.querySelectorAll('#attributesContainer .attribute-row').forEach(row => {
const name = row.querySelector('[data-attr-name]').value.trim();
const value = row.querySelector('[data-attr-value]').value.trim();
if (name) attributes[name] = value;
});
if (Object.keys(attributes).length > 0) {
jsonData.Description = jsonData.Description || {};
jsonData.Description.Attributes = attributes;
}
// Replace the body before sending
e.preventDefault(); // stop default form submit
fetch('/Assets/Create', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Accept': 'application/json'
},
body: JSON.stringify(jsonData)
})
.then(res => res.json())
.then(result => {
const createModalEl = document.getElementById('createAssetModal');
if (result.success) {
bootstrap.Modal.getInstance(createModalEl).hide();
createForm.reset();
attributesContainer.innerHTML = '';
showToast('✅ Asset created successfully', 'success');
} else {
showToast(`❌ ${result.reason || 'Error creating asset'}`, 'danger');
}
})
.catch(err => {
console.error(err);
showToast('Error contacting server', 'danger');
});
});
});
</script>