mirror of
https://github.com/LD-Reborn/Berufsschule_HAM.git
synced 2025-12-20 06:51:55 +00:00
Merge pull request #102 from LD-Reborn/100-feature-add-the-ability-to-create-an-asset
Fixed Asset delete wrong HTTP method, Added Asset creation in /Home/A…
This commit is contained in:
@@ -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)
|
||||||
|
|||||||
@@ -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>
|
// Let’s 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>
|
||||||
Reference in New Issue
Block a user