++ Primo Caricamento

This commit is contained in:
2026-03-30 19:15:13 +02:00
commit 663a68d59b
47 changed files with 3561 additions and 0 deletions

View File

@@ -0,0 +1,189 @@
{{--
resources/views/customers/_form.blade.php Partial del form cliente
Il prefisso underscore (_form) è una convenzione per indicare "partial view":
frammenti di HTML riutilizzati in più pagine tramite @include().
Questa tecnica evita di duplicare il form identico in create.blade.php e edit.blade.php.
La variabile $customer viene passata da edit.blade.php. In create.blade.php
non esiste, quindi usiamo old() come fallback per ripopolare dopo errore.
--}}
{{-- ─── Sezione 1: Informazioni principali ──────────────────────────────────── --}}
<div class="row g-3 mb-4">
<div class="col-12">
<h6 class="text-muted fw-semibold text-uppercase small letter-spacing-1 mb-3">
<i class="bi bi-person me-1"></i>Informazioni principali
</h6>
</div>
<div class="col-md-6">
<label class="form-label fw-medium">
Nome / Ragione Sociale <span class="text-danger">*</span>
</label>
{{--
is-invalid: classe Bootstrap che mostra il bordo rosso se c'è un errore.
$errors->has('name'): controlla se il campo ha errori di validazione.
old('name', $customer->name ?? ''): usa il valore OLD se c'è (dopo errore),
altrimenti il valore del Model (modifica), altrimenti stringa vuota (crea).
--}}
<input type="text" name="name"
class="form-control {{ $errors->has('name') ? 'is-invalid' : '' }}"
value="{{ old('name', $customer->name ?? '') }}"
placeholder="Es. Mario Rossi / Rossi S.r.l."
required>
{{-- Messaggio di errore specifico del campo --}}
@error('name')
<div class="invalid-feedback">{{ $message }}</div>
@enderror
</div>
<div class="col-md-6">
<label class="form-label fw-medium">
Email <span class="text-danger">*</span>
</label>
<input type="email" name="email"
class="form-control {{ $errors->has('email') ? 'is-invalid' : '' }}"
value="{{ old('email', $customer->email ?? '') }}"
placeholder="mario@esempio.it"
required>
@error('email')
<div class="invalid-feedback">{{ $message }}</div>
@enderror
</div>
<div class="col-md-4">
<label class="form-label fw-medium">Telefono</label>
<input type="tel" name="phone"
class="form-control {{ $errors->has('phone') ? 'is-invalid' : '' }}"
value="{{ old('phone', $customer->phone ?? '') }}"
placeholder="+39 02 1234567">
@error('phone')
<div class="invalid-feedback">{{ $message }}</div>
@enderror
</div>
<div class="col-md-4">
<label class="form-label fw-medium">
Tipo <span class="text-danger">*</span>
</label>
<select name="type" class="form-select {{ $errors->has('type') ? 'is-invalid' : '' }}" required>
<option value=""> Seleziona </option>
<option value="privato" {{ old('type', $customer->type ?? '') === 'privato' ? 'selected' : '' }}>
Privato
</option>
<option value="azienda" {{ old('type', $customer->type ?? '') === 'azienda' ? 'selected' : '' }}>
Azienda
</option>
</select>
@error('type')
<div class="invalid-feedback">{{ $message }}</div>
@enderror
</div>
<div class="col-md-4">
<label class="form-label fw-medium">
Stato <span class="text-danger">*</span>
</label>
<select name="status" class="form-select {{ $errors->has('status') ? 'is-invalid' : '' }}" required>
<option value=""> Seleziona </option>
<option value="prospect" {{ old('status', $customer->status ?? 'prospect') === 'prospect' ? 'selected' : '' }}>
Prospect
</option>
<option value="attivo" {{ old('status', $customer->status ?? '') === 'attivo' ? 'selected' : '' }}>
Attivo
</option>
<option value="inattivo" {{ old('status', $customer->status ?? '') === 'inattivo' ? 'selected' : '' }}>
Inattivo
</option>
</select>
@error('status')
<div class="invalid-feedback">{{ $message }}</div>
@enderror
</div>
</div>
{{-- ─── Sezione 2: Sede e dati fiscali ──────────────────────────────────────── --}}
<div class="row g-3 mb-4">
<div class="col-12">
<h6 class="text-muted fw-semibold text-uppercase small mb-3">
<i class="bi bi-building me-1"></i>Sede e dati fiscali
</h6>
</div>
<div class="col-md-4">
<label class="form-label fw-medium">Città</label>
<input type="text" name="city"
class="form-control {{ $errors->has('city') ? 'is-invalid' : '' }}"
value="{{ old('city', $customer->city ?? '') }}"
placeholder="Milano">
@error('city')
<div class="invalid-feedback">{{ $message }}</div>
@enderror
</div>
<div class="col-md-8">
<label class="form-label fw-medium">Indirizzo</label>
<input type="text" name="address"
class="form-control {{ $errors->has('address') ? 'is-invalid' : '' }}"
value="{{ old('address', $customer->address ?? '') }}"
placeholder="Via Roma, 1 - 20100 Milano MI">
@error('address')
<div class="invalid-feedback">{{ $message }}</div>
@enderror
</div>
<div class="col-md-4">
<label class="form-label fw-medium">Partita IVA</label>
<input type="text" name="vat_number"
class="form-control {{ $errors->has('vat_number') ? 'is-invalid' : '' }}"
value="{{ old('vat_number', $customer->vat_number ?? '') }}"
placeholder="IT12345678901">
@error('vat_number')
<div class="invalid-feedback">{{ $message }}</div>
@enderror
</div>
<div class="col-md-4">
<label class="form-label fw-medium">Codice Fiscale</label>
<input type="text" name="fiscal_code"
class="form-control {{ $errors->has('fiscal_code') ? 'is-invalid' : '' }}"
value="{{ old('fiscal_code', $customer->fiscal_code ?? '') }}"
placeholder="RSSMRA80A01H501Z">
@error('fiscal_code')
<div class="invalid-feedback">{{ $message }}</div>
@enderror
</div>
<div class="col-md-4">
<label class="form-label fw-medium">Valore Contratto ({{ $appSettings['currency_symbol'] }})</label>
<div class="input-group">
<span class="input-group-text">{{ $appSettings['currency_symbol'] }}</span>
<input type="number" name="contract_value" step="0.01" min="0"
class="form-control {{ $errors->has('contract_value') ? 'is-invalid' : '' }}"
value="{{ old('contract_value', $customer->contract_value ?? '0') }}"
placeholder="0.00">
</div>
@error('contract_value')
<div class="invalid-feedback">{{ $message }}</div>
@enderror
</div>
</div>
{{-- ─── Sezione 3: Note ─────────────────────────────────────────────────────── --}}
@if($appSettings['allow_notes'] ?? true)
<div class="row g-3">
<div class="col-12">
<h6 class="text-muted fw-semibold text-uppercase small mb-3">
<i class="bi bi-sticky me-1"></i>Note
</h6>
<textarea name="notes" rows="4"
class="form-control {{ $errors->has('notes') ? 'is-invalid' : '' }}"
placeholder="Note interne, informazioni aggiuntive...">{{ old('notes', $customer->notes ?? '') }}</textarea>
@error('notes')
<div class="invalid-feedback">{{ $message }}</div>
@enderror
</div>
</div>
@endif

View File

@@ -0,0 +1,49 @@
@extends('layouts.app')
@section('title', 'Nuovo Cliente')
@section('page-title', 'Nuovo Cliente')
@section('page-actions')
<a href="{{ route('customers.index') }}" class="btn btn-outline-secondary">
<i class="bi bi-arrow-left me-1"></i>Torna alla lista
</a>
@endsection
@section('content')
<div class="row justify-content-center">
<div class="col-lg-10">
<div class="card">
<div class="card-header bg-white border-0 pt-4 pb-0 px-4">
<h5 class="fw-semibold">
<i class="bi bi-person-plus me-2 text-primary"></i>Dati cliente
</h5>
<p class="text-muted small">I campi contrassegnati con * sono obbligatori.</p>
</div>
<div class="card-body p-4">
{{--
@csrf: genera un token nascosto per proteggere da attacchi CSRF
(Cross-Site Request Forgery). Laravel lo verifica automaticamente.
OBBLIGATORIO in tutti i form POST/PUT/DELETE!
--}}
<form method="POST" action="{{ route('customers.store') }}">
@csrf
@include('customers._form')
<div class="d-flex justify-content-end gap-2 mt-4 pt-3 border-top">
<a href="{{ route('customers.index') }}" class="btn btn-outline-secondary">
Annulla
</a>
<button type="submit" class="btn btn-primary">
<i class="bi bi-check-lg me-1"></i>Salva Cliente
</button>
</div>
</form>
</div>
</div>
</div>
</div>
@endsection

View File

@@ -0,0 +1,49 @@
@extends('layouts.app')
@section('title', 'Modifica ' . $customer->name)
@section('page-title', 'Modifica Cliente')
@section('page-actions')
<a href="{{ route('customers.show', $customer) }}" class="btn btn-outline-secondary">
<i class="bi bi-arrow-left me-1"></i>Torna al dettaglio
</a>
@endsection
@section('content')
<div class="row justify-content-center">
<div class="col-lg-10">
<div class="card">
<div class="card-header bg-white border-0 pt-4 pb-0 px-4">
<h5 class="fw-semibold">
<i class="bi bi-pencil me-2 text-warning"></i>{{ $customer->name }}
</h5>
<p class="text-muted small">I campi contrassegnati con * sono obbligatori.</p>
</div>
<div class="card-body p-4">
{{--
@method('PUT'): poiché i browser inviano solo GET/POST,
Laravel "finge" il metodo PUT tramite un campo nascosto _method.
--}}
<form method="POST" action="{{ route('customers.update', $customer) }}">
@csrf
@method('PUT')
@include('customers._form', ['customer' => $customer])
<div class="d-flex justify-content-end gap-2 mt-4 pt-3 border-top">
<a href="{{ route('customers.show', $customer) }}" class="btn btn-outline-secondary">
Annulla
</a>
<button type="submit" class="btn btn-warning">
<i class="bi bi-check-lg me-1"></i>Salva Modifiche
</button>
</div>
</form>
</div>
</div>
</div>
</div>
@endsection

View File

@@ -0,0 +1,158 @@
@extends('layouts.app')
@section('title', 'Clienti')
@section('page-title', 'Gestione Clienti')
@section('page-actions')
<a href="{{ route('customers.create') }}" class="btn btn-primary">
<i class="bi bi-person-plus me-1"></i>Nuovo Cliente
</a>
@endsection
@section('content')
{{-- ═══ Barra filtri ════════════════════════════════════════════════════ --}}
<div class="card mb-4">
<div class="card-body py-3">
{{-- method="GET": i filtri vengono messi nell'URL (?search=...&type=...) --}}
<form method="GET" action="{{ route('customers.index') }}" class="row g-2 align-items-end">
<div class="col-md-4">
<label class="form-label small fw-semibold text-muted">Cerca</label>
<div class="input-group">
<span class="input-group-text"><i class="bi bi-search"></i></span>
<input type="text" name="search"
class="form-control"
placeholder="Nome, email, città, P.IVA..."
{{-- old(): ripopola il campo con il valore precedente dopo ricarica pagina --}}
value="{{ request('search') }}">
</div>
</div>
<div class="col-md-2">
<label class="form-label small fw-semibold text-muted">Tipo</label>
<select name="type" class="form-select">
<option value="">Tutti</option>
<option value="privato" {{ request('type') === 'privato' ? 'selected' : '' }}>Privato</option>
<option value="azienda" {{ request('type') === 'azienda' ? 'selected' : '' }}>Azienda</option>
</select>
</div>
<div class="col-md-2">
<label class="form-label small fw-semibold text-muted">Stato</label>
<select name="status" class="form-select">
<option value="">Tutti</option>
<option value="attivo" {{ request('status') === 'attivo' ? 'selected' : '' }}>Attivo</option>
<option value="prospect" {{ request('status') === 'prospect' ? 'selected' : '' }}>Prospect</option>
<option value="inattivo" {{ request('status') === 'inattivo' ? 'selected' : '' }}>Inattivo</option>
</select>
</div>
<div class="col-md-2">
<button type="submit" class="btn btn-primary w-100">
<i class="bi bi-funnel me-1"></i>Filtra
</button>
</div>
<div class="col-md-2">
<a href="{{ route('customers.index') }}" class="btn btn-outline-secondary w-100">
<i class="bi bi-x me-1"></i>Reset
</a>
</div>
</form>
</div>
</div>
{{-- ═══ Tabella clienti ════════════════════════════════════════════════ --}}
<div class="card">
<div class="card-header bg-white border-0 pt-3">
<span class="text-muted small">
{{ $customers->total() }} {{ Str::plural('cliente', $customers->total()) }} trovati
</span>
</div>
<div class="table-responsive">
<table class="table table-hover align-middle mb-0">
<thead class="table-light">
<tr>
<th class="ps-4">Cliente</th>
<th>Contatto</th>
<th>Città</th>
<th>Tipo</th>
<th>Stato</th>
<th>Contratto</th>
<th class="text-end pe-4">Azioni</th>
</tr>
</thead>
<tbody>
@forelse ($customers as $customer)
<tr>
<td class="ps-4">
<div class="fw-semibold">{{ $customer->name }}</div>
@if ($customer->vat_number)
<div class="text-muted small">P.IVA: {{ $customer->vat_number }}</div>
@endif
</td>
<td>
<div>{{ $customer->email }}</div>
@if ($customer->phone)
<div class="text-muted small">{{ $customer->phone }}</div>
@endif
</td>
<td class="text-muted">{{ $customer->city ?? '' }}</td>
<td>
<span class="badge bg-light text-dark border">{{ $customer->type_label }}</span>
</td>
<td>
<span class="badge bg-{{ $customer->badge_color }}">
{{ ucfirst($customer->status) }}
</span>
</td>
<td class="fw-semibold">
{{ $appSettings['currency_symbol'] }}{{ number_format($customer->contract_value, 0, ',', '.') }}
</td>
<td class="text-end pe-4">
<div class="btn-group btn-group-sm">
<a href="{{ route('customers.show', $customer) }}"
class="btn btn-outline-primary" title="Dettaglio">
<i class="bi bi-eye"></i>
</a>
<a href="{{ route('customers.edit', $customer) }}"
class="btn btn-outline-secondary" title="Modifica">
<i class="bi bi-pencil"></i>
</a>
{{-- Form DELETE: i browser non supportano DELETE nativo,
Laravel usa il campo _method come workaround --}}
<form method="POST" action="{{ route('customers.destroy', $customer) }}"
onsubmit="return confirm('Eliminare {{ addslashes($customer->name) }}?')">
@csrf
@method('DELETE')
<button type="submit" class="btn btn-outline-danger" title="Elimina">
<i class="bi bi-trash"></i>
</button>
</form>
</div>
</td>
</tr>
@empty
<tr>
<td colspan="7" class="text-center text-muted py-5">
<i class="bi bi-people display-6 d-block mb-2 opacity-25"></i>
Nessun cliente trovato.<br>
<a href="{{ route('customers.create') }}" class="btn btn-primary mt-3">
<i class="bi bi-person-plus me-1"></i>Aggiungi il primo cliente
</a>
</td>
</tr>
@endforelse
</tbody>
</table>
</div>
{{-- Paginazione: generata automaticamente da Laravel --}}
@if ($customers->hasPages())
<div class="card-footer bg-white d-flex justify-content-between align-items-center">
<div class="text-muted small">
Pagina {{ $customers->currentPage() }} di {{ $customers->lastPage() }}
</div>
{{-- links() genera i bottoni prev/next con Bootstrap styling --}}
{{ $customers->links('pagination::bootstrap-5') }}
</div>
@endif
</div>
@endsection

View File

@@ -0,0 +1,153 @@
@extends('layouts.app')
@section('title', $customer->name)
@section('page-title', $customer->name)
@section('page-actions')
<div class="btn-group">
<a href="{{ route('customers.index') }}" class="btn btn-outline-secondary">
<i class="bi bi-arrow-left me-1"></i>Lista
</a>
<a href="{{ route('customers.edit', $customer) }}" class="btn btn-warning">
<i class="bi bi-pencil me-1"></i>Modifica
</a>
<form method="POST" action="{{ route('customers.destroy', $customer) }}"
onsubmit="return confirm('Sei sicuro di voler eliminare questo cliente?')">
@csrf
@method('DELETE')
<button type="submit" class="btn btn-danger">
<i class="bi bi-trash me-1"></i>Elimina
</button>
</form>
</div>
@endsection
@section('content')
<div class="row g-4">
{{-- ─── Scheda principale ────────────────────────────────────────────── --}}
<div class="col-lg-8">
<div class="card">
<div class="card-header bg-white border-0 pt-4 px-4 pb-0">
<div class="d-flex align-items-center gap-3">
{{-- Avatar generato con iniziali --}}
<div class="rounded-circle bg-primary d-flex align-items-center justify-content-center text-white fw-bold fs-4"
style="width: 56px; height: 56px; flex-shrink: 0;">
{{ strtoupper(substr($customer->name, 0, 1)) }}
</div>
<div>
<h4 class="mb-1">{{ $customer->name }}</h4>
<div class="d-flex gap-2">
<span class="badge bg-{{ $customer->badge_color }}">{{ ucfirst($customer->status) }}</span>
<span class="badge bg-light text-dark border">{{ $customer->type_label }}</span>
</div>
</div>
</div>
</div>
<div class="card-body p-4">
<div class="row g-4">
{{-- Contatti --}}
<div class="col-md-6">
<h6 class="text-muted text-uppercase small fw-semibold mb-3">Contatto</h6>
<dl class="row mb-0">
<dt class="col-4 text-muted fw-normal small">Email</dt>
<dd class="col-8">
<a href="mailto:{{ $customer->email }}">{{ $customer->email }}</a>
</dd>
@if($customer->phone)
<dt class="col-4 text-muted fw-normal small">Telefono</dt>
<dd class="col-8">
<a href="tel:{{ $customer->phone }}">{{ $customer->phone }}</a>
</dd>
@endif
@if($customer->city)
<dt class="col-4 text-muted fw-normal small">Città</dt>
<dd class="col-8">{{ $customer->city }}</dd>
@endif
@if($customer->address)
<dt class="col-4 text-muted fw-normal small">Indirizzo</dt>
<dd class="col-8">{{ $customer->address }}</dd>
@endif
</dl>
</div>
{{-- Dati fiscali --}}
<div class="col-md-6">
<h6 class="text-muted text-uppercase small fw-semibold mb-3">Dati fiscali</h6>
<dl class="row mb-0">
@if($customer->vat_number)
<dt class="col-5 text-muted fw-normal small">P. IVA</dt>
<dd class="col-7 font-monospace">{{ $customer->vat_number }}</dd>
@endif
@if($customer->fiscal_code)
<dt class="col-5 text-muted fw-normal small">Cod. Fiscale</dt>
<dd class="col-7 font-monospace">{{ $customer->fiscal_code }}</dd>
@endif
<dt class="col-5 text-muted fw-normal small">Contratto</dt>
<dd class="col-7 fw-bold text-success fs-5">
{{ $appSettings['currency_symbol'] }}{{ number_format($customer->contract_value, 2, ',', '.') }}
</dd>
</dl>
</div>
</div>
{{-- Note --}}
@if ($customer->notes)
<hr>
<h6 class="text-muted text-uppercase small fw-semibold mb-2">Note</h6>
<p class="mb-0 text-secondary">{{ $customer->notes }}</p>
@endif
</div>
</div>
</div>
{{-- ─── Sidebar informazioni ─────────────────────────────────────────── --}}
<div class="col-lg-4">
<div class="card mb-3">
<div class="card-body">
<h6 class="text-muted text-uppercase small fw-semibold mb-3">Timeline</h6>
<div class="d-flex flex-column gap-2">
<div class="d-flex justify-content-between">
<span class="text-muted small">Creato il</span>
{{-- format() usa il formato da configurazione dinamica --}}
<span class="small">{{ $customer->created_at->format($appSettings['date_format'] ?? 'd/m/Y') }}</span>
</div>
<div class="d-flex justify-content-between">
<span class="text-muted small">Aggiornato il</span>
<span class="small">{{ $customer->updated_at->format($appSettings['date_format'] ?? 'd/m/Y') }}</span>
</div>
<div class="d-flex justify-content-between">
<span class="text-muted small">ID sistema</span>
<span class="small font-monospace">#{{ $customer->id }}</span>
</div>
</div>
</div>
</div>
<div class="card">
<div class="card-body text-center">
<p class="text-muted small mb-3">Azioni rapide</p>
<div class="d-grid gap-2">
<a href="{{ route('customers.edit', $customer) }}" class="btn btn-warning btn-sm">
<i class="bi bi-pencil me-1"></i>Modifica dati
</a>
<a href="mailto:{{ $customer->email }}" class="btn btn-outline-primary btn-sm">
<i class="bi bi-envelope me-1"></i>Invia email
</a>
</div>
</div>
</div>
</div>
</div>
@endsection