trf_certest/public/userarea/quotations.php
2026-04-29 12:16:44 +02:00

887 lines
32 KiB
PHP

<?php
// Debug
ini_set('display_errors', 1);
ini_set('display_startup_errors', 1);
error_reporting(E_ALL);
ini_set('log_errors', 1);
ini_set('error_log', __DIR__ . '/quotations_debug.log');
if (!file_exists(__DIR__ . '/quotations_debug.log')) {
file_put_contents(__DIR__ . '/quotations_debug.log', "Inizio operazioni alle " . date('Y-m-d H:i:s') . "\n", FILE_APPEND);
}
error_log("Inizio operazioni alle " . date('Y-m-d H:i:s'));
include('include/headscript.php');
$db = DBHandlerSelect::getInstance();
$pdo = $db->getConnection();
$user_id = $iduserlogin ?? 1;
/**
* Helper redirect
*/
function redirectWithMessage($status, $message, $extraQuery = '')
{
$url = 'quotations.php?status=' . urlencode($status) . '&message=' . urlencode($message);
if ($extraQuery !== '') {
$url .= '&' . ltrim($extraQuery, '&');
}
header("Location: " . $url);
exit;
}
/**
* CREATE quotation
* Ora description e customer vengono salvati subito dal modale.
*/
if ($_SERVER['REQUEST_METHOD'] === 'POST' && ($_POST['action'] ?? '') === 'create') {
$description = trim($_POST['description'] ?? '');
$customer = trim($_POST['customer'] ?? '');
if ($description === '' || $customer === '') {
redirectWithMessage('error', 'Descrizione e Cliente sono obbligatori');
}
try {
$stmt = $pdo->prepare("
INSERT INTO quotations
(description, customer, iduser)
VALUES
(?, ?, ?)
");
$success = $stmt->execute([
$description,
$customer,
$user_id
]);
if ($success) {
$newId = $pdo->lastInsertId();
error_log("Creata nuova quotation ID: " . $newId);
redirectWithMessage('success', 'Quotation creata con successo');
}
error_log("Errore: impossibile creare la quotation");
redirectWithMessage('error', 'Errore durante la creazione della quotation');
} catch (PDOException $e) {
error_log("Errore PDO durante la creazione della quotation: " . $e->getMessage());
redirectWithMessage('error', 'Errore database: ' . $e->getMessage());
}
}
/**
* UPDATE quotation
* Gestisce sia form normale sia salvataggio inline AJAX.
*/
if ($_SERVER['REQUEST_METHOD'] === 'POST' && ($_POST['action'] ?? '') === 'update' && isset($_POST['id'])) {
$id = intval($_POST['id']);
$description = trim($_POST['description'] ?? '');
$customer = trim($_POST['customer'] ?? '');
$isAjax = isset($_POST['ajax']) && $_POST['ajax'] === '1';
if ($description === '' || $customer === '') {
if ($isAjax) {
header('Content-Type: application/json');
echo json_encode([
'success' => false,
'message' => 'Descrizione e Cliente sono obbligatori'
]);
exit;
}
redirectWithMessage('error', 'Descrizione e Cliente sono obbligatori');
}
try {
$stmt = $pdo->prepare("
UPDATE quotations
SET description = ?, customer = ?
WHERE id = ? AND iduser = ?
");
$stmt->execute([
$description,
$customer,
$id,
$user_id
]);
error_log("Modificata quotation ID: " . $id);
if ($isAjax) {
header('Content-Type: application/json');
echo json_encode([
'success' => true,
'message' => 'Quotation modificata con successo'
]);
exit;
}
redirectWithMessage('success', 'Quotation modificata con successo');
} catch (PDOException $e) {
error_log("Errore PDO durante la modifica della quotation: " . $e->getMessage());
if ($isAjax) {
header('Content-Type: application/json');
echo json_encode([
'success' => false,
'message' => 'Errore database: ' . $e->getMessage()
]);
exit;
}
redirectWithMessage('error', 'Errore database: ' . $e->getMessage());
}
}
/**
* DELETE quotation
*/
if ($_SERVER['REQUEST_METHOD'] === 'POST' && ($_POST['action'] ?? '') === 'delete' && isset($_POST['id'])) {
$id = intval($_POST['id']);
try {
$stmt = $pdo->prepare("
DELETE FROM quotations
WHERE id = ? AND iduser = ?
");
$stmt->execute([
$id,
$user_id
]);
error_log("Cancellata quotation ID: " . $id);
redirectWithMessage('success', 'Quotation cancellata con successo');
} catch (PDOException $e) {
error_log("Errore PDO durante la cancellazione della quotation: " . $e->getMessage());
redirectWithMessage('error', 'Errore database: ' . $e->getMessage());
}
}
/**
* Recupera tutte le quotations dell'utente.
* Ultima creata in alto.
*/
try {
$stmt = $pdo->prepare("
SELECT *
FROM quotations
WHERE iduser = ?
ORDER BY creation_date DESC, id DESC
");
$stmt->execute([
$user_id
]);
$quotations = $stmt->fetchAll(PDO::FETCH_ASSOC);
} catch (PDOException $e) {
error_log("Errore PDO durante il recupero delle quotations: " . $e->getMessage());
$quotations = [];
}
/**
* Recupera quotation in modifica dettagliata
*/
$editQuotation = null;
if (isset($_GET['edit_id'])) {
$editId = intval($_GET['edit_id']);
try {
$stmt = $pdo->prepare("
SELECT *
FROM quotations
WHERE id = ? AND iduser = ?
");
$stmt->execute([
$editId,
$user_id
]);
$editQuotation = $stmt->fetch(PDO::FETCH_ASSOC);
if (!$editQuotation) {
error_log("Nessuna quotation trovata per id: " . $editId);
}
} catch (PDOException $e) {
error_log("Errore PDO durante il recupero della quotation per modifica: " . $e->getMessage());
$editQuotation = null;
}
}
?>
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="icon" href="assets/images/favicon-32x32.png" type="image/png" />
<?php include('cssinclude.php'); ?>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.5.1/css/all.min.css">
<link rel="stylesheet" href="https://cdn.datatables.net/1.13.4/css/jquery.dataTables.min.css">
<script src="https://cdnjs.cloudflare.com/ajax/libs/fabric.js/5.3.1/fabric.min.js"></script>
<style>
.cell-changed {
background-color: #fff3b0 !important;
transition: background-color 0.3s ease;
}
input.manual-input,
select.manual-input,
textarea.manual-input {
background-color: #fff3cd;
}
input.required-input,
select.required-input,
textarea.required-input {
background-color: #f8d7da;
}
input,
select,
textarea {
width: 100%;
box-sizing: border-box;
border: 1px solid #ced4da;
border-radius: 4px;
padding: 5px;
font-size: 14px;
}
textarea {
resize: vertical;
}
.action-btn {
padding: 6px 8px;
margin-right: 5px;
border: none;
border-radius: 5px;
cursor: pointer;
width: 35px;
height: 35px;
box-sizing: border-box;
display: inline-flex;
align-items: center;
justify-content: center;
text-decoration: none;
}
.save-btn {
background-color: #28a745;
color: white;
}
.delete-btn {
background-color: #dc3545;
color: white;
}
.photos-btn {
background-color: #007bff;
color: white;
}
.parts-btn {
background-color: #ffc107;
color: #212529;
}
.edit-detail-btn {
background-color: #6c757d;
color: white;
}
.form-group {
margin-bottom: 15px;
}
.form-group label {
display: block;
margin-bottom: 5px;
font-weight: bold;
}
.form-group textarea {
min-height: 100px;
}
.flash-success {
background-color: #d4edda !important;
transition: background-color 0.3s ease;
}
.quotation-actions {
margin-top: 20px;
}
.modal.fade {
z-index: 1060 !important;
}
.modal-backdrop {
z-index: 1055 !important;
}
.overlay.toggle-icon {
z-index: 1000 !important;
}
.alert {
margin-bottom: 15px;
padding: 10px;
border-radius: 4px;
}
.alert-success {
background-color: #d4edda;
color: #155724;
border: 1px solid #c3e6cb;
}
.alert-danger {
background-color: #f8d7da;
color: #721c24;
border: 1px solid #f5c6cb;
}
#quotationsTable textarea {
min-height: 60px;
}
.modal-body textarea {
min-height: 120px;
}
/* =========================================================
FIX PHOTOS MODAL
The photos modal used by photos.js is not a standard Bootstrap modal.
Without this scoped CSS it can appear full screen.
========================================================= */
#photosModal,
#photoModal,
#imageModal,
#annotationsModal {
display: none;
position: fixed;
z-index: 1070 !important;
left: 0;
top: 0;
width: 100%;
height: 100%;
overflow: auto;
background-color: rgba(0, 0, 0, 0.5);
}
#photosModal .modal-content,
#photoModal .modal-content,
#imageModal .modal-content,
#annotationsModal .modal-content {
background-color: #ffffff;
margin: 5% auto;
padding: 20px;
border: 1px solid #888;
width: 90%;
max-width: 900px;
border-radius: 8px;
position: relative;
}
/* If the modal contains images/canvas, avoid overflow */
#photosModal img,
#photoModal img,
#imageModal img,
#annotationsModal img,
#photosModal canvas,
#photoModal canvas,
#imageModal canvas,
#annotationsModal canvas {
max-width: 100%;
height: auto;
}
/* Close button used by custom photo modal */
#photosModal .close-btn,
#photoModal .close-btn,
#imageModal .close-btn,
#annotationsModal .close-btn {
color: #aaa;
float: right;
font-size: 28px;
font-weight: bold;
cursor: pointer;
}
#photosModal .close-btn:hover,
#photoModal .close-btn:hover,
#imageModal .close-btn:hover,
#annotationsModal .close-btn:hover {
color: #000;
}
</style>
<title>Gestione Quotations - <?= htmlspecialchars($titlewebsite, ENT_QUOTES, 'UTF-8'); ?></title>
</head>
<body>
<div class="wrapper">
<?php include('include/navbar.php'); ?>
<?php include('include/topbar.php'); ?>
<div class="page-wrapper">
<div class="page-content">
<div class="card radius-10">
<div class="card-header">
<div class="d-flex align-items-center justify-content-between">
<div>
<h6 class="mb-0">Gestione Quotations</h6>
</div>
<?php if (!$editQuotation): ?>
<button type="button" class="btn btn-primary" data-bs-toggle="modal" data-bs-target="#createModal">
<i class="fas fa-plus me-1"></i> Crea Nuova Quotation
</button>
<?php endif; ?>
</div>
</div>
<div class="card-body">
<?php if (isset($_GET['status']) && isset($_GET['message'])): ?>
<div class="alert alert-<?= $_GET['status'] === 'success' ? 'success' : 'danger' ?> temp-alert">
<?= htmlspecialchars(urldecode($_GET['message']), ENT_QUOTES, 'UTF-8') ?>
</div>
<?php endif; ?>
<?php if ($editQuotation): ?>
<h6 class="mb-3">
Modifica Quotation ID: <?= htmlspecialchars($editQuotation['id'], ENT_QUOTES, 'UTF-8') ?>
</h6>
<form id="editForm" method="post" action="quotations.php">
<input type="hidden" name="action" value="update">
<input type="hidden" name="id" value="<?= htmlspecialchars($editQuotation['id'], ENT_QUOTES, 'UTF-8') ?>">
<div class="form-group">
<label for="description">Descrizione</label>
<textarea
id="description"
name="description"
class="manual-input required-input"
required><?= htmlspecialchars($editQuotation['description'] ?? '', ENT_QUOTES, 'UTF-8') ?></textarea>
</div>
<div class="form-group">
<label for="customer">Cliente</label>
<input
type="text"
id="customer"
name="customer"
class="manual-input required-input"
value="<?= htmlspecialchars($editQuotation['customer'] ?? '', ENT_QUOTES, 'UTF-8') ?>"
required>
</div>
<button type="submit" class="btn btn-primary">
Salva Modifiche
</button>
<a href="quotations.php" class="btn btn-secondary">
Torna alla Lista
</a>
</form>
<div class="quotation-actions">
<h6 class="mb-3">Azioni</h6>
<button
type="button"
class="photos-btn action-btn"
data-entity-type="quotation"
data-row="0"
data-idquotations="<?= htmlspecialchars($editQuotation['id'], ENT_QUOTES, 'UTF-8') ?>"
title="Photos">
<i class="fas fa-camera"></i>
</button>
<button
type="button"
class="parts-btn action-btn"
data-entity-type="quotation"
data-iddatadb=""
data-idquotations="<?= htmlspecialchars($editQuotation['id'], ENT_QUOTES, 'UTF-8') ?>"
data-row="0"
title="Parts">
<i class="fas fa-puzzle-piece"></i>
</button>
</div>
<?php else: ?>
<h6 class="mb-3">Quotations Esistenti</h6>
<table id="quotationsTable" class="table table-striped table-bordered">
<thead>
<tr>
<th>ID</th>
<th>Data Creazione</th>
<th>Descrizione</th>
<th>Cliente</th>
<th>Azioni</th>
</tr>
</thead>
<tbody>
<?php foreach ($quotations as $index => $row): ?>
<tr data-id="<?= htmlspecialchars($row['id'], ENT_QUOTES, 'UTF-8') ?>">
<td><?= htmlspecialchars($row['id'], ENT_QUOTES, 'UTF-8') ?></td>
<td><?= htmlspecialchars($row['creation_date'] ?? '', ENT_QUOTES, 'UTF-8') ?></td>
<td>
<textarea
name="description"
class="cell-input manual-input form-control"
required><?= htmlspecialchars($row['description'] ?? '', ENT_QUOTES, 'UTF-8') ?></textarea>
</td>
<td>
<input
type="text"
name="customer"
class="cell-input manual-input form-control"
value="<?= htmlspecialchars($row['customer'] ?? '', ENT_QUOTES, 'UTF-8') ?>"
required>
</td>
<td>
<button
type="button"
class="save-btn action-btn edit-btn"
data-id="<?= htmlspecialchars($row['id'], ENT_QUOTES, 'UTF-8') ?>"
title="Salva Modifiche">
<i class="fas fa-save"></i>
</button>
<button
type="button"
class="delete-btn action-btn"
data-id="<?= htmlspecialchars($row['id'], ENT_QUOTES, 'UTF-8') ?>"
title="Cancella"
data-bs-toggle="modal"
data-bs-target="#deleteModal">
<i class="fas fa-trash"></i>
</button>
<button
type="button"
class="photos-btn action-btn"
data-entity-type="quotation"
data-idquotations="<?= htmlspecialchars($row['id'], ENT_QUOTES, 'UTF-8') ?>"
data-row="<?= htmlspecialchars($index, ENT_QUOTES, 'UTF-8') ?>"
title="Photos">
<i class="fas fa-camera"></i>
</button>
<button
type="button"
class="parts-btn action-btn"
data-entity-type="quotation"
data-idquotations="<?= htmlspecialchars($row['id'], ENT_QUOTES, 'UTF-8') ?>"
data-row="<?= htmlspecialchars($index, ENT_QUOTES, 'UTF-8') ?>"
title="Parts">
<i class="fas fa-puzzle-piece"></i>
</button>
<a
href="quotations.php?edit_id=<?= urlencode($row['id']) ?>"
class="edit-detail-btn action-btn"
title="Modifica Dettagliata">
<i class="fas fa-edit"></i>
</a>
</td>
</tr>
<?php endforeach; ?>
</tbody>
</table>
<?php endif; ?>
</div>
</div>
</div>
</div>
<!-- Modal creazione nuova quotation -->
<div class="modal fade" id="createModal" tabindex="-1" aria-labelledby="createModalLabel" aria-hidden="true">
<div class="modal-dialog modal-lg">
<div class="modal-content">
<form id="createModalForm" method="post" action="quotations.php">
<input type="hidden" name="action" value="create">
<div class="modal-header">
<h5 class="modal-title" id="createModalLabel">
Crea Nuova Quotation
</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
<div class="modal-body">
<div class="form-group">
<label for="create_description">Descrizione</label>
<textarea
id="create_description"
name="description"
class="manual-input required-input"
required
placeholder="Inserisci la descrizione della quotation"></textarea>
</div>
<div class="form-group">
<label for="create_customer">Cliente</label>
<input
type="text"
id="create_customer"
name="customer"
class="manual-input required-input"
required
placeholder="Inserisci il nome del cliente">
</div>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">
Annulla
</button>
<button type="submit" class="btn btn-primary">
Crea Quotation
</button>
</div>
</form>
</div>
</div>
</div>
<!-- Modal cancellazione quotation -->
<div class="modal fade" id="deleteModal" tabindex="-1" aria-labelledby="deleteModalLabel" aria-hidden="true">
<div class="modal-dialog">
<div class="modal-content">
<form id="deleteForm" method="post" action="quotations.php">
<input type="hidden" name="action" value="delete">
<input type="hidden" name="id" id="deleteQuotationId">
<div class="modal-header">
<h5 class="modal-title" id="deleteModalLabel">
Conferma Cancellazione
</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
<div class="modal-body">
<p>Sicuro di voler cancellare questa quotation?</p>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">
Annulla
</button>
<button type="submit" class="btn btn-danger">
Conferma
</button>
</div>
</form>
</div>
</div>
</div>
<div class="overlay toggle-icon"></div>
<a href="javaScript:;" class="back-to-top">
<i class='bx bxs-up-arrow-alt'></i>
</a>
<?php include('include/footer.php'); ?>
</div>
<div id="partsModalContainer"></div>
<div id="annotationsModalContainer"></div>
<?php include 'photos_functions.php'; ?>
<?php include('jsinclude.php'); ?>
<script src="https://code.jquery.com/jquery-3.6.0.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js"></script>
<script src="https://cdn.datatables.net/1.13.4/js/jquery.dataTables.min.js"></script>
<script src="photos.js"></script>
<script src="annotationsModal.js"></script>
<script src="partsTable.js"></script>
<script>
document.addEventListener("DOMContentLoaded", function() {
// Rimuove automaticamente gli alert dopo 5 secondi
setTimeout(function() {
document.querySelectorAll('.temp-alert').forEach(function(el) {
el.remove();
});
}, 5000);
// Inizializza DataTables solo nella lista
if (!document.querySelector('#editForm') && document.querySelector('#quotationsTable')) {
$('#quotationsTable').DataTable({
paging: true,
searching: true,
ordering: true,
info: true,
autoWidth: false,
responsive: true,
order: [
[1, 'desc'],
[0, 'desc']
]
});
}
// Nasconde overlay template quando si apre il modal create
$('#createModal').on('show.bs.modal', function() {
$('.overlay.toggle-icon').css('display', 'none');
});
$('#createModal').on('hide.bs.modal', function() {
$('.overlay.toggle-icon').css('display', '');
});
// Apertura modal Parts
$(document).on('click', '.parts-btn', function() {
const idquotations = $(this).data('idquotations');
if (!idquotations) {
alert('ID quotation mancante.');
return;
}
$.ajax({
url: 'modal_partsTable.php',
method: 'GET',
data: {
idquotations: idquotations
},
success: function(response) {
$('#partsModalContainer').html(response);
const modalElement = document.getElementById('partsModal');
if (!modalElement) {
alert('Modale parts non trovato.');
return;
}
$("#trfHeader").text(`Quotation #${idquotations}`);
$("#partsModal").data("idquotations", idquotations);
let modal = bootstrap.Modal.getInstance(modalElement) || new bootstrap.Modal(modalElement, {
backdrop: true
});
modal.show();
if (typeof window.loadParts === 'function') {
window.loadParts(null, idquotations);
}
},
error: function(xhr, status, error) {
alert('Errore nel caricamento del modale: ' + error);
}
});
});
// Pulizia modal dinamici
$(document).on('hidden.bs.modal', '#partsModal', function() {
$('#partsModalContainer').empty();
$('.modal-backdrop').remove();
$('body').removeClass('modal-open').css('padding-right', '');
});
$(document).on('hidden.bs.modal', '#annotationsModal', function() {
$('#annotationsModalContainer').empty();
$('.modal-backdrop').remove();
$('body').removeClass('modal-open').css('padding-right', '');
});
// Salvataggio inline
$(document).on('click', '.edit-btn', function() {
const row = this.closest('tr');
const id = row.dataset.id;
const description = row.querySelector('textarea[name="description"]').value.trim();
const customer = row.querySelector('input[name="customer"]').value.trim();
if (!description || !customer) {
alert('Descrizione e Cliente sono obbligatori.');
return;
}
const formData = new FormData();
formData.append('action', 'update');
formData.append('ajax', '1');
formData.append('id', id);
formData.append('description', description);
formData.append('customer', customer);
fetch('quotations.php', {
method: 'POST',
body: formData
})
.then(response => response.json())
.then(data => {
if (data.success) {
row.classList.add('flash-success');
setTimeout(() => row.classList.remove('flash-success'), 700);
} else {
alert(data.message || 'Errore durante la modifica.');
}
})
.catch(error => {
console.error('Errore durante la modifica della quotation:', error);
alert('Errore durante la modifica: ' + error.message);
});
});
// Imposta ID da cancellare nel modal
$(document).on('click', '.delete-btn', function() {
const id = this.dataset.id;
document.getElementById('deleteQuotationId').value = id;
});
});
</script>
</body>
</html>