glastree_on_gitea
This commit is contained in:
@@ -0,0 +1,191 @@
|
||||
@extends('layouts.adminlte')
|
||||
@section('title', 'Nuovo Individuo')
|
||||
@section('page_title', 'Nuovo Individuo')
|
||||
|
||||
@section('content')
|
||||
<form action="/individui" method="POST">
|
||||
@csrf
|
||||
<div class="row">
|
||||
<div class="col-md-6">
|
||||
<div class="card card-primary">
|
||||
<div class="card-header">
|
||||
<h3 class="card-title"><i class="fas fa-user mr-2"></i>Dati Anagrafici</h3>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<div class="form-group">
|
||||
<label>Codice ID</label>
|
||||
<input type="text" class="form-control" value="sarà generato automaticamente" disabled>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label>Cognome *</label>
|
||||
<input type="text" name="cognome" class="form-control @error('cognome') is-invalid @enderror" value="{{ old('cognome') }}" required>
|
||||
@error('cognome')
|
||||
<span class="invalid-feedback">{{ $message }}</span>
|
||||
@enderror
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label>Nome *</label>
|
||||
<input type="text" name="nome" class="form-control @error('nome') is-invalid @enderror" value="{{ old('nome') }}" required>
|
||||
@error('nome')
|
||||
<span class="invalid-feedback">{{ $message }}</span>
|
||||
@enderror
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label>Data di nascita</label>
|
||||
<input type="date" name="data_nascita" class="form-control" value="{{ old('data_nascita') }}">
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label>Genere</label>
|
||||
<select name="genere" class="form-control">
|
||||
<option value="">Seleziona...</option>
|
||||
<option value="M" {{ old('genere') === 'M' ? 'selected' : '' }}>Maschio</option>
|
||||
<option value="F" {{ old('genere') === 'F' ? 'selected' : '' }}>Femmina</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<div class="card">
|
||||
<div class="card-header"><h3 class="card-title"><i class="fas fa-map-marker-alt mr-2"></i>Residenza</h3></div>
|
||||
<div class="card-body">
|
||||
<div class="form-group">
|
||||
<label>Indirizzo</label>
|
||||
<input type="text" name="indirizzo" class="form-control" value="{{ old('indirizzo') }}">
|
||||
</div>
|
||||
<div class="form-row">
|
||||
<div class="form-group col-md-4">
|
||||
<label>CAP</label>
|
||||
<input type="text" name="cap" class="form-control" value="{{ old('cap') }}">
|
||||
</div>
|
||||
<div class="form-group col-md-5">
|
||||
<label>Città</label>
|
||||
<input type="text" name="città" class="form-control" value="{{ old('città') }}">
|
||||
</div>
|
||||
<div class="form-group col-md-3">
|
||||
<label>Provincia</label>
|
||||
<input type="text" name="sigla_provincia" class="form-control" maxlength="2" value="{{ old('sigla_provincia') }}">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row mt-3">
|
||||
<div class="col-md-6">
|
||||
<div class="card">
|
||||
<div class="card-header"><h3 class="card-title"><i class="fas fa-id-card mr-2"></i>Documento di identità</h3></div>
|
||||
<div class="card-body">
|
||||
<div class="form-group">
|
||||
<label>Tipo documento</label>
|
||||
<select name="tipo_documento" class="form-control">
|
||||
<option value="">Seleziona...</option>
|
||||
<option value="carta_identita" {{ old('tipo_documento') === 'carta_identita' ? 'selected' : '' }}>Carta d'Identità</option>
|
||||
<option value="patente" {{ old('tipo_documento') === 'patente' ? 'selected' : '' }}>Patente</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label>Numero documento</label>
|
||||
<input type="text" name="numero_documento" class="form-control" value="{{ old('numero_documento') }}">
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label>Scadenza documento</label>
|
||||
<input type="date" name="scadenza_documento" class="form-control" value="{{ old('scadenza_documento') }}">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<div class="card">
|
||||
<div class="card-header"><h3 class="card-title"><i class="fas fa-sticky-note mr-2"></i>Note</h3></div>
|
||||
<div class="card-body">
|
||||
<div class="form-group mb-0">
|
||||
<textarea name="note" class="form-control" rows="4">{{ old('note') }}</textarea>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="card mt-3">
|
||||
<div class="card-header">
|
||||
<h3 class="card-title"><i class="fas fa-address-book mr-2"></i>Contatti</h3>
|
||||
<button type="button" class="btn btn-xs btn-success float-right" onclick="aggiungiRigaContatto()">
|
||||
<i class="fas fa-plus mr-1"></i> Aggiungi
|
||||
</button>
|
||||
</div>
|
||||
<div class="card-body p-0">
|
||||
<table class="table table-bordered mb-0" id="contatti-table">
|
||||
<thead class="thead-light">
|
||||
<tr>
|
||||
<th style="width: 20%;">Tipo</th>
|
||||
<th style="width: 30%;">Valore</th>
|
||||
<th style="width: 20%;">Etichetta</th>
|
||||
<th style="width: 10%;">Primario</th>
|
||||
<th style="width: 50px;"></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody id="contatti-tbody">
|
||||
</tbody>
|
||||
</table>
|
||||
<div id="no-contatti-msg" class="text-center text-muted py-4">
|
||||
<p class="mb-0">Nessun contatto. Clicca su "Aggiungi" per aggiungerne uno.</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="mt-3">
|
||||
<button type="submit" class="btn btn-success">
|
||||
<i class="fas fa-save mr-1"></i> Salva
|
||||
</button>
|
||||
<a href="/individui" class="btn btn-secondary">
|
||||
<i class="fas fa-times mr-1"></i> Annulla
|
||||
</a>
|
||||
</div>
|
||||
</form>
|
||||
@endsection
|
||||
|
||||
@section('scripts')
|
||||
<script>
|
||||
let contattoIndex = 0;
|
||||
|
||||
function aggiungiRigaContatto() {
|
||||
const tbody = document.getElementById('contatti-tbody');
|
||||
const noMsg = document.getElementById('no-contatti-msg');
|
||||
if (noMsg) noMsg.style.display = 'none';
|
||||
|
||||
const row = document.createElement('tr');
|
||||
row.innerHTML = `
|
||||
<td>
|
||||
<select name="contatti[${contattoIndex}][tipo]" class="form-control">
|
||||
<option value="">Seleziona...</option>
|
||||
<option value="telefono">Telefono</option>
|
||||
<option value="cellulare">Cellulare</option>
|
||||
<option value="email">Email</option>
|
||||
<option value="fax">Fax</option>
|
||||
<option value="web">Web</option>
|
||||
<option value="telegram">Telegram</option>
|
||||
<option value="whatsapp">WhatsApp</option>
|
||||
<option value="altro">Altro</option>
|
||||
</select>
|
||||
</td>
|
||||
<td><input type="text" name="contatti[${contattoIndex}][valore]" class="form-control" placeholder="Valore"></td>
|
||||
<td><input type="text" name="contatti[${contattoIndex}][etichetta]" class="form-control" placeholder="Etichetta"></td>
|
||||
<td class="text-center"><input type="checkbox" name="contatti[${contattoIndex}][is_primary]" value="1"></td>
|
||||
<td><button type="button" class="btn btn-danger btn-xs" onclick="rimuoviRiga(this)"><i class="fas fa-trash"></i></button></td>
|
||||
`;
|
||||
tbody.appendChild(row);
|
||||
contattoIndex++;
|
||||
}
|
||||
|
||||
function rimuoviRiga(btn) {
|
||||
const row = btn.closest('tr');
|
||||
row.remove();
|
||||
if (document.getElementById('contatti-tbody').children.length === 0) {
|
||||
const noMsg = document.getElementById('no-contatti-msg');
|
||||
if (noMsg) noMsg.style.display = 'block';
|
||||
}
|
||||
}
|
||||
</script>
|
||||
@endsection
|
||||
@@ -0,0 +1,718 @@
|
||||
@extends('layouts.adminlte')
|
||||
@section('title', 'Modifica Individuo')
|
||||
@section('page_title', 'Modifica Individuo')
|
||||
|
||||
@section('content')
|
||||
<form action="/individui/{{ $individuo->id }}" method="POST" id="main-form">
|
||||
@csrf
|
||||
@method('PUT')
|
||||
<input type="hidden" name="individuo_id" value="{{ $individuo->id }}">
|
||||
<input type="hidden" name="_avatar_token" value="{{ csrf_token() }}">
|
||||
<div class="row">
|
||||
<div class="col-md-6">
|
||||
<div class="card card-primary">
|
||||
<div class="card-header">
|
||||
<h3 class="card-title"><i class="fas fa-user mr-2"></i>Dati Anagrafici</h3>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<div class="form-group">
|
||||
<label>Codice ID</label>
|
||||
<input type="text" class="form-control" value="{{ $individuo->codice_id }}" disabled>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label>Cognome *</label>
|
||||
<input type="text" name="cognome" class="form-control" value="{{ $individuo->cognome }}" required>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label>Nome *</label>
|
||||
<input type="text" name="nome" class="form-control" value="{{ $individuo->nome }}" required>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label>Data di nascita</label>
|
||||
<input type="date" name="data_nascita" class="form-control" value="{{ $individuo->data_nascita?->format('Y-m-d') }}">
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label>Genere</label>
|
||||
<select name="genere" class="form-control">
|
||||
<option value="">Seleziona...</option>
|
||||
<option value="M" {{ $individuo->genere === 'M' ? 'selected' : '' }}>Maschio</option>
|
||||
<option value="F" {{ $individuo->genere === 'F' ? 'selected' : '' }}>Femmina</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<div class="card card-secondary">
|
||||
<div class="card-header">
|
||||
<h3 class="card-title"><i class="fas fa-camera mr-2"></i>Avatar</h3>
|
||||
</div>
|
||||
<div class="card-body text-center">
|
||||
@if($individuo->avatar)
|
||||
<img src="/individui/{{ $individuo->id }}/avatar"
|
||||
alt="Avatar"
|
||||
class="img-thumbnail mb-2"
|
||||
style="max-width: 120px; max-height: 120px; object-fit: cover; border-radius: 50%;">
|
||||
<br>
|
||||
<button type="button" class="btn btn-danger btn-sm" onclick="deleteAvatar()">
|
||||
<i class="fas fa-trash mr-1"></i>Rimuovi
|
||||
</button>
|
||||
@else
|
||||
<div class="bg-secondary d-flex align-items-center justify-content-center mb-3"
|
||||
style="width: 120px; height: 120px; margin: 0 auto; border-radius: 50%;">
|
||||
<i class="fas fa-user fa-4x text-white"></i>
|
||||
</div>
|
||||
@endif
|
||||
<div class="custom-file mb-2">
|
||||
<input type="file" class="custom-file-input" id="avatar-input" name="avatar" accept="image/jpeg,image/png,image/gif">
|
||||
<label class="custom-file-label" for="avatar-input">Scegli immagine...</label>
|
||||
</div>
|
||||
<button type="button" class="btn btn-primary btn-sm" onclick="uploadAvatar()">
|
||||
<i class="fas fa-upload mr-1"></i>Carica Avatar
|
||||
</button>
|
||||
<div id="avatar-message" class="mt-2"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
<div class="col-md-6">
|
||||
<div class="card">
|
||||
<div class="card-header"><h3 class="card-title"><i class="fas fa-map-marker-alt mr-2"></i>Residenza</h3></div>
|
||||
<div class="card-body">
|
||||
<div class="form-group">
|
||||
<label>Indirizzo</label>
|
||||
<input type="text" name="indirizzo" class="form-control" value="{{ $individuo->indirizzo }}">
|
||||
</div>
|
||||
<div class="form-row">
|
||||
<div class="form-group col-md-4">
|
||||
<label>CAP</label>
|
||||
<input type="text" name="cap" class="form-control" value="{{ $individuo->cap }}">
|
||||
</div>
|
||||
<div class="form-group col-md-5">
|
||||
<label>Città</label>
|
||||
<input type="text" name="città" class="form-control" value="{{ $individuo->città }}">
|
||||
</div>
|
||||
<div class="form-group col-md-3">
|
||||
<label>Provincia</label>
|
||||
<input type="text" name="sigla_provincia" class="form-control" maxlength="2" value="{{ $individuo->sigla_provincia }}">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<div class="card">
|
||||
<div class="card-header"><h3 class="card-title"><i class="fas fa-id-card mr-2"></i>Documento di identità</h3></div>
|
||||
<div class="card-body">
|
||||
<div class="form-group">
|
||||
<label>Tipo documento</label>
|
||||
<select name="tipo_documento" class="form-control">
|
||||
<option value="">Seleziona...</option>
|
||||
<option value="carta_identita" {{ $individuo->tipo_documento === 'carta_identita' ? 'selected' : '' }}>Carta d'Identità</option>
|
||||
<option value="patente" {{ $individuo->tipo_documento === 'patente' ? 'selected' : '' }}>Patente</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label>Numero documento</label>
|
||||
<input type="text" name="numero_documento" class="form-control" value="{{ $individuo->numero_documento }}">
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label>Scadenza documento</label>
|
||||
<input type="date" name="scadenza_documento" class="form-control" value="{{ $individuo->scadenza_documento?->format('Y-m-d') }}">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
<div class="col-md-6">
|
||||
<div class="card">
|
||||
<div class="card-header"><h3 class="card-title"><i class="fas fa-sticky-note mr-2"></i>Note</h3></div>
|
||||
<div class="card-body">
|
||||
<div class="form-group mb-0">
|
||||
<textarea name="note" class="form-control" rows="4">{{ $individuo->note }}</textarea>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="card mt-3">
|
||||
<div class="card-header">
|
||||
<h3 class="card-title"><i class="fas fa-address-book mr-2"></i>Contatti</h3>
|
||||
<button type="button" class="btn btn-xs btn-success float-right" onclick="aggiungiRigaContatto()">
|
||||
<i class="fas fa-plus mr-1"></i> Aggiungi
|
||||
</button>
|
||||
</div>
|
||||
<div class="card-body p-0">
|
||||
<table class="table table-bordered mb-0" id="contatti-table">
|
||||
<thead class="thead-light">
|
||||
<tr>
|
||||
<th style="width: 20%;">Tipo</th>
|
||||
<th style="width: 30%;">Valore</th>
|
||||
<th style="width: 20%;">Etichetta</th>
|
||||
<th style="width: 10%;">Primario</th>
|
||||
<th style="width: 50px;"></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody id="contatti-tbody">
|
||||
@foreach($individuo->contatti as $contatto)
|
||||
<tr>
|
||||
<td>
|
||||
<select name="contatti[{{ $loop->index }}][tipo]" class="form-control">
|
||||
<option value="telefono" {{ $contatto->tipo === 'telefono' ? 'selected' : '' }}>Telefono</option>
|
||||
<option value="cellulare" {{ $contatto->tipo === 'cellulare' ? 'selected' : '' }}>Cellulare</option>
|
||||
<option value="email" {{ $contatto->tipo === 'email' ? 'selected' : '' }}>Email</option>
|
||||
<option value="fax" {{ $contatto->tipo === 'fax' ? 'selected' : '' }}>Fax</option>
|
||||
<option value="web" {{ $contatto->tipo === 'web' ? 'selected' : '' }}>Web</option>
|
||||
<option value="telegram" {{ $contatto->tipo === 'telegram' ? 'selected' : '' }}>Telegram</option>
|
||||
<option value="whatsapp" {{ $contatto->tipo === 'whatsapp' ? 'selected' : '' }}>WhatsApp</option>
|
||||
<option value="altro" {{ $contatto->tipo === 'altro' ? 'selected' : '' }}>Altro</option>
|
||||
</select>
|
||||
</td>
|
||||
<td><input type="text" name="contatti[{{ $loop->index }}][valore]" class="form-control" value="{{ $contatto->valore }}"></td>
|
||||
<td><input type="text" name="contatti[{{ $loop->index }}][etichetta]" class="form-control" value="{{ $contatto->etichetta }}"></td>
|
||||
<td class="text-center"><input type="checkbox" name="contatti[{{ $loop->index }}][is_primary]" value="1" {{ $contatto->is_primary ? 'checked' : '' }}></td>
|
||||
<td><button type="button" class="btn btn-danger btn-xs" onclick="this.closest('tr').remove()"><i class="fas fa-trash"></i></button></td>
|
||||
</tr>
|
||||
@endforeach
|
||||
</tbody>
|
||||
</table>
|
||||
@if($individuo->contatti->isEmpty())
|
||||
<div id="no-contatti-msg" class="text-center text-muted py-4">
|
||||
<p class="mb-0">Nessun contatto. Clicca su "Aggiungi" per aggiungerne uno.</p>
|
||||
</div>
|
||||
@endif
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="card mt-3">
|
||||
<div class="card-header">
|
||||
<h3 class="card-title"><i class="fas fa-folder mr-2"></i>Gruppi</h3>
|
||||
<button type="button" class="btn btn-xs btn-success float-right" onclick="showGruppoForm()">
|
||||
<i class="fas fa-plus mr-1"></i> Aggiungi
|
||||
</button>
|
||||
</div>
|
||||
<div class="card-body p-0">
|
||||
|
||||
@if($individuo->gruppi->count() > 0)
|
||||
<table class="table table-bordered table-hover table-striped mb-0">
|
||||
<thead class="thead-light">
|
||||
<tr>
|
||||
<th>Nome</th>
|
||||
<th>Diocesi</th>
|
||||
<th style="width: 130px;">Ruolo</th>
|
||||
<th style="width: 110px;">Data Adesione</th>
|
||||
<th style="width: 80px;">Azioni</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
@foreach($individuo->gruppi as $gruppo)
|
||||
<tr id="gruppo-row-{{ $gruppo->id }}">
|
||||
<td>
|
||||
<i class="fas fa-folder text-warning mr-1"></i>
|
||||
<a href="/gruppi/{{ $gruppo->id }}">{{ $gruppo->nome }}</a>
|
||||
</td>
|
||||
<td>{{ $gruppo->diocesi?->nome ?: '-' }}</td>
|
||||
<td>
|
||||
@php $ruoliIndividuo = $individuo->getRuoliForGruppo($gruppo->id) @endphp
|
||||
@if($ruoliIndividuo->count() > 0)
|
||||
@foreach($ruoliIndividuo as $ruolo)
|
||||
<span class="badge badge-info mr-1">{{ $ruolo->nome }}</span>
|
||||
@endforeach
|
||||
@else
|
||||
-
|
||||
@endif
|
||||
</td>
|
||||
<td>{{ $gruppo->pivot->data_adesione ? \Carbon\Carbon::parse($gruppo->pivot->data_adesione)->format('d/m/Y') : '-' }}</td>
|
||||
<td>
|
||||
<button type="button" class="btn btn-xs btn-warning" onclick="editGruppoInline({{ $gruppo->id }})" title="Modifica">
|
||||
<i class="fas fa-edit"></i>
|
||||
</button>
|
||||
<form action="/individui/{{ $individuo->id }}/gruppi/{{ $gruppo->id }}" method="POST" class="mb-0">
|
||||
@csrf @method('DELETE')
|
||||
<button type="submit" class="btn btn-xs btn-danger" onclick="return confirm('Rimuovere da questo gruppo?')" title="Rimuovi">
|
||||
<i class="fas fa-trash"></i>
|
||||
</button>
|
||||
</form>
|
||||
</td>
|
||||
</tr>
|
||||
<tr id="gruppo-edit-{{ $gruppo->id }}" style="display:none;">
|
||||
<td colspan="5">
|
||||
<form action="/individui/{{ $individuo->id }}/gruppi/{{ $gruppo->id }}" method="POST" class="mb-0">
|
||||
@csrf @method('PUT')
|
||||
<div class="row">
|
||||
<div class="col-md-4">
|
||||
<input type="text" class="form-control form-control-sm" value="{{ $gruppo->nome }}" disabled>
|
||||
</div>
|
||||
<div class="col-md-3">
|
||||
<select name="ruolo_ids[]" class="form-control form-control-sm" multiple size="3">
|
||||
@php $selectedRuoliIds = $individuo->getRuoloIdsForGruppo($gruppo->id) @endphp
|
||||
@foreach(\App\Models\Ruolo::attive() as $ruolo)
|
||||
<option value="{{ $ruolo->id }}" {{ in_array($ruolo->id, $selectedRuoliIds) ? 'selected' : '' }}>{{ $ruolo->nome }}</option>
|
||||
@endforeach
|
||||
</select>
|
||||
</div>
|
||||
<div class="col-md-3">
|
||||
<input type="date" name="data_adesione" class="form-control form-control-sm" value="{{ $gruppo->pivot->data_adesione }}">
|
||||
</div>
|
||||
<div class="col-md-2">
|
||||
<button type="submit" class="btn btn-xs btn-success"><i class="fas fa-check mr-1"></i>Salva</button>
|
||||
<button type="button" class="btn btn-xs btn-secondary" onclick="cancelEditGruppo({{ $gruppo->id }})"><i class="fas fa-times"></i></button>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</td>
|
||||
</tr>
|
||||
@endforeach
|
||||
</tbody>
|
||||
</table>
|
||||
@else
|
||||
<div class="text-center text-muted py-4">
|
||||
<i class="fas fa-users fa-2x mb-2"></i>
|
||||
<p class="mb-0">Nessun gruppo associato</p>
|
||||
</div>
|
||||
@endif
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="card mt-3">
|
||||
<div class="card-header">
|
||||
<h3 class="card-title"><i class="fas fa-file mr-2"></i>Documenti</h3>
|
||||
<button type="button" class="btn btn-xs btn-success float-right" onclick="showDocumentoForm()">
|
||||
<i class="fas fa-plus mr-1"></i> Carica
|
||||
</button>
|
||||
</div>
|
||||
<div class="card-body p-0">
|
||||
<div id="documento-add-form" style="display:none;" class="p-3 bg-light border-bottom">
|
||||
<form action="/documenti" method="POST" enctype="multipart/form-data" class="mb-0">
|
||||
@csrf
|
||||
<input type="hidden" name="_redirect" value="{{ url()->current() }}">
|
||||
<input type="hidden" name="visibilita" value="individuo">
|
||||
<input type="hidden" name="visibilita_target_id" value="{{ $individuo->id }}">
|
||||
<input type="hidden" name="visibilita_target_type" value="App\Models\Individuo">
|
||||
<div class="row">
|
||||
<div class="col-md-3">
|
||||
<input type="text" name="nome_file" class="form-control form-control-sm" placeholder="Nome documento" required>
|
||||
</div>
|
||||
<div class="col-md-3">
|
||||
<select name="tipologia" class="form-control form-control-sm" required>
|
||||
<option value="">Tipologia...</option>
|
||||
<option value="documento">Documento</option>
|
||||
<option value="avatar">Avatar</option>
|
||||
<option value="galleria">Galleria</option>
|
||||
<option value="statuto">Statuto</option>
|
||||
<option value="altro">Altro</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="col-md-4">
|
||||
<input type="file" name="file" class="form-control form-control-sm" required>
|
||||
</div>
|
||||
<div class="col-md-2">
|
||||
<button type="submit" class="btn btn-xs btn-success"><i class="fas fa-upload mr-1"></i>Carica</button>
|
||||
<button type="button" class="btn btn-xs btn-secondary" onclick="hideDocumentoForm()"><i class="fas fa-times"></i></button>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
@if($individuo->documenti->count() > 0)
|
||||
<table class="table table-bordered table-hover table-striped mb-0">
|
||||
<thead class="thead-light">
|
||||
<tr>
|
||||
<th>Nome</th>
|
||||
<th style="width: 120px;">Tipologia</th>
|
||||
<th style="width: 80px;">Dimensione</th>
|
||||
<th style="width: 130px;">Data Upload</th>
|
||||
<th style="width: 80px;">Azioni</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
@foreach($individuo->documenti as $documento)
|
||||
<tr>
|
||||
<td>
|
||||
<a href="#" onclick="event.preventDefault(); previewDocumento({{ $documento->id }}, '{{ $documento->mime_type }}');">
|
||||
<i class="fas fa-file text-secondary mr-1"></i>
|
||||
{{ $documento->nome_file }}
|
||||
</a>
|
||||
</td>
|
||||
<td>{{ ucfirst(str_replace('_', ' ', $documento->tipologia)) }}</td>
|
||||
<td>{{ number_format($documento->dimensione / 1024, 1) }} KB</td>
|
||||
<td>{{ $documento->created_at->format('d/m/Y') }}</td>
|
||||
<td>
|
||||
@if($documento->file_path)
|
||||
<button type="button" class="btn btn-xs btn-primary" onclick="previewDocumento({{ $documento->id }}, '{{ $documento->mime_type }}')" title="Anteprima">
|
||||
<i class="fas fa-eye"></i>
|
||||
</button>
|
||||
@endif
|
||||
<form action="/documenti/{{ $documento->id }}" method="POST" class="d-inline">
|
||||
@csrf @method('DELETE')
|
||||
<button type="submit" class="btn btn-xs btn-danger" onclick="return confirm('Eliminare questo documento?')" title="Elimina">
|
||||
<i class="fas fa-trash"></i>
|
||||
</button>
|
||||
</form>
|
||||
</td>
|
||||
</tr>
|
||||
@endforeach
|
||||
</tbody>
|
||||
</table>
|
||||
@else
|
||||
<div class="text-center text-muted py-4">
|
||||
<i class="fas fa-file fa-2x mb-2"></i>
|
||||
<p class="mb-0">Nessun documento</p>
|
||||
</div>
|
||||
@endif
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="mt-3 mb-4">
|
||||
<button type="button" class="btn btn-success btn-lg" id="save-all-btn" onclick="submitMainForm()">
|
||||
<i class="fas fa-save mr-2"></i> Salva Tutte le Modifiche
|
||||
</button>
|
||||
<a href="/individui/{{ $individuo->id }}" class="btn btn-secondary btn-lg ml-2">
|
||||
<i class="fas fa-times mr-1"></i> Annulla
|
||||
</a>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
<div class="card mt-3" id="gruppo-add-card" style="display:none; background-color: #fff3cd; border-color: #ffc107;">
|
||||
<div class="card-header bg-warning">
|
||||
<h3 class="card-title"><i class="fas fa-folder-plus mr-2"></i>Aggiungi a Gruppo</h3>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<div class="row">
|
||||
<div class="col-md-4">
|
||||
<select id="new-gruppo-id" class="form-control form-control-sm">
|
||||
<option value="">Seleziona gruppo...</option>
|
||||
@foreach(\App\Models\Gruppo::orderBy('nome')->get() as $g)
|
||||
@if(!$individuo->gruppi->contains($g->id))
|
||||
<option value="{{ $g->id }}">{{ $g->full_path }}</option>
|
||||
@endif
|
||||
@endforeach
|
||||
</select>
|
||||
</div>
|
||||
<div class="col-md-3">
|
||||
<select id="new-gruppo-ruolo" class="form-control form-control-sm" multiple size="3">
|
||||
@foreach(\App\Models\Ruolo::attive() as $ruolo)
|
||||
<option value="{{ $ruolo->id }}">{{ $ruolo->nome }}</option>
|
||||
@endforeach
|
||||
</select>
|
||||
<small class="text-muted">Ctrl+click per selezionare più ruoli</small>
|
||||
</div>
|
||||
<div class="col-md-3">
|
||||
<input type="date" id="new-gruppo-data" class="form-control form-control-sm">
|
||||
</div>
|
||||
<div class="col-md-2">
|
||||
<button type="button" class="btn btn-success btn-sm" onclick="submitGruppoForm()">
|
||||
<i class="fas fa-check mr-1"></i> Associa
|
||||
</button>
|
||||
<button type="button" class="btn btn-secondary btn-sm" onclick="hideGruppoForm()">
|
||||
<i class="fas fa-times"></i>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="modal fade" id="previewModal" tabindex="-1" role="dialog">
|
||||
<div class="modal-dialog modal-lg" role="document" style="max-width: 90vw;">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h5 class="modal-title"><i class="fas fa-file mr-2"></i><span id="previewModalTitle">Anteprima</span></h5>
|
||||
<button type="button" class="close" data-dismiss="modal" aria-label="Chiudi">
|
||||
<span aria-hidden="true">×</span>
|
||||
</button>
|
||||
</div>
|
||||
<div class="modal-body text-center p-0" style="min-height: 400px;">
|
||||
<iframe id="previewFrame" src="" style="width: 100%; height: 70vh; border: none;"></iframe>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<a id="previewDownloadBtn" href="#" class="btn btn-primary" download>
|
||||
<i class="fas fa-download mr-1"></i> Scarica
|
||||
</a>
|
||||
<button type="button" class="btn btn-secondary" data-dismiss="modal">Chiudi</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@endsection
|
||||
|
||||
@section('scripts')
|
||||
<script>
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
console.log('All form fields:');
|
||||
var form = document.getElementById('main-form');
|
||||
var fields = form.querySelectorAll('input, textarea, select');
|
||||
fields.forEach(function(f) {
|
||||
console.log(f.name + ' [' + f.type + ']: value="' + f.value.substring(0,30) + '" disabled=' + f.disabled);
|
||||
});
|
||||
|
||||
// Debug note field specifically
|
||||
var noteField = form.querySelector('[name="note"]');
|
||||
console.log('Note field found:', !!noteField);
|
||||
if (noteField) {
|
||||
console.log('Note value:', noteField.value);
|
||||
}
|
||||
|
||||
var saveBtn = document.getElementById('save-btn');
|
||||
if (saveBtn) {
|
||||
saveBtn.addEventListener('click', function() {
|
||||
console.log('=== SAVE CLICKED ===');
|
||||
var note = form.querySelector('[name="note"]');
|
||||
console.log('Note at save time:', note ? note.value : 'NOT FOUND');
|
||||
});
|
||||
}
|
||||
|
||||
form.addEventListener('submit', function(e) {
|
||||
console.log('=== FORM SUBMIT ===');
|
||||
var note = form.querySelector('[name="note"]');
|
||||
console.log('Note at submit:', note ? note.value : 'NOT FOUND');
|
||||
|
||||
var contattiFields = form.querySelectorAll('[name^="contatti"]');
|
||||
console.log('Contatti fields count:', contattiFields.length);
|
||||
contattiFields.forEach(function(f, i) {
|
||||
console.log('Contatto field ' + i + ':', f.name, '=', f.value);
|
||||
});
|
||||
|
||||
var formData = new FormData(form);
|
||||
console.log('FormData contatti:', formData.getAll('contatti'));
|
||||
});
|
||||
});
|
||||
</script>
|
||||
<script>
|
||||
function submitMainForm() {
|
||||
var form = document.getElementById('main-form');
|
||||
console.log('=== DEBUG SUBMIT ===');
|
||||
|
||||
var contattiFields = form.querySelectorAll('[name^="contatti"]');
|
||||
console.log('Contatti fields found:', contattiFields.length);
|
||||
contattiFields.forEach(function(f, i) {
|
||||
console.log('Field ' + i + ': name=' + f.name + ', value=' + f.value);
|
||||
});
|
||||
|
||||
form.action = '/individui/{{ $individuo->id }}';
|
||||
var methodInput = form.querySelector('input[name="_method"]');
|
||||
if (!methodInput) {
|
||||
var m = document.createElement('input');
|
||||
m.type = 'hidden';
|
||||
m.name = '_method';
|
||||
m.value = 'PUT';
|
||||
form.appendChild(m);
|
||||
}
|
||||
var csrfInput = form.querySelector('input[name="_token"]');
|
||||
if (!csrfInput) {
|
||||
var c = document.createElement('input');
|
||||
c.type = 'hidden';
|
||||
c.name = '_token';
|
||||
c.value = '{{ csrf_token() }}';
|
||||
form.appendChild(c);
|
||||
}
|
||||
|
||||
var formData = new FormData(form);
|
||||
console.log('FormData contatti keys:');
|
||||
for (var pair of formData.entries()) {
|
||||
if (pair[0].startsWith('contatti')) {
|
||||
console.log(' ' + pair[0] + ' = ' + pair[1]);
|
||||
}
|
||||
}
|
||||
|
||||
form.submit();
|
||||
}
|
||||
|
||||
let contattoIndex = {{ $individuo->contatti->count() }};
|
||||
|
||||
function aggiungiRigaContatto() {
|
||||
const tbody = document.getElementById('contatti-tbody');
|
||||
const noMsg = document.getElementById('no-contatti-msg');
|
||||
if (noMsg) noMsg.style.display = 'none';
|
||||
|
||||
const row = document.createElement('tr');
|
||||
row.innerHTML = `
|
||||
<td>
|
||||
<select name="contatti[${contattoIndex}][tipo]" class="form-control">
|
||||
<option value="">Seleziona...</option>
|
||||
<option value="telefono">Telefono</option>
|
||||
<option value="cellulare">Cellulare</option>
|
||||
<option value="email">Email</option>
|
||||
<option value="fax">Fax</option>
|
||||
<option value="web">Web</option>
|
||||
<option value="telegram">Telegram</option>
|
||||
<option value="whatsapp">WhatsApp</option>
|
||||
<option value="altro">Altro</option>
|
||||
</select>
|
||||
</td>
|
||||
<td><input type="text" name="contatti[${contattoIndex}][valore]" class="form-control" placeholder="Valore"></td>
|
||||
<td><input type="text" name="contatti[${contattoIndex}][etichetta]" class="form-control" placeholder="Etichetta"></td>
|
||||
<td class="text-center"><input type="checkbox" name="contatti[${contattoIndex}][is_primary]" value="1"></td>
|
||||
<td>
|
||||
<button type="button" class="btn btn-success btn-xs" title="Salva" onclick="submitMainForm()">
|
||||
<i class="fas fa-save"></i>
|
||||
</button>
|
||||
<button type="button" class="btn btn-danger btn-xs" title="Elimina" onclick="this.closest('tr').remove()">
|
||||
<i class="fas fa-trash"></i>
|
||||
</button>
|
||||
</td>
|
||||
`;
|
||||
tbody.appendChild(row);
|
||||
contattoIndex++;
|
||||
}
|
||||
|
||||
function showGruppoForm() {
|
||||
var card = document.getElementById('gruppo-add-card');
|
||||
if (card) {
|
||||
card.style.display = 'block';
|
||||
card.scrollIntoView({ behavior: 'smooth', block: 'start' });
|
||||
}
|
||||
}
|
||||
|
||||
function hideGruppoForm() {
|
||||
var card = document.getElementById('gruppo-add-card');
|
||||
if (card) {
|
||||
card.style.display = 'none';
|
||||
}
|
||||
}
|
||||
|
||||
function submitGruppoForm() {
|
||||
var gruppoId = document.getElementById('new-gruppo-id').value;
|
||||
var ruoloSelect = document.getElementById('new-gruppo-ruolo');
|
||||
var dataAdesione = document.getElementById('new-gruppo-data').value;
|
||||
|
||||
if (!gruppoId) {
|
||||
alert('Seleziona un gruppo');
|
||||
return;
|
||||
}
|
||||
|
||||
var formData = new URLSearchParams();
|
||||
formData.append('gruppo_id', gruppoId);
|
||||
|
||||
var selectedRuoli = Array.from(ruoloSelect.selectedOptions).map(opt => opt.value).filter(v => v);
|
||||
selectedRuoli.forEach(function(ruoloId) {
|
||||
formData.append('ruolo_ids[]', ruoloId);
|
||||
});
|
||||
|
||||
formData.append('data_adesione', dataAdesione);
|
||||
|
||||
fetch('/individui/{{ $individuo->id }}/gruppi', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'X-CSRF-TOKEN': '{{ csrf_token() }}',
|
||||
'Accept': 'application/json',
|
||||
'Content-Type': 'application/x-www-form-urlencoded'
|
||||
},
|
||||
body: formData
|
||||
})
|
||||
.then(response => {
|
||||
if (!response.ok) {
|
||||
return response.text().then(text => { throw new Error(text); });
|
||||
}
|
||||
return response.json();
|
||||
})
|
||||
.then(data => {
|
||||
if (data.success) {
|
||||
hideGruppoForm();
|
||||
window.location.reload();
|
||||
} else {
|
||||
alert(data.error || 'Errore');
|
||||
}
|
||||
})
|
||||
.catch(error => {
|
||||
alert('Errore: ' + error.message);
|
||||
});
|
||||
}
|
||||
|
||||
function editGruppoInline(id) {
|
||||
document.getElementById('gruppo-row-' + id).style.display = 'none';
|
||||
document.getElementById('gruppo-edit-' + id).style.display = 'table-row';
|
||||
}
|
||||
|
||||
function cancelEditGruppo(id) {
|
||||
document.getElementById('gruppo-row-' + id).style.display = 'table-row';
|
||||
document.getElementById('gruppo-edit-' + id).style.display = 'none';
|
||||
}
|
||||
|
||||
function showDocumentoForm() {
|
||||
document.getElementById('documento-add-form').style.display = 'block';
|
||||
}
|
||||
|
||||
function hideDocumentoForm() {
|
||||
document.getElementById('documento-add-form').style.display = 'none';
|
||||
}
|
||||
|
||||
function previewDocumento(id, mimeType) {
|
||||
var previewUrl = '/documenti/' + id + '/preview';
|
||||
var downloadUrl = '/documenti/' + id + '/download';
|
||||
|
||||
document.getElementById('previewFrame').src = previewUrl;
|
||||
document.getElementById('previewDownloadBtn').href = downloadUrl;
|
||||
document.getElementById('previewModalTitle').textContent = 'Anteprima';
|
||||
|
||||
if (mimeType && mimeType.startsWith('image/')) {
|
||||
document.getElementById('previewDownloadBtn').style.display = 'inline-block';
|
||||
} else {
|
||||
document.getElementById('previewDownloadBtn').style.display = 'none';
|
||||
}
|
||||
|
||||
$('#previewModal').modal('show');
|
||||
}
|
||||
|
||||
document.getElementById('avatar-input').addEventListener('change', function(e) {
|
||||
var fileName = e.target.files[0]?.name || 'Scegli file...';
|
||||
e.target.nextElementSibling.textContent = fileName;
|
||||
});
|
||||
|
||||
function uploadAvatar() {
|
||||
var input = document.getElementById('avatar-input');
|
||||
if (!input.files[0]) {
|
||||
document.getElementById('avatar-message').innerHTML = '<span class="text-danger">Seleziona un\'immagine</span>';
|
||||
return;
|
||||
}
|
||||
|
||||
var formData = new FormData();
|
||||
formData.append('avatar', input.files[0]);
|
||||
formData.append('_token', '{{ csrf_token() }}');
|
||||
|
||||
document.getElementById('avatar-message').innerHTML = '<span class="text-info"><i class="fas fa-spinner fa-spin"></i> Caricamento...</span>';
|
||||
|
||||
fetch('/individui/{{ $individuo->id }}/avatar', {
|
||||
method: 'POST',
|
||||
body: formData
|
||||
})
|
||||
.then(response => response.json())
|
||||
.then(data => {
|
||||
if (data.success) {
|
||||
document.getElementById('avatar-message').innerHTML = '<span class="text-success">Avatar caricato!</span>';
|
||||
setTimeout(function() { window.location.reload(); }, 500);
|
||||
} else {
|
||||
document.getElementById('avatar-message').innerHTML = '<span class="text-danger">' + (data.message || 'Errore') + '</span>';
|
||||
}
|
||||
})
|
||||
.catch(error => {
|
||||
document.getElementById('avatar-message').innerHTML = '<span class="text-danger">Errore: ' + error + '</span>';
|
||||
});
|
||||
}
|
||||
|
||||
function deleteAvatar() {
|
||||
if (!confirm('Rimuovere l\'avatar?')) return;
|
||||
|
||||
fetch('/individui/{{ $individuo->id }}/avatar', {
|
||||
method: 'DELETE',
|
||||
headers: {
|
||||
'X-CSRF-TOKEN': '{{ csrf_token() }}',
|
||||
'Accept': 'application/json'
|
||||
}
|
||||
})
|
||||
.then(response => response.json())
|
||||
.then(data => {
|
||||
if (data.success) {
|
||||
window.location.reload();
|
||||
} else {
|
||||
alert(data.message || 'Errore');
|
||||
}
|
||||
})
|
||||
.catch(error => {
|
||||
alert('Errore: ' + error);
|
||||
});
|
||||
}
|
||||
</script>
|
||||
@endsection
|
||||
@@ -0,0 +1,78 @@
|
||||
@extends('layouts.adminlte')
|
||||
@section('title', 'Importa Individui')
|
||||
@section('page_title', 'Importa Individui da CSV')
|
||||
|
||||
@section('content')
|
||||
<div class="row">
|
||||
<div class="col-md-8">
|
||||
<div class="card">
|
||||
<div class="card-header">
|
||||
<h3 class="card-title"><i class="fas fa-upload mr-2"></i>Carica file CSV</h3>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<form action="{{ route('individui.import.store') }}" method="POST" enctype="multipart/form-data">
|
||||
@csrf
|
||||
<div class="form-group">
|
||||
<label for="file">Seleziona file CSV *</label>
|
||||
<input type="file" name="file" id="file" class="form-control" accept=".csv,.txt" required>
|
||||
<small class="form-text text-muted">
|
||||
Il file deve essere in formato CSV con separatore virgola.
|
||||
</small>
|
||||
</div>
|
||||
<button type="submit" class="btn btn-primary">
|
||||
<i class="fas fa-upload mr-1"></i> Importa
|
||||
</button>
|
||||
<a href="{{ route('individui.index') }}" class="btn btn-secondary ml-2">
|
||||
Annulla
|
||||
</a>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-4">
|
||||
<div class="card">
|
||||
<div class="card-header bg-info">
|
||||
<h3 class="card-title text-white"><i class="fas fa-download mr-2"></i>Template</h3>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<p>Scarica il template CSV per l'importazione:</p>
|
||||
<a href="{{ route('individui.template') }}" class="btn btn-success btn-block">
|
||||
<i class="fas fa-file-csv mr-1"></i> Scarica Template
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
<div class="card mt-3">
|
||||
<div class="card-header">
|
||||
<h3 class="card-title"><i class="fas fa-info-circle mr-2"></i>Formato CSV</h3>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<h6>Campi obbligatori:</h6>
|
||||
<ul>
|
||||
<li><code>cognome</code></li>
|
||||
<li><code>nome</code></li>
|
||||
</ul>
|
||||
<h6>Campi opzionali:</h6>
|
||||
<ul>
|
||||
<li><code>data_nascita</code> (formato YYYY-MM-DD)</li>
|
||||
<li><code>indirizzo</code></li>
|
||||
<li><code>cap</code></li>
|
||||
<li><code>città</code></li>
|
||||
<li><code>sigla_provincia</code> (2 lettere)</li>
|
||||
<li><code>genere</code> (M o F)</li>
|
||||
<li><code>tipo_documento</code> (carta_identita, patente)</li>
|
||||
<li><code>numero_documento</code></li>
|
||||
<li><code>scadenza_documento</code> (YYYY-MM-DD)</li>
|
||||
<li><code>note</code></li>
|
||||
</ul>
|
||||
<h6>Contatti (fino a 2):</h6>
|
||||
<ul>
|
||||
<li><code>contatto_1_tipo</code> (email, telefono, cellulare)</li>
|
||||
<li><code>contatto_1_valore</code></li>
|
||||
<li><code>contatto_1_etichetta</code> (personale, lavoro)</li>
|
||||
<li><code>contatto_2_*</code> (secondo contatto)</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@endsection
|
||||
@@ -0,0 +1,921 @@
|
||||
@extends('layouts.adminlte')
|
||||
@section('title', 'Individui')
|
||||
@section('page_title', 'Elenco Individui')
|
||||
|
||||
@php
|
||||
$columnLabels = [
|
||||
'codice' => 'Codice',
|
||||
'cognome' => 'Cognome',
|
||||
'nome' => 'Nome',
|
||||
'email' => 'Email',
|
||||
'telefono' => 'Telefono',
|
||||
];
|
||||
$vistaDefaultJson = $vista ? $vista->toJson() : 'null';
|
||||
$allColumnsJson = json_encode($allColumns);
|
||||
$visibleColumnsJson = json_encode($visibleColumns);
|
||||
$canWriteIndividui = Auth::user()->canManage('individui');
|
||||
$canDeleteIndividui = Auth::user()->canDelete('individui');
|
||||
@endphp
|
||||
|
||||
@section('content')
|
||||
<div class="card">
|
||||
<div class="card-header">
|
||||
<h3 class="card-title"><i class="fas fa-users mr-2"></i>Lista Individui</h3>
|
||||
<div class="card-tools">
|
||||
<button type="button" class="btn btn-info btn-sm mr-1" onclick="toggleSearchPanel()">
|
||||
<i class="fas fa-search mr-1"></i> Ricerca/Filtri
|
||||
</button>
|
||||
<div class="btn-group mr-1">
|
||||
<button type="button" class="btn btn-secondary btn-sm dropdown-toggle" data-toggle="dropdown">
|
||||
<i class="fas fa-cog mr-1"></i> Azioni
|
||||
</button>
|
||||
<div class="dropdown-menu">
|
||||
<a class="dropdown-item" href="#" onclick="exportCSV(); return false;">
|
||||
<i class="fas fa-file-csv mr-2"></i>Esporta Tutti CSV
|
||||
</a>
|
||||
<a class="dropdown-item" href="#" onclick="exportSelectedCSV(); return false;">
|
||||
<i class="fas fa-file-csv mr-2"></i>Esporta Selezionati CSV
|
||||
</a>
|
||||
<div class="dropdown-divider"></div>
|
||||
<a class="dropdown-item" href="#" onclick="printSelected(); return false;">
|
||||
<i class="fas fa-print mr-2"></i>Stampa Selezionati
|
||||
</a>
|
||||
<a class="dropdown-item" href="#" onclick="emailSelected(); return false;">
|
||||
<i class="fas fa-envelope mr-2"></i>Email Selezionati
|
||||
</a>
|
||||
@if($canWriteIndividui)
|
||||
<a class="dropdown-item" href="#" onclick="showCreateMailingListModal(); return false;">
|
||||
<i class="fas fa-list mr-2"></i>Crea Mailing List
|
||||
</a>
|
||||
<div class="dropdown-divider"></div>
|
||||
<a class="dropdown-item text-danger" href="#" onclick="deleteSelected(); return false;">
|
||||
<i class="fas fa-trash mr-2"></i>Elimina Selezionati
|
||||
</a>
|
||||
@endif
|
||||
</div>
|
||||
</div>
|
||||
<button type="button" class="btn btn-primary btn-sm" onclick="showSaveVistaModal()">
|
||||
<i class="fas fa-save mr-1"></i> Salva Vista
|
||||
</button>
|
||||
@if($canWriteIndividui)
|
||||
<a href="{{ route('individui.import') }}" class="btn btn-warning btn-sm ml-2">
|
||||
<i class="fas fa-file-import mr-1"></i> Importa
|
||||
</a>
|
||||
<a href="/individui/create" class="btn btn-success btn-sm ml-2">
|
||||
<i class="fas fa-plus mr-1"></i> Nuovo
|
||||
</a>
|
||||
@endif
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="search-panel" class="p-3 bg-light border-bottom" style="display: none;">
|
||||
<div class="row">
|
||||
<div class="col-md-4">
|
||||
<div class="form-group mb-2">
|
||||
<label class="small font-weight-bold">Cerca</label>
|
||||
<input type="text" id="global-search" class="form-control form-control-sm" placeholder="Cerca in tutte le colonne..." oninput="applyGlobalSearch(this.value)">
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-3">
|
||||
<div class="form-group mb-2">
|
||||
<label class="small font-weight-bold">Colonna</label>
|
||||
<select id="filter-column" class="form-control form-control-sm">
|
||||
<option value="">Tutte le colonne</option>
|
||||
@foreach($allColumns as $col)
|
||||
<option value="{{ $col['key'] }}">{{ $col['label'] }}</option>
|
||||
@endforeach
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-2">
|
||||
<div class="form-group mb-2">
|
||||
<label class="small font-weight-bold">Operatore</label>
|
||||
<select id="filter-operator" class="form-control form-control-sm">
|
||||
<option value="contains">Contiene</option>
|
||||
<option value="equals">Uguale a</option>
|
||||
<option value="starts">Inizia con</option>
|
||||
<option value="ends">Finisce con</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-2">
|
||||
<div class="form-group mb-2">
|
||||
<label class="small font-weight-bold">Valore</label>
|
||||
<input type="text" id="filter-value" class="form-control form-control-sm" placeholder="Valore filtro">
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-1">
|
||||
<label class="small"> </label>
|
||||
<div>
|
||||
<button type="button" class="btn btn-xs btn-primary" onclick="applyColumnFilter()">
|
||||
<i class="fas fa-filter"></i>
|
||||
</button>
|
||||
<button type="button" class="btn btn-xs btn-secondary" onclick="clearFilters()">
|
||||
<i class="fas fa-times"></i>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="mt-2">
|
||||
<small class="text-muted">
|
||||
<i class="fas fa-info-circle mr-1"></i>
|
||||
Ordina cliccando sulle intestazioni delle colonne
|
||||
</small>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="card-body p-0">
|
||||
@if($individui->count() > 0)
|
||||
<table class="table table-bordered table-hover table-striped mb-0" id="individui-table">
|
||||
<thead class="thead-light">
|
||||
<tr>
|
||||
<th style="width: 40px;">
|
||||
<div class="custom-control custom-checkbox">
|
||||
<input type="checkbox" class="custom-control-input" id="select-all" onchange="toggleSelectAll(this)">
|
||||
<label class="custom-control-label" for="select-all"></label>
|
||||
</div>
|
||||
</th>
|
||||
@if(in_array('codice', $visibleColumns))
|
||||
<th style="width: 100px;" data-sortable="true" data-column="codice" onclick="sortTable('codice')">Codice <i class="fas fa-sort float-right"></i></th>
|
||||
@endif
|
||||
@if(in_array('cognome', $visibleColumns))
|
||||
<th data-sortable="true" data-column="cognome" onclick="sortTable('cognome')">Cognome <i class="fas fa-sort float-right"></i></th>
|
||||
@endif
|
||||
@if(in_array('nome', $visibleColumns))
|
||||
<th data-sortable="true" data-column="nome" onclick="sortTable('nome')">Nome <i class="fas fa-sort float-right"></i></th>
|
||||
@endif
|
||||
@if(in_array('email', $visibleColumns))
|
||||
<th data-sortable="true" data-column="email" onclick="sortTable('email')">Email <i class="fas fa-sort float-right"></i></th>
|
||||
@endif
|
||||
@if(in_array('telefono', $visibleColumns))
|
||||
<th data-sortable="true" data-column="telefono" onclick="sortTable('telefono')">Telefono <i class="fas fa-sort float-right"></i></th>
|
||||
@endif
|
||||
<th style="width: 120px;">Azioni</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody id="table-body">
|
||||
@foreach($individui as $ind)
|
||||
<tr data-id="{{ $ind->id }}">
|
||||
<td>
|
||||
<div class="custom-control custom-checkbox">
|
||||
<input type="checkbox" class="custom-control-input row-checkbox" id="select-{{ $ind->id }}" value="{{ $ind->id }}">
|
||||
<label class="custom-control-label" for="select-{{ $ind->id }}"></label>
|
||||
</div>
|
||||
</td>
|
||||
@if(in_array('codice', $visibleColumns))
|
||||
<td><span class="badge badge-secondary">{{ $ind->codice_id }}</span></td>
|
||||
@endif
|
||||
@if(in_array('cognome', $visibleColumns))
|
||||
<td class="font-weight-bold">
|
||||
<a href="{{ url('/individui/' . $ind->id . '/edit') }}">{{ $ind->cognome }}</a>
|
||||
</td>
|
||||
@endif
|
||||
@if(in_array('nome', $visibleColumns))
|
||||
<td>
|
||||
<a href="{{ url('/individui/' . $ind->id . '/edit') }}">{{ $ind->nome }}</a>
|
||||
</td>
|
||||
@endif
|
||||
@if(in_array('email', $visibleColumns))
|
||||
<td>{{ $ind->getEmailPrimariaAttribute() ?: '-' }}</td>
|
||||
@endif
|
||||
@if(in_array('telefono', $visibleColumns))
|
||||
<td>{{ $ind->getTelefonoPrimarioAttribute() ?: '-' }}</td>
|
||||
@endif
|
||||
<td>
|
||||
@if($canWriteIndividui)
|
||||
<a href="{{ url('/individui/' . $ind->id . '/edit') }}" class="btn btn-xs btn-warning" title="Modifica">
|
||||
<i class="fas fa-edit"></i>
|
||||
</a>
|
||||
@endif
|
||||
@if($canDeleteIndividui)
|
||||
<button type="button" class="btn btn-xs btn-danger" onclick="showDeleteModal({{ $ind->id }}, '{{ $ind->cognome }} {{ $ind->nome }}')" title="Elimina">
|
||||
<i class="fas fa-trash"></i>
|
||||
</button>
|
||||
@endif
|
||||
<button type="button" class="btn btn-xs btn-secondary" onclick="printIndividuo({{ $ind->id }})" title="Stampa">
|
||||
<i class="fas fa-print"></i>
|
||||
</button>
|
||||
<button type="button" class="btn btn-xs btn-primary" onclick="exportIndividuoCSV({{ $ind->id }})" title="Esporta CSV">
|
||||
<i class="fas fa-download"></i>
|
||||
</button>
|
||||
<button type="button" class="btn btn-xs btn-info" onclick="sendEmail({{ $ind->id }})" title="Invia Email">
|
||||
<i class="fas fa-envelope"></i>
|
||||
</button>
|
||||
<button type="button" class="btn btn-xs btn-dark" onclick="generateReport({{ $ind->id }})" title="Genera Report">
|
||||
<i class="fas fa-chart-bar"></i>
|
||||
</button>
|
||||
</td>
|
||||
</tr>
|
||||
@endforeach
|
||||
</tbody>
|
||||
</table>
|
||||
@else
|
||||
<div class="text-center text-muted py-5">
|
||||
<i class="fas fa-users fa-3x mb-3"></i>
|
||||
<p>Nessun individuo presente</p>
|
||||
@if($canWriteIndividui)
|
||||
<a href="/individui/create" class="btn btn-success">Crea il primo individuo</a>
|
||||
@endif
|
||||
</div>
|
||||
@endif
|
||||
</div>
|
||||
@if($individui->count() > 0)
|
||||
<div class="card-footer">
|
||||
{{ $individui->links() }}
|
||||
</div>
|
||||
@endif
|
||||
</div>
|
||||
|
||||
<div class="modal fade" id="saveVistaModal" tabindex="-1" role="dialog">
|
||||
<div class="modal-dialog" role="document">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h5 class="modal-title"><i class="fas fa-save mr-2"></i>Salva Vista</h5>
|
||||
<button type="button" class="close" data-dismiss="modal">
|
||||
<span>×</span>
|
||||
</button>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<div class="form-group">
|
||||
<label>Nome Vista *</label>
|
||||
<input type="text" id="vista-nome" class="form-control" placeholder="Es. Elenco soci attivi">
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label>Colonne visibili</label>
|
||||
<div class="row" id="colonne-checkboxes">
|
||||
@foreach($allColumns as $col)
|
||||
<div class="col-md-6">
|
||||
<div class="custom-control custom-checkbox">
|
||||
<input type="checkbox" class="custom-control-input col-visibile" id="col-vis-{{ $col['key'] }}" value="{{ $col['key'] }}" {{ in_array($col['key'], $visibleColumns) ? 'checked' : '' }}>
|
||||
<label class="custom-control-label" for="col-vis-{{ $col['key'] }}">{{ $col['label'] }}</label>
|
||||
</div>
|
||||
</div>
|
||||
@endforeach
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<div class="custom-control custom-checkbox">
|
||||
<input type="checkbox" class="custom-control-input" id="vista-default" value="true">
|
||||
<label class="custom-control-label" for="vista-default">Imposta come vista predefinita</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-secondary" data-dismiss="modal">Annulla</button>
|
||||
<button type="button" class="btn btn-primary" onclick="saveVista()">Salva</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="modal fade" id="colonneModal" tabindex="-1" role="dialog">
|
||||
<div class="modal-dialog" role="document">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h5 class="modal-title"><i class="fas fa-columns mr-2"></i>Colonne Visibili</h5>
|
||||
<button type="button" class="close" data-dismiss="modal">
|
||||
<span>×</span>
|
||||
</button>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<div class="row">
|
||||
@foreach($allColumns as $col)
|
||||
<div class="col-md-6">
|
||||
<div class="custom-control custom-checkbox">
|
||||
<input type="checkbox" class="custom-control-input col-toggle" id="col-toggle-{{ $col['key'] }}" value="{{ $col['key'] }}" {{ in_array($col['key'], $visibleColumns) ? 'checked' : '' }} onchange="toggleColumn('{{ $col['key'] }}', this.checked)">
|
||||
<label class="custom-control-label" for="col-toggle-{{ $col['key'] }}">{{ $col['label'] }}</label>
|
||||
</div>
|
||||
</div>
|
||||
@endforeach
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-secondary" data-dismiss="modal">Chiudi</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="modal fade" id="deleteModal" tabindex="-1" role="dialog">
|
||||
@if($canDeleteIndividui)
|
||||
<div class="modal-dialog" role="document">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header bg-danger">
|
||||
<h5 class="modal-title text-white"><i class="fas fa-trash-alt mr-2"></i>Conferma Eliminazione</h5>
|
||||
<button type="button" class="close text-white" data-dismiss="modal">
|
||||
<span>×</span>
|
||||
</button>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<p>Stai per eliminare l'individuo: <strong id="delete-individuo-name"></strong></p>
|
||||
<p class="text-muted">Seleziona gli elementi collegati da eliminare:</p>
|
||||
<div class="ml-3">
|
||||
<div class="custom-control custom-checkbox">
|
||||
<input type="checkbox" class="custom-control-input" id="delete-contatti" checked>
|
||||
<label class="custom-control-label" for="delete-contatti">Contatti (<span id="count-contatti">0</span>)</label>
|
||||
</div>
|
||||
<div class="custom-control custom-checkbox">
|
||||
<input type="checkbox" class="custom-control-input" id="delete-gruppi">
|
||||
<label class="custom-control-label" for="delete-gruppi">Gruppi (<span id="count-gruppi">0</span>) - solo scollegamento</label>
|
||||
</div>
|
||||
<div class="custom-control custom-checkbox">
|
||||
<input type="checkbox" class="custom-control-input" id="delete-documenti" checked>
|
||||
<label class="custom-control-label" for="delete-documenti">Documenti (<span id="count-documenti">0</span>)</label>
|
||||
</div>
|
||||
<div class="custom-control custom-checkbox">
|
||||
<input type="checkbox" class="custom-control-input" id="delete-eventi">
|
||||
<label class="custom-control-label" for="delete-eventi">Eventi (<span id="count-eventi">0</span>) - solo scollegamento</label>
|
||||
</div>
|
||||
</div>
|
||||
<div class="alert alert-warning mt-3 mb-0">
|
||||
<small><i class="fas fa-info-circle mr-1"></i>Se non selezionato, l'elemento verrà scollegato ma non eliminato.</small>
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-secondary" data-dismiss="modal">Annulla</button>
|
||||
<button type="button" class="btn btn-danger" id="confirm-delete-btn" onclick="confirmDelete()">Elimina</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@endif
|
||||
</div>
|
||||
|
||||
<div class="modal fade" id="createMailingListModal" tabindex="-1" role="dialog">
|
||||
<div class="modal-dialog modal-lg" role="document">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header bg-info">
|
||||
<h5 class="modal-title text-white"><i class="fas fa-list mr-2"></i>Crea Mailing List</h5>
|
||||
<button type="button" class="close text-white" data-dismiss="modal">
|
||||
<span>×</span>
|
||||
</button>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<div class="form-group">
|
||||
<label>Nome Lista *</label>
|
||||
<input type="text" id="ml-nome" class="form-control" placeholder="Es. Newsletter Maggio 2026">
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label>Descrizione</label>
|
||||
<textarea id="ml-descrizione" class="form-control" rows="2" placeholder="Descrizione opzionale"></textarea>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label>Contatti inclusi (<span id="ml-count">0</span>)</label>
|
||||
<div class="table-responsive" style="max-height: 300px; overflow-y: auto;">
|
||||
<table class="table table-bordered table-sm" id="ml-contatti-table">
|
||||
<thead class="thead-light">
|
||||
<tr>
|
||||
<th style="width: 40px;">
|
||||
<div class="custom-control custom-checkbox">
|
||||
<input type="checkbox" class="custom-control-input" id="ml-select-all" onchange="toggleMlSelectAll(this)">
|
||||
<label class="custom-control-label" for="ml-select-all"></label>
|
||||
</div>
|
||||
</th>
|
||||
<th>Codice</th>
|
||||
<th>Cognome</th>
|
||||
<th>Nome</th>
|
||||
<th>Email</th>
|
||||
<th style="width: 50px;"></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody id="ml-contatti-tbody">
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-secondary" data-dismiss="modal">Annulla</button>
|
||||
<button type="button" class="btn btn-success" onclick="createMailingList()">
|
||||
<i class="fas fa-save mr-1"></i> Salva Lista
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@endsection
|
||||
|
||||
@section('scripts')
|
||||
<script>
|
||||
let currentSort = { column: null, direction: 'asc' };
|
||||
let columnVisibility = {!! json_encode(array_column($allColumns, 'key')) !!};
|
||||
|
||||
function toggleSearchPanel() {
|
||||
const panel = document.getElementById('search-panel');
|
||||
panel.style.display = panel.style.display === 'none' ? 'block' : 'none';
|
||||
}
|
||||
|
||||
function applyGlobalSearch(value) {
|
||||
const rows = document.querySelectorAll('#table-body tr');
|
||||
const searchTerm = value.toLowerCase();
|
||||
rows.forEach(row => {
|
||||
const text = row.textContent.toLowerCase();
|
||||
row.style.display = text.includes(searchTerm) ? '' : 'none';
|
||||
});
|
||||
}
|
||||
|
||||
function applyColumnFilter() {
|
||||
const col = document.getElementById('filter-column').value;
|
||||
const op = document.getElementById('filter-operator').value;
|
||||
const val = document.getElementById('filter-value').value.toLowerCase();
|
||||
|
||||
if (!col || !val) return;
|
||||
|
||||
const rows = document.querySelectorAll('#table-body tr');
|
||||
const colIndex = { codice: 1, cognome: 2, nome: 3, email: 4, telefono: 5, azioni: 6 }[col];
|
||||
|
||||
rows.forEach(row => {
|
||||
const cells = row.querySelectorAll('td');
|
||||
const cellText = cells[colIndex]?.textContent.toLowerCase() || '';
|
||||
let matches = false;
|
||||
|
||||
switch(op) {
|
||||
case 'contains': matches = cellText.includes(val); break;
|
||||
case 'equals': matches = cellText === val; break;
|
||||
case 'starts': matches = cellText.startsWith(val); break;
|
||||
case 'ends': matches = cellText.endsWith(val); break;
|
||||
}
|
||||
|
||||
row.style.display = matches ? '' : 'none';
|
||||
});
|
||||
}
|
||||
|
||||
function clearFilters() {
|
||||
document.getElementById('global-search').value = '';
|
||||
document.getElementById('filter-column').value = '';
|
||||
document.getElementById('filter-value').value = '';
|
||||
document.querySelectorAll('#table-body tr').forEach(row => row.style.display = '');
|
||||
}
|
||||
|
||||
function sortTable(column) {
|
||||
if (currentSort.column === column) {
|
||||
currentSort.direction = currentSort.direction === 'asc' ? 'desc' : 'asc';
|
||||
} else {
|
||||
currentSort = { column: column, direction: 'asc' };
|
||||
}
|
||||
|
||||
const colIndex = { codice: 0, cognome: 1, nome: 2, email: 3, telefono: 4, azioni: 5 }[column];
|
||||
const tbody = document.getElementById('table-body');
|
||||
const rows = Array.from(tbody.querySelectorAll('tr'));
|
||||
|
||||
rows.sort((a, b) => {
|
||||
const aVal = a.querySelectorAll('td')[colIndex]?.textContent.trim() || '';
|
||||
const bVal = b.querySelectorAll('td')[colIndex]?.textContent.trim() || '';
|
||||
const cmp = aVal.localeCompare(bVal, undefined, { numeric: true });
|
||||
return currentSort.direction === 'asc' ? cmp : -cmp;
|
||||
});
|
||||
|
||||
rows.forEach(row => tbody.appendChild(row));
|
||||
|
||||
document.querySelectorAll('th[data-sortable]').forEach(th => {
|
||||
const icon = th.querySelector('i');
|
||||
if (th.dataset.column === column) {
|
||||
icon.className = currentSort.direction === 'asc' ? 'fas fa-sort-up float-right' : 'fas fa-sort-down float-right';
|
||||
} else {
|
||||
icon.className = 'fas fa-sort float-right';
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function toggleColumn(column, visible) {
|
||||
const colIndex = { codice: 1, cognome: 2, nome: 3, email: 4, telefono: 5, azioni: 6 }[column];
|
||||
const ths = document.querySelectorAll('#individui-table thead th');
|
||||
const rows = document.querySelectorAll('#table-body tr');
|
||||
|
||||
if (visible) {
|
||||
ths[colIndex].style.display = '';
|
||||
rows.forEach(row => {
|
||||
row.querySelectorAll('td')[colIndex].style.display = '';
|
||||
});
|
||||
} else {
|
||||
ths[colIndex].style.display = 'none';
|
||||
rows.forEach(row => {
|
||||
row.querySelectorAll('td')[colIndex].style.display = 'none';
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
async function saveVista() {
|
||||
let nome = document.getElementById('vista-nome').value.trim();
|
||||
if (!nome) {
|
||||
const now = new Date();
|
||||
nome = 'Vista ' + now.toLocaleDateString('it-IT') + ' ' + now.toLocaleTimeString('it-IT', { hour: '2-digit', minute: '2-digit' });
|
||||
}
|
||||
|
||||
const colonneVisibili = Array.from(document.querySelectorAll('#colonne-checkboxes input:checked')).map(i => i.value);
|
||||
const isDefault = document.getElementById('vista-default').checked;
|
||||
|
||||
const data = {
|
||||
nome: nome,
|
||||
tipo: 'individui',
|
||||
colonne_visibili: colonneVisibili,
|
||||
colonne_ordinamento: currentSort.column ? [[currentSort.column, currentSort.direction]] : [],
|
||||
ricerca: document.getElementById('global-search').value,
|
||||
is_default: isDefault
|
||||
};
|
||||
|
||||
try {
|
||||
const response = await fetch('/viste', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'X-CSRF-TOKEN': document.querySelector('meta[name="csrf-token"]')?.content || ''
|
||||
},
|
||||
body: JSON.stringify(data)
|
||||
});
|
||||
|
||||
if (response.ok) {
|
||||
alert('Vista salvata!');
|
||||
$('#saveVistaModal').modal('hide');
|
||||
} else {
|
||||
alert('Errore salvataggio');
|
||||
}
|
||||
} catch(e) {
|
||||
alert('Errore: ' + e.message);
|
||||
}
|
||||
}
|
||||
|
||||
function toggleSelectAll(source) {
|
||||
const checkboxes = document.querySelectorAll('.row-checkbox');
|
||||
checkboxes.forEach(cb => {
|
||||
cb.checked = source.checked;
|
||||
});
|
||||
}
|
||||
|
||||
function exportCSV() {
|
||||
window.location.href = '/individui/export';
|
||||
}
|
||||
|
||||
function exportSelectedCSV() {
|
||||
const selectedIds = getSelectedIds();
|
||||
if (selectedIds.length === 0) {
|
||||
alert('Seleziona almeno un individuo');
|
||||
return;
|
||||
}
|
||||
|
||||
const params = new URLSearchParams();
|
||||
selectedIds.forEach(function(id) {
|
||||
params.append('ids[]', id);
|
||||
});
|
||||
|
||||
window.location.href = '/individui/export?' + params.toString();
|
||||
}
|
||||
|
||||
let deleteIndividuoId = null;
|
||||
|
||||
async function showDeleteModal(id, nome) {
|
||||
deleteIndividuoId = id;
|
||||
document.getElementById('delete-individuo-name').textContent = nome;
|
||||
|
||||
document.getElementById('count-contatti').textContent = '...';
|
||||
document.getElementById('count-gruppi').textContent = '...';
|
||||
document.getElementById('count-documenti').textContent = '...';
|
||||
document.getElementById('count-eventi').textContent = '...';
|
||||
|
||||
try {
|
||||
const response = await fetch(`/individui/${id}/collegati`);
|
||||
const data = await response.json();
|
||||
document.getElementById('count-contatti').textContent = data.contatti;
|
||||
document.getElementById('count-gruppi').textContent = data.gruppi;
|
||||
document.getElementById('count-documenti').textContent = data.documenti;
|
||||
document.getElementById('count-eventi').textContent = data.eventi;
|
||||
} catch(e) {
|
||||
console.error('Errore recupero dati:', e);
|
||||
}
|
||||
|
||||
$('#deleteModal').modal('show');
|
||||
}
|
||||
|
||||
function confirmDelete() {
|
||||
const eliminaContatti = document.getElementById('delete-contatti').checked;
|
||||
const eliminaDocumenti = document.getElementById('delete-documenti').checked;
|
||||
|
||||
const form = document.createElement('form');
|
||||
form.method = 'POST';
|
||||
form.action = `/individui/${deleteIndividuoId}/elimina`;
|
||||
|
||||
const csrfToken = document.querySelector('meta[name="csrf-token"]')?.content || '{{ csrf_token() }}';
|
||||
form.innerHTML = `
|
||||
<input type="hidden" name="_token" value="${csrfToken}">
|
||||
<input type="hidden" name="elimina_contatti" value="${eliminaContatti}">
|
||||
<input type="hidden" name="elimina_documenti" value="${eliminaDocumenti}">
|
||||
`;
|
||||
|
||||
document.body.appendChild(form);
|
||||
form.submit();
|
||||
}
|
||||
|
||||
function printIndividuo(id) {
|
||||
window.open('/individui/' + id + '/report', '_blank');
|
||||
}
|
||||
|
||||
function exportIndividuoCSV(id) {
|
||||
const row = document.querySelector(`tr[data-id="${id}"]`);
|
||||
if (!row) return;
|
||||
|
||||
const cells = Array.from(row.querySelectorAll('td'));
|
||||
const data = {
|
||||
codice: cells[1].textContent.trim(),
|
||||
cognome: cells[2].textContent.trim(),
|
||||
nome: cells[3].textContent.trim(),
|
||||
email: cells[4].textContent.trim(),
|
||||
telefono: cells[5].textContent.trim()
|
||||
};
|
||||
|
||||
const csv = `Codice,Cognome,Nome,Email,Telefono\n"${data.codice}","${data.cognome}","${data.nome}","${data.email}","${data.telefono}"`;
|
||||
const blob = new Blob([csv], { type: 'text/csv' });
|
||||
const url = URL.createObjectURL(blob);
|
||||
const a = document.createElement('a');
|
||||
a.href = url;
|
||||
a.download = `individuo_${data.codice}.csv`;
|
||||
a.click();
|
||||
}
|
||||
|
||||
function sendEmail(id) {
|
||||
window.location.href = `/mailing/nuovo?individui=${id}`;
|
||||
}
|
||||
|
||||
function generateReport(id) {
|
||||
window.open('/individui/' + id + '/report', '_blank');
|
||||
}
|
||||
|
||||
function getSelectedIds() {
|
||||
return Array.from(document.querySelectorAll('.row-checkbox:checked')).map(cb => cb.value);
|
||||
}
|
||||
|
||||
function exportSelectedCSV() {
|
||||
const selectedIds = getSelectedIds();
|
||||
if (selectedIds.length === 0) {
|
||||
alert('Seleziona almeno un individuo');
|
||||
return;
|
||||
}
|
||||
|
||||
const rows = [];
|
||||
rows.push('Codice,Cognome,Nome,Email,Telefono');
|
||||
|
||||
document.querySelectorAll('#table-body tr').forEach(row => {
|
||||
const checkbox = row.querySelector('.row-checkbox');
|
||||
if (checkbox && checkbox.checked) {
|
||||
const cells = Array.from(row.querySelectorAll('td'));
|
||||
const codice = cells[1].textContent.trim();
|
||||
const cognome = cells[2].textContent.trim();
|
||||
const nome = cells[3].textContent.trim();
|
||||
const email = cells[4].textContent.trim();
|
||||
const telefono = cells[5].textContent.trim();
|
||||
rows.push(`"${codice}","${cognome}","${nome}","${email}","${telefono}"`);
|
||||
}
|
||||
});
|
||||
|
||||
const blob = new Blob([rows.join('\n')], { type: 'text/csv' });
|
||||
const url = URL.createObjectURL(blob);
|
||||
const a = document.createElement('a');
|
||||
a.href = url;
|
||||
a.download = `individui_selezionati_${new Date().toISOString().slice(0,10)}.csv`;
|
||||
a.click();
|
||||
}
|
||||
|
||||
function printSelected() {
|
||||
const selectedIds = getSelectedIds();
|
||||
if (selectedIds.length === 0) {
|
||||
alert('Seleziona almeno un individuo');
|
||||
return;
|
||||
}
|
||||
window.open(`/individui/report/stampa?ids=${selectedIds.join(',')}`, '_blank');
|
||||
}
|
||||
|
||||
function emailSelected() {
|
||||
const selectedIds = getSelectedIds();
|
||||
if (selectedIds.length === 0) {
|
||||
alert('Seleziona almeno un individuo');
|
||||
return;
|
||||
}
|
||||
window.location.href = `/mailing/nuovo?individui=${selectedIds.join(',')}`;
|
||||
}
|
||||
|
||||
function deleteSelected() {
|
||||
const selectedIds = getSelectedIds();
|
||||
if (selectedIds.length === 0) {
|
||||
alert('Seleziona almeno un individuo');
|
||||
return;
|
||||
}
|
||||
|
||||
document.getElementById('delete-individuo-name').textContent = selectedIds.length + ' individui selezionati';
|
||||
document.getElementById('count-contatti').textContent = '...';
|
||||
document.getElementById('count-gruppi').textContent = '...';
|
||||
document.getElementById('count-documenti').textContent = '...';
|
||||
document.getElementById('count-eventi').textContent = '...';
|
||||
|
||||
document.getElementById('confirm-delete-btn').onclick = function() {
|
||||
const eliminaContatti = document.getElementById('delete-contatti').checked;
|
||||
const eliminaDocumenti = document.getElementById('delete-documenti').checked;
|
||||
|
||||
const form = document.createElement('form');
|
||||
form.method = 'POST';
|
||||
form.action = '/individui/mass-elimina';
|
||||
|
||||
const csrfToken = document.querySelector('meta[name="csrf-token"]')?.content || '{{ csrf_token() }}';
|
||||
|
||||
let html = `<input type="hidden" name="_token" value="${csrfToken}">`;
|
||||
html += `<input type="hidden" name="elimina_contatti" value="${eliminaContatti}">`;
|
||||
html += `<input type="hidden" name="elimina_documenti" value="${eliminaDocumenti}">`;
|
||||
|
||||
selectedIds.forEach(function(id) {
|
||||
html += `<input type="hidden" name="ids[]" value="${id}">`;
|
||||
});
|
||||
|
||||
form.innerHTML = html;
|
||||
document.body.appendChild(form);
|
||||
form.submit();
|
||||
};
|
||||
|
||||
$('#deleteModal').modal('show');
|
||||
}
|
||||
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
const csrfToken = document.querySelector('meta[name="csrf-token"]');
|
||||
if (!csrfToken) {
|
||||
const meta = document.createElement('meta');
|
||||
meta.name = 'csrf-token';
|
||||
meta.content = '{{ csrf_token() }}';
|
||||
document.head.appendChild(meta);
|
||||
}
|
||||
|
||||
const visibleCols = {!! $visibleColumnsJson !!};
|
||||
const allCols = {!! $allColumnsJson !!};
|
||||
|
||||
allCols.forEach(function(col) {
|
||||
const visibile = visibleCols.includes(col.key);
|
||||
const saveCheckbox = document.querySelector(`#col-vis-${col.key}`);
|
||||
const toggleCheckbox = document.querySelector(`#col-toggle-${col.key}`);
|
||||
if (saveCheckbox) saveCheckbox.checked = visibile;
|
||||
if (toggleCheckbox) toggleCheckbox.checked = visibile;
|
||||
});
|
||||
|
||||
const vistaDefault = {!! $vistaDefaultJson !!};
|
||||
if (vistaDefault && vistaDefault.ricerca) {
|
||||
document.getElementById('global-search').value = vistaDefault.ricerca;
|
||||
applyGlobalSearch(vistaDefault.ricerca);
|
||||
}
|
||||
});
|
||||
|
||||
function showCreateMailingListModal() {
|
||||
document.getElementById('ml-nome').value = '';
|
||||
document.getElementById('ml-descrizione').value = '';
|
||||
document.getElementById('ml-contatti-tbody').innerHTML = '';
|
||||
document.getElementById('ml-count').textContent = '0';
|
||||
|
||||
const selectedIds = getSelectedIds();
|
||||
console.log('Selected IDs:', selectedIds);
|
||||
if (selectedIds.length > 0) {
|
||||
const url = '/individui/email-list?ids=' + selectedIds.join(',');
|
||||
console.log('Fetching URL:', url);
|
||||
fetch(url, {
|
||||
credentials: 'include',
|
||||
headers: {
|
||||
'Accept': 'application/json',
|
||||
'X-Requested-With': 'XMLHttpRequest'
|
||||
}
|
||||
})
|
||||
.then(response => {
|
||||
console.log('Response status:', response.status);
|
||||
console.log('Response type:', response.headers.get('content-type'));
|
||||
if (!response.ok) {
|
||||
return response.text().then(text => {
|
||||
console.log('Full error response:', text.substring(0, 500));
|
||||
const contentType = response.headers.get('content-type');
|
||||
if (contentType && contentType.includes('application/json')) {
|
||||
const json = JSON.parse(text);
|
||||
throw new Error(json.message || 'Errore HTTP ' + response.status);
|
||||
} else {
|
||||
throw new Error('Server returned HTML (status ' + response.status + ') - probabilmente sessione scaduta. Ricarica la pagina.');
|
||||
}
|
||||
});
|
||||
}
|
||||
return response.json();
|
||||
})
|
||||
.then(data => {
|
||||
console.log('Data:', data);
|
||||
updateMlTable(data);
|
||||
})
|
||||
.catch(function(err) {
|
||||
console.error('Error:', err);
|
||||
alert('Errore caricamento contatti: ' + err.message + '\nURL: ' + url);
|
||||
});
|
||||
} else {
|
||||
alert('Seleziona almeno un individuo dalla tabella');
|
||||
}
|
||||
|
||||
$('#createMailingListModal').modal('show');
|
||||
}
|
||||
|
||||
function updateMlTable(data) {
|
||||
const tbody = document.getElementById('ml-contatti-tbody');
|
||||
const existingIds = new Set();
|
||||
|
||||
document.querySelectorAll('.ml-row-checkbox').forEach(function(cb) {
|
||||
existingIds.add(parseInt(cb.value));
|
||||
});
|
||||
|
||||
data.forEach(function(item) {
|
||||
if (existingIds.has(item.id)) return;
|
||||
|
||||
const tr = document.createElement('tr');
|
||||
const checked = item.email_count === 1 ? 'checked' : '';
|
||||
tr.innerHTML = `
|
||||
<td>
|
||||
<div class="custom-control custom-checkbox">
|
||||
<input type="checkbox" class="custom-control-input ml-row-checkbox" id="ml-${item.id}" value="${item.id}" data-email="${item.email || ''}" ${checked}>
|
||||
<label class="custom-control-label" for="ml-${item.id}"></label>
|
||||
</div>
|
||||
</td>
|
||||
<td><span class="badge badge-secondary">${item.codice_id}</span></td>
|
||||
<td>${item.cognome}</td>
|
||||
<td>${item.nome}</td>
|
||||
<td>
|
||||
${item.email_count === 1 ? (item.email || '-') :
|
||||
'<select class="form-control form-control-sm" onchange="updateMlCheckboxEmail(' + item.id + ', this.value)">' +
|
||||
'<option value="">Seleziona email...</option>' +
|
||||
(item.emails || []).map(function(e) { return '<option value="' + e + '">' + e + '</option>'; }).join('') +
|
||||
'</select>'}
|
||||
</td>
|
||||
<td>
|
||||
<button type="button" class="btn btn-xs btn-danger" onclick="this.closest('tr').remove(); updateMlCount();">
|
||||
<i class="fas fa-times"></i>
|
||||
</button>
|
||||
</td>
|
||||
`;
|
||||
tbody.appendChild(tr);
|
||||
});
|
||||
|
||||
updateMlCount();
|
||||
}
|
||||
|
||||
function updateMlCount() {
|
||||
document.getElementById('ml-count').textContent = document.querySelectorAll('.ml-row-checkbox').length;
|
||||
}
|
||||
|
||||
function toggleMlSelectAll(source) {
|
||||
document.querySelectorAll('.ml-row-checkbox').forEach(function(cb) {
|
||||
cb.checked = source.checked;
|
||||
});
|
||||
}
|
||||
|
||||
function updateMlCheckboxEmail(id, email) {
|
||||
const checkbox = document.getElementById('ml-' + id);
|
||||
if (checkbox) {
|
||||
checkbox.dataset.email = email;
|
||||
checkbox.checked = email !== '';
|
||||
}
|
||||
}
|
||||
|
||||
async function createMailingList() {
|
||||
const nome = document.getElementById('ml-nome').value.trim();
|
||||
if (!nome) {
|
||||
alert('Inserisci un nome per la lista');
|
||||
return;
|
||||
}
|
||||
|
||||
const selected = [];
|
||||
document.querySelectorAll('.ml-row-checkbox:checked').forEach(function(cb) {
|
||||
if (cb.dataset.email) {
|
||||
selected.push({
|
||||
individuo_id: parseInt(cb.value),
|
||||
email: cb.dataset.email
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
if (selected.length === 0) {
|
||||
alert('Seleziona almeno un contatto con email');
|
||||
return;
|
||||
}
|
||||
|
||||
const data = {
|
||||
nome: nome,
|
||||
descrizione: document.getElementById('ml-descrizione').value.trim(),
|
||||
contatti: selected
|
||||
};
|
||||
|
||||
try {
|
||||
const response = await fetch('/mailing-liste/create-from-individui', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'X-CSRF-TOKEN': document.querySelector('meta[name="csrf-token"]')?.content || ''
|
||||
},
|
||||
body: JSON.stringify(data)
|
||||
});
|
||||
|
||||
if (response.ok) {
|
||||
alert('Mailing list creata con successo!');
|
||||
$('#createMailingListModal').modal('hide');
|
||||
} else {
|
||||
alert('Errore nella creazione della lista');
|
||||
}
|
||||
} catch(e) {
|
||||
alert('Errore: ' + e.message);
|
||||
}
|
||||
}
|
||||
</script>
|
||||
@endsection
|
||||
@@ -0,0 +1,301 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="it">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Report Individui Multipli - {{ now()->format('d/m/Y') }}</title>
|
||||
<style>
|
||||
* {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
body {
|
||||
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
|
||||
font-size: 12px;
|
||||
line-height: 1.4;
|
||||
color: #333;
|
||||
padding: 20px;
|
||||
}
|
||||
.header {
|
||||
text-align: center;
|
||||
margin-bottom: 20px;
|
||||
padding-bottom: 15px;
|
||||
border-bottom: 2px solid #2c3e50;
|
||||
}
|
||||
.header h1 {
|
||||
font-size: 24px;
|
||||
color: #2c3e50;
|
||||
margin-bottom: 5px;
|
||||
}
|
||||
.header .subtitle {
|
||||
font-size: 14px;
|
||||
color: #7f8c8d;
|
||||
}
|
||||
.individuo-block {
|
||||
margin-bottom: 30px;
|
||||
page-break-after: always;
|
||||
}
|
||||
.individuo-block:last-child {
|
||||
page-break-after: avoid;
|
||||
}
|
||||
.section {
|
||||
margin-bottom: 15px;
|
||||
page-break-inside: avoid;
|
||||
}
|
||||
.section-title {
|
||||
font-size: 14px;
|
||||
font-weight: bold;
|
||||
color: #fff;
|
||||
background: #2c3e50;
|
||||
padding: 8px 12px;
|
||||
margin-bottom: 8px;
|
||||
border-radius: 3px;
|
||||
}
|
||||
table {
|
||||
width: 100%;
|
||||
border-collapse: collapse;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
th, td {
|
||||
border: 1px solid #ddd;
|
||||
padding: 6px 8px;
|
||||
text-align: left;
|
||||
}
|
||||
th {
|
||||
background: #f8f9fa;
|
||||
font-weight: bold;
|
||||
width: 150px;
|
||||
}
|
||||
.badge {
|
||||
display: inline-block;
|
||||
padding: 3px 8px;
|
||||
background: #6c757d;
|
||||
color: #fff;
|
||||
border-radius: 3px;
|
||||
font-size: 11px;
|
||||
}
|
||||
.badge-success { background: #28a745; }
|
||||
.badge-warning { background: #ffc107; color: #333; }
|
||||
.badge-danger { background: #dc3545; }
|
||||
.badge-info { background: #17a2b8; }
|
||||
.info-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(2, 1fr);
|
||||
gap: 8px;
|
||||
}
|
||||
.info-item {
|
||||
border: 1px solid #ddd;
|
||||
padding: 8px;
|
||||
}
|
||||
.info-item label {
|
||||
display: block;
|
||||
font-weight: bold;
|
||||
color: #666;
|
||||
font-size: 10px;
|
||||
text-transform: uppercase;
|
||||
margin-bottom: 2px;
|
||||
}
|
||||
.empty {
|
||||
color: #999;
|
||||
font-style: italic;
|
||||
}
|
||||
.footer {
|
||||
margin-top: 30px;
|
||||
padding-top: 15px;
|
||||
border-top: 1px solid #ddd;
|
||||
text-align: center;
|
||||
font-size: 10px;
|
||||
color: #999;
|
||||
}
|
||||
.totale {
|
||||
background: #f8f9fa;
|
||||
padding: 10px;
|
||||
text-align: center;
|
||||
margin-bottom: 20px;
|
||||
border: 2px solid #2c3e50;
|
||||
font-weight: bold;
|
||||
}
|
||||
@media print {
|
||||
body { padding: 0; }
|
||||
.individuo-block { page-break-after: always; }
|
||||
.individuo-block:last-child { page-break-after: avoid; }
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="header">
|
||||
<h1>Report Individui Multipli</h1>
|
||||
<div class="subtitle">Totale: {{ $individui->count() }} individui</div>
|
||||
</div>
|
||||
|
||||
<div class="totale">
|
||||
Report generato il {{ now()->format('d/m/Y H:i') }}
|
||||
</div>
|
||||
|
||||
@foreach($individui as $ind)
|
||||
<div class="individuo-block">
|
||||
<div class="section">
|
||||
<div class="section-title">
|
||||
@if($ind->avatar)
|
||||
<img src="{{ url('/individui/' . $ind->id . '/avatar') }}"
|
||||
alt="Avatar"
|
||||
style="width: 30px; height: 30px; object-fit: cover; border-radius: 50%; margin-right: 8px; vertical-align: middle;">
|
||||
@endif
|
||||
{{ $ind->cognome }} {{ $ind->nome }} - Codice: {{ $ind->codice_id }}
|
||||
</div>
|
||||
<table>
|
||||
<tr>
|
||||
<th>Codice</th>
|
||||
<td><span class="badge">{{ $ind->codice_id }}</span></td>
|
||||
<th>Genere</th>
|
||||
<td>{{ $ind->genere === 'M' ? 'Maschile' : ($ind->genere === 'F' ? 'Femminile' : '-') }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>Data di nascita</th>
|
||||
<td>{{ $ind->data_nascita ? $ind->data_nascita->format('d/m/Y') : '-' }}</td>
|
||||
<th>Luogo di nascita</th>
|
||||
<td>{{ $ind->città ?: '-' }}</td>
|
||||
</tr>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<div class="section">
|
||||
<div class="section-title">Residenza</div>
|
||||
<div class="info-grid">
|
||||
<div class="info-item">
|
||||
<label>Indirizzo</label>
|
||||
{{ $ind->indirizzo ?: '-' }}
|
||||
</div>
|
||||
<div class="info-item">
|
||||
<label>CAP</label>
|
||||
{{ $ind->cap ?: '-' }}
|
||||
</div>
|
||||
<div class="info-item">
|
||||
<label>Città</label>
|
||||
{{ $ind->città ?: '-' }}
|
||||
</div>
|
||||
<div class="info-item">
|
||||
<label>Provincia</label>
|
||||
{{ $ind->sigla_provincia ?: '-' }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="section">
|
||||
<div class="section-title">Documento di Identità</div>
|
||||
@if($ind->tipo_documento || $ind->numero_documento)
|
||||
<table>
|
||||
<tr>
|
||||
<th>Tipo</th>
|
||||
<td>
|
||||
@if($ind->tipo_documento === 'carta_identita')
|
||||
<span class="badge badge-info">Carta d'Identità</span>
|
||||
@elseif($ind->tipo_documento === 'patente')
|
||||
<span class="badge badge-warning">Patente</span>
|
||||
@else
|
||||
{{ $ind->tipo_documento }}
|
||||
@endif
|
||||
</td>
|
||||
<th>Numero</th>
|
||||
<td>{{ $ind->numero_documento }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>Scadenza</th>
|
||||
<td>
|
||||
@if($ind->scadenza_documento)
|
||||
@if($ind->scadenza_documento->isPast())
|
||||
<span class="badge badge-danger">SCADUTO</span>
|
||||
@else
|
||||
{{ $ind->scadenza_documento->format('d/m/Y') }}
|
||||
@endif
|
||||
@else
|
||||
-
|
||||
@endif
|
||||
</td>
|
||||
<th></th>
|
||||
<td></td>
|
||||
</tr>
|
||||
</table>
|
||||
@else
|
||||
<p class="empty">Nessun documento registrato</p>
|
||||
@endif
|
||||
</div>
|
||||
|
||||
<div class="section">
|
||||
<div class="section-title">Contatti ({{ $ind->contatti->count() }})</div>
|
||||
@if($ind->contatti->count() > 0)
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Tipo</th>
|
||||
<th>Valore</th>
|
||||
<th>Etichetta</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
@foreach($ind->contatti as $contatto)
|
||||
<tr>
|
||||
<td>
|
||||
@switch($contatto->tipo)
|
||||
@case('email')
|
||||
<span class="badge badge-info">Email</span>
|
||||
@break
|
||||
@case('telefono')
|
||||
<span class="badge">Telefono</span>
|
||||
@break
|
||||
@case('cellulare')
|
||||
<span class="badge badge-success">Cellulare</span>
|
||||
@break
|
||||
@default
|
||||
{{ $contatto->tipo }}
|
||||
@endswitch
|
||||
</td>
|
||||
<td>{{ $contatto->valore }}</td>
|
||||
<td>{{ $contatto->etichetta ?: '-' }}</td>
|
||||
</tr>
|
||||
@endforeach
|
||||
</tbody>
|
||||
</table>
|
||||
@else
|
||||
<p class="empty">Nessun contatto registrato</p>
|
||||
@endif
|
||||
</div>
|
||||
|
||||
<div class="section">
|
||||
<div class="section-title">Gruppi di Appartenenza ({{ $ind->gruppi->count() }})</div>
|
||||
@if($ind->gruppi->count() > 0)
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Gruppo</th>
|
||||
<th>Ruolo</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
@foreach($ind->gruppi as $gruppo)
|
||||
<tr>
|
||||
<td>{{ $gruppo->nome }}</td>
|
||||
<td>{{ $gruppo->pivot->ruolo_nel_gruppo ?: '-' }}</td>
|
||||
</tr>
|
||||
@endforeach
|
||||
</tbody>
|
||||
</table>
|
||||
@else
|
||||
<p class="empty">Nessun gruppo associato</p>
|
||||
@endif
|
||||
</div>
|
||||
</div>
|
||||
@endforeach
|
||||
|
||||
<div class="footer">
|
||||
<p>Glastree - Report generato automaticamente</p>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
window.onload = function() {
|
||||
window.print();
|
||||
}
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,355 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="it">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Scheda Individuo - {{ $individuo->cognome }} {{ $individuo->nome }}</title>
|
||||
<style>
|
||||
* {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
body {
|
||||
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
|
||||
font-size: 12px;
|
||||
line-height: 1.4;
|
||||
color: #333;
|
||||
padding: 20px;
|
||||
}
|
||||
.header {
|
||||
text-align: center;
|
||||
margin-bottom: 20px;
|
||||
padding-bottom: 15px;
|
||||
border-bottom: 2px solid #2c3e50;
|
||||
}
|
||||
.header-content {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
.header h1 {
|
||||
font-size: 24px;
|
||||
color: #2c3e50;
|
||||
margin-bottom: 5px;
|
||||
}
|
||||
.header .subtitle {
|
||||
font-size: 14px;
|
||||
color: #7f8c8d;
|
||||
}
|
||||
.section {
|
||||
margin-bottom: 20px;
|
||||
page-break-inside: avoid;
|
||||
}
|
||||
.section-title {
|
||||
font-size: 14px;
|
||||
font-weight: bold;
|
||||
color: #fff;
|
||||
background: #2c3e50;
|
||||
padding: 8px 12px;
|
||||
margin-bottom: 10px;
|
||||
border-radius: 3px;
|
||||
}
|
||||
table {
|
||||
width: 100%;
|
||||
border-collapse: collapse;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
th, td {
|
||||
border: 1px solid #ddd;
|
||||
padding: 8px;
|
||||
text-align: left;
|
||||
}
|
||||
th {
|
||||
background: #f8f9fa;
|
||||
font-weight: bold;
|
||||
width: 150px;
|
||||
}
|
||||
.badge {
|
||||
display: inline-block;
|
||||
padding: 3px 8px;
|
||||
background: #6c757d;
|
||||
color: #fff;
|
||||
border-radius: 3px;
|
||||
font-size: 11px;
|
||||
}
|
||||
.badge-success { background: #28a745; }
|
||||
.badge-warning { background: #ffc107; color: #333; }
|
||||
.badge-danger { background: #dc3545; }
|
||||
.badge-info { background: #17a2b8; }
|
||||
.info-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(2, 1fr);
|
||||
gap: 10px;
|
||||
}
|
||||
.info-item {
|
||||
border: 1px solid #ddd;
|
||||
padding: 10px;
|
||||
}
|
||||
.info-item label {
|
||||
display: block;
|
||||
font-weight: bold;
|
||||
color: #666;
|
||||
font-size: 10px;
|
||||
text-transform: uppercase;
|
||||
margin-bottom: 3px;
|
||||
}
|
||||
.empty {
|
||||
color: #999;
|
||||
font-style: italic;
|
||||
}
|
||||
.footer {
|
||||
margin-top: 30px;
|
||||
padding-top: 15px;
|
||||
border-top: 1px solid #ddd;
|
||||
text-align: center;
|
||||
font-size: 10px;
|
||||
color: #999;
|
||||
}
|
||||
@media print {
|
||||
body { padding: 0; }
|
||||
.section { page-break-inside: avoid; }
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="header">
|
||||
<div class="header-content">
|
||||
@if($individuo->avatar)
|
||||
<img src="{{ url('/individui/' . $individuo->id . '/avatar') }}"
|
||||
alt="Avatar"
|
||||
style="width: 80px; height: 80px; object-fit: cover; border-radius: 50%; margin-right: 20px; border: 3px solid #2c3e50;">
|
||||
@endif
|
||||
<div>
|
||||
<h1>{{ $individuo->cognome }} {{ $individuo->nome }}</h1>
|
||||
<div class="subtitle">Scheda Individuo - Codice: {{ $individuo->codice_id }}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="section">
|
||||
<div class="section-title">Dati Anagrafici</div>
|
||||
<table>
|
||||
<tr>
|
||||
<th>Codice</th>
|
||||
<td><span class="badge">{{ $individuo->codice_id }}</span></td>
|
||||
<th>Cognome</th>
|
||||
<td>{{ $individuo->cognome }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>Nome</th>
|
||||
<td>{{ $individuo->nome }}</td>
|
||||
<th>Genere</th>
|
||||
<td>{{ $individuo->genere === 'M' ? 'Maschile' : ($individuo->genere === 'F' ? 'Femminile' : '-') }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>Data di nascita</th>
|
||||
<td>{{ $individuo->data_nascita ? $individuo->data_nascita->format('d/m/Y') : '-' }}</td>
|
||||
<th>Luogo di nascita</th>
|
||||
<td>{{ $individuo->città ?: '-' }}</td>
|
||||
</tr>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<div class="section">
|
||||
<div class="section-title">Residenza</div>
|
||||
<div class="info-grid">
|
||||
<div class="info-item">
|
||||
<label>Indirizzo</label>
|
||||
{{ $individuo->indirizzo ?: '-' }}
|
||||
</div>
|
||||
<div class="info-item">
|
||||
<label>CAP</label>
|
||||
{{ $individuo->cap ?: '-' }}
|
||||
</div>
|
||||
<div class="info-item">
|
||||
<label>Città</label>
|
||||
{{ $individuo->città ?: '-' }}
|
||||
</div>
|
||||
<div class="info-item">
|
||||
<label>Provincia</label>
|
||||
{{ $individuo->sigla_provincia ?: '-' }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="section">
|
||||
<div class="section-title">Documento di Identità</div>
|
||||
@if($individuo->tipo_documento || $individuo->numero_documento)
|
||||
<table>
|
||||
<tr>
|
||||
<th>Tipo</th>
|
||||
<td>
|
||||
@if($individuo->tipo_documento === 'carta_identita')
|
||||
<span class="badge badge-info">Carta d'Identità</span>
|
||||
@elseif($individuo->tipo_documento === 'patente')
|
||||
<span class="badge badge-warning">Patente</span>
|
||||
@else
|
||||
{{ $individuo->tipo_documento }}
|
||||
@endif
|
||||
</td>
|
||||
<th>Numero</th>
|
||||
<td>{{ $individuo->numero_documento }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>Scadenza</th>
|
||||
<td>
|
||||
@if($individuo->scadenza_documento)
|
||||
@if($individuo->scadenza_documento->isPast())
|
||||
<span class="badge badge-danger">SCADUTO</span>
|
||||
@else
|
||||
{{ $individuo->scadenza_documento->format('d/m/Y') }}
|
||||
@endif
|
||||
@else
|
||||
-
|
||||
@endif
|
||||
</td>
|
||||
<th></th>
|
||||
<td></td>
|
||||
</tr>
|
||||
</table>
|
||||
@else
|
||||
<p class="empty">Nessun documento registrato</p>
|
||||
@endif
|
||||
</div>
|
||||
|
||||
<div class="section">
|
||||
<div class="section-title">Contatti ({{ $individuo->contatti->count() }})</div>
|
||||
@if($individuo->contatti->count() > 0)
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Tipo</th>
|
||||
<th>Valore</th>
|
||||
<th>Etichetta</th>
|
||||
<th>Primario</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
@foreach($individuo->contatti as $contatto)
|
||||
<tr>
|
||||
<td>
|
||||
@switch($contatto->tipo)
|
||||
@case('email')
|
||||
<span class="badge badge-info">Email</span>
|
||||
@break
|
||||
@case('telefono')
|
||||
<span class="badge">Telefono</span>
|
||||
@break
|
||||
@case('cellulare')
|
||||
<span class="badge badge-success">Cellulare</span>
|
||||
@break
|
||||
@default
|
||||
{{ $contatto->tipo }}
|
||||
@endswitch
|
||||
</td>
|
||||
<td>{{ $contatto->valore }}</td>
|
||||
<td>{{ $contatto->etichetta ?: '-' }}</td>
|
||||
<td>{{ $contatto->is_primary ? '✓' : '-' }}</td>
|
||||
</tr>
|
||||
@endforeach
|
||||
</tbody>
|
||||
</table>
|
||||
@else
|
||||
<p class="empty">Nessun contatto registrato</p>
|
||||
@endif
|
||||
</div>
|
||||
|
||||
<div class="section">
|
||||
<div class="section-title">Gruppi di Appartenenza ({{ $individuo->gruppi->count() }})</div>
|
||||
@if($individuo->gruppi->count() > 0)
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Gruppo</th>
|
||||
<th>Ruolo</th>
|
||||
<th>Data Adesione</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
@foreach($individuo->gruppi as $gruppo)
|
||||
<tr>
|
||||
<td>{{ $gruppo->nome }}</td>
|
||||
<td>{{ $gruppo->pivot->ruolo_nel_gruppo ?: '-' }}</td>
|
||||
<td>{{ $gruppo->pivot->data_adesione ? \Carbon\Carbon::parse($gruppo->pivot->data_adesione)->format('d/m/Y') : '-' }}</td>
|
||||
</tr>
|
||||
@endforeach
|
||||
</tbody>
|
||||
</table>
|
||||
@else
|
||||
<p class="empty">Nessun gruppo associato</p>
|
||||
@endif
|
||||
</div>
|
||||
|
||||
<div class="section">
|
||||
<div class="section-title">Documenti ({{ $individuo->documenti->count() }})</div>
|
||||
@if($individuo->documenti->count() > 0)
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Nome</th>
|
||||
<th>Tipologia</th>
|
||||
<th>Dimensione</th>
|
||||
<th>Data Upload</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
@foreach($individuo->documenti as $documento)
|
||||
<tr>
|
||||
<td>{{ $documento->nome }}</td>
|
||||
<td>{{ $documento->tipologia }}</td>
|
||||
<td>{{ $documento->dimensione ? number_format($documento->dimensione / 1024, 1) . ' KB' : '-' }}</td>
|
||||
<td>{{ $documento->created_at->format('d/m/Y') }}</td>
|
||||
</tr>
|
||||
@endforeach
|
||||
</tbody>
|
||||
</table>
|
||||
@else
|
||||
<p class="empty">Nessun documento caricato</p>
|
||||
@endif
|
||||
</div>
|
||||
|
||||
<div class="section">
|
||||
<div class="section-title">Eventi come Responsabile ({{ $individuo->eventiResponsabili->count() }})</div>
|
||||
@if($individuo->eventiResponsabili->count() > 0)
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Evento</th>
|
||||
<th>Data</th>
|
||||
<th>Tipo Ricorrenza</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
@foreach($individuo->eventiResponsabili as $evento)
|
||||
<tr>
|
||||
<td>{{ $evento->nome }}</td>
|
||||
<td>{{ $evento->data_specifica ? $evento->data_specifica->format('d/m/Y') : '-' }}</td>
|
||||
<td>{{ $evento->tipo_recorrenza ?: '-' }}</td>
|
||||
</tr>
|
||||
@endforeach
|
||||
</tbody>
|
||||
</table>
|
||||
@else
|
||||
<p class="empty">Nessun evento assegnato come responsabile</p>
|
||||
@endif
|
||||
</div>
|
||||
|
||||
@if($individuo->note)
|
||||
<div class="section">
|
||||
<div class="section-title">Note</div>
|
||||
<p>{{ $individuo->note }}</p>
|
||||
</div>
|
||||
@endif
|
||||
|
||||
<div class="footer">
|
||||
<p>Report generato il {{ now()->format('d/m/Y H:i') }} - Glastree</p>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
window.onload = function() {
|
||||
window.print();
|
||||
}
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,671 @@
|
||||
@extends('layouts.adminlte')
|
||||
@section('title', 'Dettaglio Individuo')
|
||||
@section('page_title', 'Dettaglio Individuo')
|
||||
|
||||
@section('breadcrumbs')
|
||||
<li class="breadcrumb-item"><a href="{{ route('individui.index') }}">Individui</a></li>
|
||||
<li class="breadcrumb-item active">{{ $individuo->nome_completo }}</li>
|
||||
@endsection
|
||||
|
||||
@section('content')
|
||||
|
||||
<div class="row">
|
||||
<div class="col-md-6">
|
||||
<div class="card card-success">
|
||||
<div class="card-header">
|
||||
<h3 class="card-title"><i class="fas fa-user mr-2"></i>{{ $individuo->nome_completo }}</h3>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<p><strong>Codice:</strong> <span class="badge badge-secondary">{{ $individuo->codice_id }}</span></p>
|
||||
<p><strong>Data di nascita:</strong> {{ $individuo->data_nascita?->format('d/m/Y') ?: '-' }}</p>
|
||||
<p><strong>Genere:</strong> {{ $individuo->genere === 'M' ? 'Maschio' : ($individuo->genere === 'F' ? 'Femmina' : '-') }}</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<div class="card card-secondary">
|
||||
<div class="card-header">
|
||||
<h3 class="card-title"><i class="fas fa-camera mr-2"></i>Avatar</h3>
|
||||
</div>
|
||||
<div class="card-body text-center">
|
||||
@if($individuo->avatar)
|
||||
<img src="{{ url('/individui/' . $individuo->id . '/avatar') }}"
|
||||
alt="Avatar"
|
||||
class="img-thumbnail mb-2"
|
||||
style="max-width: 120px; max-height: 120px; object-fit: cover; border-radius: 50%;">
|
||||
<br>
|
||||
<button type="button" class="btn btn-danger btn-sm" onclick="deleteAvatar()">
|
||||
<i class="fas fa-trash mr-1"></i>Rimuovi
|
||||
</button>
|
||||
@else
|
||||
<div class="bg-secondary d-flex align-items-center justify-content-center mb-3"
|
||||
style="width: 120px; height: 120px; margin: 0 auto; border-radius: 50%;">
|
||||
<i class="fas fa-user fa-4x text-white"></i>
|
||||
</div>
|
||||
@endif
|
||||
<form id="avatar-upload-form" enctype="multipart/form-data">
|
||||
@csrf
|
||||
<div class="custom-file mb-2">
|
||||
<input type="file" class="custom-file-input" id="avatar-input" name="avatar" accept="image/jpeg,image/png,image/gif">
|
||||
<label class="custom-file-label" for="avatar-input">Scegli immagine...</label>
|
||||
</div>
|
||||
<button type="button" class="btn btn-primary btn-sm" onclick="uploadAvatar()">
|
||||
<i class="fas fa-upload mr-1"></i>Carica
|
||||
</button>
|
||||
</form>
|
||||
<div id="avatar-message" class="mt-2"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
<div class="col-md-6">
|
||||
<div class="card">
|
||||
<div class="card-header"><h3 class="card-title"><i class="fas fa-map-marker-alt mr-2"></i>Residenza</h3></div>
|
||||
<div class="card-body">
|
||||
<p><strong>Indirizzo:</strong> {{ $individuo->indirizzo ?: '-' }}</p>
|
||||
<p><strong>CAP:</strong> {{ $individuo->cap ?: '-' }}</p>
|
||||
<p><strong>Città:</strong> {{ $individuo->città ?: '-' }}</p>
|
||||
<p><strong>Provincia:</strong> {{ $individuo->sigla_provincia ?: '-' }}</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<div class="card">
|
||||
<div class="card-header"><h3 class="card-title"><i class="fas fa-id-card mr-2"></i>Documento di identità</h3></div>
|
||||
<div class="card-body">
|
||||
<p><strong>Tipo:</strong> {{ $individuo->tipo_documento ? ucfirst(str_replace('_', ' ', $individuo->tipo_documento)) : '-' }}</p>
|
||||
<p><strong>Numero:</strong> {{ $individuo->numero_documento ?: '-' }}</p>
|
||||
<p><strong>Scadenza:</strong> {{ $individuo->scadenza_documento?->format('d/m/Y') ?: '-' }}</p>
|
||||
@if($individuo->hasDocumentoScaduto())
|
||||
<p><span class="badge badge-danger"><i class="fas fa-exclamation-triangle mr-1"></i> Documento scaduto da {{ $individuo->giorni_scadenza_documento }} giorni</span></p>
|
||||
@elseif($individuo->hasDocumentoScadeEntroGiorni(30))
|
||||
<p><span class="badge badge-warning"><i class="fas fa-exclamation-circle mr-1"></i> Scade tra {{ $individuo->giorni_scadenza_documento }} giorni</span></p>
|
||||
@endif
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@if($individuo->note)
|
||||
<div class="row mt-3">
|
||||
<div class="col-md-8">
|
||||
<div class="card">
|
||||
<div class="card-header"><h3 class="card-title"><i class="fas fa-sticky-note mr-2"></i>Note</h3></div>
|
||||
<div class="card-body">
|
||||
<p class="mb-0">{!! nl2br(e($individuo->note)) !!}</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@endif
|
||||
|
||||
<div class="card mt-3">
|
||||
<div class="card-header">
|
||||
<h3 class="card-title"><i class="fas fa-address-book mr-2"></i>Contatti</h3>
|
||||
</div>
|
||||
<div class="card-body p-0">
|
||||
@if($individuo->contatti->count() > 0)
|
||||
<table class="table table-bordered table-hover table-striped mb-0">
|
||||
<thead class="thead-light">
|
||||
<tr>
|
||||
<th style="width: 20%;">Tipo</th>
|
||||
<th style="width: 30%;">Valore</th>
|
||||
<th style="width: 20%;">Etichetta</th>
|
||||
<th style="width: 10%;">Primario</th>
|
||||
<th style="width: 80px;">Azioni</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
@foreach($individuo->contatti as $contatto)
|
||||
@php
|
||||
$protocols = ['http://', 'https://', 'ftp://', 'ssh://', 'sftp://', 'telnet://'];
|
||||
$isUrl = false;
|
||||
if (in_array($contatto->tipo, ['web', 'telegram'])) {
|
||||
foreach ($protocols as $p) {
|
||||
if (str_starts_with(strtolower($contatto->valore), $p)) {
|
||||
$isUrl = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
@endphp
|
||||
<tr id="contatto-row-{{ $contatto->id }}">
|
||||
<td><span class="text-capitalize">{{ $contatto->tipo }}</span></td>
|
||||
<td>
|
||||
@if($contatto->tipo === 'email')
|
||||
<a href="mailto:{{ $contatto->valore }}">{{ $contatto->valore }}</a>
|
||||
@elseif(in_array($contatto->tipo, ['telefono', 'cellulare', 'whatsapp']))
|
||||
<a href="tel:{{ $contatto->valore }}">{{ $contatto->valore }}</a>
|
||||
@elseif($isUrl)
|
||||
<a href="{{ $contatto->valore }}" target="_blank">{{ $contatto->valore }}</a>
|
||||
@else
|
||||
{{ $contatto->valore }}
|
||||
@endif
|
||||
</td>
|
||||
<td>{{ $contatto->etichetta ?: '-' }}</td>
|
||||
<td>
|
||||
@if($contatto->is_primary)
|
||||
<span class="badge badge-success"><i class="fas fa-check mr-1"></i> Sì</span>
|
||||
@else
|
||||
<span class="text-muted">No</span>
|
||||
@endif
|
||||
</td>
|
||||
<td>
|
||||
<button type="button" class="btn btn-xs btn-warning" onclick="editContattoInline({{ $contatto->id }})" title="Modifica">
|
||||
<i class="fas fa-edit"></i>
|
||||
</button>
|
||||
<form action="{{ url('/contatti/' . $contatto->id) }}" method="POST" class="d-inline">
|
||||
@csrf @method('DELETE')
|
||||
<input type="hidden" name="_redirect" value="{{ url('/individui/' . $individuo->id) }}">
|
||||
<button type="submit" class="btn btn-xs btn-danger" onclick="return confirm('Eliminare questo contatto?')" title="Elimina">
|
||||
<i class="fas fa-trash"></i>
|
||||
</button>
|
||||
</form>
|
||||
</td>
|
||||
</tr>
|
||||
<tr id="contatto-edit-{{ $contatto->id }}" style="display:none;">
|
||||
<td colspan="5">
|
||||
<form action="{{ url('/contatti/' . $contatto->id) }}" method="POST" class="mb-0">
|
||||
@csrf @method('PUT')
|
||||
<input type="hidden" name="_redirect" value="{{ url('/individui/' . $individuo->id) }}">
|
||||
<input type="hidden" name="individuo_id" value="{{ $individuo->id }}">
|
||||
<div class="row">
|
||||
<div class="col-md-3">
|
||||
<select name="tipo" class="form-control form-control-sm" required>
|
||||
<option value="telefono" {{ $contatto->tipo === 'telefono' ? 'selected' : '' }}>Telefono</option>
|
||||
<option value="cellulare" {{ $contatto->tipo === 'cellulare' ? 'selected' : '' }}>Cellulare</option>
|
||||
<option value="email" {{ $contatto->tipo === 'email' ? 'selected' : '' }}>Email</option>
|
||||
<option value="fax" {{ $contatto->tipo === 'fax' ? 'selected' : '' }}>Fax</option>
|
||||
<option value="web" {{ $contatto->tipo === 'web' ? 'selected' : '' }}>Web</option>
|
||||
<option value="telegram" {{ $contatto->tipo === 'telegram' ? 'selected' : '' }}>Telegram</option>
|
||||
<option value="whatsapp" {{ $contatto->tipo === 'whatsapp' ? 'selected' : '' }}>WhatsApp</option>
|
||||
<option value="altro" {{ $contatto->tipo === 'altro' ? 'selected' : '' }}>Altro</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="col-md-4">
|
||||
<input type="text" name="valore" class="form-control form-control-sm" value="{{ $contatto->valore }}" required>
|
||||
</div>
|
||||
<div class="col-md-2">
|
||||
<input type="text" name="etichetta" class="form-control form-control-sm" value="{{ $contatto->etichetta }}" placeholder="Etichetta">
|
||||
</div>
|
||||
<div class="col-md-1 text-center">
|
||||
<input type="checkbox" name="is_primary" value="1" {{ $contatto->is_primary ? 'checked' : '' }}>
|
||||
</div>
|
||||
<div class="col-md-2">
|
||||
<button type="submit" class="btn btn-xs btn-success"><i class="fas fa-check mr-1"></i>Salva</button>
|
||||
<button type="button" class="btn btn-xs btn-secondary" onclick="cancelEditContatto({{ $contatto->id }})"><i class="fas fa-times"></i></button>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</td>
|
||||
</tr>
|
||||
@endforeach
|
||||
</tbody>
|
||||
</table>
|
||||
@else
|
||||
<div class="text-center text-muted py-4">
|
||||
<i class="fas fa-address-book fa-2x mb-2"></i>
|
||||
<p class="mb-0">Nessun contatto</p>
|
||||
</div>
|
||||
@endif
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="card mt-3">
|
||||
<div class="card-header">
|
||||
<h3 class="card-title"><i class="fas fa-folder mr-2"></i>Gruppi</h3>
|
||||
<button type="button" class="btn btn-xs btn-success float-right" onclick="showGruppoForm()">
|
||||
<i class="fas fa-plus mr-1"></i> Aggiungi
|
||||
</button>
|
||||
</div>
|
||||
<div class="card-body p-0">
|
||||
|
||||
@if($individuo->gruppi->count() > 0)
|
||||
<table class="table table-bordered table-hover table-striped mb-0">
|
||||
<thead class="thead-light">
|
||||
<tr>
|
||||
<th>Nome</th>
|
||||
<th>Diocesi</th>
|
||||
<th>Responsabile</th>
|
||||
<th style="width: 130px;">Ruolo</th>
|
||||
<th style="width: 110px;">Data Adesione</th>
|
||||
<th style="width: 80px;">Azioni</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
@foreach($individuo->gruppi as $gruppo)
|
||||
<tr id="gruppo-row-{{ $gruppo->id }}">
|
||||
<td>
|
||||
<i class="fas fa-folder text-warning mr-1"></i>
|
||||
<a href="{{ url('/gruppi/' . $gruppo->id) }}">{{ $gruppo->nome }}</a>
|
||||
</td>
|
||||
<td>{{ $gruppo->diocesi?->nome ?: '-' }}</td>
|
||||
<td>{{ $gruppo->getResponsabili()->isNotEmpty() ? $gruppo->getResponsabili()->first()->nome_completo : '-' }}</td>
|
||||
<td>
|
||||
@php
|
||||
$ruoloIds = $gruppo->pivot->ruolo_ids ? json_decode($gruppo->pivot->ruolo_ids, true) : [];
|
||||
$ruoli = \App\Models\Ruolo::findByIds($ruoloIds);
|
||||
@endphp
|
||||
@if($ruoli->count() > 0)
|
||||
@foreach($ruoli as $ruolo)
|
||||
<span class="badge badge-info mr-1">{{ $ruolo->nome }}</span>
|
||||
@endforeach
|
||||
@else
|
||||
<span class="text-muted">-</span>
|
||||
@endif
|
||||
</td>
|
||||
<td>{{ $gruppo->pivot->data_adesione ? \Carbon\Carbon::parse($gruppo->pivot->data_adesione)->format('d/m/Y') : '-' }}</td>
|
||||
<td>
|
||||
<button type="button" class="btn btn-xs btn-warning" onclick="editGruppoInline({{ $gruppo->id }})" title="Modifica ruolo">
|
||||
<i class="fas fa-edit"></i>
|
||||
</button>
|
||||
<button type="button" class="btn btn-xs btn-danger" onclick="deleteGruppo({{ $gruppo->id }})" title="Rimuovi">
|
||||
<i class="fas fa-trash"></i>
|
||||
</button>
|
||||
</td>
|
||||
</tr>
|
||||
<tr id="gruppo-edit-{{ $gruppo->id }}" style="display:none;">
|
||||
<td colspan="6">
|
||||
<form action="{{ url('/individui/' . $individuo->id . '/gruppi/' . $gruppo->id) }}" method="POST" class="mb-0">
|
||||
@csrf @method('PUT')
|
||||
<div class="row">
|
||||
<div class="col-md-4">
|
||||
<input type="text" class="form-control form-control-sm" value="{{ $gruppo->nome }}" disabled>
|
||||
</div>
|
||||
<div class="col-md-3">
|
||||
<select name="ruolo_ids[]" class="form-control form-control-sm" multiple size="3">
|
||||
@php $selectedRuoliIds = $gruppo->pivot->ruolo_ids ? json_decode($gruppo->pivot->ruolo_ids, true) : [] @endphp
|
||||
@foreach(\App\Models\Ruolo::attive() as $ruolo)
|
||||
<option value="{{ $ruolo->id }}" {{ in_array($ruolo->id, $selectedRuoliIds) ? 'selected' : '' }}>{{ $ruolo->nome }}</option>
|
||||
@endforeach
|
||||
</select>
|
||||
</div>
|
||||
<div class="col-md-3">
|
||||
<input type="date" name="data_adesione" class="form-control form-control-sm" value="{{ $gruppo->pivot->data_adesione }}">
|
||||
</div>
|
||||
<div class="col-md-2">
|
||||
<button type="submit" class="btn btn-xs btn-success"><i class="fas fa-check mr-1"></i>Salva</button>
|
||||
<button type="button" class="btn btn-xs btn-secondary" onclick="cancelEditGruppo({{ $gruppo->id }})"><i class="fas fa-times"></i></button>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</td>
|
||||
</tr>
|
||||
@endforeach
|
||||
</tbody>
|
||||
</table>
|
||||
@else
|
||||
<div class="text-center text-muted py-4">
|
||||
<i class="fas fa-users fa-2x mb-2"></i>
|
||||
<p class="mb-0">Nessun gruppo associato</p>
|
||||
</div>
|
||||
@endif
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="card mt-3" id="gruppo-add-card" style="display:none; background-color: #fff3cd; border-color: #ffc107;">
|
||||
<div class="card-header bg-warning">
|
||||
<h3 class="card-title"><i class="fas fa-folder-plus mr-2"></i>Aggiungi a Gruppo</h3>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<div class="row">
|
||||
<div class="col-md-4">
|
||||
<select id="new-gruppo-id" class="form-control form-control-sm">
|
||||
<option value="">Seleziona gruppo...</option>
|
||||
@foreach(\App\Models\Gruppo::orderBy('nome')->get() as $g)
|
||||
@if(!$individuo->gruppi->contains($g->id))
|
||||
<option value="{{ $g->id }}">{{ $g->full_path }}</option>
|
||||
@endif
|
||||
@endforeach
|
||||
</select>
|
||||
</div>
|
||||
<div class="col-md-3">
|
||||
<select id="new-gruppo-ruolo" class="form-control form-control-sm" multiple size="3">
|
||||
@foreach(\App\Models\Ruolo::attive() as $ruolo)
|
||||
<option value="{{ $ruolo->id }}">{{ $ruolo->nome }}</option>
|
||||
@endforeach
|
||||
</select>
|
||||
<small class="text-muted">Ctrl+click per selezionare più ruoli</small>
|
||||
</div>
|
||||
<div class="col-md-3">
|
||||
<input type="date" id="new-gruppo-data" class="form-control form-control-sm">
|
||||
</div>
|
||||
<div class="col-md-2">
|
||||
<button type="button" class="btn btn-success btn-sm" onclick="submitGruppoForm()">
|
||||
<i class="fas fa-check mr-1"></i> Associa
|
||||
</button>
|
||||
<button type="button" class="btn btn-secondary btn-sm" onclick="hideGruppoForm()">
|
||||
<i class="fas fa-times"></i>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="card mt-3">
|
||||
<div class="card-header">
|
||||
<h3 class="card-title"><i class="fas fa-file mr-2"></i>Documenti</h3>
|
||||
<button type="button" class="btn btn-xs btn-success float-right" onclick="showDocumentoForm()">
|
||||
<i class="fas fa-plus mr-1"></i> Carica
|
||||
</button>
|
||||
</div>
|
||||
<div class="card-body p-0">
|
||||
<div id="documento-add-form" style="display:none;" class="p-3 bg-light border-bottom">
|
||||
<form action="/documenti" method="POST" enctype="multipart/form-data" class="mb-0">
|
||||
@csrf
|
||||
<input type="hidden" name="visibilita" value="individuo">
|
||||
<input type="hidden" name="visibilita_target_id" value="{{ $individuo->id }}">
|
||||
<input type="hidden" name="visibilita_target_type" value="App\Models\Individuo">
|
||||
<input type="hidden" name="redirect_to" value="{{ url('/individui/' . $individuo->id) }}">
|
||||
<div class="row">
|
||||
<div class="col-md-3">
|
||||
<input type="text" name="nome_file" class="form-control form-control-sm" placeholder="Nome documento" required>
|
||||
</div>
|
||||
<div class="col-md-3">
|
||||
<select name="tipologia" class="form-control form-control-sm" required>
|
||||
<option value="">Tipologia...</option>
|
||||
<option value="documento">Documento</option>
|
||||
<option value="avatar">Avatar</option>
|
||||
<option value="galleria">Galleria</option>
|
||||
<option value="statuto">Statuto</option>
|
||||
<option value="altro">Altro</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="col-md-4">
|
||||
<input type="file" name="file" class="form-control form-control-sm" required>
|
||||
</div>
|
||||
<div class="col-md-2">
|
||||
<button type="submit" class="btn btn-xs btn-success"><i class="fas fa-upload mr-1"></i>Carica</button>
|
||||
<button type="button" class="btn btn-xs btn-secondary" onclick="hideDocumentoForm()"><i class="fas fa-times"></i></button>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
@if($individuo->documenti->count() > 0)
|
||||
<table class="table table-bordered table-hover table-striped mb-0">
|
||||
<thead class="thead-light">
|
||||
<tr>
|
||||
<th>Nome</th>
|
||||
<th style="width: 120px;">Tipologia</th>
|
||||
<th style="width: 80px;">Dimensione</th>
|
||||
<th style="width: 130px;">Data Upload</th>
|
||||
<th style="width: 80px;">Azioni</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
@foreach($individuo->documenti as $documento)
|
||||
<tr>
|
||||
<td>
|
||||
<a href="#" onclick="event.preventDefault(); previewDocumento({{ $documento->id }}, '{{ $documento->mime_type }}');">
|
||||
<i class="fas fa-file text-secondary mr-1"></i>
|
||||
{{ $documento->nome_file }}
|
||||
</a>
|
||||
</td>
|
||||
<td>{{ ucfirst(str_replace('_', ' ', $documento->tipologia)) }}</td>
|
||||
<td>{{ number_format($documento->dimensione / 1024, 1) }} KB</td>
|
||||
<td>{{ $documento->created_at->format('d/m/Y') }}</td>
|
||||
<td>
|
||||
@if($documento->file_path)
|
||||
<button type="button" class="btn btn-xs btn-primary" onclick="previewDocumento({{ $documento->id }}, '{{ $documento->mime_type }}')" title="Anteprima">
|
||||
<i class="fas fa-eye"></i>
|
||||
</button>
|
||||
@endif
|
||||
<form action="{{ url('/documenti/' . $documento->id) }}" method="POST" class="d-inline">
|
||||
@csrf @method('DELETE')
|
||||
<input type="hidden" name="_redirect" value="{{ url('/individui/' . $individuo->id) }}">
|
||||
<button type="submit" class="btn btn-xs btn-danger" onclick="return confirm('Eliminare questo documento?')" title="Elimina">
|
||||
<i class="fas fa-trash"></i>
|
||||
</button>
|
||||
</form>
|
||||
</td>
|
||||
</tr>
|
||||
@endforeach
|
||||
</tbody>
|
||||
</table>
|
||||
@else
|
||||
<div class="text-center text-muted py-4">
|
||||
<i class="fas fa-file fa-2x mb-2"></i>
|
||||
<p class="mb-0">Nessun documento</p>
|
||||
</div>
|
||||
@endif
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="mt-3">
|
||||
<a href="{{ url('/individui/' . $individuo->id . '/edit') }}" class="btn btn-warning">
|
||||
<i class="fas fa-edit mr-1"></i> Modifica
|
||||
</a>
|
||||
<form action="{{ url('/individui/' . $individuo->id) }}" method="POST" class="d-inline">
|
||||
@csrf @method('DELETE')
|
||||
<button type="submit" class="btn btn-danger" onclick="return confirm('Confermi l\'eliminazione di {{ $individuo->nome_completo }}?')">
|
||||
<i class="fas fa-trash mr-1"></i> Elimina
|
||||
</button>
|
||||
</form>
|
||||
<a href="{{ route('individui.index') }}" class="btn btn-secondary">
|
||||
<i class="fas fa-arrow-left mr-1"></i> Torna all'elenco
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<div class="modal fade" id="previewModal" tabindex="-1" role="dialog">
|
||||
<div class="modal-dialog modal-lg" role="document" style="max-width: 90vw;">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h5 class="modal-title"><i class="fas fa-file mr-2"></i><span id="previewModalTitle">Anteprima</span></h5>
|
||||
<button type="button" class="close" data-dismiss="modal" aria-label="Chiudi">
|
||||
<span aria-hidden="true">×</span>
|
||||
</button>
|
||||
</div>
|
||||
<div class="modal-body text-center p-0" style="min-height: 400px;">
|
||||
<iframe id="previewFrame" src="" style="width: 100%; height: 70vh; border: none;"></iframe>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<a id="previewDownloadBtn" href="#" class="btn btn-primary" download>
|
||||
<i class="fas fa-download mr-1"></i> Scarica
|
||||
</a>
|
||||
<button type="button" class="btn btn-secondary" data-dismiss="modal">Chiudi</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@endsection
|
||||
|
||||
@section('scripts')
|
||||
<script>
|
||||
function editContattoInline(id) {
|
||||
document.getElementById('contatto-row-' + id).style.display = 'none';
|
||||
document.getElementById('contatto-edit-' + id).style.display = 'table-row';
|
||||
}
|
||||
|
||||
function cancelEditContatto(id) {
|
||||
document.getElementById('contatto-row-' + id).style.display = 'table-row';
|
||||
document.getElementById('contatto-edit-' + id).style.display = 'none';
|
||||
}
|
||||
|
||||
function showGruppoForm() {
|
||||
var card = document.getElementById('gruppo-add-card');
|
||||
if (card) {
|
||||
card.style.display = 'block';
|
||||
card.scrollIntoView({ behavior: 'smooth', block: 'start' });
|
||||
}
|
||||
}
|
||||
|
||||
function hideGruppoForm() {
|
||||
var card = document.getElementById('gruppo-add-card');
|
||||
if (card) {
|
||||
card.style.display = 'none';
|
||||
}
|
||||
}
|
||||
|
||||
function submitGruppoForm() {
|
||||
var gruppoId = document.getElementById('new-gruppo-id').value;
|
||||
var ruoloSelect = document.getElementById('new-gruppo-ruolo');
|
||||
var dataAdesione = document.getElementById('new-gruppo-data').value;
|
||||
|
||||
if (!gruppoId) {
|
||||
alert('Seleziona un gruppo');
|
||||
return;
|
||||
}
|
||||
|
||||
var formData = new URLSearchParams();
|
||||
formData.append('gruppo_id', gruppoId);
|
||||
|
||||
var selectedRuoli = Array.from(ruoloSelect.selectedOptions).map(opt => opt.value).filter(v => v);
|
||||
selectedRuoli.forEach(function(ruoloId) {
|
||||
formData.append('ruolo_ids[]', ruoloId);
|
||||
});
|
||||
|
||||
formData.append('data_adesione', dataAdesione);
|
||||
|
||||
fetch('{{ url('/individui/' . $individuo->id . '/gruppi') }}', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'X-CSRF-TOKEN': '{{ csrf_token() }}',
|
||||
'Accept': 'application/json',
|
||||
'Content-Type': 'application/x-www-form-urlencoded'
|
||||
},
|
||||
body: formData
|
||||
})
|
||||
.then(response => {
|
||||
if (!response.ok) {
|
||||
return response.text().then(text => { throw new Error(text); });
|
||||
}
|
||||
return response.json();
|
||||
})
|
||||
.then(data => {
|
||||
if (data.success) {
|
||||
hideGruppoForm();
|
||||
window.location.reload();
|
||||
} else {
|
||||
alert(data.error || 'Errore');
|
||||
}
|
||||
})
|
||||
.catch(error => {
|
||||
alert('Errore: ' + error.message);
|
||||
});
|
||||
}
|
||||
|
||||
function editGruppoInline(id) {
|
||||
document.getElementById('gruppo-row-' + id).style.display = 'none';
|
||||
document.getElementById('gruppo-edit-' + id).style.display = 'table-row';
|
||||
}
|
||||
|
||||
function cancelEditGruppo(id) {
|
||||
document.getElementById('gruppo-row-' + id).style.display = 'table-row';
|
||||
document.getElementById('gruppo-edit-' + id).style.display = 'none';
|
||||
}
|
||||
|
||||
function deleteGruppo(gruppoId) {
|
||||
if (!confirm('Rimuovere da questo gruppo?')) return;
|
||||
|
||||
fetch('{{ url('/individui/' . $individuo->id . '/gruppi') }}/' + gruppoId, {
|
||||
method: 'DELETE',
|
||||
headers: {
|
||||
'X-CSRF-TOKEN': '{{ csrf_token() }}',
|
||||
'Accept': 'application/json'
|
||||
}
|
||||
})
|
||||
.then(response => {
|
||||
if (!response.ok) {
|
||||
return response.text().then(text => { throw new Error(text); });
|
||||
}
|
||||
return response.json();
|
||||
})
|
||||
.then(data => {
|
||||
if (data.success) {
|
||||
window.location.reload();
|
||||
} else {
|
||||
alert(data.error || 'Errore');
|
||||
}
|
||||
})
|
||||
.catch(error => {
|
||||
alert('Errore: ' + error.message);
|
||||
});
|
||||
}
|
||||
|
||||
function showDocumentoForm() {
|
||||
document.getElementById('documento-add-form').style.display = 'block';
|
||||
}
|
||||
|
||||
function hideDocumentoForm() {
|
||||
document.getElementById('documento-add-form').style.display = 'none';
|
||||
}
|
||||
|
||||
function previewDocumento(id, mimeType) {
|
||||
var previewUrl = '/documenti/' + id + '/preview';
|
||||
var downloadUrl = '/documenti/' + id + '/download';
|
||||
|
||||
document.getElementById('previewFrame').src = previewUrl;
|
||||
document.getElementById('previewDownloadBtn').href = downloadUrl;
|
||||
document.getElementById('previewModalTitle').textContent = 'Anteprima';
|
||||
|
||||
if (mimeType && mimeType.startsWith('image/')) {
|
||||
document.getElementById('previewDownloadBtn').style.display = 'inline-block';
|
||||
} else {
|
||||
document.getElementById('previewDownloadBtn').style.display = 'none';
|
||||
}
|
||||
|
||||
$('#previewModal').modal('show');
|
||||
}
|
||||
|
||||
document.getElementById('avatar-input').addEventListener('change', function(e) {
|
||||
var fileName = e.target.files[0]?.name || 'Scegli file...';
|
||||
e.target.nextElementSibling.textContent = fileName;
|
||||
});
|
||||
|
||||
function uploadAvatar() {
|
||||
var input = document.getElementById('avatar-input');
|
||||
if (!input.files[0]) {
|
||||
document.getElementById('avatar-message').innerHTML = '<span class="text-danger">Seleziona un\'immagine</span>';
|
||||
return;
|
||||
}
|
||||
|
||||
var formData = new FormData();
|
||||
formData.append('avatar', input.files[0]);
|
||||
formData.append('_token', '{{ csrf_token() }}');
|
||||
|
||||
document.getElementById('avatar-message').innerHTML = '<span class="text-info"><i class="fas fa-spinner fa-spin"></i> Caricamento...</span>';
|
||||
|
||||
fetch('{{ url('/individui/' . $individuo->id . '/avatar') }}', {
|
||||
method: 'POST',
|
||||
body: formData
|
||||
})
|
||||
.then(response => response.json())
|
||||
.then(data => {
|
||||
if (data.success) {
|
||||
document.getElementById('avatar-message').innerHTML = '<span class="text-success">Avatar caricato!</span>';
|
||||
setTimeout(function() { window.location.reload(); }, 500);
|
||||
} else {
|
||||
document.getElementById('avatar-message').innerHTML = '<span class="text-danger">' + (data.message || 'Errore') + '</span>';
|
||||
}
|
||||
})
|
||||
.catch(error => {
|
||||
document.getElementById('avatar-message').innerHTML = '<span class="text-danger">Errore: ' + error + '</span>';
|
||||
});
|
||||
}
|
||||
|
||||
function deleteAvatar() {
|
||||
if (!confirm('Rimuovere l\'avatar?')) return;
|
||||
|
||||
fetch('{{ url('/individui/' . $individuo->id . '/avatar') }}', {
|
||||
method: 'DELETE',
|
||||
headers: {
|
||||
'X-CSRF-TOKEN': '{{ csrf_token() }}',
|
||||
'Accept': 'application/json'
|
||||
}
|
||||
})
|
||||
.then(response => response.json())
|
||||
.then(data => {
|
||||
if (data.success) {
|
||||
window.location.reload();
|
||||
} else {
|
||||
alert(data.message || 'Errore');
|
||||
}
|
||||
})
|
||||
.catch(error => {
|
||||
alert('Errore: ' + error);
|
||||
});
|
||||
}
|
||||
</script>
|
||||
@endsection
|
||||
Reference in New Issue
Block a user