Compare commits
10 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 67bbd9bbbb | |||
| fa7997c980 | |||
| 66be442eb6 | |||
| 19a2d6e3f7 | |||
| a15ab08576 | |||
| f71e8a56b5 | |||
| cb38bfb75a | |||
| 28a708dad3 | |||
| 6b9cf20ab9 | |||
| d40fc7d177 |
@@ -55,3 +55,5 @@ yarn-error.log
|
|||||||
|
|
||||||
# Ignora tutti i log ovunque
|
# Ignora tutti i log ovunque
|
||||||
*.log
|
*.log
|
||||||
|
|
||||||
|
public/userarea/cache/
|
||||||
@@ -0,0 +1,131 @@
|
|||||||
|
<?php
|
||||||
|
header('Content-Type: application/json');
|
||||||
|
include('include/headscript.php');
|
||||||
|
|
||||||
|
$dbHandler = DBHandlerSelect::getInstance();
|
||||||
|
$pdo = $dbHandler->getConnection();
|
||||||
|
$pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
|
||||||
|
|
||||||
|
$data = json_decode(file_get_contents('php://input'), true);
|
||||||
|
|
||||||
|
$sourceIddatadb = isset($data['source_iddatadb']) ? (int)$data['source_iddatadb'] : 0;
|
||||||
|
$targetList = $data['target_iddatadb_list'] ?? [];
|
||||||
|
|
||||||
|
$targetIds = array_values(array_unique(array_filter(array_map('intval', (array)$targetList), function ($v) use ($sourceIddatadb) {
|
||||||
|
return $v > 0 && $v !== $sourceIddatadb;
|
||||||
|
})));
|
||||||
|
|
||||||
|
if ($sourceIddatadb <= 0 || empty($targetIds)) {
|
||||||
|
echo json_encode([
|
||||||
|
'success' => false,
|
||||||
|
'message' => 'Missing source or target records'
|
||||||
|
]);
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
$pdo->beginTransaction();
|
||||||
|
|
||||||
|
// 1. Load source parts
|
||||||
|
$stmtParts = $pdo->prepare("
|
||||||
|
SELECT id, part_number, part_description, mix, idmatrice, note, dateexpiry
|
||||||
|
FROM identification_parts
|
||||||
|
WHERE iddatadb = ?
|
||||||
|
ORDER BY part_number ASC, id ASC
|
||||||
|
");
|
||||||
|
$stmtParts->execute([$sourceIddatadb]);
|
||||||
|
$sourceParts = $stmtParts->fetchAll(PDO::FETCH_ASSOC);
|
||||||
|
|
||||||
|
if (empty($sourceParts)) {
|
||||||
|
$pdo->rollBack();
|
||||||
|
echo json_encode([
|
||||||
|
'success' => false,
|
||||||
|
'message' => 'No parts found for source record'
|
||||||
|
]);
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2. Prepare statements
|
||||||
|
$stmtInsertPart = $pdo->prepare("
|
||||||
|
INSERT INTO identification_parts
|
||||||
|
(iddatadb, part_number, part_description, mix, idmatrice, note, dateexpiry, created_at, updated_at)
|
||||||
|
VALUES
|
||||||
|
(:iddatadb, :part_number, :part_description, :mix, :idmatrice, :note, :dateexpiry, NOW(), NOW())
|
||||||
|
");
|
||||||
|
|
||||||
|
$stmtLoadCF = $pdo->prepare("
|
||||||
|
SELECT field_id, value_id, value_text
|
||||||
|
FROM identification_parts_customfields
|
||||||
|
WHERE part_id = ?
|
||||||
|
ORDER BY id ASC
|
||||||
|
");
|
||||||
|
|
||||||
|
$stmtInsertCF = $pdo->prepare("
|
||||||
|
INSERT INTO identification_parts_customfields
|
||||||
|
(part_id, field_id, value_id, value_text, created_at, updated_at)
|
||||||
|
VALUES
|
||||||
|
(:part_id, :field_id, :value_id, :value_text, NOW(), NOW())
|
||||||
|
");
|
||||||
|
|
||||||
|
$details = [];
|
||||||
|
$totalClonedParts = 0;
|
||||||
|
|
||||||
|
// 3. Clone source parts to each target record
|
||||||
|
foreach ($targetIds as $targetIddatadb) {
|
||||||
|
$clonedCountForTarget = 0;
|
||||||
|
|
||||||
|
foreach ($sourceParts as $part) {
|
||||||
|
$stmtInsertPart->execute([
|
||||||
|
':iddatadb' => $targetIddatadb,
|
||||||
|
':part_number' => $part['part_number'],
|
||||||
|
':part_description' => $part['part_description'],
|
||||||
|
':mix' => $part['mix'] ?? 'N',
|
||||||
|
':idmatrice' => $part['idmatrice'] !== '' ? $part['idmatrice'] : null,
|
||||||
|
':note' => $part['note'] ?? null,
|
||||||
|
':dateexpiry' => $part['dateexpiry'] ?? null,
|
||||||
|
]);
|
||||||
|
|
||||||
|
$newPartId = (int)$pdo->lastInsertId();
|
||||||
|
|
||||||
|
// Load source custom fields for this part
|
||||||
|
$stmtLoadCF->execute([(int)$part['id']]);
|
||||||
|
$customFields = $stmtLoadCF->fetchAll(PDO::FETCH_ASSOC);
|
||||||
|
|
||||||
|
foreach ($customFields as $cf) {
|
||||||
|
$stmtInsertCF->execute([
|
||||||
|
':part_id' => $newPartId,
|
||||||
|
':field_id' => (int)$cf['field_id'],
|
||||||
|
':value_id' => ($cf['value_id'] !== null && $cf['value_id'] !== '') ? (int)$cf['value_id'] : null,
|
||||||
|
':value_text' => $cf['value_text'] !== '' ? $cf['value_text'] : null,
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
$clonedCountForTarget++;
|
||||||
|
$totalClonedParts++;
|
||||||
|
}
|
||||||
|
|
||||||
|
$details[] = [
|
||||||
|
'target_iddatadb' => $targetIddatadb,
|
||||||
|
'cloned_parts' => $clonedCountForTarget
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
$pdo->commit();
|
||||||
|
|
||||||
|
echo json_encode([
|
||||||
|
'success' => true,
|
||||||
|
'source_iddatadb' => $sourceIddatadb,
|
||||||
|
'cloned_targets' => count($targetIds),
|
||||||
|
'total_cloned_parts' => $totalClonedParts,
|
||||||
|
'details' => $details
|
||||||
|
]);
|
||||||
|
} catch (Throwable $e) {
|
||||||
|
if ($pdo->inTransaction()) {
|
||||||
|
$pdo->rollBack();
|
||||||
|
}
|
||||||
|
|
||||||
|
echo json_encode([
|
||||||
|
'success' => false,
|
||||||
|
'message' => 'Clone failed: ' . $e->getMessage()
|
||||||
|
]);
|
||||||
|
}
|
||||||
@@ -432,6 +432,71 @@ try {
|
|||||||
$logFilePhotos = $logDir . "commessa_{$commessaId}_photos_step5_2_" . time() . ".txt";
|
$logFilePhotos = $logDir . "commessa_{$commessaId}_photos_step5_2_" . time() . ".txt";
|
||||||
$writeLog($logFilePhotos, $logContentPhotos, "STEP 6.2 - Photos (commessa={$commessaId})");
|
$writeLog($logFilePhotos, $logContentPhotos, "STEP 6.2 - Photos (commessa={$commessaId})");
|
||||||
|
|
||||||
|
// 🔹 STEP 6.3: Add Analyses (AnalisiCampione) via Campione({id})/AddAnalisi bound action
|
||||||
|
$stmt = $pdo->prepare("
|
||||||
|
SELECT part_id, analysis_recordkey, analysis_name, analysis_method
|
||||||
|
FROM identification_parts_analyses
|
||||||
|
WHERE iddatadb = :iddatadb
|
||||||
|
ORDER BY part_id, id
|
||||||
|
");
|
||||||
|
$stmt->execute(['iddatadb' => $iddatadb]);
|
||||||
|
$analysesRows = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
||||||
|
|
||||||
|
$partIdToIndex = [];
|
||||||
|
foreach ($parts as $idx => $part) {
|
||||||
|
$partIdToIndex[(int)$part['part_id']] = $idx;
|
||||||
|
}
|
||||||
|
|
||||||
|
$totalAnalyses = count($analysesRows);
|
||||||
|
$addedAnalyses = 0;
|
||||||
|
$failedAnalyses = [];
|
||||||
|
$logContentStep63Analisi = "Analyses for iddatadb={$iddatadb}: total={$totalAnalyses}\n\n";
|
||||||
|
|
||||||
|
foreach ($analysesRows as $a) {
|
||||||
|
$partId = (int)$a['part_id'];
|
||||||
|
$recordKey = trim((string)($a['analysis_recordkey'] ?? ''));
|
||||||
|
$idx = $partIdToIndex[$partId] ?? null;
|
||||||
|
|
||||||
|
if ($idx === null || !isset($campioni[$idx]) || $recordKey === '') {
|
||||||
|
$logContentStep63Analisi .= "SKIP (no campione for part_id={$partId} / empty recordkey): '{$recordKey}'\n";
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
$campioneId = (int)($campioni[$idx]['IdCampione'] ?? 0);
|
||||||
|
if ($campioneId <= 0) {
|
||||||
|
$logContentStep63Analisi .= "SKIP (invalid IdCampione for part_id={$partId}): '{$recordKey}'\n";
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
$payload = ['RecordKey' => $recordKey];
|
||||||
|
$jsonPayload = json_encode($payload, JSON_UNESCAPED_SLASHES);
|
||||||
|
|
||||||
|
$logContentStep63Analisi .= "curl --location --request POST '{$apiBaseUrl}Campione({$campioneId})/AddAnalisi' \\\n" .
|
||||||
|
"--header 'Content-Type: application/json' \\\n" .
|
||||||
|
"--header 'Authorization: Bearer ••••••' \\\n" .
|
||||||
|
"--data '{$jsonPayload}'\n";
|
||||||
|
|
||||||
|
try {
|
||||||
|
$result = $api->post("Campione({$campioneId})/AddAnalisi", $payload);
|
||||||
|
$logContentStep63Analisi .= "OK (part_id={$partId}, campione={$campioneId}): " .
|
||||||
|
($a['analysis_name'] ?? '') . "\n---\n";
|
||||||
|
$addedAnalyses++;
|
||||||
|
} catch (Exception $e) {
|
||||||
|
$errMsg = $e->getMessage();
|
||||||
|
$logContentStep63Analisi .= "FAIL: {$errMsg}\n---\n";
|
||||||
|
$failedAnalyses[] = [
|
||||||
|
'part_id' => $partId,
|
||||||
|
'campione_id' => $campioneId,
|
||||||
|
'analysis_recordkey' => $recordKey,
|
||||||
|
'analysis_name' => $a['analysis_name'] ?? '',
|
||||||
|
'error' => $errMsg,
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$logFileStep63Analisi = $logDir . "commessa_{$commessaId}_analyses_step63_" . time() . ".txt";
|
||||||
|
$writeLog($logFileStep63Analisi, $logContentStep63Analisi, "STEP 6.3 - AddAnalisi (commessa={$commessaId})");
|
||||||
|
|
||||||
// 🔹 STEP 7: Update Custom Fields for CommessaWeb
|
// 🔹 STEP 7: Update Custom Fields for CommessaWeb
|
||||||
if (!empty($fieldValues)) {
|
if (!empty($fieldValues)) {
|
||||||
// GET con espansione per CustomField
|
// GET con espansione per CustomField
|
||||||
@@ -563,11 +628,15 @@ try {
|
|||||||
"totalCampioni" => count($campioni),
|
"totalCampioni" => count($campioni),
|
||||||
"totalCustomFields" => count($commessaAfterPatch["CommesseCustomFields"] ?? []),
|
"totalCustomFields" => count($commessaAfterPatch["CommesseCustomFields"] ?? []),
|
||||||
"totalPhotos" => count($photos),
|
"totalPhotos" => count($photos),
|
||||||
|
"totalAnalyses" => $totalAnalyses,
|
||||||
|
"addedAnalyses" => $addedAnalyses,
|
||||||
|
"failedAnalyses" => $failedAnalyses,
|
||||||
"message" => "Export successful",
|
"message" => "Export successful",
|
||||||
"logFiles" => [
|
"logFiles" => [
|
||||||
"step5_create" => $logFileStep5,
|
"step5_create" => $logFileStep5,
|
||||||
"step5_2_photos" => $logFilePhotos,
|
"step5_2_photos" => $logFilePhotos,
|
||||||
"step6_campioni" => $logFileStep6,
|
"step6_campioni" => $logFileStep6,
|
||||||
|
"step63_analyses" => $logFileStep63Analisi,
|
||||||
"step7_patch" => $logFileStep7 ?? null,
|
"step7_patch" => $logFileStep7 ?? null,
|
||||||
"step9_1_importa" => $logFileStep91,
|
"step9_1_importa" => $logFileStep91,
|
||||||
"step10_get" => $logFileStep10
|
"step10_get" => $logFileStep10
|
||||||
@@ -583,6 +652,7 @@ try {
|
|||||||
"step5_create" => $logFileStep5 ?? null,
|
"step5_create" => $logFileStep5 ?? null,
|
||||||
"step5_2_photos" => $logFilePhotos ?? null,
|
"step5_2_photos" => $logFilePhotos ?? null,
|
||||||
"step6_campioni" => $logFileStep6 ?? null,
|
"step6_campioni" => $logFileStep6 ?? null,
|
||||||
|
"step63_analyses" => $logFileStep63Analisi ?? null,
|
||||||
"step7_patch" => $logFileStep7 ?? null,
|
"step7_patch" => $logFileStep7 ?? null,
|
||||||
"step9_1_importa" => $logFileStep91 ?? null,
|
"step9_1_importa" => $logFileStep91 ?? null,
|
||||||
"step10_get" => $logFileStep10 ?? null
|
"step10_get" => $logFileStep10 ?? null
|
||||||
|
|||||||
+570
-270
File diff suppressed because it is too large
Load Diff
@@ -40,6 +40,22 @@ error_log("Loaded template: " . print_r($template, true));
|
|||||||
<?php include('cssinclude.php'); ?>
|
<?php include('cssinclude.php'); ?>
|
||||||
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet">
|
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet">
|
||||||
<style>
|
<style>
|
||||||
|
.top-scrollbar {
|
||||||
|
overflow-x: auto;
|
||||||
|
overflow-y: hidden;
|
||||||
|
width: 100%;
|
||||||
|
height: 18px;
|
||||||
|
margin-bottom: 8px;
|
||||||
|
border: 1px solid #dee2e6;
|
||||||
|
border-radius: 0.25rem;
|
||||||
|
background: #f8f9fa;
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.top-scrollbar-inner {
|
||||||
|
height: 1px;
|
||||||
|
}
|
||||||
|
|
||||||
.table-container {
|
.table-container {
|
||||||
overflow-x: auto;
|
overflow-x: auto;
|
||||||
max-width: 100%;
|
max-width: 100%;
|
||||||
@@ -324,8 +340,12 @@ error_log("Loaded template: " . print_r($template, true));
|
|||||||
<button type="submit" class="btn btn-primary" id="proceedButtonTop" disabled>Prosegui</button>
|
<button type="submit" class="btn btn-primary" id="proceedButtonTop" disabled>Prosegui</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="table-container">
|
<div class="top-scrollbar" id="topTableScrollbar">
|
||||||
<table class="table table-striped table-bordered">
|
<div class="top-scrollbar-inner" id="topTableScrollbarInner"></div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="table-container" id="mainTableContainer">
|
||||||
|
<table class="table table-striped table-bordered" id="importPreviewTable">
|
||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
<th><input type="checkbox" id="selectAll"> Seleziona</th>
|
<th><input type="checkbox" id="selectAll"> Seleziona</th>
|
||||||
@@ -363,6 +383,47 @@ error_log("Loaded template: " . print_r($template, true));
|
|||||||
`;
|
`;
|
||||||
tableContainer.innerHTML = html;
|
tableContainer.innerHTML = html;
|
||||||
|
|
||||||
|
const topTableScrollbar = document.getElementById('topTableScrollbar');
|
||||||
|
const topTableScrollbarInner = document.getElementById('topTableScrollbarInner');
|
||||||
|
const mainTableContainer = document.getElementById('mainTableContainer');
|
||||||
|
const importPreviewTable = document.getElementById('importPreviewTable');
|
||||||
|
|
||||||
|
function updateTopTableScrollbar() {
|
||||||
|
if (!topTableScrollbar || !topTableScrollbarInner || !mainTableContainer || !importPreviewTable) return;
|
||||||
|
|
||||||
|
topTableScrollbarInner.style.width = importPreviewTable.scrollWidth + 'px';
|
||||||
|
|
||||||
|
if (mainTableContainer.scrollWidth > mainTableContainer.clientWidth) {
|
||||||
|
topTableScrollbar.style.display = 'block';
|
||||||
|
} else {
|
||||||
|
topTableScrollbar.style.display = 'none';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let syncingTop = false;
|
||||||
|
let syncingBottom = false;
|
||||||
|
|
||||||
|
if (topTableScrollbar && mainTableContainer) {
|
||||||
|
topTableScrollbar.addEventListener('scroll', function() {
|
||||||
|
if (syncingBottom) return;
|
||||||
|
syncingTop = true;
|
||||||
|
mainTableContainer.scrollLeft = topTableScrollbar.scrollLeft;
|
||||||
|
syncingTop = false;
|
||||||
|
});
|
||||||
|
|
||||||
|
mainTableContainer.addEventListener('scroll', function() {
|
||||||
|
if (syncingTop) return;
|
||||||
|
syncingBottom = true;
|
||||||
|
topTableScrollbar.scrollLeft = mainTableContainer.scrollLeft;
|
||||||
|
syncingBottom = false;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
updateTopTableScrollbar();
|
||||||
|
setTimeout(updateTopTableScrollbar, 100);
|
||||||
|
setTimeout(updateTopTableScrollbar, 300);
|
||||||
|
window.addEventListener('resize', updateTopTableScrollbar);
|
||||||
|
|
||||||
const proceedButtonTop = document.getElementById('proceedButtonTop');
|
const proceedButtonTop = document.getElementById('proceedButtonTop');
|
||||||
const proceedButtonBottom = document.getElementById('proceedButtonBottom');
|
const proceedButtonBottom = document.getElementById('proceedButtonBottom');
|
||||||
const selectAllCheckbox = document.getElementById('selectAll');
|
const selectAllCheckbox = document.getElementById('selectAll');
|
||||||
|
|||||||
+138
-17
@@ -202,13 +202,13 @@ foreach ($importedData as $index => $row) {
|
|||||||
'status' => $row['status'] ?? 'i',
|
'status' => $row['status'] ?? 'i',
|
||||||
'idclient' => $row['idclient'] ?? $default_idclient,
|
'idclient' => $row['idclient'] ?? $default_idclient,
|
||||||
'cliente_fornitore_id' => $row['cliente_fornitore_id'] ?? null,
|
'cliente_fornitore_id' => $row['cliente_fornitore_id'] ?? null,
|
||||||
|
'tested_component' => $row['tested_component'] ?? '',
|
||||||
'commessaweb' => $row['commessaweb'] ?? null,
|
'commessaweb' => $row['commessaweb'] ?? null,
|
||||||
'user_name' => $row['user_name'] ?? '',
|
'user_name' => $row['user_name'] ?? '',
|
||||||
'importreferencecode' => $row['importreferencecode'] ?? '',
|
'importreferencecode' => $row['importreferencecode'] ?? '',
|
||||||
'filename_import' => $row['filename_import'] ?? '',
|
'filename_import' => $row['filename_import'] ?? '',
|
||||||
'importdate' => $row['importdate'] ?? '',
|
'importdate' => $row['importdate'] ?? '',
|
||||||
];
|
];
|
||||||
|
|
||||||
// Fixed fields
|
// Fixed fields
|
||||||
$rowObj['fixedFields'] = [];
|
$rowObj['fixedFields'] = [];
|
||||||
foreach ($fixedFields as $f) {
|
foreach ($fixedFields as $f) {
|
||||||
@@ -267,7 +267,12 @@ $gridColumns[] = ['type' => 'cliente_fornitore_id', 'key' => 'cliente_fornitore_
|
|||||||
|
|
||||||
// 5. Auto fields
|
// 5. Auto fields
|
||||||
foreach ($allMappings as $mapping) {
|
foreach ($allMappings as $mapping) {
|
||||||
if (!$mapping['is_manual'] && $mapping['main_field'] != 1 && $mapping['is_visible_import'] == 1) {
|
if (
|
||||||
|
!$mapping['is_manual']
|
||||||
|
&& $mapping['main_field'] != 1
|
||||||
|
&& $mapping['is_visible_import'] == 1
|
||||||
|
&& trim((string)$mapping['field_label']) !== 'Tested Component:'
|
||||||
|
) {
|
||||||
$gridColumns[] = [
|
$gridColumns[] = [
|
||||||
'type' => 'detail',
|
'type' => 'detail',
|
||||||
'key' => (string)$mapping['id'],
|
'key' => (string)$mapping['id'],
|
||||||
@@ -284,7 +289,12 @@ foreach ($allMappings as $mapping) {
|
|||||||
|
|
||||||
// 6. Manual fields
|
// 6. Manual fields
|
||||||
foreach ($allMappings as $mapping) {
|
foreach ($allMappings as $mapping) {
|
||||||
if ($mapping['is_manual'] && $mapping['main_field'] != 1 && $mapping['is_visible_import'] == 1) {
|
if (
|
||||||
|
$mapping['is_manual']
|
||||||
|
&& $mapping['main_field'] != 1
|
||||||
|
&& $mapping['is_visible_import'] == 1
|
||||||
|
&& trim((string)$mapping['field_label']) !== 'Tested Component:'
|
||||||
|
) {
|
||||||
$gridColumns[] = [
|
$gridColumns[] = [
|
||||||
'type' => 'detail',
|
'type' => 'detail',
|
||||||
'key' => (string)$mapping['id'],
|
'key' => (string)$mapping['id'],
|
||||||
@@ -342,8 +352,8 @@ $gridMeta = [
|
|||||||
|
|
||||||
?>
|
?>
|
||||||
<script>
|
<script>
|
||||||
window.gridData = <?= json_encode($gridDataArray, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES) ?>;
|
window.gridData = <?= json_encode($gridDataArray, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES) ?>;
|
||||||
window.gridMeta = <?= json_encode($gridMeta, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES) ?>;
|
window.gridMeta = <?= json_encode($gridMeta, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES) ?>;
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<!doctype html>
|
<!doctype html>
|
||||||
@@ -463,6 +473,21 @@ window.gridMeta = <?= json_encode($gridMeta, JSON_UNESCAPED_UNICODE | JSON_UNESC
|
|||||||
position: relative;
|
position: relative;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.top-scrollbar {
|
||||||
|
overflow-x: auto;
|
||||||
|
overflow-y: hidden;
|
||||||
|
width: 100%;
|
||||||
|
height: 18px;
|
||||||
|
margin-bottom: 8px;
|
||||||
|
border: 1px solid #dee2e6;
|
||||||
|
border-radius: 0.25rem;
|
||||||
|
background: #f8f9fa;
|
||||||
|
}
|
||||||
|
|
||||||
|
.top-scrollbar-inner {
|
||||||
|
height: 1px;
|
||||||
|
}
|
||||||
|
|
||||||
.grid-row {
|
.grid-row {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
@@ -797,7 +822,7 @@ window.gridMeta = <?= json_encode($gridMeta, JSON_UNESCAPED_UNICODE | JSON_UNESC
|
|||||||
background-color: rgba(0, 0, 0, 0.5);
|
background-color: rgba(0, 0, 0, 0.5);
|
||||||
}
|
}
|
||||||
|
|
||||||
#photosModal > .modal-content {
|
#photosModal>.modal-content {
|
||||||
background-color: #fff;
|
background-color: #fff;
|
||||||
margin: 5% auto;
|
margin: 5% auto;
|
||||||
padding: 20px;
|
padding: 20px;
|
||||||
@@ -892,9 +917,17 @@ window.gridMeta = <?= json_encode($gridMeta, JSON_UNESCAPED_UNICODE | JSON_UNESC
|
|||||||
}
|
}
|
||||||
|
|
||||||
@keyframes new-row-pulse {
|
@keyframes new-row-pulse {
|
||||||
0%, 100% { background-color: transparent; }
|
|
||||||
50% { background-color: #cce5ff; }
|
0%,
|
||||||
|
100% {
|
||||||
|
background-color: transparent;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
50% {
|
||||||
|
background-color: #cce5ff;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
.row-just-created {
|
.row-just-created {
|
||||||
animation: new-row-pulse 1s ease-in-out 3;
|
animation: new-row-pulse 1s ease-in-out 3;
|
||||||
}
|
}
|
||||||
@@ -1029,21 +1062,25 @@ window.gridMeta = <?= json_encode($gridMeta, JSON_UNESCAPED_UNICODE | JSON_UNESC
|
|||||||
font-size: 13px;
|
font-size: 13px;
|
||||||
color: #495057;
|
color: #495057;
|
||||||
}
|
}
|
||||||
|
|
||||||
.pager-rows-per-page {
|
.pager-rows-per-page {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
gap: 8px;
|
gap: 8px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.pager-label {
|
.pager-label {
|
||||||
font-weight: 500;
|
font-weight: 500;
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
}
|
}
|
||||||
|
|
||||||
.pager-limit-group {
|
.pager-limit-group {
|
||||||
display: inline-flex;
|
display: inline-flex;
|
||||||
border: 1px solid #ced4da;
|
border: 1px solid #ced4da;
|
||||||
border-radius: 4px;
|
border-radius: 4px;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
}
|
}
|
||||||
|
|
||||||
.pager-limit-btn {
|
.pager-limit-btn {
|
||||||
padding: 3px 10px;
|
padding: 3px 10px;
|
||||||
text-decoration: none;
|
text-decoration: none;
|
||||||
@@ -1052,24 +1089,41 @@ window.gridMeta = <?= json_encode($gridMeta, JSON_UNESCAPED_UNICODE | JSON_UNESC
|
|||||||
transition: background .15s, color .15s;
|
transition: background .15s, color .15s;
|
||||||
font-weight: 500;
|
font-weight: 500;
|
||||||
}
|
}
|
||||||
.pager-limit-btn:last-child { border-right: none; }
|
|
||||||
.pager-limit-btn:hover { background: #e9ecef; text-decoration: none; color: #212529; }
|
.pager-limit-btn:last-child {
|
||||||
|
border-right: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pager-limit-btn:hover {
|
||||||
|
background: #e9ecef;
|
||||||
|
text-decoration: none;
|
||||||
|
color: #212529;
|
||||||
|
}
|
||||||
|
|
||||||
.pager-limit-btn.active {
|
.pager-limit-btn.active {
|
||||||
background: #0d6efd;
|
background: #0d6efd;
|
||||||
color: #fff;
|
color: #fff;
|
||||||
}
|
}
|
||||||
.pager-limit-btn.active:hover { background: #0b5ed7; color: #fff; }
|
|
||||||
|
.pager-limit-btn.active:hover {
|
||||||
|
background: #0b5ed7;
|
||||||
|
color: #fff;
|
||||||
|
}
|
||||||
|
|
||||||
.pager-nav {
|
.pager-nav {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
gap: 4px;
|
gap: 4px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.pager-info {
|
.pager-info {
|
||||||
margin-right: 8px;
|
margin-right: 8px;
|
||||||
font-weight: 500;
|
font-weight: 500;
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
}
|
}
|
||||||
.pager-btn, .pager-num {
|
|
||||||
|
.pager-btn,
|
||||||
|
.pager-num {
|
||||||
display: inline-flex;
|
display: inline-flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
@@ -1083,16 +1137,25 @@ window.gridMeta = <?= json_encode($gridMeta, JSON_UNESCAPED_UNICODE | JSON_UNESC
|
|||||||
font-weight: 500;
|
font-weight: 500;
|
||||||
transition: background .15s, color .15s, border-color .15s;
|
transition: background .15s, color .15s, border-color .15s;
|
||||||
}
|
}
|
||||||
.pager-btn:hover, .pager-num:hover { background: #e9ecef; text-decoration: none; color: #212529; }
|
|
||||||
|
.pager-btn:hover,
|
||||||
|
.pager-num:hover {
|
||||||
|
background: #e9ecef;
|
||||||
|
text-decoration: none;
|
||||||
|
color: #212529;
|
||||||
|
}
|
||||||
|
|
||||||
.pager-num.active {
|
.pager-num.active {
|
||||||
background: #0d6efd;
|
background: #0d6efd;
|
||||||
color: #fff;
|
color: #fff;
|
||||||
border-color: #0d6efd;
|
border-color: #0d6efd;
|
||||||
}
|
}
|
||||||
|
|
||||||
.pager-btn.disabled {
|
.pager-btn.disabled {
|
||||||
pointer-events: none;
|
pointer-events: none;
|
||||||
opacity: .4;
|
opacity: .4;
|
||||||
}
|
}
|
||||||
|
|
||||||
.pager-dots {
|
.pager-dots {
|
||||||
padding: 0 2px;
|
padding: 0 2px;
|
||||||
color: #adb5bd;
|
color: #adb5bd;
|
||||||
@@ -1179,9 +1242,9 @@ window.gridMeta = <?= json_encode($gridMeta, JSON_UNESCAPED_UNICODE | JSON_UNESC
|
|||||||
<i class="fas fa-cogs"></i> Actions
|
<i class="fas fa-cogs"></i> Actions
|
||||||
</button>
|
</button>
|
||||||
<ul class="dropdown-menu dropdown-menu-end">
|
<ul class="dropdown-menu dropdown-menu-end">
|
||||||
<?php if ((Auth::user()->hasRole('Admin'))) : ?>
|
|
||||||
<li><a class="dropdown-item export-all-lims-btn" href="#"><i class="fas fa-upload" style="color: #eb0b0b;"></i>Export All</a></li>
|
<li><a class="dropdown-item export-all-lims-btn" href="#"><i class="fas fa-upload" style="color: #eb0b0b;"></i>Export All</a></li>
|
||||||
<?php endif; ?>
|
|
||||||
<li><a class="dropdown-item save-all-btn" href="#"><i class="fas fa-save" style="color: #28a745;"></i>Save All</a></li>
|
<li><a class="dropdown-item save-all-btn" href="#"><i class="fas fa-save" style="color: #28a745;"></i>Save All</a></li>
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
@@ -1201,13 +1264,18 @@ window.gridMeta = <?= json_encode($gridMeta, JSON_UNESCAPED_UNICODE | JSON_UNESC
|
|||||||
</div>
|
</div>
|
||||||
<div class="card-body">
|
<div class="card-body">
|
||||||
<form id="editForm">
|
<form id="editForm">
|
||||||
<div class="grid-container">
|
<div class="top-scrollbar" id="topScrollbar">
|
||||||
|
<div class="top-scrollbar-inner" id="topScrollbarInner"></div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="grid-container" id="gridContainer">
|
||||||
<div class="grid-top" id="gridTopContainer"></div>
|
<div class="grid-top" id="gridTopContainer"></div>
|
||||||
<div class="grid-row" id="gridHeaderContainer" style="font-weight:600;"></div>
|
<div class="grid-row" id="gridHeaderContainer" style="font-weight:600;"></div>
|
||||||
<div id="gridRowContainer"></div>
|
<div id="gridRowContainer"></div>
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
<div id="partsModalContainer"></div>
|
<div id="partsModalContainer"></div>
|
||||||
|
<div id="analysisModalContainer"></div>
|
||||||
<div id="annotationsModalContainer"></div>
|
<div id="annotationsModalContainer"></div>
|
||||||
<?php include 'photos_functions.php'; ?>
|
<?php include 'photos_functions.php'; ?>
|
||||||
</div>
|
</div>
|
||||||
@@ -1218,7 +1286,12 @@ window.gridMeta = <?= json_encode($gridMeta, JSON_UNESCAPED_UNICODE | JSON_UNESC
|
|||||||
<a href="javaScript:;" class="back-to-top"><i class='bx bxs-up-arrow-alt'></i></a>
|
<a href="javaScript:;" class="back-to-top"><i class='bx bxs-up-arrow-alt'></i></a>
|
||||||
<?php include('include/footer.php'); ?>
|
<?php include('include/footer.php'); ?>
|
||||||
</div>
|
</div>
|
||||||
<style>.btn i { margin-top: 0 !important; margin-bottom: 0 !important; }</style>
|
<style>
|
||||||
|
.btn i {
|
||||||
|
margin-top: 0 !important;
|
||||||
|
margin-bottom: 0 !important;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
<?php include('jsinclude.php'); ?>
|
<?php include('jsinclude.php'); ?>
|
||||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/fabric.js/5.3.1/fabric.min.js"></script>
|
<script src="https://cdnjs.cloudflare.com/ajax/libs/fabric.js/5.3.1/fabric.min.js"></script>
|
||||||
<script src="https://cdn.jsdelivr.net/npm/flatpickr@4.6.13/dist/flatpickr.min.js"></script>
|
<script src="https://cdn.jsdelivr.net/npm/flatpickr@4.6.13/dist/flatpickr.min.js"></script>
|
||||||
@@ -1231,6 +1304,54 @@ window.gridMeta = <?= json_encode($gridMeta, JSON_UNESCAPED_UNICODE | JSON_UNESC
|
|||||||
<script src="photos.js"></script>
|
<script src="photos.js"></script>
|
||||||
<script src="annotationsModal.js"></script>
|
<script src="annotationsModal.js"></script>
|
||||||
<script src="partsTable.js"></script>
|
<script src="partsTable.js"></script>
|
||||||
|
<script src="analysisModal.js"></script>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
document.addEventListener('DOMContentLoaded', function() {
|
||||||
|
const topScrollbar = document.getElementById('topScrollbar');
|
||||||
|
const topScrollbarInner = document.getElementById('topScrollbarInner');
|
||||||
|
const gridContainer = document.getElementById('gridContainer');
|
||||||
|
|
||||||
|
if (!topScrollbar || !topScrollbarInner || !gridContainer) return;
|
||||||
|
|
||||||
|
let syncingFromTop = false;
|
||||||
|
let syncingFromGrid = false;
|
||||||
|
|
||||||
|
function updateTopScrollbarWidth() {
|
||||||
|
topScrollbarInner.style.width = gridContainer.scrollWidth + 'px';
|
||||||
|
|
||||||
|
// Mostra la barra solo se serve davvero
|
||||||
|
if (gridContainer.scrollWidth > gridContainer.clientWidth) {
|
||||||
|
topScrollbar.style.display = 'block';
|
||||||
|
} else {
|
||||||
|
topScrollbar.style.display = 'none';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
topScrollbar.addEventListener('scroll', function() {
|
||||||
|
if (syncingFromGrid) return;
|
||||||
|
syncingFromTop = true;
|
||||||
|
gridContainer.scrollLeft = topScrollbar.scrollLeft;
|
||||||
|
syncingFromTop = false;
|
||||||
|
});
|
||||||
|
|
||||||
|
gridContainer.addEventListener('scroll', function() {
|
||||||
|
if (syncingFromTop) return;
|
||||||
|
syncingFromGrid = true;
|
||||||
|
topScrollbar.scrollLeft = gridContainer.scrollLeft;
|
||||||
|
syncingFromGrid = false;
|
||||||
|
});
|
||||||
|
|
||||||
|
updateTopScrollbarWidth();
|
||||||
|
|
||||||
|
window.addEventListener('resize', updateTopScrollbarWidth);
|
||||||
|
|
||||||
|
// Ritarda un attimo per sicurezza, visto che la griglia viene renderizzata via JS
|
||||||
|
setTimeout(updateTopScrollbarWidth, 200);
|
||||||
|
setTimeout(updateTopScrollbarWidth, 600);
|
||||||
|
setTimeout(updateTopScrollbarWidth, 1200);
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
<div class="modal fade" id="exportConfirmModal" tabindex="-1" aria-labelledby="exportConfirmModalLabel" aria-hidden="true">
|
<div class="modal fade" id="exportConfirmModal" tabindex="-1" aria-labelledby="exportConfirmModalLabel" aria-hidden="true">
|
||||||
<div class="modal-dialog">
|
<div class="modal-dialog">
|
||||||
|
|||||||
@@ -26,7 +26,9 @@
|
|||||||
<button type="button" class="btn btn-info btn-sm" id="renumberPartsBtn" style="padding: 0.1rem 0.5rem; font-size: 0.8rem;">
|
<button type="button" class="btn btn-info btn-sm" id="renumberPartsBtn" style="padding: 0.1rem 0.5rem; font-size: 0.8rem;">
|
||||||
Rinumera Parti
|
Rinumera Parti
|
||||||
</button>
|
</button>
|
||||||
|
<button type="button" class="btn btn-primary btn-sm" id="clonePartsBtn" style="padding: 0.1rem 0.5rem; font-size: 0.8rem;">
|
||||||
|
<i class="fas fa-clone"></i> Clona Parti
|
||||||
|
</button>
|
||||||
<button type="button" class="btn btn-secondary btn-sm ms-2" id="toggleVoiceBtn" style="padding: 0.1rem 0.5rem; font-size: 0.8rem;">
|
<button type="button" class="btn btn-secondary btn-sm ms-2" id="toggleVoiceBtn" style="padding: 0.1rem 0.5rem; font-size: 0.8rem;">
|
||||||
<i class="fas fa-microphone"></i> Voce
|
<i class="fas fa-microphone"></i> Voce
|
||||||
</button>
|
</button>
|
||||||
|
|||||||
@@ -2,93 +2,111 @@
|
|||||||
* modals_gridData.js — Photos, Parts, Tested Component handlers for gridData pages
|
* modals_gridData.js — Photos, Parts, Tested Component handlers for gridData pages
|
||||||
*/
|
*/
|
||||||
(function () {
|
(function () {
|
||||||
'use strict';
|
"use strict";
|
||||||
|
|
||||||
// ── Photos — use photos.js loadPopupContent (exported to window) ──
|
// ── Photos — use photos.js loadPopupContent (exported to window) ──
|
||||||
$(document).on('click', '.photos-btn', function () {
|
$(document).on("click", ".photos-btn", function () {
|
||||||
const iddatadb = $(this).data('iddatadb') || null;
|
const iddatadb = $(this).data("iddatadb") || null;
|
||||||
const idquotations = $(this).data('idquotations') || null;
|
const idquotations = $(this).data("idquotations") || null;
|
||||||
const modal = document.getElementById('photosModal');
|
const modal = document.getElementById("photosModal");
|
||||||
if (!modal) return;
|
if (!modal) return;
|
||||||
|
|
||||||
modal.style.display = 'block';
|
modal.style.display = "block";
|
||||||
|
|
||||||
if (typeof window.loadPopupContent === 'function') {
|
if (typeof window.loadPopupContent === "function") {
|
||||||
window.loadPopupContent(iddatadb, idquotations);
|
window.loadPopupContent(iddatadb, idquotations);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// Close photos modal
|
// Close photos modal
|
||||||
$(document).on('click', '.close-btn', function () {
|
$(document).on("click", ".close-btn", function () {
|
||||||
const modal = document.getElementById('photosModal');
|
const modal = document.getElementById("photosModal");
|
||||||
if (modal) modal.style.display = 'none';
|
if (modal) modal.style.display = "none";
|
||||||
});
|
});
|
||||||
|
|
||||||
// Close on backdrop click
|
// Close on backdrop click
|
||||||
$(document).on('click', '#photosModal', function (e) {
|
$(document).on("click", "#photosModal", function (e) {
|
||||||
if (e.target === this) {
|
if (e.target === this) {
|
||||||
this.style.display = 'none';
|
this.style.display = "none";
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// ── Parts (matching import_edit2.php behavior) ─────────────────────
|
// ── Parts (matching import_edit2.php behavior) ─────────────────────
|
||||||
$(document).on('click', '.parts-btn', function () {
|
$(document).on("click", ".parts-btn", function () {
|
||||||
const iddatadb = $(this).data('iddatadb') || null;
|
const iddatadb = $(this).data("iddatadb") || null;
|
||||||
const idquotations = $(this).data('idquotations') || null;
|
const idquotations = $(this).data("idquotations") || null;
|
||||||
|
|
||||||
$.ajax({
|
$.ajax({
|
||||||
url: 'modal_partsTable.php',
|
url: "modal_partsTable.php",
|
||||||
method: 'GET',
|
method: "GET",
|
||||||
data: { iddatadb: iddatadb },
|
data: { iddatadb: iddatadb },
|
||||||
success: function (response) {
|
success: function (response) {
|
||||||
$('#partsModalContainer').html(response);
|
$("#partsModalContainer").html(response);
|
||||||
const modalElement = document.getElementById('partsModal');
|
const modalElement = document.getElementById("partsModal");
|
||||||
if (!modalElement) return;
|
if (!modalElement) return;
|
||||||
|
|
||||||
$("#trfHeader").text(iddatadb || idquotations || '');
|
$("#trfHeader").text(iddatadb || idquotations || "");
|
||||||
$("#partsModal").data("iddatadb", iddatadb).data("idquotations", idquotations);
|
|
||||||
|
const visibleIddatadbList = Array.isArray(window.gridData)
|
||||||
|
? window.gridData
|
||||||
|
.map((r) => parseInt(r.iddatadb, 10))
|
||||||
|
.filter((v) => !isNaN(v) && v > 0)
|
||||||
|
: [];
|
||||||
|
|
||||||
|
$("#partsModal")
|
||||||
|
.data("iddatadb", iddatadb)
|
||||||
|
.data("idquotations", idquotations)
|
||||||
|
.data("visible-iddatadb-list", visibleIddatadbList);
|
||||||
|
|
||||||
let modal = bootstrap.Modal.getInstance(modalElement);
|
let modal = bootstrap.Modal.getInstance(modalElement);
|
||||||
if (!modal) modal = new bootstrap.Modal(modalElement, { backdrop: true, keyboard: true, focus: true });
|
if (!modal)
|
||||||
|
modal = new bootstrap.Modal(modalElement, {
|
||||||
|
backdrop: true,
|
||||||
|
keyboard: true,
|
||||||
|
focus: true,
|
||||||
|
});
|
||||||
modal.show();
|
modal.show();
|
||||||
|
|
||||||
if (typeof window.loadParts === 'function') {
|
if (typeof window.loadParts === "function") {
|
||||||
window.loadParts(iddatadb, idquotations);
|
window.loadParts(iddatadb, idquotations);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
error: function (xhr, status, error) {
|
error: function (xhr, status, error) {
|
||||||
console.error('Error loading parts:', error);
|
console.error("Error loading parts:", error);
|
||||||
}
|
},
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
$(document).on('hidden.bs.modal', '#partsModal', function () {
|
$(document).on("hidden.bs.modal", "#partsModal", function () {
|
||||||
const modalElement = document.getElementById('partsModal');
|
const modalElement = document.getElementById("partsModal");
|
||||||
if (modalElement) {
|
if (modalElement) {
|
||||||
const modal = bootstrap.Modal.getInstance(modalElement);
|
const modal = bootstrap.Modal.getInstance(modalElement);
|
||||||
if (modal) modal.dispose();
|
if (modal) modal.dispose();
|
||||||
}
|
}
|
||||||
$('#partsModalContainer').empty();
|
$("#partsModalContainer").empty();
|
||||||
$('.modal-backdrop').remove();
|
$(".modal-backdrop").remove();
|
||||||
$('body').removeClass('modal-open').css('padding-right', '');
|
$("body").removeClass("modal-open").css("padding-right", "");
|
||||||
});
|
});
|
||||||
|
|
||||||
// ── Tested Component quick add ───────────────────────────────────────
|
// ── Tested Component quick add ───────────────────────────────────────
|
||||||
$(document).on('click', '.add-part-btn', async function () {
|
$(document).on("click", ".add-part-btn", async function () {
|
||||||
const iddatadb = $(this).data('iddatadb') || null;
|
const iddatadb = $(this).data("iddatadb") || null;
|
||||||
const rowIndex = parseInt($(this).data('row'));
|
const rowIndex = parseInt($(this).data("row"));
|
||||||
const row = window.gridData?.[rowIndex];
|
const row = window.gridData?.[rowIndex];
|
||||||
const id = iddatadb || (row ? row.iddatadb : null);
|
const id = iddatadb || (row ? row.iddatadb : null);
|
||||||
if (!id) return;
|
if (!id) return;
|
||||||
|
|
||||||
const $cell = $(this).closest('.grid-cell, div');
|
const $cell = $(this).closest(".grid-cell, div");
|
||||||
const $input = $cell.find('input');
|
const $input = $cell.find("input");
|
||||||
const raw = ($input.val() || '').trim();
|
const raw = ($input.val() || "").trim();
|
||||||
const parts = raw.split('|').map(s => s.trim()).filter(s => s.length > 0);
|
const parts = raw
|
||||||
|
.split("|")
|
||||||
|
.map((s) => s.trim())
|
||||||
|
.filter((s) => s.length > 0);
|
||||||
const uniqueParts = [...new Set(parts)];
|
const uniqueParts = [...new Set(parts)];
|
||||||
|
|
||||||
if (!uniqueParts.length) {
|
if (!uniqueParts.length) {
|
||||||
alert('Insert a description first.');
|
alert("Insert a description first.");
|
||||||
$input.focus();
|
$input.focus();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -96,14 +114,23 @@
|
|||||||
try {
|
try {
|
||||||
for (const p of uniqueParts) {
|
for (const p of uniqueParts) {
|
||||||
const formData = new FormData();
|
const formData = new FormData();
|
||||||
formData.append('iddatadb', id);
|
formData.append("iddatadb", id);
|
||||||
formData.append('part_description', p);
|
formData.append("part_description", p);
|
||||||
await fetch('add_part_quick.php', { method: 'POST', body: formData });
|
await fetch("add_part_quick.php", {
|
||||||
|
method: "POST",
|
||||||
|
body: formData,
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (row) {
|
||||||
|
row.tested_component = raw;
|
||||||
|
row._dirty = true;
|
||||||
|
}
|
||||||
|
|
||||||
alert(`Added ${uniqueParts.length} part(s).`);
|
alert(`Added ${uniqueParts.length} part(s).`);
|
||||||
$input.val('');
|
$input.val(raw);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
alert('Error: ' + e.message);
|
alert("Error: " + e.message);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -111,44 +138,47 @@
|
|||||||
let deleteIddatadb = null;
|
let deleteIddatadb = null;
|
||||||
let deleteRowIndex = null;
|
let deleteRowIndex = null;
|
||||||
|
|
||||||
$(document).on('click', '.delete-btn', function () {
|
$(document).on("click", ".delete-btn", function () {
|
||||||
deleteIddatadb = $(this).data('iddatadb');
|
deleteIddatadb = $(this).data("iddatadb");
|
||||||
deleteRowIndex = parseInt($(this).data('row'));
|
deleteRowIndex = parseInt($(this).data("row"));
|
||||||
const modalEl = document.getElementById('deleteConfirmModal');
|
const modalEl = document.getElementById("deleteConfirmModal");
|
||||||
if (modalEl) {
|
if (modalEl) {
|
||||||
document.getElementById('deleteIddatadbText').textContent = deleteIddatadb;
|
document.getElementById("deleteIddatadbText").textContent =
|
||||||
|
deleteIddatadb;
|
||||||
new bootstrap.Modal(modalEl).show();
|
new bootstrap.Modal(modalEl).show();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
$(document).on('click', '#deleteConfirmBtn', async function () {
|
$(document).on("click", "#deleteConfirmBtn", async function () {
|
||||||
const modalEl = document.getElementById('deleteConfirmModal');
|
const modalEl = document.getElementById("deleteConfirmModal");
|
||||||
const modal = bootstrap.Modal.getInstance(modalEl);
|
const modal = bootstrap.Modal.getInstance(modalEl);
|
||||||
if (modal) modal.hide();
|
if (modal) modal.hide();
|
||||||
|
|
||||||
if (!deleteIddatadb) return;
|
if (!deleteIddatadb) return;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const resp = await fetch('delete_record.php', {
|
const resp = await fetch("delete_record.php", {
|
||||||
method: 'POST',
|
method: "POST",
|
||||||
headers: { 'Content-Type': 'application/json' },
|
headers: { "Content-Type": "application/json" },
|
||||||
body: JSON.stringify({ id: deleteIddatadb })
|
body: JSON.stringify({ id: deleteIddatadb }),
|
||||||
});
|
});
|
||||||
const result = await resp.json();
|
const result = await resp.json();
|
||||||
|
|
||||||
if (result.success) {
|
if (result.success) {
|
||||||
// Remove from gridData
|
// Remove from gridData
|
||||||
const idx = window.gridData.findIndex(r => r.iddatadb === deleteIddatadb);
|
const idx = window.gridData.findIndex(
|
||||||
|
(r) => r.iddatadb === deleteIddatadb,
|
||||||
|
);
|
||||||
if (idx >= 0) window.gridData.splice(idx, 1);
|
if (idx >= 0) window.gridData.splice(idx, 1);
|
||||||
|
|
||||||
// Re-render
|
// Re-render
|
||||||
const gr = window.gridRenderer;
|
const gr = window.gridRenderer;
|
||||||
if (gr) gr.renderVisibleRows();
|
if (gr) gr.renderVisibleRows();
|
||||||
} else {
|
} else {
|
||||||
alert('Error: ' + result.message);
|
alert("Error: " + result.message);
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
alert('Error: ' + e.message);
|
alert("Error: " + e.message);
|
||||||
}
|
}
|
||||||
|
|
||||||
deleteIddatadb = null;
|
deleteIddatadb = null;
|
||||||
@@ -156,20 +186,26 @@
|
|||||||
});
|
});
|
||||||
|
|
||||||
// ── Add new row ──────────────────────────────────────────────────────
|
// ── Add new row ──────────────────────────────────────────────────────
|
||||||
$(document).on('click', '#addRowBtn', async function () {
|
$(document).on("click", "#addRowBtn", async function () {
|
||||||
const btn = this;
|
const btn = this;
|
||||||
btn.disabled = true;
|
btn.disabled = true;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const templateId = window.gridMeta?.templateId;
|
const templateId = window.gridMeta?.templateId;
|
||||||
if (!templateId) { alert('Template ID missing'); return; }
|
if (!templateId) {
|
||||||
|
alert("Template ID missing");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
const urlParams = new URLSearchParams(window.location.search);
|
const urlParams = new URLSearchParams(window.location.search);
|
||||||
const importref = urlParams.get('importref') || '';
|
const importref = urlParams.get("importref") || "";
|
||||||
const resp = await fetch('add_record.php', {
|
const resp = await fetch("add_record.php", {
|
||||||
method: 'POST',
|
method: "POST",
|
||||||
headers: { 'Content-Type': 'application/json' },
|
headers: { "Content-Type": "application/json" },
|
||||||
body: JSON.stringify({ template_id: templateId, importreferencecode: importref })
|
body: JSON.stringify({
|
||||||
|
template_id: templateId,
|
||||||
|
importreferencecode: importref,
|
||||||
|
}),
|
||||||
});
|
});
|
||||||
const result = await resp.json();
|
const result = await resp.json();
|
||||||
|
|
||||||
@@ -177,17 +213,20 @@
|
|||||||
// Build new row object
|
// Build new row object
|
||||||
const newRow = {
|
const newRow = {
|
||||||
iddatadb: result.iddatadb,
|
iddatadb: result.iddatadb,
|
||||||
status: 'i',
|
status: "i",
|
||||||
idclient: window.gridMeta?.defaultIdclient || '',
|
idclient: window.gridMeta?.defaultIdclient || "",
|
||||||
cliente_fornitore_id: null,
|
cliente_fornitore_id: null,
|
||||||
commessaweb: null,
|
commessaweb: null,
|
||||||
user_name: result.user_name || '',
|
user_name: result.user_name || "",
|
||||||
importreferencecode: result.importreferencecode || '',
|
importreferencecode: result.importreferencecode || "",
|
||||||
filename_import: '',
|
filename_import: "",
|
||||||
importdate: new Date().toISOString().slice(0, 19).replace('T', ' '),
|
importdate: new Date()
|
||||||
|
.toISOString()
|
||||||
|
.slice(0, 19)
|
||||||
|
.replace("T", " "),
|
||||||
fixedFields: {},
|
fixedFields: {},
|
||||||
details: {},
|
details: {},
|
||||||
mainFieldValue: '',
|
mainFieldValue: "",
|
||||||
_dirty: false,
|
_dirty: false,
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -199,20 +238,27 @@
|
|||||||
if (gr) gr.renderVisibleRows();
|
if (gr) gr.renderVisibleRows();
|
||||||
|
|
||||||
// Highlight new row briefly
|
// Highlight new row briefly
|
||||||
const newGridRow = document.querySelector(`.grid-row[data-id="${result.iddatadb}"]`);
|
const newGridRow = document.querySelector(
|
||||||
|
`.grid-row[data-id="${result.iddatadb}"]`,
|
||||||
|
);
|
||||||
if (newGridRow) {
|
if (newGridRow) {
|
||||||
newGridRow.classList.add('row-just-created');
|
newGridRow.classList.add("row-just-created");
|
||||||
newGridRow.scrollIntoView({ behavior: 'smooth', block: 'center' });
|
newGridRow.scrollIntoView({
|
||||||
setTimeout(() => newGridRow.classList.remove('row-just-created'), 4000);
|
behavior: "smooth",
|
||||||
|
block: "center",
|
||||||
|
});
|
||||||
|
setTimeout(
|
||||||
|
() => newGridRow.classList.remove("row-just-created"),
|
||||||
|
4000,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
alert('Error: ' + (result.message || 'Unknown error'));
|
alert("Error: " + (result.message || "Unknown error"));
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
alert('Error: ' + e.message);
|
alert("Error: " + e.message);
|
||||||
} finally {
|
} finally {
|
||||||
btn.disabled = false;
|
btn.disabled = false;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
})();
|
})();
|
||||||
|
|||||||
@@ -1595,7 +1595,11 @@ $(document).ready(function () {
|
|||||||
dataType: "json",
|
dataType: "json",
|
||||||
delay: 150,
|
delay: 150,
|
||||||
data: function (params) {
|
data: function (params) {
|
||||||
return { q: params.term || "", limit: 20, macro: selectedMacro || "" };
|
return {
|
||||||
|
q: params.term || "",
|
||||||
|
limit: 20,
|
||||||
|
macro: selectedMacro || "",
|
||||||
|
};
|
||||||
},
|
},
|
||||||
processResults: function (data) {
|
processResults: function (data) {
|
||||||
return { results: data.results || [] };
|
return { results: data.results || [] };
|
||||||
@@ -1654,7 +1658,11 @@ $(document).ready(function () {
|
|||||||
dataType: "json",
|
dataType: "json",
|
||||||
delay: 150,
|
delay: 150,
|
||||||
data: function (params) {
|
data: function (params) {
|
||||||
return { q: params.term || "", limit: 20, macro: selectedMacro || "" };
|
return {
|
||||||
|
q: params.term || "",
|
||||||
|
limit: 20,
|
||||||
|
macro: selectedMacro || "",
|
||||||
|
};
|
||||||
},
|
},
|
||||||
processResults: function (data) {
|
processResults: function (data) {
|
||||||
return { results: data.results || [] };
|
return { results: data.results || [] };
|
||||||
@@ -1800,7 +1808,9 @@ $(document).ready(function () {
|
|||||||
$("#partsTableBody .part-matrice").each(function () {
|
$("#partsTableBody .part-matrice").each(function () {
|
||||||
const $target = $(this);
|
const $target = $(this);
|
||||||
if (!$target.find(`option[value="${globalVal}"]`).length) {
|
if (!$target.find(`option[value="${globalVal}"]`).length) {
|
||||||
$target.append(new Option(globalText, globalVal, true, true));
|
$target.append(
|
||||||
|
new Option(globalText, globalVal, true, true),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
$target.val(globalVal).trigger("change");
|
$target.val(globalVal).trigger("change");
|
||||||
});
|
});
|
||||||
@@ -1969,7 +1979,109 @@ $(document).ready(function () {
|
|||||||
".add-row-global, .add-mix-global, .add-mix-row, .remove-row, .propagate-matrice-btn, .propagate-all-btn, .note-btn",
|
".add-row-global, .add-mix-global, .add-mix-row, .remove-row, .propagate-matrice-btn, .propagate-all-btn, .note-btn",
|
||||||
markUnsaved,
|
markUnsaved,
|
||||||
);
|
);
|
||||||
|
$(document).on("click", "#clonePartsBtn", function (e) {
|
||||||
|
e.preventDefault();
|
||||||
|
|
||||||
|
const sourceIddatadb =
|
||||||
|
parseInt($("#partsModal").data("iddatadb"), 10) || null;
|
||||||
|
const visibleListRaw =
|
||||||
|
$("#partsModal").data("visible-iddatadb-list") || [];
|
||||||
|
|
||||||
|
if (!sourceIddatadb) {
|
||||||
|
const errorMsg = $(
|
||||||
|
'<div class="alert alert-danger temp-alert" role="alert">Source record not found.</div>',
|
||||||
|
);
|
||||||
|
$("#partsModal .modal-body").prepend(errorMsg);
|
||||||
|
setTimeout(() => {
|
||||||
|
errorMsg.fadeOut(500, function () {
|
||||||
|
$(this).remove();
|
||||||
|
});
|
||||||
|
}, 5000);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const targetIds = (Array.isArray(visibleListRaw) ? visibleListRaw : [])
|
||||||
|
.map((v) => parseInt(v, 10))
|
||||||
|
.filter((v) => !isNaN(v) && v > 0 && v !== sourceIddatadb);
|
||||||
|
|
||||||
|
if (!targetIds.length) {
|
||||||
|
const errorMsg = $(
|
||||||
|
'<div class="alert alert-warning temp-alert" role="alert">No other visible records available for clone.</div>',
|
||||||
|
);
|
||||||
|
$("#partsModal .modal-body").prepend(errorMsg);
|
||||||
|
setTimeout(() => {
|
||||||
|
errorMsg.fadeOut(500, function () {
|
||||||
|
$(this).remove();
|
||||||
|
});
|
||||||
|
}, 5000);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (
|
||||||
|
!confirm(
|
||||||
|
`Confermi il clone delle parti del record ${sourceIddatadb} negli altri ${targetIds.length} record visibili?`,
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const $btn = $(this);
|
||||||
|
const originalHtml = $btn.html();
|
||||||
|
$btn.prop("disabled", true).html(
|
||||||
|
'<i class="fas fa-spinner fa-spin"></i> Clonazione...',
|
||||||
|
);
|
||||||
|
|
||||||
|
$.ajax({
|
||||||
|
url: "clone_parts_to_visible.php",
|
||||||
|
method: "POST",
|
||||||
|
contentType: "application/json",
|
||||||
|
dataType: "json",
|
||||||
|
data: JSON.stringify({
|
||||||
|
source_iddatadb: sourceIddatadb,
|
||||||
|
target_iddatadb_list: targetIds,
|
||||||
|
}),
|
||||||
|
success: function (response) {
|
||||||
|
$btn.prop("disabled", false).html(originalHtml);
|
||||||
|
|
||||||
|
if (response.success) {
|
||||||
|
const successMsg = $(
|
||||||
|
`<div class="alert alert-success temp-alert" role="alert">
|
||||||
|
Clone completed. Source parts copied to ${response.cloned_targets || 0} record(s), total cloned parts: ${response.total_cloned_parts || 0}.
|
||||||
|
</div>`,
|
||||||
|
);
|
||||||
|
$("#partsModal .modal-body").prepend(successMsg);
|
||||||
|
setTimeout(() => {
|
||||||
|
successMsg.fadeOut(500, function () {
|
||||||
|
$(this).remove();
|
||||||
|
});
|
||||||
|
}, 5000);
|
||||||
|
} else {
|
||||||
|
const errorMsg = $(
|
||||||
|
`<div class="alert alert-danger temp-alert" role="alert">Clone error: ${response.message || "Unknown error"}</div>`,
|
||||||
|
);
|
||||||
|
$("#partsModal .modal-body").prepend(errorMsg);
|
||||||
|
setTimeout(() => {
|
||||||
|
errorMsg.fadeOut(500, function () {
|
||||||
|
$(this).remove();
|
||||||
|
});
|
||||||
|
}, 5000);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
error: function (xhr, status, error) {
|
||||||
|
$btn.prop("disabled", false).html(originalHtml);
|
||||||
|
|
||||||
|
const errorMsg = $(
|
||||||
|
`<div class="alert alert-danger temp-alert" role="alert">Clone error: ${error} (${xhr.status})</div>`,
|
||||||
|
);
|
||||||
|
$("#partsModal .modal-body").prepend(errorMsg);
|
||||||
|
setTimeout(() => {
|
||||||
|
errorMsg.fadeOut(500, function () {
|
||||||
|
$(this).remove();
|
||||||
|
});
|
||||||
|
}, 5000);
|
||||||
|
},
|
||||||
|
});
|
||||||
|
});
|
||||||
// Esporta la funzione loadParts per essere usata da import_Edit2.php
|
// Esporta la funzione loadParts per essere usata da import_Edit2.php
|
||||||
window.loadParts = loadParts;
|
window.loadParts = loadParts;
|
||||||
|
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ try {
|
|||||||
$iddatadb = intval($_POST['iddatadb']);
|
$iddatadb = intval($_POST['iddatadb']);
|
||||||
$idclient = isset($_POST['idclient']) ? (is_numeric($_POST['idclient']) ? intval($_POST['idclient']) : null) : null;
|
$idclient = isset($_POST['idclient']) ? (is_numeric($_POST['idclient']) ? intval($_POST['idclient']) : null) : null;
|
||||||
$clienteFornitoreId = isset($_POST['cliente_fornitore_id']) ? (is_numeric($_POST['cliente_fornitore_id']) ? intval($_POST['cliente_fornitore_id']) : null) : null;
|
$clienteFornitoreId = isset($_POST['cliente_fornitore_id']) ? (is_numeric($_POST['cliente_fornitore_id']) ? intval($_POST['cliente_fornitore_id']) : null) : null;
|
||||||
|
$testedComponent = isset($_POST['tested_component']) ? trim((string)$_POST['tested_component']) : null;
|
||||||
$db = DBHandlerSelect::getInstance();
|
$db = DBHandlerSelect::getInstance();
|
||||||
$pdo = $db->getConnection();
|
$pdo = $db->getConnection();
|
||||||
|
|
||||||
@@ -144,7 +144,11 @@ try {
|
|||||||
$setParts[] = "cliente_fornitore_id = ?";
|
$setParts[] = "cliente_fornitore_id = ?";
|
||||||
$params[] = $clienteFornitoreId;
|
$params[] = $clienteFornitoreId;
|
||||||
}
|
}
|
||||||
|
// 5a3) tested_component (se presente)
|
||||||
|
if (isset($testedComponent)) {
|
||||||
|
$setParts[] = "tested_component = ?";
|
||||||
|
$params[] = ($testedComponent === '') ? null : $testedComponent;
|
||||||
|
}
|
||||||
// 5b) fixed fields dal POST
|
// 5b) fixed fields dal POST
|
||||||
// QUI è il punto chiave: accettiamo SOLO colonne reali (realWhitelist),
|
// QUI è il punto chiave: accettiamo SOLO colonne reali (realWhitelist),
|
||||||
// ma se dal JS arrivassero ancora key logiche, funzionerebbe uguale
|
// ma se dal JS arrivassero ancora key logiche, funzionerebbe uguale
|
||||||
|
|||||||
@@ -760,7 +760,7 @@
|
|||||||
{
|
{
|
||||||
"IdSchemaCustomFields": 182,
|
"IdSchemaCustomFields": 182,
|
||||||
"ConteggioClienti": 0,
|
"ConteggioClienti": 0,
|
||||||
"Nome": "Ralph Lauren - All testing V.6",
|
"Nome": "Ralph Lauren - All testing V.10",
|
||||||
"Descrizione": "AGGIORNAMENTO AL 16\/03\/2026 per estrazione fatturazione"
|
"Descrizione": "AGGIORNAMENTO AL 16\/03\/2026 per estrazione fatturazione"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -876,6 +876,12 @@
|
|||||||
"ConteggioClienti": 0,
|
"ConteggioClienti": 0,
|
||||||
"Nome": "ROSSIMODA",
|
"Nome": "ROSSIMODA",
|
||||||
"Descrizione": "Per tutti i campioni di ROSSIMODA\r\n\r\n"
|
"Descrizione": "Per tutti i campioni di ROSSIMODA\r\n\r\n"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"IdSchemaCustomFields": 202,
|
||||||
|
"ConteggioClienti": 0,
|
||||||
|
"Nome": "LIMS-CIM - MAX MARA",
|
||||||
|
"Descrizione": "Schema per MAX MARA scambio dati Database"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
@@ -2016,6 +2016,51 @@ function resolveFixedValue(string $key, $val, array $fixedLookup): string {
|
|||||||
el.addEventListener('change', () => el.classList.toggle('has-value', el.value.trim() !== ''));
|
el.addEventListener('change', () => el.classList.toggle('has-value', el.value.trim() !== ''));
|
||||||
});
|
});
|
||||||
})();
|
})();
|
||||||
|
|
||||||
|
// ── Column resize ──
|
||||||
|
(function() {
|
||||||
|
const resizers = document.querySelectorAll('.resizer');
|
||||||
|
let currentResizer = null, startX = 0, startWidth = 0, columnIndex = null;
|
||||||
|
|
||||||
|
function resize(e) {
|
||||||
|
if (!currentResizer || columnIndex === null) return;
|
||||||
|
const newWidth = Math.max(80, startWidth + (e.pageX - startX));
|
||||||
|
const sel = '[data-index="' + columnIndex + '"]';
|
||||||
|
const header = document.querySelector('.grid-header' + sel);
|
||||||
|
if (header) header.style.flex = '0 0 ' + newWidth + 'px';
|
||||||
|
const topCell = document.querySelector('.grid-top .grid-cell' + sel);
|
||||||
|
if (topCell) topCell.style.flex = '0 0 ' + newWidth + 'px';
|
||||||
|
document.querySelectorAll('.grid-row .grid-cell' + sel).forEach(function(cell) {
|
||||||
|
cell.style.flex = '0 0 ' + newWidth + 'px';
|
||||||
|
});
|
||||||
|
var filterCell = document.querySelector('#gridFilterRow > .grid-cell:nth-child(' + (parseInt(columnIndex, 10) + 1) + ')');
|
||||||
|
if (filterCell) filterCell.style.flex = '0 0 ' + newWidth + 'px';
|
||||||
|
}
|
||||||
|
|
||||||
|
function stopResize() {
|
||||||
|
if (currentResizer) {
|
||||||
|
document.removeEventListener('mousemove', resize);
|
||||||
|
document.removeEventListener('mouseup', stopResize);
|
||||||
|
currentResizer = null;
|
||||||
|
columnIndex = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
resizers.forEach(function(resizer) {
|
||||||
|
resizer.addEventListener('mousedown', function(e) {
|
||||||
|
e.preventDefault();
|
||||||
|
e.stopPropagation();
|
||||||
|
currentResizer = this;
|
||||||
|
var header = this.closest('.grid-header');
|
||||||
|
if (!header) return;
|
||||||
|
columnIndex = header.getAttribute('data-index');
|
||||||
|
startX = e.pageX;
|
||||||
|
startWidth = header.offsetWidth;
|
||||||
|
document.addEventListener('mousemove', resize);
|
||||||
|
document.addEventListener('mouseup', stopResize);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
})();
|
||||||
</script>
|
</script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|||||||
Reference in New Issue
Block a user