Compare commits
4 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 9969cc9edc | |||
| 8d6fe92481 | |||
| dbc66723a6 | |||
| 218fc14462 |
File diff suppressed because one or more lines are too long
@@ -1084,7 +1084,7 @@ foreach ($stmt->fetchAll(PDO::FETCH_ASSOC) as $row) {
|
|||||||
clientData.forEach(client => {
|
clientData.forEach(client => {
|
||||||
const nome = client.Nominativo || "Nome non disponibile";
|
const nome = client.Nominativo || "Nome non disponibile";
|
||||||
const id = client.IdCliente || "ID non disponibile";
|
const id = client.IdCliente || "ID non disponibile";
|
||||||
const option = new Option(`${nome.trim()} (ID: ${id})`, id);
|
const option = new Option(`${nome.trim()} - ${client.CodiceNazioneFatturazione} (ID: ${id})`, id);
|
||||||
if (parseInt(id) === parseInt(<?php echo json_encode($default_idclient ?? 0); ?>)) {
|
if (parseInt(id) === parseInt(<?php echo json_encode($default_idclient ?? 0); ?>)) {
|
||||||
option.selected = true;
|
option.selected = true;
|
||||||
}
|
}
|
||||||
@@ -1116,7 +1116,7 @@ foreach ($stmt->fetchAll(PDO::FETCH_ASSOC) as $row) {
|
|||||||
clientData.forEach(client => {
|
clientData.forEach(client => {
|
||||||
const nome = client.Nominativo || "Nome non disponibile";
|
const nome = client.Nominativo || "Nome non disponibile";
|
||||||
const id = client.IdCliente || "ID non disponibile";
|
const id = client.IdCliente || "ID non disponibile";
|
||||||
const option = new Option(`${nome.trim()} (ID: ${id})`, id);
|
const option = new Option(`${nome.trim()} - ${client.CodiceNazioneFatturazione} (ID: ${id})`, id);
|
||||||
if (String(id) === String(currentValue)) {
|
if (String(id) === String(currentValue)) {
|
||||||
option.selected = true;
|
option.selected = true;
|
||||||
}
|
}
|
||||||
@@ -1347,7 +1347,9 @@ foreach ($stmt->fetchAll(PDO::FETCH_ASSOC) as $row) {
|
|||||||
clientData.forEach(client => {
|
clientData.forEach(client => {
|
||||||
const nome = client.Nominativo || "Nome non disponibile";
|
const nome = client.Nominativo || "Nome non disponibile";
|
||||||
const id = client.IdCliente || "ID non disponibile";
|
const id = client.IdCliente || "ID non disponibile";
|
||||||
const option = new Option(`${nome.trim()} (ID: ${id})`, id);
|
const codice = (client.CodiceNazioneFatturazione || '').trim();
|
||||||
|
const option = new Option(`${nome.trim()} - ${codice} (ID: ${id})`, id);
|
||||||
|
|
||||||
if (parseInt(id) === parseInt(<?php echo json_encode($default_idclient ?? 0); ?>)) {
|
if (parseInt(id) === parseInt(<?php echo json_encode($default_idclient ?? 0); ?>)) {
|
||||||
option.selected = true;
|
option.selected = true;
|
||||||
}
|
}
|
||||||
@@ -1377,7 +1379,9 @@ foreach ($stmt->fetchAll(PDO::FETCH_ASSOC) as $row) {
|
|||||||
clientData.forEach(client => {
|
clientData.forEach(client => {
|
||||||
const nome = client.Nominativo || "Nome non disponibile";
|
const nome = client.Nominativo || "Nome non disponibile";
|
||||||
const id = client.IdCliente || "ID non disponibile";
|
const id = client.IdCliente || "ID non disponibile";
|
||||||
const option = new Option(`${nome.trim()} (ID: ${id})`, id);
|
const codice = (client.CodiceNazioneFatturazione || '').trim();
|
||||||
|
const option = new Option(`${nome.trim()} - ${codice} (ID: ${id})`, id);
|
||||||
|
|
||||||
if (String(id) === String(currentValue)) {
|
if (String(id) === String(currentValue)) {
|
||||||
option.selected = true;
|
option.selected = true;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -13,13 +13,13 @@ if (session_status() == PHP_SESSION_NONE) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Imposta variabili di sessione di default per evitare errori
|
// Imposta variabili di sessione di default per evitare errori
|
||||||
$_SESSION['iduserlogin'] = null; // Nessun utente loggato
|
$_SESSION['iduserlogin'] = '1'; // Nessun utente loggato
|
||||||
$_SESSION['nameuser'] = 'Ospite';
|
$_SESSION['nameuser'] = 'Ospite';
|
||||||
$_SESSION['surnameuser'] = '';
|
$_SESSION['surnameuser'] = '';
|
||||||
$_SESSION['emailuser'] = '';
|
$_SESSION['emailuser'] = '';
|
||||||
$_SESSION['photouser'] = '';
|
$_SESSION['photouser'] = '';
|
||||||
$photouser = $_SESSION['photouser'];
|
$photouser = $_SESSION['photouser'];
|
||||||
$photousername = '';
|
$photousername = '';
|
||||||
|
$iduserlogin = $_SESSION['iduserlogin'];
|
||||||
// Include file di lingua, se necessario
|
// Include file di lingua, se necessario
|
||||||
require_once(__DIR__ . '/../../languages/en/general.php');
|
require_once(__DIR__ . '/../../languages/en/general.php');
|
||||||
|
|||||||
@@ -143,172 +143,323 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
|
/* --- Base --- */
|
||||||
#partsModal {
|
#partsModal {
|
||||||
z-index: 1060 !important;
|
z-index: 1060 !important
|
||||||
}
|
}
|
||||||
|
|
||||||
#partsModal .modal-backdrop {
|
#partsModal .modal-backdrop {
|
||||||
z-index: 1055 !important;
|
z-index: 1055 !important
|
||||||
}
|
}
|
||||||
|
|
||||||
#partsModal .modal-content {
|
#partsModal .modal-content {
|
||||||
width: 100% !important;
|
width: 100% !important;
|
||||||
max-width: 100% !important;
|
max-width: 100% !important
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Tabelle */
|
||||||
#partsTable tr {
|
#partsTable tr {
|
||||||
display: table-row !important;
|
display: table-row !important
|
||||||
}
|
}
|
||||||
|
|
||||||
#partsTable tr:hover {
|
#partsTable tr:hover {
|
||||||
background-color: #f5f5f5;
|
background: #f5f5f5
|
||||||
display: table-row !important;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#partsTable td,
|
#partsTable td,
|
||||||
#partsTable th {
|
#partsTable th {
|
||||||
padding: 0.2rem;
|
padding: .2rem;
|
||||||
vertical-align: middle;
|
vertical-align: middle
|
||||||
}
|
}
|
||||||
|
|
||||||
#partsTable input,
|
#partsTable input,
|
||||||
#partsTable select {
|
#partsTable select {
|
||||||
height: 24px;
|
height: 24px;
|
||||||
padding: 0.1rem 0.3rem;
|
padding: .1rem .3rem
|
||||||
}
|
}
|
||||||
|
|
||||||
#partsTable button {
|
#partsTable button {
|
||||||
padding: 0.1rem 0.3rem;
|
padding: .1rem .3rem;
|
||||||
margin: 0 2px;
|
margin: 0 2px
|
||||||
}
|
}
|
||||||
|
|
||||||
#partsTable i {
|
#partsTable i {
|
||||||
font-size: 0.6rem !important;
|
font-size: .6rem !important
|
||||||
}
|
}
|
||||||
|
|
||||||
#global-matrice,
|
/* --- Larghezze fisse header --- */
|
||||||
.part-matrice,
|
/* MacroMatrici = 250px */
|
||||||
#macro-matrice-filter {
|
#macro-matrice-filter {
|
||||||
width: 100% !important;
|
width: 250px !important;
|
||||||
min-width: 100% !important;
|
min-width: 250px !important;
|
||||||
|
max-width: 250px !important;
|
||||||
|
flex: 0 0 250px !important;
|
||||||
|
box-sizing: border-box;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
white-space: nowrap;
|
||||||
}
|
}
|
||||||
|
|
||||||
.select2-container--default #global-matrice {
|
#macro-matrice-filter.select2-hidden-accessible+.select2 {
|
||||||
width: 350px !important;
|
width: 250px !important;
|
||||||
|
min-width: 250px !important;
|
||||||
|
max-width: 250px !important;
|
||||||
|
flex: 0 0 250px !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
#macro-matrice-filter.select2-hidden-accessible+.select2 .select2-selection__rendered {
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Matrice globale = 450px */
|
||||||
|
#global-matrice {
|
||||||
|
width: 450px !important;
|
||||||
|
min-width: 450px !important;
|
||||||
|
max-width: 450px !important;
|
||||||
|
flex: 0 0 450px !important;
|
||||||
|
box-sizing: border-box;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
#global-matrice.select2-hidden-accessible+.select2 {
|
||||||
|
width: 450px !important;
|
||||||
|
min-width: 450px !important;
|
||||||
|
max-width: 450px !important;
|
||||||
|
flex: 0 0 450px !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
#global-matrice.select2-hidden-accessible+.select2 .select2-selection__rendered {
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Select delle righe (colonna Matrice) = 150px */
|
||||||
|
.part-matrice {
|
||||||
|
width: 300px !important;
|
||||||
|
min-width: 300px !important;
|
||||||
|
max-width: 300px !important;
|
||||||
|
flex: 0 0 300px !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.part-matrice.select2-hidden-accessible+.select2 {
|
||||||
|
width: 300px !important;
|
||||||
|
min-width: 300px !important;
|
||||||
|
max-width: 300px !important;
|
||||||
|
flex: 0 0 300px !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Colonna Descrizione (2ª colonna) = 420px */
|
||||||
|
#partsTable th:nth-child(2),
|
||||||
|
#partsTable td:nth-child(2) {
|
||||||
|
width: 350 !important;
|
||||||
min-width: 350px !important;
|
min-width: 350px !important;
|
||||||
|
max-width: 350px !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
.select2-container--default #macro-matrice-filter {
|
#partsTable td:nth-child(2) .part-description {
|
||||||
width: 200px !important;
|
width: 100% !important;
|
||||||
min-width: 200px !important;
|
max-width: 100% !important;
|
||||||
}
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
.select2-container--default .part-matrice {
|
white-space: nowrap;
|
||||||
width: 150px !important;
|
|
||||||
min-width: 150px !important;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Aspetto Select2 */
|
||||||
.select2-container--default .select2-selection--single {
|
.select2-container--default .select2-selection--single {
|
||||||
height: 24px !important;
|
height: 24px !important;
|
||||||
padding: 0.1rem 0.3rem !important;
|
padding: .1rem .3rem !important;
|
||||||
font-size: 0.8rem !important;
|
font-size: .8rem !important;
|
||||||
border: 1px solid #ced4da !important;
|
border: 1px solid #ced4da !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
.select2-container--default .select2-selection__rendered {
|
|
||||||
line-height: 22px !important;
|
|
||||||
overflow: hidden !important;
|
|
||||||
text-overflow: ellipsis !important;
|
|
||||||
white-space: nowrap !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
.select2-container--default .select2-selection__arrow {
|
.select2-container--default .select2-selection__arrow {
|
||||||
height: 24px !important;
|
height: 24px !important
|
||||||
}
|
}
|
||||||
|
|
||||||
.select2-container--open .select2-dropdown {
|
.select2-container--open .select2-dropdown {
|
||||||
z-index: 1061 !important;
|
z-index: 1061 !important;
|
||||||
border: 1px solid #aaa !important;
|
border: 1px solid #aaa !important;
|
||||||
border-radius: 4px !important;
|
border-radius: 4px !important;
|
||||||
background: white !important;
|
background: #fff !important;
|
||||||
overflow-y: auto !important;
|
|
||||||
max-height: 200px !important;
|
max-height: 200px !important;
|
||||||
|
overflow-y: auto !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Evita stretching del flex nella riga dei filtri */
|
||||||
|
#partsModal .modal-body>.row .col-md-9>div[style*="display: flex"]>* {
|
||||||
|
flex: 0 0 auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Altri modali e pulsanti */
|
||||||
.propagate-matrice-btn,
|
.propagate-matrice-btn,
|
||||||
.propagate-all-btn {
|
.propagate-all-btn {
|
||||||
padding: 0.1rem 0.3rem !important;
|
padding: .1rem .3rem !important;
|
||||||
font-size: 0.8rem !important;
|
font-size: .8rem !important
|
||||||
}
|
}
|
||||||
|
|
||||||
.save-status,
|
.save-status,
|
||||||
.save-loading {
|
.save-loading {
|
||||||
margin-left: 5px;
|
margin-left: 5px
|
||||||
}
|
}
|
||||||
|
|
||||||
#confirmDeleteModal {
|
#confirmDeleteModal {
|
||||||
z-index: 1070 !important;
|
z-index: 1070 !important
|
||||||
}
|
}
|
||||||
|
|
||||||
#confirmDeleteModal .modal-backdrop {
|
#confirmDeleteModal .modal-backdrop {
|
||||||
z-index: 1065 !important;
|
z-index: 1065 !important
|
||||||
}
|
}
|
||||||
|
|
||||||
.note-btn {
|
.note-btn {
|
||||||
padding: 0.2rem 0.4rem !important;
|
padding: .2rem .4rem !important;
|
||||||
/* Aumentato leggermente il padding per un pulsante più grande */
|
font-size: .9rem !important
|
||||||
font-size: 0.9rem !important;
|
|
||||||
/* Aumentato il font-size per un'icona più grande */
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.note-btn.has-note {
|
.note-btn.has-note {
|
||||||
color: #dc3545 !important;
|
color: #dc3545 !important
|
||||||
/* Rosso quando la nota è presente */
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#noteModal {
|
#noteModal {
|
||||||
z-index: 1090 !important;
|
z-index: 1090 !important
|
||||||
}
|
}
|
||||||
|
|
||||||
#noteModal .modal-backdrop {
|
#noteModal .modal-backdrop {
|
||||||
z-index: 1085 !important;
|
z-index: 1085 !important
|
||||||
}
|
}
|
||||||
|
|
||||||
#noteModal .modal-dialog {
|
#noteModal .modal-dialog {
|
||||||
position: relative;
|
position: relative;
|
||||||
z-index: 1090 !important;
|
z-index: 1090 !important
|
||||||
}
|
}
|
||||||
|
|
||||||
#noteModal textarea {
|
#noteModal textarea,
|
||||||
resize: vertical;
|
#commonNoteModal textarea {
|
||||||
}
|
resize: vertical
|
||||||
|
|
||||||
.propagate-date-btn {
|
|
||||||
padding: 0.2rem 0.4rem !important;
|
|
||||||
font-size: 0.9rem !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
.propagate-note-btn {
|
|
||||||
padding: 0.2rem 0.4rem !important;
|
|
||||||
font-size: 0.9rem !important;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#commonNoteModal {
|
#commonNoteModal {
|
||||||
z-index: 1095 !important;
|
z-index: 1095 !important
|
||||||
/* Sopra #noteModal (1090) */
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#commonNoteModal .modal-backdrop {
|
#commonNoteModal .modal-backdrop {
|
||||||
z-index: 1090 !important;
|
z-index: 1090 !important
|
||||||
/* Sopra il backdrop di #noteModal (1085) */
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#commonNoteModal .modal-dialog {
|
#commonNoteModal .modal-dialog {
|
||||||
position: relative;
|
position: relative;
|
||||||
z-index: 1095 !important;
|
z-index: 1095 !important
|
||||||
}
|
}
|
||||||
|
|
||||||
#commonNoteModal textarea {
|
/* Evidenza salvataggio riga nel parts table */
|
||||||
resize: vertical;
|
/* Aumenta la specificità per le classi di flash */
|
||||||
|
table#partsTable tr.row-saving {
|
||||||
|
background-color: #f0ad4e !important;
|
||||||
|
/* Arancione per salvataggio in corso */
|
||||||
|
transition: background-color 0.3s ease;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
table#partsTable tr.row-success {
|
||||||
|
background-color: #5cb85c !important;
|
||||||
|
/* Verde per successo */
|
||||||
|
transition: background-color 0.3s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
table#partsTable tr.row-error {
|
||||||
|
background-color: #d9534f !important;
|
||||||
|
/* Rosso per errore */
|
||||||
|
transition: background-color 0.3s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Stato base: nascosti (verrà sovrascritto dallo style inline di jQuery) */
|
||||||
|
#partsModal .save-loading,
|
||||||
|
#partsModal .save-status {
|
||||||
|
display: none;
|
||||||
|
align-items: center;
|
||||||
|
gap: 6px;
|
||||||
|
padding: 2px 8px;
|
||||||
|
border-radius: 999px;
|
||||||
|
font-weight: 700;
|
||||||
|
font-size: 12px;
|
||||||
|
line-height: 1.2;
|
||||||
|
margin-left: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Quando NON sono nascosti via style inline (jQuery .show()), forzali a inline-flex */
|
||||||
|
#partsModal .save-loading:not([style*="display: none"]),
|
||||||
|
#partsModal .save-status:not([style*="display: none"]) {
|
||||||
|
display: inline-flex;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Loading (giallo) */
|
||||||
|
/* Loading (giallo) */
|
||||||
|
#partsModal .save-loading {
|
||||||
|
background: #ffd753ff;
|
||||||
|
border: 1px solid #ffd042ff;
|
||||||
|
color: #111;
|
||||||
|
/* testo nero */
|
||||||
|
}
|
||||||
|
|
||||||
|
#partsModal .save-loading i {
|
||||||
|
color: #111;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* icona nera */
|
||||||
|
|
||||||
|
#partsModal .save-loading::after {
|
||||||
|
content: " Salvataggio…";
|
||||||
|
color: #111;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Salvato (verde) */
|
||||||
|
#partsModal .save-status {
|
||||||
|
background: #5dff83ff;
|
||||||
|
border: 1px solid #4effafff;
|
||||||
|
color: #111;
|
||||||
|
/* testo nero */
|
||||||
|
}
|
||||||
|
|
||||||
|
#partsModal .save-status i {
|
||||||
|
color: #111;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* icona nera */
|
||||||
|
#partsModal .save-status::after {
|
||||||
|
content: " Salvato";
|
||||||
|
color: #111;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Animazioni */
|
||||||
|
@keyframes pulse {
|
||||||
|
|
||||||
|
0%,
|
||||||
|
100% {
|
||||||
|
transform: scale(1);
|
||||||
|
opacity: .9
|
||||||
|
}
|
||||||
|
|
||||||
|
50% {
|
||||||
|
transform: scale(1.05);
|
||||||
|
opacity: 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes pop {
|
||||||
|
0% {
|
||||||
|
transform: scale(.85);
|
||||||
|
opacity: 0
|
||||||
|
}
|
||||||
|
|
||||||
|
100% {
|
||||||
|
transform: scale(1);
|
||||||
|
opacity: 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/* rosso */
|
||||||
</style>
|
</style>
|
||||||
+242
-120
@@ -7,6 +7,35 @@ $(document).ready(function () {
|
|||||||
let matrici = [];
|
let matrici = [];
|
||||||
let macroMatrici = [];
|
let macroMatrici = [];
|
||||||
|
|
||||||
|
// --- ROW ID helpers: niente più cache impazzita di jQuery .data() ---
|
||||||
|
function getPartId($row) {
|
||||||
|
// Legge in ordine: cache jQuery, nostra cache, attributo
|
||||||
|
const d = $row.data("part-id");
|
||||||
|
const ours = $row.data("__pid");
|
||||||
|
const attr = $row.attr("data-part-id");
|
||||||
|
|
||||||
|
// Scegli il primo definito
|
||||||
|
const id =
|
||||||
|
d !== undefined
|
||||||
|
? d
|
||||||
|
: ours !== undefined
|
||||||
|
? ours
|
||||||
|
: attr !== undefined
|
||||||
|
? attr
|
||||||
|
: null;
|
||||||
|
|
||||||
|
// Normalizza: "new" o "" NON sono ID validi
|
||||||
|
return id === "new" || id === "" || id === null ? null : id;
|
||||||
|
}
|
||||||
|
|
||||||
|
function setPartId($row, id) {
|
||||||
|
if (!id) return;
|
||||||
|
// Sincronizza TUTTO: attributo + cache jQuery + nostra cache
|
||||||
|
$row.attr("data-part-id", id);
|
||||||
|
$row.data("part-id", id); // <<< fondamentale per invalidare la cache di jQuery
|
||||||
|
$row.data("__pid", id);
|
||||||
|
}
|
||||||
|
|
||||||
// ===================
|
// ===================
|
||||||
// VOICE RECOGNITION SETUP
|
// VOICE RECOGNITION SETUP
|
||||||
// ===================
|
// ===================
|
||||||
@@ -439,6 +468,7 @@ $(document).ready(function () {
|
|||||||
|
|
||||||
$(document).on("click", ".add-mix-global", function (e) {
|
$(document).on("click", ".add-mix-global", function (e) {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
|
|
||||||
const maxPartNumber = Math.max(
|
const maxPartNumber = Math.max(
|
||||||
...$("#partsTableBody tr")
|
...$("#partsTableBody tr")
|
||||||
.map(function () {
|
.map(function () {
|
||||||
@@ -446,36 +476,177 @@ $(document).ready(function () {
|
|||||||
})
|
})
|
||||||
.get(),
|
.get(),
|
||||||
);
|
);
|
||||||
addNewRow(maxPartNumber + 1, true); // Riga Mix
|
|
||||||
|
// Crea la riga Mix
|
||||||
|
addNewRow(maxPartNumber + 1, true);
|
||||||
|
const $mixRow = $("#partsTableBody tr:last");
|
||||||
|
|
||||||
|
// Consenti SOLO ora la creazione (INSERT) della riga Mix
|
||||||
|
$mixRow.data("allowCreateMix", true);
|
||||||
|
|
||||||
|
// esegue SUBITO l'INSERT così ottieni part-id
|
||||||
|
saveRow($mixRow);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
function extractPartId(response) {
|
||||||
|
// prova i campi più comuni
|
||||||
|
if (response == null) return null;
|
||||||
|
|
||||||
|
if (response.part_id) return response.part_id;
|
||||||
|
if (response.id) return response.id;
|
||||||
|
if (response.insert_id) return response.insert_id;
|
||||||
|
if (response.lastId) return response.lastId;
|
||||||
|
|
||||||
|
// array di id
|
||||||
|
if (Array.isArray(response.part_ids) && response.part_ids[0]) {
|
||||||
|
return response.part_ids[0];
|
||||||
|
}
|
||||||
|
|
||||||
|
// oggetti annidati
|
||||||
|
if (response.parts && response.parts[0]) {
|
||||||
|
if (response.parts[0].id) return response.parts[0].id;
|
||||||
|
if (response.parts[0].part_id) return response.parts[0].part_id;
|
||||||
|
if (response.parts[0].insert_id) return response.parts[0].insert_id;
|
||||||
|
}
|
||||||
|
|
||||||
|
// altri possibili nomi dal backend
|
||||||
|
if (response.new_id) return response.new_id;
|
||||||
|
if (response.partId) return response.partId;
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
function saveRow($row) {
|
||||||
|
const partNumber = $row.find(".part-number").val();
|
||||||
|
const partDescription = $row.find(".part-description").val().trim();
|
||||||
|
const dateexpiry = $row.find(".part-dateexpiry").val();
|
||||||
|
const note = $row.data("note") || null;
|
||||||
|
const $saveStatus = $row.find(".save-status");
|
||||||
|
const $saveLoading = $row.find(".save-loading");
|
||||||
|
const iddatadb = $("#partsModal").data("iddatadb");
|
||||||
|
const idquotations = $("#partsModal").data("idquotations");
|
||||||
|
const isMix = partDescription.startsWith("Mix") ? "Y" : "N";
|
||||||
|
let partId = getPartId($row);
|
||||||
|
if (partId === "new") partId = null; // difesa extra (non dovrebbe più servire, ma sicura)
|
||||||
|
const endpoint = idquotations
|
||||||
|
? "save_parts_quotation.php"
|
||||||
|
: "save_parts.php";
|
||||||
|
const data = idquotations ? { idquotations } : { iddatadb };
|
||||||
|
|
||||||
|
// Evita salvataggi concorrenti
|
||||||
|
if ($row.data("saving") === true) return;
|
||||||
|
|
||||||
|
// Blocca INSERT del Mix se non esplicitamente richiesta
|
||||||
|
if (isMix === "Y" && !partId && $row.data("allowCreateMix") !== true) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
$row.data("saving", true);
|
||||||
|
|
||||||
|
if (partDescription && (iddatadb || idquotations)) {
|
||||||
|
$saveLoading.show();
|
||||||
|
$saveStatus.hide();
|
||||||
|
|
||||||
|
$.ajax({
|
||||||
|
url: endpoint,
|
||||||
|
method: "POST",
|
||||||
|
data: JSON.stringify({
|
||||||
|
...data,
|
||||||
|
parts: [
|
||||||
|
{
|
||||||
|
id: partId,
|
||||||
|
part_number: partNumber,
|
||||||
|
part_description: partDescription,
|
||||||
|
mix: isMix,
|
||||||
|
dateexpiry: dateexpiry || null,
|
||||||
|
note: note,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
}),
|
||||||
|
contentType: "application/json",
|
||||||
|
|
||||||
|
success: function (response) {
|
||||||
|
// assegna ID appena arriva (robusto)
|
||||||
|
if (response.success) {
|
||||||
|
const newId = extractPartId(response);
|
||||||
|
if (newId) {
|
||||||
|
setPartId($row, newId);
|
||||||
|
} else {
|
||||||
|
console.warn(
|
||||||
|
"Parte salvata ma ID non presente nella risposta. Ricarico parti per sincronizzare gli ID.",
|
||||||
|
);
|
||||||
|
loadExistingParts(iddatadb, idquotations); // <<< ORA ANCHE PER RIGHE NORMALI
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$row.data("saving", false);
|
||||||
|
$row.removeData("allowCreateMix");
|
||||||
|
|
||||||
|
// ora che l'ID c'è di sicuro, sblocco gli update pendenti (es. M+)
|
||||||
|
$row.trigger("row:saved");
|
||||||
|
|
||||||
|
$saveLoading.hide();
|
||||||
|
if (response.success) {
|
||||||
|
$saveStatus.show();
|
||||||
|
setTimeout(() => $saveStatus.hide(), 2000);
|
||||||
|
} else {
|
||||||
|
const errorMsg = $(
|
||||||
|
'<div class="alert alert-danger temp-alert" role="alert">Errore nel salvataggio: ' +
|
||||||
|
response.message +
|
||||||
|
"</div>",
|
||||||
|
);
|
||||||
|
$("#partsModal .modal-body").prepend(errorMsg);
|
||||||
|
setTimeout(() => {
|
||||||
|
errorMsg.fadeOut(500, function () {
|
||||||
|
$(this).remove();
|
||||||
|
});
|
||||||
|
}, 5000);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
error: function (xhr, status, error) {
|
||||||
|
$row.data("saving", false);
|
||||||
|
$row.removeData("allowCreateMix");
|
||||||
|
$saveLoading.hide();
|
||||||
|
const errorMsg = $(
|
||||||
|
'<div class="alert alert-danger temp-alert" role="alert">Errore nel salvataggio delle parti: ' +
|
||||||
|
error +
|
||||||
|
" (" +
|
||||||
|
xhr.status +
|
||||||
|
")</div>",
|
||||||
|
);
|
||||||
|
$("#partsModal .modal-body").prepend(errorMsg);
|
||||||
|
setTimeout(() => {
|
||||||
|
errorMsg.fadeOut(500, function () {
|
||||||
|
$(this).remove();
|
||||||
|
});
|
||||||
|
}, 5000);
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
$(document).on("click", ".add-mix-row", function (e) {
|
$(document).on("click", ".add-mix-row", function (e) {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
const $row = $(this).closest("tr");
|
|
||||||
const partDescription = $row.find(".part-description").val().trim();
|
const $srcRow = $(this).closest("tr");
|
||||||
|
const partDescription = $srcRow.find(".part-description").val().trim();
|
||||||
if (!partDescription) {
|
if (!partDescription) {
|
||||||
const errorMsg = $(
|
const errorMsg = $(
|
||||||
'<div class="alert alert-danger temp-alert" role="alert">Inserisci una descrizione valida prima di aggiungerla al Mix.</div>',
|
'<div class="alert alert-danger temp-alert" role="alert">Inserisci una descrizione valida prima di aggiungerla al Mix.</div>',
|
||||||
);
|
);
|
||||||
$("#partsModal .modal-body").prepend(errorMsg);
|
$("#partsModal .modal-body").prepend(errorMsg);
|
||||||
setTimeout(function () {
|
setTimeout(
|
||||||
|
() =>
|
||||||
errorMsg.fadeOut(500, function () {
|
errorMsg.fadeOut(500, function () {
|
||||||
$(this).remove();
|
$(this).remove();
|
||||||
});
|
}),
|
||||||
}, 5000);
|
5000,
|
||||||
|
);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const maxPartNumber = Math.max(
|
let $mixRow = $("#partsTableBody tr")
|
||||||
...$("#partsTableBody tr")
|
|
||||||
.map(function () {
|
|
||||||
return parseInt($(this).find(".part-number").val()) || 0;
|
|
||||||
})
|
|
||||||
.get(),
|
|
||||||
);
|
|
||||||
|
|
||||||
let mixDescription = `Mix ${partDescription}`;
|
|
||||||
const $mixRow = $("#partsTableBody tr")
|
|
||||||
.filter(function () {
|
.filter(function () {
|
||||||
return $(this)
|
return $(this)
|
||||||
.find(".part-description")
|
.find(".part-description")
|
||||||
@@ -485,31 +656,59 @@ $(document).ready(function () {
|
|||||||
})
|
})
|
||||||
.last();
|
.last();
|
||||||
|
|
||||||
if ($mixRow.length > 0) {
|
// Se non esiste una riga Mix, ne creo una e la INSERISCO SUBITO (come fa il bottone in header)
|
||||||
let currentMix = $mixRow.find(".part-description").val().trim();
|
if ($mixRow.length === 0) {
|
||||||
if (currentMix === "Mix") {
|
const maxPartNumber = Math.max(
|
||||||
mixDescription = currentMix + " " + partDescription;
|
...$("#partsTableBody tr")
|
||||||
} else if (!currentMix.includes(partDescription)) {
|
.map(function () {
|
||||||
mixDescription = currentMix + " + " + partDescription;
|
return (
|
||||||
|
parseInt($(this).find(".part-number").val()) || 0
|
||||||
|
);
|
||||||
|
})
|
||||||
|
.get(),
|
||||||
|
);
|
||||||
|
|
||||||
|
addNewRow(maxPartNumber + 1, true);
|
||||||
|
$mixRow = $("#partsTableBody tr:last");
|
||||||
|
$mixRow.find(".part-description").val(`Mix ${partDescription}`);
|
||||||
|
|
||||||
|
// Consenti la creazione (INSERT) della riga Mix e salvala subito
|
||||||
|
$mixRow.data("allowCreateMix", true);
|
||||||
|
|
||||||
|
saveRow($mixRow); // -> INSERT
|
||||||
|
return; // la descrizione include già l'elemento appena aggiunto
|
||||||
|
}
|
||||||
|
|
||||||
|
// Aggiorna la descrizione del Mix esistente
|
||||||
|
const currentMix = $mixRow.find(".part-description").val().trim();
|
||||||
|
let newDesc = currentMix;
|
||||||
|
if (currentMix === "Mix") newDesc = currentMix + " " + partDescription;
|
||||||
|
else if (!currentMix.includes(partDescription))
|
||||||
|
newDesc = currentMix + " + " + partDescription;
|
||||||
|
|
||||||
|
$mixRow.find(".part-description").val(newDesc);
|
||||||
|
|
||||||
|
// Se il Mix è già in salvataggio (INSERT o UPDATE in corso), accodiamo un solo UPDATE
|
||||||
|
if ($mixRow.data("saving") === true) {
|
||||||
|
// evita più code accumulate
|
||||||
|
if (!$mixRow.data("pendingUpdate")) {
|
||||||
|
$mixRow.data("pendingUpdate", true);
|
||||||
|
$mixRow.one("row:saved", function () {
|
||||||
|
$mixRow.removeData("pendingUpdate");
|
||||||
|
// ora che saving è false, salviamo l'ultima descrizione impostata
|
||||||
|
saveRow($mixRow);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
$mixRow
|
|
||||||
.find(".part-description")
|
|
||||||
.val(mixDescription)
|
|
||||||
.trigger("blur");
|
|
||||||
} else {
|
} else {
|
||||||
addNewRow(maxPartNumber + 1, true); // Crea nuova riga Mix
|
// libero: salva subito
|
||||||
const $newMixRow = $("#partsTableBody tr:last");
|
saveRow($mixRow);
|
||||||
$newMixRow
|
|
||||||
.find(".part-description")
|
|
||||||
.val(mixDescription)
|
|
||||||
.trigger("blur");
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
function addNewRow(nextPartNumber, isMix = false) {
|
function addNewRow(nextPartNumber, isMix = false) {
|
||||||
const description = isMix ? "Mix" : "";
|
const description = isMix ? "Mix" : "";
|
||||||
const newRow = `
|
const newRow = `
|
||||||
<tr data-part-id="">
|
<tr data-part-id="new">
|
||||||
<td><input type="number" class="form-control form-control-sm part-number" value="${nextPartNumber || 1}" style="width: 80px;"></td>
|
<td><input type="number" class="form-control form-control-sm part-number" value="${nextPartNumber || 1}" style="width: 80px;"></td>
|
||||||
<td><input type="text" class="form-control form-control-sm part-description" value="${description}" placeholder="Inserisci descrizione"></td>
|
<td><input type="text" class="form-control form-control-sm part-description" value="${description}" placeholder="Inserisci descrizione"></td>
|
||||||
<td>
|
<td>
|
||||||
@@ -540,7 +739,7 @@ $(document).ready(function () {
|
|||||||
// ===================
|
// ===================
|
||||||
$(document).on("click", ".note-btn", function () {
|
$(document).on("click", ".note-btn", function () {
|
||||||
const $row = $(this).closest("tr");
|
const $row = $(this).closest("tr");
|
||||||
const partId = $row.data("part-id");
|
const partId = getPartId($row);
|
||||||
const note = $row.data("note") || "";
|
const note = $row.data("note") || "";
|
||||||
const $noteModal = $("#noteModal");
|
const $noteModal = $("#noteModal");
|
||||||
$noteModal.find(".part-note").val(note);
|
$noteModal.find(".part-note").val(note);
|
||||||
@@ -655,7 +854,7 @@ $(document).ready(function () {
|
|||||||
$(document).on("change", ".part-dateexpiry", function () {
|
$(document).on("change", ".part-dateexpiry", function () {
|
||||||
const $input = $(this);
|
const $input = $(this);
|
||||||
const $row = $input.closest("tr");
|
const $row = $input.closest("tr");
|
||||||
const partId = $row.data("part-id");
|
const partId = getPartId($row);
|
||||||
const dateexpiry = $input.val();
|
const dateexpiry = $input.val();
|
||||||
const iddatadb = $("#partsModal").data("iddatadb");
|
const iddatadb = $("#partsModal").data("iddatadb");
|
||||||
const idquotations = $("#partsModal").data("idquotations");
|
const idquotations = $("#partsModal").data("idquotations");
|
||||||
@@ -953,88 +1152,7 @@ $(document).ready(function () {
|
|||||||
});
|
});
|
||||||
|
|
||||||
$(document).on("blur", ".part-description, .part-number", function () {
|
$(document).on("blur", ".part-description, .part-number", function () {
|
||||||
const $input = $(this);
|
saveRow($(this).closest("tr"));
|
||||||
const $row = $input.closest("tr");
|
|
||||||
const partNumber = $row.find(".part-number").val();
|
|
||||||
const partDescription = $row.find(".part-description").val().trim();
|
|
||||||
const dateexpiry = $row.find(".part-dateexpiry").val();
|
|
||||||
const note = $row.data("note") || null;
|
|
||||||
const $saveStatus = $row.find(".save-status");
|
|
||||||
const $saveLoading = $row.find(".save-loading");
|
|
||||||
const iddatadb = $("#partsModal").data("iddatadb");
|
|
||||||
const idquotations = $("#partsModal").data("idquotations");
|
|
||||||
const isMix = partDescription.startsWith("Mix") ? "Y" : "N";
|
|
||||||
const partId = $row.data("part-id") || null;
|
|
||||||
const endpoint = idquotations
|
|
||||||
? "save_parts_quotation.php"
|
|
||||||
: "save_parts.php";
|
|
||||||
const data = idquotations
|
|
||||||
? { idquotations: idquotations }
|
|
||||||
: { iddatadb: iddatadb };
|
|
||||||
|
|
||||||
if (partDescription && (iddatadb || idquotations)) {
|
|
||||||
$saveLoading.show();
|
|
||||||
$saveStatus.hide();
|
|
||||||
$.ajax({
|
|
||||||
url: endpoint,
|
|
||||||
method: "POST",
|
|
||||||
data: JSON.stringify({
|
|
||||||
...data,
|
|
||||||
parts: [
|
|
||||||
{
|
|
||||||
id: partId,
|
|
||||||
part_number: partNumber,
|
|
||||||
part_description: partDescription,
|
|
||||||
mix: isMix,
|
|
||||||
dateexpiry: dateexpiry || null,
|
|
||||||
note: note,
|
|
||||||
},
|
|
||||||
],
|
|
||||||
}),
|
|
||||||
contentType: "application/json",
|
|
||||||
success: function (response) {
|
|
||||||
$saveLoading.hide();
|
|
||||||
if (response.success) {
|
|
||||||
$saveStatus.show();
|
|
||||||
if (response.part_id) {
|
|
||||||
$row.attr("data-part-id", response.part_id).data(
|
|
||||||
"part-id",
|
|
||||||
response.part_id,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
setTimeout(() => $saveStatus.hide(), 2000);
|
|
||||||
} else {
|
|
||||||
const errorMsg = $(
|
|
||||||
'<div class="alert alert-danger temp-alert" role="alert">Errore nel salvataggio: ' +
|
|
||||||
response.message +
|
|
||||||
"</div>",
|
|
||||||
);
|
|
||||||
$("#partsModal .modal-body").prepend(errorMsg);
|
|
||||||
setTimeout(function () {
|
|
||||||
errorMsg.fadeOut(500, function () {
|
|
||||||
$(this).remove();
|
|
||||||
});
|
|
||||||
}, 5000);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
error: function (xhr, status, error) {
|
|
||||||
$saveLoading.hide();
|
|
||||||
const errorMsg = $(
|
|
||||||
'<div class="alert alert-danger temp-alert" role="alert">Errore nel salvataggio delle parti: ' +
|
|
||||||
error +
|
|
||||||
" (" +
|
|
||||||
xhr.status +
|
|
||||||
")</div>",
|
|
||||||
);
|
|
||||||
$("#partsModal .modal-body").prepend(errorMsg);
|
|
||||||
setTimeout(function () {
|
|
||||||
errorMsg.fadeOut(500, function () {
|
|
||||||
$(this).remove();
|
|
||||||
});
|
|
||||||
}, 5000);
|
|
||||||
},
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
|
|
||||||
function loadExistingParts(iddatadb, idquotations) {
|
function loadExistingParts(iddatadb, idquotations) {
|
||||||
@@ -1356,8 +1474,8 @@ $(document).ready(function () {
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
// Ripristina il valore se valido
|
// === MODIFICA CHIRURGICA: solo qui ===
|
||||||
if (partId && partId !== "new" && currentValue) {
|
if (currentValue && currentValue !== "new") {
|
||||||
const matrice = matrici.find((m) => m.IdMatrice == currentValue);
|
const matrice = matrici.find((m) => m.IdMatrice == currentValue);
|
||||||
if (matrice) {
|
if (matrice) {
|
||||||
const option = new Option(
|
const option = new Option(
|
||||||
@@ -1369,11 +1487,15 @@ $(document).ready(function () {
|
|||||||
$select.append(option).trigger("change");
|
$select.append(option).trigger("change");
|
||||||
partMatrice[partNumber] = matrice.IdMatrice;
|
partMatrice[partNumber] = matrice.IdMatrice;
|
||||||
} else {
|
} else {
|
||||||
// Aggiusta valore non valido
|
|
||||||
$select.val(null).trigger("change");
|
$select.val(null).trigger("change");
|
||||||
partMatrice[partNumber] = null;
|
partMatrice[partNumber] = null;
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
// Nessun valore: assicurati che sia vuoto
|
||||||
|
$select.val(null).trigger("change");
|
||||||
|
partMatrice[partNumber] = null;
|
||||||
}
|
}
|
||||||
|
// === FINE MODIFICA ===
|
||||||
|
|
||||||
$select.on("change", function () {
|
$select.on("change", function () {
|
||||||
const idmatrice = $(this).val();
|
const idmatrice = $(this).val();
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
<?php
|
<?php
|
||||||
// upload_photos_mobile.php
|
// upload_photos_mobile.php
|
||||||
include('include/headscript.php');
|
include('include/headscriptnologin.php');
|
||||||
|
|
||||||
$db = DBHandlerSelect::getInstance();
|
$db = DBHandlerSelect::getInstance();
|
||||||
$pdo = $db->getConnection();
|
$pdo = $db->getConnection();
|
||||||
|
|||||||
Reference in New Issue
Block a user