1065 lines
46 KiB
PHP
1065 lines
46 KiB
PHP
@extends('layouts.adminlte')
|
|
@section('title', 'Individui')
|
|
@section('page_title', 'Elenco Individui')
|
|
|
|
@php
|
|
$columnLabels = [
|
|
'codice' => 'Codice',
|
|
'cognome' => 'Cognome',
|
|
'nome' => 'Nome',
|
|
'email' => 'Email',
|
|
'telefono' => 'Telefono',
|
|
'tag' => 'Tag',
|
|
];
|
|
$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>
|
|
<a class="dropdown-item" href="#" onclick="showMassGruppoModal(); return false;">
|
|
<i class="fas fa-users mr-2"></i>Assegna Gruppo
|
|
</a>
|
|
<a class="dropdown-item" href="#" onclick="showMassTagModal(); return false;">
|
|
<i class="fas fa-tags mr-2"></i>Assegna Tag
|
|
</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>
|
|
|
|
@include('partials._tag-filter-bar', ['filterTags' => $allTags])
|
|
|
|
<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
|
|
@if(in_array('tag', $visibleColumns))
|
|
<th>Tag</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
|
|
@if(in_array('tag', $visibleColumns))
|
|
<td>
|
|
@foreach($ind->tags as $tag)
|
|
<a href="{{ route('individui.index', ['tag[]' => $tag->slug]) }}" class="badge" style="background-color: {{ $tag->color ?? '#6c757d' }}; color: #fff;">{{ $tag->name }}</a>
|
|
@endforeach
|
|
</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 d-flex justify-content-between align-items-center">
|
|
<div>
|
|
<form method="GET" class="form-inline" id="perPageForm">
|
|
<label class="mr-2 small">Righe per pagina:</label>
|
|
<select name="perPage" class="form-control form-control-sm" onchange="this.form.submit()" style="width: auto;">
|
|
@foreach([10, 20, 25, 50, 100] as $p)
|
|
<option value="{{ $p }}" {{ (int) request('perPage', 20) === $p ? 'selected' : '' }}>{{ $p }}</option>
|
|
@endforeach
|
|
</select>
|
|
@foreach(request()->except(['perPage', 'page']) as $key => $value)
|
|
@if(is_array($value))
|
|
@foreach($value as $v)
|
|
<input type="hidden" name="{{ $key }}[]" value="{{ $v }}">
|
|
@endforeach
|
|
@else
|
|
<input type="hidden" name="{{ $key }}" value="{{ $value }}">
|
|
@endif
|
|
@endforeach
|
|
</form>
|
|
</div>
|
|
<div>
|
|
{{ $individui->links() }}
|
|
</div>
|
|
</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>
|
|
|
|
{{-- MODAL: Assegna Gruppo massivo --}}
|
|
<div class="modal fade" id="massGruppoModal" tabindex="-1" role="dialog">
|
|
<div class="modal-dialog" role="document">
|
|
<div class="modal-content">
|
|
<div class="modal-header bg-info">
|
|
<h5 class="modal-title text-white"><i class="fas fa-users mr-2"></i>Assegna Gruppo</h5>
|
|
<button type="button" class="close text-white" data-dismiss="modal"><span>×</span></button>
|
|
</div>
|
|
<form id="massGruppoForm" method="POST" action="/individui/mass-gruppo">
|
|
@csrf
|
|
<div class="modal-body">
|
|
<p class="mb-2">Operazione su <strong id="massGruppoCount">0</strong> individui selezionati.</p>
|
|
<div class="form-group mb-3">
|
|
<label class="font-weight-bold">Azione</label>
|
|
<div class="d-flex" style="gap:1.5rem;">
|
|
<div class="form-check">
|
|
<input class="form-check-input" type="radio" name="mode" id="mgModeAssign" value="assign" checked>
|
|
<label class="form-check-label" for="mgModeAssign">Assegna a gruppo</label>
|
|
</div>
|
|
<div class="form-check">
|
|
<input class="form-check-input" type="radio" name="mode" id="mgModeRemove" value="remove">
|
|
<label class="form-check-label" for="mgModeRemove">Rimuovi da gruppo</label>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div class="form-group">
|
|
<label>Seleziona Gruppi</label>
|
|
<select name="gruppi[]" class="form-control" multiple size="8">
|
|
@foreach($gruppi as $g)
|
|
<option value="{{ $g->id }}">{{ $g->nome }}</option>
|
|
@endforeach
|
|
</select>
|
|
<small class="text-muted">Seleziona con Ctrl/Cmd per scegliere più gruppi</small>
|
|
</div>
|
|
<input type="hidden" name="ids" id="massGruppoIds" value="">
|
|
</div>
|
|
<div class="modal-footer">
|
|
<button type="button" class="btn btn-secondary" data-dismiss="modal">Annulla</button>
|
|
<button type="submit" class="btn btn-primary"><i class="fas fa-users mr-1"></i> Applica</button>
|
|
</div>
|
|
</form>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
{{-- MODAL: Gestione Tag massiva --}}
|
|
<div class="modal fade" id="massTagModal" tabindex="-1" role="dialog">
|
|
<div class="modal-dialog modal-dialog-scrollable" role="document">
|
|
<div class="modal-content">
|
|
<div class="modal-header bg-info">
|
|
<h5 class="modal-title text-white"><i class="fas fa-tags mr-2"></i>Gestione Tag</h5>
|
|
<button type="button" class="close text-white" data-dismiss="modal"><span>×</span></button>
|
|
</div>
|
|
<form id="massTagForm" method="POST" action="/individui/mass-tag">
|
|
@csrf
|
|
<div class="modal-body">
|
|
<p class="mb-2">Operazione su <strong id="massTagCount">0</strong> individui selezionati.</p>
|
|
<div class="form-group mb-3">
|
|
<label class="font-weight-bold">Azione</label>
|
|
<div class="d-flex" style="gap:1.5rem;">
|
|
<div class="form-check">
|
|
<input class="form-check-input" type="radio" name="mode" id="indMtModeAssign" value="assign" checked>
|
|
<label class="form-check-label" for="indMtModeAssign">Assegna tag</label>
|
|
</div>
|
|
<div class="form-check">
|
|
<input class="form-check-input" type="radio" name="mode" id="indMtModeRemove" value="remove">
|
|
<label class="form-check-label" for="indMtModeRemove">Rimuovi tag</label>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
@include('partials._tag-selector', ['label' => 'Seleziona tag'])
|
|
<input type="hidden" name="ids" id="massTagIds" value="">
|
|
</div>
|
|
<div class="modal-footer">
|
|
<button type="button" class="btn btn-secondary" data-dismiss="modal">Annulla</button>
|
|
<button type="submit" class="btn btn-primary"><i class="fas fa-tags mr-1"></i> Applica</button>
|
|
</div>
|
|
</form>
|
|
</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, tag: 6, azioni: 7 }[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: 1, cognome: 2, nome: 3, email: 4, telefono: 5, tag: 6, azioni: 7 }[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, tag: 6, azioni: 7 }[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 !== '';
|
|
}
|
|
}
|
|
|
|
function showMassGruppoModal() {
|
|
const ids = getSelectedIds();
|
|
if (ids.length === 0) {
|
|
alert('Seleziona almeno un individuo');
|
|
return;
|
|
}
|
|
document.getElementById('massGruppoIds').value = ids.join(',');
|
|
document.getElementById('massGruppoCount').textContent = ids.length;
|
|
$('#massGruppoModal').modal('show');
|
|
}
|
|
|
|
function showMassTagModal() {
|
|
const ids = getSelectedIds();
|
|
if (ids.length === 0) {
|
|
alert('Seleziona almeno un individuo');
|
|
return;
|
|
}
|
|
document.getElementById('massTagIds').value = ids.join(',');
|
|
document.getElementById('massTagCount').textContent = ids.length;
|
|
$('#massTagModal').modal('show');
|
|
}
|
|
|
|
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 |