Merge feature/added-some-features into main (ignore debug log conflicts)

This commit is contained in:
Claudio 2025-08-26 10:46:27 +02:00
commit 21fcee8ff5
51 changed files with 1181 additions and 612 deletions

View File

@ -0,0 +1,74 @@
<?php
namespace App\Http\Controllers\Userarea;
use App\Http\Controllers\Controller;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Storage;
class UploadPhotosMobileController extends Controller
{
public function index(Request $request)
{
$iddatadb = $request->query('iddatadb');
if (empty($iddatadb)) {
return response('ID riga non fornito', 400);
}
// Show the upload form
return view('userarea.upload_photos_mobile', [
'iddatadb' => $iddatadb
]);
}
public function upload(Request $request)
{
// Validation
$request->validate([
'photo' => 'required|file|mimes:jpeg,png,gif,heic,heif|max:5120', // 5MB
'iddatadb' => 'required|integer'
]);
$iddatadb = $request->input('iddatadb');
$photo = $request->file('photo');
$iduserlogin = auth()->id(); // assuming Laravel authentication
// Check if user exists
$userExists = DB::table('auth_users')->where('id', $iduserlogin)->exists();
if (!$userExists) {
return response()->json(['success' => false, 'message' => 'Utente non valido']);
}
// Upload folder
$uploadDir = public_path('photostrf');
if (!is_dir($uploadDir)) {
mkdir($uploadDir, 0755, true);
}
if (!is_writable($uploadDir)) {
return response()->json(['success' => false, 'message' => 'La cartella photostrf non è scrivibile']);
}
// New filename
$timestamp = now()->format('YmdHis');
$originalName = pathinfo($photo->getClientOriginalName(), PATHINFO_FILENAME);
$extension = strtolower($photo->getClientOriginalExtension());
$newFileName = "{$iddatadb}-{$timestamp}-{$originalName}.{$extension}";
$destination = $uploadDir . '/' . $newFileName;
// Move uploaded file
$photo->move($uploadDir, $newFileName);
// Save DB record
DB::table('datadb_photos')->insert([
'iddatadb' => $iddatadb,
'file_path' => $newFileName,
'file_name' => $newFileName,
'uploaded_by' => $iduserlogin
]);
return response()->json(['success' => true, 'message' => 'Foto caricata con successo']);
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 52 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 54 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 52 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 92 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 52 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 52 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 54 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 54 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 451 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 510 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 462 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 443 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 460 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 454 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 456 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 460 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 457 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 454 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 455 B

View File

@ -1,5 +1,5 @@
<?php
require_once dirname(__DIR__, 2) . '/vendor/autoload.php'; // Torna al livello di public
require_once dirname(__DIR__, 2) . '/vendor/autoload.php';
require_once dirname(__FILE__) . '/class/VisualLimsApiClient.class.php';
header('Content-Type: application/json');
@ -9,20 +9,30 @@ error_reporting(E_ALL);
try {
$api = VisualLimsApiClient::getInstance();
// ID del campo custom passato da GET oppure default
$customFieldId = isset($_GET['field_id']) && is_numeric($_GET['field_id']) ? intval($_GET['field_id']) : 156;
// მივიღოთ მრავლობითი field_ids
$fieldIds = [];
if (isset($_GET['field_ids'])) {
$fieldIds = array_filter(array_map('intval', explode(',', $_GET['field_ids'])));
}
// Endpoint con $expand per ottenere i valori
$endpoint = "CustomField($customFieldId)?\$expand=CustomFieldsValues";
// თუ არ გადმოგვცეს -> ერთი default
if (empty($fieldIds)) {
$fieldIds = [156];
}
// Recupera i dati dal server
$data = $api->get($endpoint);
$results = [];
// Salva la risposta in un file per debug
file_put_contents(__DIR__ . '/customfield_values_response.json', json_encode($data));
foreach ($fieldIds as $customFieldId) {
$endpoint = "CustomField($customFieldId)?\$expand=CustomFieldsValues";
$data = $api->get($endpoint);
// Output JSON al client
echo json_encode($data);
$results[$customFieldId] = $data['CustomFieldsValues'] ?? [];
}
// Debug ფაილი
file_put_contents(__DIR__ . '/customfield_values_response.json', json_encode($results));
echo json_encode($results);
} catch (Exception $e) {
http_response_code(500);
echo json_encode([

View File

@ -19,11 +19,11 @@ if ($_SERVER['REQUEST_METHOD'] !== 'POST' || !isset($_POST['template_id']) || !i
exit;
}
$template_id = intval($_POST['template_id']);
$selected_rows = $_POST['selected_rows'];
$columns = json_decode($_POST['columns'], true); // Header dell'XLS
$rows = json_decode($_POST['rows'], true); // Dati dell'XLS
$newFilename = htmlspecialchars($_POST['filename']);
$template_id = intval($_POST['template_id']) ?? $_SESSION['template_id'];
$selected_rows = $_POST['selected_rows'] ?? $_SESSION['selected_rows'];
$columns = json_decode($_POST['columns'], true) ?? $_SESSION['columns']; // Header dell'XLS
$rows = json_decode($_POST['rows'], true) ?? $_SESSION['rows']; // Dati dell'XLS
$newFilename = htmlspecialchars($_POST['filename']) ?? $_SESSION['filename'];
// Log dei dati ricevuti
error_log("Received Data - Template ID: $template_id, Selected Rows: " . json_encode($selected_rows));
@ -40,7 +40,7 @@ $pdo = $db->getConnection();
$importReferenceCode = date('YmdHis') . '-' . uniqid();
// Recupera tutti i mapping dal template
$stmt = $pdo->prepare("SELECT id, excel_column, data_type, is_required, manual_default, is_manual, field_label, field_id FROM template_mapping WHERE template_id = ?");
$stmt = $pdo->prepare("SELECT id, excel_column, data_type, is_required, manual_default, is_manual, field_label, field_id, main_field FROM template_mapping WHERE template_id = ?");
$stmt->execute([$template_id]);
$allMappings = $stmt->fetchAll(PDO::FETCH_ASSOC);
@ -49,71 +49,82 @@ if (empty($allMappings)) {
exit;
}
// Inserisci le righe selezionate in datadb (solo campi generici con templateid)
$insertedIds = [];
foreach ($selected_rows as $rowIndex) {
$row = $rows[$rowIndex];
$values = [
$template_id, // templateid
$importReferenceCode, // importreferencecode
$newFilename, // filename_import
'i', // status
$user_id, // user_id
null, // limscode
date('Y-m-d') // importdate
];
$sql = "INSERT INTO datadb (templateid, importreferencecode, filename_import, status, user_id, limscode, importdate) VALUES (?, ?, ?, ?, ?, ?, ?)";
$stmt = $pdo->prepare($sql);
$stmt->execute($values);
$iddatadb = $pdo->lastInsertId();
$insertedIds[] = $iddatadb;
// Inserisci tutti i campi (automatici e manuali) in import_data_details
foreach ($allMappings as $mapping) {
$fieldValue = null;
if (!$mapping['is_manual']) { // Campi automatici dall'XLS
$excelColumn = trim($mapping['excel_column']);
$excelColumnIndex = array_search($excelColumn, array_map('trim', $columns));
if ($excelColumnIndex !== false && isset($row[$excelColumnIndex]) && $row[$excelColumnIndex] !== '') {
$fieldValue = $row[$excelColumnIndex];
error_log("Found Excel column '$excelColumn' at index $excelColumnIndex, value: " . var_export($fieldValue, true));
} else {
$fieldValue = $mapping['manual_default'] ?? '';
error_log("Excel column '$excelColumn' not found or empty, using default: " . var_export($fieldValue, true));
}
switch ($mapping['data_type']) {
case 'INT':
$fieldValue = is_numeric($fieldValue) ? (int)$fieldValue : ($mapping['manual_default'] ?? 0);
break;
case 'DATE':
$fieldValue = !empty($fieldValue) ? date('Y-m-d', strtotime($fieldValue)) : ($mapping['manual_default'] === 'today' ? date('Y-m-d') : ($mapping['manual_default'] ?? ''));
break;
case 'CHAR':
$fieldValue = !empty($fieldValue) ? substr((string)$fieldValue, 0, 1) : ($mapping['manual_default'] ?? '');
break;
case 'Testo':
case 'VARCHAR':
default:
$fieldValue = !empty($fieldValue) ? htmlspecialchars((string)$fieldValue) : ($mapping['manual_default'] ?? '');
break;
}
} else { // Campi manuali
$fieldValue = $mapping['manual_default'] ?? '';
if ($mapping['data_type'] === 'DATE' && $mapping['manual_default'] === 'today') {
$fieldValue = date('Y-m-d');
}
}
if ($mapping['is_required'] && (is_null($fieldValue) || $fieldValue === '')) {
error_log("Required field missing for mapping ID: " . $mapping['id'] . ", field: " . $mapping['field_label']);
}
error_log("Inserting into import_data_details - Mapping ID: " . $mapping['id'] . ", Field Value: " . var_export($fieldValue, true) . ", Is Manual: " . $mapping['is_manual'] . ", Excel Column: " . ($mapping['excel_column'] ?? 'N/A') . ", Manual Default: " . ($mapping['manual_default'] ?? 'N/A'));
$stmt = $pdo->prepare("INSERT INTO import_data_details (id, mapping_id, field_value) VALUES (?, ?, ?)");
$stmt->execute([$iddatadb, $mapping['id'], $fieldValue]);
error_log("Inserted into import_data_details for ID $iddatadb, Mapping ID: " . $mapping['id'] . ", Field Value: " . var_export($fieldValue, true));
// Trova il campo main_field
$mainFieldMapping = null;
foreach ($allMappings as $mapping) {
if ($mapping['main_field'] == 1) {
$mainFieldMapping = $mapping;
break;
}
}
//// Inserisci le righe selezionate in datadb (solo campi generici con templateid)
//$insertedIds = [];
//foreach ($selected_rows as $rowIndex) {
// $row = $rows[$rowIndex];
// $values = [
// $template_id, // templateid
// $importReferenceCode, // importreferencecode
// $newFilename, // filename_import
// 'i', // status
// $user_id, // user_id
// null, // limscode
// date('Y-m-d') // importdate
// ];
// $sql = "INSERT INTO datadb (templateid, importreferencecode, filename_import, status, user_id, limscode, importdate) VALUES (?, ?, ?, ?, ?, ?, ?)";
// $stmt = $pdo->prepare($sql);
// $stmt->execute($values);
//
// $iddatadb = $pdo->lastInsertId();
// $insertedIds[] = $iddatadb;
//
// // Inserisci tutti i campi (automatici e manuali) in import_data_details
// foreach ($allMappings as $mapping) {
// $fieldValue = null;
// if (!$mapping['is_manual']) { // Campi automatici dall'XLS
// $excelColumn = trim($mapping['excel_column']);
// $excelColumnIndex = array_search($excelColumn, array_map('trim', $columns));
// if ($excelColumnIndex !== false && isset($row[$excelColumnIndex]) && $row[$excelColumnIndex] !== '') {
// $fieldValue = $row[$excelColumnIndex];
// error_log("Found Excel column '$excelColumn' at index $excelColumnIndex, value: " . var_export($fieldValue, true));
// } else {
// $fieldValue = $mapping['manual_default'] ?? '';
// error_log("Excel column '$excelColumn' not found or empty, using default: " . var_export($fieldValue, true));
// }
// switch ($mapping['data_type']) {
// case 'INT':
// $fieldValue = is_numeric($fieldValue) ? (int)$fieldValue : ($mapping['manual_default'] ?? 0);
// break;
// case 'DATE':
// $fieldValue = !empty($fieldValue) ? date('Y-m-d', strtotime($fieldValue)) : ($mapping['manual_default'] === 'today' ? date('Y-m-d') : ($mapping['manual_default'] ?? ''));
// break;
// case 'CHAR':
// $fieldValue = !empty($fieldValue) ? substr((string)$fieldValue, 0, 1) : ($mapping['manual_default'] ?? '');
// break;
// case 'Testo':
// case 'VARCHAR':
// default:
// $fieldValue = !empty($fieldValue) ? htmlspecialchars((string)$fieldValue) : ($mapping['manual_default'] ?? '');
// break;
// }
// } else { // Campi manuali
// $fieldValue = $mapping['manual_default'] ?? '';
// if ($mapping['data_type'] === 'DATE' && $mapping['manual_default'] === 'today') {
// $fieldValue = date('Y-m-d');
// }
// }
// if ($mapping['is_required'] && (is_null($fieldValue) || $fieldValue === '')) {
// error_log("Required field missing for mapping ID: " . $mapping['id'] . ", field: " . $mapping['field_label']);
// }
// error_log("Inserting into import_data_details - Mapping ID: " . $mapping['id'] . ", Field Value: " . var_export($fieldValue, true) . ", Is Manual: " . $mapping['is_manual'] . ", Excel Column: " . ($mapping['excel_column'] ?? 'N/A') . ", Manual Default: " . ($mapping['manual_default'] ?? 'N/A'));
// $stmt = $pdo->prepare("INSERT INTO import_data_details (id, mapping_id, field_value) VALUES (?, ?, ?)");
// $stmt->execute([$iddatadb, $mapping['id'], $fieldValue]);
// error_log("Inserted into import_data_details for ID $iddatadb, Mapping ID: " . $mapping['id'] . ", Field Value: " . var_export($fieldValue, true));
// }
//}
$insertedIds = $_POST['inserted_ids'] ?? $_SESSION['inserted_ids'];
// Recupera i dati appena inseriti con i nomi degli utenti
$stmt = $pdo->prepare("
SELECT d.*, CONCAT(u.first_name, ' ', u.last_name) AS user_name
@ -221,7 +232,8 @@ foreach ($stmt->fetchAll(PDO::FETCH_ASSOC) as $row) {
.grid-header,
.grid-cell {
flex: 1;
min-width: 100px;
min-width: 70px;
/* Ridotto da 100px per compatibilità con pulsanti */
padding: 12px 15px;
border-right: 1px solid #dee2e6;
overflow: hidden;
@ -397,6 +409,25 @@ foreach ($stmt->fetchAll(PDO::FETCH_ASSOC) as $row) {
border-color: #80bdff;
box-shadow: 0 0 5px rgba(0, 123, 255, 0.5);
}
/* Sovrascrivi min-width per le celle dei pulsanti */
.grid-cell.button-cell,
.grid-header.button-header {
min-width: 70px !important;
flex: 0 0 70px !important;
}
/* Stile per l'header dei pulsanti */
.button-header {
min-height: 48px;
/* Altezza minima per uniformare */
padding: 12px 0;
/* Centra verticalmente, no padding orizzontale */
background-color: #e9ecef !important;
/* Grigio uniforme */
border-right: 1px solid #dee2e6 !important;
/* Bordo destro coerente */
}
</style>
<title>Edit Imported Data - <?= htmlspecialchars($titlewebsite, ENT_QUOTES, 'UTF-8'); ?></title>
</head>
@ -407,13 +438,21 @@ foreach ($stmt->fetchAll(PDO::FETCH_ASSOC) as $row) {
<?php include('include/topbar.php'); ?>
<div class="page-wrapper">
<div class="page-content">
<?php //include('top_stat_widget.php');
<?php //include('top_stat_widget.php');
?>
<div class="mb-3 text">
<a href="historical_trf.php?id=<?= $template_id ?>&status=i" class="btn btn-warning me-2">Imported (i)</a>
<a href="historical_trf.php?id=<?= $template_id ?>&status=P" class="btn btn-primary me-2">In Progress (P)</a>
<a href="historical_trf.php?id=<?= $template_id ?>&status=l" class="btn btn-success">To LIMS (l)</a>
</div>
<div class="card radius-10">
<div class="card-header">
<div class="d-flex align-items-center">
<div>
<h6 class="mb-0">Modifica Dati Importati</h6>
<div id="unsavedChanges" style="display:none; color: red; font-weight: bold; margin:10px 0;">
⚠️ Unsaved changes detected! Please save before leaving this page.
</div>
</div>
</div>
</div>
@ -422,19 +461,46 @@ foreach ($stmt->fetchAll(PDO::FETCH_ASSOC) as $row) {
<div class="grid-container">
<!-- Riga superiore per gli input dei campi manuali -->
<div class="grid-top">
<div class="grid-cell" style="flex: 0 0 100px;"></div> <!-- Save -->
<div class="grid-cell" style="flex: 0 0 100px;"></div> <!-- Photos -->
<div class="grid-cell" style="flex: 0 0 100px;"></div> <!-- Parts -->
<div class="grid-cell button-cell" style="flex: 0 0 70px;"></div> <!-- Save -->
<div class="grid-cell button-cell" style="flex: 0 0 70px;"></div> <!-- Photos -->
<div class="grid-cell button-cell" style="flex: 0 0 70px;"></div> <!-- Parts -->
<?php if ($mainFieldMapping): ?>
<div class="grid-cell" style="flex: 0 0 150px;">
<?php
$fieldValue = $mainFieldMapping['manual_default'] ?? '';
if ($mainFieldMapping['data_type'] === 'DATE' && $mainFieldMapping['manual_default'] === 'today') {
$fieldValue = date('Y-m-d');
}
$inputClass = $mainFieldMapping['is_manual'] ? 'manual-input' : 'auto-input';
if ($mainFieldMapping['is_required']) $inputClass .= ' required-input';
if ($mainFieldMapping['data_type'] === 'SceltaMultipla') {
echo "<select class='custom-field dropdown-select $inputClass' data-column='main_field' data-field-id='{$mainFieldMapping['field_id']}' " . ($mainFieldMapping['is_required'] ? 'required' : '') . ">";
echo "<option value=''>Seleziona...</option>";
echo "</select>";
echo "<button type='button' class='propagate-btn' data-column='main_field'><i class='fas fa-arrow-down'></i></button>";
} elseif ($mainFieldMapping['data_type'] === 'DATE') {
echo "<input type='date' class='custom-field $inputClass' data-column='main_field' value='" . htmlspecialchars($fieldValue) . "' " . ($mainFieldMapping['is_required'] ? 'required' : '') . ">";
echo "<button type='button' class='propagate-btn' data-column='main_field'><i class='fas fa-arrow-down'></i></button>";
} elseif ($mainFieldMapping['data_type'] === 'INT') {
echo "<input type='number' class='custom-field $inputClass' data-column='main_field' value='" . htmlspecialchars($fieldValue) . "' " . ($mainFieldMapping['is_required'] ? 'required' : '') . ">";
echo "<button type='button' class='propagate-btn' data-column='main_field'><i class='fas fa-arrow-down'></i></button>";
} else {
echo "<input type='text' class='custom-field $inputClass' data-column='main_field' value='" . htmlspecialchars($fieldValue) . "' " . ($mainFieldMapping['is_required'] ? 'required' : '') . ">";
echo "<button type='button' class='propagate-btn' data-column='main_field'><i class='fas fa-arrow-down'></i></button>";
}
?>
</div>
<?php endif; ?>
<div class="grid-cell" style="flex: 0 0 150px;"></div> <!-- importreferencecode -->
<?php
$fixedColumns = ['filename_import', 'status', 'importdate'];
foreach ($fixedColumns as $col) {
echo "<div class='grid-cell' style='flex: 0 0 150px;'></div>";
}
// Campi automatici (is_manual = 0) - Solo SceltaMultipla ha campo e propagazione
// Campi automatici (is_manual = 0) escluso main_field
$autoIndex = 0;
foreach ($allMappings as $mapping) {
if (!$mapping['is_manual']) {
if (!$mapping['is_manual'] && $mapping['main_field'] != 1) {
$inputClass = 'auto-input';
if ($mapping['is_required']) $inputClass .= ' required-input';
if ($mapping['data_type'] === 'SceltaMultipla') {
@ -445,15 +511,15 @@ foreach ($stmt->fetchAll(PDO::FETCH_ASSOC) as $row) {
echo "<button type='button' class='propagate-btn' data-column='auto_$autoIndex'><i class='fas fa-arrow-down'></i></button>";
echo "</div>";
} else {
echo "<div class='grid-cell' style='flex: 0 0 150px;'></div>"; // Nessun input per altri tipi
echo "<div class='grid-cell' style='flex: 0 0 150px;'></div>";
}
$autoIndex++;
}
}
// Campi manuali (is_manual = 1) con propagate-btn - Rimane invariato
// Campi manuali (is_manual = 1) escluso main_field
$manualIndex = 0;
foreach ($allMappings as $mapping) {
if ($mapping['is_manual']) {
if ($mapping['is_manual'] && $mapping['main_field'] != 1) {
$fieldValue = $mapping['manual_default'] ?? '';
if ($mapping['data_type'] === 'DATE' && $mapping['manual_default'] === 'today') {
$fieldValue = date('Y-m-d');
@ -465,7 +531,6 @@ foreach ($stmt->fetchAll(PDO::FETCH_ASSOC) as $row) {
echo "<select class='custom-field dropdown-select $inputClass' data-column='manual_$manualIndex' data-field-id='{$mapping['field_id']}' " . ($mapping['is_required'] ? 'required' : '') . ">";
echo "<option value=''>Seleziona...</option>";
echo "</select>";
echo "<button type='button' class='propagate-btn' data-column='manual_$manualIndex'><i class='fas fa-arrow-down'></i></button>";
} elseif ($mapping['data_type'] === 'DATE') {
echo "<input type='date' class='custom-field $inputClass' data-column='manual_$manualIndex' value='" . htmlspecialchars($fieldValue) . "' " . ($mapping['is_required'] ? 'required' : '') . ">";
} elseif ($mapping['data_type'] === 'INT') {
@ -485,26 +550,32 @@ foreach ($stmt->fetchAll(PDO::FETCH_ASSOC) as $row) {
<!-- Header della tabella -->
<div class="grid-row">
<div class="grid-header" style="flex: 0 0 100px;">Save</div>
<div class="grid-header" style="flex: 0 0 100px;">Photos</div>
<div class="grid-header" style="flex: 0 0 100px;">Parts</div>
<div class="grid-header" data-index="3" style="flex: 0 0 150px; position: relative;">Import Reference Code<div class="resizer"></div>
<div class="grid-header button-header" style="flex: 0 0 70px;"></div> <!-- Save -->
<div class="grid-header button-header" style="flex: 0 0 70px;"></div> <!-- Photos -->
<div class="grid-header button-header" style="flex: 0 0 70px;"></div> <!-- Parts -->
<?php if ($mainFieldMapping): ?>
<div class="grid-header" data-index="3" style="flex: 0 0 150px; position: relative;">
<?= htmlspecialchars($mainFieldMapping['field_label']) ?>
<div class="resizer"></div>
</div>
<?php endif; ?>
<div class="grid-header" data-index="<?= $mainFieldMapping ? 4 : 3 ?>" style="flex: 0 0 150px; position: relative;">Import Reference Code<div class="resizer"></div>
</div>
<?php
$headerIndex = 4;
$headerIndex = $mainFieldMapping ? 5 : 4;
foreach ($fixedColumns as $col) {
$displayName = $slugMapping[$col] ?? $col;
echo "<div class='grid-header' data-index='$headerIndex' style='flex: 0 0 150px; position: relative;'>$displayName<div class='resizer'></div></div>";
$headerIndex++;
}
foreach ($allMappings as $mapping) {
if (!$mapping['is_manual']) {
if (!$mapping['is_manual'] && $mapping['main_field'] != 1) {
echo "<div class='grid-header' data-index='$headerIndex' style='flex: 0 0 150px; position: relative;'>" . htmlspecialchars($mapping['field_label']) . "<div class='resizer'></div></div>";
$headerIndex++;
}
}
foreach ($allMappings as $mapping) {
if ($mapping['is_manual']) {
if ($mapping['is_manual'] && $mapping['main_field'] != 1) {
echo "<div class='grid-header' data-index='$headerIndex' style='flex: 0 0 150px; position: relative;'>" . htmlspecialchars($mapping['field_label']) . "<div class='resizer'></div></div>";
$headerIndex++;
}
@ -517,22 +588,47 @@ foreach ($stmt->fetchAll(PDO::FETCH_ASSOC) as $row) {
<!-- Righe della tabella -->
<?php foreach ($importedData as $index => $row): ?>
<div class="grid-row" data-id="<?= $row['iddatadb'] ?>">
<div class="grid-cell" style="flex: 0 0 100px; position: relative;">
<button type="button" class="save-btn action-btn" data-row="<?= $index ?>" style="background: #28a745; color: white; border: none; padding: 8px 12px; border-radius: 5px; cursor: pointer; width: 100%; box-sizing: border-box;"><i class="fas fa-save"></i></button>
<div class="grid-cell button-cell" style="flex: 0 0 70px; position: relative;">
<button type="button" class="save-btn action-btn" data-row="<?= $index ?>" title="Save" style="background: #28a745; color: white; border: none; padding: 6px 8px; border-radius: 5px; cursor: pointer; width: 100%; box-sizing: border-box;"><i class="fas fa-save"></i></button>
</div>
<div class="grid-cell" style="flex: 0 0 100px; position: relative;">
<button type="button" class="photos-btn action-btn" data-row="<?= $index ?>" data-iddatadb="<?= $row['iddatadb'] ?>" style="background: #007bff; color: white; border: none; padding: 8px 12px; border-radius: 5px; cursor: pointer; width: 100%; box-sizing: border-box;"><i class="fas fa-camera"></i></button>
<div class="grid-cell button-cell" style="flex: 0 0 70px; position: relative;">
<button type="button" class="photos-btn action-btn" data-row="<?= $index ?>" data-iddatadb="<?= $row['iddatadb'] ?>" title="Photos" style="background: #007bff; color: white; border: none; padding: 6px 8px; border-radius: 5px; cursor: pointer; width: 100%; box-sizing: border-box;"><i class="fas fa-camera"></i></button>
</div>
<div class="grid-cell" style="flex: 0 0 100px; position: relative;">
<button type="button" class="parts-btn action-btn" data-row="<?= $index ?>" data-iddatadb="<?= $row['iddatadb'] ?>" style="background: #ffc107; color: white; border: none; padding: 8px 12px; border-radius: 5px; cursor: pointer; width: 100%; box-sizing: border-box;"><i class="fas fa-puzzle-piece"></i></button>
<div class="grid-cell button-cell" style="flex: 0 0 70px; position: relative;">
<button type="button" class="parts-btn action-btn" data-row="<?= $index ?>" data-iddatadb="<?= $row['iddatadb'] ?>" title="Parts" style="background: #ffc107; color: white; border: none; padding: 6px 8px; border-radius: 5px; cursor: pointer; width: 100%; box-sizing: border-box;"><i class="fas fa-puzzle-piece"></i></button>
</div>
<?php if ($mainFieldMapping): ?>
<?php
$detail = array_filter($manualDetails, fn($d) => $d['mapping_id'] == $mainFieldMapping['id'] && $d['datadb_id'] == $row['iddatadb']);
$detail = reset($detail) ?: ['field_value' => $mainFieldMapping['manual_default']];
$fieldValue = $detail['field_value'] ?? $mainFieldMapping['manual_default'] ?? '';
if ($mainFieldMapping['data_type'] === 'DATE' && $mainFieldMapping['manual_default'] === 'today' && empty($fieldValue)) {
$fieldValue = date('Y-m-d');
}
$requiredClass = ($mainFieldMapping['is_required'] && (is_null($fieldValue) || $fieldValue === '')) ? 'missing-required' : '';
$inputClass = $mainFieldMapping['is_manual'] ? 'manual-input' : 'auto-input';
if ($mainFieldMapping['is_required']) $inputClass .= ' required-input';
?>
<div class="grid-cell editable-cell <?= $requiredClass ?>" data-col="main_field" data-row="<?= $index ?>" data-index="3" style="flex: 0 0 150px;">
<?php if ($mainFieldMapping['data_type'] === 'SceltaMultipla'): ?>
<select name="rows[<?= $index ?>][details][<?= $mainFieldMapping['id'] ?>][field_value]" class="cell-input dropdown-select <?= $inputClass ?>" data-mapping-id="<?= $mainFieldMapping['id'] ?>" data-field-id="<?= $mainFieldMapping['field_id'] ?>" <?= $mainFieldMapping['is_required'] ? 'required' : '' ?>>
<option value="">Seleziona...</option>
</select>
<?php elseif ($mainFieldMapping['data_type'] === 'DATE'): ?>
<input type="date" name="rows[<?= $index ?>][details][<?= $mainFieldMapping['id'] ?>][field_value]" value="<?= htmlspecialchars($fieldValue) ?>" class="cell-input <?= $inputClass ?>" <?= $mainFieldMapping['is_required'] ? 'required' : '' ?>>
<?php elseif ($mainFieldMapping['data_type'] === 'INT'): ?>
<input type="number" name="rows[<?= $index ?>][details][<?= $mainFieldMapping['id'] ?>][field_value]" value="<?= htmlspecialchars($fieldValue) ?>" class="cell-input <?= $inputClass ?>" <?= $mainFieldMapping['is_required'] ? 'required' : '' ?>>
<?php else: ?>
<input type="text" name="rows[<?= $index ?>][details][<?= $mainFieldMapping['id'] ?>][field_value]" value="<?= htmlspecialchars($fieldValue) ?>" class="cell-input <?= $inputClass ?>" <?= $mainFieldMapping['is_required'] ? 'required' : '' ?>>
<?php endif; ?>
</div>
<?php endif; ?>
<div class="grid-cell" data-col="importreferencecode" data-row="<?= $index ?>" data-index="<?= $mainFieldMapping ? 4 : 3 ?>" style="flex: 0 0 150px;">
<span><?= htmlspecialchars($row['importreferencecode']) ?></span>
<input type="hidden" name="rows[<?= $index ?>][importreferencecode]" value="<?= htmlspecialchars($row['importreferencecode']) ?>">
</div>
<?php
$cellIndex = 3; // Inizia dopo importreferencecode
echo "<div class='grid-cell' data-col='importreferencecode' data-row='$index' data-index='$cellIndex' style='flex: 0 0 150px;'>";
echo "<span>" . htmlspecialchars($row['importreferencecode']) . "</span>";
echo "<input type='hidden' name='rows[$index][importreferencecode]' value='" . htmlspecialchars($row['importreferencecode']) . "'>";
echo "</div>";
$cellIndex++;
$cellIndex = $mainFieldMapping ? 5 : 4;
foreach ($fixedColumns as $col) {
$value = $row[$col] ?? '';
echo "<div class='grid-cell editable-cell' data-col='$col' data-row='$index' data-index='$cellIndex' style='flex: 0 0 150px;'>";
@ -552,7 +648,7 @@ foreach ($stmt->fetchAll(PDO::FETCH_ASSOC) as $row) {
$rowDetails = array_filter($manualDetails, fn($d) => $d['datadb_id'] == $row['iddatadb']);
$autoIndex = 0;
foreach ($allMappings as $mapping) {
if (!$mapping['is_manual']) {
if (!$mapping['is_manual'] && $mapping['main_field'] != 1) {
$detail = array_filter($rowDetails, fn($d) => $d['mapping_id'] == $mapping['id']);
$detail = reset($detail) ?: ['field_value' => $mapping['manual_default']];
$fieldValue = $detail['field_value'] ?? $mapping['manual_default'] ?? '';
@ -578,7 +674,7 @@ foreach ($stmt->fetchAll(PDO::FETCH_ASSOC) as $row) {
}
$manualIndex = 0;
foreach ($allMappings as $mapping) {
if ($mapping['is_manual']) {
if ($mapping['is_manual'] && $mapping['main_field'] != 1) {
$detail = array_filter($rowDetails, fn($d) => $d['mapping_id'] == $mapping['id']);
$detail = reset($detail) ?: ['field_value' => $mapping['manual_default']];
$fieldValue = $detail['field_value'] ?? $mapping['manual_default'] ?? '';
@ -640,6 +736,38 @@ foreach ($stmt->fetchAll(PDO::FETCH_ASSOC) as $row) {
<script src="photos.js"></script>
<script src="parts.js"></script>
<script src="tracking.js"></script>
<script>
document.addEventListener("DOMContentLoaded", function() {
const inputs = document.querySelectorAll(".cell-input, .dropdown-select, .carrier-select, .awb-input");
const unsavedDiv = document.getElementById("unsavedChanges");
let hasChanges = false;
// როცა მნიშვნელობა შეიცვლება
inputs.forEach(el => {
el.addEventListener("change", () => {
hasChanges = true;
unsavedDiv.style.display = "block";
});
});
// როცა save ღილაკს დააჭერს
document.querySelectorAll(".save-btn").forEach(btn => {
btn.addEventListener("click", () => {
hasChanges = false;
unsavedDiv.style.display = "none";
});
});
// სურვილისამებრ: გაფრთხილება გვერდიდან გასვლისას
window.addEventListener("beforeunload", function (e) {
if (hasChanges) {
e.preventDefault();
e.returnValue = "";
}
});
});
</script>
<script>
document.addEventListener("DOMContentLoaded", function() {
const inputs = document.querySelectorAll('.cell-input');
@ -666,6 +794,7 @@ foreach ($stmt->fetchAll(PDO::FETCH_ASSOC) as $row) {
formData.append(name, input.value);
});
formData.append('iddatadb', iddatadb);
formData.append('mapping', JSON.stringify(<?= json_encode($allMappings) ?>));
fetch('save_edited_row.php', {
method: 'POST',
@ -681,6 +810,7 @@ foreach ($stmt->fetchAll(PDO::FETCH_ASSOC) as $row) {
cell.classList.add('flash-success');
});
setTimeout(() => cells.forEach(cell => cell.classList.remove('flash-success')), 500);
alert('Salvataggio avvenuto con successo!');
} else {
alert('Errore durante il salvataggio: ' + data.message);
}
@ -692,14 +822,14 @@ foreach ($stmt->fetchAll(PDO::FETCH_ASSOC) as $row) {
const propagateButtons = document.querySelectorAll('.propagate-btn');
propagateButtons.forEach(button => {
button.addEventListener('click', function() {
const columnIndex = this.getAttribute('data-column').replace('manual_', '');
const column = this.getAttribute('data-column');
const input = this.previousElementSibling;
const value = input.value;
// Trova la colonna target nella griglia superiore e propaga solo verticalmente
const gridTopCells = document.querySelector('.grid-top').querySelectorAll('.grid-cell');
const targetTopIndex = Array.from(gridTopCells).findIndex(cell =>
cell.querySelector('.propagate-btn') === button
cell.querySelector('.propagate-btn[data-column="' + column + '"]')
);
if (targetTopIndex !== -1) {
@ -756,7 +886,7 @@ foreach ($stmt->fetchAll(PDO::FETCH_ASSOC) as $row) {
});
});
</script>
<!-- script dropdonw senza overlay -->
<!-- script dropdown senza overlay -->
<script>
document.addEventListener("DOMContentLoaded", function() {
// Oggetto per memorizzare i dati delle tendine recuperati
@ -767,23 +897,32 @@ foreach ($stmt->fetchAll(PDO::FETCH_ASSOC) as $row) {
if (dropdowns.length === 0) return;
// Recupera i dati solo per i field_id univoci
const uniqueFieldIds = [...new Set(Array.from(dropdowns).map(d => d.getAttribute('data-field-id')))].filter(fieldId => fieldId);
for (const fieldId of uniqueFieldIds) {
if (!dropdownData[fieldId]) {
try {
const response = await fetch(`get_customfield_values.php?field_id=${fieldId}`);
const data = await response.json();
if (data.error) {
console.error('Errore per field_id', fieldId, ':', data.error);
continue;
const uniqueFieldIds = [
...new Set(Array.from(dropdowns).map(d => d.getAttribute('data-field-id')))
].filter(fieldId => fieldId);
const missingFieldIds = uniqueFieldIds.filter(fieldId => !dropdownData[fieldId]);
if (missingFieldIds.length > 0) {
try {
const response = await fetch(
`get_customfield_values.php?field_ids=${missingFieldIds.join(",")}`
);
const data = await response.json();
if (data.error) {
console.error("Errore fetch multiplo:", data.error);
} else {
for (const fieldId of Object.keys(data)) {
dropdownData[fieldId] = data[fieldId] || [];
}
dropdownData[fieldId] = data.CustomFieldsValues || [];
} catch (error) {
console.error('Errore nel fetch per field_id', fieldId, ':', error);
}
} catch (error) {
console.error("Errore generale nel fetch multiplo:", error);
}
}
// Popola tutti i dropdown con i dati recuperati
dropdowns.forEach(dropdown => {
const fieldId = dropdown.getAttribute('data-field-id');
@ -816,6 +955,134 @@ foreach ($stmt->fetchAll(PDO::FETCH_ASSOC) as $row) {
// Gestione della propagazione per mantenere i valori sincronizzati
const propagateButtons = document.querySelectorAll('.propagate-btn');
propagateButtons.forEach(button => {
button.addEventListener('click', function() {
const column = this.getAttribute('data-column');
const input = this.previousElementSibling;
const value = input.value;
const gridTopCells = document.querySelector('.grid-top').querySelectorAll('.grid-cell');
const targetTopIndex = Array.from(gridTopCells).findIndex(cell =>
cell.querySelector('.propagate-btn[data-column="' + column + '"]')
);
if (targetTopIndex !== -1) {
const rows = document.querySelectorAll('.grid-row');
rows.forEach(row => {
const cells = row.querySelectorAll('.grid-cell');
if (cells.length > targetTopIndex) {
const targetInput = cells[targetTopIndex].querySelector('select.dropdown-select, input');
if (targetInput) {
targetInput.value = value;
if (targetInput.tagName === 'SELECT') {
const event = new Event('change');
targetInput.dispatchEvent(event);
}
}
}
});
}
});
});
});
</script>
<!-- dropdown with overlay -->
<!-- <script>
document.addEventListener("DOMContentLoaded", function() {
// Crea un overlay di caricamento
const loadingOverlay = document.createElement('div');
loadingOverlay.id = 'loading-overlay';
loadingOverlay.style.cssText = `
display: none;
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: rgba(0, 0, 0, 0.7);
z-index: 9999;
justify-content: center;
align-items: center;
`;
const loadingMessage = document.createElement('div');
loadingMessage.style.cssText = `
color: white;
font-size: 24px;
padding: 20px;
background: #333;
border-radius: 10px;
box-shadow: 0 0 10px rgba(0, 0, 0, 0.5);
`;
loadingMessage.textContent = 'Loading Dropdown Options...';
loadingOverlay.appendChild(loadingMessage);
document.body.appendChild(loadingOverlay);
// Funzione originale populateDropdowns
async function populateDropdowns() {
const dropdowns = document.querySelectorAll('.dropdown-select');
if (dropdowns.length === 0) return;
const dropdownData = {};
// Recupera i dati solo per i field_id univoci
const uniqueFieldIds = [...new Set(Array.from(dropdowns).map(d => d.getAttribute('data-field-id')))].filter(fieldId => fieldId);
for (const fieldId of uniqueFieldIds) {
if (!dropdownData[fieldId]) {
try {
const response = await fetch(`get_customfield_values.php?field_id=${fieldId}`);
const data = await response.json();
if (data.error) {
console.error('Errore per field_id', fieldId, ':', data.error);
continue;
}
dropdownData[fieldId] = data.CustomFieldsValues || [];
} catch (error) {
console.error('Errore nel fetch per field_id', fieldId, ':', error);
}
}
}
// Popola tutti i dropdown con i dati recuperati
dropdowns.forEach(dropdown => {
const fieldId = dropdown.getAttribute('data-field-id');
if (!fieldId || !dropdownData[fieldId]) {
dropdown.innerHTML = '<option value="">Errore nel caricamento</option>';
return;
}
dropdown.innerHTML = '<option value="">Seleziona...</option>';
dropdownData[fieldId].forEach(value => {
const option = document.createElement('option');
option.value = value.IdCustomFieldsValue;
option.textContent = value.Valore;
if (dropdown.value === option.value) option.selected = true;
dropdown.appendChild(option);
});
});
}
// Esegui al caricamento della pagina con l'overlay
async function loadDropdownsWithOverlay() {
console.log('Inizio caricamento tendine');
loadingOverlay.style.display = 'flex';
await new Promise(resolve => setTimeout(resolve, 500)); // Minimo 500ms di visibilità
await populateDropdowns();
console.log('Caricamento tendine completato');
loadingOverlay.style.display = 'none';
}
// Esegui il caricamento iniziale
loadDropdownsWithOverlay();
// Rielabora i dropdown quando si aggiunge una nuova riga
document.querySelectorAll('.save-btn').forEach(btn => {
btn.addEventListener('click', function() {
setTimeout(loadDropdownsWithOverlay, 100);
});
});
// Gestione della propagazione
const propagateButtons = document.querySelectorAll('.propagate-btn');
propagateButtons.forEach(button => {
button.addEventListener('click', function() {
const columnIndex = this.getAttribute('data-column').replace('manual_', '');
@ -835,7 +1102,6 @@ foreach ($stmt->fetchAll(PDO::FETCH_ASSOC) as $row) {
const targetInput = cells[targetTopIndex].querySelector('select.dropdown-select');
if (targetInput) {
targetInput.value = value;
// Aggiorna visivamente il dropdown
const event = new Event('change');
targetInput.dispatchEvent(event);
}
@ -845,135 +1111,7 @@ foreach ($stmt->fetchAll(PDO::FETCH_ASSOC) as $row) {
});
});
});
</script>
<!-- dropdown with overlay -->
<!--
<script>
document.addEventListener("DOMContentLoaded", function() {
// Crea un overlay di caricamento
const loadingOverlay = document.createElement('div');
loadingOverlay.id = 'loading-overlay';
loadingOverlay.style.cssText = `
display: none;
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: rgba(0, 0, 0, 0.7);
z-index: 9999;
justify-content: center;
align-items: center;
`;
const loadingMessage = document.createElement('div');
loadingMessage.style.cssText = `
color: white;
font-size: 24px;
padding: 20px;
background: #333;
border-radius: 10px;
box-shadow: 0 0 10px rgba(0, 0, 0, 0.5);
`;
loadingMessage.textContent = 'Loading Dropdown Options...';
loadingOverlay.appendChild(loadingMessage);
document.body.appendChild(loadingOverlay);
// Funzione originale populateDropdowns
async function populateDropdowns() {
const dropdowns = document.querySelectorAll('.dropdown-select');
if (dropdowns.length === 0) return;
const dropdownData = {};
// Recupera i dati solo per i field_id univoci
const uniqueFieldIds = [...new Set(Array.from(dropdowns).map(d => d.getAttribute('data-field-id')))].filter(fieldId => fieldId);
for (const fieldId of uniqueFieldIds) {
if (!dropdownData[fieldId]) {
try {
const response = await fetch(`get_customfield_values.php?field_id=${fieldId}`);
const data = await response.json();
if (data.error) {
console.error('Errore per field_id', fieldId, ':', data.error);
continue;
}
dropdownData[fieldId] = data.CustomFieldsValues || [];
} catch (error) {
console.error('Errore nel fetch per field_id', fieldId, ':', error);
}
}
}
// Popola tutti i dropdown con i dati recuperati
dropdowns.forEach(dropdown => {
const fieldId = dropdown.getAttribute('data-field-id');
if (!fieldId || !dropdownData[fieldId]) {
dropdown.innerHTML = '<option value="">Errore nel caricamento</option>';
return;
}
dropdown.innerHTML = '<option value="">Seleziona...</option>';
dropdownData[fieldId].forEach(value => {
const option = document.createElement('option');
option.value = value.IdCustomFieldsValue;
option.textContent = value.Valore;
if (dropdown.value === option.value) option.selected = true;
dropdown.appendChild(option);
});
});
}
// Esegui al caricamento della pagina con l'overlay
async function loadDropdownsWithOverlay() {
console.log('Inizio caricamento tendine');
loadingOverlay.style.display = 'flex';
await new Promise(resolve => setTimeout(resolve, 500)); // Minimo 500ms di visibilità
await populateDropdowns();
console.log('Caricamento tendine completato');
loadingOverlay.style.display = 'none';
}
// Esegui il caricamento iniziale
loadDropdownsWithOverlay();
// Rielabora i dropdown quando si aggiunge una nuova riga
document.querySelectorAll('.save-btn').forEach(btn => {
btn.addEventListener('click', function() {
setTimeout(loadDropdownsWithOverlay, 100);
});
});
// Gestione della propagazione
const propagateButtons = document.querySelectorAll('.propagate-btn');
propagateButtons.forEach(button => {
button.addEventListener('click', function() {
const columnIndex = this.getAttribute('data-column').replace('manual_', '');
const input = this.previousElementSibling;
const value = input.value;
const gridTopCells = document.querySelector('.grid-top').querySelectorAll('.grid-cell');
const targetTopIndex = Array.from(gridTopCells).findIndex(cell =>
cell.querySelector('.propagate-btn') === button
);
if (targetTopIndex !== -1) {
const rows = document.querySelectorAll('.grid-row');
rows.forEach(row => {
const cells = row.querySelectorAll('.grid-cell');
if (cells.length > targetTopIndex) {
const targetInput = cells[targetTopIndex].querySelector('select.dropdown-select');
if (targetInput) {
targetInput.value = value;
const event = new Event('change');
targetInput.dispatchEvent(event);
}
}
});
}
});
});
});
</script>
-->
</script> -->
</body>
</html>
</html>

View File

@ -0,0 +1,157 @@
<?php
ini_set('display_errors', 1);
ini_set('display_startup_errors', 1);
error_reporting(E_ALL);
ini_set('log_errors', 1);
ini_set('error_log', __DIR__ . '/import_debug.log');
if (!file_exists(__DIR__ . '/import_debug.log')) {
file_put_contents(__DIR__ . '/import_debug.log', "Inizio importazione alle " . date('Y-m-d H:i:s') . "\n", FILE_APPEND);
}
// Log iniziale
error_log("Inizio importazione alle " . date('Y-m-d H:i:s'));
include('include/headscript.php');
if ($_SERVER['REQUEST_METHOD'] !== 'POST' || !isset($_POST['template_id']) || !isset($_POST['selected_rows']) || !isset($_POST['filename'])) {
header("Location: xlstemplates_grid.php?status=error&message=" . urlencode("Richiesta non valida"));
exit;
}
$template_id = intval($_POST['template_id']);
$selected_rows = $_POST['selected_rows'];
$columns = json_decode($_POST['columns'], true); // Header dell'XLS
$rows = json_decode($_POST['rows'], true); // Dati dell'XLS
$newFilename = htmlspecialchars($_POST['filename']);
$_SESSION['template_id'] = $template_id;
$_SESSION['selected_rows'] = $selected_rows;
$_SESSION['columns'] = $columns;
$_SESSION['rows'] = $rows;
$_SESSION['filename'] = $newFilename;
error_log("Received Data - Template ID: $template_id, Selected Rows: " . json_encode($selected_rows));
error_log("Columns: " . json_encode($columns));
error_log("Rows: " . json_encode($rows));
$user_id = $iduserlogin ?? 1; // Default a 1 se non definito
$db = DBHandlerSelect::getInstance();
$pdo = $db->getConnection();
// Genera un UUID univoco per importreferencecode
$importReferenceCode = date('YmdHis') . '-' . uniqid();
// Recupera tutti i mapping dal template
$stmt = $pdo->prepare("SELECT id, excel_column, data_type, is_required, manual_default, is_manual, field_label, field_id, main_field FROM template_mapping WHERE template_id = ?");
$stmt->execute([$template_id]);
$allMappings = $stmt->fetchAll(PDO::FETCH_ASSOC);
if (empty($allMappings)) {
header("Location: import_xls.php?id=$template_id&status=error&message=" . urlencode("Nessun mapping trovato per il template"));
exit;
}
// Trova il campo main_field
$mainFieldMapping = null;
foreach ($allMappings as $mapping) {
if ($mapping['main_field'] == 1) {
$mainFieldMapping = $mapping;
break;
}
}
// Inserisci le righe selezionate in datadb (solo campi generici con templateid)
$insertedIds = [];
foreach ($selected_rows as $rowIndex) {
$row = $rows[$rowIndex];
$values = [
$template_id, // templateid
$importReferenceCode, // importreferencecode
$newFilename, // filename_import
'i', // status
$user_id, // user_id
null, // limscode
date('Y-m-d') // importdate
];
$sql = "INSERT INTO datadb (templateid, importreferencecode, filename_import, status, user_id, limscode, importdate) VALUES (?, ?, ?, ?, ?, ?, ?)";
$stmt = $pdo->prepare($sql);
$stmt->execute($values);
$iddatadb = $pdo->lastInsertId();
$insertedIds[] = $iddatadb;
// Inserisci tutti i campi (automatici e manuali) in import_data_details
foreach ($allMappings as $mapping) {
$fieldValue = null;
if (!$mapping['is_manual']) { // Campi automatici dall'XLS
$excelColumn = trim($mapping['excel_column']);
$excelColumnIndex = array_search($excelColumn, array_map('trim', $columns));
if ($excelColumnIndex !== false && isset($row[$excelColumnIndex]) && $row[$excelColumnIndex] !== '') {
$fieldValue = $row[$excelColumnIndex];
error_log("Found Excel column '$excelColumn' at index $excelColumnIndex, value: " . var_export($fieldValue, true));
} else {
$fieldValue = $mapping['manual_default'] ?? '';
error_log("Excel column '$excelColumn' not found or empty, using default: " . var_export($fieldValue, true));
}
switch ($mapping['data_type']) {
case 'INT':
$fieldValue = is_numeric($fieldValue) ? (int)$fieldValue : ($mapping['manual_default'] ?? 0);
break;
case 'DATE':
$fieldValue = !empty($fieldValue) ? date('Y-m-d', strtotime($fieldValue)) : ($mapping['manual_default'] === 'today' ? date('Y-m-d') : ($mapping['manual_default'] ?? ''));
break;
case 'CHAR':
$fieldValue = !empty($fieldValue) ? substr((string)$fieldValue, 0, 1) : ($mapping['manual_default'] ?? '');
break;
case 'Testo':
case 'VARCHAR':
default:
$fieldValue = !empty($fieldValue) ? htmlspecialchars((string)$fieldValue) : ($mapping['manual_default'] ?? '');
break;
}
} else { // Campi manuali
$fieldValue = $mapping['manual_default'] ?? '';
if ($mapping['data_type'] === 'DATE' && $mapping['manual_default'] === 'today') {
$fieldValue = date('Y-m-d');
}
}
if ($mapping['is_required'] && (is_null($fieldValue) || $fieldValue === '')) {
error_log("Required field missing for mapping ID: " . $mapping['id'] . ", field: " . $mapping['field_label']);
}
error_log("Inserting into import_data_details - Mapping ID: " . $mapping['id'] . ", Field Value: " . var_export($fieldValue, true) . ", Is Manual: " . $mapping['is_manual'] . ", Excel Column: " . ($mapping['excel_column'] ?? 'N/A') . ", Manual Default: " . ($mapping['manual_default'] ?? 'N/A'));
$stmt = $pdo->prepare("INSERT INTO import_data_details (id, mapping_id, field_value) VALUES (?, ?, ?)");
$stmt->execute([$iddatadb, $mapping['id'], $fieldValue]);
error_log("Inserted into import_data_details for ID $iddatadb, Mapping ID: " . $mapping['id'] . ", Field Value: " . var_export($fieldValue, true));
}
}
$_SESSION['inserted_ids'] = $insertedIds;
$params = [
'template_id' => $template_id,
'filename' => $newFilename,
];
?>
<form id="redirectForm" action="import_edit2.php" method="post">
<input type="hidden" name="template_id" value="<?= htmlspecialchars($template_id) ?>">
<input type="hidden" name="filename" value="<?= htmlspecialchars($newFilename) ?>">
<?php foreach ($selected_rows as $row): ?>
<input type="hidden" name="selected_rows[]" value="<?= htmlspecialchars($row) ?>">
<?php endforeach; ?>
<?php foreach ($insertedIds as $id): ?>
<input type="hidden" name="inserted_ids[]" value="<?= htmlspecialchars($id) ?>">
<?php endforeach; ?>
<input type="hidden" name="columns" value='<?= json_encode($columns) ?>'>
<input type="hidden" name="rows" value='<?= json_encode($rows) ?>'>
</form>
<script>
document.getElementById('redirectForm').submit();
</script>
<?php
exit;

View File

@ -164,12 +164,12 @@ error_log("Loaded template: " . print_r($template, true));
<!--end wrapper-->
<!-- search modal -->
<?php //include('include/searchmodal.php');
<?php //include('include/searchmodal.php');
?>
<!-- end search modal -->
<!--start switcher-->
<?php //include('include/themeswitcher.php');
<?php //include('include/themeswitcher.php');
?>
<!--end switcher-->
<?php include('jsinclude.php'); ?>
@ -205,7 +205,7 @@ error_log("Loaded template: " . print_r($template, true));
errorContainer.style.display = 'block';
} else {
let html = `
<form id="selectRowsForm" action="import_edit2.php" method="POST">
<form id="selectRowsForm" action="import_insert.php" method="POST">
<input type="hidden" name="template_id" value="${data.template_id}">
<input type="hidden" name="columns" value='${JSON.stringify(data.columns)}'>
<input type="hidden" name="rows" value='${JSON.stringify(data.rows)}'>
@ -326,4 +326,4 @@ error_log("Loaded template: " . print_r($template, true));
</script>
</body>
</html>
</html>

View File

@ -14,13 +14,18 @@ if (!$iddatadb) {
}
try {
$stmt = $pdo->prepare("SELECT file_path FROM datadb_photos WHERE iddatadb = :iddatadb LIMIT 1");
// Adjust the query to select all photo paths for the given iddatadb
$stmt = $pdo->prepare("SELECT file_path FROM datadb_photos WHERE iddatadb = :iddatadb");
$stmt->execute([':iddatadb' => $iddatadb]);
$photo = $stmt->fetch(PDO::FETCH_ASSOC);
$photos = $stmt->fetchAll(PDO::FETCH_ASSOC);
if ($photo && $photo['file_path']) {
$fullPath = '../photostrf/' . $photo['file_path']; // Assumi che le foto siano nella cartella photostrf
echo json_encode(['success' => true, 'file_path' => $fullPath]);
if ($photos && count($photos) > 0) {
// Prepare an array of full file paths
$photoPaths = array_map(function($photo) {
return '../photostrf/' . $photo['file_path']; // Assuming the photos are stored in the "photostrf" folder
}, $photos);
echo json_encode(['success' => true, 'photos' => $photoPaths]); // Return an array of photo paths
} else {
echo json_encode(['success' => false, 'message' => 'Nessuna foto trovata']);
}

View File

@ -13,29 +13,32 @@
<ul id="partsList" class="list-group"></ul>
<table class="table table-striped table-sm mt-3" id="partsTable">
<thead>
<tr>
<th>Num. Parte</th>
<th>Descrizione Parte</th>
<th>Azioni</th>
</tr>
<tr>
<th>Num. Parte</th>
<th>Descrizione Parte</th>
<th>Azioni</th>
</tr>
</thead>
<tbody id="partsTableBody">
<tr data-part-id="new">
<td><input type="number" class="form-control form-control-sm part-number" value="1" style="width: 80px;"></td>
<td><input type="text" class="form-control form-control-sm part-description" placeholder="Inserisci descrizione" style="width: 100%;"></td>
<td>
<button type="button" class="btn btn-success btn-sm add-row" style="padding: 0.1rem 0.3rem; font-size: 0.8rem;"><i class="fas fa-plus fa-xs"></i></button>
<button type="button" class="btn btn-primary btn-sm add-mix-row" style="padding: 0.1rem 0.3rem; font-size: 0.8rem;">M</button>
<button type="button" class="btn btn-danger btn-sm remove-row" style="padding: 0.1rem 0.3rem; font-size: 0.8rem; display: none;"><i class="fas fa-trash fa-xs"></i></button>
<span class="save-status text-success" style="display: none; margin-left: 5px;"><i class="fas fa-check fa-xs"></i></span>
<span class="save-loading text-warning" style="display: none; margin-left: 5px;"><i class="fas fa-spinner fa-spin fa-xs"></i></span>
</td>
</tr>
<tr data-part-id="new">
<td><input type="number" class="form-control form-control-sm part-number" value="1" style="width: 80px;"></td>
<td><input type="text" class="form-control form-control-sm part-description" placeholder="Inserisci descrizione" style="width: 100%;"></td>
<td>
<button type="button" class="btn btn-success btn-sm add-row" style="padding: 0.1rem 0.3rem; font-size: 0.8rem;"><i class="fas fa-plus fa-xs"></i></button>
<button type="button" class="btn btn-primary btn-sm add-mix-row" style="padding: 0.1rem 0.3rem; font-size: 0.8rem;">M</button>
<button type="button" class="btn btn-danger btn-sm remove-row" style="padding: 0.1rem 0.3rem; font-size: 0.8rem; display: none;"><i class="fas fa-trash fa-xs"></i></button>
<span class="save-status text-success" style="display: none; margin-left: 5px;"><i class="fas fa-check fa-xs"></i></span>
<span class="save-loading text-warning" style="display: none; margin-left: 5px;"><i class="fas fa-spinner fa-spin fa-xs"></i></span>
</td>
</tr>
</tbody>
</table>
</div>
<div class="col-md-6">
<h6>Foto del Campione</h6>
<div id="photoSelectorContainer" style="display: none;">
<!-- Dropdown or buttons for photo selection will appear here -->
</div>
<div style="position: relative; width: 100%; min-height: 400px;">
<img id="samplePhoto" src="" alt="Foto del campione" style="max-width: 100%; max-height: 100%; object-fit: contain; position: absolute; top: 0; left: 0;">
<canvas id="photoCanvas" style="position: absolute; top: 0; left: 0; width: 100%; height: 100%;"></canvas>
@ -142,4 +145,24 @@
padding: 0.1rem 0.3rem;
font-size: 0.8rem;
}
</style>
/* ნორმალური Save ღილაკი */
#savePhotoBtn {
transition: all 0.3s ease-in-out;
}
/* დაუმახსოვრებელი ცვლილებები */
#savePhotoBtn.unsaved {
background-color: #dc3545 !important; /* წითელი */
border-color: #dc3545 !important;
color: white !important;
animation: pulse 1.2s infinite;
}
/* ლამაზი პულსაცია */
@keyframes pulse {
0% { box-shadow: 0 0 0 0 rgba(220, 53, 69, 0.7); }
70% { box-shadow: 0 0 10px 15px rgba(220, 53, 69, 0); }
100% { box-shadow: 0 0 0 0 rgba(220, 53, 69, 0); }
}
</style>

View File

@ -1,7 +1,28 @@
$(document).ready(function () {
console.log("parts.js caricato correttamente");
// Gestione del popup per le parti
// ===================
// GLOBAL STATE (NEW)
// ===================
let photoData = {
naturalWidth: 0,
naturalHeight: 0,
displayWidth: 0,
displayHeight: 0,
scale: 1,
};
// markers keyed by photo src => [{ partNumber, x, y } using NATURAL coords]
let photoMarkers = {};
// selection & descriptions
let selectedPartNumber = null;
let descriptionPosition = {x: 10, y: 10}; // NATURAL coords
let hasDescriptions = false;
// ===================
// POPUP HANDLING
// ===================
const partsButtons = document.querySelectorAll(".parts-btn");
const partsModal = document.getElementById("partsModal");
const closeBtn = document.querySelector("#partsModal .close-btn");
@ -12,14 +33,8 @@ $(document).ready(function () {
console.log("Pulsante Parts cliccato");
const iddatadb = $(this).data("iddatadb");
const rowIndex = $(this).data("row");
const importRef = $("table tbody tr")
.eq(rowIndex)
.find("td")
.eq(1)
.text();
const description =
$("table tbody tr").eq(rowIndex).find("td").eq(2).text() ||
"Sconosciuto";
const importRef = $("table tbody tr").eq(rowIndex).find("td").eq(1).text();
const description = $("table tbody tr").eq(rowIndex).find("td").eq(2).text() || "Sconosciuto";
$("#trfHeader").text(`${iddatadb} - ${importRef} - ${description}`);
$("#partsModal").data("iddatadb", iddatadb);
@ -36,7 +51,6 @@ $(document).ready(function () {
});
});
// Gestione della chiusura del modal Parts
if (closeBtn) {
closeBtn.addEventListener("click", function () {
partsModal.style.display = "none";
@ -55,41 +69,26 @@ $(document).ready(function () {
});
}
// ===================
// PHOTO LOADERS
// ===================
function loadPhoto(iddatadb) {
$.ajax({
url: "load_photo.php",
method: "GET",
data: { iddatadb: iddatadb },
data: {iddatadb: iddatadb},
success: function (response) {
if (response.success && response.file_path) {
const img = $("#samplePhoto");
img.attr("src", response.file_path);
img.on("load", function () {
const container = img.parent();
const canvas = document.getElementById("photoCanvas");
const containerWidth = container.width();
const containerHeight = container.height();
const scaleX = containerWidth / img[0].naturalWidth;
const scaleY = containerHeight / img[0].naturalHeight;
const scale = Math.min(scaleX, scaleY);
canvas.width = img[0].naturalWidth * scale;
canvas.height = img[0].naturalHeight * scale;
canvas.style.width = `${containerWidth}px`;
canvas.style.height = `${containerHeight}px`;
const ctx = canvas.getContext("2d");
ctx.clearRect(0, 0, canvas.width, canvas.height);
ctx.drawImage(
img.get(0),
0,
0,
canvas.width,
canvas.height,
);
updateMarkers();
});
if (response.success) {
if (response.photos && response.photos.length > 1) {
showPhotoSelector(response.photos);
} else if (response.photos && response.photos.length === 1) {
loadSinglePhoto(response.photos[0]);
} else {
$("#samplePhoto").attr("src", "");
alert("Nessuna foto trovata per questo TRF.");
}
} else {
$("#samplePhoto").attr("src", "");
alert("Nessuna foto trovata per questo TRF.");
alert(response.message || "Errore nel caricamento della foto.");
}
},
error: function (xhr, status, error) {
@ -98,10 +97,89 @@ $(document).ready(function () {
});
}
function showPhotoSelector(photos) {
const selectorContainer = $("#photoSelectorContainer");
selectorContainer.empty();
const selector = $('<select id="photoSelector"></select>');
photos.forEach((photo, index) => {
const option = $('<option></option>').val(photo).text(`Photo ${index + 1}`);
// display option with photo name if available
if (photo.includes("/")) {
const photoName = photo.split("/").pop();
option.text(`Photo ${index + 1} - ${photoName}`);
}
selector.append(option);
});
selector.on("change", function () {
const selectedPhoto = $(this).val();
loadSinglePhoto(selectedPhoto);
});
selectorContainer.append(selector);
selectorContainer.show();
if (photos.length > 0) {
selector.val(photos[0]);
loadSinglePhoto(photos[0]);
}
}
function loadSinglePhoto(photoPath) {
const img = $("#samplePhoto");
img.off("load"); // avoid stacking multiple handlers
img.attr("src", photoPath);
img.on("load", function () {
const canvas = document.getElementById("photoCanvas");
const ctx = canvas.getContext("2d");
// Real image size
const naturalWidth = img[0].naturalWidth;
const naturalHeight = img[0].naturalHeight;
// Compute scale to FIT inside its parent without distorting aspect ratio
const parent = $(canvas).parent();
const maxW = parent.width();
const maxH = parent.height();
const scale = Math.min(maxW / naturalWidth, maxH / naturalHeight);
// Display size on screen
const displayWidth = Math.max(1, Math.round(naturalWidth * scale));
const displayHeight = Math.max(1, Math.round(naturalHeight * scale));
// Save globally
photoData = {naturalWidth, naturalHeight, displayWidth, displayHeight, scale};
// Canvas in REAL size (so saving uses natural coords 1:1)
canvas.width = naturalWidth;
canvas.height = naturalHeight;
// Visual size on screen
canvas.style.width = `${displayWidth}px`;
canvas.style.height = `${displayHeight}px`;
// Also size/align the overlay containers to match the canvas
$("#markerContainer").css({width: `${displayWidth}px`, height: `${displayHeight}px`});
$("#descriptionList").css({maxWidth: `${Math.max(200, Math.round(displayWidth * 0.35))}px`});
// Draw fresh image at full resolution
ctx.clearRect(0, 0, naturalWidth, naturalHeight);
ctx.drawImage(img.get(0), 0, 0, naturalWidth, naturalHeight);
updateMarkers();
if (hasDescriptions) drawDescriptions(descriptionPosition.x, descriptionPosition.y);
});
}
// ===================
// PARTS TABLE
// ===================
function addNewRow(nextPartNumber, isMix = false) {
const description = isMix ? "Mix" : "";
const newRow = `
<tr data-part-id="new">
<tr data-part-id="">
<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" style="width: 100%;"></td>
<td>
@ -111,33 +189,25 @@ $(document).ready(function () {
<span class="save-status text-success" style="display: none; margin-left: 5px;"><i class="fas fa-check fa-xs"></i></span>
<span class="save-loading text-warning" style="display: none; margin-left: 5px;"><i class="fas fa-spinner fa-spin fa-xs"></i></span>
</td>
</tr>
`;
</tr>`;
$("#partsTableBody").append(newRow);
updateRowButtons();
}
function updateRowButtons() {
const rowCount = $("#partsTableBody tr").length;
$("#partsTableBody tr").each(function (index) {
$("#partsTableBody tr").each(function () {
const $removeBtn = $(this).find(".remove-row");
if (rowCount > 1) {
$removeBtn.show();
} else {
$removeBtn.hide();
}
if (rowCount > 1) $removeBtn.show(); else $removeBtn.hide();
});
}
$(document).on("click", ".add-row", function (e) {
e.preventDefault();
console.log("Pulsante Aggiungi riga cliccato");
const maxPartNumber = Math.max(
...$("#partsTableBody tr")
.map(function () {
return parseInt($(this).find(".part-number").val()) || 0;
})
.get(),
...$("#partsTableBody tr").map(function () {
return parseInt($(this).find(".part-number").val()) || 0;
}).get(),
);
addNewRow(maxPartNumber + 1);
updatePartsList();
@ -145,13 +215,10 @@ $(document).ready(function () {
$(document).on("click", ".add-mix-row", function (e) {
e.preventDefault();
console.log("Pulsante Aggiungi Mix cliccato");
const maxPartNumber = Math.max(
...$("#partsTableBody tr")
.map(function () {
return parseInt($(this).find(".part-number").val()) || 0;
})
.get(),
...$("#partsTableBody tr").map(function () {
return parseInt($(this).find(".part-number").val()) || 0;
}).get(),
);
addNewRow(maxPartNumber + 1, true);
updatePartsList();
@ -159,26 +226,16 @@ $(document).ready(function () {
$(document).on("click", ".remove-row", function (e) {
e.preventDefault();
console.log("Pulsante Rimuovi riga cliccato");
const $row = $(this).closest("tr");
const partId = $row.data("part-id");
console.log("ID parte da eliminare:", partId);
if (partId !== "new" && partId !== undefined && partId !== null) {
console.log("Procedo con la cancellazione dal database");
$.ajax({
url: "delete_part.php",
method: "POST",
data: JSON.stringify({ part_id: partId }),
data: JSON.stringify({part_id: partId}),
contentType: "application/json",
beforeSend: function () {
console.log(
"Invio richiesta AJAX a delete_part.php con part_id:",
partId,
);
},
success: function (response) {
console.log("Risposta da delete_part.php:", response);
if (response.success) {
$row.remove();
updateRowButtons();
@ -189,21 +246,10 @@ $(document).ready(function () {
}
},
error: function (xhr, status, error) {
console.log("Errore AJAX:", status, error);
alert(
"Errore nell'eliminazione: " +
error +
". Stato: " +
xhr.status +
" - " +
xhr.responseText,
);
alert("Errore nell'eliminazione: " + error + ". Stato: " + xhr.status + " - " + xhr.responseText);
},
});
} else {
console.log(
'Riga non salvata nel database (partId = "new" o non definito), rimuovo solo visivamente',
);
$row.remove();
updateRowButtons();
updatePartsList();
@ -220,11 +266,8 @@ $(document).ready(function () {
const iddatadb = $("#partsModal").data("iddatadb");
const isMix = partDescription.startsWith("Mix") ? "Y" : "N";
console.log("Evento blur su input:", {
partNumber,
partDescription,
isMix,
});
// არსებული part-id row-დან (თუ უკვე არსებობს)
const partId = $row.data("part-id") || null;
if (partDescription && iddatadb) {
$saveLoading.show();
@ -237,6 +280,7 @@ $(document).ready(function () {
iddatadb: iddatadb,
parts: [
{
id: partId, // გავგზავნე part-ის ID (თუ არის)
part_number: partNumber,
part_description: partDescription,
mix: isMix,
@ -246,16 +290,14 @@ $(document).ready(function () {
contentType: "application/json",
success: function (response) {
if (response.success) {
if (response.part_id) {
$row.data("part-id", response.part_id);
console.log(
"Aggiornato partId della riga:",
response.part_id,
);
}
$saveLoading.hide();
$saveStatus.show();
updatePartsList();
// თუ ახალია, backend-მა მოგვცა ახალი ID
if (response.part_id) {
$row.attr("data-part-id", response.part_id);
$row.data("part-id", response.part_id);
}
setTimeout(() => $saveStatus.hide(), 2000);
} else {
alert("Errore nel salvataggio: " + response.message);
@ -274,7 +316,7 @@ $(document).ready(function () {
$.ajax({
url: "load_parts.php",
method: "GET",
data: { iddatadb: iddatadb },
data: {iddatadb: iddatadb},
success: function (response) {
if (response.success) {
$("#partsTableBody").empty();
@ -291,8 +333,7 @@ $(document).ready(function () {
<span class="save-status text-success" style="display: none; margin-left: 5px;"><i class="fas fa-check fa-xs"></i></span>
<span class="save-loading text-warning" style="display: none; margin-left: 5px;"><i class="fas fa-spinner fa-spin fa-xs"></i></span>
</td>
</tr>
`;
</tr>`;
$("#partsTableBody").append(newRow);
});
} else {
@ -301,10 +342,7 @@ $(document).ready(function () {
updateRowButtons();
updatePartsList();
} else {
alert(
"Errore nel caricamento delle parti: " +
response.message,
);
alert("Errore nel caricamento delle parti: " + response.message);
addNewRow(1);
}
},
@ -320,11 +358,7 @@ $(document).ready(function () {
$("#partsTableBody tr").each(function () {
const partNumber = $(this).find(".part-number").val();
const partDescription = $(this).find(".part-description").val();
if (
partNumber &&
partDescription &&
!partDescription.startsWith("Mix")
) {
if (partNumber && partDescription && !partDescription.startsWith("Mix")) {
const listItem = `
<li class="list-group-item" data-part-number="${partNumber}">
${partNumber} - ${partDescription}
@ -337,15 +371,10 @@ $(document).ready(function () {
$(document).on("click", ".add-to-mix-btn", function () {
const $listItem = $(this).closest("li");
const partDescription = $listItem.text().split(" - ")[1].trim(); // Prende tutta la descrizione dopo il trattino
const $mixRow = $("#partsTableBody tr")
.filter(function () {
return $(this)
.find(".part-description")
.val()
.startsWith("Mix");
})
.last();
const partDescription = $listItem.text().split(" - ")[1].trim();
const $mixRow = $("#partsTableBody tr").filter(function () {
return $(this).find(".part-description").val().startsWith("Mix");
}).last();
if ($mixRow.length === 0) {
alert("Crea prima una riga Mix usando il pulsante 'M'.");
@ -360,167 +389,131 @@ $(document).ready(function () {
} else if (!currentDescription.includes(partDescription)) {
currentDescription += ` + ${partDescription}`;
} else {
return; // Parte già presente, non aggiungerla
return;
}
$descriptionInput.val(currentDescription);
$descriptionInput.trigger("blur"); // Attiva il salvataggio
$descriptionInput.trigger("blur");
updatePartsList();
});
let selectedPartNumber = null;
let markers = [];
let descriptionPosition = { x: 10, y: 10 };
let hasDescriptions = false;
$("#partsList").on("click", "li", function (e) {
if ($(e.target).hasClass("add-to-mix-btn")) return;
selectedPartNumber = $(this).data("part-number");
console.log("Part number selezionato:", selectedPartNumber);
$(this).addClass("active").siblings().removeClass("active");
});
// ===================
// MARKERS & DESCRIPTIONS
// ===================
const canvas = document.getElementById("photoCanvas");
const ctx = canvas.getContext("2d");
$("#markerContainer").on("click", function (e) {
console.log("Click sul markerContainer rilevato");
if (selectedPartNumber !== null) {
const img = $("#samplePhoto");
const canvas = document.getElementById("photoCanvas");
const rect = canvas.getBoundingClientRect();
const container = img.parent();
const containerWidth = container.width();
const containerHeight = container.height();
const scaleX = containerWidth / img.get(0).naturalWidth;
const scaleY = containerHeight / img.get(0).naturalHeight;
const scale = Math.min(scaleX, scaleY);
const x = (e.clientX - rect.left) / scale;
const y = (e.clientY - rect.top) / scale;
if (selectedPartNumber === null) return;
console.log("Coordinate cliccate (x, y):", x, y);
const rect = canvas.getBoundingClientRect();
const clickX = e.clientX - rect.left;
const clickY = e.clientY - rect.top;
const existingMarker = markers.find(
(m) => m.partNumber == selectedPartNumber,
);
if (existingMarker) {
existingMarker.x = x;
existingMarker.y = y;
} else {
markers.push({ partNumber: selectedPartNumber, x, y });
}
console.log("Markers aggiornati:", markers);
updateMarkers();
if (hasDescriptions) {
drawDescriptions(descriptionPosition.x, descriptionPosition.y);
}
selectedPartNumber = null;
$("#partsList li").removeClass("active");
const x = clickX / photoData.scale; // convert to NATURAL coords
const y = clickY / photoData.scale;
const currentPhoto = $("#samplePhoto").attr("src");
if (!photoMarkers[currentPhoto]) photoMarkers[currentPhoto] = [];
const existingMarker = photoMarkers[currentPhoto].find(m => m.partNumber == selectedPartNumber);
if (existingMarker) {
existingMarker.x = x;
existingMarker.y = y;
} else {
console.log("Nessun part number selezionato");
photoMarkers[currentPhoto].push({partNumber: selectedPartNumber, x, y});
}
updateMarkers();
if (hasDescriptions) drawDescriptions(descriptionPosition.x, descriptionPosition.y);
selectedPartNumber = null;
$("#partsList li").removeClass("active");
});
function updateMarkers() {
const img = $("#samplePhoto");
const container = img.parent();
const containerWidth = container.width();
const containerHeight = container.height();
const scaleX = containerWidth / img.get(0).naturalWidth;
const scaleY = containerHeight / img.get(0).naturalHeight;
const scale = Math.min(scaleX, scaleY);
const markerContainer = $("#markerContainer");
markerContainer.empty();
// keep overlay sized to canvas display
markerContainer.css({width: `${photoData.displayWidth}px`, height: `${photoData.displayHeight}px`});
const currentPhoto = $("#samplePhoto").attr("src");
const markers = photoMarkers[currentPhoto] || [];
markers.forEach((marker) => {
const scaledX = marker.x * scale;
const scaledY = marker.y * scale;
console.log(
"Aggiungo marker:",
marker.partNumber,
"a posizione (scaledX, scaledY):",
scaledX,
scaledY,
);
const $marker = $(
`<div class="draggable-marker">${marker.partNumber}</div>`,
).css({
const scaledX = marker.x * photoData.scale;
const scaledY = marker.y * photoData.scale;
const $marker = $(`<div class="draggable-marker">${marker.partNumber}</div>`).css({
left: scaledX - 8 + "px",
top: scaledY - 8 + "px",
});
markerContainer.append($marker);
makeDraggable($marker, marker, scale);
makeDraggable($marker, marker);
});
}
function makeDraggable($element, item, scale) {
function makeDraggable($element, item) {
let isDragging = false;
let currentX = parseFloat($element.css("left")) || 0;
let currentY = parseFloat($element.css("top")) || 0;
let initialX, initialY;
let startLeft = 0;
let startTop = 0;
let initialX = 0;
let initialY = 0;
$element.on("mousedown", function (e) {
e.preventDefault();
isDragging = true;
initialX = e.clientX - currentX;
initialY = e.clientY - currentY;
startLeft = parseFloat($element.css("left")) || 0;
startTop = parseFloat($element.css("top")) || 0;
initialX = e.clientX - startLeft;
initialY = e.clientY - startTop;
$element.css("z-index", 1001);
});
$(document).on("mousemove", function (e) {
if (isDragging) {
e.preventDefault();
currentX = e.clientX - initialX;
currentY = e.clientY - initialY;
const container = $("#photoCanvas").parent();
const containerWidth = container.width();
const containerHeight = container.height();
const maxX = containerWidth - $element.width();
const maxY = containerHeight - $element.height();
$(document).on("mousemove.dragMarker", function (e) {
if (!isDragging) return;
let currentX = e.clientX - initialX;
let currentY = e.clientY - initialY;
currentX = Math.max(0, Math.min(currentX, maxX));
currentY = Math.max(0, Math.min(currentY, maxY));
const maxX = photoData.displayWidth - $element.width();
const maxY = photoData.displayHeight - $element.height();
$element.css({
left: currentX + "px",
top: currentY + "px",
});
currentX = Math.max(0, Math.min(currentX, maxX));
currentY = Math.max(0, Math.min(currentY, maxY));
if (item.partNumber) {
item.x = (currentX + 8) / scale;
item.y = (currentY + 8) / scale;
} else {
descriptionPosition.x = (currentX + 5) / scale;
descriptionPosition.y = (currentY + 5) / scale;
}
$element.css({left: currentX + "px", top: currentY + "px"});
if (item && item.partNumber) {
item.x = (currentX + 8) / photoData.scale;
item.y = (currentY + 8) / photoData.scale;
} else {
// draggable description panel
descriptionPosition.x = (currentX + 5) / photoData.scale;
descriptionPosition.y = (currentY + 5) / photoData.scale;
}
});
$(document).on("mouseup", function () {
$(document).on("mouseup.dragMarker", function () {
if (!isDragging) return;
isDragging = false;
$element.css("z-index", 1000);
$(document).off("mousemove.dragMarker mouseup.dragMarker");
});
}
function drawDescriptions(x, y) {
const img = $("#samplePhoto");
const container = img.parent();
const containerWidth = container.width();
const containerHeight = container.height();
const scaleX = containerWidth / img.get(0).naturalWidth;
const scaleY = containerHeight / img.get(0).naturalHeight;
const scale = Math.min(scaleX, scaleY);
const partsList = [];
$("#partsTableBody tr").each(function () {
const partNumber = $(this).find(".part-number").val();
const partDescription = $(this).find(".part-description").val();
if (
partNumber &&
partDescription &&
!partDescription.startsWith("Mix")
) {
if (partNumber && partDescription && !partDescription.startsWith("Mix")) {
partsList.push(`${partNumber} ${partDescription}`);
}
});
@ -529,148 +522,223 @@ $(document).ready(function () {
descriptionList.empty();
descriptionList.css({
display: "block",
top: y * scale + "px",
left: x * scale + "px",
width: "200px",
});
partsList.forEach((part) => {
descriptionList.append(`<div>${part}</div>`);
top: y * photoData.scale + "px",
left: x * photoData.scale + "px",
});
partsList.forEach((part) => descriptionList.append(`<div>${part}</div>`));
updateMarkers();
}
function clearCanvasMarkers() {
markers = [];
const currentPhoto = $("#samplePhoto").attr("src");
photoMarkers[currentPhoto] = [];
hasDescriptions = false;
$("#descriptionList").css("display", "none");
$("#markerContainer").empty();
const canvas = document.getElementById("photoCanvas");
const img = $("#samplePhoto");
const ctx = canvas.getContext("2d");
const container = img.parent();
const containerWidth = container.width();
const containerHeight = container.height();
const scaleX = containerWidth / img.get(0).naturalWidth;
const scaleY = containerHeight / img.get(0).naturalHeight;
const scale = Math.min(scaleX, scaleY);
canvas.width = img.get(0).naturalWidth * scale;
canvas.height = img.get(0).naturalHeight * scale;
const canvas = document.getElementById("photoCanvas");
const ctx = canvas.getContext("2d");
// reset canvas to current image (keeps proportions)
canvas.width = photoData.naturalWidth;
canvas.height = photoData.naturalHeight;
canvas.style.width = `${photoData.displayWidth}px`;
canvas.style.height = `${photoData.displayHeight}px`;
const img = $("#samplePhoto");
ctx.clearRect(0, 0, canvas.width, canvas.height);
ctx.drawImage(img.get(0), 0, 0, canvas.width, canvas.height);
if (img[0].naturalWidth) {
ctx.drawImage(img.get(0), 0, 0, canvas.width, canvas.height);
}
}
$("#addDescriptionsBtn").on("click", function () {
hasDescriptions = true;
descriptionPosition = { x: 10, y: 10 };
descriptionPosition = {x: 10, y: 10};
drawDescriptions(descriptionPosition.x, descriptionPosition.y);
makeDraggable(
$("#descriptionList"),
descriptionPosition,
Math.min(
$("#photoCanvas").parent().width() /
$("#samplePhoto").get(0).naturalWidth,
$("#photoCanvas").parent().height() /
$("#samplePhoto").get(0).naturalHeight,
),
);
makeDraggable($("#descriptionList"));
});
$("#removeAnnotationsBtn").on("click", function () {
clearCanvasMarkers();
});
let unsavedChanges = false;
// --- helper functions ---
function markUnsaved() {
if (!unsavedChanges) {
unsavedChanges = true;
$("#savePhotoBtn").addClass("unsaved").text("⚠️ Salva Modifiche");
}
}
function clearUnsaved() {
unsavedChanges = false;
$("#savePhotoBtn").removeClass("unsaved").text("Salva Foto con Nome");
}
// --- event listeners ---
// როცა ვცვლით input-ს ცხრილში
$(document).on("input change", "#partsTableBody input", markUnsaved);
// როცა ვამატებთ/ვშლით რიგს
$(document).on("click", ".add-row, .add-mix-row, .remove-row", markUnsaved);
// თუ გაქვს draggable marker ან description list
$(document).on("markerChanged descriptionChanged", markUnsaved);
// --- modal close protection ---
$('#partsModal').on('hide.bs.modal', function (e) {
if (unsavedChanges) {
if (!confirm("Hai modifiche non salvate. Vuoi davvero uscire?")) {
e.preventDefault();
}
}
});
// --- SAVE BUTTON ---
$("#savePhotoBtn").on("click", function () {
const canvas = document.getElementById("photoCanvas");
const img = $("#samplePhoto");
const ctx = canvas.getContext("2d");
const img = $("#samplePhoto");
canvas.width = img.get(0).naturalWidth;
canvas.height = img.get(0).naturalHeight;
ctx.drawImage(img.get(0), 0, 0);
// Ensure canvas is real size
const naturalWidth = img.get(0).naturalWidth;
const naturalHeight = img.get(0).naturalHeight;
canvas.width = naturalWidth;
canvas.height = naturalHeight;
// Redraw base image
ctx.drawImage(img.get(0), 0, 0, naturalWidth, naturalHeight);
// Descriptions box
const partsList = [];
$("#partsTableBody tr").each(function () {
const partNumber = $(this).find(".part-number").val();
const partDescription = $(this).find(".part-description").val();
if (
partNumber &&
partDescription &&
!partDescription.startsWith("Mix")
) {
if (partNumber && partDescription && !partDescription.startsWith("Mix")) {
partsList.push(`${partNumber} ${partDescription}`);
}
});
if (hasDescriptions) {
ctx.fillStyle = "rgba(255, 255, 255, 0.8)";
ctx.fillRect(
descriptionPosition.x,
descriptionPosition.y,
200,
partsList.length * 12 + 10,
);
ctx.fillStyle = "#000000";
ctx.font = "10px Arial";
if (hasDescriptions && partsList.length > 0) {
const fontSize = Math.round(naturalWidth * 0.02);
ctx.font = fontSize + "px Arial";
const textHeight = fontSize + 8;
const boxWidth = Math.round(naturalWidth * 0.28);
const boxHeight = partsList.length * textHeight + 25;
const x = descriptionPosition.x;
const y = descriptionPosition.y;
// ჩრდილი
ctx.save();
ctx.shadowColor = "rgba(0,0,0,0.3)";
ctx.shadowBlur = 8;
ctx.shadowOffsetX = 3;
ctx.shadowOffsetY = 3;
// ლამაზი ბექგრაუნდი
ctx.fillStyle = "rgba(255, 255, 255, 0.9)";
ctx.beginPath();
ctx.roundRect(x, y, boxWidth, boxHeight, 12);
ctx.fill();
ctx.restore();
// ტექსტი
ctx.fillStyle = "#111111";
partsList.forEach((part, index) => {
ctx.fillText(
part,
descriptionPosition.x + 5,
descriptionPosition.y + 12 + index * 12,
);
const domWidth = $("#samplePhoto").width();
const domHeight = $("#samplePhoto").height();
// NATURAL ზომა (ფაილის რეალური ზომა)
const naturalWidth = photoData.naturalWidth;
const naturalHeight = photoData.naturalHeight;
// მასშტაბები
const scaleX = naturalWidth / domWidth;
const scaleY = naturalHeight / domHeight;
// გადაყვანილი კოორდინატები
const x = descriptionPosition.x * scaleX;
const y = descriptionPosition.y * scaleY;
ctx.fillText(part, x + 15, y + 35 + index * textHeight);
});
}
// Markers
const currentPhoto = $("#samplePhoto").attr("src");
const markers = photoMarkers[currentPhoto] || [];
markers.forEach((marker) => {
const x = marker.x; // already NATURAL coords
const y = marker.y;
const radius = Math.max(5, Math.round(naturalWidth * 0.025));
const fontSize = Math.max(8, Math.round(radius * 0.9));
ctx.beginPath();
ctx.arc(marker.x, marker.y, 8, 0, 2 * Math.PI);
ctx.fillStyle = "rgba(255, 0, 0, 0.5)";
ctx.arc(x, y, radius, 0, 2 * Math.PI);
ctx.fillStyle = "rgba(255,0,0,0.85)";
ctx.fill();
ctx.strokeStyle = "#ff0000";
ctx.lineWidth = 1;
ctx.lineWidth = 3;
ctx.strokeStyle = "#ffffff";
ctx.stroke();
ctx.fillStyle = "#ffffff";
ctx.font = "bold 8px Arial";
ctx.font = `bold ${fontSize}px Arial`;
ctx.textAlign = "center";
ctx.textBaseline = "middle";
ctx.fillText(marker.partNumber, marker.x, marker.y);
ctx.fillText(marker.partNumber || "", x, y);
});
const dataURL = canvas.toDataURL("image/png");
const timestamp = new Date().toISOString().replace(/[:.]/g, "-");
const defaultName = `photo_${$("#partsModal").data("iddatadb")}_${timestamp}.png`;
const newName = prompt(
"Inserisci il nome del file (senza estensione):",
defaultName.split(".png")[0],
);
const iddatadb = $("#partsModal").data("iddatadb");
const defaultName = `photo_${iddatadb}_${timestamp}.png`;
const newName = prompt("Inserisci il nome del file (senza estensione):", defaultName.split(".png")[0]);
if (newName) {
const finalName = newName + "_" + timestamp + ".png";
$.ajax({
url: "save_annotated_photo.php",
method: "POST",
data: { dataURL: dataURL, filename: finalName },
data: {
dataURL: dataURL,
filename: finalName,
iddatadb: iddatadb
},
success: function (response) {
if (response.success) {
alert(
"Foto salvata con successo: " + response.file_path,
);
alert("Foto salvata con successo: " + response.file_path);
$("#samplePhoto").attr("src", response.file_path);
loadPhoto(iddatadb);
clearCanvasMarkers();
clearUnsaved(); // ✅ reset unsaved status
} else {
alert("Errore nel salvataggio: " + response.message);
alert("Errore: " + response.message);
}
},
error: function (xhr, status, error) {
alert("Errore nel salvataggio della foto: " + error);
alert("Errore Ajax: " + error);
},
});
}
});
// ===================
// DEBUG HOVER LOGS
// ===================
$(document).on("mouseenter", "tr", function () {
console.log("Mouse entrato su riga");
// console.log("Mouse entrato su riga");
});
$(document).on("mouseleave", "tr", function () {
console.log("Mouse uscito da riga");
// console.log("Mouse uscito da riga");
});
});

View File

@ -44,7 +44,7 @@ $photos = $stmt->fetchAll(PDO::FETCH_ASSOC);
$photoBasePath = '../photostrf/';
// Genera l'URL per il QR code
$baseUrl = "http://localhost/trf_certest/public/userarea/"; // Sostituisci con il tuo dominio
$baseUrl = "http://localhost:8000/userarea/"; // Sostituisci con il tuo dominio
$uploadUrl = $baseUrl . "upload_photos_mobile.php?iddatadb=" . $iddatadb;
// Genera il QR code con endroid/qr-code 6.0.6
@ -224,4 +224,4 @@ $result->saveToFile($qrCodeFile);
font-size: 16px;
color: white;
}
</style>
</style>

View File

@ -1,25 +1,53 @@
<?php
header('Content-Type: application/json');
include('include/headscript.php');
include('include/headscript.php'); // აქედან უნდა იყოს DB კავშირიც
error_reporting(E_ALL);
ini_set('display_errors', 1);
$dataURL = $_POST['dataURL'] ?? null;
$filename = $_POST['filename'] ?? null;
$dataURL = $_POST['dataURL'] ?? null;
$filename = $_POST['filename'] ?? null;
$iddatadb = $_POST['iddatadb'] ?? null; // 🟢 ახალი ველი
if (!$dataURL || !$filename) {
if (!$dataURL || !$filename || !$iddatadb) {
echo json_encode(['success' => false, 'message' => 'Dati mancanti']);
exit;
}
try {
// --- ფაილის შენახვა ---
$data = explode(',', $dataURL)[1];
$decodedData = base64_decode($data);
$filePath = '../photostrf/annotated/' . $filename; // Crea una sottocartella 'annotated' per le foto modificate
if (!file_exists('../photostrf/annotated')) {
mkdir('../photostrf/annotated', 0777, true);
$dirPath = '../photostrf/annotated';
if (!file_exists($dirPath)) {
mkdir($dirPath, 0777, true);
}
$filePath = $dirPath . '/' . $filename;
file_put_contents($filePath, $decodedData);
echo json_encode(['success' => true, 'file_path' => $filePath, 'message' => 'Foto salvata con successo']);
$db = DBHandlerSelect::getInstance();
$pdo = $db->getConnection();
// --- ბაზაში ჩაწერა ---
$stmt = $pdo->prepare("
INSERT INTO datadb_photos (iddatadb, file_path, file_name, uploaded_at, uploaded_by)
VALUES (:iddatadb, :file_path, :file_name, NOW(), :uploaded_by)
");
$stmt->execute([
':iddatadb' => $iddatadb,
':file_path' => $filePath,
':file_name' => $filename,
':uploaded_by'=> $iduserlogin
]);
echo json_encode([
'success' => true,
'file_path' => $filePath,
'message' => 'Foto salvata con successo e registrata nel DB'
]);
} catch (Exception $e) {
echo json_encode(['success' => false, 'message' => 'Errore nel salvataggio: ' . $e->getMessage()]);
echo json_encode(['success' => false, 'message' => 'Errore: ' . $e->getMessage()]);
}

View File

@ -14,28 +14,64 @@ try {
$db = DBHandlerSelect::getInstance();
$pdo = $db->getConnection();
// Prepara i dati da aggiornare
$updates = [];
$values = [];
foreach ($_POST as $key => $value) {
if ($key !== 'iddatadb') {
$updates[] = "$key = ?";
$values[] = htmlspecialchars($value);
$data = $_POST;
$details = [];
// 1. POST-დან ამოვიღოთ მხოლოდ details
foreach ($data as $key => $value) {
if (preg_match('/^details(\d+)field_value$/', $key, $matches)) {
$id = $matches[1];
$details[$id] = $value;
}
}
$values[] = $iddatadb;
if (empty($updates)) {
throw new Exception('Nessun dato da aggiornare');
// 2. DB-დან წამოვიღოთ არსებული მნიშვნელობები
$stmt = $pdo->prepare("SELECT mapping_id, field_value FROM import_data_details WHERE id = ?");
$stmt->execute([$iddatadb]);
$currentValues = [];
while ($row = $stmt->fetch(PDO::FETCH_ASSOC)) {
$currentValues[$row['mapping_id']] = $row['field_value'];
}
$sql = "UPDATE datadb SET " . implode(', ', $updates) . " WHERE iddatadb = ?";
$stmt = $pdo->prepare($sql);
$stmt->execute($values);
// 3. შევადაროთ POST-ს და DB-ს
$changed = [];
foreach ($details as $id => $newValue) {
$oldValue = $currentValues[$id] ?? null;
if ($oldValue !== $newValue) {
$changed[$id] = [
'old' => $oldValue,
'new' => $newValue
];
}
}
// 4. თუ არის ცვლილებები → UPDATE
if (!empty($changed)) {
$updateStmt = $pdo->prepare("
UPDATE import_data_details
SET field_value = :newValue
WHERE id = :iddatadb AND mapping_id = :mappingId
");
foreach ($changed as $mappingId => $values) {
$updateStmt->execute([
':newValue' => $values['new'],
':iddatadb' => $iddatadb,
':mappingId' => $mappingId
]);
}
$response['success'] = true;
$response['message'] = "Updated successfully";
$response['changed'] = $changed; // Debug / optional
} else {
$response['success'] = true;
$response['message'] = "No changes found";
}
$response['success'] = true;
$response['message'] = 'Riga aggiornata con successo';
} catch (Exception $e) {
$response['success'] = false;
$response['message'] = $e->getMessage();
error_log("Errore in save_edited_row.php: " . $e->getMessage());
}

View File

@ -1,6 +1,5 @@
<?php
header('Content-Type: application/json');
include('include/headscript.php');
$dbHandler = DBHandlerSelect::getInstance();
@ -17,20 +16,42 @@ if (!$iddatadb || empty($parts)) {
}
$part = $parts[0];
$partId = $part['id'] ?? null; // part_id თუ არსებობს
$partNumber = $part['part_number'] ?? null;
$partDescription = $part['part_description'] ?? '';
$mix = $part['mix'] ?? 'N'; // Aggiunto per gestire il campo mix
$mix = $part['mix'] ?? 'N';
if ($partDescription) {
try {
$stmt = $pdo->prepare("INSERT INTO identification_parts (iddatadb, part_number, part_description, mix, created_at, updated_at) VALUES (:iddatadb, :part_number, :part_description, :mix, NOW(), NOW())");
$stmt->execute([
':iddatadb' => $iddatadb,
':part_number' => $partNumber,
':part_description' => $partDescription,
':mix' => $mix
]);
echo json_encode(['success' => true, 'message' => 'Parte salvata con successo']);
if ($partId) {
// UPDATE თუ უკვე არსებობს part
$stmt = $pdo->prepare("UPDATE identification_parts
SET part_number = :part_number,
part_description = :part_description,
mix = :mix,
updated_at = NOW()
WHERE id = :id");
$stmt->execute([
':id' => $partId,
':part_number' => $partNumber,
':part_description' => $partDescription,
':mix' => $mix
]);
echo json_encode(['success' => true, 'part_id' => $partId, 'part_number'=>$partNumber, 'message' => 'Parte aggiornata con successo']);
} else {
// INSERT თუ ახალია
$stmt = $pdo->prepare("INSERT INTO identification_parts
(iddatadb, part_number, part_description, mix, created_at, updated_at)
VALUES (:iddatadb, :part_number, :part_description, :mix, NOW(), NOW())");
$stmt->execute([
':iddatadb' => $iddatadb,
':part_number' => $partNumber,
':part_description' => $partDescription,
':mix' => $mix
]);
$newId = $pdo->lastInsertId();
echo json_encode(['success' => true, 'part_id' => $newId, 'part_number'=>$partNumber, 'message' => 'Parte salvata con successo']);
}
} catch (PDOException $e) {
echo json_encode(['success' => false, 'message' => 'Errore nel salvataggio: ' . $e->getMessage()]);
}

View File

@ -142,4 +142,4 @@ $sampleCode = $row['sample_code'] ?? 'Non disponibile';
</script>
</body>
</html>
</html>

View File

@ -42,6 +42,7 @@
<script src="{{ url(mix('assets/js/vendor.js')) }}"></script>
<script src="{{ url('assets/js/as/app.js') }}"></script>
<script src="{{ url('assets/js/alpinejs.js') }}"></script>
@yield('scripts')
@hook('app:scripts')

View File

@ -188,3 +188,11 @@ Route::group(['prefix' => 'install'], function () {
Route::get('complete', 'InstallController@complete')->name('install.complete');
Route::get('error', 'InstallController@error')->name('install.error');
});
use App\Vanguard\Http\Controllers\Userarea\UploadPhotosMobileController;
Route::get('/userarea/upload-photos-mobile', [UploadPhotosMobileController::class, 'index'])
->name('userarea.upload-photos-mobile.index');
Route::post('/userarea/upload-photos-mobile', [UploadPhotosMobileController::class, 'upload'])
->name('userarea.upload-photos-mobile.upload');