Aggiungi campi per immagini e diametro del foro nel modello Articolo e nelle relative migrazioni; aggiorna i form e le viste per gestire i nuovi campi
This commit is contained in:
82
CLAUDE.md
Normal file
82
CLAUDE.md
Normal file
@@ -0,0 +1,82 @@
|
||||
# CLAUDE.md
|
||||
|
||||
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
|
||||
|
||||
## Project Overview
|
||||
|
||||
**HTT QRCode** is a Laravel 12 web application for managing industrial articles with QR code generation and scanning. Key capabilities: article catalog CRUD, unique QR code generation per article, PDF label printing, public QR scanner (no auth required), and Excel import.
|
||||
|
||||
**Stack**: Laravel 12 / PHP 8.2+ · Blade + TailwindCSS 3 + Alpine.js · SQLite (default) or MySQL · Vite · SimpleSoftwareIO/simple-qrcode · Barryvdh/laravel-dompdf · PHPOffice/PhpSpreadsheet
|
||||
|
||||
## Development Commands
|
||||
|
||||
```bash
|
||||
# Start everything (PHP server + queue + logs + Vite HMR)
|
||||
composer run dev
|
||||
|
||||
# Or separately:
|
||||
php artisan serve # http://localhost:8000
|
||||
npm run dev # Vite with hot reload
|
||||
|
||||
# Build for production
|
||||
npm run build
|
||||
|
||||
# Run tests
|
||||
composer run test
|
||||
# or
|
||||
php artisan test
|
||||
php artisan test --filter NomeTest # single test
|
||||
|
||||
# Database
|
||||
php artisan migrate
|
||||
php artisan migrate:fresh --seed # reset + re-seed
|
||||
php artisan db:seed --class=AdminUserSeeder # admin@example.com / password
|
||||
|
||||
# Code style (Laravel Pint)
|
||||
./vendor/bin/pint
|
||||
|
||||
# Clear caches
|
||||
php artisan cache:clear && php artisan config:clear && php artisan view:clear
|
||||
|
||||
# View all routes
|
||||
php artisan route:list
|
||||
|
||||
# Real-time logs
|
||||
php artisan pail
|
||||
```
|
||||
|
||||
## Architecture
|
||||
|
||||
### Route → Controller → View mapping
|
||||
|
||||
| Route | Controller | View |
|
||||
|---|---|---|
|
||||
| `GET /` | closure | `welcome.blade.php` |
|
||||
| `GET /scanner` | `PublicArticoloController@scanner` | `public/scanner.blade.php` |
|
||||
| `GET /articolo/{qr_code}` | `PublicArticoloController@show` | `public/articolo.blade.php` |
|
||||
| `admin/articoli` (resource) | `Admin/ArticoloController` | `admin/articoli/*.blade.php` |
|
||||
|
||||
Admin routes are grouped under `middleware(['auth'])` with prefix `admin/` and name prefix `admin.`. The `/dashboard` route simply redirects to `admin.articoli.index`.
|
||||
|
||||
### Articolo Model
|
||||
|
||||
`qr_code` is auto-generated on `created` event using the format `{id}-{timestamp}`. The `codice_articolo` field is unique. The public scanner URL resolves articles via `qr_code`, not `id`.
|
||||
|
||||
### QR Code features in `ArticoloController`
|
||||
|
||||
- `qrCode()` — renders QR inline as SVG/PNG
|
||||
- `downloadQrCode()` — streams PNG (300×300px, error correction H)
|
||||
- `printQrCodes()` — generates multi-article PDF via dompdf using `pdf-qrcodes.blade.php`
|
||||
- `import()` — Excel import via PhpSpreadsheet; supports "merge" (default) and "clean import" modes
|
||||
|
||||
### Layouts
|
||||
|
||||
- `layouts/app.blade.php` — authenticated area (uses `<x-app-layout>`)
|
||||
- `layouts/guest.blade.php` — public/auth pages (uses `<x-guest-layout>`)
|
||||
- Blade components live in `resources/views/components/` and `app/View/Components/`
|
||||
|
||||
## Database
|
||||
|
||||
Default is **SQLite** (`database/database.sqlite`). The `articoli` table has two unique constraints: `codice_articolo` and `qr_code`.
|
||||
|
||||
Excel import expects these column groups: `UBICAZIONE` (Codice Articolo, Ciclo, Diametro, Descrizione, Posizione, Quantita) and `PARAMETRI TECNOLOGICI` (Tipo Lavorazione, Materiale da lavorare, Maximum Thickness, Speed RPM, Feed, Max Thrust A, Min Torque A, Quantita Fori).
|
||||
Binary file not shown.
@@ -5,8 +5,8 @@ namespace App\Http\Controllers\Admin;
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Models\Articolo;
|
||||
use Barryvdh\DomPDF\Facade\Pdf;
|
||||
use Database\Seeders\ArticoloSeeder;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\Storage;
|
||||
use SimpleSoftwareIO\QrCode\Facades\QrCode;
|
||||
use \PhpOffice\PhpSpreadsheet\IOFactory;
|
||||
|
||||
@@ -40,6 +40,7 @@ class ArticoloController extends Controller
|
||||
$validated = $request->validate([
|
||||
'codice_articolo' => 'required|string|max:255|unique:articoli',
|
||||
'ciclo' => 'nullable|string|max:255',
|
||||
'hole_diameter' => 'nullable|string|max:255',
|
||||
'diametro' => 'nullable|string|max:255',
|
||||
'descrizione' => 'nullable|string',
|
||||
'posizione' => 'nullable|string|max:255',
|
||||
@@ -75,6 +76,7 @@ class ArticoloController extends Controller
|
||||
$validated = $request->validate([
|
||||
'codice_articolo' => 'required|string|max:255|unique:articoli,codice_articolo,' . $articolo->id,
|
||||
'ciclo' => 'nullable|string|max:255',
|
||||
'hole_diameter' => 'nullable|string|max:255',
|
||||
'diametro' => 'nullable|string|max:255',
|
||||
'descrizione' => 'nullable|string',
|
||||
'posizione' => 'nullable|string|max:255',
|
||||
@@ -87,8 +89,36 @@ class ArticoloController extends Controller
|
||||
'max_thrust_a' => 'nullable|string|max:255',
|
||||
'min_torque_a' => 'nullable|string|max:255',
|
||||
'quantita_fori' => 'nullable|integer|min:0',
|
||||
'immagine_articolo' => 'nullable|image|mimes:jpeg,png,jpg,gif,webp|max:4096',
|
||||
'immagine_thrust' => 'nullable|image|mimes:jpeg,png,jpg,gif,webp|max:4096',
|
||||
'immagine_tourque' => 'nullable|image|mimes:jpeg,png,jpg,gif,webp|max:4096',
|
||||
'rimuovi_immagine_articolo' => 'nullable|boolean',
|
||||
'rimuovi_immagine_thrust' => 'nullable|boolean',
|
||||
'rimuovi_immagine_tourque' => 'nullable|boolean',
|
||||
]);
|
||||
|
||||
$imageFields = ['immagine_articolo', 'immagine_thrust', 'immagine_tourque'];
|
||||
|
||||
foreach ($imageFields as $field) {
|
||||
$removeKey = 'rimuovi_' . $field;
|
||||
|
||||
if ($request->boolean($removeKey)) {
|
||||
if ($articolo->$field) {
|
||||
Storage::disk('public')->delete($articolo->$field);
|
||||
}
|
||||
$validated[$field] = null;
|
||||
} elseif ($request->hasFile($field)) {
|
||||
if ($articolo->$field) {
|
||||
Storage::disk('public')->delete($articolo->$field);
|
||||
}
|
||||
$validated[$field] = $request->file($field)->store('articoli', 'public');
|
||||
} else {
|
||||
unset($validated[$field]);
|
||||
}
|
||||
|
||||
unset($validated[$removeKey]);
|
||||
}
|
||||
|
||||
$articolo->update($validated);
|
||||
|
||||
return redirect()->route('admin.articoli.show', $articolo)
|
||||
@@ -164,7 +194,7 @@ class ArticoloController extends Controller
|
||||
public function import(Request $request)
|
||||
{
|
||||
$request->validate([
|
||||
'file' => 'required|file|mimes:xlsx,xls',
|
||||
'file' => 'required|file|mimetypes:application/vnd.openxmlformats-officedocument.spreadsheetml.sheet,application/vnd.ms-excel,application/octet-stream',
|
||||
'clean_import' => 'nullable|boolean',
|
||||
]);
|
||||
|
||||
@@ -172,7 +202,6 @@ class ArticoloController extends Controller
|
||||
$cleanImport = $request->boolean('clean_import', false);
|
||||
|
||||
try {
|
||||
$seeder = new ArticoloSeeder();
|
||||
$result = $this->importFromExcel($file->getPathname(), $cleanImport);
|
||||
|
||||
$message = "Import completato! ";
|
||||
@@ -216,6 +245,11 @@ class ArticoloController extends Controller
|
||||
}
|
||||
|
||||
// Skip header rows (first 2 rows)
|
||||
// Column layout (v2): 0=Codice Articolo, 1=Immagine Articolo (skip), 2=Ciclo,
|
||||
// 3=Hole Diameter, 4=Diametro, 5=Descrizione, 6=Posizione, 7=Quantità,
|
||||
// 8=Tipo lavorazione, 9=Materiale, 10=Max Thickness, 11=Speed RPM, 12=Feed,
|
||||
// 13=Max Thrust A, 14=Immagine Thrust (skip), 15=Min Torque A,
|
||||
// 16=Immagine Tourque (skip), 17=Quantità fori
|
||||
foreach (array_slice($rows, 2) as $index => $row) {
|
||||
// Skip empty rows
|
||||
if (empty($row[0])) {
|
||||
@@ -228,19 +262,20 @@ class ArticoloController extends Controller
|
||||
Articolo::updateOrCreate(
|
||||
['codice_articolo' => $row[0]],
|
||||
[
|
||||
'ciclo' => $row[1] ?? null,
|
||||
'diametro' => $row[2] ?? null,
|
||||
'descrizione' => $row[3] ?? null,
|
||||
'posizione' => $row[4] ?? null,
|
||||
'quantita' => is_numeric($row[5]) ? (int) $row[5] : 0,
|
||||
'tipo_lavorazione' => $row[6] ?? null,
|
||||
'materiale_lavorare' => $row[7] ?? null,
|
||||
'maximum_thickness' => $row[8] ?? null,
|
||||
'speed_rpm' => is_numeric($row[9]) ? (int) $row[9] : null,
|
||||
'feed' => is_numeric($row[10]) ? (float) $row[10] : null,
|
||||
'max_thrust_a' => $row[11] ?? null,
|
||||
'min_torque_a' => $row[12] ?? null,
|
||||
'quantita_fori' => is_numeric($row[13]) ? (int) $row[13] : null,
|
||||
'ciclo' => $row[2] ?? null,
|
||||
'hole_diameter' => $row[3] ?? null,
|
||||
'diametro' => $row[4] ?? null,
|
||||
'descrizione' => $row[5] ?? null,
|
||||
'posizione' => $row[6] ?? null,
|
||||
'quantita' => is_numeric($row[7]) ? (int) round((float) $row[7]) : 0,
|
||||
'tipo_lavorazione' => $row[8] ?? null,
|
||||
'materiale_lavorare' => $row[9] ?? null,
|
||||
'maximum_thickness' => $row[10] ?? null,
|
||||
'speed_rpm' => is_numeric($row[11]) ? (int) round((float) $row[11]) : null,
|
||||
'feed' => is_numeric($row[12]) ? round((float) $row[12], 2) : null,
|
||||
'max_thrust_a' => $row[13] ?? null,
|
||||
'min_torque_a' => $row[15] ?? null,
|
||||
'quantita_fori' => is_numeric($row[17]) ? (int) round((float) $row[17]) : null,
|
||||
]
|
||||
);
|
||||
|
||||
@@ -250,18 +285,11 @@ class ArticoloController extends Controller
|
||||
$imported++;
|
||||
}
|
||||
|
||||
if (isset($this->command)) {
|
||||
$this->command->info('Importato articolo: ' . $row[0]);
|
||||
}
|
||||
} catch (\Exception $e) {
|
||||
$errors[] = "Riga " . ($index + 3) . ": " . $e->getMessage();
|
||||
}
|
||||
}
|
||||
|
||||
if (isset($this->command)) {
|
||||
$this->command->info('Import completato! Totale articoli: ' . Articolo::count());
|
||||
}
|
||||
|
||||
return [
|
||||
'imported' => $imported,
|
||||
'updated' => $updated,
|
||||
|
||||
@@ -9,7 +9,9 @@ class Articolo extends Model
|
||||
|
||||
protected $fillable = [
|
||||
'codice_articolo',
|
||||
'immagine_articolo',
|
||||
'ciclo',
|
||||
'hole_diameter',
|
||||
'diametro',
|
||||
'descrizione',
|
||||
'posizione',
|
||||
@@ -20,7 +22,9 @@ class Articolo extends Model
|
||||
'speed_rpm',
|
||||
'feed',
|
||||
'max_thrust_a',
|
||||
'immagine_thrust',
|
||||
'min_torque_a',
|
||||
'immagine_tourque',
|
||||
'quantita_fori',
|
||||
'qr_code',
|
||||
];
|
||||
|
||||
@@ -0,0 +1,27 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
return new class extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*/
|
||||
public function up(): void
|
||||
{
|
||||
Schema::table('articoli', function (Blueprint $table) {
|
||||
$table->string('immagine_articolo')->nullable()->after('codice_articolo');
|
||||
$table->string('immagine_thrust')->nullable()->after('max_thrust_a');
|
||||
$table->string('immagine_tourque')->nullable()->after('min_torque_a');
|
||||
});
|
||||
}
|
||||
|
||||
public function down(): void
|
||||
{
|
||||
Schema::table('articoli', function (Blueprint $table) {
|
||||
$table->dropColumn(['immagine_articolo', 'immagine_thrust', 'immagine_tourque']);
|
||||
});
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,25 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
return new class extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*/
|
||||
public function up(): void
|
||||
{
|
||||
Schema::table('articoli', function (Blueprint $table) {
|
||||
$table->string('hole_diameter')->nullable()->after('ciclo');
|
||||
});
|
||||
}
|
||||
|
||||
public function down(): void
|
||||
{
|
||||
Schema::table('articoli', function (Blueprint $table) {
|
||||
$table->dropColumn('hole_diameter');
|
||||
});
|
||||
}
|
||||
};
|
||||
@@ -1,3 +1,5 @@
|
||||
@tailwind base;
|
||||
@tailwind components;
|
||||
@tailwind utilities;
|
||||
|
||||
[x-cloak] { display: none !important; }
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
@use('Illuminate\Support\Facades\Storage')
|
||||
<x-app-layout>
|
||||
<x-slot name="header">
|
||||
<div class="flex justify-between items-center">
|
||||
@@ -15,11 +16,11 @@
|
||||
</div>
|
||||
</x-slot>
|
||||
|
||||
<div class="py-12">
|
||||
<div class="py-12" x-data="{ modalImg: null }">
|
||||
<div class="max-w-4xl mx-auto sm:px-6 lg:px-8">
|
||||
<div class="bg-white overflow-hidden shadow-sm sm:rounded-lg">
|
||||
<div class="p-6">
|
||||
<form method="POST" action="{{ route('admin.articoli.update', $articolo) }}">
|
||||
<form method="POST" action="{{ route('admin.articoli.update', $articolo) }}" enctype="multipart/form-data">
|
||||
@csrf
|
||||
@method('PUT')
|
||||
|
||||
@@ -42,6 +43,12 @@
|
||||
class="mt-1 block w-full rounded-md border-gray-300 shadow-sm focus:border-blue-500 focus:ring-blue-500">
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label for="hole_diameter" class="block text-sm font-medium text-gray-700">Hole Diameter</label>
|
||||
<input type="text" name="hole_diameter" id="hole_diameter" value="{{ old('hole_diameter', $articolo->hole_diameter) }}"
|
||||
class="mt-1 block w-full rounded-md border-gray-300 shadow-sm focus:border-blue-500 focus:ring-blue-500">
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label for="diametro" class="block text-sm font-medium text-gray-700">Diametro</label>
|
||||
<input type="text" name="diametro" id="diametro" value="{{ old('diametro', $articolo->diametro) }}"
|
||||
@@ -65,6 +72,26 @@
|
||||
<textarea name="descrizione" id="descrizione" rows="3"
|
||||
class="mt-1 block w-full rounded-md border-gray-300 shadow-sm focus:border-blue-500 focus:ring-blue-500">{{ old('descrizione', $articolo->descrizione) }}</textarea>
|
||||
</div>
|
||||
|
||||
<div class="md:col-span-2">
|
||||
<label class="block text-sm font-medium text-gray-700">Immagine Articolo</label>
|
||||
@if ($articolo->immagine_articolo)
|
||||
<div class="mt-2 mb-3 flex items-center gap-4">
|
||||
<img src="{{ Storage::url($articolo->immagine_articolo) }}" alt="Immagine Articolo"
|
||||
class="h-16 w-16 object-cover rounded border cursor-pointer hover:opacity-80 transition-opacity"
|
||||
@click="modalImg = '{{ Storage::url($articolo->immagine_articolo) }}'">
|
||||
<label class="flex items-center gap-2 text-sm text-red-600 cursor-pointer">
|
||||
<input type="checkbox" name="rimuovi_immagine_articolo" value="1" class="rounded border-gray-300">
|
||||
Rimuovi immagine
|
||||
</label>
|
||||
</div>
|
||||
@endif
|
||||
<input type="file" name="immagine_articolo" id="immagine_articolo" accept="image/*"
|
||||
class="mt-1 block w-full text-sm text-gray-500 file:mr-4 file:py-2 file:px-4 file:rounded file:border-0 file:text-sm file:font-semibold file:bg-blue-50 file:text-blue-700 hover:file:bg-blue-100">
|
||||
@error('immagine_articolo')
|
||||
<p class="mt-1 text-sm text-red-600">{{ $message }}</p>
|
||||
@enderror
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -108,12 +135,52 @@
|
||||
class="mt-1 block w-full rounded-md border-gray-300 shadow-sm focus:border-blue-500 focus:ring-blue-500">
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label class="block text-sm font-medium text-gray-700">Immagine Thrust</label>
|
||||
@if ($articolo->immagine_thrust)
|
||||
<div class="mt-2 mb-3 flex items-center gap-4">
|
||||
<img src="{{ Storage::url($articolo->immagine_thrust) }}" alt="Immagine Thrust"
|
||||
class="h-16 w-16 object-cover rounded border cursor-pointer hover:opacity-80 transition-opacity"
|
||||
@click="modalImg = '{{ Storage::url($articolo->immagine_thrust) }}'">
|
||||
<label class="flex items-center gap-2 text-sm text-red-600 cursor-pointer">
|
||||
<input type="checkbox" name="rimuovi_immagine_thrust" value="1" class="rounded border-gray-300">
|
||||
Rimuovi immagine
|
||||
</label>
|
||||
</div>
|
||||
@endif
|
||||
<input type="file" name="immagine_thrust" id="immagine_thrust" accept="image/*"
|
||||
class="mt-1 block w-full text-sm text-gray-500 file:mr-4 file:py-2 file:px-4 file:rounded file:border-0 file:text-sm file:font-semibold file:bg-blue-50 file:text-blue-700 hover:file:bg-blue-100">
|
||||
@error('immagine_thrust')
|
||||
<p class="mt-1 text-sm text-red-600">{{ $message }}</p>
|
||||
@enderror
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label for="min_torque_a" class="block text-sm font-medium text-gray-700">Min Torque (A)</label>
|
||||
<input type="text" name="min_torque_a" id="min_torque_a" value="{{ old('min_torque_a', $articolo->min_torque_a) }}"
|
||||
class="mt-1 block w-full rounded-md border-gray-300 shadow-sm focus:border-blue-500 focus:ring-blue-500">
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label class="block text-sm font-medium text-gray-700">Immagine Tourque</label>
|
||||
@if ($articolo->immagine_tourque)
|
||||
<div class="mt-2 mb-3 flex items-center gap-4">
|
||||
<img src="{{ Storage::url($articolo->immagine_tourque) }}" alt="Immagine Tourque"
|
||||
class="h-16 w-16 object-cover rounded border cursor-pointer hover:opacity-80 transition-opacity"
|
||||
@click="modalImg = '{{ Storage::url($articolo->immagine_tourque) }}'">
|
||||
<label class="flex items-center gap-2 text-sm text-red-600 cursor-pointer">
|
||||
<input type="checkbox" name="rimuovi_immagine_tourque" value="1" class="rounded border-gray-300">
|
||||
Rimuovi immagine
|
||||
</label>
|
||||
</div>
|
||||
@endif
|
||||
<input type="file" name="immagine_tourque" id="immagine_tourque" accept="image/*"
|
||||
class="mt-1 block w-full text-sm text-gray-500 file:mr-4 file:py-2 file:px-4 file:rounded file:border-0 file:text-sm file:font-semibold file:bg-blue-50 file:text-blue-700 hover:file:bg-blue-100">
|
||||
@error('immagine_tourque')
|
||||
<p class="mt-1 text-sm text-red-600">{{ $message }}</p>
|
||||
@enderror
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label for="quantita_fori" class="block text-sm font-medium text-gray-700">Quantita Fori</label>
|
||||
<input type="number" name="quantita_fori" id="quantita_fori" value="{{ old('quantita_fori', $articolo->quantita_fori) }}" min="0"
|
||||
@@ -134,5 +201,14 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Image Modal -->
|
||||
<div x-show="modalImg" x-cloak
|
||||
class="fixed inset-0 bg-black/80 flex items-center justify-center z-50 p-4"
|
||||
@click="modalImg = null"
|
||||
@keydown.escape.window="modalImg = null">
|
||||
<img :src="modalImg" @click.stop class="max-h-[90vh] max-w-full rounded-lg shadow-2xl">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</x-app-layout>
|
||||
|
||||
@@ -288,7 +288,7 @@
|
||||
}
|
||||
|
||||
function handleFileSelect(file) {
|
||||
if (file && (file.name.endsWith('.xlsx') || file.name.endsWith('.xls'))) {
|
||||
if (file && (file.name.toLowerCase().endsWith('.xlsx') || file.name.toLowerCase().endsWith('.xls'))) {
|
||||
const dataTransfer = new DataTransfer();
|
||||
dataTransfer.items.add(file);
|
||||
fileInput.files = dataTransfer.files;
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
@use('Illuminate\Support\Facades\Storage')
|
||||
<x-app-layout>
|
||||
<x-slot name="header">
|
||||
<div class="flex justify-between items-center">
|
||||
@@ -15,7 +16,7 @@
|
||||
</div>
|
||||
</x-slot>
|
||||
|
||||
<div class="py-12">
|
||||
<div class="py-12" x-data="{ modalImg: null }">
|
||||
<div class="max-w-6xl mx-auto sm:px-6 lg:px-8">
|
||||
@if (session('success'))
|
||||
<div class="bg-green-100 border border-green-400 text-green-700 px-4 py-3 rounded mb-4">
|
||||
@@ -39,6 +40,10 @@
|
||||
<dt class="text-sm font-medium text-gray-500">Ciclo</dt>
|
||||
<dd class="mt-1 text-sm text-gray-900">{{ $articolo->ciclo ?? '-' }}</dd>
|
||||
</div>
|
||||
<div>
|
||||
<dt class="text-sm font-medium text-gray-500">Hole Diameter</dt>
|
||||
<dd class="mt-1 text-sm text-gray-900">{{ $articolo->hole_diameter ?? '-' }}</dd>
|
||||
</div>
|
||||
<div>
|
||||
<dt class="text-sm font-medium text-gray-500">Diametro</dt>
|
||||
<dd class="mt-1 text-sm text-gray-900">{{ $articolo->diametro ?? '-' }}</dd>
|
||||
@@ -55,6 +60,16 @@
|
||||
<dt class="text-sm font-medium text-gray-500">Descrizione</dt>
|
||||
<dd class="mt-1 text-sm text-gray-900">{{ $articolo->descrizione ?? '-' }}</dd>
|
||||
</div>
|
||||
@if ($articolo->immagine_articolo)
|
||||
<div>
|
||||
<dt class="text-sm font-medium text-gray-500">Immagine Articolo</dt>
|
||||
<dd class="mt-2">
|
||||
<img src="{{ Storage::url($articolo->immagine_articolo) }}" alt="Immagine Articolo"
|
||||
class="h-16 w-16 object-cover rounded border cursor-pointer hover:opacity-80 transition-opacity"
|
||||
@click="modalImg = '{{ Storage::url($articolo->immagine_articolo) }}'">
|
||||
</dd>
|
||||
</div>
|
||||
@endif
|
||||
</dl>
|
||||
</div>
|
||||
</div>
|
||||
@@ -88,10 +103,30 @@
|
||||
<dt class="text-sm font-medium text-gray-500">Max Thrust (A)</dt>
|
||||
<dd class="mt-1 text-sm text-gray-900">{{ $articolo->max_thrust_a ?? '-' }}</dd>
|
||||
</div>
|
||||
@if ($articolo->immagine_thrust)
|
||||
<div>
|
||||
<dt class="text-sm font-medium text-gray-500">Immagine Thrust</dt>
|
||||
<dd class="mt-2">
|
||||
<img src="{{ Storage::url($articolo->immagine_thrust) }}" alt="Immagine Thrust"
|
||||
class="h-16 w-16 object-cover rounded border cursor-pointer hover:opacity-80 transition-opacity"
|
||||
@click="modalImg = '{{ Storage::url($articolo->immagine_thrust) }}'">
|
||||
</dd>
|
||||
</div>
|
||||
@endif
|
||||
<div>
|
||||
<dt class="text-sm font-medium text-gray-500">Min Torque (A)</dt>
|
||||
<dd class="mt-1 text-sm text-gray-900">{{ $articolo->min_torque_a ?? '-' }}</dd>
|
||||
</div>
|
||||
@if ($articolo->immagine_tourque)
|
||||
<div>
|
||||
<dt class="text-sm font-medium text-gray-500">Immagine Tourque</dt>
|
||||
<dd class="mt-2">
|
||||
<img src="{{ Storage::url($articolo->immagine_tourque) }}" alt="Immagine Tourque"
|
||||
class="h-16 w-16 object-cover rounded border cursor-pointer hover:opacity-80 transition-opacity"
|
||||
@click="modalImg = '{{ Storage::url($articolo->immagine_tourque) }}'">
|
||||
</dd>
|
||||
</div>
|
||||
@endif
|
||||
<div>
|
||||
<dt class="text-sm font-medium text-gray-500">Quantita Fori</dt>
|
||||
<dd class="mt-1 text-sm text-gray-900">{{ $articolo->quantita_fori ?? '-' }}</dd>
|
||||
@@ -146,5 +181,13 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Image Modal -->
|
||||
<div x-show="modalImg" x-cloak
|
||||
class="fixed inset-0 bg-black/80 flex items-center justify-center z-50 p-4"
|
||||
@click="modalImg = null"
|
||||
@keydown.escape.window="modalImg = null">
|
||||
<img :src="modalImg" @click.stop class="max-h-[90vh] max-w-full rounded-lg shadow-2xl">
|
||||
</div>
|
||||
</div>
|
||||
</x-app-layout>
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
@use('Illuminate\Support\Facades\Storage')
|
||||
<!DOCTYPE html>
|
||||
<html lang="it">
|
||||
<head>
|
||||
@@ -5,13 +6,15 @@
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<title>{{ $articolo->codice_articolo }} - HTT Locator</title>
|
||||
<script src="https://cdn.tailwindcss.com"></script>
|
||||
<script defer src="https://cdn.jsdelivr.net/npm/alpinejs@3.x.x/dist/cdn.min.js"></script>
|
||||
<style>
|
||||
.gradient-bg {
|
||||
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
||||
}
|
||||
[x-cloak] { display: none !important; }
|
||||
</style>
|
||||
</head>
|
||||
<body class="bg-gray-100 min-h-screen">
|
||||
<body class="bg-gray-100 min-h-screen" x-data="{ modalImg: null }">
|
||||
<!-- Header -->
|
||||
<header class="gradient-bg text-white py-4 px-4 shadow-lg">
|
||||
<div class="max-w-lg mx-auto">
|
||||
@@ -35,7 +38,7 @@
|
||||
</div>
|
||||
|
||||
<!-- Ubicazione -->
|
||||
<div class="bg-white rounded-2xl shadow-lg overflow-hidden mb-6">
|
||||
<div class="bg-white rounded-2xl shadow-lg overflow-hidden mb-2">
|
||||
<div class="px-4 py-3 bg-gray-50 border-b">
|
||||
<h3 class="font-semibold text-gray-700 flex items-center">
|
||||
<svg class="w-5 h-5 mr-2 text-blue-500" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
@@ -46,12 +49,26 @@
|
||||
</h3>
|
||||
</div>
|
||||
<div class="divide-y">
|
||||
@if($articolo->immagine_articolo)
|
||||
<div class="flex justify-between items-center px-4 py-3">
|
||||
<span class="text-gray-500 text-sm">Immagine</span>
|
||||
<img src="{{ Storage::url($articolo->immagine_articolo) }}" alt="Immagine Articolo"
|
||||
class="h-12 w-12 object-cover rounded cursor-pointer hover:opacity-80 transition-opacity"
|
||||
@click="modalImg = '{{ Storage::url($articolo->immagine_articolo) }}'">
|
||||
</div>
|
||||
@endif
|
||||
@if($articolo->ciclo)
|
||||
<div class="flex justify-between items-center px-4 py-3">
|
||||
<span class="text-gray-500 text-sm">Ciclo</span>
|
||||
<span class="font-medium text-gray-900">{{ $articolo->ciclo }}</span>
|
||||
</div>
|
||||
@endif
|
||||
@if($articolo->hole_diameter)
|
||||
<div class="flex justify-between items-center px-4 py-3">
|
||||
<span class="text-gray-500 text-sm">Hole Diameter</span>
|
||||
<span class="font-medium text-gray-900">{{ $articolo->hole_diameter }}</span>
|
||||
</div>
|
||||
@endif
|
||||
@if($articolo->diametro)
|
||||
<div class="flex justify-between items-center px-4 py-3">
|
||||
<span class="text-gray-500 text-sm">Diametro</span>
|
||||
@@ -121,12 +138,28 @@
|
||||
<span class="font-medium text-gray-900">{{ $articolo->max_thrust_a }}</span>
|
||||
</div>
|
||||
@endif
|
||||
@if($articolo->immagine_thrust)
|
||||
<div class="flex justify-between items-center px-4 py-3">
|
||||
<span class="text-gray-500 text-sm">Immagine Thrust</span>
|
||||
<img src="{{ Storage::url($articolo->immagine_thrust) }}" alt="Immagine Thrust"
|
||||
class="h-12 w-12 object-cover rounded cursor-pointer hover:opacity-80 transition-opacity"
|
||||
@click="modalImg = '{{ Storage::url($articolo->immagine_thrust) }}'">
|
||||
</div>
|
||||
@endif
|
||||
@if($articolo->min_torque_a)
|
||||
<div class="flex justify-between items-center px-4 py-3">
|
||||
<span class="text-gray-500 text-sm">Min Torque (A)</span>
|
||||
<span class="font-medium text-gray-900">{{ $articolo->min_torque_a }}</span>
|
||||
</div>
|
||||
@endif
|
||||
@if($articolo->immagine_tourque)
|
||||
<div class="flex justify-between items-center px-4 py-3">
|
||||
<span class="text-gray-500 text-sm">Immagine Tourque</span>
|
||||
<img src="{{ Storage::url($articolo->immagine_tourque) }}" alt="Immagine Tourque"
|
||||
class="h-12 w-12 object-cover rounded cursor-pointer hover:opacity-80 transition-opacity"
|
||||
@click="modalImg = '{{ Storage::url($articolo->immagine_tourque) }}'">
|
||||
</div>
|
||||
@endif
|
||||
@if($articolo->quantita_fori)
|
||||
<div class="flex justify-between items-center px-4 py-3">
|
||||
<span class="text-gray-500 text-sm">Quantita Fori</span>
|
||||
@@ -136,8 +169,13 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Footer Info -->
|
||||
<div class="text-center text-gray-400 text-xs">
|
||||
<p>Ultimo aggiornamento: {{ $articolo->updated_at->format('d/m/Y H:i') }}</p>
|
||||
</div>
|
||||
|
||||
<!-- Scan New QR Button -->
|
||||
<div class="mb-6">
|
||||
<div class="mb-6 mt-3">
|
||||
<a href="{{ route('scanner') }}" class="flex items-center justify-center gap-3 w-full bg-blue-600 hover:bg-blue-700 text-white font-semibold py-4 px-6 rounded-2xl shadow-lg transition-all">
|
||||
<svg class="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 4v1m6 11h2m-6 0h-2v4m0-11v3m0 0h.01M12 12h4.01M16 20h4M4 12h4m12 0h.01M5 8h2a1 1 0 001-1V5a1 1 0 00-1-1H5a1 1 0 00-1 1v2a1 1 0 001 1zm12 0h2a1 1 0 001-1V5a1 1 0 00-1-1h-2a1 1 0 00-1 1v2a1 1 0 001 1zM5 20h2a1 1 0 001-1v-2a1 1 0 00-1-1H5a1 1 0 00-1 1v2a1 1 0 001 1z"></path>
|
||||
@@ -146,10 +184,25 @@
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<!-- Footer Info -->
|
||||
<div class="text-center text-gray-400 text-xs">
|
||||
<p>Ultimo aggiornamento: {{ $articolo->updated_at->format('d/m/Y H:i') }}</p>
|
||||
<!-- Back to Home Button (Mobile Only) -->
|
||||
<div class="bottom-4 left-4 right-4 md:hidden">
|
||||
<a href="/" class="flex items-center justify-center gap-3 w-full bg-gray-800 hover:bg-gray-900 text-white font-semibold py-3 px-6 rounded-full shadow-lg transition-all">
|
||||
<svg class="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M3 12l9-9m0 0l9 9m-9-9v18"></path>
|
||||
</svg>
|
||||
Torna alla Home
|
||||
</a>
|
||||
</div>
|
||||
|
||||
|
||||
</main>
|
||||
|
||||
<!-- Image Modal -->
|
||||
<div x-show="modalImg" x-cloak
|
||||
class="fixed inset-0 bg-black/80 flex items-center justify-center z-50 p-4"
|
||||
@click="modalImg = null"
|
||||
@keydown.escape.window="modalImg = null">
|
||||
<img :src="modalImg" @click.stop class="max-h-[90vh] max-w-full rounded-lg shadow-2xl">
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
Reference in New Issue
Block a user