Files
2025-07-31 16:18:32 +10:00

232 lines
9.1 KiB
Plaintext

@model Disco.Web.Models.Job.ShowModel
@{
Authorization.Require(Claims.Job.ShowNonWarrantyComponents);
var hasEdit = Authorization.Has(Claims.Job.Properties.NonWarrantyProperties.EditComponents);
var hasAdd = Authorization.Has(Claims.Job.Properties.NonWarrantyProperties.AddComponents);
if (hasEdit)
{
Html.BundleDeferred("~/ClientScripts/Modules/jQuery-NumberFormatter");
}
}
<table id="jobComponents" data-addurl="@Url.Action(MVC.API.Job.ComponentAdd(Model.Job.Id, null, null))" data-removeurl="@Url.Action(MVC.API.Job.ComponentRemove())" data-updateurl="@Url.Action(MVC.API.Job.ComponentUpdate())">
<tr>
<th>
Description
</th>
<th>
Cost
</th>
@if (hasEdit)
{
<th class="actions">
&nbsp;
</th>
}
</tr>
@if (hasEdit)
{
foreach (var jc in Model.Job.JobComponents)
{
<tr data-jobcomponentid="@jc.Id">
<td>
<input type="text" class="description" value="@jc.Description" />
</td>
<td>
<input type="text" class="cost" value="@jc.Cost.ToString("C")" />
</td>
<td>
<span class="remove fa fa-times-circle"></span>
</td>
</tr>
}
}
else
{
foreach (var jc in Model.Job.JobComponents)
{
<tr data-jobcomponentid="@jc.Id">
<td>
<span class="description">@jc.Description</span>
</td>
<td>
<span class="cost">@jc.Cost.ToString("C")</span>
</td>
</tr>
}
}
<tr>
<td>
@if (hasEdit && hasAdd)
{
<a href="#" id="jobComponentsAdd">Add Component</a>
}
&nbsp;
</td>
<td colspan="@(hasEdit ? 2 : 1)" class="totalCost">
Total: <span id="jobComponentsTotalCost">
@if (!hasEdit)
{
@Model.Job.JobComponentsTotalCost().ToString("C")
}
</span>
</td>
</tr>
</table>
@if (hasEdit)
{
<div id="dialogRemoveComponent" title="Remove this Component?">
<p>
<i class="fa fa-exclamation-triangle fa-lg"></i>&nbsp;Are you sure?
</p>
</div>
<script type="text/javascript">
$(function () {
const $jobComponents = $('#jobComponents');
$jobComponents.on('change', 'input', updateComponent);
$jobComponents.on('focus', 'input', function () { $(this).select() });
$jobComponents.on('click', 'span.remove', removeComponent);
$('#jobComponentsAdd').click(function () {
const jc = $('<tr><td><input type="text" class="description" /></td><td><input type="text" class="cost" /></td><td><span class="remove fa fa-times-circle"></span></td></tr>');
jc.find('input').focus(function () { $(this).select() })
jc.insertBefore($jobComponents.find('tr').last());
jc.find('input.description').focus();
return false;
});
$('#dialogRemoveComponent').dialog({
resizable: false,
height: 140,
modal: true,
autoOpen: false
});
function removeComponent() {
const componentRow = $(this).closest('tr');
const id = componentRow.attr('data-jobcomponentid');
if (id) {
var $dialogRemoveComponent = $('#dialogRemoveComponent');
$dialogRemoveComponent.dialog("enable");
$dialogRemoveComponent.dialog('option', 'buttons', {
"Remove": function () {
$dialogRemoveComponent.dialog("disable");
$dialogRemoveComponent.dialog("option", "buttons", null);
async function removeComponentAsync(id) {
const body = new FormData();
body.append('__RequestVerificationToken', document.body.dataset.antiforgery);
body.append('id', id);
const response = await fetch($jobComponents.attr('data-removeurl'), {
method: 'POST',
body: body
});
if (response.ok) {
componentRow.remove();
updateTotalCost();
} else {
alert('Unable to remove component: ' + response.statusText);
}
$dialogRemoveComponent.dialog("close");
}
removeComponentAsync(id);
},
Cancel: function () {
$dialogRemoveComponent.dialog("close");
}
});
$dialogRemoveComponent.dialog('open');
} else {
// New - Remove
componentRow.remove();
updateTotalCost();
}
}
function updateTotalCost() {
var totalCost = 0;
$jobComponents.find('input.cost').each(function () {
var v = $(this).val();
v = $.parseNumber(v, { format: '#,##0.00', locale: 'au' });
if (!isNaN(v))
totalCost += v;
});
var totalCostFormatted = $.formatNumber(totalCost, { format: '#,##0.00', locale: 'au' });
$('#jobComponentsTotalCost').text('$' + totalCostFormatted);
}
function updateComponent() {
var componentRow = $(this).closest('tr');
componentRow.find('input').attr('disabled', true).addClass('updating');
var id = componentRow.attr('data-jobcomponentid');
if (id) {
// Update
async function updateComponentAsync(id, description, cost) {
const body = new FormData();
body.append('__RequestVerificationToken', document.body.dataset.antiforgery);
body.append('id', id);
body.append('description', description);
body.append('cost', cost);
const response = await fetch($jobComponents.attr('data-updateurl'), {
method: 'POST',
body: body
});
componentRow.find('input').attr('disabled', false).removeClass('updating');
if (response.ok) {
const component = await response.json();
componentRow.find('input.description').val(component.Description);
componentRow.find('input.cost').val(component.Cost);
} else {
alert('Unable to update component: ' + response.statusText);
}
updateTotalCost();
}
updateComponentAsync(id, componentRow.find('input.description').val(), componentRow.find('input.cost').val());
} else {
// Add
async function addComponentAsync(description, cost) {
const body = new FormData();
body.append('__RequestVerificationToken', document.body.dataset.antiforgery);
body.append('description', description);
body.append('cost', cost);
const response = await fetch($jobComponents.attr('data-addurl'), {
method: 'POST',
body: body
});
componentRow.find('input').attr('disabled', false).removeClass('updating');
if (response.ok) {
const component = await response.json();
componentRow.attr('data-jobcomponentid', component.Id);
componentRow.find('input.description').val(component.Description);
componentRow.find('input.cost').val(component.Cost);
} else {
alert('Unable to add component: ' + response.statusText);
}
updateTotalCost();
}
addComponentAsync(componentRow.find('input.description').val(), componentRow.find('input.cost').val())
}
}
updateTotalCost();
});
</script>
}