Compare commits
16 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| bb34b775bd | |||
| 8b08969c69 | |||
| 34d4dc8660 | |||
| 1510ef03f1 | |||
| ce8c95921f | |||
| 095a6ae879 | |||
| 296143016a | |||
| 3aa2504f3c | |||
| c1a396f246 | |||
| a45ba1c8b3 | |||
| 7a944a73f7 | |||
| 71595cc8de | |||
| f89dbd0c23 | |||
| 9ba859e15b | |||
| 672e448e9a | |||
| d692614f70 |
@@ -1,47 +0,0 @@
|
|||||||
APP_ENV=production
|
|
||||||
APP_DEBUG=true
|
|
||||||
APP_KEY=base64:C+sutHm6xP5sE4QXhoZFhYjArlVN11s2mDU1F8beUkM=
|
|
||||||
APP_URL=http://vanguard.test
|
|
||||||
|
|
||||||
LOG_CHANNEL=stack
|
|
||||||
|
|
||||||
DB_CONNECTION=mysql
|
|
||||||
DB_HOST="localhost"
|
|
||||||
DB_DATABASE="trfcertest"
|
|
||||||
DB_USERNAME="solocla"
|
|
||||||
DB_PASSWORD="!Massarosa2"
|
|
||||||
DB_PREFIX="auth_"
|
|
||||||
|
|
||||||
BROADCAST_DRIVER=log
|
|
||||||
CACHE_DRIVER=file
|
|
||||||
QUEUE_DRIVER=sync
|
|
||||||
SESSION_DRIVER=database
|
|
||||||
SESSION_LIFETIME=120
|
|
||||||
|
|
||||||
REDIS_HOST=127.0.0.1
|
|
||||||
REDIS_PASSWORD=null
|
|
||||||
REDIS_PORT=6379
|
|
||||||
|
|
||||||
MAIL_MAILER=mail
|
|
||||||
MAIL_FROM_NAME=Vanguard
|
|
||||||
MAIL_FROM_ADDRESS=vanguard@test.dev
|
|
||||||
MAIL_HOST=smtp.mailtrap.io
|
|
||||||
MAIL_PORT=2525
|
|
||||||
MAIL_USERNAME=null
|
|
||||||
MAIL_PASSWORD=null
|
|
||||||
MAIL_ENCRYPTION=null
|
|
||||||
|
|
||||||
PUSHER_APP_ID=
|
|
||||||
PUSHER_APP_KEY=
|
|
||||||
PUSHER_APP_SECRET=
|
|
||||||
PUSHER_APP_CLUSTER=mt1
|
|
||||||
|
|
||||||
MIX_PUSHER_APP_KEY="${PUSHER_APP_KEY}"
|
|
||||||
MIX_PUSHER_APP_CLUSTER="${PUSHER_APP_CLUSTER}"
|
|
||||||
|
|
||||||
# Credenziali API VisualLims
|
|
||||||
API_BASE_URL=https://93.43.5.102/limsapi
|
|
||||||
API_USERNAME=WebApiUser
|
|
||||||
API_PASSWORD=webapiuser01
|
|
||||||
|
|
||||||
BASE_URL=http://localhost:8000/userarea/
|
|
||||||
@@ -1,36 +0,0 @@
|
|||||||
* Trying 93.43.5.102:443...
|
|
||||||
* Connected to 93.43.5.102 (93.43.5.102) port 443
|
|
||||||
* ALPN: curl offers h2,http/1.1
|
|
||||||
* SSL connection using TLSv1.2 / ECDHE-RSA-AES256-GCM-SHA384
|
|
||||||
* ALPN: server accepted h2
|
|
||||||
* Server certificate:
|
|
||||||
* subject: C=FR; ST=Île-de-France; O=Bureau Veritas; CN=bvcpsitaly-elims.it
|
|
||||||
* start date: Feb 17 00:00:00 2025 GMT
|
|
||||||
* expire date: Feb 17 23:59:59 2026 GMT
|
|
||||||
* issuer: C=US; O=Corporation Service Company; CN=Corporation Service Company RSA OV SSL CA
|
|
||||||
* SSL certificate verify result: unable to get local issuer certificate (20), continuing anyway.
|
|
||||||
* using HTTP/2
|
|
||||||
* [HTTP/2] [1] OPENED stream for https://93.43.5.102/limsapi/api/authentication/authenticate
|
|
||||||
* [HTTP/2] [1] [:method: POST]
|
|
||||||
* [HTTP/2] [1] [:scheme: https]
|
|
||||||
* [HTTP/2] [1] [:authority: 93.43.5.102]
|
|
||||||
* [HTTP/2] [1] [:path: /limsapi/api/authentication/authenticate]
|
|
||||||
* [HTTP/2] [1] [content-type: application/json]
|
|
||||||
* [HTTP/2] [1] [accept: application/json]
|
|
||||||
* [HTTP/2] [1] [content-length: 51]
|
|
||||||
> POST /limsapi/api/authentication/authenticate HTTP/2
|
|
||||||
Host: 93.43.5.102
|
|
||||||
Content-Type: application/json
|
|
||||||
Accept: application/json
|
|
||||||
Content-Length: 51
|
|
||||||
|
|
||||||
< HTTP/2 200
|
|
||||||
< cache-control: max-age=0
|
|
||||||
< content-type: application/json; charset=utf-8
|
|
||||||
< server: Microsoft-IIS/10.0
|
|
||||||
< strict-transport-security: max-age=2592000
|
|
||||||
< x-powered-by: ASP.NET
|
|
||||||
< x-content-type-options: nosniff
|
|
||||||
< date: Thu, 04 Sep 2025 12:59:20 GMT
|
|
||||||
<
|
|
||||||
* Connection #0 to host 93.43.5.102 left intact
|
|
||||||
@@ -1,35 +0,0 @@
|
|||||||
* Trying 93.43.5.102:443...
|
|
||||||
* Connected to 93.43.5.102 (93.43.5.102) port 443
|
|
||||||
* ALPN: curl offers h2,http/1.1
|
|
||||||
* SSL connection using TLSv1.2 / ECDHE-RSA-AES256-GCM-SHA384
|
|
||||||
* ALPN: server accepted h2
|
|
||||||
* Server certificate:
|
|
||||||
* subject: C=FR; ST=Île-de-France; O=Bureau Veritas; CN=bvcpsitaly-elims.it
|
|
||||||
* start date: Feb 17 00:00:00 2025 GMT
|
|
||||||
* expire date: Feb 17 23:59:59 2026 GMT
|
|
||||||
* issuer: C=US; O=Corporation Service Company; CN=Corporation Service Company RSA OV SSL CA
|
|
||||||
* SSL certificate verify result: unable to get local issuer certificate (20), continuing anyway.
|
|
||||||
* using HTTP/2
|
|
||||||
* [HTTP/2] [1] OPENED stream for https://93.43.5.102/limsapi/api/odata/CustomField(1083)?$expand=CustomFieldsValues
|
|
||||||
* [HTTP/2] [1] [:method: GET]
|
|
||||||
* [HTTP/2] [1] [:scheme: https]
|
|
||||||
* [HTTP/2] [1] [:authority: 93.43.5.102]
|
|
||||||
* [HTTP/2] [1] [:path: /limsapi/api/odata/CustomField(1083)?$expand=CustomFieldsValues]
|
|
||||||
* [HTTP/2] [1] [authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJodHRwOi8vc2NoZW1hcy54bWxzb2FwLm9yZy93cy8yMDA1LzA1L2lkZW50aXR5L2NsYWltcy9uYW1laWRlbnRpZmllciI6IjQ5MiIsIlhhZlNlY3VyaXR5QXV0aFBhc3NlZCI6IlhhZlNlY3VyaXR5QXV0aFBhc3NlZCIsImh0dHA6Ly9zY2hlbWFzLnhtbHNvYXAub3JnL3dzLzIwMDUvMDUvaWRlbnRpdHkvY2xhaW1zL25hbWUiOiJXZWJBcGlVc2VyIiwiWGFmU2VjdXJpdHkiOiJYYWZTZWN1cml0eSIsIlhhZkxvZ29uUGFyYW1zIjoicTFZS0xVNHQ4a3ZNVFZXeVVncFBUWElzeUFRSktPa29CU1FXRjVmbkY2VUF4Y3RUa3hJTE1rdUI0Z2FHU3JVQSIsImV4cCI6MTc1Njk5Nzk2MCwiaXNzIjoiTXkiLCJhdWQiOiJodHRwOi8vbG9jYWxob3N0OjQyMDAifQ.zHug3m3GFz-uMMbRXi70DlHNfw0Islrxyy5vdJ7RKtA]
|
|
||||||
* [HTTP/2] [1] [accept: application/json]
|
|
||||||
> GET /limsapi/api/odata/CustomField(1083)?$expand=CustomFieldsValues HTTP/2
|
|
||||||
Host: 93.43.5.102
|
|
||||||
Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJodHRwOi8vc2NoZW1hcy54bWxzb2FwLm9yZy93cy8yMDA1LzA1L2lkZW50aXR5L2NsYWltcy9uYW1laWRlbnRpZmllciI6IjQ5MiIsIlhhZlNlY3VyaXR5QXV0aFBhc3NlZCI6IlhhZlNlY3VyaXR5QXV0aFBhc3NlZCIsImh0dHA6Ly9zY2hlbWFzLnhtbHNvYXAub3JnL3dzLzIwMDUvMDUvaWRlbnRpdHkvY2xhaW1zL25hbWUiOiJXZWJBcGlVc2VyIiwiWGFmU2VjdXJpdHkiOiJYYWZTZWN1cml0eSIsIlhhZkxvZ29uUGFyYW1zIjoicTFZS0xVNHQ4a3ZNVFZXeVVncFBUWElzeUFRSktPa29CU1FXRjVmbkY2VUF4Y3RUa3hJTE1rdUI0Z2FHU3JVQSIsImV4cCI6MTc1Njk5Nzk2MCwiaXNzIjoiTXkiLCJhdWQiOiJodHRwOi8vbG9jYWxob3N0OjQyMDAifQ.zHug3m3GFz-uMMbRXi70DlHNfw0Islrxyy5vdJ7RKtA
|
|
||||||
Accept: application/json
|
|
||||||
|
|
||||||
< HTTP/2 200
|
|
||||||
< cache-control: max-age=0
|
|
||||||
< content-type: application/json; odata.metadata=minimal; odata.streaming=true; charset=utf-8
|
|
||||||
< server: Microsoft-IIS/10.0
|
|
||||||
< strict-transport-security: max-age=2592000
|
|
||||||
< odata-version: 4.0
|
|
||||||
< x-powered-by: ASP.NET
|
|
||||||
< x-content-type-options: nosniff
|
|
||||||
< date: Thu, 04 Sep 2025 12:59:23 GMT
|
|
||||||
<
|
|
||||||
* Connection #0 to host 93.43.5.102 left intact
|
|
||||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -0,0 +1,23 @@
|
|||||||
|
# Swagger Codegen Ignore
|
||||||
|
# Generated by swagger-codegen https://github.com/swagger-api/swagger-codegen
|
||||||
|
|
||||||
|
# Use this file to prevent files from being overwritten by the generator.
|
||||||
|
# The patterns follow closely to .gitignore or .dockerignore.
|
||||||
|
|
||||||
|
# As an example, the C# client generator defines ApiClient.cs.
|
||||||
|
# You can make changes and tell Swagger Codgen to ignore just this file by uncommenting the following line:
|
||||||
|
#ApiClient.cs
|
||||||
|
|
||||||
|
# You can match any string of characters against a directory, file or extension with a single asterisk (*):
|
||||||
|
#foo/*/qux
|
||||||
|
# The above matches foo/bar/qux and foo/baz/qux, but not foo/bar/baz/qux
|
||||||
|
|
||||||
|
# You can recursively match patterns against a directory, file or extension with a double asterisk (**):
|
||||||
|
#foo/**/qux
|
||||||
|
# This matches foo/bar/qux, foo/baz/qux, and foo/bar/baz/qux
|
||||||
|
|
||||||
|
# You can also negate patterns with an exclamation (!).
|
||||||
|
# For example, you can ignore all files in a docs folder with the file extension .md:
|
||||||
|
#docs/*.md
|
||||||
|
# Then explicitly reverse the ignore rule for a single file:
|
||||||
|
#!docs/README.md
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
3.0.34
|
||||||
Binary file not shown.
File diff suppressed because it is too large
Load Diff
Binary file not shown.
Binary file not shown.
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -11,3 +11,7 @@
|
|||||||
2025-08-26 16:48:44 - Risposta non JSON valida: <?xml version="1.0" encoding="utf-8"?><edmx:Edmx Version="4.0" xmlns:edmx="http://docs.oasis-open.org/odata/ns/edmx"><edmx:DataServices><Schema Namespace="DevExpress.ExpressApp.SystemModule" xmlns="http://docs.oasis-open.org/odata/ns/edm"><EntityType Name="DashboardViewItemDescriptor"><Key><PropertyRef Name="ViewId" /></Key><Property Name="ViewId" Type="Edm.String" Nullable="false" /></EntityType><EntityType Name="DashboardOrganizationItem" BaseType="DevExpress.ExpressApp.NonPersistentLiteObject" Abstract="true"><Property Name="Visibility" Type="DevExpress.ExpressApp.Editors.ViewItemVisibility" Nullable="false" /></EntityType><EntityType Name="DashboardOrganizationItem_1OfIModelDashboardViewItem" BaseType="DevExpress.ExpressApp.SystemModule.DashboardOrganizationItem" Abstract="true" /><EntityType Name="ViewDashboardOrganizationItem" BaseType="DevExpress.ExpressApp.SystemModule.DashboardOrganizationItem_1OfIModelDashboardViewItem"><Property Name="ObjectType" Type="System.Type" /><Proper
|
2025-08-26 16:48:44 - Risposta non JSON valida: <?xml version="1.0" encoding="utf-8"?><edmx:Edmx Version="4.0" xmlns:edmx="http://docs.oasis-open.org/odata/ns/edmx"><edmx:DataServices><Schema Namespace="DevExpress.ExpressApp.SystemModule" xmlns="http://docs.oasis-open.org/odata/ns/edm"><EntityType Name="DashboardViewItemDescriptor"><Key><PropertyRef Name="ViewId" /></Key><Property Name="ViewId" Type="Edm.String" Nullable="false" /></EntityType><EntityType Name="DashboardOrganizationItem" BaseType="DevExpress.ExpressApp.NonPersistentLiteObject" Abstract="true"><Property Name="Visibility" Type="DevExpress.ExpressApp.Editors.ViewItemVisibility" Nullable="false" /></EntityType><EntityType Name="DashboardOrganizationItem_1OfIModelDashboardViewItem" BaseType="DevExpress.ExpressApp.SystemModule.DashboardOrganizationItem" Abstract="true" /><EntityType Name="ViewDashboardOrganizationItem" BaseType="DevExpress.ExpressApp.SystemModule.DashboardOrganizationItem_1OfIModelDashboardViewItem"><Property Name="ObjectType" Type="System.Type" /><Proper
|
||||||
2025-08-26 16:49:24 - Risposta non JSON valida: <?xml version="1.0" encoding="utf-8"?><edmx:Edmx Version="4.0" xmlns:edmx="http://docs.oasis-open.org/odata/ns/edmx"><edmx:DataServices><Schema Namespace="DevExpress.ExpressApp.SystemModule" xmlns="http://docs.oasis-open.org/odata/ns/edm"><EntityType Name="DashboardViewItemDescriptor"><Key><PropertyRef Name="ViewId" /></Key><Property Name="ViewId" Type="Edm.String" Nullable="false" /></EntityType><EntityType Name="DashboardOrganizationItem" BaseType="DevExpress.ExpressApp.NonPersistentLiteObject" Abstract="true"><Property Name="Visibility" Type="DevExpress.ExpressApp.Editors.ViewItemVisibility" Nullable="false" /></EntityType><EntityType Name="DashboardOrganizationItem_1OfIModelDashboardViewItem" BaseType="DevExpress.ExpressApp.SystemModule.DashboardOrganizationItem" Abstract="true" /><EntityType Name="ViewDashboardOrganizationItem" BaseType="DevExpress.ExpressApp.SystemModule.DashboardOrganizationItem_1OfIModelDashboardViewItem"><Property Name="ObjectType" Type="System.Type" /><Proper
|
2025-08-26 16:49:24 - Risposta non JSON valida: <?xml version="1.0" encoding="utf-8"?><edmx:Edmx Version="4.0" xmlns:edmx="http://docs.oasis-open.org/odata/ns/edmx"><edmx:DataServices><Schema Namespace="DevExpress.ExpressApp.SystemModule" xmlns="http://docs.oasis-open.org/odata/ns/edm"><EntityType Name="DashboardViewItemDescriptor"><Key><PropertyRef Name="ViewId" /></Key><Property Name="ViewId" Type="Edm.String" Nullable="false" /></EntityType><EntityType Name="DashboardOrganizationItem" BaseType="DevExpress.ExpressApp.NonPersistentLiteObject" Abstract="true"><Property Name="Visibility" Type="DevExpress.ExpressApp.Editors.ViewItemVisibility" Nullable="false" /></EntityType><EntityType Name="DashboardOrganizationItem_1OfIModelDashboardViewItem" BaseType="DevExpress.ExpressApp.SystemModule.DashboardOrganizationItem" Abstract="true" /><EntityType Name="ViewDashboardOrganizationItem" BaseType="DevExpress.ExpressApp.SystemModule.DashboardOrganizationItem_1OfIModelDashboardViewItem"><Property Name="ObjectType" Type="System.Type" /><Proper
|
||||||
2025-08-26 16:50:23 - Risposta non JSON valida: <?xml version="1.0" encoding="utf-8"?><edmx:Edmx Version="4.0" xmlns:edmx="http://docs.oasis-open.org/odata/ns/edmx"><edmx:DataServices><Schema Namespace="DevExpress.ExpressApp.SystemModule" xmlns="http://docs.oasis-open.org/odata/ns/edm"><EntityType Name="DashboardViewItemDescriptor"><Key><PropertyRef Name="ViewId" /></Key><Property Name="ViewId" Type="Edm.String" Nullable="false" /></EntityType><EntityType Name="DashboardOrganizationItem" BaseType="DevExpress.ExpressApp.NonPersistentLiteObject" Abstract="true"><Property Name="Visibility" Type="DevExpress.ExpressApp.Editors.ViewItemVisibility" Nullable="false" /></EntityType><EntityType Name="DashboardOrganizationItem_1OfIModelDashboardViewItem" BaseType="DevExpress.ExpressApp.SystemModule.DashboardOrganizationItem" Abstract="true" /><EntityType Name="ViewDashboardOrganizationItem" BaseType="DevExpress.ExpressApp.SystemModule.DashboardOrganizationItem_1OfIModelDashboardViewItem"><Property Name="ObjectType" Type="System.Type" /><Proper
|
2025-08-26 16:50:23 - Risposta non JSON valida: <?xml version="1.0" encoding="utf-8"?><edmx:Edmx Version="4.0" xmlns:edmx="http://docs.oasis-open.org/odata/ns/edmx"><edmx:DataServices><Schema Namespace="DevExpress.ExpressApp.SystemModule" xmlns="http://docs.oasis-open.org/odata/ns/edm"><EntityType Name="DashboardViewItemDescriptor"><Key><PropertyRef Name="ViewId" /></Key><Property Name="ViewId" Type="Edm.String" Nullable="false" /></EntityType><EntityType Name="DashboardOrganizationItem" BaseType="DevExpress.ExpressApp.NonPersistentLiteObject" Abstract="true"><Property Name="Visibility" Type="DevExpress.ExpressApp.Editors.ViewItemVisibility" Nullable="false" /></EntityType><EntityType Name="DashboardOrganizationItem_1OfIModelDashboardViewItem" BaseType="DevExpress.ExpressApp.SystemModule.DashboardOrganizationItem" Abstract="true" /><EntityType Name="ViewDashboardOrganizationItem" BaseType="DevExpress.ExpressApp.SystemModule.DashboardOrganizationItem_1OfIModelDashboardViewItem"><Property Name="ObjectType" Type="System.Type" /><Proper
|
||||||
|
2025-09-08 08:39:17 - Risposta non JSON valida: <?xml version="1.0" encoding="utf-8"?><edmx:Edmx Version="4.0" xmlns:edmx="http://docs.oasis-open.org/odata/ns/edmx"><edmx:DataServices><Schema Namespace="DevExpress.ExpressApp.SystemModule" xmlns="http://docs.oasis-open.org/odata/ns/edm"><EntityType Name="DashboardViewItemDescriptor"><Key><PropertyRef Name="ViewId" /></Key><Property Name="ViewId" Type="Edm.String" Nullable="false" /></EntityType><EntityType Name="DashboardOrganizationItem" BaseType="DevExpress.ExpressApp.NonPersistentLiteObject" Abstract="true"><Property Name="Visibility" Type="DevExpress.ExpressApp.Editors.ViewItemVisibility" Nullable="false" /></EntityType><EntityType Name="DashboardOrganizationItem_1OfIModelDashboardViewItem" BaseType="DevExpress.ExpressApp.SystemModule.DashboardOrganizationItem" Abstract="true" /><EntityType Name="ViewDashboardOrganizationItem" BaseType="DevExpress.ExpressApp.SystemModule.DashboardOrganizationItem_1OfIModelDashboardViewItem"><Property Name="ObjectType" Type="System.Type" /><Proper
|
||||||
|
2025-09-16 13:37:03 - Autenticazione fallita: HTTP 400, Errore cURL: , Risposta: {"title":"Bad Request","status":400,"detail":"Cannot persist the object. It was modified or deleted (purged) by another application.","instance":"POST /api/authentication/authenticate","errorCode":"96bfc1252b"}
|
||||||
|
2025-09-16 13:37:51 - Autenticazione fallita: HTTP 400, Errore cURL: , Risposta: {"title":"Bad Request","status":400,"detail":"Cannot persist the object. It was modified or deleted (purged) by another application.","instance":"POST /api/authentication/authenticate","errorCode":"96bfc1252b"}
|
||||||
|
2025-09-16 13:44:24 - Autenticazione fallita: HTTP 400, Errore cURL: , Risposta: {"title":"Bad Request","status":400,"detail":"Cannot persist the object. It was modified or deleted (purged) by another application.","instance":"POST /api/authentication/authenticate","errorCode":"96bfc1252b"}
|
||||||
|
|||||||
@@ -0,0 +1,39 @@
|
|||||||
|
<?php
|
||||||
|
require_once dirname(__DIR__, 2) . '/vendor/autoload.php';
|
||||||
|
require_once dirname(__FILE__) . '/class/VisualLimsApiClient.class.php';
|
||||||
|
|
||||||
|
header('Content-Type: application/json');
|
||||||
|
|
||||||
|
ini_set('display_errors', '0');
|
||||||
|
error_reporting(E_ALL);
|
||||||
|
|
||||||
|
try {
|
||||||
|
$api = VisualLimsApiClient::getInstance();
|
||||||
|
|
||||||
|
// ID della CommessaWeb specifica (cambialo di volta in volta)
|
||||||
|
$id = 529435; // TODO: Cambia questo valore con l'ID desiderato
|
||||||
|
|
||||||
|
// Endpoint per recuperare la CommessaWeb specifica con espansione dello schema custom
|
||||||
|
$endpoint = "CommessaWeb({$id})";
|
||||||
|
|
||||||
|
// Opzioni per l'espansione: includi OrderCustomFields per ottenere i campi custom dello schema assegnato all'ordine
|
||||||
|
$options = ['$expand' => 'OrderCustomFields'];
|
||||||
|
|
||||||
|
// Debug: salva URL usato
|
||||||
|
$base_url = 'https://93.43.5.102/limsapi/api/odata/';
|
||||||
|
$query = http_build_query($options);
|
||||||
|
$full_url = $base_url . $endpoint . ($query ? '?' . $query : '');
|
||||||
|
file_put_contents(__DIR__ . '/last_url.txt', $full_url . PHP_EOL, FILE_APPEND);
|
||||||
|
|
||||||
|
// Chiamata API
|
||||||
|
$data = $api->get($endpoint, $options);
|
||||||
|
|
||||||
|
// Salva il JSON in locale
|
||||||
|
file_put_contents(__DIR__ . '/commessaweb_schema_response.json', json_encode($data, JSON_PRETTY_PRINT));
|
||||||
|
|
||||||
|
echo json_encode($data);
|
||||||
|
} catch (Exception $e) {
|
||||||
|
file_put_contents(__DIR__ . '/error_log.txt', date('Y-m-d H:i:s') . ' - ' . $e->getMessage() . PHP_EOL, FILE_APPEND);
|
||||||
|
http_response_code(500);
|
||||||
|
echo json_encode(['error' => $e->getMessage()]);
|
||||||
|
}
|
||||||
@@ -0,0 +1,36 @@
|
|||||||
|
<?php
|
||||||
|
require_once dirname(__DIR__, 2) . '/vendor/autoload.php';
|
||||||
|
require_once dirname(__FILE__) . '/class/VisualLimsApiClient.class.php';
|
||||||
|
|
||||||
|
header('Content-Type: application/json');
|
||||||
|
|
||||||
|
ini_set('display_errors', '0');
|
||||||
|
error_reporting(E_ALL);
|
||||||
|
|
||||||
|
try {
|
||||||
|
$api = VisualLimsApiClient::getInstance();
|
||||||
|
|
||||||
|
// Endpoint per recuperare le Matrici
|
||||||
|
$endpoint = 'Matrice';
|
||||||
|
|
||||||
|
// (Opzionale) aggiungi parametri, ad esempio $top per limitare i risultati
|
||||||
|
$options = []; // oppure ad esempio: ['$top' => 100]
|
||||||
|
|
||||||
|
// Debug: salva URL usato
|
||||||
|
$base_url = 'https://93.43.5.102/limsapi/api/odata/';
|
||||||
|
$query = http_build_query($options);
|
||||||
|
$full_url = $base_url . $endpoint . ($query ? '?' . $query : '');
|
||||||
|
file_put_contents(__DIR__ . '/last_url.txt', $full_url . PHP_EOL, FILE_APPEND);
|
||||||
|
|
||||||
|
// Chiamata API
|
||||||
|
$data = $api->get($endpoint, $options);
|
||||||
|
|
||||||
|
// Salva il JSON in locale
|
||||||
|
file_put_contents(__DIR__ . '/matrici_response.json', json_encode($data, JSON_PRETTY_PRINT));
|
||||||
|
|
||||||
|
echo json_encode($data);
|
||||||
|
} catch (Exception $e) {
|
||||||
|
file_put_contents(__DIR__ . '/error_log.txt', date('Y-m-d H:i:s') . ' - ' . $e->getMessage() . PHP_EOL, FILE_APPEND);
|
||||||
|
http_response_code(500);
|
||||||
|
echo json_encode(['error' => $e->getMessage()]);
|
||||||
|
}
|
||||||
@@ -128,6 +128,9 @@ foreach ($stmt->fetchAll(PDO::FETCH_ASSOC) as $row) {
|
|||||||
<link rel="icon" href="assets/images/favicon-32x32.png" type="image/png" />
|
<link rel="icon" href="assets/images/favicon-32x32.png" type="image/png" />
|
||||||
<?php include('cssinclude.php'); ?>
|
<?php include('cssinclude.php'); ?>
|
||||||
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.6.0/css/all.min.css" crossorigin="anonymous" referrerpolicy="no-referrer" />
|
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.6.0/css/all.min.css" crossorigin="anonymous" referrerpolicy="no-referrer" />
|
||||||
|
<link href="https://cdn.jsdelivr.net/npm/select2@4.1.0-rc.0/dist/css/select2.min.css" rel="stylesheet" />
|
||||||
|
<script src="https://code.jquery.com/jquery-3.6.0.min.js"></script>
|
||||||
|
<script src="https://cdn.jsdelivr.net/npm/select2@4.1.0-rc.0/dist/js/select2.min.js"></script>
|
||||||
<style>
|
<style>
|
||||||
input.auto-input,
|
input.auto-input,
|
||||||
select.auto-input {
|
select.auto-input {
|
||||||
@@ -947,7 +950,7 @@ foreach ($stmt->fetchAll(PDO::FETCH_ASSOC) as $row) {
|
|||||||
<?php include('include/footer.php'); ?>
|
<?php include('include/footer.php'); ?>
|
||||||
</div>
|
</div>
|
||||||
<?php include('jsinclude.php'); ?>
|
<?php include('jsinclude.php'); ?>
|
||||||
<script src="https://code.jquery.com/jquery-3.6.0.min.js"></script>
|
|
||||||
<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="photos.js"></script>
|
<script src="photos.js"></script>
|
||||||
<script src="parts.js"></script>
|
<script src="parts.js"></script>
|
||||||
|
|||||||
File diff suppressed because one or more lines are too long
+230
-150
@@ -21,8 +21,8 @@ if ($_SERVER['REQUEST_METHOD'] !== 'POST' || !isset($_POST['template_id']) || !i
|
|||||||
|
|
||||||
$template_id = intval($_POST['template_id']) ?? $_SESSION['template_id'];
|
$template_id = intval($_POST['template_id']) ?? $_SESSION['template_id'];
|
||||||
$selected_rows = $_POST['selected_rows'] ?? $_SESSION['selected_rows'];
|
$selected_rows = $_POST['selected_rows'] ?? $_SESSION['selected_rows'];
|
||||||
$columns = json_decode($_POST['columns'], true) ?? $_SESSION['columns']; // Header dell'XLS
|
$columns = json_decode($_POST['columns'], true) ?? $_SESSION['columns'];
|
||||||
$rows = json_decode($_POST['rows'], true) ?? $_SESSION['rows']; // Dati dell'XLS
|
$rows = json_decode($_POST['rows'], true) ?? $_SESSION['rows'];
|
||||||
$newFilename = htmlspecialchars($_POST['filename']) ?? $_SESSION['filename'];
|
$newFilename = htmlspecialchars($_POST['filename']) ?? $_SESSION['filename'];
|
||||||
|
|
||||||
// Log dei dati ricevuti
|
// Log dei dati ricevuti
|
||||||
@@ -31,7 +31,7 @@ error_log("Columns: " . json_encode($columns));
|
|||||||
error_log("Rows: " . json_encode($rows));
|
error_log("Rows: " . json_encode($rows));
|
||||||
|
|
||||||
// Recupera l'ID dell'utente loggato
|
// Recupera l'ID dell'utente loggato
|
||||||
$user_id = $iduserlogin ?? 1; // Default a 1 se non definito
|
$user_id = $iduserlogin ?? 1;
|
||||||
|
|
||||||
$db = DBHandlerSelect::getInstance();
|
$db = DBHandlerSelect::getInstance();
|
||||||
$pdo = $db->getConnection();
|
$pdo = $db->getConnection();
|
||||||
@@ -98,26 +98,26 @@ foreach ($stmt->fetchAll(PDO::FETCH_ASSOC) as $row) {
|
|||||||
<?php include('cssinclude.php'); ?>
|
<?php include('cssinclude.php'); ?>
|
||||||
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.5.1/css/all.min.css" integrity="sha512-DTOQO9RWCH3ppGqcWaEA1BIZOC6xxalwEsw9c2QQeAIftl+Vegovlnee1c9QX4TctnWMn13TZye+giMm8e2Lw==" crossorigin="anonymous" referrerpolicy="no-referrer" />
|
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.5.1/css/all.min.css" integrity="sha512-DTOQO9RWCH3ppGqcWaEA1BIZOC6xxalwEsw9c2QQeAIftl+Vegovlnee1c9QX4TctnWMn13TZye+giMm8e2Lw==" crossorigin="anonymous" referrerpolicy="no-referrer" />
|
||||||
<style>
|
<style>
|
||||||
/* Colori pastello per input/select */
|
.cell-changed {
|
||||||
|
background-color: #fff3b0 !important;
|
||||||
|
transition: background-color 0.3s ease;
|
||||||
|
}
|
||||||
|
|
||||||
input.auto-input,
|
input.auto-input,
|
||||||
select.auto-input {
|
select.auto-input {
|
||||||
background-color: #d4edda;
|
background-color: #d4edda;
|
||||||
/* Verde pastello */
|
|
||||||
}
|
}
|
||||||
|
|
||||||
input.manual-input,
|
input.manual-input,
|
||||||
select.manual-input {
|
select.manual-input {
|
||||||
background-color: #fff3cd;
|
background-color: #fff3cd;
|
||||||
/* Giallino pastello */
|
|
||||||
}
|
}
|
||||||
|
|
||||||
input.required-input,
|
input.required-input,
|
||||||
select.required-input {
|
select.required-input {
|
||||||
background-color: #f8d7da;
|
background-color: #f8d7da;
|
||||||
/* Rossino chiaro */
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Stili base per input/select */
|
|
||||||
input,
|
input,
|
||||||
select {
|
select {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
@@ -128,14 +128,11 @@ foreach ($stmt->fetchAll(PDO::FETCH_ASSOC) as $row) {
|
|||||||
font-size: 14px;
|
font-size: 14px;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Mantieni leggibilità del testo */
|
|
||||||
input,
|
input,
|
||||||
select {
|
select {
|
||||||
color: #333;
|
color: #333;
|
||||||
/* Colore scuro per contrasto */
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Stili per i badge di stato */
|
|
||||||
.status-badge {
|
.status-badge {
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
padding: 2px 8px;
|
padding: 2px 8px;
|
||||||
@@ -161,7 +158,6 @@ foreach ($stmt->fetchAll(PDO::FETCH_ASSOC) as $row) {
|
|||||||
color: white;
|
color: white;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Stili esistenti rimangono invariati */
|
|
||||||
.grid-container {
|
.grid-container {
|
||||||
overflow-x: auto;
|
overflow-x: auto;
|
||||||
max-width: 100%;
|
max-width: 100%;
|
||||||
@@ -254,6 +250,11 @@ foreach ($stmt->fetchAll(PDO::FETCH_ASSOC) as $row) {
|
|||||||
align-items: center;
|
align-items: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.grid-top .save-all-cell {
|
||||||
|
flex: 0 0 210px;
|
||||||
|
align-items: flex-start;
|
||||||
|
}
|
||||||
|
|
||||||
.propagate-btn {
|
.propagate-btn {
|
||||||
background: none;
|
background: none;
|
||||||
border: none;
|
border: none;
|
||||||
@@ -370,7 +371,6 @@ foreach ($stmt->fetchAll(PDO::FETCH_ASSOC) as $row) {
|
|||||||
box-shadow: 0 0 5px rgba(0, 123, 255, 0.5);
|
box-shadow: 0 0 5px rgba(0, 123, 255, 0.5);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Stile per l'header dei pulsanti combinati */
|
|
||||||
.grid-cell.button-cell,
|
.grid-cell.button-cell,
|
||||||
.grid-header.button-header {
|
.grid-header.button-header {
|
||||||
min-width: 210px !important;
|
min-width: 210px !important;
|
||||||
@@ -391,6 +391,20 @@ foreach ($stmt->fetchAll(PDO::FETCH_ASSOC) as $row) {
|
|||||||
background-color: #d4edda !important;
|
background-color: #d4edda !important;
|
||||||
transition: background-color 0.3s ease;
|
transition: background-color 0.3s ease;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.save-all-btn {
|
||||||
|
background-color: #28a745;
|
||||||
|
color: white;
|
||||||
|
border: none;
|
||||||
|
border-radius: 5px;
|
||||||
|
padding: 8px 16px;
|
||||||
|
cursor: pointer;
|
||||||
|
font-size: 14px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.save-all-btn:hover {
|
||||||
|
background-color: #218838;
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
<title>Edit Imported Data - <?= htmlspecialchars($titlewebsite, ENT_QUOTES, 'UTF-8'); ?></title>
|
<title>Edit Imported Data - <?= htmlspecialchars($titlewebsite, ENT_QUOTES, 'UTF-8'); ?></title>
|
||||||
</head>
|
</head>
|
||||||
@@ -413,6 +427,7 @@ foreach ($stmt->fetchAll(PDO::FETCH_ASSOC) as $row) {
|
|||||||
<h6 class="mb-0">Modifica Dati Importati</h6>
|
<h6 class="mb-0">Modifica Dati Importati</h6>
|
||||||
<div id="unsavedChanges" style="display:none; color: red; font-weight: bold; margin:10px 0;">
|
<div id="unsavedChanges" style="display:none; color: red; font-weight: bold; margin:10px 0;">
|
||||||
⚠️ Unsaved changes detected! Please save before leaving this page.
|
⚠️ Unsaved changes detected! Please save before leaving this page.
|
||||||
|
<ul id="changedFields" style="margin-top:5px; font-weight:normal; color:darkred;"></ul>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -420,9 +435,10 @@ foreach ($stmt->fetchAll(PDO::FETCH_ASSOC) as $row) {
|
|||||||
<div class="card-body">
|
<div class="card-body">
|
||||||
<form id="editForm">
|
<form id="editForm">
|
||||||
<div class="grid-container">
|
<div class="grid-container">
|
||||||
<!-- Riga superiore per gli input dei campi manuali -->
|
|
||||||
<div class="grid-top">
|
<div class="grid-top">
|
||||||
<div class="grid-cell button-cell" style="flex: 0 0 210px;"></div> <!-- Actions -->
|
<div class="grid-cell save-all-cell">
|
||||||
|
<button type="button" class="save-all-btn" title="Save All Rows"><i class="fas fa-save"></i> Save All</button>
|
||||||
|
</div>
|
||||||
<?php if ($mainFieldMapping): ?>
|
<?php if ($mainFieldMapping): ?>
|
||||||
<div class="grid-cell" style="flex: 0 0 150px;">
|
<div class="grid-cell" style="flex: 0 0 150px;">
|
||||||
<?php
|
<?php
|
||||||
@@ -450,9 +466,8 @@ foreach ($stmt->fetchAll(PDO::FETCH_ASSOC) as $row) {
|
|||||||
?>
|
?>
|
||||||
</div>
|
</div>
|
||||||
<?php endif; ?>
|
<?php endif; ?>
|
||||||
<div class="grid-cell" style="flex: 0 0 150px;"></div> <!-- status -->
|
<div class="grid-cell" style="flex: 0 0 150px;"></div>
|
||||||
<?php
|
<?php
|
||||||
// Campi automatici (is_manual = 0) escluso main_field
|
|
||||||
$autoIndex = 0;
|
$autoIndex = 0;
|
||||||
foreach ($allMappings as $mapping) {
|
foreach ($allMappings as $mapping) {
|
||||||
if (!$mapping['is_manual'] && $mapping['main_field'] != 1) {
|
if (!$mapping['is_manual'] && $mapping['main_field'] != 1) {
|
||||||
@@ -471,7 +486,6 @@ foreach ($stmt->fetchAll(PDO::FETCH_ASSOC) as $row) {
|
|||||||
$autoIndex++;
|
$autoIndex++;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// Campi manuali (is_manual = 1) escluso main_field
|
|
||||||
$manualIndex = 0;
|
$manualIndex = 0;
|
||||||
foreach ($allMappings as $mapping) {
|
foreach ($allMappings as $mapping) {
|
||||||
if ($mapping['is_manual'] && $mapping['main_field'] != 1) {
|
if ($mapping['is_manual'] && $mapping['main_field'] != 1) {
|
||||||
@@ -483,7 +497,7 @@ foreach ($stmt->fetchAll(PDO::FETCH_ASSOC) as $row) {
|
|||||||
if ($mapping['is_required']) $inputClass .= ' required-input';
|
if ($mapping['is_required']) $inputClass .= ' required-input';
|
||||||
echo "<div class='grid-cell' style='flex: 0 0 150px;'>";
|
echo "<div class='grid-cell' style='flex: 0 0 150px;'>";
|
||||||
if ($mapping['data_type'] === 'SceltaMultipla') {
|
if ($mapping['data_type'] === 'SceltaMultipla') {
|
||||||
echo "<select class='custom-field dropdown-select $inputClass' data-column='manual_$manualIndex' data-field-id='{$mapping['field_id']}' data-selected-value='" . htmlspecialchars($fieldValue) . "' " . ($mapping['is_required'] ? 'required' : '') . ">";
|
echo "<select class='custom-field dropdown-select $inputClass' data-column='manual_$manualIndex' data-field-id='{$mapping['field_id']}' data-selected-value='" . htmlspecialchars($fieldValue) . "' " . ($mainFieldMapping['is_required'] ? 'required' : '') . ">";
|
||||||
echo "<option value=''>Seleziona...</option>";
|
echo "<option value=''>Seleziona...</option>";
|
||||||
echo "</select>";
|
echo "</select>";
|
||||||
} elseif ($mapping['data_type'] === 'DATE') {
|
} elseif ($mapping['data_type'] === 'DATE') {
|
||||||
@@ -498,15 +512,14 @@ foreach ($stmt->fetchAll(PDO::FETCH_ASSOC) as $row) {
|
|||||||
$manualIndex++;
|
$manualIndex++;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
echo "<div class='grid-cell' style='flex: 0 0 200px;'></div>"; // AWB
|
echo "<div class='grid-cell' style='flex: 0 0 200px;'></div>";
|
||||||
echo "<div class='grid-cell' style='flex: 0 0 250px;'></div>"; // Tracking Info
|
echo "<div class='grid-cell' style='flex: 0 0 250px;'></div>";
|
||||||
echo "<div class='grid-cell' style='flex: 0 0 150px;'></div>"; // importreferencecode
|
echo "<div class='grid-cell' style='flex: 0 0 150px;'></div>";
|
||||||
echo "<div class='grid-cell' style='flex: 0 0 150px;'></div>"; // filename_import
|
echo "<div class='grid-cell' style='flex: 0 0 150px;'></div>";
|
||||||
echo "<div class='grid-cell' style='flex: 0 0 150px;'></div>"; // importdate
|
echo "<div class='grid-cell' style='flex: 0 0 150px;'></div>";
|
||||||
?>
|
?>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Header della tabella -->
|
|
||||||
<div class="grid-row">
|
<div class="grid-row">
|
||||||
<div class="grid-header button-header" style="flex: 0 0 210px;">Actions</div>
|
<div class="grid-header button-header" style="flex: 0 0 210px;">Actions</div>
|
||||||
<?php if ($mainFieldMapping): ?>
|
<?php if ($mainFieldMapping): ?>
|
||||||
@@ -543,7 +556,6 @@ foreach ($stmt->fetchAll(PDO::FETCH_ASSOC) as $row) {
|
|||||||
?>
|
?>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Righe della tabella -->
|
|
||||||
<?php foreach ($importedData as $index => $row): ?>
|
<?php foreach ($importedData as $index => $row): ?>
|
||||||
<div class="grid-row" data-id="<?= $row['iddatadb'] ?>">
|
<div class="grid-row" data-id="<?= $row['iddatadb'] ?>">
|
||||||
<div class="grid-cell button-cell" style="flex: 0 0 210px; position: relative;">
|
<div class="grid-cell button-cell" style="flex: 0 0 210px; position: relative;">
|
||||||
@@ -698,24 +710,196 @@ foreach ($stmt->fetchAll(PDO::FETCH_ASSOC) as $row) {
|
|||||||
<script src="tracking.js"></script>
|
<script src="tracking.js"></script>
|
||||||
<script>
|
<script>
|
||||||
document.addEventListener("DOMContentLoaded", function() {
|
document.addEventListener("DOMContentLoaded", function() {
|
||||||
|
console.log("Page loaded, initializing event listeners");
|
||||||
|
|
||||||
const inputs = document.querySelectorAll(".cell-input, .dropdown-select, .carrier-select, .awb-input");
|
const inputs = document.querySelectorAll(".cell-input, .dropdown-select, .carrier-select, .awb-input");
|
||||||
const unsavedDiv = document.getElementById("unsavedChanges");
|
const unsavedDiv = document.getElementById("unsavedChanges");
|
||||||
|
const changedList = document.getElementById("changedFields");
|
||||||
let hasChanges = false;
|
let hasChanges = false;
|
||||||
|
let changedFields = {};
|
||||||
|
|
||||||
|
function renderChangedList() {
|
||||||
|
console.log("Rendering changed fields list:", changedFields);
|
||||||
|
changedList.innerHTML = "";
|
||||||
|
Object.keys(changedFields).forEach(rowIndex => {
|
||||||
|
const fields = changedFields[rowIndex];
|
||||||
|
if (fields.length > 0) {
|
||||||
|
const li = document.createElement("li");
|
||||||
|
li.textContent = `Row ${parseInt(rowIndex) + 1}: ${fields.join(", ")}`;
|
||||||
|
changedList.appendChild(li);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
unsavedDiv.style.display = Object.keys(changedFields).length > 0 ? "block" : "none";
|
||||||
|
}
|
||||||
|
|
||||||
inputs.forEach(el => {
|
inputs.forEach(el => {
|
||||||
el.addEventListener("change", () => {
|
el.addEventListener("change", () => {
|
||||||
|
console.log("Input changed:", el.name);
|
||||||
hasChanges = true;
|
hasChanges = true;
|
||||||
unsavedDiv.style.display = "block";
|
const gridCell = el.closest(".grid-cell");
|
||||||
|
const colIndex = gridCell?.dataset.index;
|
||||||
|
const rowIndex = gridCell?.dataset.row;
|
||||||
|
let label = "Unknown field";
|
||||||
|
|
||||||
|
if (colIndex) {
|
||||||
|
const header = document.querySelector(`.grid-header[data-index="${colIndex}"]`);
|
||||||
|
if (header) {
|
||||||
|
label = header.textContent.replace(":", "").trim();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (rowIndex !== undefined) {
|
||||||
|
if (!changedFields[rowIndex]) {
|
||||||
|
changedFields[rowIndex] = [];
|
||||||
|
}
|
||||||
|
if (!changedFields[rowIndex].includes(label)) {
|
||||||
|
changedFields[rowIndex].push(label);
|
||||||
|
}
|
||||||
|
gridCell.classList.add("cell-changed");
|
||||||
|
}
|
||||||
|
|
||||||
|
renderChangedList();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
document.querySelectorAll(".save-btn").forEach(btn => {
|
document.querySelectorAll(".save-btn").forEach(btn => {
|
||||||
btn.addEventListener("click", () => {
|
btn.addEventListener("click", () => {
|
||||||
hasChanges = false;
|
const rowIndex = btn.dataset.row;
|
||||||
unsavedDiv.style.display = "none";
|
console.log(`Saving row ${rowIndex}`);
|
||||||
|
const row = btn.closest('.grid-row');
|
||||||
|
const iddatadb = row.getAttribute('data-id');
|
||||||
|
const formData = new FormData();
|
||||||
|
|
||||||
|
const inputs = row.querySelectorAll(`input[name^="rows[${rowIndex}][details]"], select[name^="rows[${rowIndex}][details]"]`);
|
||||||
|
inputs.forEach(input => {
|
||||||
|
const matches = input.name.match(/rows\[\d+\]\[details\]\[(\d+)\]\[field_value\]/);
|
||||||
|
if (matches) {
|
||||||
|
const mappingId = matches[1];
|
||||||
|
formData.append(`details${mappingId}field_value`, input.value);
|
||||||
|
if (input.tagName === 'SELECT' && input.classList.contains('dropdown-select')) {
|
||||||
|
input.setAttribute('data-selected-value', input.value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
formData.append('iddatadb', iddatadb);
|
||||||
|
|
||||||
|
fetch('save_edited_row.php', {
|
||||||
|
method: 'POST',
|
||||||
|
body: formData
|
||||||
|
})
|
||||||
|
.then(response => {
|
||||||
|
if (!response.ok) throw new Error(`HTTP error! status: ${response.status}`);
|
||||||
|
return response.json();
|
||||||
|
})
|
||||||
|
.then(data => {
|
||||||
|
console.log("Save response:", data);
|
||||||
|
if (data.success) {
|
||||||
|
const cells = row.querySelectorAll('.grid-cell');
|
||||||
|
cells.forEach(cell => {
|
||||||
|
cell.classList.remove('flash-success');
|
||||||
|
void cell.offsetWidth;
|
||||||
|
cell.classList.add('flash-success');
|
||||||
|
});
|
||||||
|
setTimeout(() => cells.forEach(cell => cell.classList.remove('flash-success')), 500);
|
||||||
|
|
||||||
|
if (changedFields[rowIndex]) {
|
||||||
|
delete changedFields[rowIndex];
|
||||||
|
document.querySelectorAll(`.grid-cell[data-row="${rowIndex}"]`)
|
||||||
|
.forEach(cell => cell.classList.remove("cell-changed"));
|
||||||
|
renderChangedList();
|
||||||
|
hasChanges = Object.keys(changedFields).length > 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
alert('Salvataggio riga avvenuto con successo!');
|
||||||
|
} else {
|
||||||
|
alert('Errore durante il salvataggio: ' + data.message);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch(error => {
|
||||||
|
console.error("Save error:", error);
|
||||||
|
alert('Errore durante il salvataggio: ' + error.message);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
document.querySelector('.save-all-btn').addEventListener('click', async () => {
|
||||||
|
console.log("Saving all rows");
|
||||||
|
const rows = document.querySelectorAll('.grid-row');
|
||||||
|
let successCount = 0;
|
||||||
|
let errorMessages = [];
|
||||||
|
|
||||||
|
for (const row of rows) {
|
||||||
|
const saveBtn = row.querySelector('.save-btn');
|
||||||
|
if (!saveBtn) {
|
||||||
|
console.warn(`No save button found in row with data-id: ${row.getAttribute('data-id')}`);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
const rowIndex = saveBtn.dataset.row;
|
||||||
|
const iddatadb = row.getAttribute('data-id');
|
||||||
|
if (!rowIndex || !iddatadb) {
|
||||||
|
console.warn(`Missing rowIndex or iddatadb in row:`, row);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
console.log(`Processing row ${rowIndex} with iddatadb ${iddatadb}`);
|
||||||
|
const formData = new FormData();
|
||||||
|
|
||||||
|
const inputs = row.querySelectorAll(`input[name^="rows[${rowIndex}][details]"], select[name^="rows[${rowIndex}][details]"]`);
|
||||||
|
inputs.forEach(input => {
|
||||||
|
const matches = input.name.match(/rows\[\d+\]\[details\]\[(\d+)\]\[field_value\]/);
|
||||||
|
if (matches) {
|
||||||
|
const mappingId = matches[1];
|
||||||
|
formData.append(`details${mappingId}field_value`, input.value);
|
||||||
|
if (input.tagName === 'SELECT' && input.classList.contains('dropdown-select')) {
|
||||||
|
input.setAttribute('data-selected-value', input.value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
formData.append('iddatadb', iddatadb);
|
||||||
|
|
||||||
|
try {
|
||||||
|
const response = await fetch('save_edited_row.php', {
|
||||||
|
method: 'POST',
|
||||||
|
body: formData
|
||||||
|
});
|
||||||
|
if (!response.ok) throw new Error(`HTTP error! status: ${response.status}`);
|
||||||
|
const data = await response.json();
|
||||||
|
|
||||||
|
console.log(`Row ${rowIndex} save response:`, data);
|
||||||
|
if (data.success) {
|
||||||
|
successCount++;
|
||||||
|
const cells = row.querySelectorAll('.grid-cell');
|
||||||
|
cells.forEach(cell => {
|
||||||
|
cell.classList.remove('flash-success');
|
||||||
|
void cell.offsetWidth;
|
||||||
|
cell.classList.add('flash-success');
|
||||||
|
});
|
||||||
|
setTimeout(() => cells.forEach(cell => cell.classList.remove('flash-success')), 500);
|
||||||
|
|
||||||
|
if (changedFields[rowIndex]) {
|
||||||
|
delete changedFields[rowIndex];
|
||||||
|
document.querySelectorAll(`.grid-cell[data-row="${rowIndex}"]`)
|
||||||
|
.forEach(cell => cell.classList.remove("cell-changed"));
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
errorMessages.push(`Riga ${parseInt(rowIndex) + 1}: ${data.message}`);
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error(`Row ${rowIndex} save error:`, error);
|
||||||
|
errorMessages.push(`Riga ${parseInt(rowIndex) + 1}: ${error.message}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
renderChangedList();
|
||||||
|
hasChanges = Object.keys(changedFields).length > 0;
|
||||||
|
console.log(`Save all completed: ${successCount} successes, ${errorMessages.length} errors`);
|
||||||
|
|
||||||
|
if (errorMessages.length === 0) {
|
||||||
|
alert(`Tutte le ${successCount} righe salvate con successo!`);
|
||||||
|
} else {
|
||||||
|
alert(`Salvate ${successCount} righe con successo.\nErrori:\n${errorMessages.join('\n')}`);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
window.addEventListener("beforeunload", function(e) {
|
window.addEventListener("beforeunload", function(e) {
|
||||||
if (hasChanges) {
|
if (hasChanges) {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
@@ -724,9 +908,9 @@ foreach ($stmt->fetchAll(PDO::FETCH_ASSOC) as $row) {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
document.addEventListener("DOMContentLoaded", function() {
|
document.addEventListener("DOMContentLoaded", function() {
|
||||||
|
console.log("Initializing cell expansion and propagation");
|
||||||
const inputs = document.querySelectorAll('.cell-input');
|
const inputs = document.querySelectorAll('.cell-input');
|
||||||
inputs.forEach(input => {
|
inputs.forEach(input => {
|
||||||
input.addEventListener('focus', function() {
|
input.addEventListener('focus', function() {
|
||||||
@@ -737,48 +921,10 @@ foreach ($stmt->fetchAll(PDO::FETCH_ASSOC) as $row) {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
const saveButtons = document.querySelectorAll('.save-btn');
|
|
||||||
saveButtons.forEach(button => {
|
|
||||||
button.addEventListener('click', function() {
|
|
||||||
const rowIndex = this.getAttribute('data-row');
|
|
||||||
const row = this.closest('.grid-row');
|
|
||||||
const iddatadb = row.getAttribute('data-id');
|
|
||||||
const formData = new FormData();
|
|
||||||
|
|
||||||
const inputs = row.querySelectorAll(`input[name^="rows[${rowIndex}]"], select[name^="rows[${rowIndex}]"]`);
|
|
||||||
inputs.forEach(input => {
|
|
||||||
const name = input.name.replace(`rows[${rowIndex}]`, '').replace(/\[|\]/g, '');
|
|
||||||
formData.append(name, input.value);
|
|
||||||
});
|
|
||||||
formData.append('iddatadb', iddatadb);
|
|
||||||
formData.append('mapping', JSON.stringify(<?= json_encode($allMappings) ?>));
|
|
||||||
|
|
||||||
fetch('save_edited_row.php', {
|
|
||||||
method: 'POST',
|
|
||||||
body: formData
|
|
||||||
})
|
|
||||||
.then(response => response.json())
|
|
||||||
.then(data => {
|
|
||||||
if (data.success) {
|
|
||||||
const cells = row.querySelectorAll('.grid-cell');
|
|
||||||
cells.forEach(cell => {
|
|
||||||
cell.classList.remove('flash-success');
|
|
||||||
void cell.offsetWidth;
|
|
||||||
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);
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.catch(error => alert('Errore durante il salvataggio: ' + error.message));
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
const propagateButtons = document.querySelectorAll('.propagate-btn');
|
const propagateButtons = document.querySelectorAll('.propagate-btn');
|
||||||
propagateButtons.forEach(button => {
|
propagateButtons.forEach(button => {
|
||||||
button.addEventListener('click', function() {
|
button.addEventListener('click', function() {
|
||||||
|
console.log("Propagating value for column:", this.getAttribute('data-column'));
|
||||||
const column = this.getAttribute('data-column');
|
const column = this.getAttribute('data-column');
|
||||||
const input = this.previousElementSibling;
|
const input = this.previousElementSibling;
|
||||||
const value = input.value;
|
const value = input.value;
|
||||||
@@ -797,6 +943,7 @@ foreach ($stmt->fetchAll(PDO::FETCH_ASSOC) as $row) {
|
|||||||
if (targetInput) {
|
if (targetInput) {
|
||||||
targetInput.value = value;
|
targetInput.value = value;
|
||||||
if (targetInput.tagName === 'SELECT') {
|
if (targetInput.tagName === 'SELECT') {
|
||||||
|
targetInput.setAttribute('data-selected-value', value);
|
||||||
const event = new Event('change');
|
const event = new Event('change');
|
||||||
targetInput.dispatchEvent(event);
|
targetInput.dispatchEvent(event);
|
||||||
}
|
}
|
||||||
@@ -814,6 +961,7 @@ foreach ($stmt->fetchAll(PDO::FETCH_ASSOC) as $row) {
|
|||||||
let columnIndex = 0;
|
let columnIndex = 0;
|
||||||
resizers.forEach(resizer => {
|
resizers.forEach(resizer => {
|
||||||
resizer.addEventListener('mousedown', function(e) {
|
resizer.addEventListener('mousedown', function(e) {
|
||||||
|
console.log("Starting column resize");
|
||||||
currentResizer = resizer;
|
currentResizer = resizer;
|
||||||
const header = resizer.parentElement;
|
const header = resizer.parentElement;
|
||||||
columnIndex = header.getAttribute('data-index');
|
columnIndex = header.getAttribute('data-index');
|
||||||
@@ -836,6 +984,7 @@ foreach ($stmt->fetchAll(PDO::FETCH_ASSOC) as $row) {
|
|||||||
|
|
||||||
function stopResize() {
|
function stopResize() {
|
||||||
if (currentResizer) {
|
if (currentResizer) {
|
||||||
|
console.log("Stopping column resize");
|
||||||
document.removeEventListener('mousemove', resize);
|
document.removeEventListener('mousemove', resize);
|
||||||
document.removeEventListener('mouseup', stopResize);
|
document.removeEventListener('mouseup', stopResize);
|
||||||
currentResizer = null;
|
currentResizer = null;
|
||||||
@@ -846,11 +995,15 @@ foreach ($stmt->fetchAll(PDO::FETCH_ASSOC) as $row) {
|
|||||||
</script>
|
</script>
|
||||||
<script>
|
<script>
|
||||||
document.addEventListener("DOMContentLoaded", function() {
|
document.addEventListener("DOMContentLoaded", function() {
|
||||||
|
console.log("Initializing dropdown population");
|
||||||
const dropdownData = {};
|
const dropdownData = {};
|
||||||
|
|
||||||
async function populateDropdowns() {
|
async function populateDropdowns() {
|
||||||
const dropdowns = document.querySelectorAll('.dropdown-select');
|
const dropdowns = document.querySelectorAll('.dropdown-select');
|
||||||
if (dropdowns.length === 0) return;
|
if (dropdowns.length === 0) {
|
||||||
|
console.log("No dropdowns found");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
const uniqueFieldIds = [
|
const uniqueFieldIds = [
|
||||||
...new Set(Array.from(dropdowns).map(d => d.getAttribute('data-field-id')))
|
...new Set(Array.from(dropdowns).map(d => d.getAttribute('data-field-id')))
|
||||||
@@ -859,31 +1012,34 @@ foreach ($stmt->fetchAll(PDO::FETCH_ASSOC) as $row) {
|
|||||||
const missingFieldIds = uniqueFieldIds.filter(fieldId => !dropdownData[fieldId]);
|
const missingFieldIds = uniqueFieldIds.filter(fieldId => !dropdownData[fieldId]);
|
||||||
|
|
||||||
if (missingFieldIds.length > 0) {
|
if (missingFieldIds.length > 0) {
|
||||||
|
console.log("Fetching dropdown data for field IDs:", missingFieldIds);
|
||||||
try {
|
try {
|
||||||
const response = await fetch(
|
const response = await fetch(
|
||||||
`get_customfield_values.php?field_ids=${missingFieldIds.join(",")}`
|
`get_customfield_values.php?field_ids=${missingFieldIds.join(",")}`
|
||||||
);
|
);
|
||||||
|
if (!response.ok) throw new Error(`HTTP error! status: ${response.status}`);
|
||||||
const data = await response.json();
|
const data = await response.json();
|
||||||
|
|
||||||
if (data.error) {
|
if (data.error) {
|
||||||
console.error("Errore fetch multiplo:", data.error);
|
console.error("Fetch error:", data.error);
|
||||||
} else {
|
} else {
|
||||||
for (const fieldId of Object.keys(data)) {
|
for (const fieldId of Object.keys(data)) {
|
||||||
dropdownData[fieldId] = data[fieldId] || [];
|
dropdownData[fieldId] = data[fieldId] || [];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("Errore generale nel fetch multiplo:", error);
|
console.error("Fetch error:", error);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
dropdowns.forEach(dropdown => {
|
dropdowns.forEach(dropdown => {
|
||||||
const fieldId = dropdown.getAttribute('data-field-id');
|
const fieldId = dropdown.getAttribute('data-field-id');
|
||||||
const selectedValue = dropdown.getAttribute('data-selected-value') || '';
|
const selectedValue = dropdown.getAttribute('data-selected-value') || '';
|
||||||
const currentValue = dropdown.value; // Preserva il valore corrente
|
const currentValue = dropdown.value;
|
||||||
|
|
||||||
if (!fieldId || !dropdownData[fieldId]) {
|
if (!fieldId || !dropdownData[fieldId]) {
|
||||||
dropdown.innerHTML = '<option value="">Errore nel caricamento</option>';
|
dropdown.innerHTML = '<option value="">Errore nel caricamento</option>';
|
||||||
|
console.warn(`No data for fieldId ${fieldId}`);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -892,7 +1048,6 @@ foreach ($stmt->fetchAll(PDO::FETCH_ASSOC) as $row) {
|
|||||||
const option = document.createElement('option');
|
const option = document.createElement('option');
|
||||||
option.value = value.IdCustomFieldsValue;
|
option.value = value.IdCustomFieldsValue;
|
||||||
option.textContent = value.Valore;
|
option.textContent = value.Valore;
|
||||||
// Usa il valore corrente se disponibile, altrimenti usa data-selected-value
|
|
||||||
if (currentValue && currentValue === String(value.IdCustomFieldsValue)) {
|
if (currentValue && currentValue === String(value.IdCustomFieldsValue)) {
|
||||||
option.selected = true;
|
option.selected = true;
|
||||||
} else if (!currentValue && selectedValue === String(value.IdCustomFieldsValue)) {
|
} else if (!currentValue && selectedValue === String(value.IdCustomFieldsValue)) {
|
||||||
@@ -903,87 +1058,12 @@ foreach ($stmt->fetchAll(PDO::FETCH_ASSOC) as $row) {
|
|||||||
|
|
||||||
if ((currentValue || selectedValue) && dropdown.value !== (currentValue || selectedValue)) {
|
if ((currentValue || selectedValue) && dropdown.value !== (currentValue || selectedValue)) {
|
||||||
dropdown.value = '';
|
dropdown.value = '';
|
||||||
console.warn(`Valore ${currentValue || selectedValue} non trovato per fieldId ${fieldId}`);
|
console.warn(`Value ${currentValue || selectedValue} not found for fieldId ${fieldId}`);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// Chiama populateDropdowns solo al caricamento iniziale
|
|
||||||
populateDropdowns();
|
populateDropdowns();
|
||||||
|
|
||||||
document.querySelectorAll('.save-btn').forEach(btn => {
|
|
||||||
btn.addEventListener('click', function() {
|
|
||||||
const rowIndex = this.getAttribute('data-row');
|
|
||||||
const row = this.closest('.grid-row');
|
|
||||||
const iddatadb = row.getAttribute('data-id');
|
|
||||||
const formData = new FormData();
|
|
||||||
|
|
||||||
const inputs = row.querySelectorAll(`input[name^="rows[${rowIndex}]"], select[name^="rows[${rowIndex}]"]`);
|
|
||||||
inputs.forEach(input => {
|
|
||||||
const name = input.name.replace(`rows[${rowIndex}]`, '').replace(/\[|\]/g, '');
|
|
||||||
formData.append(name, input.value);
|
|
||||||
// Aggiorna data-selected-value per i dropdown
|
|
||||||
if (input.tagName === 'SELECT' && input.classList.contains('dropdown-select')) {
|
|
||||||
input.setAttribute('data-selected-value', input.value);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
formData.append('iddatadb', iddatadb);
|
|
||||||
formData.append('mapping', JSON.stringify(<?= json_encode($allMappings) ?>));
|
|
||||||
|
|
||||||
fetch('save_edited_row.php', {
|
|
||||||
method: 'POST',
|
|
||||||
body: formData
|
|
||||||
})
|
|
||||||
.then(response => response.json())
|
|
||||||
.then(data => {
|
|
||||||
if (data.success) {
|
|
||||||
const cells = row.querySelectorAll('.grid-cell');
|
|
||||||
cells.forEach(cell => {
|
|
||||||
cell.classList.remove('flash-success');
|
|
||||||
void cell.offsetWidth;
|
|
||||||
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);
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.catch(error => alert('Errore durante il salvataggio: ' + error.message));
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
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') {
|
|
||||||
targetInput.setAttribute('data-selected-value', value); // Aggiorna anche qui
|
|
||||||
const event = new Event('change');
|
|
||||||
targetInput.dispatchEvent(event);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
</body>
|
</body>
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
<!-- Bootstrap JS -->
|
<!-- Bootstrap JS -->
|
||||||
<script src="assets/js/bootstrap.bundle.min.js"></script>
|
<script src="assets/js/bootstrap.bundle.min.js"></script>
|
||||||
<!--plugins-->
|
<!--plugins-->
|
||||||
<script src="assets/js/jquery.min.js"></script>
|
|
||||||
<script src="assets/plugins/simplebar/js/simplebar.min.js"></script>
|
<script src="assets/plugins/simplebar/js/simplebar.min.js"></script>
|
||||||
<script src="assets/plugins/metismenu/js/metisMenu.min.js"></script>
|
<script src="assets/plugins/metismenu/js/metisMenu.min.js"></script>
|
||||||
<script src="assets/plugins/perfect-scrollbar/js/perfect-scrollbar.js"></script>
|
<script src="assets/plugins/perfect-scrollbar/js/perfect-scrollbar.js"></script>
|
||||||
|
|||||||
@@ -14,9 +14,9 @@ if (!$iddatadb) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
$stmt = $pdo->prepare("SELECT id, iddatadb, part_number, part_description FROM identification_parts WHERE iddatadb = :iddatadb ORDER BY part_number ASC");
|
$stmt = $pdo->prepare("SELECT id, iddatadb, part_number, part_description, idmatrice FROM identification_parts WHERE iddatadb = :iddatadb ORDER BY part_number ASC");
|
||||||
$stmt->execute([':iddatadb' => $iddatadb]);
|
$stmt->execute([':iddatadb' => $iddatadb]);
|
||||||
$parts = $stmt->fetchAll();
|
$parts = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
||||||
|
|
||||||
echo json_encode(['success' => true, 'parts' => $parts]);
|
echo json_encode(['success' => true, 'parts' => $parts]);
|
||||||
} catch (PDOException $e) {
|
} catch (PDOException $e) {
|
||||||
|
|||||||
@@ -21,7 +21,7 @@ $schemajson = $template['schemajson'] ? json_decode($template['schemajson'], tru
|
|||||||
$isSchemajsonEmpty = empty(trim($template['schemajson']));
|
$isSchemajsonEmpty = empty(trim($template['schemajson']));
|
||||||
|
|
||||||
// Recupera i campi dalla tabella template_mapping
|
// Recupera i campi dalla tabella template_mapping
|
||||||
$stmt = $pdo->prepare("SELECT id, field_id, excel_column, is_manual, manual_default, data_type, is_required, default_value, has_list, length, decimals, min_value, max_value, default_curr_date, tablename, field_label, main_field FROM template_mapping WHERE template_id = ?");
|
$stmt = $pdo->prepare("SELECT id, field_id, excel_column, is_manual, manual_default, data_type, is_required, default_value, has_list, length, decimals, min_value, max_value, default_curr_date, tablename, field_label, main_field, is_visible_import FROM template_mapping WHERE template_id = ?");
|
||||||
$stmt->execute([$id]);
|
$stmt->execute([$id]);
|
||||||
$mappings = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
$mappings = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
||||||
|
|
||||||
@@ -83,8 +83,10 @@ $xlsHeaders = $template['xls_headers'] ? json_decode($template['xls_headers'], t
|
|||||||
}
|
}
|
||||||
|
|
||||||
#schemaFieldsTable th:first-child,
|
#schemaFieldsTable th:first-child,
|
||||||
#schemaFieldsTable td:first-child {
|
#schemaFieldsTable td:first-child,
|
||||||
width: 50px;
|
#schemaFieldsTable th:nth-child(2),
|
||||||
|
#schemaFieldsTable td:nth-child(2) {
|
||||||
|
width: 100px;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
@@ -137,8 +139,8 @@ $xlsHeaders = $template['xls_headers'] ? json_decode($template['xls_headers'], t
|
|||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
<th>Main</th>
|
<th>Main</th>
|
||||||
|
<th>Visible on Import</th>
|
||||||
<th>Title</th>
|
<th>Title</th>
|
||||||
<th>ID</th>
|
|
||||||
<th>Type</th>
|
<th>Type</th>
|
||||||
<th>Mapping</th>
|
<th>Mapping</th>
|
||||||
<th>Default Value</th>
|
<th>Default Value</th>
|
||||||
@@ -150,8 +152,15 @@ $xlsHeaders = $template['xls_headers'] ? json_decode($template['xls_headers'], t
|
|||||||
<td>
|
<td>
|
||||||
<input type="checkbox" class="main-field-checkbox" data-mapping-id="<?php echo $mapping['id']; ?>" <?php echo $mapping['main_field'] == 1 ? 'checked' : ''; ?>>
|
<input type="checkbox" class="main-field-checkbox" data-mapping-id="<?php echo $mapping['id']; ?>" <?php echo $mapping['main_field'] == 1 ? 'checked' : ''; ?>>
|
||||||
</td>
|
</td>
|
||||||
<td><?php echo htmlspecialchars($mapping['field_label'] ?? 'N/A'); ?></td>
|
<td>
|
||||||
<td><?php echo htmlspecialchars($mapping['field_id'] ?? 'N/A'); ?></td>
|
<input type="checkbox" class="visible-import-checkbox" data-mapping-id="<?php echo $mapping['id']; ?>" <?php echo (isset($mapping['is_visible_import']) ? $mapping['is_visible_import'] : 1) == 1 ? 'checked' : ''; ?>>
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<?php echo htmlspecialchars($mapping['field_label'] ?? 'N/A'); ?>
|
||||||
|
<?php if ($mapping['is_required'] == 1): ?>
|
||||||
|
<span class="badge bg-danger ms-2">Required</span>
|
||||||
|
<?php endif; ?>
|
||||||
|
</td>
|
||||||
<td><?php echo htmlspecialchars($mapping['data_type'] ?? 'N/A'); ?></td>
|
<td><?php echo htmlspecialchars($mapping['data_type'] ?? 'N/A'); ?></td>
|
||||||
<td>
|
<td>
|
||||||
<?php
|
<?php
|
||||||
@@ -428,7 +437,7 @@ $xlsHeaders = $template['xls_headers'] ? json_decode($template['xls_headers'], t
|
|||||||
|
|
||||||
async function updateSchemaDetails() {
|
async function updateSchemaDetails() {
|
||||||
if (!schemaId) {
|
if (!schemaId) {
|
||||||
document.getElementById('schemaFieldsBody').innerHTML = '<tr><td colspan="6" class="text-warning">No schema associated.</td></tr>'; // Aggiornato colspan a 6
|
document.getElementById('schemaFieldsBody').innerHTML = '<tr><td colspan="6" class="text-warning">No schema associated.</td></tr>';
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -445,7 +454,7 @@ $xlsHeaders = $template['xls_headers'] ? json_decode($template['xls_headers'], t
|
|||||||
await saveSchemaJson(templateId, JSON.stringify(data));
|
await saveSchemaJson(templateId, JSON.stringify(data));
|
||||||
alert('Schema updated successfully. Refresh the page to see changes.');
|
alert('Schema updated successfully. Refresh the page to see changes.');
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
document.getElementById('schemaFieldsBody').innerHTML = '<tr><td colspan="6" class="text-danger">Error: ' + error.message + '</td></tr>'; // Aggiornato colspan a 6
|
document.getElementById('schemaFieldsBody').innerHTML = '<tr><td colspan="6" class="text-danger">Error: ' + error.message + '</td></tr>';
|
||||||
} finally {
|
} finally {
|
||||||
updateSchemaButton.disabled = false;
|
updateSchemaButton.disabled = false;
|
||||||
updateSchemaButton.textContent = 'Update Schema Details';
|
updateSchemaButton.textContent = 'Update Schema Details';
|
||||||
@@ -524,16 +533,11 @@ $xlsHeaders = $template['xls_headers'] ? json_decode($template['xls_headers'], t
|
|||||||
.then(data => {
|
.then(data => {
|
||||||
if (!data.success) {
|
if (!data.success) {
|
||||||
console.error("❌ Error updating main_field:", data.message);
|
console.error("❌ Error updating main_field:", data.message);
|
||||||
// Revert checkbox state on error
|
|
||||||
checkbox.checked = !checkbox.checked;
|
checkbox.checked = !checkbox.checked;
|
||||||
// Riselezione visiva degli altri se errore
|
document.querySelectorAll('.main-field-checkbox').forEach(cb => {
|
||||||
if (value === 1) {
|
cb.checked = cb.dataset.originalChecked === 'true';
|
||||||
document.querySelectorAll('.main-field-checkbox').forEach(cb => {
|
});
|
||||||
cb.checked = cb.dataset.originalChecked === 'true';
|
|
||||||
});
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
// Aggiorna lo stato originale dopo successo
|
|
||||||
document.querySelectorAll('.main-field-checkbox').forEach(cb => {
|
document.querySelectorAll('.main-field-checkbox').forEach(cb => {
|
||||||
cb.dataset.originalChecked = cb.checked;
|
cb.dataset.originalChecked = cb.checked;
|
||||||
});
|
});
|
||||||
@@ -542,12 +546,36 @@ $xlsHeaders = $template['xls_headers'] ? json_decode($template['xls_headers'], t
|
|||||||
.catch(error => {
|
.catch(error => {
|
||||||
console.error("❌ Fetch error:", error);
|
console.error("❌ Fetch error:", error);
|
||||||
checkbox.checked = !checkbox.checked;
|
checkbox.checked = !checkbox.checked;
|
||||||
// Riselezione visiva degli altri se errore
|
document.querySelectorAll('.main-field-checkbox').forEach(cb => {
|
||||||
if (value === 1) {
|
cb.checked = cb.dataset.originalChecked === 'true';
|
||||||
document.querySelectorAll('.main-field-checkbox').forEach(cb => {
|
});
|
||||||
cb.checked = cb.dataset.originalChecked === 'true';
|
});
|
||||||
});
|
} else if (event.target.classList.contains('visible-import-checkbox')) {
|
||||||
|
const checkbox = event.target;
|
||||||
|
const mappingId = checkbox.dataset.mappingId;
|
||||||
|
const value = checkbox.checked ? 1 : 0;
|
||||||
|
|
||||||
|
fetch('update_visible_import.php', {
|
||||||
|
method: 'POST',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json'
|
||||||
|
},
|
||||||
|
body: JSON.stringify({
|
||||||
|
template_id: <?php echo $id; ?>,
|
||||||
|
mapping_id: mappingId,
|
||||||
|
value: value
|
||||||
|
})
|
||||||
|
})
|
||||||
|
.then(response => response.json())
|
||||||
|
.then(data => {
|
||||||
|
if (!data.success) {
|
||||||
|
console.error("❌ Error updating is_visible_import:", data.message);
|
||||||
|
checkbox.checked = !checkbox.checked;
|
||||||
}
|
}
|
||||||
|
})
|
||||||
|
.catch(error => {
|
||||||
|
console.error("❌ Fetch error:", error);
|
||||||
|
checkbox.checked = !checkbox.checked;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@@ -569,7 +597,7 @@ $xlsHeaders = $template['xls_headers'] ? json_decode($template['xls_headers'], t
|
|||||||
mappedColumn = document.createElement('span');
|
mappedColumn = document.createElement('span');
|
||||||
mappedColumn.className = 'mapped-column';
|
mappedColumn.className = 'mapped-column';
|
||||||
mappedColumn.style.marginLeft = '5px';
|
mappedColumn.style.marginLeft = '5px';
|
||||||
tr.querySelector('td:nth-child(5)').appendChild(mappedColumn); // Aggiornato a nth-child(5) per la nuova colonna
|
tr.querySelector('td:nth-child(5)').appendChild(mappedColumn);
|
||||||
}
|
}
|
||||||
if (!removeBtn) {
|
if (!removeBtn) {
|
||||||
removeBtn = document.createElement('button');
|
removeBtn = document.createElement('button');
|
||||||
@@ -577,7 +605,7 @@ $xlsHeaders = $template['xls_headers'] ? json_decode($template['xls_headers'], t
|
|||||||
removeBtn.textContent = 'X';
|
removeBtn.textContent = 'X';
|
||||||
removeBtn.style.marginLeft = '5px';
|
removeBtn.style.marginLeft = '5px';
|
||||||
removeBtn.setAttribute('data-id', mappingId);
|
removeBtn.setAttribute('data-id', mappingId);
|
||||||
tr.querySelector('td:nth-child(5)').appendChild(removeBtn); // Aggiornato a nth-child(5)
|
tr.querySelector('td:nth-child(5)').appendChild(removeBtn);
|
||||||
|
|
||||||
removeBtn.addEventListener('click', function(e) {
|
removeBtn.addEventListener('click', function(e) {
|
||||||
let tr = e.target.closest('tr');
|
let tr = e.target.closest('tr');
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
<!-- Modal -->
|
<!-- Modal modificato con pulsante per riconoscimento vocale -->
|
||||||
<div class="modal fade" id="partsModal" tabindex="-1" aria-labelledby="partsModalLabel" aria-hidden="true">
|
<div class="modal fade" id="partsModal" tabindex="-1" aria-labelledby="partsModalLabel" aria-hidden="true">
|
||||||
<div class="modal-dialog modal-xl" style="max-width: 80% !important; width: 80% !important;">
|
<div class="modal-dialog modal-xl" style="max-width: 80% !important; width: 80% !important;">
|
||||||
<div class="modal-content">
|
<div class="modal-content">
|
||||||
@@ -9,12 +9,15 @@
|
|||||||
<div class="modal-body">
|
<div class="modal-body">
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col-md-6">
|
<div class="col-md-6">
|
||||||
<div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 10px;">
|
<div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 10px; min-width: 0;">
|
||||||
<h6 style="margin: 0; white-space: nowrap;">Elenco Parti</h6>
|
<h6 style="margin: 0; white-space: nowrap;">Elenco Parti</h6>
|
||||||
<div style="display: flex; align-items: center;">
|
<div style="display: flex; align-items: center; min-width: 0;">
|
||||||
<input type="checkbox" id="showMixParts" name="showMixParts" style="margin-right: 5px;">
|
<select id="global-matrice" class="ms-2" style="width: 250px !important; min-width: 250px !important;">
|
||||||
|
</select>
|
||||||
|
<input type="checkbox" id="showMixParts" name="showMixParts" style="margin-right: 5px; margin-left: 10px;">
|
||||||
<label for="showMixParts" style="font-size: 0.9rem; margin-right: 10px;">Mix</label>
|
<label for="showMixParts" style="font-size: 0.9rem; margin-right: 10px;">Mix</label>
|
||||||
<button type="button" class="btn btn-info btn-sm" id="renumberPartsBtn" style="padding: 0.1rem 0.5rem; font-size: 0.8rem;">Rinumera Parti</button>
|
<button type="button" class="btn btn-info btn-sm" id="renumberPartsBtn" style="padding: 0.1rem 0.5rem; font-size: 0.8rem;">Rinumera 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;"><i class="fas fa-microphone"></i> Voce</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<ul id="partsList" class="list-group"></ul>
|
<ul id="partsList" class="list-group"></ul>
|
||||||
@@ -169,7 +172,6 @@
|
|||||||
/* Unsaved changes */
|
/* Unsaved changes */
|
||||||
#savePhotoBtn.unsaved {
|
#savePhotoBtn.unsaved {
|
||||||
background-color: #dc3545 !important;
|
background-color: #dc3545 !important;
|
||||||
/* Rosso */
|
|
||||||
border-color: #dc3545 !important;
|
border-color: #dc3545 !important;
|
||||||
color: white !important;
|
color: white !important;
|
||||||
animation: pulse 1.2s infinite;
|
animation: pulse 1.2s infinite;
|
||||||
@@ -189,4 +191,59 @@
|
|||||||
box-shadow: 0 0 0 0 rgba(220, 53, 69, 0);
|
box-shadow: 0 0 0 0 rgba(220, 53, 69, 0);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Stili per Select2 in #partsList e #global-matrice */
|
||||||
|
#partsList .select2-container,
|
||||||
|
.select2-container--default #global-matrice {
|
||||||
|
width: 250px !important;
|
||||||
|
min-width: 250px !important;
|
||||||
|
margin-left: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
#partsList .select2-selection--single,
|
||||||
|
.select2-container--default #global-matrice .select2-selection--single {
|
||||||
|
height: 26px !important;
|
||||||
|
padding: 0.2rem 0.5rem !important;
|
||||||
|
font-size: 0.9rem !important;
|
||||||
|
border: 1px solid #ced4da !important;
|
||||||
|
background-color: #fff !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
#partsList .select2-selection__rendered,
|
||||||
|
.select2-container--default #global-matrice .select2-selection__rendered {
|
||||||
|
line-height: 24px !important;
|
||||||
|
overflow: hidden !important;
|
||||||
|
text-overflow: ellipsis !important;
|
||||||
|
white-space: nowrap !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
#partsList .select2-selection__arrow,
|
||||||
|
.select2-container--default #global-matrice .select2-selection__arrow {
|
||||||
|
height: 26px !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
#partsList .save-status,
|
||||||
|
#partsList .save-loading {
|
||||||
|
margin-left: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.select2-container--open .select2-dropdown {
|
||||||
|
z-index: 1051 !important;
|
||||||
|
border: 1px solid #aaa !important;
|
||||||
|
border-radius: 4px !important;
|
||||||
|
background: white !important;
|
||||||
|
overflow-y: auto !important;
|
||||||
|
max-height: 200px !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.select2-container--default .select2-search--dropdown .select2-search__field {
|
||||||
|
width: 100% !important;
|
||||||
|
padding: 0.2rem !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.select2-container--default .part-matrice,
|
||||||
|
.select2-container--default #global-matrice {
|
||||||
|
width: 250px !important;
|
||||||
|
min-width: 250px !important;
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
+368
-41
@@ -16,10 +16,115 @@ $(document).ready(function () {
|
|||||||
let photoAnnotations = {};
|
let photoAnnotations = {};
|
||||||
// colors keyed by part number
|
// colors keyed by part number
|
||||||
let partColors = {};
|
let partColors = {};
|
||||||
|
// matrice IDs keyed by part number
|
||||||
|
let partMatrice = {};
|
||||||
|
|
||||||
// selection
|
// selection
|
||||||
let selectedPartNumber = null;
|
let selectedPartNumber = null;
|
||||||
|
|
||||||
|
// lista delle matrici precaricata
|
||||||
|
let matrici = [];
|
||||||
|
|
||||||
|
// ===================
|
||||||
|
// VOICE RECOGNITION SETUP
|
||||||
|
// ===================
|
||||||
|
const SpeechRecognition =
|
||||||
|
window.SpeechRecognition || window.webkitSpeechRecognition;
|
||||||
|
let recognition = null;
|
||||||
|
let isVoiceActive = false;
|
||||||
|
const magicWord = "salva"; // Parola magica scelta
|
||||||
|
|
||||||
|
if (SpeechRecognition) {
|
||||||
|
recognition = new SpeechRecognition();
|
||||||
|
recognition.lang = "it-IT";
|
||||||
|
recognition.continuous = true;
|
||||||
|
recognition.interimResults = false;
|
||||||
|
|
||||||
|
recognition.onresult = function (event) {
|
||||||
|
const transcript = event.results[
|
||||||
|
event.results.length - 1
|
||||||
|
][0].transcript
|
||||||
|
.trim()
|
||||||
|
.toLowerCase();
|
||||||
|
console.log("Transcript vocale:", transcript);
|
||||||
|
|
||||||
|
const $currentRow = $("#partsTableBody tr:last");
|
||||||
|
const $descriptionInput = $currentRow.find(".part-description");
|
||||||
|
|
||||||
|
if (transcript.includes(magicWord)) {
|
||||||
|
const cleanedTranscript = transcript
|
||||||
|
.replace(magicWord, "")
|
||||||
|
.trim();
|
||||||
|
if (cleanedTranscript) {
|
||||||
|
$descriptionInput.val(
|
||||||
|
(
|
||||||
|
$descriptionInput.val() +
|
||||||
|
" " +
|
||||||
|
cleanedTranscript
|
||||||
|
).trim(),
|
||||||
|
);
|
||||||
|
$descriptionInput.trigger("blur");
|
||||||
|
}
|
||||||
|
const maxPartNumber = Math.max(
|
||||||
|
...$("#partsTableBody tr")
|
||||||
|
.map(function () {
|
||||||
|
return (
|
||||||
|
parseInt($(this).find(".part-number").val()) ||
|
||||||
|
0
|
||||||
|
);
|
||||||
|
})
|
||||||
|
.get(),
|
||||||
|
);
|
||||||
|
addNewRow(maxPartNumber + 1);
|
||||||
|
const $newRow = $("#partsTableBody tr:last");
|
||||||
|
$newRow.find(".part-description").focus();
|
||||||
|
} else {
|
||||||
|
$descriptionInput.val(
|
||||||
|
($descriptionInput.val() + " " + transcript).trim(),
|
||||||
|
);
|
||||||
|
$descriptionInput.trigger("blur");
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
recognition.onerror = function (event) {
|
||||||
|
console.error("Errore riconoscimento vocale:", event.error);
|
||||||
|
if (event.error === "no-speech" || event.error === "aborted") {
|
||||||
|
if (isVoiceActive) recognition.start();
|
||||||
|
} else {
|
||||||
|
alert("Errore nel riconoscimento vocale: " + event.error);
|
||||||
|
toggleVoiceRecognition();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
recognition.onend = function () {
|
||||||
|
if (isVoiceActive) recognition.start();
|
||||||
|
};
|
||||||
|
} else {
|
||||||
|
console.warn("Riconoscimento vocale non supportato dal browser.");
|
||||||
|
$("#toggleVoiceBtn").hide();
|
||||||
|
}
|
||||||
|
|
||||||
|
function toggleVoiceRecognition() {
|
||||||
|
if (!recognition) return;
|
||||||
|
isVoiceActive = !isVoiceActive;
|
||||||
|
const $btn = $("#toggleVoiceBtn");
|
||||||
|
if (isVoiceActive) {
|
||||||
|
$btn.addClass("btn-danger").html(
|
||||||
|
'<i class="fas fa-microphone-slash"></i> Stop Voce',
|
||||||
|
);
|
||||||
|
recognition.start();
|
||||||
|
const $currentRow = $("#partsTableBody tr:last");
|
||||||
|
$currentRow.find(".part-description").focus();
|
||||||
|
} else {
|
||||||
|
$btn.removeClass("btn-danger")
|
||||||
|
.addClass("btn-secondary")
|
||||||
|
.html('<i class="fas fa-microphone"></i> Voce');
|
||||||
|
recognition.stop();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$("#toggleVoiceBtn").on("click", toggleVoiceRecognition);
|
||||||
|
|
||||||
// ===================
|
// ===================
|
||||||
// POPUP HANDLING
|
// POPUP HANDLING
|
||||||
// ===================
|
// ===================
|
||||||
@@ -45,8 +150,39 @@ $(document).ready(function () {
|
|||||||
$("#trfHeader").text(`${iddatadb} - ${importRef} - ${description}`);
|
$("#trfHeader").text(`${iddatadb} - ${importRef} - ${description}`);
|
||||||
$("#partsModal").data("iddatadb", iddatadb);
|
$("#partsModal").data("iddatadb", iddatadb);
|
||||||
|
|
||||||
loadPhoto(iddatadb);
|
// Precarica le matrici una volta sola
|
||||||
loadExistingParts(iddatadb);
|
if (matrici.length === 0) {
|
||||||
|
$.ajax({
|
||||||
|
url: "get_matrice.php",
|
||||||
|
method: "GET",
|
||||||
|
dataType: "json",
|
||||||
|
success: function (data) {
|
||||||
|
matrici = data.value || [];
|
||||||
|
console.log(
|
||||||
|
"Matrici precaricate (una volta sola):",
|
||||||
|
matrici,
|
||||||
|
);
|
||||||
|
initializeGlobalSelect2();
|
||||||
|
loadPhoto(iddatadb);
|
||||||
|
loadExistingParts(iddatadb);
|
||||||
|
},
|
||||||
|
error: function (xhr, status, error) {
|
||||||
|
console.error(
|
||||||
|
"Errore nel precaricamento delle matrici:",
|
||||||
|
error,
|
||||||
|
);
|
||||||
|
alert("Errore nel caricamento delle matrici: " + error);
|
||||||
|
matrici = [];
|
||||||
|
loadPhoto(iddatadb);
|
||||||
|
loadExistingParts(iddatadb);
|
||||||
|
},
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
console.log("Matrici già precaricate, riutilizzo.");
|
||||||
|
initializeGlobalSelect2();
|
||||||
|
loadPhoto(iddatadb);
|
||||||
|
loadExistingParts(iddatadb);
|
||||||
|
}
|
||||||
|
|
||||||
if (partsModal) {
|
if (partsModal) {
|
||||||
const modal = new bootstrap.Modal(partsModal);
|
const modal = new bootstrap.Modal(partsModal);
|
||||||
@@ -141,7 +277,7 @@ $(document).ready(function () {
|
|||||||
|
|
||||||
function loadSinglePhoto(photoPath) {
|
function loadSinglePhoto(photoPath) {
|
||||||
const img = $("#samplePhoto");
|
const img = $("#samplePhoto");
|
||||||
img.off("load"); // avoid stacking multiple handlers
|
img.off("load");
|
||||||
img.attr("src", photoPath);
|
img.attr("src", photoPath);
|
||||||
|
|
||||||
img.on("load", function () {
|
img.on("load", function () {
|
||||||
@@ -172,7 +308,6 @@ $(document).ready(function () {
|
|||||||
|
|
||||||
canvas.width = naturalWidth;
|
canvas.width = naturalWidth;
|
||||||
canvas.height = naturalHeight;
|
canvas.height = naturalHeight;
|
||||||
|
|
||||||
canvas.style.width = `${displayWidth}px`;
|
canvas.style.width = `${displayWidth}px`;
|
||||||
canvas.style.height = `${displayHeight}px`;
|
canvas.style.height = `${displayHeight}px`;
|
||||||
|
|
||||||
@@ -212,7 +347,6 @@ $(document).ready(function () {
|
|||||||
</tr>`;
|
</tr>`;
|
||||||
$("#partsTableBody").append(newRow);
|
$("#partsTableBody").append(newRow);
|
||||||
updateRowButtons();
|
updateRowButtons();
|
||||||
// Initialize color for the new part
|
|
||||||
const partNumber = nextPartNumber || 1;
|
const partNumber = nextPartNumber || 1;
|
||||||
partColors[partNumber] = defaultColor;
|
partColors[partNumber] = defaultColor;
|
||||||
}
|
}
|
||||||
@@ -268,9 +402,10 @@ $(document).ready(function () {
|
|||||||
if (response.success) {
|
if (response.success) {
|
||||||
$row.remove();
|
$row.remove();
|
||||||
delete partColors[partNumber];
|
delete partColors[partNumber];
|
||||||
|
delete partMatrice[partNumber];
|
||||||
updateRowButtons();
|
updateRowButtons();
|
||||||
updatePartsList();
|
updatePartsList();
|
||||||
clearCanvasMarkers(false); // Preserve descriptions
|
clearCanvasMarkers(false);
|
||||||
} else {
|
} else {
|
||||||
alert("Errore nell'eliminazione: " + response.message);
|
alert("Errore nell'eliminazione: " + response.message);
|
||||||
}
|
}
|
||||||
@@ -289,6 +424,7 @@ $(document).ready(function () {
|
|||||||
} else {
|
} else {
|
||||||
$row.remove();
|
$row.remove();
|
||||||
delete partColors[partNumber];
|
delete partColors[partNumber];
|
||||||
|
delete partMatrice[partNumber];
|
||||||
updateRowButtons();
|
updateRowButtons();
|
||||||
updatePartsList();
|
updatePartsList();
|
||||||
}
|
}
|
||||||
@@ -303,7 +439,6 @@ $(document).ready(function () {
|
|||||||
const $saveLoading = $row.find(".save-loading");
|
const $saveLoading = $row.find(".save-loading");
|
||||||
const iddatadb = $("#partsModal").data("iddatadb");
|
const iddatadb = $("#partsModal").data("iddatadb");
|
||||||
const isMix = partDescription.startsWith("Mix") ? "Y" : "N";
|
const isMix = partDescription.startsWith("Mix") ? "Y" : "N";
|
||||||
|
|
||||||
const partId = $row.data("part-id") || null;
|
const partId = $row.data("part-id") || null;
|
||||||
|
|
||||||
if (partDescription && iddatadb) {
|
if (partDescription && iddatadb) {
|
||||||
@@ -356,6 +491,160 @@ $(document).ready(function () {
|
|||||||
markUnsaved();
|
markUnsaved();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Funzione per inizializzare Select2 sulle tendine delle matrici
|
||||||
|
function initializeSelect2($select, partNumber, partId, idmatrice) {
|
||||||
|
if (typeof $.fn.select2 === "undefined") {
|
||||||
|
console.error("Select2 non disponibile per parte " + partNumber);
|
||||||
|
$select.replaceWith(
|
||||||
|
'<input type="text" class="form-control form-control-sm" placeholder="Select2 non disponibile" disabled>',
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Popola con i dati precaricati
|
||||||
|
const options = matrici.map(function (matrice) {
|
||||||
|
return {
|
||||||
|
id: matrice.IdMatrice,
|
||||||
|
text: matrice.NomeMatriceTraduzione,
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
$select.select2({
|
||||||
|
placeholder: "Seleziona matrice",
|
||||||
|
allowClear: true,
|
||||||
|
data: options, // Carica la lista completa all'apertura
|
||||||
|
dropdownParent: $("#partsModal"),
|
||||||
|
matcher: function (params, data) {
|
||||||
|
// Filtraggio lato client sulla descrizione
|
||||||
|
if (!params.term || params.term.length < 3) {
|
||||||
|
return data; // Mostra tutto se meno di 3 caratteri
|
||||||
|
}
|
||||||
|
const term = params.term.toUpperCase();
|
||||||
|
if (data.text.toUpperCase().indexOf(term) >= 0) {
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
// Carica la matrice esistente, se presente
|
||||||
|
if (partId && partId !== "new" && idmatrice) {
|
||||||
|
const matrice = matrici.find((m) => m.IdMatrice == idmatrice);
|
||||||
|
if (matrice) {
|
||||||
|
console.log(
|
||||||
|
"Preselezione matrice per partNumber " + partNumber + ":",
|
||||||
|
matrice,
|
||||||
|
);
|
||||||
|
const option = new Option(
|
||||||
|
matrice.NomeMatriceTraduzione,
|
||||||
|
matrice.IdMatrice,
|
||||||
|
true,
|
||||||
|
true,
|
||||||
|
);
|
||||||
|
$select.append(option).trigger("change");
|
||||||
|
partMatrice[partNumber] = matrice.IdMatrice;
|
||||||
|
} else {
|
||||||
|
console.warn(
|
||||||
|
"Matrice con ID " +
|
||||||
|
idmatrice +
|
||||||
|
" non trovata per partNumber " +
|
||||||
|
partNumber,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Gestione del cambio della matrice
|
||||||
|
$select.on("change", function () {
|
||||||
|
const idmatrice = $(this).val();
|
||||||
|
const $listItem = $(this).closest("li");
|
||||||
|
const $saveStatus = $listItem.find(".save-status");
|
||||||
|
const $saveLoading = $listItem.find(".save-loading");
|
||||||
|
|
||||||
|
console.log(
|
||||||
|
"Cambio matrice per partNumber:",
|
||||||
|
partNumber,
|
||||||
|
"nuovo idmatrice:",
|
||||||
|
idmatrice,
|
||||||
|
);
|
||||||
|
partMatrice[partNumber] = idmatrice || null;
|
||||||
|
|
||||||
|
if (partId && partId !== "new") {
|
||||||
|
$saveLoading.show();
|
||||||
|
$saveStatus.hide();
|
||||||
|
|
||||||
|
$.ajax({
|
||||||
|
url: "save_matrice.php",
|
||||||
|
method: "POST",
|
||||||
|
data: JSON.stringify({
|
||||||
|
iddatadb: $("#partsModal").data("iddatadb"),
|
||||||
|
parts: [
|
||||||
|
{
|
||||||
|
id: partId,
|
||||||
|
idmatrice: idmatrice || null,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
}),
|
||||||
|
contentType: "application/json",
|
||||||
|
success: function (response) {
|
||||||
|
if (response.success) {
|
||||||
|
$saveLoading.hide();
|
||||||
|
$saveStatus.show();
|
||||||
|
setTimeout(() => $saveStatus.hide(), 2000);
|
||||||
|
} else {
|
||||||
|
alert(
|
||||||
|
"Errore nel salvataggio della matrice: " +
|
||||||
|
response.message,
|
||||||
|
);
|
||||||
|
$saveLoading.hide();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
error: function (xhr, status, error) {
|
||||||
|
alert("Errore nel salvataggio della matrice: " + error);
|
||||||
|
$saveLoading.hide();
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Funzione per inizializzare Select2 sul dropdown globale
|
||||||
|
function initializeGlobalSelect2() {
|
||||||
|
const $select = $("#global-matrice");
|
||||||
|
if (typeof $.fn.select2 === "undefined") {
|
||||||
|
console.error("Select2 non disponibile per il dropdown globale");
|
||||||
|
$select.replaceWith(
|
||||||
|
'<input type="text" class="form-control form-control-sm" placeholder="Select2 non disponibile" disabled>',
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Popola con i dati precaricati
|
||||||
|
const options = matrici.map(function (matrice) {
|
||||||
|
return {
|
||||||
|
id: matrice.IdMatrice,
|
||||||
|
text: matrice.NomeMatriceTraduzione,
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
$select.select2({
|
||||||
|
placeholder: "Seleziona matrice globale",
|
||||||
|
allowClear: true,
|
||||||
|
data: options, // Carica la lista completa all'apertura
|
||||||
|
dropdownParent: $("#partsModal"),
|
||||||
|
matcher: function (params, data) {
|
||||||
|
// Filtraggio lato client sulla descrizione
|
||||||
|
if (!params.term || params.term.length < 3) {
|
||||||
|
return data; // Mostra tutto se meno di 3 caratteri
|
||||||
|
}
|
||||||
|
const term = params.term.toUpperCase();
|
||||||
|
if (data.text.toUpperCase().indexOf(term) >= 0) {
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
function loadExistingParts(iddatadb) {
|
function loadExistingParts(iddatadb) {
|
||||||
$.ajax({
|
$.ajax({
|
||||||
url: "load_parts.php",
|
url: "load_parts.php",
|
||||||
@@ -384,6 +673,9 @@ $(document).ready(function () {
|
|||||||
</tr>`;
|
</tr>`;
|
||||||
$("#partsTableBody").append(newRow);
|
$("#partsTableBody").append(newRow);
|
||||||
partColors[part.part_number] = defaultColor;
|
partColors[part.part_number] = defaultColor;
|
||||||
|
if (part.idmatrice) {
|
||||||
|
partMatrice[part.part_number] = part.idmatrice;
|
||||||
|
}
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
addNewRow(1);
|
addNewRow(1);
|
||||||
@@ -411,6 +703,7 @@ $(document).ready(function () {
|
|||||||
$("#partsTableBody tr").each(function () {
|
$("#partsTableBody tr").each(function () {
|
||||||
const partNumber = $(this).find(".part-number").val();
|
const partNumber = $(this).find(".part-number").val();
|
||||||
const partDescription = $(this).find(".part-description").val();
|
const partDescription = $(this).find(".part-description").val();
|
||||||
|
const partId = $(this).data("part-id");
|
||||||
const partColor =
|
const partColor =
|
||||||
partColors[partNumber] ||
|
partColors[partNumber] ||
|
||||||
(partDescription.startsWith("Mix") ? "#0000ff" : "#ff0000");
|
(partDescription.startsWith("Mix") ? "#0000ff" : "#ff0000");
|
||||||
@@ -420,14 +713,27 @@ $(document).ready(function () {
|
|||||||
(showMixParts || !partDescription.startsWith("Mix"))
|
(showMixParts || !partDescription.startsWith("Mix"))
|
||||||
) {
|
) {
|
||||||
const listItem = `
|
const listItem = `
|
||||||
<li class="list-group-item" data-part-number="${partNumber}">
|
<li class="list-group-item" data-part-number="${partNumber}" data-part-id="${partId}">
|
||||||
${partNumber} - ${partDescription}
|
${partNumber} - ${partDescription}
|
||||||
<div style="display: flex; align-items: center;">
|
<div style="display: flex; align-items: center;">
|
||||||
<button type="button" class="btn btn-success btn-sm add-to-mix-btn" 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 propagate-matrice-btn" style="padding: 0.1rem 0.3rem; font-size: 0.8rem; margin-right: 3px;"><i class="fas fa-arrow-right fa-xs"></i></button>
|
||||||
<input type="color" class="part-color" value="${partColor}" style="margin-left: 5px;">
|
<select class="part-matrice" style="width: 250px !important; margin-right: 10px;"></select>
|
||||||
</div>
|
<button type="button" class="btn btn-success btn-sm add-to-mix-btn" style="padding: 0.1rem 0.3rem; font-size: 0.8rem;"><i class="fas fa-plus fa-xs"></i></button>
|
||||||
</li>`;
|
<input type="color" class="part-color" value="${partColor}" style="margin-left: 5px;">
|
||||||
|
<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>
|
||||||
|
</div>
|
||||||
|
</li>`;
|
||||||
$("#partsList").append(listItem);
|
$("#partsList").append(listItem);
|
||||||
|
const $select = $("#partsList").find(
|
||||||
|
`li[data-part-number="${partNumber}"] .part-matrice`,
|
||||||
|
);
|
||||||
|
initializeSelect2(
|
||||||
|
$select,
|
||||||
|
partNumber,
|
||||||
|
partId,
|
||||||
|
partMatrice[partNumber],
|
||||||
|
);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
updateMarkers();
|
updateMarkers();
|
||||||
@@ -437,8 +743,8 @@ $(document).ready(function () {
|
|||||||
const $rows = $("#partsTableBody tr");
|
const $rows = $("#partsTableBody tr");
|
||||||
const iddatadb = $("#partsModal").data("iddatadb");
|
const iddatadb = $("#partsModal").data("iddatadb");
|
||||||
let newPartColors = {};
|
let newPartColors = {};
|
||||||
|
let newPartMatrice = {};
|
||||||
|
|
||||||
// Raccogli tutte le righe con i loro dati attuali
|
|
||||||
let partsData = $rows
|
let partsData = $rows
|
||||||
.map(function (index) {
|
.map(function (index) {
|
||||||
const $row = $(this);
|
const $row = $(this);
|
||||||
@@ -449,23 +755,21 @@ $(document).ready(function () {
|
|||||||
})
|
})
|
||||||
.get();
|
.get();
|
||||||
|
|
||||||
// Rinumera in modo sequenziale
|
|
||||||
partsData.forEach((part, index) => {
|
partsData.forEach((part, index) => {
|
||||||
const newNumber = index + 1;
|
const newNumber = index + 1;
|
||||||
newPartColors[newNumber] = partColors[part.partNumber] || "#ff0000";
|
newPartColors[newNumber] = partColors[part.partNumber] || "#ff0000";
|
||||||
|
newPartMatrice[newNumber] = partMatrice[part.partNumber] || null;
|
||||||
part.partNumber = newNumber;
|
part.partNumber = newNumber;
|
||||||
});
|
});
|
||||||
|
|
||||||
// Aggiorna i valori nella tabella
|
|
||||||
$rows.each(function (index) {
|
$rows.each(function (index) {
|
||||||
const $row = $(this);
|
const $row = $(this);
|
||||||
$row.find(".part-number").val(index + 1);
|
$row.find(".part-number").val(index + 1);
|
||||||
});
|
});
|
||||||
|
|
||||||
// Aggiorna partColors
|
|
||||||
partColors = newPartColors;
|
partColors = newPartColors;
|
||||||
|
partMatrice = newPartMatrice;
|
||||||
|
|
||||||
// Aggiorna i marker nelle annotazioni
|
|
||||||
const currentPhoto = $("#samplePhoto").attr("src");
|
const currentPhoto = $("#samplePhoto").attr("src");
|
||||||
if (photoAnnotations[currentPhoto]) {
|
if (photoAnnotations[currentPhoto]) {
|
||||||
photoAnnotations[currentPhoto].markers.forEach((marker) => {
|
photoAnnotations[currentPhoto].markers.forEach((marker) => {
|
||||||
@@ -480,29 +784,36 @@ $(document).ready(function () {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// Salva le modifiche nel database
|
|
||||||
const partsToSave = partsData.map((part) => ({
|
const partsToSave = partsData.map((part) => ({
|
||||||
id: part.partId || null,
|
id: part.partId || null,
|
||||||
part_number: part.partNumber,
|
part_number: part.partNumber,
|
||||||
part_description: part.partDescription,
|
part_description: part.partDescription,
|
||||||
mix: part.partDescription.startsWith("Mix") ? "Y" : "N",
|
mix: part.partDescription.startsWith("Mix") ? "Y" : "N",
|
||||||
|
idmatrice: partMatrice[part.partNumber] || null,
|
||||||
}));
|
}));
|
||||||
|
|
||||||
|
console.log(
|
||||||
|
"Dati inviati a renumber_parts.php:",
|
||||||
|
JSON.stringify({ iddatadb: iddatadb, parts: partsToSave }),
|
||||||
|
);
|
||||||
|
|
||||||
$.ajax({
|
$.ajax({
|
||||||
url: "save_parts.php",
|
url: "renumber_parts.php",
|
||||||
method: "POST",
|
method: "POST",
|
||||||
data: JSON.stringify({
|
data: JSON.stringify({ iddatadb: iddatadb, parts: partsToSave }),
|
||||||
iddatadb: iddatadb,
|
|
||||||
parts: partsToSave,
|
|
||||||
}),
|
|
||||||
contentType: "application/json",
|
contentType: "application/json",
|
||||||
success: function (response) {
|
success: function (response) {
|
||||||
|
console.log("Risposta da renumber_parts.php:", response);
|
||||||
if (response.success) {
|
if (response.success) {
|
||||||
$rows.each(function (index) {
|
$rows.each(function (index) {
|
||||||
const $row = $(this);
|
const $row = $(this);
|
||||||
if (response.part_ids && response.part_ids[index]) {
|
const newPartId =
|
||||||
$row.attr("data-part-id", response.part_ids[index]);
|
response.part_ids && response.part_ids[index]
|
||||||
$row.data("part-id", response.part_ids[index]);
|
? response.part_ids[index]
|
||||||
|
: $row.data("part-id");
|
||||||
|
if (newPartId) {
|
||||||
|
$row.attr("data-part-id", newPartId);
|
||||||
|
$row.data("part-id", newPartId);
|
||||||
}
|
}
|
||||||
const $saveStatus = $row.find(".save-status");
|
const $saveStatus = $row.find(".save-status");
|
||||||
const $saveLoading = $row.find(".save-loading");
|
const $saveLoading = $row.find(".save-loading");
|
||||||
@@ -515,14 +826,21 @@ $(document).ready(function () {
|
|||||||
updateDescriptions();
|
updateDescriptions();
|
||||||
markUnsaved();
|
markUnsaved();
|
||||||
} else {
|
} else {
|
||||||
|
console.error("Errore dal server:", response.message);
|
||||||
alert(
|
alert(
|
||||||
"Errore nel salvataggio delle parti: " +
|
"Errore nella rinumerazione delle parti: " +
|
||||||
response.message,
|
response.message,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
error: function (xhr, status, error) {
|
error: function (xhr, status, error) {
|
||||||
alert("Errore nel salvataggio delle parti: " + error);
|
console.error("Errore AJAX:", status, error, xhr.responseText);
|
||||||
|
alert(
|
||||||
|
"Errore nella rinumerazione delle parti: " +
|
||||||
|
error +
|
||||||
|
" - " +
|
||||||
|
xhr.responseText,
|
||||||
|
);
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -560,10 +878,23 @@ $(document).ready(function () {
|
|||||||
updatePartsList();
|
updatePartsList();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
$(document).on("click", ".propagate-matrice-btn", function () {
|
||||||
|
const $listItem = $(this).closest("li");
|
||||||
|
const globalVal = $("#global-matrice").val();
|
||||||
|
if (globalVal) {
|
||||||
|
$listItem.find(".part-matrice").val(globalVal).trigger("change");
|
||||||
|
} else {
|
||||||
|
alert("Seleziona una matrice globale prima di propagare.");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
$("#partsList").on("click", "li", function (e) {
|
$("#partsList").on("click", "li", function (e) {
|
||||||
if (
|
if (
|
||||||
$(e.target).hasClass("add-to-mix-btn") ||
|
$(e.target).hasClass("add-to-mix-btn") ||
|
||||||
$(e.target).hasClass("part-color")
|
$(e.target).hasClass("part-color") ||
|
||||||
|
$(e.target).hasClass("part-matrice") ||
|
||||||
|
$(e.target).hasClass("propagate-matrice-btn") ||
|
||||||
|
$(e.target).closest(".select2-container").length
|
||||||
)
|
)
|
||||||
return;
|
return;
|
||||||
selectedPartNumber = $(this).data("part-number");
|
selectedPartNumber = $(this).data("part-number");
|
||||||
@@ -577,6 +908,7 @@ $(document).ready(function () {
|
|||||||
$("#renumberPartsBtn").on("click", function () {
|
$("#renumberPartsBtn").on("click", function () {
|
||||||
renumberParts();
|
renumberParts();
|
||||||
});
|
});
|
||||||
|
|
||||||
// ===================
|
// ===================
|
||||||
// MARKERS & DESCRIPTIONS
|
// MARKERS & DESCRIPTIONS
|
||||||
// ===================
|
// ===================
|
||||||
@@ -590,7 +922,7 @@ $(document).ready(function () {
|
|||||||
const clickX = e.clientX - rect.left;
|
const clickX = e.clientX - rect.left;
|
||||||
const clickY = e.clientY - rect.top;
|
const clickY = e.clientY - rect.top;
|
||||||
|
|
||||||
const x = clickX / photoData.scale; // convert to NATURAL coords
|
const x = clickX / photoData.scale;
|
||||||
const y = clickY / photoData.scale;
|
const y = clickY / photoData.scale;
|
||||||
|
|
||||||
const currentPhoto = $("#samplePhoto").attr("src");
|
const currentPhoto = $("#samplePhoto").attr("src");
|
||||||
@@ -831,7 +1163,7 @@ $(document).ready(function () {
|
|||||||
});
|
});
|
||||||
|
|
||||||
$("#removeAnnotationsBtn").on("click", function () {
|
$("#removeAnnotationsBtn").on("click", function () {
|
||||||
clearCanvasMarkers(true); // Remove only descriptions
|
clearCanvasMarkers(true);
|
||||||
});
|
});
|
||||||
|
|
||||||
$("#undoMarkerBtn").on("click", function () {
|
$("#undoMarkerBtn").on("click", function () {
|
||||||
@@ -840,7 +1172,6 @@ $(document).ready(function () {
|
|||||||
|
|
||||||
let unsavedChanges = false;
|
let unsavedChanges = false;
|
||||||
|
|
||||||
// --- helper functions ---
|
|
||||||
function markUnsaved() {
|
function markUnsaved() {
|
||||||
if (!unsavedChanges) {
|
if (!unsavedChanges) {
|
||||||
unsavedChanges = true;
|
unsavedChanges = true;
|
||||||
@@ -853,12 +1184,10 @@ $(document).ready(function () {
|
|||||||
$("#savePhotoBtn").removeClass("unsaved").text("Salva Foto con Nome");
|
$("#savePhotoBtn").removeClass("unsaved").text("Salva Foto con Nome");
|
||||||
}
|
}
|
||||||
|
|
||||||
// --- event listeners ---
|
|
||||||
$(document).on("input change", "#partsTableBody input", markUnsaved);
|
$(document).on("input change", "#partsTableBody input", markUnsaved);
|
||||||
$(document).on("click", ".add-row, .add-mix-row, .remove-row", markUnsaved);
|
$(document).on("click", ".add-row, .add-mix-row, .remove-row", markUnsaved);
|
||||||
$(document).on("markerChanged descriptionChanged", markUnsaved);
|
$(document).on("markerChanged descriptionChanged", markUnsaved);
|
||||||
|
|
||||||
// --- modal close protection ---
|
|
||||||
$("#partsModal").on("hide.bs.modal", function (e) {
|
$("#partsModal").on("hide.bs.modal", function (e) {
|
||||||
if (unsavedChanges) {
|
if (unsavedChanges) {
|
||||||
if (!confirm("Hai modifiche non salvate. Vuoi davvero uscire?")) {
|
if (!confirm("Hai modifiche non salvate. Vuoi davvero uscire?")) {
|
||||||
@@ -867,7 +1196,6 @@ $(document).ready(function () {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// --- SAVE BUTTON ---
|
|
||||||
$("#savePhotoBtn").on("click", function () {
|
$("#savePhotoBtn").on("click", function () {
|
||||||
const canvas = document.getElementById("photoCanvas");
|
const canvas = document.getElementById("photoCanvas");
|
||||||
const ctx = canvas.getContext("2d");
|
const ctx = canvas.getContext("2d");
|
||||||
@@ -955,11 +1283,10 @@ $(document).ready(function () {
|
|||||||
|
|
||||||
ctx.beginPath();
|
ctx.beginPath();
|
||||||
ctx.arc(x, y, radius, 0, 2 * Math.PI);
|
ctx.arc(x, y, radius, 0, 2 * Math.PI);
|
||||||
ctx.fillStyle = markerColor; // Use the stored color
|
ctx.fillStyle = markerColor;
|
||||||
ctx.fill();
|
ctx.fill();
|
||||||
|
|
||||||
ctx.lineWidth = 3;
|
ctx.lineWidth = 3;
|
||||||
ctx.strokeStyle = markerColor; // Use the same color for the border
|
ctx.strokeStyle = markerColor;
|
||||||
ctx.stroke();
|
ctx.stroke();
|
||||||
|
|
||||||
ctx.fillStyle = "#ffffff";
|
ctx.fillStyle = "#ffffff";
|
||||||
@@ -996,7 +1323,7 @@ $(document).ready(function () {
|
|||||||
);
|
);
|
||||||
$("#samplePhoto").attr("src", response.file_path);
|
$("#samplePhoto").attr("src", response.file_path);
|
||||||
loadPhoto(iddatadb);
|
loadPhoto(iddatadb);
|
||||||
clearCanvasMarkers(false); // Preserve descriptions
|
clearCanvasMarkers(false);
|
||||||
clearUnsaved();
|
clearUnsaved();
|
||||||
} else {
|
} else {
|
||||||
alert("Errore: " + response.message);
|
alert("Errore: " + response.message);
|
||||||
|
|||||||
+521
-87
@@ -7,7 +7,6 @@ document.addEventListener("DOMContentLoaded", function () {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
console.log("Caricamento contenuto per iddatadb:", iddatadb);
|
|
||||||
const response = await fetch(
|
const response = await fetch(
|
||||||
`photos_popup.php?iddatadb=${iddatadb}`,
|
`photos_popup.php?iddatadb=${iddatadb}`,
|
||||||
);
|
);
|
||||||
@@ -56,22 +55,18 @@ document.addEventListener("DOMContentLoaded", function () {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Funzione per avviare la webcam con un deviceId specifico
|
|
||||||
async function startWebcam(deviceId = null) {
|
async function startWebcam(deviceId = null) {
|
||||||
try {
|
try {
|
||||||
// Ferma il flusso video esistente, se presente
|
|
||||||
if (stream) {
|
if (stream) {
|
||||||
stream.getTracks().forEach((track) => track.stop());
|
stream.getTracks().forEach((track) => track.stop());
|
||||||
stream = null;
|
stream = null;
|
||||||
webcamVideo.srcObject = null;
|
webcamVideo.srcObject = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Configura i vincoli per getUserMedia
|
|
||||||
const constraints = {
|
const constraints = {
|
||||||
video: deviceId ? { deviceId: { exact: deviceId } } : true,
|
video: deviceId ? { deviceId: { exact: deviceId } } : true,
|
||||||
};
|
};
|
||||||
|
|
||||||
// Avvia il flusso video
|
|
||||||
stream = await navigator.mediaDevices.getUserMedia(constraints);
|
stream = await navigator.mediaDevices.getUserMedia(constraints);
|
||||||
webcamVideo.srcObject = stream;
|
webcamVideo.srcObject = stream;
|
||||||
webcamArea.style.display = "block";
|
webcamArea.style.display = "block";
|
||||||
@@ -86,21 +81,17 @@ document.addEventListener("DOMContentLoaded", function () {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Funzione per popolare il dropdown delle webcam
|
|
||||||
async function populateWebcamSelect() {
|
async function populateWebcamSelect() {
|
||||||
try {
|
try {
|
||||||
// Richiedi i permessi per accedere ai dispositivi
|
|
||||||
await navigator.mediaDevices.getUserMedia({ video: true });
|
await navigator.mediaDevices.getUserMedia({ video: true });
|
||||||
const devices = await navigator.mediaDevices.enumerateDevices();
|
const devices = await navigator.mediaDevices.enumerateDevices();
|
||||||
const videoDevices = devices.filter(
|
const videoDevices = devices.filter(
|
||||||
(device) => device.kind === "videoinput",
|
(device) => device.kind === "videoinput",
|
||||||
);
|
);
|
||||||
|
|
||||||
// Svuota il dropdown
|
|
||||||
webcamSelect.innerHTML =
|
webcamSelect.innerHTML =
|
||||||
'<option value="">Select a webcam</option>';
|
'<option value="">Select a webcam</option>';
|
||||||
|
|
||||||
// Popola il dropdown con le webcam disponibili
|
|
||||||
videoDevices.forEach((device) => {
|
videoDevices.forEach((device) => {
|
||||||
const option = document.createElement("option");
|
const option = document.createElement("option");
|
||||||
option.value = device.deviceId;
|
option.value = device.deviceId;
|
||||||
@@ -109,11 +100,9 @@ document.addEventListener("DOMContentLoaded", function () {
|
|||||||
webcamSelect.appendChild(option);
|
webcamSelect.appendChild(option);
|
||||||
});
|
});
|
||||||
|
|
||||||
// Mostra il dropdown solo se ci sono più webcam
|
|
||||||
webcamSelect.style.display =
|
webcamSelect.style.display =
|
||||||
videoDevices.length > 1 ? "block" : "none";
|
videoDevices.length > 1 ? "block" : "none";
|
||||||
|
|
||||||
// Avvia la webcam predefinita se ce n'è almeno una
|
|
||||||
if (videoDevices.length > 0) {
|
if (videoDevices.length > 0) {
|
||||||
await startWebcam(videoDevices[0].deviceId);
|
await startWebcam(videoDevices[0].deviceId);
|
||||||
} else {
|
} else {
|
||||||
@@ -129,12 +118,10 @@ document.addEventListener("DOMContentLoaded", function () {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Apri la webcam e popola il dropdown
|
|
||||||
openWebcamBtn.addEventListener("click", async () => {
|
openWebcamBtn.addEventListener("click", async () => {
|
||||||
await populateWebcamSelect();
|
await populateWebcamSelect();
|
||||||
});
|
});
|
||||||
|
|
||||||
// Gestisci il cambio della webcam selezionata
|
|
||||||
webcamSelect.addEventListener("change", async (e) => {
|
webcamSelect.addEventListener("change", async (e) => {
|
||||||
const deviceId = e.target.value;
|
const deviceId = e.target.value;
|
||||||
if (deviceId) {
|
if (deviceId) {
|
||||||
@@ -142,7 +129,6 @@ document.addEventListener("DOMContentLoaded", function () {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// Chiudi la webcam
|
|
||||||
closeWebcamBtn.addEventListener("click", () => {
|
closeWebcamBtn.addEventListener("click", () => {
|
||||||
if (stream) {
|
if (stream) {
|
||||||
stream.getTracks().forEach((track) => track.stop());
|
stream.getTracks().forEach((track) => track.stop());
|
||||||
@@ -154,7 +140,6 @@ document.addEventListener("DOMContentLoaded", function () {
|
|||||||
dropArea.style.display = "block";
|
dropArea.style.display = "block";
|
||||||
});
|
});
|
||||||
|
|
||||||
// Cattura la foto
|
|
||||||
captureBtn.addEventListener("click", () => {
|
captureBtn.addEventListener("click", () => {
|
||||||
const canvas = document.createElement("canvas");
|
const canvas = document.createElement("canvas");
|
||||||
canvas.width = webcamVideo.videoWidth;
|
canvas.width = webcamVideo.videoWidth;
|
||||||
@@ -171,7 +156,6 @@ document.addEventListener("DOMContentLoaded", function () {
|
|||||||
);
|
);
|
||||||
const loader = document.getElementById("loader");
|
const loader = document.getElementById("loader");
|
||||||
if (loader) {
|
if (loader) {
|
||||||
console.log("Mostro loader per upload webcam");
|
|
||||||
loader.style.display = "flex";
|
loader.style.display = "flex";
|
||||||
}
|
}
|
||||||
await handleFiles([file], iddatadb);
|
await handleFiles([file], iddatadb);
|
||||||
@@ -187,7 +171,6 @@ document.addEventListener("DOMContentLoaded", function () {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// Funzione per gestire il caricamento dei file
|
|
||||||
async function handleFiles(files, iddatadb) {
|
async function handleFiles(files, iddatadb) {
|
||||||
const loader = document.getElementById("loader");
|
const loader = document.getElementById("loader");
|
||||||
if (!loader) {
|
if (!loader) {
|
||||||
@@ -206,7 +189,6 @@ document.addEventListener("DOMContentLoaded", function () {
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
console.log("Inizio upload del file:", file.name);
|
|
||||||
loader.style.display = "flex";
|
loader.style.display = "flex";
|
||||||
|
|
||||||
const formData = new FormData();
|
const formData = new FormData();
|
||||||
@@ -219,9 +201,6 @@ document.addEventListener("DOMContentLoaded", function () {
|
|||||||
});
|
});
|
||||||
const result = await response.json();
|
const result = await response.json();
|
||||||
if (result.success) {
|
if (result.success) {
|
||||||
console.log(
|
|
||||||
"Upload completato con successo, ricarico popup",
|
|
||||||
);
|
|
||||||
loadPopupContent(iddatadb);
|
loadPopupContent(iddatadb);
|
||||||
} else {
|
} else {
|
||||||
alert("Errore durante il caricamento: " + result.message);
|
alert("Errore durante il caricamento: " + result.message);
|
||||||
@@ -229,13 +208,11 @@ document.addEventListener("DOMContentLoaded", function () {
|
|||||||
} catch (error) {
|
} catch (error) {
|
||||||
alert("Errore durante il caricamento: " + error.message);
|
alert("Errore durante il caricamento: " + error.message);
|
||||||
} finally {
|
} finally {
|
||||||
console.log("Nascondo loader dopo upload");
|
|
||||||
loader.style.display = "none";
|
loader.style.display = "none";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Funzione per attaccare gli event listener al contenuto del popup
|
|
||||||
function attachPhotoEventListeners(iddatadb) {
|
function attachPhotoEventListeners(iddatadb) {
|
||||||
const dropArea = document.getElementById("dropArea");
|
const dropArea = document.getElementById("dropArea");
|
||||||
const photoInput = document.getElementById("photoInput");
|
const photoInput = document.getElementById("photoInput");
|
||||||
@@ -250,9 +227,6 @@ document.addEventListener("DOMContentLoaded", function () {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
console.log("Event listener associati per iddatadb:", iddatadb);
|
|
||||||
|
|
||||||
// Gestione drag-and-drop
|
|
||||||
const preventDefaults = (e) => {
|
const preventDefaults = (e) => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
@@ -281,7 +255,6 @@ document.addEventListener("DOMContentLoaded", function () {
|
|||||||
dropArea.addEventListener(
|
dropArea.addEventListener(
|
||||||
"drop",
|
"drop",
|
||||||
(e) => {
|
(e) => {
|
||||||
console.log("Evento drop attivato");
|
|
||||||
const files = e.dataTransfer.files;
|
const files = e.dataTransfer.files;
|
||||||
if (files.length > 0) {
|
if (files.length > 0) {
|
||||||
handleFiles(files, iddatadb);
|
handleFiles(files, iddatadb);
|
||||||
@@ -293,7 +266,6 @@ document.addEventListener("DOMContentLoaded", function () {
|
|||||||
dropArea.addEventListener(
|
dropArea.addEventListener(
|
||||||
"click",
|
"click",
|
||||||
() => {
|
() => {
|
||||||
console.log("Click su dropArea, apro input file");
|
|
||||||
photoInput.click();
|
photoInput.click();
|
||||||
},
|
},
|
||||||
false,
|
false,
|
||||||
@@ -302,18 +274,15 @@ document.addEventListener("DOMContentLoaded", function () {
|
|||||||
photoInput.addEventListener(
|
photoInput.addEventListener(
|
||||||
"change",
|
"change",
|
||||||
(e) => {
|
(e) => {
|
||||||
console.log("Evento change su photoInput");
|
|
||||||
const files = e.target.files;
|
const files = e.target.files;
|
||||||
if (files.length > 0) {
|
if (files.length > 0) {
|
||||||
handleFiles(files, iddatadb);
|
handleFiles(files, iddatadb);
|
||||||
}
|
}
|
||||||
// Resetta l'input per consentire il caricamento dello stesso file
|
|
||||||
e.target.value = "";
|
e.target.value = "";
|
||||||
},
|
},
|
||||||
false,
|
false,
|
||||||
);
|
);
|
||||||
|
|
||||||
// Gestione rimozione foto
|
|
||||||
document.querySelectorAll(".delete-photo-btn").forEach((button) => {
|
document.querySelectorAll(".delete-photo-btn").forEach((button) => {
|
||||||
button.addEventListener("click", async function () {
|
button.addEventListener("click", async function () {
|
||||||
const photoId = this.getAttribute("data-photo-id");
|
const photoId = this.getAttribute("data-photo-id");
|
||||||
@@ -329,9 +298,6 @@ document.addEventListener("DOMContentLoaded", function () {
|
|||||||
});
|
});
|
||||||
const result = await response.json();
|
const result = await response.json();
|
||||||
if (result.success) {
|
if (result.success) {
|
||||||
console.log(
|
|
||||||
"Foto eliminata con successo, ricarico popup",
|
|
||||||
);
|
|
||||||
loadPopupContent(iddatadb);
|
loadPopupContent(iddatadb);
|
||||||
} else {
|
} else {
|
||||||
alert(
|
alert(
|
||||||
@@ -348,21 +314,17 @@ document.addEventListener("DOMContentLoaded", function () {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
// Gestione ingrandimento immagini
|
|
||||||
document.querySelectorAll(".thumbnail").forEach((img) => {
|
document.querySelectorAll(".thumbnail").forEach((img) => {
|
||||||
img.addEventListener("click", function () {
|
img.addEventListener("click", function () {
|
||||||
console.log("Click su thumbnail, apro modale immagine");
|
|
||||||
const enlargedImage = document.getElementById("enlargedImage");
|
const enlargedImage = document.getElementById("enlargedImage");
|
||||||
enlargedImage.src = this.src;
|
enlargedImage.src = this.src;
|
||||||
document.getElementById("imageModal").style.display = "block";
|
document.getElementById("imageModal").style.display = "block";
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
// Gestione chiusura modale immagine
|
|
||||||
const imageCloseBtn = document.querySelector(".image-modal-close");
|
const imageCloseBtn = document.querySelector(".image-modal-close");
|
||||||
if (imageCloseBtn) {
|
if (imageCloseBtn) {
|
||||||
imageCloseBtn.addEventListener("click", () => {
|
imageCloseBtn.addEventListener("click", () => {
|
||||||
console.log("Chiusura modale immagine");
|
|
||||||
document.getElementById("imageModal").style.display = "none";
|
document.getElementById("imageModal").style.display = "none";
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -370,37 +332,43 @@ document.addEventListener("DOMContentLoaded", function () {
|
|||||||
.getElementById("imageModal")
|
.getElementById("imageModal")
|
||||||
.addEventListener("click", function (event) {
|
.addEventListener("click", function (event) {
|
||||||
if (event.target === this) {
|
if (event.target === this) {
|
||||||
console.log(
|
|
||||||
"Chiusura modale immagine cliccando sullo sfondo",
|
|
||||||
);
|
|
||||||
this.style.display = "none";
|
this.style.display = "none";
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// Inizializza la gestione della webcam
|
|
||||||
setupWebcam(iddatadb);
|
setupWebcam(iddatadb);
|
||||||
|
|
||||||
// Gestione bottone Crea Collage
|
|
||||||
const createCollageBtn = document.getElementById("createCollageBtn");
|
const createCollageBtn = document.getElementById("createCollageBtn");
|
||||||
if (createCollageBtn) {
|
if (createCollageBtn) {
|
||||||
createCollageBtn.addEventListener("click", () => {
|
createCollageBtn.addEventListener("click", () => {
|
||||||
console.log("Apertura modale collage");
|
|
||||||
document.getElementById("collageModal").style.display = "block";
|
document.getElementById("collageModal").style.display = "block";
|
||||||
initCollageCanvas();
|
initCollageCanvas();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// Chiusura modale collage
|
|
||||||
const closeCollageBtn = document.querySelector(".close-collage");
|
const closeCollageBtn = document.querySelector(".close-collage");
|
||||||
if (closeCollageBtn) {
|
if (closeCollageBtn) {
|
||||||
closeCollageBtn.addEventListener("click", () => {
|
closeCollageBtn.addEventListener("click", () => {
|
||||||
console.log("Chiusura modale collage");
|
|
||||||
document.getElementById("collageModal").style.display = "none";
|
document.getElementById("collageModal").style.display = "none";
|
||||||
|
if (isCropping) {
|
||||||
|
exitCropMode();
|
||||||
|
}
|
||||||
|
if (isRemovingBackground) {
|
||||||
|
exitBackgroundRemovalMode();
|
||||||
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// Inizializza canvas con Fabric.js
|
|
||||||
let canvas;
|
let canvas;
|
||||||
|
let cropRect = null;
|
||||||
|
let isCropping = false;
|
||||||
|
let croppedImage = null;
|
||||||
|
let isApplyingCrop = false;
|
||||||
|
let isRemovingBackground = false;
|
||||||
|
let backgroundRemovalImage = null;
|
||||||
|
let history = [];
|
||||||
|
const maxHistory = 20;
|
||||||
|
|
||||||
function initCollageCanvas() {
|
function initCollageCanvas() {
|
||||||
if (typeof fabric === "undefined") {
|
if (typeof fabric === "undefined") {
|
||||||
console.error("Fabric.js non è caricato!");
|
console.error("Fabric.js non è caricato!");
|
||||||
@@ -413,11 +381,436 @@ document.addEventListener("DOMContentLoaded", function () {
|
|||||||
backgroundColor: "#fff",
|
backgroundColor: "#fff",
|
||||||
selection: true,
|
selection: true,
|
||||||
});
|
});
|
||||||
// Abilita ridimensionamento e trascinamento
|
fabric.Object.prototype.set({
|
||||||
canvas.on("object:modified", () => canvas.renderAll());
|
cornerColor: "black",
|
||||||
|
cornerStrokeColor: "black",
|
||||||
|
cornerSize: 12,
|
||||||
|
borderColor: "black",
|
||||||
|
transparentCorners: false,
|
||||||
|
});
|
||||||
|
canvas.on("object:modified", () => {
|
||||||
|
saveCanvasState();
|
||||||
|
updateLayersPanel();
|
||||||
|
canvas.renderAll();
|
||||||
|
});
|
||||||
|
canvas.on("object:added", () => {
|
||||||
|
updateLayersPanel();
|
||||||
|
});
|
||||||
|
canvas.on("object:removed", () => {
|
||||||
|
updateLayersPanel();
|
||||||
|
});
|
||||||
|
canvas.on("selection:created", () => {
|
||||||
|
updateButtons();
|
||||||
|
});
|
||||||
|
canvas.on("selection:updated", () => {
|
||||||
|
updateButtons();
|
||||||
|
});
|
||||||
|
canvas.on("selection:cleared", () => {
|
||||||
|
if (!isCropping && !isRemovingBackground) {
|
||||||
|
updateButtons();
|
||||||
|
} else if (isCropping && cropRect) {
|
||||||
|
canvas.setActiveObject(cropRect);
|
||||||
|
canvas.renderAll();
|
||||||
|
} else if (isRemovingBackground) {
|
||||||
|
canvas.setActiveObject(backgroundRemovalImage);
|
||||||
|
canvas.renderAll();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
canvas.on("mouse:down", (event) => {
|
||||||
|
if (isRemovingBackground && backgroundRemovalImage) {
|
||||||
|
handleBackgroundColorSelection(event);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
saveCanvasState();
|
||||||
|
updateLayersPanel();
|
||||||
|
updateButtons();
|
||||||
|
}
|
||||||
|
|
||||||
|
function updateLayersPanel() {
|
||||||
|
const layersList = document.getElementById("layersList");
|
||||||
|
if (!layersList) {
|
||||||
|
console.error("Elemento layersList non trovato");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
layersList.innerHTML = "";
|
||||||
|
|
||||||
|
const images = canvas.getObjects("image");
|
||||||
|
images.forEach((img, index) => {
|
||||||
|
let thumbSrc;
|
||||||
|
try {
|
||||||
|
const thumbCanvas = document.createElement("canvas");
|
||||||
|
thumbCanvas.width = 50;
|
||||||
|
thumbCanvas.height = 50;
|
||||||
|
const thumbFabric = new fabric.Canvas(thumbCanvas);
|
||||||
|
|
||||||
|
const clonedImg = fabric.util.object.clone(img);
|
||||||
|
const scaleFactor = Math.min(
|
||||||
|
50 / img.width,
|
||||||
|
50 / img.height,
|
||||||
|
);
|
||||||
|
clonedImg.scaleX = scaleFactor;
|
||||||
|
clonedImg.scaleY = scaleFactor;
|
||||||
|
clonedImg.left = (50 - img.width * scaleFactor) / 2;
|
||||||
|
clonedImg.top = (50 - img.height * scaleFactor) / 2;
|
||||||
|
clonedImg.setCoords();
|
||||||
|
thumbFabric.add(clonedImg);
|
||||||
|
thumbFabric.renderAll();
|
||||||
|
|
||||||
|
thumbSrc = thumbCanvas.toDataURL("image/png");
|
||||||
|
thumbFabric.dispose();
|
||||||
|
} catch (error) {
|
||||||
|
console.warn(
|
||||||
|
"Errore nella generazione della thumbnail per immagine",
|
||||||
|
index + 1,
|
||||||
|
error,
|
||||||
|
);
|
||||||
|
thumbSrc = img.getSrc(); // Fallback all'URL originale
|
||||||
|
}
|
||||||
|
|
||||||
|
const layerItem = document.createElement("li");
|
||||||
|
const thumbImg = document.createElement("img");
|
||||||
|
thumbImg.src = thumbSrc;
|
||||||
|
thumbImg.title = `Layer ${index + 1}`;
|
||||||
|
thumbImg.addEventListener("click", () => {
|
||||||
|
canvas.setActiveObject(img);
|
||||||
|
canvas.renderAll();
|
||||||
|
});
|
||||||
|
|
||||||
|
layerItem.appendChild(thumbImg);
|
||||||
|
layersList.appendChild(layerItem);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function saveCanvasState() {
|
||||||
|
if (isCropping || isRemovingBackground) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const state = JSON.stringify(
|
||||||
|
canvas.toJSON([
|
||||||
|
"cornerColor",
|
||||||
|
"cornerStrokeColor",
|
||||||
|
"cornerSize",
|
||||||
|
"borderColor",
|
||||||
|
"transparentCorners",
|
||||||
|
]),
|
||||||
|
);
|
||||||
|
history.push(state);
|
||||||
|
if (history.length > maxHistory) {
|
||||||
|
history.shift();
|
||||||
|
}
|
||||||
|
updateButtons();
|
||||||
|
}
|
||||||
|
|
||||||
|
function undo() {
|
||||||
|
if (history.length <= 1) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
history.pop();
|
||||||
|
const previousState = history[history.length - 1];
|
||||||
|
if (previousState) {
|
||||||
|
canvas.clear();
|
||||||
|
canvas.loadFromJSON(previousState, () => {
|
||||||
|
canvas.renderAll();
|
||||||
|
updateLayersPanel();
|
||||||
|
updateButtons();
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
console.warn("Nessuno stato precedente disponibile");
|
||||||
|
canvas.clear();
|
||||||
|
canvas.setBackgroundColor("#fff");
|
||||||
|
canvas.renderAll();
|
||||||
|
updateLayersPanel();
|
||||||
|
updateButtons();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function updateButtons() {
|
||||||
|
const cropBtn = document.getElementById("cropImageBtn");
|
||||||
|
const applyCropBtn = document.getElementById("applyCropBtn");
|
||||||
|
const cancelCropBtn = document.getElementById("cancelCropBtn");
|
||||||
|
const removeBackgroundBtn = document.getElementById(
|
||||||
|
"removeBackgroundBtn",
|
||||||
|
);
|
||||||
|
const removeImageBtn = document.getElementById("removeImageBtn");
|
||||||
|
const undoBtn = document.getElementById("undoBtn");
|
||||||
|
const instruction = document.getElementById(
|
||||||
|
"backgroundRemovalInstruction",
|
||||||
|
);
|
||||||
|
const activeObject = canvas.getActiveObject();
|
||||||
|
if (isCropping && cropRect) {
|
||||||
|
cropBtn.disabled = true;
|
||||||
|
applyCropBtn.disabled = false;
|
||||||
|
cancelCropBtn.disabled = false;
|
||||||
|
removeBackgroundBtn.disabled = true;
|
||||||
|
removeImageBtn.disabled = true;
|
||||||
|
undoBtn.disabled = true;
|
||||||
|
instruction.style.display = "none";
|
||||||
|
} else if (isRemovingBackground && backgroundRemovalImage) {
|
||||||
|
cropBtn.disabled = true;
|
||||||
|
applyCropBtn.disabled = true;
|
||||||
|
cancelCropBtn.disabled = true;
|
||||||
|
removeBackgroundBtn.disabled = true;
|
||||||
|
removeImageBtn.disabled = true;
|
||||||
|
undoBtn.disabled = true;
|
||||||
|
instruction.style.display = "block";
|
||||||
|
} else if (
|
||||||
|
activeObject &&
|
||||||
|
activeObject.type === "image" &&
|
||||||
|
!isCropping &&
|
||||||
|
!isRemovingBackground
|
||||||
|
) {
|
||||||
|
cropBtn.disabled = false;
|
||||||
|
applyCropBtn.disabled = true;
|
||||||
|
cancelCropBtn.disabled = true;
|
||||||
|
removeBackgroundBtn.disabled = false;
|
||||||
|
removeImageBtn.disabled = false;
|
||||||
|
undoBtn.disabled = history.length <= 1;
|
||||||
|
instruction.style.display = "none";
|
||||||
|
} else {
|
||||||
|
cropBtn.disabled = true;
|
||||||
|
applyCropBtn.disabled = true;
|
||||||
|
cancelCropBtn.disabled = true;
|
||||||
|
removeBackgroundBtn.disabled = true;
|
||||||
|
removeImageBtn.disabled = true;
|
||||||
|
undoBtn.disabled = history.length <= 1;
|
||||||
|
instruction.style.display = "none";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function enterCropMode() {
|
||||||
|
const activeObject = canvas.getActiveObject();
|
||||||
|
if (!activeObject || activeObject.type !== "image") {
|
||||||
|
console.warn("Nessuna immagine selezionata per il ritaglio");
|
||||||
|
alert("Seleziona un'immagine prima di attivare il ritaglio!");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
isCropping = true;
|
||||||
|
croppedImage = activeObject;
|
||||||
|
canvas.discardActiveObject();
|
||||||
|
cropRect = new fabric.Rect({
|
||||||
|
left: activeObject.left,
|
||||||
|
top: activeObject.top,
|
||||||
|
width: activeObject.width * activeObject.scaleX * 0.5,
|
||||||
|
height: activeObject.height * activeObject.scaleY * 0.5,
|
||||||
|
fill: "rgba(0, 0, 0, 0.3)",
|
||||||
|
stroke: "red",
|
||||||
|
strokeWidth: 2,
|
||||||
|
hasBorders: true,
|
||||||
|
hasControls: true,
|
||||||
|
lockRotation: true,
|
||||||
|
selectable: true,
|
||||||
|
cornerColor: "black",
|
||||||
|
cornerStrokeColor: "black",
|
||||||
|
cornerSize: 12,
|
||||||
|
borderColor: "black",
|
||||||
|
transparentCorners: false,
|
||||||
|
});
|
||||||
|
canvas.add(cropRect);
|
||||||
|
canvas.setActiveObject(cropRect);
|
||||||
|
canvas.renderAll();
|
||||||
|
updateButtons();
|
||||||
|
}
|
||||||
|
|
||||||
|
function exitCropMode() {
|
||||||
|
if (cropRect) {
|
||||||
|
canvas.remove(cropRect);
|
||||||
|
cropRect = null;
|
||||||
|
}
|
||||||
|
isCropping = false;
|
||||||
|
croppedImage = null;
|
||||||
|
isApplyingCrop = false;
|
||||||
|
canvas.discardActiveObject();
|
||||||
|
canvas.renderAll();
|
||||||
|
updateButtons();
|
||||||
|
}
|
||||||
|
|
||||||
|
function applyCrop() {
|
||||||
|
if (isApplyingCrop) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!isCropping || !cropRect || !croppedImage) {
|
||||||
|
console.warn("Condizioni per il ritaglio non soddisfatte", {
|
||||||
|
isCropping,
|
||||||
|
cropRect: !!cropRect,
|
||||||
|
croppedImage: !!croppedImage,
|
||||||
|
});
|
||||||
|
alert(
|
||||||
|
"Nessun rettangolo di ritaglio attivo o immagine selezionata!",
|
||||||
|
);
|
||||||
|
exitCropMode();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
isApplyingCrop = true;
|
||||||
|
const img = croppedImage;
|
||||||
|
const cropX = (cropRect.left - img.left) / img.scaleX;
|
||||||
|
const cropY = (cropRect.top - img.top) / img.scaleY;
|
||||||
|
const cropWidth = (cropRect.width * cropRect.scaleX) / img.scaleX;
|
||||||
|
const cropHeight = (cropRect.height * cropRect.scaleY) / img.scaleY;
|
||||||
|
|
||||||
|
fabric.Image.fromURL(
|
||||||
|
img.getSrc(),
|
||||||
|
(newImg) => {
|
||||||
|
newImg.set({
|
||||||
|
left: cropRect.left,
|
||||||
|
top: cropRect.top,
|
||||||
|
scaleX: img.scaleX,
|
||||||
|
scaleY: img.scaleY,
|
||||||
|
cropX: cropX,
|
||||||
|
cropY: cropY,
|
||||||
|
width: cropWidth,
|
||||||
|
height: cropHeight,
|
||||||
|
hasControls: true,
|
||||||
|
hasBorders: true,
|
||||||
|
cornerColor: "black",
|
||||||
|
cornerStrokeColor: "black",
|
||||||
|
cornerSize: 12,
|
||||||
|
borderColor: "black",
|
||||||
|
transparentCorners: false,
|
||||||
|
});
|
||||||
|
canvas.remove(img);
|
||||||
|
canvas.remove(cropRect);
|
||||||
|
canvas.add(newImg);
|
||||||
|
canvas.setActiveObject(newImg);
|
||||||
|
exitCropMode();
|
||||||
|
saveCanvasState();
|
||||||
|
updateLayersPanel();
|
||||||
|
canvas.renderAll();
|
||||||
|
},
|
||||||
|
{ crossOrigin: "anonymous" },
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function enterBackgroundRemovalMode() {
|
||||||
|
const activeObject = canvas.getActiveObject();
|
||||||
|
if (!activeObject || activeObject.type !== "image") {
|
||||||
|
console.warn(
|
||||||
|
"Nessuna immagine selezionata per la rimozione dello sfondo",
|
||||||
|
);
|
||||||
|
alert(
|
||||||
|
"Seleziona un'immagine prima di attivare la rimozione dello sfondo!",
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
isRemovingBackground = true;
|
||||||
|
backgroundRemovalImage = activeObject;
|
||||||
|
updateButtons();
|
||||||
|
}
|
||||||
|
|
||||||
|
function exitBackgroundRemovalMode() {
|
||||||
|
isRemovingBackground = false;
|
||||||
|
backgroundRemovalImage = null;
|
||||||
|
canvas.discardActiveObject();
|
||||||
|
canvas.renderAll();
|
||||||
|
updateButtons();
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleBackgroundColorSelection(event) {
|
||||||
|
if (!isRemovingBackground || !backgroundRemovalImage) {
|
||||||
|
console.warn(
|
||||||
|
"Condizioni per la rimozione dello sfondo non soddisfatte",
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const pointer = canvas.getPointer(event.e);
|
||||||
|
const img = backgroundRemovalImage;
|
||||||
|
|
||||||
|
const tempCanvas = document.createElement("canvas");
|
||||||
|
tempCanvas.width = img.width;
|
||||||
|
tempCanvas.height = img.height;
|
||||||
|
const ctx = tempCanvas.getContext("2d");
|
||||||
|
ctx.drawImage(img.getElement(), 0, 0, img.width, img.height);
|
||||||
|
|
||||||
|
const imgLeft = img.left;
|
||||||
|
const imgTop = img.top;
|
||||||
|
const scaleX = img.scaleX;
|
||||||
|
const scaleY = img.scaleY;
|
||||||
|
const x = (pointer.x - imgLeft) / scaleX;
|
||||||
|
const y = (pointer.y - imgTop) / scaleY;
|
||||||
|
|
||||||
|
const pixelData = ctx.getImageData(x, y, 1, 1).data;
|
||||||
|
const targetColor = {
|
||||||
|
r: pixelData[0],
|
||||||
|
g: pixelData[1],
|
||||||
|
b: pixelData[2],
|
||||||
|
};
|
||||||
|
|
||||||
|
removeBackground(img, targetColor);
|
||||||
|
}
|
||||||
|
|
||||||
|
function removeBackground(img, targetColor) {
|
||||||
|
const tempCanvas = document.createElement("canvas");
|
||||||
|
tempCanvas.width = img.width;
|
||||||
|
tempCanvas.height = img.height;
|
||||||
|
const ctx = tempCanvas.getContext("2d");
|
||||||
|
ctx.drawImage(img.getElement(), 0, 0, img.width, img.height);
|
||||||
|
|
||||||
|
const imageData = ctx.getImageData(
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
tempCanvas.width,
|
||||||
|
tempCanvas.height,
|
||||||
|
);
|
||||||
|
const data = imageData.data;
|
||||||
|
const tolerance = 50;
|
||||||
|
|
||||||
|
for (let i = 0; i < data.length; i += 4) {
|
||||||
|
const r = data[i];
|
||||||
|
const g = data[i + 1];
|
||||||
|
const b = data[i + 2];
|
||||||
|
if (
|
||||||
|
Math.abs(r - targetColor.r) <= tolerance &&
|
||||||
|
Math.abs(g - targetColor.g) <= tolerance &&
|
||||||
|
Math.abs(b - targetColor.b) <= tolerance
|
||||||
|
) {
|
||||||
|
data[i + 3] = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx.putImageData(imageData, 0, 0);
|
||||||
|
const newImageUrl = tempCanvas.toDataURL("image/png");
|
||||||
|
|
||||||
|
fabric.Image.fromURL(
|
||||||
|
newImageUrl,
|
||||||
|
(newImg) => {
|
||||||
|
newImg.set({
|
||||||
|
left: img.left,
|
||||||
|
top: img.top,
|
||||||
|
scaleX: img.scaleX,
|
||||||
|
scaleY: img.scaleY,
|
||||||
|
hasControls: true,
|
||||||
|
hasBorders: true,
|
||||||
|
cornerColor: "black",
|
||||||
|
cornerStrokeColor: "black",
|
||||||
|
cornerSize: 12,
|
||||||
|
borderColor: "black",
|
||||||
|
transparentCorners: false,
|
||||||
|
});
|
||||||
|
canvas.remove(img);
|
||||||
|
canvas.add(newImg);
|
||||||
|
canvas.setActiveObject(newImg);
|
||||||
|
exitBackgroundRemovalMode();
|
||||||
|
saveCanvasState();
|
||||||
|
updateLayersPanel();
|
||||||
|
canvas.renderAll();
|
||||||
|
},
|
||||||
|
{ crossOrigin: "anonymous" },
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function removeImage() {
|
||||||
|
const activeObject = canvas.getActiveObject();
|
||||||
|
if (!activeObject || activeObject.type !== "image") {
|
||||||
|
console.warn("Nessuna immagine selezionata per la rimozione");
|
||||||
|
alert("Seleziona un'immagine da rimuovere!");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
canvas.remove(activeObject);
|
||||||
|
canvas.discardActiveObject();
|
||||||
|
saveCanvasState();
|
||||||
|
updateLayersPanel();
|
||||||
|
canvas.renderAll();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Aggiungi foto selezionate al canvas
|
|
||||||
const addToCanvasBtn = document.getElementById("addToCanvasBtn");
|
const addToCanvasBtn = document.getElementById("addToCanvasBtn");
|
||||||
if (addToCanvasBtn) {
|
if (addToCanvasBtn) {
|
||||||
addToCanvasBtn.addEventListener("click", () => {
|
addToCanvasBtn.addEventListener("click", () => {
|
||||||
@@ -430,29 +823,40 @@ document.addEventListener("DOMContentLoaded", function () {
|
|||||||
}
|
}
|
||||||
checkboxes.forEach((cb) => {
|
checkboxes.forEach((cb) => {
|
||||||
const imgPath = cb.getAttribute("data-path");
|
const imgPath = cb.getAttribute("data-path");
|
||||||
fabric.Image.fromURL(imgPath, (img) => {
|
fabric.Image.fromURL(
|
||||||
img.set({
|
imgPath,
|
||||||
left: Math.random() * 600, // Posizione random iniziale
|
(img) => {
|
||||||
top: Math.random() * 400,
|
img.set({
|
||||||
scaleX: 0.5, // Scala iniziale
|
left: Math.random() * 600,
|
||||||
scaleY: 0.5,
|
top: Math.random() * 400,
|
||||||
hasControls: true, // Abilita resize/rotate
|
scaleX: 0.5,
|
||||||
hasBorders: true,
|
scaleY: 0.5,
|
||||||
});
|
hasControls: true,
|
||||||
canvas.add(img);
|
hasBorders: true,
|
||||||
canvas.renderAll();
|
cornerColor: "black",
|
||||||
});
|
cornerStrokeColor: "black",
|
||||||
|
cornerSize: 12,
|
||||||
|
borderColor: "black",
|
||||||
|
transparentCorners: false,
|
||||||
|
});
|
||||||
|
canvas.add(img);
|
||||||
|
canvas.renderAll();
|
||||||
|
},
|
||||||
|
{ crossOrigin: "anonymous" },
|
||||||
|
);
|
||||||
});
|
});
|
||||||
// Deseleziona checkbox dopo aggiunta
|
|
||||||
checkboxes.forEach((cb) => (cb.checked = false));
|
checkboxes.forEach((cb) => (cb.checked = false));
|
||||||
|
saveCanvasState();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// Salva collage
|
|
||||||
const saveCollageBtn = document.getElementById("saveCollageBtn");
|
const saveCollageBtn = document.getElementById("saveCollageBtn");
|
||||||
if (saveCollageBtn) {
|
if (saveCollageBtn) {
|
||||||
saveCollageBtn.addEventListener("click", async () => {
|
saveCollageBtn.addEventListener("click", async () => {
|
||||||
if (canvas.getObjects().length === 0) {
|
if (
|
||||||
|
canvas.getObjects().length === 0 &&
|
||||||
|
!canvas.backgroundImage
|
||||||
|
) {
|
||||||
alert("Il canvas è vuoto! Aggiungi almeno una foto.");
|
alert("Il canvas è vuoto! Aggiungi almeno una foto.");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -465,25 +869,12 @@ document.addEventListener("DOMContentLoaded", function () {
|
|||||||
type: "image/jpeg",
|
type: "image/jpeg",
|
||||||
});
|
});
|
||||||
|
|
||||||
// Upload come nuova foto
|
|
||||||
await handleFiles([file], iddatadb);
|
await handleFiles([file], iddatadb);
|
||||||
// Chiudi modale e ricarica popup
|
|
||||||
document.getElementById("collageModal").style.display = "none";
|
document.getElementById("collageModal").style.display = "none";
|
||||||
loadPopupContent(iddatadb);
|
loadPopupContent(iddatadb);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// Pulisci canvas
|
|
||||||
const clearCanvasBtn = document.getElementById("clearCanvasBtn");
|
|
||||||
if (clearCanvasBtn) {
|
|
||||||
clearCanvasBtn.addEventListener("click", () => {
|
|
||||||
canvas.clear();
|
|
||||||
canvas.setBackgroundColor("#fff");
|
|
||||||
canvas.renderAll();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// Gestione livelli delle immagini
|
|
||||||
const bringToFrontBtn = document.getElementById("bringToFrontBtn");
|
const bringToFrontBtn = document.getElementById("bringToFrontBtn");
|
||||||
if (bringToFrontBtn) {
|
if (bringToFrontBtn) {
|
||||||
bringToFrontBtn.addEventListener("click", () => {
|
bringToFrontBtn.addEventListener("click", () => {
|
||||||
@@ -491,6 +882,8 @@ document.addEventListener("DOMContentLoaded", function () {
|
|||||||
if (activeObject) {
|
if (activeObject) {
|
||||||
canvas.bringToFront(activeObject);
|
canvas.bringToFront(activeObject);
|
||||||
canvas.renderAll();
|
canvas.renderAll();
|
||||||
|
saveCanvasState();
|
||||||
|
updateLayersPanel();
|
||||||
} else {
|
} else {
|
||||||
alert("Seleziona un'immagine sul canvas!");
|
alert("Seleziona un'immagine sul canvas!");
|
||||||
}
|
}
|
||||||
@@ -504,6 +897,8 @@ document.addEventListener("DOMContentLoaded", function () {
|
|||||||
if (activeObject) {
|
if (activeObject) {
|
||||||
canvas.sendToBack(activeObject);
|
canvas.sendToBack(activeObject);
|
||||||
canvas.renderAll();
|
canvas.renderAll();
|
||||||
|
saveCanvasState();
|
||||||
|
updateLayersPanel();
|
||||||
} else {
|
} else {
|
||||||
alert("Seleziona un'immagine sul canvas!");
|
alert("Seleziona un'immagine sul canvas!");
|
||||||
}
|
}
|
||||||
@@ -517,6 +912,8 @@ document.addEventListener("DOMContentLoaded", function () {
|
|||||||
if (activeObject) {
|
if (activeObject) {
|
||||||
canvas.bringForward(activeObject);
|
canvas.bringForward(activeObject);
|
||||||
canvas.renderAll();
|
canvas.renderAll();
|
||||||
|
saveCanvasState();
|
||||||
|
updateLayersPanel();
|
||||||
} else {
|
} else {
|
||||||
alert("Seleziona un'immagine sul canvas!");
|
alert("Seleziona un'immagine sul canvas!");
|
||||||
}
|
}
|
||||||
@@ -530,21 +927,64 @@ document.addEventListener("DOMContentLoaded", function () {
|
|||||||
if (activeObject) {
|
if (activeObject) {
|
||||||
canvas.sendBackwards(activeObject);
|
canvas.sendBackwards(activeObject);
|
||||||
canvas.renderAll();
|
canvas.renderAll();
|
||||||
|
saveCanvasState();
|
||||||
|
updateLayersPanel();
|
||||||
} else {
|
} else {
|
||||||
alert("Seleziona un'immagine sul canvas!");
|
alert("Seleziona un'immagine sul canvas!");
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// Assicurati che il loader sia nascosto all'apertura del popup
|
const cropImageBtn = document.getElementById("cropImageBtn");
|
||||||
|
if (cropImageBtn) {
|
||||||
|
cropImageBtn.addEventListener("click", () => {
|
||||||
|
enterCropMode();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const applyCropBtn = document.getElementById("applyCropBtn");
|
||||||
|
if (applyCropBtn) {
|
||||||
|
applyCropBtn.addEventListener("click", () => {
|
||||||
|
applyCrop();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const cancelCropBtn = document.getElementById("cancelCropBtn");
|
||||||
|
if (cancelCropBtn) {
|
||||||
|
cancelCropBtn.addEventListener("click", () => {
|
||||||
|
exitCropMode();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const removeBackgroundBtn = document.getElementById(
|
||||||
|
"removeBackgroundBtn",
|
||||||
|
);
|
||||||
|
if (removeBackgroundBtn) {
|
||||||
|
removeBackgroundBtn.addEventListener("click", () => {
|
||||||
|
enterBackgroundRemovalMode();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const removeImageBtn = document.getElementById("removeImageBtn");
|
||||||
|
if (removeImageBtn) {
|
||||||
|
removeImageBtn.addEventListener("click", () => {
|
||||||
|
removeImage();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const undoBtn = document.getElementById("undoBtn");
|
||||||
|
if (undoBtn) {
|
||||||
|
undoBtn.addEventListener("click", () => {
|
||||||
|
undo();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
const loader = document.getElementById("loader");
|
const loader = document.getElementById("loader");
|
||||||
if (loader) {
|
if (loader) {
|
||||||
console.log("Nascondo loader all'apertura del popup");
|
|
||||||
loader.style.display = "none";
|
loader.style.display = "none";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Gestione del pulsante Photos
|
|
||||||
const photosButtons = document.querySelectorAll(".photos-btn");
|
const photosButtons = document.querySelectorAll(".photos-btn");
|
||||||
const photosModal = document.getElementById("photosModal");
|
const photosModal = document.getElementById("photosModal");
|
||||||
const closeBtn = document.querySelector(".close-btn");
|
const closeBtn = document.querySelector(".close-btn");
|
||||||
@@ -552,10 +992,6 @@ document.addEventListener("DOMContentLoaded", function () {
|
|||||||
if (photosButtons.length && photosModal && closeBtn) {
|
if (photosButtons.length && photosModal && closeBtn) {
|
||||||
photosButtons.forEach((button) => {
|
photosButtons.forEach((button) => {
|
||||||
button.addEventListener("click", function () {
|
button.addEventListener("click", function () {
|
||||||
console.log(
|
|
||||||
"Pulsante Photos cliccato per iddatadb:",
|
|
||||||
this.getAttribute("data-iddatadb"),
|
|
||||||
);
|
|
||||||
const iddatadb = this.getAttribute("data-iddatadb");
|
const iddatadb = this.getAttribute("data-iddatadb");
|
||||||
loadPopupContent(iddatadb);
|
loadPopupContent(iddatadb);
|
||||||
photosModal.style.display = "block";
|
photosModal.style.display = "block";
|
||||||
@@ -564,7 +1000,6 @@ document.addEventListener("DOMContentLoaded", function () {
|
|||||||
});
|
});
|
||||||
|
|
||||||
closeBtn.addEventListener("click", function () {
|
closeBtn.addEventListener("click", function () {
|
||||||
console.log("Chiusura modale photos");
|
|
||||||
photosModal.style.display = "none";
|
photosModal.style.display = "none";
|
||||||
document.querySelector(".overlay").style.display = "none";
|
document.querySelector(".overlay").style.display = "none";
|
||||||
document.body.style.pointerEvents = "auto";
|
document.body.style.pointerEvents = "auto";
|
||||||
@@ -572,7 +1007,6 @@ document.addEventListener("DOMContentLoaded", function () {
|
|||||||
|
|
||||||
window.addEventListener("click", function (event) {
|
window.addEventListener("click", function (event) {
|
||||||
if (event.target === photosModal) {
|
if (event.target === photosModal) {
|
||||||
console.log("Chiusura modale photos cliccando sullo sfondo");
|
|
||||||
photosModal.style.display = "none";
|
photosModal.style.display = "none";
|
||||||
document.querySelector(".overlay").style.display = "none";
|
document.querySelector(".overlay").style.display = "none";
|
||||||
document.body.style.pointerEvents = "auto";
|
document.body.style.pointerEvents = "auto";
|
||||||
|
|||||||
@@ -4,6 +4,8 @@ include('include/headscript.php');
|
|||||||
// Includi Fabric.js solo per questa pagina
|
// Includi Fabric.js solo per questa pagina
|
||||||
?>
|
?>
|
||||||
|
|
||||||
|
<script src="https://cdnjs.cloudflare.com/ajax/libs/fabric.js/5.3.1/fabric.min.js"></script>
|
||||||
|
|
||||||
<?php
|
<?php
|
||||||
// Includi l'autoloader di Composer
|
// Includi l'autoloader di Composer
|
||||||
require_once __DIR__ . '/../../vendor/autoload.php';
|
require_once __DIR__ . '/../../vendor/autoload.php';
|
||||||
@@ -19,7 +21,6 @@ use Endroid\QrCode\Writer\PngWriter;
|
|||||||
try {
|
try {
|
||||||
$dotenv = Dotenv\Dotenv::createImmutable(__DIR__ . '/../../');
|
$dotenv = Dotenv\Dotenv::createImmutable(__DIR__ . '/../../');
|
||||||
$dotenv->load();
|
$dotenv->load();
|
||||||
error_log("File .env caricato correttamente da " . __DIR__ . '/../../.env');
|
|
||||||
} catch (Exception $e) {
|
} catch (Exception $e) {
|
||||||
error_log("Errore nel caricamento del file .env: " . $e->getMessage());
|
error_log("Errore nel caricamento del file .env: " . $e->getMessage());
|
||||||
echo json_encode(['error' => 'Errore nel caricamento del file di configurazione']);
|
echo json_encode(['error' => 'Errore nel caricamento del file di configurazione']);
|
||||||
@@ -171,10 +172,10 @@ $result->saveToFile($qrCodeFile);
|
|||||||
<img id="enlargedImage" class="image-modal-content" src="" alt="Immagine ingrandita">
|
<img id="enlargedImage" class="image-modal-content" src="" alt="Immagine ingrandita">
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Nuovo modale per collage -->
|
<!-- Modale per collage -->
|
||||||
<div id="collageModal" class="modal" style="display: none; position: fixed; z-index: 1002; left: 0; top: 0; width: 100%; height: 100%; overflow: auto; background-color: rgba(0,0,0,0.8);">
|
<div id="collageModal" class="modal" style="display: none; position: fixed; z-index: 1002; left: 0; top: 0; width: 100%; height: 100%; overflow: auto; background-color: rgba(0,0,0,0.8);">
|
||||||
<div class="modal-content" style="background: white; margin: 5% auto; padding: 20px; width: 80%; max-width: 1200px; position: relative;">
|
<div class="modal-content" style="background: white; margin: 5% auto; padding: 20px; width: 80%; max-width: 1200px; position: relative;">
|
||||||
<span class="close-collage" style="position: absolute; top: 10px; right: 20px; font-size: 30px; cursor: pointer;">×</span>
|
<span class="close-collage" style="position: absolute; top: 10px; right: 20px; font-size: 30px; font-weight: bold; color: #333; cursor: pointer; z-index: 1003; background: #fff; padding: 5px 10px; border-radius: 50%;">×</span>
|
||||||
<h3>Crea Collage</h3>
|
<h3>Crea Collage</h3>
|
||||||
|
|
||||||
<!-- Lista foto selezionabili -->
|
<!-- Lista foto selezionabili -->
|
||||||
@@ -193,14 +194,26 @@ $result->saveToFile($qrCodeFile);
|
|||||||
<!-- Canvas per editing -->
|
<!-- Canvas per editing -->
|
||||||
<canvas id="collageCanvas" width="800" height="600" style="border: 1px solid #ccc; margin-top: 20px;"></canvas>
|
<canvas id="collageCanvas" width="800" height="600" style="border: 1px solid #ccc; margin-top: 20px;"></canvas>
|
||||||
|
|
||||||
|
<!-- Pannello dei livelli -->
|
||||||
|
<div id="layersPanel" style="width: 120px; max-height: 600px; overflow-y: auto; background: #f8f9fa; padding: 10px; position: absolute; right: 0; top: 60px;">
|
||||||
|
<h4 style="margin: 0 0 10px 0; font-size: 16px;">Livelli</h4>
|
||||||
|
<ul id="layersList" style="list-style: none; padding: 0;"></ul>
|
||||||
|
</div>
|
||||||
|
|
||||||
<!-- Bottoni azioni -->
|
<!-- Bottoni azioni -->
|
||||||
<div style="margin-top: 20px;">
|
<div style="margin-top: 20px; display: flex; flex-wrap: wrap; gap: 5px;">
|
||||||
<button id="saveCollageBtn">Salva Collage</button>
|
<button id="saveCollageBtn" title="Salva il collage"><i class="fas fa-save"></i></button>
|
||||||
<button id="clearCanvasBtn">Pulisci Canvas</button>
|
<button id="bringToFrontBtn" title="Porta in primo piano"><i class="fas fa-arrow-up"></i></button>
|
||||||
<button id="bringToFrontBtn" title="Porta in primo piano">In Alto</button>
|
<button id="sendToBackBtn" title="Manda in fondo"><i class="fas fa-arrow-down"></i></button>
|
||||||
<button id="sendToBackBtn" title="Manda in fondo">In Fondo</button>
|
<button id="bringForwardBtn" title="Sposta avanti di un livello"><i class="fas fa-arrow-circle-up"></i></button>
|
||||||
<button id="bringForwardBtn" title="Sposta avanti di un livello">Avanti</button>
|
<button id="sendBackwardBtn" title="Sposta indietro di un livello"><i class="fas fa-arrow-circle-down"></i></button>
|
||||||
<button id="sendBackwardBtn" title="Sposta indietro di un livello">Indietro</button>
|
<button id="cropImageBtn" title="Ritaglia immagine selezionata" disabled><i class="fas fa-crop"></i></button>
|
||||||
|
<button id="applyCropBtn" title="Applica ritaglio" disabled><i class="fas fa-crop"></i> Applica</button>
|
||||||
|
<button id="cancelCropBtn" title="Annulla ritaglio" disabled><i class="fas fa-crop"></i> Annulla</button>
|
||||||
|
<button id="removeBackgroundBtn" title="Rimuovi sfondo immagine selezionata" disabled><i class="fas fa-eraser"></i> Rimuovi Sfondo</button>
|
||||||
|
<button id="removeImageBtn" title="Rimuovi immagine selezionata" disabled><i class="fas fa-trash-alt"></i> Rimuovi</button>
|
||||||
|
<button id="undoBtn" title="Annulla ultima azione" disabled><i class="fas fa-undo"></i></button>
|
||||||
|
<p id="backgroundRemovalInstruction" style="display: none; color: #007bff;">Clicca sull'immagine per selezionare il colore dello sfondo da rimuovere</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -283,4 +296,71 @@ $result->saveToFile($qrCodeFile);
|
|||||||
font-size: 16px;
|
font-size: 16px;
|
||||||
color: white;
|
color: white;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Stile per i pulsanti del modale collage */
|
||||||
|
#collageModal button {
|
||||||
|
padding: 8px 12px;
|
||||||
|
margin: 5px;
|
||||||
|
border: none;
|
||||||
|
cursor: pointer;
|
||||||
|
border-radius: 4px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 5px;
|
||||||
|
font-size: 14px;
|
||||||
|
}
|
||||||
|
|
||||||
|
#saveCollageBtn {
|
||||||
|
background: #28a745;
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
#bringToFrontBtn,
|
||||||
|
#sendToBackBtn,
|
||||||
|
#bringForwardBtn,
|
||||||
|
#sendBackwardBtn {
|
||||||
|
background: #007bff;
|
||||||
|
color: white;
|
||||||
|
padding: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
#cropImageBtn,
|
||||||
|
#applyCropBtn,
|
||||||
|
#cancelCropBtn,
|
||||||
|
#removeBackgroundBtn,
|
||||||
|
#removeImageBtn,
|
||||||
|
#undoBtn {
|
||||||
|
background: #ffc107;
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
#collageModal button:disabled {
|
||||||
|
background: #ccc;
|
||||||
|
cursor: not-allowed;
|
||||||
|
}
|
||||||
|
|
||||||
|
#collageModal button i {
|
||||||
|
font-size: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Stile per il pannello dei livelli */
|
||||||
|
#layersPanel {
|
||||||
|
z-index: 1002;
|
||||||
|
}
|
||||||
|
|
||||||
|
#layersPanel li {
|
||||||
|
margin-bottom: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
#layersPanel img {
|
||||||
|
width: 50px;
|
||||||
|
height: 50px;
|
||||||
|
border: 2px solid #ccc;
|
||||||
|
cursor: pointer;
|
||||||
|
object-fit: cover;
|
||||||
|
}
|
||||||
|
|
||||||
|
#layersPanel img:hover {
|
||||||
|
border-color: #007bff;
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
@@ -0,0 +1,381 @@
|
|||||||
|
<?php
|
||||||
|
// Abilita errori per debug
|
||||||
|
ini_set('display_errors', 1);
|
||||||
|
ini_set('display_startup_errors', 1);
|
||||||
|
error_reporting(E_ALL);
|
||||||
|
ini_set('log_errors', 1);
|
||||||
|
ini_set('error_log', __DIR__ . '/quotations_debug.log');
|
||||||
|
if (!file_exists(__DIR__ . '/quotations_debug.log')) {
|
||||||
|
file_put_contents(__DIR__ . '/quotations_debug.log', "Inizio operazioni alle " . date('Y-m-d H:i:s') . "\n", FILE_APPEND);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Log iniziale
|
||||||
|
error_log("Inizio operazioni alle " . date('Y-m-d H:i:s'));
|
||||||
|
|
||||||
|
include('include/headscript.php');
|
||||||
|
|
||||||
|
$db = DBHandlerSelect::getInstance();
|
||||||
|
$pdo = $db->getConnection();
|
||||||
|
|
||||||
|
// Recupera l'ID dell'utente loggato
|
||||||
|
$user_id = $iduserlogin ?? 1;
|
||||||
|
|
||||||
|
// Gestione creazione nuova quotation (crea record vuoto su conferma)
|
||||||
|
if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['action']) && $_POST['action'] === 'create') {
|
||||||
|
$description = '';
|
||||||
|
$customer = '';
|
||||||
|
|
||||||
|
$stmt = $pdo->prepare("INSERT INTO quotations (description, customer, iduser) VALUES (?, ?, ?)");
|
||||||
|
$stmt->execute([$description, $customer, $user_id]);
|
||||||
|
$newId = $pdo->lastInsertId();
|
||||||
|
|
||||||
|
// Log creazione
|
||||||
|
error_log("Creata nuova quotation ID: $newId");
|
||||||
|
|
||||||
|
// Reindirizza alla modifica della nuova quotation
|
||||||
|
header("Location: quotations.php?edit_id=" . $newId . "&status=success&message=" . urlencode("Quotation creata con successo"));
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Gestione modifica quotation
|
||||||
|
if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['action']) && $_POST['action'] === 'update' && isset($_POST['id'])) {
|
||||||
|
$id = intval($_POST['id']);
|
||||||
|
$description = $_POST['description'] ?? '';
|
||||||
|
$customer = $_POST['customer'] ?? '';
|
||||||
|
|
||||||
|
$stmt = $pdo->prepare("UPDATE quotations SET description = ?, customer = ? WHERE id = ? AND iduser = ?");
|
||||||
|
$stmt->execute([$description, $customer, $id, $user_id]);
|
||||||
|
|
||||||
|
// Log modifica
|
||||||
|
error_log("Modificata quotation ID: $id");
|
||||||
|
|
||||||
|
// Reindirizza alla lista delle quotations
|
||||||
|
header("Location: quotations.php?status=success&message=" . urlencode("Quotation modificata con successo"));
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Gestione cancellazione quotation
|
||||||
|
if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['action']) && $_POST['action'] === 'delete' && isset($_POST['id'])) {
|
||||||
|
$id = intval($_POST['id']);
|
||||||
|
|
||||||
|
$stmt = $pdo->prepare("DELETE FROM quotations WHERE id = ? AND iduser = ?");
|
||||||
|
$stmt->execute([$id, $user_id]);
|
||||||
|
|
||||||
|
// Log cancellazione
|
||||||
|
error_log("Cancellata quotation ID: $id");
|
||||||
|
|
||||||
|
header("Location: quotations.php?status=success&message=" . urlencode("Quotation cancellata con successo"));
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Recupera tutte le quotations per l'utente
|
||||||
|
$stmt = $pdo->prepare("SELECT * FROM quotations WHERE iduser = ? ORDER BY creation_date DESC");
|
||||||
|
$stmt->execute([$user_id]);
|
||||||
|
$quotations = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
||||||
|
|
||||||
|
// Verifica se è richiesta la modifica di una quotation
|
||||||
|
$editQuotation = null;
|
||||||
|
if (isset($_GET['edit_id'])) {
|
||||||
|
$editId = intval($_GET['edit_id']);
|
||||||
|
$stmt = $pdo->prepare("SELECT * FROM quotations WHERE id = ? AND iduser = ?");
|
||||||
|
$stmt->execute([$editId, $user_id]);
|
||||||
|
$editQuotation = $stmt->fetch(PDO::FETCH_ASSOC);
|
||||||
|
}
|
||||||
|
?>
|
||||||
|
|
||||||
|
<!doctype html>
|
||||||
|
<html lang="en">
|
||||||
|
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||||
|
<link rel="icon" href="assets/images/favicon-32x32.png" type="image/png" />
|
||||||
|
<?php include('cssinclude.php'); ?>
|
||||||
|
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.5.1/css/all.min.css" integrity="sha512-DTOQO9RWCH3ppGqcWaEA1BIZOC6xxalwEsw9c2QQeAIftl+Vegovlnee1c9QX4TctnWMn13TZye+giMm8e2Lw==" crossorigin="anonymous" referrerpolicy="no-referrer" />
|
||||||
|
<link rel="stylesheet" href="https://cdn.datatables.net/1.13.4/css/jquery.dataTables.min.css">
|
||||||
|
<style>
|
||||||
|
/* Stili simili alla pagina fornita, adattati */
|
||||||
|
.cell-changed {
|
||||||
|
background-color: #fff3b0 !important;
|
||||||
|
transition: background-color 0.3s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
input.manual-input,
|
||||||
|
select.manual-input {
|
||||||
|
background-color: #fff3cd;
|
||||||
|
}
|
||||||
|
|
||||||
|
input.required-input,
|
||||||
|
select.required-input {
|
||||||
|
background-color: #f8d7da;
|
||||||
|
}
|
||||||
|
|
||||||
|
input,
|
||||||
|
select,
|
||||||
|
textarea {
|
||||||
|
width: 100%;
|
||||||
|
box-sizing: border-box;
|
||||||
|
border: 1px solid #ced4da;
|
||||||
|
border-radius: 4px;
|
||||||
|
padding: 5px;
|
||||||
|
font-size: 14px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.action-btn {
|
||||||
|
padding: 6px 8px;
|
||||||
|
margin-right: 5px;
|
||||||
|
border: none;
|
||||||
|
border-radius: 5px;
|
||||||
|
cursor: pointer;
|
||||||
|
width: 35px;
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
|
||||||
|
.save-btn {
|
||||||
|
background-color: #28a745;
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
.delete-btn {
|
||||||
|
background-color: #dc3545;
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
.photos-btn {
|
||||||
|
background-color: #007bff;
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
.parts-btn {
|
||||||
|
background-color: #ffc107;
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-group {
|
||||||
|
margin-bottom: 15px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-group label {
|
||||||
|
display: block;
|
||||||
|
margin-bottom: 5px;
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-group textarea {
|
||||||
|
height: 100px;
|
||||||
|
resize: vertical;
|
||||||
|
}
|
||||||
|
|
||||||
|
.flash-success {
|
||||||
|
background-color: #d4edda !important;
|
||||||
|
transition: background-color 0.3s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.quotation-actions {
|
||||||
|
margin-top: 20px;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
<title>Gestione Quotations - <?= htmlspecialchars($titlewebsite, ENT_QUOTES, 'UTF-8'); ?></title>
|
||||||
|
</head>
|
||||||
|
|
||||||
|
<body>
|
||||||
|
<div class="wrapper">
|
||||||
|
<?php include('include/navbar.php'); ?>
|
||||||
|
<?php include('include/topbar.php'); ?>
|
||||||
|
<div class="page-wrapper">
|
||||||
|
<div class="page-content">
|
||||||
|
<div class="card radius-10">
|
||||||
|
<div class="card-header">
|
||||||
|
<div class="d-flex align-items-center">
|
||||||
|
<div>
|
||||||
|
<h6 class="mb-0">Gestione Quotations</h6>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="card-body">
|
||||||
|
<?php if ($editQuotation): ?>
|
||||||
|
<!-- Modifica Quotation -->
|
||||||
|
<h6 class="mb-3">Modifica Quotation ID: <?= $editQuotation['id'] ?></h6>
|
||||||
|
<form id="editForm" method="post">
|
||||||
|
<input type="hidden" name="action" value="update">
|
||||||
|
<input type="hidden" name="id" value="<?= $editQuotation['id'] ?>">
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="description">Descrizione</label>
|
||||||
|
<textarea id="description" name="description" class="manual-input required-input" required><?= htmlspecialchars($editQuotation['description']) ?></textarea>
|
||||||
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="customer">Cliente</label>
|
||||||
|
<input type="text" id="customer" name="customer" class="manual-input required-input" value="<?= htmlspecialchars($editQuotation['customer']) ?>" required>
|
||||||
|
</div>
|
||||||
|
<button type="submit" class="btn btn-primary">Salva Modifiche</button>
|
||||||
|
<a href="quotations.php" class="btn btn-secondary">Torna alla Lista</a>
|
||||||
|
</form>
|
||||||
|
<div class="quotation-actions">
|
||||||
|
<h6 class="mb-3">Azioni</h6>
|
||||||
|
<button type="button" class="photos-btn action-btn" data-idquotations="<?= $editQuotation['id'] ?>" title="Photos"><i class="fas fa-camera"></i></button>
|
||||||
|
<button type="button" class="parts-btn action-btn" data-idquotations="<?= $editQuotation['id'] ?>" title="Parts"><i class="fas fa-puzzle-piece"></i></button>
|
||||||
|
</div>
|
||||||
|
<?php else: ?>
|
||||||
|
<!-- Lista Quotations -->
|
||||||
|
<div class="mb-3">
|
||||||
|
<button type="button" class="btn btn-primary" data-bs-toggle="modal" data-bs-target="#createModal">Crea Nuova Quotation</button>
|
||||||
|
</div>
|
||||||
|
<h6 class="mb-3">Quotations Esistenti</h6>
|
||||||
|
<table id="quotationsTable" class="table table-striped table-bordered">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>ID</th>
|
||||||
|
<th>Data Creazione</th>
|
||||||
|
<th>Descrizione</th>
|
||||||
|
<th>Cliente</th>
|
||||||
|
<th>Azioni</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
<?php foreach ($quotations as $row): ?>
|
||||||
|
<tr data-id="<?= $row['id'] ?>">
|
||||||
|
<td><?= htmlspecialchars($row['id']) ?></td>
|
||||||
|
<td><?= htmlspecialchars($row['creation_date']) ?></td>
|
||||||
|
<td>
|
||||||
|
<textarea name="description" class="cell-input manual-input form-control"><?= htmlspecialchars($row['description']) ?></textarea>
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<input type="text" name="customer" class="cell-input manual-input form-control" value="<?= htmlspecialchars($row['customer']) ?>">
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<button type="button" class="save-btn action-btn edit-btn" data-id="<?= $row['id'] ?>" title="Salva Modifiche"><i class="fas fa-save"></i></button>
|
||||||
|
<button type="button" class="delete-btn action-btn" data-id="<?= $row['id'] ?>" title="Cancella" data-bs-toggle="modal" data-bs-target="#deleteModal"><i class="fas fa-trash"></i></button>
|
||||||
|
<button type="button" class="photos-btn action-btn" data-idquotations="<?= $row['id'] ?>" title="Photos"><i class="fas fa-camera"></i></button>
|
||||||
|
<button type="button" class="parts-btn action-btn" data-idquotations="<?= $row['id'] ?>" title="Parts"><i class="fas fa-puzzle-piece"></i></button>
|
||||||
|
<a href="quotations.php?edit_id=<?= $row['id'] ?>" class="btn btn-secondary action-btn" title="Modifica Dettagliata"><i class="fas fa-edit"></i></a>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<?php endforeach; ?>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
<?php endif; ?>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<!-- Modal per conferma creazione nuova quotation -->
|
||||||
|
<div class="modal fade" id="createModal" tabindex="-1" aria-labelledby="createModalLabel" aria-hidden="true">
|
||||||
|
<div class="modal-dialog">
|
||||||
|
<div class="modal-content">
|
||||||
|
<div class="modal-header">
|
||||||
|
<h5 class="modal-title" id="createModalLabel">Conferma Creazione Nuova Quotation</h5>
|
||||||
|
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
|
||||||
|
</div>
|
||||||
|
<div class="modal-body">
|
||||||
|
<p>Vuoi creare una nuova quotation?</p>
|
||||||
|
<form id="createModalForm" method="post">
|
||||||
|
<input type="hidden" name="action" value="create">
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
<div class="modal-footer">
|
||||||
|
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Annulla</button>
|
||||||
|
<button type="button" class="btn btn-primary" id="confirmCreate">Conferma</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<!-- Modal per conferma cancellazione -->
|
||||||
|
<div class="modal fade" id="deleteModal" tabindex="-1" aria-labelledby="deleteModalLabel" aria-hidden="true">
|
||||||
|
<div class="modal-dialog">
|
||||||
|
<div class="modal-content">
|
||||||
|
<div class="modal-header">
|
||||||
|
<h5 class="modal-title" id="deleteModalLabel">Conferma Cancellazione</h5>
|
||||||
|
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
|
||||||
|
</div>
|
||||||
|
<div class="modal-body">
|
||||||
|
<p>Sicuro di voler cancellare questa quotation?</p>
|
||||||
|
<form id="deleteForm" method="post">
|
||||||
|
<input type="hidden" name="action" value="delete">
|
||||||
|
<input type="hidden" name="id" id="deleteQuotationId">
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
<div class="modal-footer">
|
||||||
|
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Annulla</button>
|
||||||
|
<button type="button" class="btn btn-danger" id="confirmDelete">Conferma</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="overlay toggle-icon"></div>
|
||||||
|
<a href="javaScript:;" class="back-to-top"><i class='bx bxs-up-arrow-alt'></i></a>
|
||||||
|
<?php include('include/footer.php'); ?>
|
||||||
|
</div>
|
||||||
|
<?php include('modal_parts.php'); ?>
|
||||||
|
<?php include('photos_functions.php'); ?>
|
||||||
|
<?php include('jsinclude.php'); ?>
|
||||||
|
<script src="https://code.jquery.com/jquery-3.6.0.min.js"></script>
|
||||||
|
<script src="https://cdn.datatables.net/1.13.4/js/jquery.dataTables.min.js"></script>
|
||||||
|
<script src="photos.js"></script>
|
||||||
|
<script src="parts.js"></script>
|
||||||
|
<script>
|
||||||
|
document.addEventListener("DOMContentLoaded", function() {
|
||||||
|
// Inizializza DataTables se non siamo in modalità modifica
|
||||||
|
if (!document.querySelector('#editForm')) {
|
||||||
|
$('#quotationsTable').DataTable({
|
||||||
|
"paging": true,
|
||||||
|
"searching": true,
|
||||||
|
"ordering": true,
|
||||||
|
"info": true,
|
||||||
|
"autoWidth": false,
|
||||||
|
"responsive": true
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Gestione conferma creazione nel modal
|
||||||
|
document.getElementById('confirmCreate').addEventListener('click', function() {
|
||||||
|
document.getElementById('createModalForm').submit();
|
||||||
|
});
|
||||||
|
|
||||||
|
// Gestione modifica inline e save nella lista
|
||||||
|
document.querySelectorAll('.edit-btn').forEach(btn => {
|
||||||
|
btn.addEventListener('click', function() {
|
||||||
|
const row = this.closest('tr');
|
||||||
|
const id = row.dataset.id;
|
||||||
|
const description = row.querySelector('textarea[name="description"]').value;
|
||||||
|
const customer = row.querySelector('input[name="customer"]').value;
|
||||||
|
|
||||||
|
const formData = new FormData();
|
||||||
|
formData.append('action', 'update');
|
||||||
|
formData.append('id', id);
|
||||||
|
formData.append('description', description);
|
||||||
|
formData.append('customer', customer);
|
||||||
|
|
||||||
|
fetch('quotations.php', {
|
||||||
|
method: 'POST',
|
||||||
|
body: formData
|
||||||
|
}).then(response => {
|
||||||
|
if (response.ok) {
|
||||||
|
row.classList.add('flash-success');
|
||||||
|
setTimeout(() => row.classList.remove('flash-success'), 500);
|
||||||
|
alert('Quotation modificata con successo!');
|
||||||
|
} else {
|
||||||
|
alert('Errore durante la modifica.');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// Gestione apertura modal di cancellazione
|
||||||
|
document.querySelectorAll('.delete-btn').forEach(btn => {
|
||||||
|
btn.addEventListener('click', function() {
|
||||||
|
const id = this.dataset.id;
|
||||||
|
document.getElementById('deleteQuotationId').value = id;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// Gestione conferma cancellazione nel modal
|
||||||
|
document.getElementById('confirmDelete').addEventListener('click', function() {
|
||||||
|
document.getElementById('deleteForm').submit();
|
||||||
|
});
|
||||||
|
|
||||||
|
// I bottoni photos e parts usano gli script esistenti (photos.js, parts.js), passando data-idquotations
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
|
||||||
|
</html>
|
||||||
@@ -0,0 +1,63 @@
|
|||||||
|
<?php
|
||||||
|
header('Content-Type: application/json');
|
||||||
|
include('include/headscript.php');
|
||||||
|
|
||||||
|
$dbHandler = DBHandlerSelect::getInstance();
|
||||||
|
$pdo = $dbHandler->getConnection();
|
||||||
|
|
||||||
|
$data = json_decode(file_get_contents('php://input'), true);
|
||||||
|
|
||||||
|
$iddatadb = $data['iddatadb'] ?? null;
|
||||||
|
$parts = $data['parts'] ?? [];
|
||||||
|
|
||||||
|
if (!$iddatadb || empty($parts)) {
|
||||||
|
echo json_encode(['success' => false, 'message' => 'Dati mancanti']);
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
$pdo->beginTransaction();
|
||||||
|
|
||||||
|
// Elimina tutte le parti esistenti per l'iddatadb per evitare conflitti di unicità
|
||||||
|
$stmt = $pdo->prepare("DELETE FROM identification_parts WHERE iddatadb = :iddatadb");
|
||||||
|
$stmt->execute([':iddatadb' => $iddatadb]);
|
||||||
|
|
||||||
|
// Prepara l'inserimento delle nuove parti
|
||||||
|
$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())
|
||||||
|
");
|
||||||
|
|
||||||
|
$part_ids = [];
|
||||||
|
foreach ($parts as $part) {
|
||||||
|
$partNumber = $part['part_number'] ?? null;
|
||||||
|
$partDescription = $part['part_description'] ?? '';
|
||||||
|
$mix = $part['mix'] ?? 'N';
|
||||||
|
|
||||||
|
if (!$partNumber || !$partDescription) {
|
||||||
|
throw new PDOException("Numero parte o descrizione mancante per parte: " . json_encode($part));
|
||||||
|
}
|
||||||
|
|
||||||
|
$stmt->execute([
|
||||||
|
':iddatadb' => $iddatadb,
|
||||||
|
':part_number' => $partNumber,
|
||||||
|
':part_description' => $partDescription,
|
||||||
|
':mix' => $mix
|
||||||
|
]);
|
||||||
|
$part_ids[] = $pdo->lastInsertId();
|
||||||
|
}
|
||||||
|
|
||||||
|
$pdo->commit();
|
||||||
|
echo json_encode([
|
||||||
|
'success' => true,
|
||||||
|
'part_ids' => $part_ids,
|
||||||
|
'message' => 'Parti rinumerate con successo'
|
||||||
|
]);
|
||||||
|
} catch (PDOException $e) {
|
||||||
|
$pdo->rollBack();
|
||||||
|
echo json_encode([
|
||||||
|
'success' => false,
|
||||||
|
'message' => 'Errore nel salvataggio: ' . $e->getMessage()
|
||||||
|
]);
|
||||||
|
}
|
||||||
@@ -0,0 +1,44 @@
|
|||||||
|
<?php
|
||||||
|
header('Content-Type: application/json');
|
||||||
|
include('include/headscript.php');
|
||||||
|
|
||||||
|
$dbHandler = DBHandlerSelect::getInstance();
|
||||||
|
$pdo = $dbHandler->getConnection();
|
||||||
|
|
||||||
|
$data = json_decode(file_get_contents('php://input'), true);
|
||||||
|
|
||||||
|
$iddatadb = $data['iddatadb'] ?? null;
|
||||||
|
$parts = $data['parts'] ?? [];
|
||||||
|
|
||||||
|
if (!$iddatadb || empty($parts)) {
|
||||||
|
echo json_encode(['success' => false, 'message' => 'Dati mancanti']);
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
$part = $parts[0];
|
||||||
|
$partId = $part['id'] ?? null;
|
||||||
|
$idmatrice = $part['idmatrice'] ?? null;
|
||||||
|
|
||||||
|
if (!$partId) {
|
||||||
|
echo json_encode(['success' => false, 'message' => 'ID della parte mancante']);
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
$stmt = $pdo->prepare("UPDATE identification_parts
|
||||||
|
SET idmatrice = :idmatrice, updated_at = NOW()
|
||||||
|
WHERE id = :id AND iddatadb = :iddatadb");
|
||||||
|
$stmt->execute([
|
||||||
|
':idmatrice' => $idmatrice,
|
||||||
|
':id' => $partId,
|
||||||
|
':iddatadb' => $iddatadb
|
||||||
|
]);
|
||||||
|
|
||||||
|
if ($stmt->rowCount() > 0) {
|
||||||
|
echo json_encode(['success' => true, 'message' => 'Matrice salvata con successo']);
|
||||||
|
} else {
|
||||||
|
echo json_encode(['success' => false, 'message' => 'Nessuna riga aggiornata. Verifica l\'ID della parte.']);
|
||||||
|
}
|
||||||
|
} catch (PDOException $e) {
|
||||||
|
echo json_encode(['success' => false, 'message' => 'Errore nel salvataggio della matrice: ' . $e->getMessage()]);
|
||||||
|
}
|
||||||
@@ -0,0 +1,26 @@
|
|||||||
|
<?php
|
||||||
|
require_once 'include/headscript.php';
|
||||||
|
|
||||||
|
header('Content-Type: application/json');
|
||||||
|
|
||||||
|
$data = json_decode(file_get_contents('php://input'), true);
|
||||||
|
$template_id = $data['template_id'] ?? null;
|
||||||
|
$mapping_id = $data['mapping_id'] ?? null;
|
||||||
|
$value = $data['value'] ?? null;
|
||||||
|
|
||||||
|
if (!$template_id || !$mapping_id || !isset($value)) {
|
||||||
|
echo json_encode(['success' => false, 'message' => 'Invalid input']);
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
$db = DBHandlerSelect::getInstance();
|
||||||
|
$pdo = $db->getConnection();
|
||||||
|
|
||||||
|
$stmt = $pdo->prepare("UPDATE template_mapping SET is_visible_import = ? WHERE id = ? AND template_id = ?");
|
||||||
|
$result = $stmt->execute([$value, $mapping_id, $template_id]);
|
||||||
|
|
||||||
|
if ($result) {
|
||||||
|
echo json_encode(['success' => true]);
|
||||||
|
} else {
|
||||||
|
echo json_encode(['success' => false, 'message' => 'Failed to update is_visible_import']);
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user