Files
2026-08-12 21:29:55 +03:00

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>