authorizeRead('gruppi'); $viewMode = request('view', 'table'); $perPage = in_array((int) request('perPage'), [10, 20, 25, 50, 100]) ? (int) request('perPage') : 20; $user = auth()->user(); $vista = \App\Models\VistaReport::where('user_id', $user->id) ->where('tipo', 'gruppi') ->where('is_default', true) ->first(); $userVistas = \App\Models\VistaReport::where('user_id', $user->id) ->where('tipo', 'gruppi') ->orderBy('is_default', 'desc') ->orderBy('nome') ->get(); $allColumnDefs = [ ['key' => 'nome', 'label' => 'Nome'], ['key' => 'descrizione', 'label' => 'Descrizione'], ['key' => 'diocesi', 'label' => 'Diocesi'], ['key' => 'livello', 'label' => 'Livello'], ['key' => 'padre', 'label' => 'Gruppo Padre'], ['key' => 'membri', 'label' => 'Membri'], ['key' => 'responsabili', 'label' => 'Responsabili'], ['key' => 'indirizzo', 'label' => 'Indirizzo'], ['key' => 'citta', 'label' => 'Città'], ['key' => 'telefono', 'label' => 'Telefono'], ['key' => 'email', 'label' => 'Email'], ['key' => 'tag', 'label' => 'Tag'], ]; $allColumns = array_column($allColumnDefs, 'key'); $defaultVisible = ['nome', 'livello', 'padre', 'membri', 'responsabili', 'tag']; if ($vista && !empty($vista->colonne_visibili)) { $visibleColumns = $vista->colonne_visibili; } else { $visibleColumns = $defaultVisible; } $tableColumns = array_map(fn($col) => [ 'key' => $col['key'], 'label' => $col['label'], 'visible' => in_array($col['key'], $visibleColumns), ], $allColumnDefs); $entityType = 'gruppi'; $columnWidths = $vista && $vista->colonne_larghezze ? $vista->colonne_larghezze : []; $gruppiQuery = Gruppo::with(['diocesi', 'parent', 'children', 'individui', 'avatar', 'tags']); if (request()->filled('tag')) { $tagSlugs = (array) request()->input('tag'); $gruppiQuery->withAnyTags($tagSlugs); } $allGruppi = $gruppiQuery->orderBy('nome')->get() ->map(function ($gruppo) { $depth = 0; $parent = $gruppo->parent; while ($parent) { $depth++; $parent = $parent->parent; } $gruppo->depth = $depth; return $gruppo; }); if ($viewMode === 'table') { $sorted = $this->sortGruppiHierarchically($allGruppi); $page = Paginator::resolveCurrentPage(); $gruppi = new LengthAwarePaginator( $sorted->forPage($page, $perPage)->values(), $sorted->count(), $perPage, $page, ['path' => Paginator::resolveCurrentPath()] ); $gruppi->appends(request()->except('page')); } else { $gruppi = $allGruppi; } $allTags = Tag::orderBy('name')->get(); return view('gruppi.index', compact( 'gruppi', 'allColumns', 'visibleColumns', 'viewMode', 'vista', 'allTags', 'tableColumns', 'entityType', 'columnWidths', 'userVistas' )); } protected function sortGruppiHierarchically($gruppi) { $rootGroups = $gruppi->filter(fn($g) => $g->parent_id === null)->sortBy('nome'); $sorted = collect(); foreach ($rootGroups as $root) { $sorted->push($root); $this->addChildrenRecursive($root, $gruppi, $sorted); } return $sorted; } protected function addChildrenRecursive($parent, $allGruppi, &$sorted) { $children = $allGruppi->filter(fn($g) => $g->parent_id === $parent->id)->sortBy('nome'); foreach ($children as $child) { $sorted->push($child); $this->addChildrenRecursive($child, $allGruppi, $sorted); } } public function create(Request $request) { $this->authorizeWrite('gruppi'); $diocesi = Diocesi::orderBy('nome')->get(); $allGruppi = Gruppo::orderBy('nome')->get(); $selectedParent = $request->query('parent_id') ? Gruppo::find($request->query('parent_id')) : null; $individui = Individuo::orderBy('cognome')->orderBy('nome')->get(); $tags = Tag::orderBy('name')->get(); return view('gruppi.create', compact('diocesi', 'allGruppi', 'selectedParent', 'individui', 'tags')); } public function store(Request $request) { $this->authorizeWrite('gruppi'); $data = $request->validate([ 'nome' => 'required|string|max:255', 'descrizione' => 'nullable|string', 'parent_id' => 'nullable|exists:gruppi,id', 'diocesi_id' => 'nullable|exists:diocesi,id', 'responsabile_ids' => 'nullable|array', 'responsabile_ids.*' => 'exists:individui,id', 'indirizzo_incontro' => 'nullable|string|max:500', 'cap_incontro' => 'nullable|string|max:10', 'città_incontro' => 'nullable|string|max:255', 'sigla_provincia_incontro' => 'nullable|string|max:2', 'mappa_posizione' => 'nullable|string', 'tags' => 'nullable|array', 'tags.*' => 'exists:tags,id', ]); if (!empty($data['responsabile_ids'])) { $data['responsabile_ids'] = json_encode(array_values($data['responsabile_ids'])); } $gruppo = Gruppo::create($data); if ($request->has('tags')) { $gruppo->tags()->sync($request->tags); } if ($request->has('individui')) { foreach ($request->individui as $individuoData) { if (!empty($individuoData['individuo_id'])) { $gruppo->individui()->attach($individuoData['individuo_id'], [ 'ruolo_nel_gruppo' => $individuoData['ruolo_nel_gruppo'] ?? null, 'data_adesione' => $individuoData['data_adesione'] ?? null, ]); } } } return redirect('/gruppi/' . $gruppo->id)->with('success', 'Gruppo creato con successo.'); } public function show($id) { $this->authorizeRead('gruppi'); $gruppo = Gruppo::with(['parent', 'children', 'diocesi', 'individui.contatti', 'individui.documenti', 'individui' => function ($q) { $q->withPivot('ruolo_ids', 'ruolo_nel_gruppo', 'data_adesione'); }, 'avatar', 'tags', 'eventi' => function ($q) { $q->where('is_incontro_gruppo', true); }, 'eventi.responsabili.contatti'])->findOrFail($id); return view('gruppi.show', compact('gruppo')); } public function edit($id) { $this->authorizeWrite('gruppi'); $gruppo = Gruppo::with(['individui.contatti', 'individui.documenti', 'avatar', 'tags'])->findOrFail($id); $diocesi = Diocesi::orderBy('nome')->get(); $gruppi = Gruppo::where('id', '!=', $gruppo->id)->orderBy('nome')->get(); $membri = $gruppo->individui()->withPivot('ruolo_ids', 'ruolo_nel_gruppo', 'data_adesione')->orderBy('cognome')->orderBy('nome')->get(); $tags = Tag::orderBy('name')->get(); $selectedTags = $gruppo->tags->pluck('id')->toArray(); return view('gruppi.edit', compact('gruppo', 'diocesi', 'gruppi', 'membri', 'tags', 'selectedTags')); } public function update(Request $request, $id) { $this->authorizeWrite('gruppi'); $gruppo = Gruppo::findOrFail($id); $data = $request->validate([ 'nome' => 'required|string|max:255', 'descrizione' => 'nullable|string', 'parent_id' => 'nullable|exists:gruppi,id', 'diocesi_id' => 'nullable|exists:diocesi,id', 'responsabile_ids' => 'nullable|array', 'responsabile_ids.*' => 'exists:individui,id', 'indirizzo_incontro' => 'nullable|string|max:500', 'cap_incontro' => 'nullable|string|max:10', 'città_incontro' => 'nullable|string|max:255', 'sigla_provincia_incontro' => 'nullable|string|max:2', 'mappa_posizione' => 'nullable|string', 'tags' => 'nullable|array', 'tags.*' => 'exists:tags,id', ]); if (!empty($data['responsabile_ids'])) { $data['responsabile_ids'] = json_encode(array_values($data['responsabile_ids'])); } else { $data['responsabile_ids'] = null; } $gruppo->update($data); if ($request->has('tags')) { $gruppo->tags()->sync($request->tags); } else { $gruppo->tags()->detach(); } if ($request->has('individui')) { $gruppo->individui()->detach(); foreach ($request->individui as $individuoData) { if (!empty($individuoData['individuo_id'])) { $gruppo->individui()->attach($individuoData['individuo_id'], [ 'ruolo_ids' => !empty($individuoData['ruolo_ids']) ? json_encode(array_values($individuoData['ruolo_ids'])) : null, 'data_adesione' => $individuoData['data_adesione'] ?? null, ]); } } } return redirect('/gruppi/' . $gruppo->id)->with('success', 'Gruppo aggiornato.'); } public function destroy($id) { $this->authorizeDelete('gruppi'); $gruppo = Gruppo::with('children', 'individui')->findOrFail($id); $hasChildren = $gruppo->children()->count() > 0; $isSuperAdmin = auth()->user()->isSuperAdmin(); if ($hasChildren && !$isSuperAdmin) { return redirect('/gruppi')->with('error', 'Solo il superamministratore può eliminare un gruppo con sottogruppi.'); } if ($hasChildren && $isSuperAdmin) { foreach ($gruppo->children as $child) { $this->destroyGruppo($child); } } $this->destroyGruppo($gruppo); return redirect('/gruppi')->with('success', 'Gruppo eliminato.'); } protected function destroyGruppo(Gruppo $gruppo): void { $gruppo->individui()->detach(); $gruppo->eventi()->detach(); foreach ($gruppo->documenti as $documento) { if ($documento->file_path && file_exists(storage_path('app/public/' . $documento->file_path))) { unlink(storage_path('app/public/' . $documento->file_path)); } } $gruppo->documenti()->delete(); $gruppo->delete(); } public function allGruppi() { $this->authorizeRead('gruppi'); $gruppi = Gruppo::select('id', 'nome') ->orderBy('nome') ->get() ->map(function($g) { return [ 'id' => $g->id, 'nome' => $g->nome, 'full_path' => $g->getFullPathAttribute(), ]; }); return response()->json($gruppi); } public function individuiEmail(Gruppo $gruppo) { $this->authorizeRead('gruppi'); $individui = $gruppo->individui()->with('contatti')->get(); $result = $individui->map(function($ind) { $emails = $ind->contatti->where('tipo', 'email')->pluck('valore')->toArray(); return [ 'id' => $ind->id, 'codice_id' => $ind->codice_id, 'cognome' => $ind->cognome, 'nome' => $ind->nome, 'emails' => $emails, ]; }); return response()->json($result); } public function saveVista(Request $request) { $data = $request->validate([ 'nome' => 'required|string|max:255', 'colonne_visibili' => 'nullable|array', 'colonne_visibili.*' => 'string', 'is_default' => 'nullable|boolean', ]); if (!empty($data['is_default'])) { \App\Models\VistaReport::where('user_id', auth()->id()) ->where('tipo', 'gruppi') ->where('is_default', true) ->update(['is_default' => false]); } $vista = \App\Models\VistaReport::create([ 'user_id' => auth()->id(), 'nome' => $data['nome'], 'tipo' => 'gruppi', 'colonne_visibili' => $data['colonne_visibili'] ?? [], 'is_default' => !empty($data['is_default']), ]); return response()->json(['success' => true, 'vista_id' => $vista->id]); } public function deleteVista($id) { $vista = \App\Models\VistaReport::where('user_id', auth()->id()) ->where('id', $id) ->firstOrFail(); $vista->delete(); return redirect()->route('gruppi.index')->with('success', 'Vista eliminata.'); } public function import() { $this->authorizeWrite('gruppi'); return view('gruppi.import'); } public function importStore(Request $request) { $this->authorizeWrite('gruppi'); $request->validate([ 'file' => 'required|file|mimes:csv,txt', ]); $file = $request->file('file'); $handle = fopen($file->path(), 'r'); $header = fgetcsv($handle, 1000, ','); if ($header === false) { fclose($handle); return back()->with('error', 'Il file CSV non è valido.'); } $header = array_map('trim', $header); $imported = 0; $skipped = 0; $errors = []; $rowNumber = 1; while (($row = fgetcsv($handle, 1000, ',')) !== false) { $rowNumber++; if (count($row) < count($header)) { $skipped++; continue; } $data = array_combine($header, $row); if ($data === false) { $skipped++; continue; } $data = array_map('trim', $data); $nome = $data['nome'] ?? ''; if (empty($nome)) { $skipped++; continue; } try { Gruppo::create([ 'nome' => $nome, 'descrizione' => $data['descrizione'] ?? null, 'parent_id' => !empty($data['parent_id']) ? (int) $data['parent_id'] : null, 'diocesi_id' => !empty($data['diocesi_id']) ? (int) $data['diocesi_id'] : null, 'indirizzo_incontro' => $data['indirizzo_incontro'] ?? null, 'cap_incontro' => $data['cap_incontro'] ?? null, 'città_incontro' => $data['città_incontro'] ?? null, 'sigla_provincia_incontro' => $data['sigla_provincia_incontro'] ?? null, ]); $imported++; } catch (\Exception $e) { Log::warning('Errore import gruppo riga ' . $rowNumber . ': ' . $e->getMessage()); $errors[] = 'Errore riga ' . $rowNumber . ': ' . $e->getMessage(); } } fclose($handle); $message = 'Importati ' . $imported . ' gruppi.'; if ($skipped > 0) { $message .= ' Saltate ' . $skipped . ' righe (vuote o incomplete).'; } if (count($errors) > 0) { return back()->with('error', $message . ' Errori: ' . implode('; ', $errors)); } return redirect()->route('gruppi.index')->with('success', $message); } public function downloadTemplate() { $this->authorizeRead('gruppi'); $headers = ['nome', 'descrizione', 'parent_id', 'diocesi_id', 'indirizzo_incontro', 'cap_incontro', 'città_incontro', 'sigla_provincia_incontro']; $example = ['Gruppo Giovani', 'Giovani dai 18 ai 30 anni', '', '', 'Via Roma 10', '00100', 'Roma', 'RM']; $csv = implode(',', $headers) . "\n" . implode(',', $example); return response($csv, 200, [ 'Content-Type' => 'text/csv', 'Content-Disposition' => 'attachment; filename="template_gruppi.csv"', ]); } public function massTag(Request $request) { $this->authorizeWrite('gruppi'); $idsInput = $request->input('ids', ''); $ids = is_array($idsInput) ? $idsInput : (is_string($idsInput) ? explode(',', $idsInput) : []); if (empty($ids)) { return redirect()->route('gruppi.index')->with('error', 'Nessun gruppo selezionato.'); } $data = $request->validate([ 'tags' => 'required|array', 'tags.*' => 'exists:tags,id', 'mode' => 'required|in:assign,remove', ]); $tagIds = $data['tags']; $mode = $data['mode']; $count = 0; Gruppo::whereIn('id', $ids)->chunk(100, function ($gruppi) use ($tagIds, $mode, &$count) { foreach ($gruppi as $gruppo) { if ($mode === 'assign') { $gruppo->tags()->syncWithoutDetaching($tagIds); } else { $gruppo->tags()->detach($tagIds); } $count++; } }); $actionLabel = $mode === 'assign' ? 'assegnati' : 'rimossi'; return redirect()->route('gruppi.index')->with('success', "Tag {$actionLabel} per {$count} gruppi."); } public function massElimina(Request $request) { $this->authorizeDelete('gruppi'); $ids = $request->input('ids', []); if (!is_array($ids) || empty($ids)) { return redirect()->route('gruppi.index')->with('error', 'Nessun gruppo selezionato.'); } $gruppi = Gruppo::whereIn('id', $ids)->get(); $count = 0; foreach ($gruppi as $gruppo) { $gruppo->individui()->detach(); $gruppo->delete(); $count++; } return redirect()->route('gruppi.index')->with('success', "{$count} gruppi eliminati con successo."); } }