311 lines
14 KiB
PHP
311 lines
14 KiB
PHP
<?php
|
|
|
|
/**
|
|
* Register/edit one intervention.
|
|
* Expects $formData and $pdo in scope. Defines mntOpenInterventionModal(data, maintenance).
|
|
*
|
|
* The signature is drawn on a canvas (finger on tablet, mouse on PC) and
|
|
* posted as a PNG data URL.
|
|
*/
|
|
|
|
$statusOptions = mnt_lookup($pdo, 'maint_status', [
|
|
'planned' => 'Pianificato',
|
|
'in_progress' => 'In corso',
|
|
'completed' => 'Completato',
|
|
]);
|
|
|
|
$resultOptions = mnt_lookup($pdo, 'maint_result', [
|
|
'compliant' => 'Conforme',
|
|
'not_compliant' => 'Non conforme',
|
|
'blocked' => 'Bloccato',
|
|
]);
|
|
?>
|
|
<div class="modal fade" id="interventionModal" tabindex="-1" aria-hidden="true">
|
|
<div class="modal-dialog modal-lg modal-dialog-centered modal-fullscreen-sm-down">
|
|
<div class="modal-content">
|
|
<div class="modal-header">
|
|
<h5 class="modal-title" id="interventionModalTitle">Registra intervento</h5>
|
|
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Chiudi"></button>
|
|
</div>
|
|
<form id="interventionForm">
|
|
<div class="modal-body">
|
|
<input type="hidden" name="id" id="ivId" value="">
|
|
<input type="hidden" name="maintenance_id" id="ivMaintenanceId" value="">
|
|
<input type="hidden" name="signature" id="ivSignature" value="">
|
|
<input type="hidden" name="clear_signature" id="ivClearSignature" value="">
|
|
|
|
<div class="alert alert-light border small mb-3" id="ivContext"></div>
|
|
|
|
<div class="row g-3">
|
|
<div class="col-12 col-md-4">
|
|
<label class="form-label fw-semibold">Data <span class="text-danger">*</span></label>
|
|
<input type="date" class="form-control" name="performed_at" id="ivDate" required>
|
|
</div>
|
|
|
|
<div class="col-6 col-md-4">
|
|
<label class="form-label fw-semibold">Stato</label>
|
|
<select class="form-select" name="status" id="ivStatus">
|
|
<?php foreach ($statusOptions as $value => $label): ?>
|
|
<option value="<?= mnt_h($value) ?>" <?= $value === 'completed' ? 'selected' : '' ?>>
|
|
<?= mnt_h($label) ?>
|
|
</option>
|
|
<?php endforeach; ?>
|
|
</select>
|
|
</div>
|
|
|
|
<div class="col-6 col-md-4">
|
|
<label class="form-label fw-semibold">Esito</label>
|
|
<select class="form-select" name="result" id="ivResult">
|
|
<option value="">—</option>
|
|
<?php foreach ($resultOptions as $value => $label): ?>
|
|
<option value="<?= mnt_h($value) ?>"><?= mnt_h($label) ?></option>
|
|
<?php endforeach; ?>
|
|
</select>
|
|
</div>
|
|
|
|
<div class="col-12 col-md-6">
|
|
<label class="form-label fw-semibold">Operatore</label>
|
|
<select class="form-select" name="operator_employee_id" id="ivOperator">
|
|
<option value="">—</option>
|
|
<?php foreach ($formData['employees'] as $employee): ?>
|
|
<option value="<?= (int)$employee['id'] ?>"><?= mnt_h($employee['full_name']) ?></option>
|
|
<?php endforeach; ?>
|
|
</select>
|
|
</div>
|
|
|
|
<div class="col-12 col-md-6">
|
|
<label class="form-label fw-semibold">Operatore esterno</label>
|
|
<input type="text" class="form-control" name="operator_name" id="ivOperatorName" maxlength="191"
|
|
placeholder="Nome tecnico non presente in anagrafica">
|
|
</div>
|
|
|
|
<div class="col-12">
|
|
<label class="form-label fw-semibold">Fornitore</label>
|
|
<select class="form-select" name="supplier_id" id="ivSupplier">
|
|
<option value="">—</option>
|
|
<?php foreach ($formData['suppliers'] as $supplier): ?>
|
|
<option value="<?= (int)$supplier['id'] ?>"><?= mnt_h($supplier['name']) ?></option>
|
|
<?php endforeach; ?>
|
|
</select>
|
|
</div>
|
|
|
|
<div class="col-12">
|
|
<label class="form-label fw-semibold">Note sull'intervento</label>
|
|
<textarea class="form-control" name="notes" id="ivNotes" rows="3"></textarea>
|
|
</div>
|
|
|
|
<div class="col-12">
|
|
<label class="form-label fw-semibold">Materiali / parti sostituite</label>
|
|
<textarea class="form-control" name="materials" id="ivMaterials" rows="2"></textarea>
|
|
</div>
|
|
|
|
<div class="col-12">
|
|
<label class="form-label fw-semibold">Firma operatore</label>
|
|
<div class="mnt-signature-wrap">
|
|
<canvas id="ivSignatureCanvas"></canvas>
|
|
</div>
|
|
<div class="d-flex justify-content-between align-items-center mt-1">
|
|
<span class="form-text mb-0">Firma con il dito su tablet o con il mouse.</span>
|
|
<button type="button" class="btn btn-sm btn-light" id="ivSignatureClear">Cancella firma</button>
|
|
</div>
|
|
<div id="ivExistingSignature" class="mt-2" style="display:none">
|
|
<span class="mnt-section-title">Firma registrata</span>
|
|
<img id="ivExistingSignatureImg" alt="Firma" style="max-height:90px;border:1px solid #e6eef0;border-radius:.4rem;background:#fff">
|
|
</div>
|
|
</div>
|
|
|
|
<div class="col-12">
|
|
<label class="form-label fw-semibold">Allegati (modulo del fornitore, foto)</label>
|
|
<div id="ivFilesList" class="mb-2"></div>
|
|
<input type="file" class="form-control" id="ivFilesInput" multiple>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div class="modal-footer">
|
|
<button type="button" class="btn btn-light" data-bs-dismiss="modal">Annulla</button>
|
|
<button type="submit" class="btn btn-mnt-primary">Salva intervento</button>
|
|
</div>
|
|
</form>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<script>
|
|
(function () {
|
|
let canvas, ctx, drawing = false, hasStrokes = false;
|
|
|
|
function resizeCanvas() {
|
|
if (!canvas) return;
|
|
const ratio = window.devicePixelRatio || 1;
|
|
const rect = canvas.getBoundingClientRect();
|
|
// Preserve what is already drawn across a resize
|
|
const snapshot = hasStrokes ? canvas.toDataURL() : null;
|
|
|
|
canvas.width = rect.width * ratio;
|
|
canvas.height = rect.height * ratio;
|
|
ctx.setTransform(ratio, 0, 0, ratio, 0, 0);
|
|
ctx.lineWidth = 2;
|
|
ctx.lineCap = 'round';
|
|
ctx.strokeStyle = '#1e3a44';
|
|
|
|
if (snapshot) {
|
|
const img = new Image();
|
|
img.onload = () => ctx.drawImage(img, 0, 0, rect.width, rect.height);
|
|
img.src = snapshot;
|
|
}
|
|
}
|
|
|
|
function pointerPos(event) {
|
|
const rect = canvas.getBoundingClientRect();
|
|
const source = event.touches ? event.touches[0] : event;
|
|
return { x: source.clientX - rect.left, y: source.clientY - rect.top };
|
|
}
|
|
|
|
function startDraw(event) {
|
|
event.preventDefault();
|
|
drawing = true;
|
|
hasStrokes = true;
|
|
const p = pointerPos(event);
|
|
ctx.beginPath();
|
|
ctx.moveTo(p.x, p.y);
|
|
}
|
|
|
|
function moveDraw(event) {
|
|
if (!drawing) return;
|
|
event.preventDefault();
|
|
const p = pointerPos(event);
|
|
ctx.lineTo(p.x, p.y);
|
|
ctx.stroke();
|
|
}
|
|
|
|
function endDraw() { drawing = false; }
|
|
|
|
function clearCanvas() {
|
|
if (!ctx) return;
|
|
const rect = canvas.getBoundingClientRect();
|
|
ctx.clearRect(0, 0, rect.width, rect.height);
|
|
hasStrokes = false;
|
|
}
|
|
|
|
window.mntOpenInterventionModal = function (data, maintenance) {
|
|
const isEdit = !!data;
|
|
|
|
$('#interventionModalTitle').text(isEdit ? 'Modifica intervento' : 'Registra intervento');
|
|
$('#ivContext').html(
|
|
'<strong>' + $('<div>').text(maintenance.equipment_name || '').html() + '</strong> — ' +
|
|
$('<div>').text(maintenance.title || '').html()
|
|
);
|
|
|
|
$('#ivId').val(isEdit ? data.id : '');
|
|
$('#ivMaintenanceId').val(maintenance.id);
|
|
$('#ivDate').val(isEdit ? data.performed_at : new Date().toISOString().slice(0, 10));
|
|
$('#ivStatus').val(isEdit ? data.status : 'completed');
|
|
$('#ivResult').val(isEdit ? (data.result || '') : '');
|
|
$('#ivOperator').val(isEdit && data.operator_employee_id ? String(data.operator_employee_id) : '');
|
|
$('#ivOperatorName').val(isEdit ? (data.operator_name || '') : '');
|
|
$('#ivSupplier').val(isEdit && data.supplier_id ? String(data.supplier_id) : (maintenance.supplier_id || ''));
|
|
$('#ivNotes').val(isEdit ? (data.notes || '') : '');
|
|
$('#ivMaterials').val(isEdit ? (data.materials || '') : '');
|
|
$('#ivSignature').val('');
|
|
$('#ivClearSignature').val('');
|
|
$('#ivFilesInput').val('');
|
|
$('#ivFilesList').empty();
|
|
|
|
if (isEdit && data.signature_path) {
|
|
$('#ivExistingSignatureImg').attr('src', 'manutenzioni/ajax/download_file.php?scope=signature&id=' + data.id);
|
|
$('#ivExistingSignature').show();
|
|
} else {
|
|
$('#ivExistingSignature').hide();
|
|
}
|
|
|
|
if (isEdit && data.files) {
|
|
data.files.forEach(f => {
|
|
$('#ivFilesList').append(
|
|
$('<div class="mnt-file-row"></div>').append(
|
|
$('<i class="fa-solid fa-paperclip text-muted"></i>'),
|
|
$('<a class="file-name" target="_blank"></a>')
|
|
.attr('href', 'manutenzioni/ajax/download_file.php?scope=intervention&id=' + f.id)
|
|
.text(f.original_name),
|
|
$('<button type="button" class="btn-action btn-action-delete btn-del-iv-file"><i class="fa-solid fa-trash"></i></button>')
|
|
.attr('data-id', f.id)
|
|
)
|
|
);
|
|
});
|
|
}
|
|
|
|
const modal = new bootstrap.Modal('#interventionModal');
|
|
modal.show();
|
|
|
|
$('#interventionModal').one('shown.bs.modal', function () {
|
|
clearCanvas();
|
|
resizeCanvas();
|
|
});
|
|
};
|
|
|
|
$(function () {
|
|
canvas = document.getElementById('ivSignatureCanvas');
|
|
if (!canvas) return;
|
|
ctx = canvas.getContext('2d');
|
|
|
|
canvas.addEventListener('mousedown', startDraw);
|
|
canvas.addEventListener('mousemove', moveDraw);
|
|
window.addEventListener('mouseup', endDraw);
|
|
canvas.addEventListener('touchstart', startDraw, { passive: false });
|
|
canvas.addEventListener('touchmove', moveDraw, { passive: false });
|
|
canvas.addEventListener('touchend', endDraw);
|
|
window.addEventListener('resize', resizeCanvas);
|
|
|
|
$('#ivSignatureClear').on('click', function () {
|
|
clearCanvas();
|
|
// Also drop a signature stored earlier
|
|
if ($('#ivExistingSignature').is(':visible')) {
|
|
$('#ivClearSignature').val('1');
|
|
$('#ivExistingSignature').hide();
|
|
}
|
|
});
|
|
|
|
$('#ivFilesList').on('click', '.btn-del-iv-file', function () {
|
|
const $row = $(this).closest('.mnt-file-row');
|
|
$.post('manutenzioni/ajax/delete_file.php', { scope: 'intervention', id: $(this).data('id') })
|
|
.done(res => {
|
|
if (res.success) { $row.remove(); }
|
|
else { Swal.fire({ icon: 'error', title: 'Errore', text: res.message }); }
|
|
});
|
|
});
|
|
|
|
$('#interventionForm').on('submit', function (e) {
|
|
e.preventDefault();
|
|
|
|
if (!$('#ivDate').val()) {
|
|
Swal.fire({ icon: 'warning', title: 'Indica la data dell\'intervento' });
|
|
return;
|
|
}
|
|
|
|
if (hasStrokes) { $('#ivSignature').val(canvas.toDataURL('image/png')); }
|
|
|
|
$.post('manutenzioni/ajax/save_intervention.php', $(this).serialize())
|
|
.done(res => {
|
|
if (!res.success) {
|
|
Swal.fire({ icon: 'error', title: 'Errore', text: res.message });
|
|
return;
|
|
}
|
|
|
|
const input = document.getElementById('ivFilesInput');
|
|
if (!input.files.length) { location.reload(); return; }
|
|
|
|
const form = new FormData();
|
|
form.append('scope', 'intervention');
|
|
form.append('owner_id', res.id);
|
|
Array.from(input.files).forEach(f => form.append('files[]', f));
|
|
|
|
$.ajax({
|
|
url: 'manutenzioni/ajax/upload_file.php',
|
|
method: 'POST', data: form, processData: false, contentType: false, dataType: 'json'
|
|
}).always(() => location.reload());
|
|
})
|
|
.fail(() => Swal.fire({ icon: 'error', title: 'Errore di rete' }));
|
|
});
|
|
});
|
|
})();
|
|
</script>
|