sistemazione reports - cambio password - profilo utente
This commit is contained in:
@@ -1 +1 @@
|
|||||||
{"version":2,"defects":{"Tests\\Feature\\ExampleTest::test_the_application_returns_a_successful_response":7,"Tests\\Feature\\CalendarEventsTest::test_generate_settimanale_events_produces_weekly_occurrences":7},"times":{"Tests\\Unit\\ExampleTest::test_that_true_is_true":0.005,"Tests\\Feature\\ExampleTest::test_the_application_returns_a_successful_response":0.049,"Tests\\Feature\\CalendarEventsTest::test_find_first_sunday_of_june_2026":0.017,"Tests\\Feature\\CalendarEventsTest::test_find_second_saturday_of_june_2026":0.001,"Tests\\Feature\\CalendarEventsTest::test_find_fifth_sunday_returns_null_when_not_in_month":0.001,"Tests\\Feature\\CalendarEventsTest::test_generate_mensile_events_for_all_months":0.013,"Tests\\Feature\\CalendarEventsTest::test_generate_mensile_events_respects_selected_months":0.001,"Tests\\Feature\\CalendarEventsTest::test_generate_annuale_events_produces_correct_dates":0.001,"Tests\\Feature\\CalendarEventsTest::test_generate_settimanale_events_produces_weekly_occurrences":0.003}}
|
{"version":2,"defects":{"Tests\\Feature\\ExampleTest::test_the_application_returns_a_successful_response":7,"Tests\\Feature\\CalendarEventsTest::test_generate_settimanale_events_produces_weekly_occurrences":7},"times":{"Tests\\Unit\\ExampleTest::test_that_true_is_true":0.005,"Tests\\Feature\\ExampleTest::test_the_application_returns_a_successful_response":0.062,"Tests\\Feature\\CalendarEventsTest::test_find_first_sunday_of_june_2026":0.037,"Tests\\Feature\\CalendarEventsTest::test_find_second_saturday_of_june_2026":0.001,"Tests\\Feature\\CalendarEventsTest::test_find_fifth_sunday_returns_null_when_not_in_month":0.001,"Tests\\Feature\\CalendarEventsTest::test_generate_mensile_events_for_all_months":0.023,"Tests\\Feature\\CalendarEventsTest::test_generate_mensile_events_respects_selected_months":0.002,"Tests\\Feature\\CalendarEventsTest::test_generate_annuale_events_produces_correct_dates":0.001,"Tests\\Feature\\CalendarEventsTest::test_generate_settimanale_events_produces_weekly_occurrences":0.006}}
|
||||||
@@ -720,3 +720,32 @@ php artisan route:list --name=email
|
|||||||
- `resources/views/impostazioni/index.blade.php`: pill Mittenti Aggiuntivi, tab-pane email-mittenti, modale sender modifica/crea, funzioni JS (resetSenderForm, editSender, deleteSender, testSenderSmtp)
|
- `resources/views/impostazioni/index.blade.php`: pill Mittenti Aggiuntivi, tab-pane email-mittenti, modale sender modifica/crea, funzioni JS (resetSenderForm, editSender, deleteSender, testSenderSmtp)
|
||||||
- `app/Http/Controllers/Admin/EmailSettingsController.php`: senderDestroy() con expectsJson()
|
- `app/Http/Controllers/Admin/EmailSettingsController.php`: senderDestroy() con expectsJson()
|
||||||
- `resources/views/layouts/adminlte.blade.php`: rimossa voce Impostazioni Email dal sidebar
|
- `resources/views/layouts/adminlte.blade.php`: rimossa voce Impostazioni Email dal sidebar
|
||||||
|
|
||||||
|
(Last updated: 27 Maggio 2026 - Password reset, profile, report enhancements)
|
||||||
|
|
||||||
|
### 27 Maggio 2026 - Password Reset & Profile
|
||||||
|
|
||||||
|
- **Rimosso registration**: Eliminata view `auth/register.blade.php`, rimossi metodi register da AuthController; rimosso link "Non hai un account? Registrati" dalla login
|
||||||
|
- **Aggiunto password reset**:
|
||||||
|
- Routes password.request, password.email, password.reset, password.update in routes/web.php
|
||||||
|
- Metodi showForgotForm(), sendResetLink(), showResetForm(), reset() in AuthController
|
||||||
|
- `User` model: implementato CanResetPassword con sendPasswordResetNotification() custom via Symfony Mailer su EmailSetting SMTP
|
||||||
|
- Views: auth/forgot-password.blade.php, auth/reset-password.blade.php, auth/emails/reset-password.blade.php
|
||||||
|
- **Login fix**: Checkbox "Ricordami" ora usa `$request->has('remember')` invece di hardcoded false
|
||||||
|
- **Login update**: Aggiunto link "Password dimenticata?" che punta a `route('password.request')`
|
||||||
|
- **ProfileController**: Creato con `show()` (view auth.profile) e `updatePassword()` con validazione `current_password` e `Password::min(8)`
|
||||||
|
- **Layout fix**: Link "Profilo" in adminlte.blade.php ora punta a `route('profile')` invece di `#`
|
||||||
|
- **Profile view**: Già esistente `auth/profile.blade.php` con form cambio password funzionante
|
||||||
|
|
||||||
|
### 27 Maggio 2026 - Report Enhancements
|
||||||
|
|
||||||
|
- **ReportColumnRegistry** (`app/Helpers/ReportColumnRegistry.php`): Registry centralizzato di colonne disponibili per entity type (individui, gruppi, eventi, documenti, contatti) con supporto per colonne dirette, accessor e relazioni (dot notation tipo `contatti.email`, `gruppi.nome`). Metodo `resolveRelationValue()` per risolvere valori su relazioni nidificate.
|
||||||
|
- **ReportController fix**:
|
||||||
|
- `storeCustom()`: colonne salvate correttamente come array in config['columns']; supporto per array da Select2 o comma-separated
|
||||||
|
- `runCustomReport()`: eager loading automatico per relazioni in colonne dot notation; risoluzione valori via `ReportColumnRegistry::resolveRelationValue()`
|
||||||
|
- `exportCSV()`: supporto per custom_id con filename da slug del nome report; BOM UTF-8 per Excel
|
||||||
|
- `resolveReport()`: metodo centralizzato per match report type + tipologia filter
|
||||||
|
- `run()`: supporto per custom_id per eseguire report personalizzati dalla stessa route
|
||||||
|
- **6 nuovi report predefiniti** (18 totali): documenti_completo, scadenze_documenti, individui_completo, gruppi_completo, eventi_completo, contatti_completo
|
||||||
|
- **Tipologia filter**: I report `documenti_completo` e `scadenze_documenti` hanno dropdown inline Select2 per filtro tipologia nella card dell'index
|
||||||
|
- **Report index view**: Select2 multiselect per colonne, Select2 tipologia filter, sort direction, nuovi bottoni CSV export per custom reports
|
||||||
|
|||||||
@@ -0,0 +1,200 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace App\Helpers;
|
||||||
|
|
||||||
|
class ReportColumnRegistry
|
||||||
|
{
|
||||||
|
private static array $columns = [];
|
||||||
|
|
||||||
|
public static function getColumns(string $entityType): array
|
||||||
|
{
|
||||||
|
if (empty(self::$columns)) {
|
||||||
|
self::boot();
|
||||||
|
}
|
||||||
|
|
||||||
|
return self::$columns[$entityType] ?? [];
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function allEntityTypes(): array
|
||||||
|
{
|
||||||
|
if (empty(self::$columns)) {
|
||||||
|
self::boot();
|
||||||
|
}
|
||||||
|
|
||||||
|
return array_keys(self::$columns);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function getColumnLabel(string $entityType, string $column): ?string
|
||||||
|
{
|
||||||
|
$cols = self::getColumns($entityType);
|
||||||
|
return $cols[$column]['label'] ?? null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function resolveRelationValue(object $item, string $column): mixed
|
||||||
|
{
|
||||||
|
$parts = explode('.', $column);
|
||||||
|
|
||||||
|
if (count($parts) === 1) {
|
||||||
|
return $item->{$column} ?? '';
|
||||||
|
}
|
||||||
|
|
||||||
|
$current = $item;
|
||||||
|
|
||||||
|
for ($i = 0; $i < count($parts); $i++) {
|
||||||
|
$part = $parts[$i];
|
||||||
|
|
||||||
|
if ($current === null || $current === '') {
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($current instanceof \Illuminate\Support\Collection) {
|
||||||
|
$remaining = array_slice($parts, $i);
|
||||||
|
$filterKey = $remaining[0] ?? null;
|
||||||
|
|
||||||
|
if ($filterKey && $current->isNotEmpty() && $current->first()->getAttribute('tipo') !== null) {
|
||||||
|
return $current->where('tipo', $filterKey)
|
||||||
|
->pluck('valore')
|
||||||
|
->filter()
|
||||||
|
->implode(', ');
|
||||||
|
}
|
||||||
|
|
||||||
|
$pluckKey = implode('.', $remaining);
|
||||||
|
return $current->pluck($pluckKey)->filter()->implode(', ');
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($current instanceof \Illuminate\Database\Eloquent\Model) {
|
||||||
|
try {
|
||||||
|
$relation = $current->{$part};
|
||||||
|
if ($relation instanceof \Illuminate\Support\Collection) {
|
||||||
|
$current = $relation;
|
||||||
|
} elseif ($relation instanceof \Illuminate\Database\Eloquent\Model) {
|
||||||
|
$current = $relation;
|
||||||
|
} else {
|
||||||
|
$current = $relation;
|
||||||
|
}
|
||||||
|
} catch (\Exception) {
|
||||||
|
try {
|
||||||
|
$current = $current->getAttribute($part) ?? '';
|
||||||
|
} catch (\Exception) {
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} elseif (is_array($current)) {
|
||||||
|
$current = $current[$part] ?? '';
|
||||||
|
} elseif (is_object($current)) {
|
||||||
|
$current = $current->{$part} ?? '';
|
||||||
|
} else {
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($current instanceof \Carbon\Carbon) {
|
||||||
|
return $current->format('d/m/Y');
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($current instanceof \Illuminate\Support\Collection) {
|
||||||
|
return $current->implode(', ');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (is_bool($current)) {
|
||||||
|
return $current ? 'Si' : 'No';
|
||||||
|
}
|
||||||
|
|
||||||
|
return $current ?? '';
|
||||||
|
}
|
||||||
|
|
||||||
|
private static function boot(): void
|
||||||
|
{
|
||||||
|
self::$columns = [
|
||||||
|
'individui' => [
|
||||||
|
'id' => ['label' => 'ID', 'type' => 'direct'],
|
||||||
|
'codice_id' => ['label' => 'Codice', 'type' => 'direct'],
|
||||||
|
'cognome' => ['label' => 'Cognome', 'type' => 'direct'],
|
||||||
|
'nome' => ['label' => 'Nome', 'type' => 'direct'],
|
||||||
|
'nome_completo' => ['label' => 'Nome Completo', 'type' => 'accessor'],
|
||||||
|
'data_nascita' => ['label' => 'Data Nascita', 'type' => 'direct'],
|
||||||
|
'genere' => ['label' => 'Genere', 'type' => 'direct'],
|
||||||
|
'indirizzo' => ['label' => 'Indirizzo', 'type' => 'direct'],
|
||||||
|
'cap' => ['label' => 'CAP', 'type' => 'direct'],
|
||||||
|
'città' => ['label' => 'Città', 'type' => 'direct'],
|
||||||
|
'sigla_provincia' => ['label' => 'Provincia', 'type' => 'direct'],
|
||||||
|
'tipo_documento' => ['label' => 'Tipo Documento', 'type' => 'direct'],
|
||||||
|
'numero_documento' => ['label' => 'Numero Documento', 'type' => 'direct'],
|
||||||
|
'scadenza_documento' => ['label' => 'Scadenza Documento', 'type' => 'direct'],
|
||||||
|
'note' => ['label' => 'Note', 'type' => 'direct'],
|
||||||
|
'created_at' => ['label' => 'Data Creazione', 'type' => 'direct'],
|
||||||
|
'updated_at' => ['label' => 'Ultimo Aggiornamento', 'type' => 'direct'],
|
||||||
|
'email_primaria' => ['label' => 'Email Primaria', 'type' => 'accessor'],
|
||||||
|
'telefono_primario' => ['label' => 'Telefono Primario', 'type' => 'accessor'],
|
||||||
|
'contatti.email' => ['label' => 'Email (da contatti)', 'type' => 'relation'],
|
||||||
|
'contatti.telefono' => ['label' => 'Telefono (da contatti)', 'type' => 'relation'],
|
||||||
|
'contatti.cellulare' => ['label' => 'Cellulare (da contatti)', 'type' => 'relation'],
|
||||||
|
'gruppi.nome' => ['label' => 'Gruppi', 'type' => 'relation'],
|
||||||
|
],
|
||||||
|
|
||||||
|
'gruppi' => [
|
||||||
|
'id' => ['label' => 'ID', 'type' => 'direct'],
|
||||||
|
'nome' => ['label' => 'Nome', 'type' => 'direct'],
|
||||||
|
'descrizione' => ['label' => 'Descrizione', 'type' => 'direct'],
|
||||||
|
'parent_id' => ['label' => 'ID Gruppo Padre', 'type' => 'direct'],
|
||||||
|
'diocesi_id' => ['label' => 'ID Diocesi', 'type' => 'direct'],
|
||||||
|
'indirizzo_incontro' => ['label' => 'Indirizzo Incontro', 'type' => 'direct'],
|
||||||
|
'cap_incontro' => ['label' => 'CAP Incontro', 'type' => 'direct'],
|
||||||
|
'città_incontro' => ['label' => 'Città Incontro', 'type' => 'direct'],
|
||||||
|
'sigla_provincia_incontro' => ['label' => 'Provincia Incontro', 'type' => 'direct'],
|
||||||
|
'mappa_posizione' => ['label' => 'Mappa', 'type' => 'direct'],
|
||||||
|
'created_at' => ['label' => 'Data Creazione', 'type' => 'direct'],
|
||||||
|
'updated_at' => ['label' => 'Ultimo Aggiornamento', 'type' => 'direct'],
|
||||||
|
'full_path' => ['label' => 'Percorso Completo', 'type' => 'accessor'],
|
||||||
|
'parent.nome' => ['label' => 'Gruppo Padre', 'type' => 'relation'],
|
||||||
|
'diocesi.nome' => ['label' => 'Diocesi', 'type' => 'relation'],
|
||||||
|
'individui_count' => ['label' => 'Numero Membri', 'type' => 'relation'],
|
||||||
|
],
|
||||||
|
|
||||||
|
'eventi' => [
|
||||||
|
'id' => ['label' => 'ID', 'type' => 'direct'],
|
||||||
|
'nome_evento' => ['label' => 'Nome Evento', 'type' => 'direct'],
|
||||||
|
'descrizione_evento' => ['label' => 'Descrizione', 'type' => 'direct'],
|
||||||
|
'tipo_evento' => ['label' => 'Tipo Evento', 'type' => 'direct'],
|
||||||
|
'tipo_recorrenza' => ['label' => 'Ricorrenza', 'type' => 'direct'],
|
||||||
|
'data_specifica' => ['label' => 'Data', 'type' => 'direct'],
|
||||||
|
'ora_inizio' => ['label' => 'Ora Inizio', 'type' => 'direct'],
|
||||||
|
'durata_minuti' => ['label' => 'Durata (minuti)', 'type' => 'direct'],
|
||||||
|
'luogo_indirizzo' => ['label' => 'Luogo', 'type' => 'direct'],
|
||||||
|
'luogo_url_maps' => ['label' => 'URL Mappe', 'type' => 'direct'],
|
||||||
|
'note' => ['label' => 'Note', 'type' => 'direct'],
|
||||||
|
'is_incontro_gruppo' => ['label' => 'Incontro Gruppo', 'type' => 'direct'],
|
||||||
|
'created_at' => ['label' => 'Data Creazione', 'type' => 'direct'],
|
||||||
|
'periodicita_label' => ['label' => 'Periodicità', 'type' => 'accessor'],
|
||||||
|
'info_ricorrenza' => ['label' => 'Info Ricorrenza', 'type' => 'accessor'],
|
||||||
|
'gruppi.nome' => ['label' => 'Gruppi', 'type' => 'relation'],
|
||||||
|
'responsabili.nome_completo' => ['label' => 'Responsabili', 'type' => 'relation'],
|
||||||
|
],
|
||||||
|
|
||||||
|
'documenti' => [
|
||||||
|
'id' => ['label' => 'ID', 'type' => 'direct'],
|
||||||
|
'nome_file' => ['label' => 'Nome File', 'type' => 'direct'],
|
||||||
|
'tipologia' => ['label' => 'Tipologia', 'type' => 'direct'],
|
||||||
|
'visibilita' => ['label' => 'Visibilità', 'type' => 'direct'],
|
||||||
|
'mime_type' => ['label' => 'Tipo MIME', 'type' => 'direct'],
|
||||||
|
'dimensione' => ['label' => 'Dimensione (bytes)', 'type' => 'direct'],
|
||||||
|
'note' => ['label' => 'Note', 'type' => 'direct'],
|
||||||
|
'created_at' => ['label' => 'Data Caricamento', 'type' => 'direct'],
|
||||||
|
'user.name' => ['label' => 'Caricato Da', 'type' => 'relation'],
|
||||||
|
],
|
||||||
|
|
||||||
|
'contatti' => [
|
||||||
|
'id' => ['label' => 'ID', 'type' => 'direct'],
|
||||||
|
'tipo' => ['label' => 'Tipo', 'type' => 'direct'],
|
||||||
|
'valore' => ['label' => 'Valore', 'type' => 'direct'],
|
||||||
|
'etichetta' => ['label' => 'Etichetta', 'type' => 'direct'],
|
||||||
|
'is_primary' => ['label' => 'Primario', 'type' => 'direct'],
|
||||||
|
'created_at' => ['label' => 'Data Creazione', 'type' => 'direct'],
|
||||||
|
'individuo.nome_completo' => ['label' => 'Individuo', 'type' => 'relation'],
|
||||||
|
'individuo.codice_id' => ['label' => 'Codice Individuo', 'type' => 'relation'],
|
||||||
|
],
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -4,11 +4,14 @@ namespace App\Http\Controllers\Auth;
|
|||||||
|
|
||||||
use App\Http\Controllers\Controller;
|
use App\Http\Controllers\Controller;
|
||||||
use App\Models\ActivityLog;
|
use App\Models\ActivityLog;
|
||||||
|
use App\Models\EmailSetting;
|
||||||
use App\Models\User;
|
use App\Models\User;
|
||||||
use Illuminate\Http\Request;
|
use Illuminate\Http\Request;
|
||||||
use Illuminate\Support\Facades\Auth;
|
use Illuminate\Support\Facades\Auth;
|
||||||
use Illuminate\Support\Facades\Hash;
|
use Illuminate\Support\Facades\Hash;
|
||||||
|
use Illuminate\Support\Facades\Password;
|
||||||
use Illuminate\Validation\ValidationException;
|
use Illuminate\Validation\ValidationException;
|
||||||
|
use Symfony\Component\Mime\Address;
|
||||||
|
|
||||||
class AuthController extends Controller
|
class AuthController extends Controller
|
||||||
{
|
{
|
||||||
@@ -32,11 +35,9 @@ class AuthController extends Controller
|
|||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (Auth::attempt($credentials)) {
|
if (Auth::attempt($credentials, $request->has('remember'))) {
|
||||||
$request->session()->regenerate();
|
$request->session()->regenerate();
|
||||||
|
|
||||||
ActivityLog::logLogin();
|
ActivityLog::logLogin();
|
||||||
|
|
||||||
return redirect()->intended('/dashboard');
|
return redirect()->intended('/dashboard');
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -45,63 +46,6 @@ class AuthController extends Controller
|
|||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function showRegisterForm()
|
|
||||||
{
|
|
||||||
return view('auth.register');
|
|
||||||
}
|
|
||||||
|
|
||||||
public function register(Request $request)
|
|
||||||
{
|
|
||||||
$data = $request->validate([
|
|
||||||
'name' => 'required|string|max:255',
|
|
||||||
'email' => 'required|email|unique:users',
|
|
||||||
'password' => 'required|string|min:8|confirmed',
|
|
||||||
]);
|
|
||||||
|
|
||||||
$isFirstUser = User::count() === 0;
|
|
||||||
|
|
||||||
$defaultPermissions = [
|
|
||||||
'individui' => 1,
|
|
||||||
'gruppi' => 1,
|
|
||||||
'eventi' => 1,
|
|
||||||
'documenti' => 1,
|
|
||||||
'mailing' => 1,
|
|
||||||
'viste' => 1,
|
|
||||||
'report' => 0,
|
|
||||||
'settings' => 0,
|
|
||||||
];
|
|
||||||
|
|
||||||
if ($isFirstUser) {
|
|
||||||
$defaultPermissions = [
|
|
||||||
'individui' => 2,
|
|
||||||
'gruppi' => 2,
|
|
||||||
'eventi' => 2,
|
|
||||||
'documenti' => 2,
|
|
||||||
'mailing' => 2,
|
|
||||||
'viste' => 2,
|
|
||||||
'report' => 0,
|
|
||||||
'settings' => 0,
|
|
||||||
];
|
|
||||||
}
|
|
||||||
|
|
||||||
$user = User::create([
|
|
||||||
'name' => $data['name'],
|
|
||||||
'email' => $data['email'],
|
|
||||||
'password' => Hash::make($data['password']),
|
|
||||||
'is_admin' => $isFirstUser,
|
|
||||||
'status' => User::STATUS_ACTIVE,
|
|
||||||
'permissions' => $defaultPermissions,
|
|
||||||
]);
|
|
||||||
|
|
||||||
ActivityLog::log('create', 'utenti', 'Registrato nuovo utente: ' . $user->name, [
|
|
||||||
'user_id' => $user->id,
|
|
||||||
]);
|
|
||||||
|
|
||||||
Auth::login($user);
|
|
||||||
|
|
||||||
return redirect('/dashboard');
|
|
||||||
}
|
|
||||||
|
|
||||||
public function logout(Request $request)
|
public function logout(Request $request)
|
||||||
{
|
{
|
||||||
Auth::logout();
|
Auth::logout();
|
||||||
@@ -109,4 +53,53 @@ class AuthController extends Controller
|
|||||||
$request->session()->regenerateToken();
|
$request->session()->regenerateToken();
|
||||||
return redirect('/');
|
return redirect('/');
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
public function showForgotForm()
|
||||||
|
{
|
||||||
|
return view('auth.forgot-password');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function sendResetLink(Request $request)
|
||||||
|
{
|
||||||
|
$request->validate(['email' => 'required|email']);
|
||||||
|
|
||||||
|
$user = User::where('email', $request->email)->first();
|
||||||
|
if (!$user) {
|
||||||
|
return back()->withErrors(['email' => 'Nessun account trovato con questa email.']);
|
||||||
|
}
|
||||||
|
|
||||||
|
$token = Password::broker()->createToken($user);
|
||||||
|
$user->sendPasswordResetNotification($token);
|
||||||
|
|
||||||
|
return back()->with('success', 'Ti abbiamo inviato un link per il reset della password via email.');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function showResetForm(string $token)
|
||||||
|
{
|
||||||
|
return view('auth.reset-password', ['token' => $token]);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function reset(Request $request)
|
||||||
|
{
|
||||||
|
$request->validate([
|
||||||
|
'token' => 'required',
|
||||||
|
'email' => 'required|email',
|
||||||
|
'password' => 'required|string|min:8|confirmed',
|
||||||
|
]);
|
||||||
|
|
||||||
|
$status = Password::broker()->reset(
|
||||||
|
$request->only('email', 'password', 'password_confirmation', 'token'),
|
||||||
|
function (User $user, string $password) {
|
||||||
|
$user->forceFill([
|
||||||
|
'password' => Hash::make($password),
|
||||||
|
])->save();
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
if ($status === Password::PASSWORD_RESET) {
|
||||||
|
return redirect()->route('login')->with('success', 'Password reimpostata con successo. Accedi con la nuova password.');
|
||||||
|
}
|
||||||
|
|
||||||
|
return back()->withErrors(['email' => [__($status)]]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -0,0 +1,32 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace App\Http\Controllers\Auth;
|
||||||
|
|
||||||
|
use App\Http\Controllers\Controller;
|
||||||
|
use Illuminate\Http\Request;
|
||||||
|
use Illuminate\Support\Facades\Hash;
|
||||||
|
use Illuminate\Validation\Rules\Password;
|
||||||
|
|
||||||
|
class ProfileController extends Controller
|
||||||
|
{
|
||||||
|
public function show()
|
||||||
|
{
|
||||||
|
return view('auth.profile');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function updatePassword(Request $request)
|
||||||
|
{
|
||||||
|
$request->validate([
|
||||||
|
'current_password' => ['required', 'current_password'],
|
||||||
|
'password' => ['required', 'confirmed', Password::min(8)],
|
||||||
|
]);
|
||||||
|
|
||||||
|
$request->user()->update([
|
||||||
|
'password' => Hash::make($request->password),
|
||||||
|
]);
|
||||||
|
|
||||||
|
return back()->with('success', 'Password aggiornata con successo.');
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,15 +1,21 @@
|
|||||||
<?php
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
namespace App\Http\Controllers;
|
namespace App\Http\Controllers;
|
||||||
|
|
||||||
use App\Models\Individuo;
|
use App\Helpers\ReportColumnRegistry;
|
||||||
use App\Models\Gruppo;
|
use App\Models\Gruppo;
|
||||||
|
use App\Models\Individuo;
|
||||||
use App\Models\Evento;
|
use App\Models\Evento;
|
||||||
use App\Models\Documento;
|
use App\Models\Documento;
|
||||||
use App\Models\MailingList;
|
use App\Models\MailingList;
|
||||||
use App\Models\Contatto;
|
use App\Models\Contatto;
|
||||||
|
use App\Models\TipologiaDocumento;
|
||||||
|
use App\Models\ReportCustom;
|
||||||
use Illuminate\Http\Request;
|
use Illuminate\Http\Request;
|
||||||
use Illuminate\Support\Facades\DB;
|
use Illuminate\Support\Facades\DB;
|
||||||
|
use Illuminate\Support\Str;
|
||||||
|
|
||||||
class ReportController extends Controller
|
class ReportController extends Controller
|
||||||
{
|
{
|
||||||
@@ -30,11 +36,24 @@ class ReportController extends Controller
|
|||||||
|
|
||||||
$prebuiltReports = $this->getPrebuiltReports();
|
$prebuiltReports = $this->getPrebuiltReports();
|
||||||
|
|
||||||
$customReports = \App\Models\ReportCustom::where('user_id', auth()->id())
|
$customReports = ReportCustom::where('user_id', auth()->id())
|
||||||
->orderBy('created_at', 'desc')
|
->orderBy('created_at', 'desc')
|
||||||
->get();
|
->get();
|
||||||
|
|
||||||
return view('report.index', compact('stats', 'prebuiltReports', 'customReports'));
|
$tipologieDocumento = TipologiaDocumento::attive()->pluck('nome');
|
||||||
|
|
||||||
|
$columnOptions = [];
|
||||||
|
foreach (ReportColumnRegistry::allEntityTypes() as $type) {
|
||||||
|
$columnOptions[$type] = ReportColumnRegistry::getColumns($type);
|
||||||
|
}
|
||||||
|
|
||||||
|
return view('report.index', compact(
|
||||||
|
'stats',
|
||||||
|
'prebuiltReports',
|
||||||
|
'customReports',
|
||||||
|
'tipologieDocumento',
|
||||||
|
'columnOptions'
|
||||||
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
public function run(Request $request)
|
public function run(Request $request)
|
||||||
@@ -44,28 +63,24 @@ class ReportController extends Controller
|
|||||||
}
|
}
|
||||||
|
|
||||||
$reportType = $request->input('report');
|
$reportType = $request->input('report');
|
||||||
|
$customReportId = $request->input('custom_id');
|
||||||
|
|
||||||
$data = match ($reportType) {
|
if ($customReportId) {
|
||||||
'individui_per_genere' => $this->reportIndividuiPerGenere(),
|
$report = ReportCustom::findOrFail($customReportId);
|
||||||
'individui_per_eta' => $this->reportIndividuiPerEta(),
|
if ($report->user_id !== auth()->id()) {
|
||||||
'gruppi_gerarchia' => $this->reportGruppiGerarchia(),
|
abort(403);
|
||||||
'gruppi_membri' => $this->reportGruppiMembri(),
|
}
|
||||||
'eventi_calendario' => $this->reportEventiCalendario(),
|
$data = $this->runCustomReport($report);
|
||||||
'documenti_per_tipo' => $this->reportDocumentiPerTipo(),
|
return view('report.result', compact('data', 'reportType', 'report'));
|
||||||
'contatti_per_tipo' => $this->reportContattiPerTipo(),
|
}
|
||||||
'mailing_list_dettaglio' => $this->reportMailingListDettaglio(),
|
|
||||||
'individui_senza_contatti' => $this->reportIndividuiSenzaContatti(),
|
$data = $this->resolveReport($request);
|
||||||
'individui_senza_gruppo' => $this->reportIndividuiSenzaGruppo(),
|
|
||||||
'eventi_per_gruppo' => $this->reportEventiPerGruppo(),
|
|
||||||
'documenti_orfani' => $this->reportDocumentiOrfani(),
|
|
||||||
default => null,
|
|
||||||
};
|
|
||||||
|
|
||||||
if (!$data) {
|
if (!$data) {
|
||||||
return redirect()->route('report.index')->with('error', 'Report non trovato.');
|
return redirect()->route('report.index')->with('error', 'Report non trovato.');
|
||||||
}
|
}
|
||||||
|
|
||||||
return view('report.result', compact('reportType', 'data'));
|
return view('report.result', compact('data', 'reportType'));
|
||||||
}
|
}
|
||||||
|
|
||||||
public function runCustom($id)
|
public function runCustom($id)
|
||||||
@@ -74,8 +89,8 @@ class ReportController extends Controller
|
|||||||
abort(403);
|
abort(403);
|
||||||
}
|
}
|
||||||
|
|
||||||
$report = \App\Models\ReportCustom::findOrFail($id);
|
$report = ReportCustom::findOrFail($id);
|
||||||
|
|
||||||
if ($report->user_id !== auth()->id()) {
|
if ($report->user_id !== auth()->id()) {
|
||||||
abort(403);
|
abort(403);
|
||||||
}
|
}
|
||||||
@@ -95,15 +110,38 @@ class ReportController extends Controller
|
|||||||
'nome' => 'required|string|max:255',
|
'nome' => 'required|string|max:255',
|
||||||
'descrizione' => 'nullable|string',
|
'descrizione' => 'nullable|string',
|
||||||
'tipo_report' => 'required|string',
|
'tipo_report' => 'required|string',
|
||||||
'config' => 'nullable|array',
|
|
||||||
]);
|
]);
|
||||||
|
|
||||||
\App\Models\ReportCustom::create([
|
$config = [];
|
||||||
|
|
||||||
|
if ($request->has('columns')) {
|
||||||
|
$columns = $request->input('columns');
|
||||||
|
$config['columns'] = is_array($columns) ? $columns : array_map('trim', explode(',', $columns));
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($request->filled('sort_by')) {
|
||||||
|
$config['sort_by'] = $request->input('sort_by');
|
||||||
|
$config['sort_direction'] = $request->input('sort_direction', 'asc');
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($request->filled('limit')) {
|
||||||
|
$config['limit'] = (int) $request->input('limit');
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($request->filled('search')) {
|
||||||
|
$config['search'] = $request->input('search');
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($request->has('search_columns')) {
|
||||||
|
$config['search_columns'] = $request->input('search_columns');
|
||||||
|
}
|
||||||
|
|
||||||
|
ReportCustom::create([
|
||||||
'user_id' => auth()->id(),
|
'user_id' => auth()->id(),
|
||||||
'nome' => $data['nome'],
|
'nome' => $data['nome'],
|
||||||
'descrizione' => $data['descrizione'] ?? null,
|
'descrizione' => $data['descrizione'] ?? null,
|
||||||
'tipo_report' => $data['tipo_report'],
|
'tipo_report' => $data['tipo_report'],
|
||||||
'config' => $data['config'] ?? [],
|
'config' => $config,
|
||||||
]);
|
]);
|
||||||
|
|
||||||
return redirect()->route('report.index')->with('success', 'Report personalizzato creato.');
|
return redirect()->route('report.index')->with('success', 'Report personalizzato creato.');
|
||||||
@@ -115,7 +153,7 @@ class ReportController extends Controller
|
|||||||
abort(403);
|
abort(403);
|
||||||
}
|
}
|
||||||
|
|
||||||
$report = \App\Models\ReportCustom::where('user_id', auth()->id())->findOrFail($id);
|
$report = ReportCustom::where('user_id', auth()->id())->findOrFail($id);
|
||||||
$report->delete();
|
$report->delete();
|
||||||
|
|
||||||
return redirect()->route('report.index')->with('success', 'Report personalizzato eliminato.');
|
return redirect()->route('report.index')->with('success', 'Report personalizzato eliminato.');
|
||||||
@@ -127,39 +165,24 @@ class ReportController extends Controller
|
|||||||
abort(403);
|
abort(403);
|
||||||
}
|
}
|
||||||
|
|
||||||
$reportType = $request->input('report');
|
|
||||||
$customReportId = $request->input('custom_id');
|
$customReportId = $request->input('custom_id');
|
||||||
|
|
||||||
if ($customReportId) {
|
if ($customReportId) {
|
||||||
$report = \App\Models\ReportCustom::findOrFail($customReportId);
|
$report = ReportCustom::findOrFail($customReportId);
|
||||||
if ($report->user_id !== auth()->id()) {
|
if ($report->user_id !== auth()->id()) {
|
||||||
abort(403);
|
abort(403);
|
||||||
}
|
}
|
||||||
$data = $this->runCustomReport($report);
|
$data = $this->runCustomReport($report);
|
||||||
return view('report.print-preview', compact('reportType', 'data', 'report'));
|
return view('report.print-preview', compact('data', 'report'));
|
||||||
}
|
}
|
||||||
|
|
||||||
$data = match ($reportType) {
|
$data = $this->resolveReport($request);
|
||||||
'individui_per_genere' => $this->reportIndividuiPerGenere(),
|
|
||||||
'individui_per_eta' => $this->reportIndividuiPerEta(),
|
|
||||||
'gruppi_gerarchia' => $this->reportGruppiGerarchia(),
|
|
||||||
'gruppi_membri' => $this->reportGruppiMembri(),
|
|
||||||
'eventi_calendario' => $this->reportEventiCalendario(),
|
|
||||||
'documenti_per_tipo' => $this->reportDocumentiPerTipo(),
|
|
||||||
'contatti_per_tipo' => $this->reportContattiPerTipo(),
|
|
||||||
'mailing_list_dettaglio' => $this->reportMailingListDettaglio(),
|
|
||||||
'individui_senza_contatti' => $this->reportIndividuiSenzaContatti(),
|
|
||||||
'individui_senza_gruppo' => $this->reportIndividuiSenzaGruppo(),
|
|
||||||
'eventi_per_gruppo' => $this->reportEventiPerGruppo(),
|
|
||||||
'documenti_orfani' => $this->reportDocumentiOrfani(),
|
|
||||||
default => null,
|
|
||||||
};
|
|
||||||
|
|
||||||
if (!$data) {
|
if (!$data) {
|
||||||
return redirect()->route('report.index')->with('error', 'Report non trovato.');
|
return redirect()->route('report.index')->with('error', 'Report non trovato.');
|
||||||
}
|
}
|
||||||
|
|
||||||
return view('report.print-preview', compact('reportType', 'data'));
|
return view('report.print-preview', compact('data'));
|
||||||
}
|
}
|
||||||
|
|
||||||
public function exportCSV(Request $request)
|
public function exportCSV(Request $request)
|
||||||
@@ -168,9 +191,49 @@ class ReportController extends Controller
|
|||||||
abort(403);
|
abort(403);
|
||||||
}
|
}
|
||||||
|
|
||||||
$reportType = $request->input('report');
|
$customReportId = $request->input('custom_id');
|
||||||
|
|
||||||
$data = match ($reportType) {
|
if ($customReportId) {
|
||||||
|
$report = ReportCustom::findOrFail($customReportId);
|
||||||
|
if ($report->user_id !== auth()->id()) {
|
||||||
|
abort(403);
|
||||||
|
}
|
||||||
|
$data = $this->runCustomReport($report);
|
||||||
|
$filename = 'report_' . Str::slug($report->nome) . '_' . now()->format('Y-m-d') . '.csv';
|
||||||
|
} else {
|
||||||
|
$data = $this->resolveReport($request);
|
||||||
|
if (!$data) {
|
||||||
|
return redirect()->route('report.index')->with('error', 'Report non trovato.');
|
||||||
|
}
|
||||||
|
$filename = 'report_' . $request->input('report') . '_' . now()->format('Y-m-d') . '.csv';
|
||||||
|
}
|
||||||
|
|
||||||
|
$response = new \Symfony\Component\HttpFoundation\StreamedResponse(function () use ($data) {
|
||||||
|
$handle = fopen('php://output', 'w');
|
||||||
|
fprintf($handle, chr(0xEF) . chr(0xBB) . chr(0xBF));
|
||||||
|
|
||||||
|
if (!empty($data['headers']) && !empty($data['rows'])) {
|
||||||
|
fputcsv($handle, $data['headers']);
|
||||||
|
foreach ($data['rows'] as $row) {
|
||||||
|
fputcsv($handle, $row);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fclose($handle);
|
||||||
|
});
|
||||||
|
|
||||||
|
$response->headers->set('Content-Type', 'text/csv; charset=utf-8');
|
||||||
|
$response->headers->set('Content-Disposition', 'attachment; filename="' . $filename . '"');
|
||||||
|
|
||||||
|
return $response;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function resolveReport(Request $request): ?array
|
||||||
|
{
|
||||||
|
$reportType = $request->input('report');
|
||||||
|
$tipologiaFilter = $request->input('tipologia_documento');
|
||||||
|
|
||||||
|
return match ($reportType) {
|
||||||
'individui_per_genere' => $this->reportIndividuiPerGenere(),
|
'individui_per_genere' => $this->reportIndividuiPerGenere(),
|
||||||
'individui_per_eta' => $this->reportIndividuiPerEta(),
|
'individui_per_eta' => $this->reportIndividuiPerEta(),
|
||||||
'gruppi_gerarchia' => $this->reportGruppiGerarchia(),
|
'gruppi_gerarchia' => $this->reportGruppiGerarchia(),
|
||||||
@@ -183,30 +246,14 @@ class ReportController extends Controller
|
|||||||
'individui_senza_gruppo' => $this->reportIndividuiSenzaGruppo(),
|
'individui_senza_gruppo' => $this->reportIndividuiSenzaGruppo(),
|
||||||
'eventi_per_gruppo' => $this->reportEventiPerGruppo(),
|
'eventi_per_gruppo' => $this->reportEventiPerGruppo(),
|
||||||
'documenti_orfani' => $this->reportDocumentiOrfani(),
|
'documenti_orfani' => $this->reportDocumentiOrfani(),
|
||||||
|
'individui_completo' => $this->reportIndividuiCompleto(),
|
||||||
|
'gruppi_completo' => $this->reportGruppiCompleto(),
|
||||||
|
'eventi_completo' => $this->reportEventiCompleto(),
|
||||||
|
'documenti_completo' => $this->reportDocumentiCompleto($tipologiaFilter),
|
||||||
|
'contatti_completo' => $this->reportContattiCompleto(),
|
||||||
|
'scadenze_documenti' => $this->reportScadenzeDocumenti($tipologiaFilter),
|
||||||
default => null,
|
default => null,
|
||||||
};
|
};
|
||||||
|
|
||||||
if (!$data) {
|
|
||||||
return redirect()->route('report.index')->with('error', 'Report non trovato.');
|
|
||||||
}
|
|
||||||
|
|
||||||
$response = new \Symfony\Component\HttpFoundation\StreamedResponse(function () use ($data) {
|
|
||||||
$handle = fopen('php://output', 'w');
|
|
||||||
|
|
||||||
if (!empty($data['headers']) && !empty($data['rows'])) {
|
|
||||||
fputcsv($handle, $data['headers']);
|
|
||||||
foreach ($data['rows'] as $row) {
|
|
||||||
fputcsv($handle, $row);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fclose($handle);
|
|
||||||
});
|
|
||||||
|
|
||||||
$response->headers->set('Content-Type', 'text/csv; charset=utf-8');
|
|
||||||
$response->headers->set('Content-Disposition', 'attachment; filename="report_' . $reportType . '_' . now()->format('Y-m-d') . '.csv"');
|
|
||||||
|
|
||||||
return $response;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
protected function getPrebuiltReports(): array
|
protected function getPrebuiltReports(): array
|
||||||
@@ -218,6 +265,7 @@ class ReportController extends Controller
|
|||||||
'descrizione' => 'Distribuzione degli individui per genere (Maschio/Femmina)',
|
'descrizione' => 'Distribuzione degli individui per genere (Maschio/Femmina)',
|
||||||
'icona' => 'fa-venus-mars',
|
'icona' => 'fa-venus-mars',
|
||||||
'colore' => 'info',
|
'colore' => 'info',
|
||||||
|
'has_tipologia_filter' => false,
|
||||||
],
|
],
|
||||||
[
|
[
|
||||||
'id' => 'individui_per_eta',
|
'id' => 'individui_per_eta',
|
||||||
@@ -225,6 +273,7 @@ class ReportController extends Controller
|
|||||||
'descrizione' => 'Distribuzione degli individui per fasce d\'età',
|
'descrizione' => 'Distribuzione degli individui per fasce d\'età',
|
||||||
'icona' => 'fa-birthday-cake',
|
'icona' => 'fa-birthday-cake',
|
||||||
'colore' => 'success',
|
'colore' => 'success',
|
||||||
|
'has_tipologia_filter' => false,
|
||||||
],
|
],
|
||||||
[
|
[
|
||||||
'id' => 'gruppi_gerarchia',
|
'id' => 'gruppi_gerarchia',
|
||||||
@@ -232,6 +281,7 @@ class ReportController extends Controller
|
|||||||
'descrizione' => 'Struttura ad albero completa di tutti i gruppi con livelli',
|
'descrizione' => 'Struttura ad albero completa di tutti i gruppi con livelli',
|
||||||
'icona' => 'fa-sitemap',
|
'icona' => 'fa-sitemap',
|
||||||
'colore' => 'warning',
|
'colore' => 'warning',
|
||||||
|
'has_tipologia_filter' => false,
|
||||||
],
|
],
|
||||||
[
|
[
|
||||||
'id' => 'gruppi_membri',
|
'id' => 'gruppi_membri',
|
||||||
@@ -239,6 +289,7 @@ class ReportController extends Controller
|
|||||||
'descrizione' => 'Elenco di tutti i gruppi con il conteggio dei membri',
|
'descrizione' => 'Elenco di tutti i gruppi con il conteggio dei membri',
|
||||||
'icona' => 'fa-users',
|
'icona' => 'fa-users',
|
||||||
'colore' => 'primary',
|
'colore' => 'primary',
|
||||||
|
'has_tipologia_filter' => false,
|
||||||
],
|
],
|
||||||
[
|
[
|
||||||
'id' => 'eventi_calendario',
|
'id' => 'eventi_calendario',
|
||||||
@@ -246,6 +297,7 @@ class ReportController extends Controller
|
|||||||
'descrizione' => 'Tutti gli eventi programmati per il mese corrente',
|
'descrizione' => 'Tutti gli eventi programmati per il mese corrente',
|
||||||
'icona' => 'fa-calendar-alt',
|
'icona' => 'fa-calendar-alt',
|
||||||
'colore' => 'danger',
|
'colore' => 'danger',
|
||||||
|
'has_tipologia_filter' => false,
|
||||||
],
|
],
|
||||||
[
|
[
|
||||||
'id' => 'documenti_per_tipo',
|
'id' => 'documenti_per_tipo',
|
||||||
@@ -253,6 +305,7 @@ class ReportController extends Controller
|
|||||||
'descrizione' => 'Distribuzione dei documenti per tipologia',
|
'descrizione' => 'Distribuzione dei documenti per tipologia',
|
||||||
'icona' => 'fa-file-alt',
|
'icona' => 'fa-file-alt',
|
||||||
'colore' => 'secondary',
|
'colore' => 'secondary',
|
||||||
|
'has_tipologia_filter' => false,
|
||||||
],
|
],
|
||||||
[
|
[
|
||||||
'id' => 'contatti_per_tipo',
|
'id' => 'contatti_per_tipo',
|
||||||
@@ -260,6 +313,7 @@ class ReportController extends Controller
|
|||||||
'descrizione' => 'Distribuzione dei contatti per tipo (telefono, email, ecc.)',
|
'descrizione' => 'Distribuzione dei contatti per tipo (telefono, email, ecc.)',
|
||||||
'icona' => 'fa-address-book',
|
'icona' => 'fa-address-book',
|
||||||
'colore' => 'teal',
|
'colore' => 'teal',
|
||||||
|
'has_tipologia_filter' => false,
|
||||||
],
|
],
|
||||||
[
|
[
|
||||||
'id' => 'mailing_list_dettaglio',
|
'id' => 'mailing_list_dettaglio',
|
||||||
@@ -267,6 +321,7 @@ class ReportController extends Controller
|
|||||||
'descrizione' => 'Elenco completo delle mailing list con i contatti associati',
|
'descrizione' => 'Elenco completo delle mailing list con i contatti associati',
|
||||||
'icona' => 'fa-envelope',
|
'icona' => 'fa-envelope',
|
||||||
'colore' => 'purple',
|
'colore' => 'purple',
|
||||||
|
'has_tipologia_filter' => false,
|
||||||
],
|
],
|
||||||
[
|
[
|
||||||
'id' => 'individui_senza_contatti',
|
'id' => 'individui_senza_contatti',
|
||||||
@@ -274,6 +329,7 @@ class ReportController extends Controller
|
|||||||
'descrizione' => 'Lista degli individui che non hanno nessun contatto registrato',
|
'descrizione' => 'Lista degli individui che non hanno nessun contatto registrato',
|
||||||
'icona' => 'fa-user-slash',
|
'icona' => 'fa-user-slash',
|
||||||
'colore' => 'dark',
|
'colore' => 'dark',
|
||||||
|
'has_tipologia_filter' => false,
|
||||||
],
|
],
|
||||||
[
|
[
|
||||||
'id' => 'individui_senza_gruppo',
|
'id' => 'individui_senza_gruppo',
|
||||||
@@ -281,6 +337,7 @@ class ReportController extends Controller
|
|||||||
'descrizione' => 'Lista degli individui non associati a nessun gruppo',
|
'descrizione' => 'Lista degli individui non associati a nessun gruppo',
|
||||||
'icona' => 'fa-user-friends',
|
'icona' => 'fa-user-friends',
|
||||||
'colore' => 'orange',
|
'colore' => 'orange',
|
||||||
|
'has_tipologia_filter' => false,
|
||||||
],
|
],
|
||||||
[
|
[
|
||||||
'id' => 'eventi_per_gruppo',
|
'id' => 'eventi_per_gruppo',
|
||||||
@@ -288,6 +345,7 @@ class ReportController extends Controller
|
|||||||
'descrizione' => 'Distribuzione degli eventi per gruppo di appartenenza',
|
'descrizione' => 'Distribuzione degli eventi per gruppo di appartenenza',
|
||||||
'icona' => 'fa-calendar-check',
|
'icona' => 'fa-calendar-check',
|
||||||
'colore' => 'pink',
|
'colore' => 'pink',
|
||||||
|
'has_tipologia_filter' => false,
|
||||||
],
|
],
|
||||||
[
|
[
|
||||||
'id' => 'documenti_orfani',
|
'id' => 'documenti_orfani',
|
||||||
@@ -295,6 +353,55 @@ class ReportController extends Controller
|
|||||||
'descrizione' => 'Documenti non collegati a nessun individuo, gruppo o evento',
|
'descrizione' => 'Documenti non collegati a nessun individuo, gruppo o evento',
|
||||||
'icona' => 'fa-file-excel',
|
'icona' => 'fa-file-excel',
|
||||||
'colore' => 'gray',
|
'colore' => 'gray',
|
||||||
|
'has_tipologia_filter' => false,
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'id' => 'documenti_completo',
|
||||||
|
'nome' => 'Documenti (dettaglio completo)',
|
||||||
|
'descrizione' => 'Elenco completo documenti con uploader, visibilità e filtro per tipologia',
|
||||||
|
'icona' => 'fa-file-alt',
|
||||||
|
'colore' => 'indigo',
|
||||||
|
'has_tipologia_filter' => true,
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'id' => 'scadenze_documenti',
|
||||||
|
'nome' => 'Scadenze Documenti',
|
||||||
|
'descrizione' => 'Individui con scadenza documento, filtrabile per tipo documento',
|
||||||
|
'icona' => 'fa-hourglass-half',
|
||||||
|
'colore' => 'danger',
|
||||||
|
'has_tipologia_filter' => true,
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'id' => 'individui_completo',
|
||||||
|
'nome' => 'Anagrafica Completa',
|
||||||
|
'descrizione' => 'Elenco completo individui con contatti email e gruppi di appartenenza',
|
||||||
|
'icona' => 'fa-address-card',
|
||||||
|
'colore' => 'info',
|
||||||
|
'has_tipologia_filter' => false,
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'id' => 'gruppi_completo',
|
||||||
|
'nome' => 'Gruppi (dettaglio completo)',
|
||||||
|
'descrizione' => 'Gruppi con conteggio membri, diocesi e sottogruppi',
|
||||||
|
'icona' => 'fa-sitemap',
|
||||||
|
'colore' => 'warning',
|
||||||
|
'has_tipologia_filter' => false,
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'id' => 'eventi_completo',
|
||||||
|
'nome' => 'Eventi (dettaglio completo)',
|
||||||
|
'descrizione' => 'Tutti gli eventi con gruppi associati, responsabili e luogo',
|
||||||
|
'icona' => 'fa-calendar-check',
|
||||||
|
'colore' => 'success',
|
||||||
|
'has_tipologia_filter' => false,
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'id' => 'contatti_completo',
|
||||||
|
'nome' => 'Contatti (dettaglio completo)',
|
||||||
|
'descrizione' => 'Elenco completo contatti con individuo associato',
|
||||||
|
'icona' => 'fa-address-book',
|
||||||
|
'colore' => 'teal',
|
||||||
|
'has_tipologia_filter' => false,
|
||||||
],
|
],
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
@@ -342,7 +449,7 @@ class ReportController extends Controller
|
|||||||
->whereYear('data_nascita', '>=', $now->year - $fascia['max'])
|
->whereYear('data_nascita', '>=', $now->year - $fascia['max'])
|
||||||
->whereYear('data_nascita', '<=', $now->year - $fascia['min'])
|
->whereYear('data_nascita', '<=', $now->year - $fascia['min'])
|
||||||
->count();
|
->count();
|
||||||
|
|
||||||
$rows[] = ['fascia' => $fascia['label'], 'totale' => $count];
|
$rows[] = ['fascia' => $fascia['label'], 'totale' => $count];
|
||||||
$totalWithDate += $count;
|
$totalWithDate += $count;
|
||||||
}
|
}
|
||||||
@@ -361,10 +468,10 @@ class ReportController extends Controller
|
|||||||
|
|
||||||
protected function reportGruppiGerarchia(): array
|
protected function reportGruppiGerarchia(): array
|
||||||
{
|
{
|
||||||
$rootGruppi = Gruppo::whereNull('parent_id')->with(['children.diocesi', 'children.children'])->orderBy('nome')->get();
|
$rootGruppi = Gruppo::whereNull('parent_id')->with(['children', 'diocesi'])->orderBy('nome')->get();
|
||||||
|
|
||||||
$rows = [];
|
$rows = [];
|
||||||
|
|
||||||
foreach ($rootGruppi as $gruppo) {
|
foreach ($rootGruppi as $gruppo) {
|
||||||
$rows[] = [
|
$rows[] = [
|
||||||
'livello' => 0,
|
'livello' => 0,
|
||||||
@@ -462,7 +569,7 @@ class ReportController extends Controller
|
|||||||
return [
|
return [
|
||||||
'title' => 'Documenti per Tipologia',
|
'title' => 'Documenti per Tipologia',
|
||||||
'headers' => ['Tipologia', 'Totale'],
|
'headers' => ['Tipologia', 'Totale'],
|
||||||
'rows' => $rows->map(fn($r) => [ucfirst($r->tipologia), $r->totale])->toArray(),
|
'rows' => $rows->map(fn($r) => [ucfirst((string) $r->tipologia), $r->totale])->toArray(),
|
||||||
'summary' => 'Totale documenti: ' . Documento::count(),
|
'summary' => 'Totale documenti: ' . Documento::count(),
|
||||||
'detail_rows' => $rows->map(fn($r) => (array) $r)->toArray(),
|
'detail_rows' => $rows->map(fn($r) => (array) $r)->toArray(),
|
||||||
];
|
];
|
||||||
@@ -478,7 +585,7 @@ class ReportController extends Controller
|
|||||||
return [
|
return [
|
||||||
'title' => 'Contatti per Tipo',
|
'title' => 'Contatti per Tipo',
|
||||||
'headers' => ['Tipo', 'Totale'],
|
'headers' => ['Tipo', 'Totale'],
|
||||||
'rows' => $rows->map(fn($r) => [ucfirst($r->tipo), $r->totale])->toArray(),
|
'rows' => $rows->map(fn($r) => [ucfirst((string) $r->tipo), $r->totale])->toArray(),
|
||||||
'summary' => 'Totale contatti: ' . Contatto::count(),
|
'summary' => 'Totale contatti: ' . Contatto::count(),
|
||||||
'detail_rows' => $rows->map(fn($r) => (array) $r)->toArray(),
|
'detail_rows' => $rows->map(fn($r) => (array) $r)->toArray(),
|
||||||
];
|
];
|
||||||
@@ -603,8 +710,8 @@ class ReportController extends Controller
|
|||||||
$rows = $documenti->map(function ($doc) {
|
$rows = $documenti->map(function ($doc) {
|
||||||
return [
|
return [
|
||||||
'nome' => $doc->nome_file,
|
'nome' => $doc->nome_file,
|
||||||
'tipologia' => ucfirst($doc->tipologia),
|
'tipologia' => ucfirst($doc->tipologia ?? ''),
|
||||||
'visibilita' => ucfirst($doc->visibilita),
|
'visibilita' => ucfirst($doc->visibilita ?? ''),
|
||||||
'data' => $doc->created_at->format('d/m/Y'),
|
'data' => $doc->created_at->format('d/m/Y'),
|
||||||
];
|
];
|
||||||
})->toArray();
|
})->toArray();
|
||||||
@@ -618,7 +725,209 @@ class ReportController extends Controller
|
|||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
protected function runCustomReport(\App\Models\ReportCustom $report): array
|
protected function reportDocumentiCompleto(?string $tipologiaFilter = null): array
|
||||||
|
{
|
||||||
|
$query = Documento::with('user')
|
||||||
|
->orderBy('created_at', 'desc');
|
||||||
|
|
||||||
|
if ($tipologiaFilter) {
|
||||||
|
$query->where('tipologia', $tipologiaFilter);
|
||||||
|
}
|
||||||
|
|
||||||
|
$documenti = $query->get();
|
||||||
|
|
||||||
|
$rows = $documenti->map(function ($doc) {
|
||||||
|
return [
|
||||||
|
'nome' => $doc->nome_file,
|
||||||
|
'tipologia' => ucfirst($doc->tipologia ?? ''),
|
||||||
|
'visibilita' => ucfirst($doc->visibilita ?? ''),
|
||||||
|
'dimensione' => $doc->dimensione ? number_format($doc->dimensione / 1024, 1) . ' KB' : '-',
|
||||||
|
'uploader' => $doc->user?->name ?? '-',
|
||||||
|
'data' => $doc->created_at->format('d/m/Y'),
|
||||||
|
];
|
||||||
|
})->toArray();
|
||||||
|
|
||||||
|
$title = $tipologiaFilter
|
||||||
|
? 'Documenti (tipologia: ' . ucfirst($tipologiaFilter) . ')'
|
||||||
|
: 'Documenti (dettaglio completo)';
|
||||||
|
|
||||||
|
return [
|
||||||
|
'title' => $title,
|
||||||
|
'headers' => ['Nome File', 'Tipologia', 'Visibilità', 'Dimensione', 'Caricato Da', 'Data'],
|
||||||
|
'rows' => array_map(fn($r) => [$r['nome'], $r['tipologia'], $r['visibilita'], $r['dimensione'], $r['uploader'], $r['data']], $rows),
|
||||||
|
'summary' => 'Totale documenti: ' . $documenti->count(),
|
||||||
|
'detail_rows' => $rows,
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function reportScadenzeDocumenti(?string $tipologiaFilter = null): array
|
||||||
|
{
|
||||||
|
$query = Individuo::whereNotNull('scadenza_documento')
|
||||||
|
->whereNotNull('tipo_documento')
|
||||||
|
->orderBy('scadenza_documento');
|
||||||
|
|
||||||
|
if ($tipologiaFilter) {
|
||||||
|
$query->where('tipo_documento', $tipologiaFilter);
|
||||||
|
}
|
||||||
|
|
||||||
|
$individui = $query->get();
|
||||||
|
|
||||||
|
$rows = $individui->map(function ($ind) {
|
||||||
|
$scadenza = $ind->scadenza_documento;
|
||||||
|
$giorniMancanti = $scadenza ? now()->diffInDays($scadenza, false) : null;
|
||||||
|
|
||||||
|
return [
|
||||||
|
'codice' => $ind->codice_id,
|
||||||
|
'cognome' => $ind->cognome,
|
||||||
|
'nome' => $ind->nome,
|
||||||
|
'tipo_documento' => ucfirst($ind->tipo_documento ?? ''),
|
||||||
|
'numero' => $ind->numero_documento ?? '-',
|
||||||
|
'scadenza' => $scadenza?->format('d/m/Y') ?? '-',
|
||||||
|
'stato' => $giorniMancanti !== null
|
||||||
|
? ($giorniMancanti < 0
|
||||||
|
? ('Scaduto da ' . abs($giorniMancanti) . ' gg')
|
||||||
|
: ($giorniMancanti <= 30
|
||||||
|
? 'Scade tra ' . $giorniMancanti . ' gg'
|
||||||
|
: 'In regola (' . $giorniMancanti . ' gg)'))
|
||||||
|
: '-',
|
||||||
|
];
|
||||||
|
})->toArray();
|
||||||
|
|
||||||
|
$title = $tipologiaFilter
|
||||||
|
? 'Scadenze Documenti (tipo: ' . ucfirst($tipologiaFilter) . ')'
|
||||||
|
: 'Scadenze Documenti';
|
||||||
|
|
||||||
|
return [
|
||||||
|
'title' => $title,
|
||||||
|
'headers' => ['Codice', 'Cognome', 'Nome', 'Tipo Documento', 'Numero', 'Scadenza', 'Stato'],
|
||||||
|
'rows' => array_map(fn($r) => [$r['codice'], $r['cognome'], $r['nome'], $r['tipo_documento'], $r['numero'], $r['scadenza'], $r['stato']], $rows),
|
||||||
|
'summary' => 'Individui con documento: ' . $individui->count(),
|
||||||
|
'detail_rows' => $rows,
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function reportIndividuiCompleto(): array
|
||||||
|
{
|
||||||
|
$individui = Individuo::with(['contatti', 'gruppi'])
|
||||||
|
->orderBy('cognome')
|
||||||
|
->orderBy('nome')
|
||||||
|
->get();
|
||||||
|
|
||||||
|
$rows = $individui->map(function ($ind) {
|
||||||
|
$email = $ind->contatti->where('tipo', 'email')->first()?->valore
|
||||||
|
?? $ind->email
|
||||||
|
?? '-';
|
||||||
|
$telefono = $ind->contatti->whereIn('tipo', ['telefono', 'cellulare'])->first()?->valore
|
||||||
|
?? $ind->telefono
|
||||||
|
?? '-';
|
||||||
|
$gruppi = $ind->gruppi->pluck('nome')->implode(', ') ?: '-';
|
||||||
|
|
||||||
|
return [
|
||||||
|
'codice' => $ind->codice_id,
|
||||||
|
'cognome' => $ind->cognome,
|
||||||
|
'nome' => $ind->nome,
|
||||||
|
'data_nascita' => $ind->data_nascita?->format('d/m/Y') ?? '-',
|
||||||
|
'genere' => match ($ind->genere) { 'M' => 'M', 'F' => 'F', default => '-' },
|
||||||
|
'email' => $email,
|
||||||
|
'telefono' => $telefono,
|
||||||
|
'città' => $ind->città ?? '-',
|
||||||
|
'gruppi' => $gruppi,
|
||||||
|
];
|
||||||
|
})->toArray();
|
||||||
|
|
||||||
|
return [
|
||||||
|
'title' => 'Anagrafica Completa Individui',
|
||||||
|
'headers' => ['Codice', 'Cognome', 'Nome', 'Data Nascita', 'Genere', 'Email', 'Telefono', 'Città', 'Gruppi'],
|
||||||
|
'rows' => array_map(fn($r) => [$r['codice'], $r['cognome'], $r['nome'], $r['data_nascita'], $r['genere'], $r['email'], $r['telefono'], $r['città'], $r['gruppi']], $rows),
|
||||||
|
'summary' => 'Totale individui: ' . $individui->count(),
|
||||||
|
'detail_rows' => $rows,
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function reportGruppiCompleto(): array
|
||||||
|
{
|
||||||
|
$gruppi = Gruppo::withCount('individui')
|
||||||
|
->with('diocesi')
|
||||||
|
->orderBy('nome')
|
||||||
|
->get();
|
||||||
|
|
||||||
|
$rows = $gruppi->map(function ($g) {
|
||||||
|
$parent = $g->parent;
|
||||||
|
return [
|
||||||
|
'nome' => $g->nome,
|
||||||
|
'descrizione' => $g->descrizione ?? '-',
|
||||||
|
'diocesi' => $g->diocesi?->nome ?? '-',
|
||||||
|
'livello' => $g->parent_id ? (count($g->getAncestors()) + 1) : 0,
|
||||||
|
'membri' => $g->individui_count,
|
||||||
|
'padre' => $parent?->nome ?? '-',
|
||||||
|
'città' => $g->città_incontro ?? '-',
|
||||||
|
];
|
||||||
|
})->toArray();
|
||||||
|
|
||||||
|
return [
|
||||||
|
'title' => 'Gruppi (dettaglio completo)',
|
||||||
|
'headers' => ['Nome', 'Descrizione', 'Diocesi', 'Livello', 'Membri', 'Padre', 'Città'],
|
||||||
|
'rows' => array_map(fn($r) => [$r['nome'], $r['descrizione'], $r['diocesi'], $r['livello'], $r['membri'], $r['padre'], $r['città']], $rows),
|
||||||
|
'summary' => 'Totale gruppi: ' . $gruppi->count() . ' | Totale membri: ' . $gruppi->sum('individui_count'),
|
||||||
|
'detail_rows' => $rows,
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function reportEventiCompleto(): array
|
||||||
|
{
|
||||||
|
$eventi = Evento::with(['gruppi', 'responsabili'])
|
||||||
|
->orderBy('data_specifica', 'desc')
|
||||||
|
->get();
|
||||||
|
|
||||||
|
$rows = $eventi->map(function ($e) {
|
||||||
|
return [
|
||||||
|
'nome' => $e->nome_evento,
|
||||||
|
'data' => $e->data_specifica?->format('d/m/Y') ?? '-',
|
||||||
|
'tipo_evento' => $e->tipo_evento ?? '-',
|
||||||
|
'ricorrenza' => ucfirst($e->tipo_recorrenza ?? 'singolo'),
|
||||||
|
'gruppi' => $e->gruppi->pluck('nome')->implode(', ') ?: '-',
|
||||||
|
'responsabili' => $e->responsabili->pluck('nome_completo')->implode(', ') ?: '-',
|
||||||
|
'luogo' => $e->luogo_indirizzo ?? '-',
|
||||||
|
];
|
||||||
|
})->toArray();
|
||||||
|
|
||||||
|
return [
|
||||||
|
'title' => 'Eventi (dettaglio completo)',
|
||||||
|
'headers' => ['Evento', 'Data', 'Tipo Evento', 'Ricorrenza', 'Gruppi', 'Responsabili', 'Luogo'],
|
||||||
|
'rows' => array_map(fn($r) => [$r['nome'], $r['data'], $r['tipo_evento'], $r['ricorrenza'], $r['gruppi'], $r['responsabili'], $r['luogo']], $rows),
|
||||||
|
'summary' => 'Totale eventi: ' . $eventi->count(),
|
||||||
|
'detail_rows' => $rows,
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function reportContattiCompleto(): array
|
||||||
|
{
|
||||||
|
$contatti = Contatto::with('individuo')
|
||||||
|
->orderBy('tipo')
|
||||||
|
->orderBy('valore')
|
||||||
|
->get();
|
||||||
|
|
||||||
|
$rows = $contatti->map(function ($c) {
|
||||||
|
return [
|
||||||
|
'tipo' => ucfirst($c->tipo),
|
||||||
|
'valore' => $c->valore,
|
||||||
|
'etichetta' => $c->etichetta ?? '-',
|
||||||
|
'primario' => $c->is_primary ? 'Si' : 'No',
|
||||||
|
'individuo' => $c->individuo?->nome_completo ?? '-',
|
||||||
|
'codice' => $c->individuo?->codice_id ?? '-',
|
||||||
|
];
|
||||||
|
})->toArray();
|
||||||
|
|
||||||
|
return [
|
||||||
|
'title' => 'Contatti (dettaglio completo)',
|
||||||
|
'headers' => ['Tipo', 'Valore', 'Etichetta', 'Primario', 'Individuo', 'Codice'],
|
||||||
|
'rows' => array_map(fn($r) => [$r['tipo'], $r['valore'], $r['etichetta'], $r['primario'], $r['individuo'], $r['codice']], $rows),
|
||||||
|
'summary' => 'Totale contatti: ' . $contatti->count(),
|
||||||
|
'detail_rows' => $rows,
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function runCustomReport(ReportCustom $report): array
|
||||||
{
|
{
|
||||||
$tipo = $report->tipo_report;
|
$tipo = $report->tipo_report;
|
||||||
$config = $report->config ?? [];
|
$config = $report->config ?? [];
|
||||||
@@ -642,17 +951,33 @@ class ReportController extends Controller
|
|||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$columns = $config['columns'] ?? [];
|
||||||
|
$hasRelations = false;
|
||||||
|
|
||||||
|
foreach ($columns as $col) {
|
||||||
|
if (str_contains($col, '.')) {
|
||||||
|
$hasRelations = true;
|
||||||
|
$relation = explode('.', $col)[0];
|
||||||
|
$query->with($relation);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (!empty($config['search'])) {
|
if (!empty($config['search'])) {
|
||||||
$search = $config['search'];
|
$search = $config['search'];
|
||||||
$query->where(function ($q) use ($search) {
|
$query->where(function ($q) use ($config, $search) {
|
||||||
foreach ($config['search_columns'] ?? [] as $col) {
|
foreach ($config['search_columns'] ?? [] as $col) {
|
||||||
$q->orWhere($col, 'like', '%' . $search . '%');
|
if (!str_contains($col, '.')) {
|
||||||
|
$q->orWhere($col, 'like', '%' . $search . '%');
|
||||||
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!empty($config['sort_by'])) {
|
if (!empty($config['sort_by'])) {
|
||||||
$query->orderBy($config['sort_by'], $config['sort_direction'] ?? 'asc');
|
$sortBy = $config['sort_by'];
|
||||||
|
if (!str_contains($sortBy, '.')) {
|
||||||
|
$query->orderBy($sortBy, $config['sort_direction'] ?? 'asc');
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!empty($config['limit'])) {
|
if (!empty($config['limit'])) {
|
||||||
@@ -661,16 +986,29 @@ class ReportController extends Controller
|
|||||||
|
|
||||||
$results = $query->get();
|
$results = $query->get();
|
||||||
|
|
||||||
$headers = !empty($config['columns']) ? $config['columns'] : array_keys($results->first()?->toArray() ?? []);
|
if (empty($columns)) {
|
||||||
|
$first = $results->first();
|
||||||
|
$columns = $first ? array_keys($first->toArray()) : [];
|
||||||
|
}
|
||||||
|
|
||||||
$rows = $results->map(function ($item) use ($headers) {
|
$headers = array_map(function ($col) use ($tipo) {
|
||||||
|
return ReportColumnRegistry::getColumnLabel($tipo, $col) ?? $col;
|
||||||
|
}, $columns);
|
||||||
|
|
||||||
|
$rows = $results->map(function ($item) use ($columns, $tipo) {
|
||||||
$row = [];
|
$row = [];
|
||||||
foreach ($headers as $col) {
|
foreach ($columns as $col) {
|
||||||
$value = $item->{$col} ?? '';
|
if (str_contains($col, '.')) {
|
||||||
if ($value instanceof \Carbon\Carbon) {
|
$row[] = ReportColumnRegistry::resolveRelationValue($item, $col);
|
||||||
$value = $value->format('d/m/Y');
|
} else {
|
||||||
|
$value = $item->{$col} ?? '';
|
||||||
|
if ($value instanceof \Carbon\Carbon) {
|
||||||
|
$value = $value->format('d/m/Y');
|
||||||
|
} elseif (is_bool($value)) {
|
||||||
|
$value = $value ? 'Si' : 'No';
|
||||||
|
}
|
||||||
|
$row[] = $value;
|
||||||
}
|
}
|
||||||
$row[] = $value;
|
|
||||||
}
|
}
|
||||||
return $row;
|
return $row;
|
||||||
})->toArray();
|
})->toArray();
|
||||||
|
|||||||
+68
-2
@@ -7,14 +7,22 @@ use Illuminate\Database\Eloquent\Attributes\Hidden;
|
|||||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||||
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
||||||
use Illuminate\Database\Eloquent\Relations\HasMany;
|
use Illuminate\Database\Eloquent\Relations\HasMany;
|
||||||
|
use Illuminate\Auth\Passwords\CanResetPassword;
|
||||||
|
use Illuminate\Contracts\Auth\CanResetPassword as CanResetPasswordContract;
|
||||||
use Illuminate\Foundation\Auth\User as Authenticatable;
|
use Illuminate\Foundation\Auth\User as Authenticatable;
|
||||||
use Illuminate\Notifications\Notifiable;
|
use Illuminate\Notifications\Notifiable;
|
||||||
|
use Illuminate\Support\Facades\Crypt;
|
||||||
|
use Illuminate\Support\Facades\Log;
|
||||||
|
use Symfony\Component\Mailer\Mailer;
|
||||||
|
use Symfony\Component\Mailer\Transport;
|
||||||
|
use Symfony\Component\Mime\Address;
|
||||||
|
use Symfony\Component\Mime\Email;
|
||||||
|
|
||||||
#[Fillable(['name', 'email', 'password', 'tenant_id', 'is_admin', 'permissions', 'role_preset_id', 'status'])]
|
#[Fillable(['name', 'email', 'password', 'tenant_id', 'is_admin', 'permissions', 'role_preset_id', 'status'])]
|
||||||
#[Hidden(['password', 'remember_token'])]
|
#[Hidden(['password', 'remember_token'])]
|
||||||
class User extends Authenticatable
|
class User extends Authenticatable implements CanResetPasswordContract
|
||||||
{
|
{
|
||||||
use HasFactory, Notifiable;
|
use HasFactory, Notifiable, CanResetPassword;
|
||||||
|
|
||||||
protected $fillable = ['name', 'email', 'password', 'tenant_id', 'is_admin', 'permissions', 'role_preset_id', 'status'];
|
protected $fillable = ['name', 'email', 'password', 'tenant_id', 'is_admin', 'permissions', 'role_preset_id', 'status'];
|
||||||
|
|
||||||
@@ -156,4 +164,62 @@ class User extends Authenticatable
|
|||||||
$this->permissions = $preset->permissions;
|
$this->permissions = $preset->permissions;
|
||||||
return $this;
|
return $this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function sendPasswordResetNotification($token): void
|
||||||
|
{
|
||||||
|
$settings = EmailSetting::getActive();
|
||||||
|
if (!$settings || empty($settings->smtp_host)) {
|
||||||
|
Log::warning('Impossibile inviare reset password: SMTP non configurato');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
$smtpUsername = $settings->smtp_username ?: $settings->email_address;
|
||||||
|
$smtpPassword = $settings->smtp_password
|
||||||
|
? Crypt::decryptString($settings->smtp_password)
|
||||||
|
: $settings->getDecryptedPassword();
|
||||||
|
|
||||||
|
$encryption = $settings->smtp_encryption ?? 'tls';
|
||||||
|
$scheme = ($encryption === 'ssl') ? 'smtps' : 'smtp';
|
||||||
|
|
||||||
|
$dsn = sprintf(
|
||||||
|
'%s://%s:%s@%s:%d?encryption=%s',
|
||||||
|
$scheme,
|
||||||
|
rawurlencode($smtpUsername),
|
||||||
|
rawurlencode($smtpPassword),
|
||||||
|
$settings->smtp_host,
|
||||||
|
$settings->smtp_port ?? 587,
|
||||||
|
$encryption
|
||||||
|
);
|
||||||
|
|
||||||
|
$transport = Transport::fromDsn($dsn);
|
||||||
|
$mailer = new Mailer($transport);
|
||||||
|
|
||||||
|
$resetUrl = url('/password/reset/' . $token);
|
||||||
|
|
||||||
|
$fromName = $settings->email_name ?? config('app.name');
|
||||||
|
$fromName = preg_replace('/[^\p{L}\p{N}\s]/u', '', $fromName);
|
||||||
|
$fromName = trim($fromName) ?: config('app.name');
|
||||||
|
|
||||||
|
$email = (new Email())
|
||||||
|
->from(new Address($settings->email_address, $fromName))
|
||||||
|
->to($this->email)
|
||||||
|
->subject('Reset della password - ' . config('app.name'))
|
||||||
|
->html(view('auth.emails.reset-password', [
|
||||||
|
'user' => $this,
|
||||||
|
'token' => $token,
|
||||||
|
'resetUrl' => $resetUrl,
|
||||||
|
'appName' => config('app.name'),
|
||||||
|
])->render());
|
||||||
|
|
||||||
|
$mailer->send($email);
|
||||||
|
|
||||||
|
Log::info('Password reset email sent', ['email' => $this->email]);
|
||||||
|
} catch (\Exception $e) {
|
||||||
|
Log::error('Failed to send password reset email', [
|
||||||
|
'email' => $this->email,
|
||||||
|
'error' => $e->getMessage(),
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,33 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="it">
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<style>
|
||||||
|
body { font-family: 'Segoe UI', Tahoma, sans-serif; line-height: 1.6; color: #333; }
|
||||||
|
.container { max-width: 600px; margin: 0 auto; padding: 20px; }
|
||||||
|
.header { background: #28a745; color: white; padding: 20px; text-align: center; border-radius: 5px 5px 0 0; }
|
||||||
|
.body { padding: 30px; background: #f9f9f9; border: 1px solid #ddd; }
|
||||||
|
.button { display: inline-block; padding: 12px 30px; background: #28a745; color: white; text-decoration: none; border-radius: 5px; font-weight: bold; }
|
||||||
|
.footer { margin-top: 20px; font-size: 12px; color: #999; text-align: center; }
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div class="container">
|
||||||
|
<div class="header">
|
||||||
|
<h2>{{ e($appName) }}</h2>
|
||||||
|
</div>
|
||||||
|
<div class="body">
|
||||||
|
<p>Ciao <strong>{{ e($user->name) }}</strong>,</p>
|
||||||
|
<p>Hai richiesto il reset della password per il tuo account.</p>
|
||||||
|
<p style="text-align: center; margin: 30px 0;">
|
||||||
|
<a href="{{ $resetUrl }}" class="button">Reimposta Password</a>
|
||||||
|
</p>
|
||||||
|
<p>Se non hai richiesto tu il reset, ignora questa email.</p>
|
||||||
|
<p>Il link è valido per 60 minuti.</p>
|
||||||
|
</div>
|
||||||
|
<div class="footer">
|
||||||
|
© {{ date('Y') }} {{ e($appName) }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
@@ -0,0 +1,60 @@
|
|||||||
|
<?php
|
||||||
|
use App\Models\AppSetting;
|
||||||
|
$appLogo = AppSetting::getLogoUrl();
|
||||||
|
$appName = AppSetting::getAppName() ?? 'Glastree';
|
||||||
|
?>
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="it">
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||||
|
<title>Reset Password - {{ e($appName) }}</title>
|
||||||
|
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/admin-lte@3.2/dist/css/adminlte.min.css">
|
||||||
|
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
|
||||||
|
</head>
|
||||||
|
<body class="hold-transition login-page">
|
||||||
|
<div class="login-box">
|
||||||
|
<div class="login-logo">
|
||||||
|
<a href="/">
|
||||||
|
@if($appLogo)
|
||||||
|
<img src="{{ $appLogo }}" alt="Logo" style="max-height: 80px; max-width: 200px;">
|
||||||
|
@else
|
||||||
|
<i class="fas fa-tree text-success"></i> <b>{{ e($appName) }}</b>
|
||||||
|
@endif
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
<div class="card">
|
||||||
|
<div class="card-body login-card-body">
|
||||||
|
<p class="login-box-msg">Password dimenticata? Inserisci la tua email e ti invieremo il link per reimpostarla.</p>
|
||||||
|
|
||||||
|
@if(session('success'))
|
||||||
|
<div class="alert alert-success">{{ session('success') }}</div>
|
||||||
|
@endif
|
||||||
|
|
||||||
|
<form action="{{ route('password.email') }}" method="POST">
|
||||||
|
@csrf
|
||||||
|
<div class="input-group mb-3">
|
||||||
|
<input type="email" name="email" class="form-control @error('email') is-invalid @enderror" placeholder="Email" required value="{{ old('email') }}">
|
||||||
|
<div class="input-group-append">
|
||||||
|
<div class="input-group-text"><span class="fas fa-envelope"></span></div>
|
||||||
|
</div>
|
||||||
|
@error('email')
|
||||||
|
<span class="invalid-feedback">{{ $message }}</span>
|
||||||
|
@enderror
|
||||||
|
</div>
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-12">
|
||||||
|
<button type="submit" class="btn btn-success btn-block">Invia link reset</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
<p class="mb-0 text-center mt-3">
|
||||||
|
<a href="{{ route('login') }}">Torna al login</a>
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<script src="https://cdn.jsdelivr.net/npm/jquery@3.7.1/dist/jquery.min.js"></script>
|
||||||
|
<script src="https://cdn.jsdelivr.net/npm/bootstrap@4.6.2/dist/js/bootstrap.bundle.min.js"></script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
@@ -69,7 +69,7 @@ $appOrgName = AppSetting::getOrgName() ?? '';
|
|||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
<p class="mb-0 text-center mt-3">
|
<p class="mb-0 text-center mt-3">
|
||||||
<a href="{{ route('register') }}" class="text-center">Non hai un account? Registrati</a>
|
<a href="{{ route('password.request') }}" class="text-center">Password dimenticata?</a>
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -0,0 +1,76 @@
|
|||||||
|
@extends('layouts.adminlte')
|
||||||
|
|
||||||
|
@section('title', 'Il Mio Profilo')
|
||||||
|
@section('page_title', 'Il Mio Profilo')
|
||||||
|
|
||||||
|
@section('breadcrumbs')
|
||||||
|
<li class="breadcrumb-item"><a href="{{ route('dashboard') }}">Dashboard</a></li>
|
||||||
|
<li class="breadcrumb-item active">Profilo</li>
|
||||||
|
@endsection
|
||||||
|
|
||||||
|
@section('content')
|
||||||
|
<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>Informazioni Account</h3>
|
||||||
|
</div>
|
||||||
|
<div class="card-body">
|
||||||
|
<dl class="row mb-0">
|
||||||
|
<dt class="col-sm-4">Nome</dt>
|
||||||
|
<dd class="col-sm-8">{{ e(Auth::user()->name) }}</dd>
|
||||||
|
<dt class="col-sm-4">Email</dt>
|
||||||
|
<dd class="col-sm-8">{{ e(Auth::user()->email) }}</dd>
|
||||||
|
<dt class="col-sm-4">Membro dal</dt>
|
||||||
|
<dd class="col-sm-8">{{ Auth::user()->created_at->format('d/m/Y') }}</dd>
|
||||||
|
</dl>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="col-md-6">
|
||||||
|
<div class="card card-warning">
|
||||||
|
<div class="card-header">
|
||||||
|
<h3 class="card-title"><i class="fas fa-key mr-2"></i>Cambia Password</h3>
|
||||||
|
</div>
|
||||||
|
<form method="POST" action="{{ route('profile.password') }}">
|
||||||
|
@csrf
|
||||||
|
@method('PUT')
|
||||||
|
<div class="card-body">
|
||||||
|
@if(session('success'))
|
||||||
|
<div class="alert alert-success">{{ session('success') }}</div>
|
||||||
|
@endif
|
||||||
|
@if(session('error'))
|
||||||
|
<div class="alert alert-danger">{{ session('error') }}</div>
|
||||||
|
@endif
|
||||||
|
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="current_password">Password attuale *</label>
|
||||||
|
<input type="password" name="current_password" id="current_password" class="form-control @error('current_password') is-invalid @enderror" required>
|
||||||
|
@error('current_password')
|
||||||
|
<span class="invalid-feedback">{{ $message }}</span>
|
||||||
|
@enderror
|
||||||
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="password">Nuova password *</label>
|
||||||
|
<input type="password" name="password" id="password" class="form-control @error('password') is-invalid @enderror" required minlength="8">
|
||||||
|
<small class="text-muted">Minimo 8 caratteri</small>
|
||||||
|
@error('password')
|
||||||
|
<span class="invalid-feedback">{{ $message }}</span>
|
||||||
|
@enderror
|
||||||
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="password_confirmation">Conferma nuova password *</label>
|
||||||
|
<input type="password" name="password_confirmation" id="password_confirmation" class="form-control" required minlength="8">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="card-footer">
|
||||||
|
<button type="submit" class="btn btn-warning">
|
||||||
|
<i class="fas fa-save mr-1"></i> Aggiorna Password
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
@endsection
|
||||||
@@ -1,80 +0,0 @@
|
|||||||
<?php
|
|
||||||
use App\Models\AppSetting;
|
|
||||||
$appName = AppSetting::getAppName() ?? 'Glastree';
|
|
||||||
?>
|
|
||||||
<!DOCTYPE html>
|
|
||||||
<html lang="it">
|
|
||||||
<head>
|
|
||||||
<meta charset="utf-8">
|
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
||||||
<title>Registrati - {{ e($appName) }}</title>
|
|
||||||
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/admin-lte@3.2/dist/css/adminlte.min.css">
|
|
||||||
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
|
|
||||||
</head>
|
|
||||||
<body class="hold-transition register-page">
|
|
||||||
<div class="register-box">
|
|
||||||
<div class="login-logo">
|
|
||||||
<a href="/"><i class="fas fa-tree text-success"></i> <b>{{ e($appName) }}</b></a>
|
|
||||||
</div>
|
|
||||||
<div class="card">
|
|
||||||
<div class="card-body register-card-body">
|
|
||||||
<p class="login-box-msg">Crea un nuovo account</p>
|
|
||||||
<form action="{{ route('register') }}" method="POST">
|
|
||||||
@csrf
|
|
||||||
<div class="input-group mb-3">
|
|
||||||
<input type="text" name="name" class="form-control @error('name') is-invalid @enderror" placeholder="Nome completo" value="{{ old('name') }}" required>
|
|
||||||
<div class="input-group-append">
|
|
||||||
<div class="input-group-text">
|
|
||||||
<span class="fas fa-user"></span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
@error('name')
|
|
||||||
<span class="invalid-feedback">{{ $message }}</span>
|
|
||||||
@enderror
|
|
||||||
</div>
|
|
||||||
<div class="input-group mb-3">
|
|
||||||
<input type="email" name="email" class="form-control @error('email') is-invalid @enderror" placeholder="Email" value="{{ old('email') }}" required>
|
|
||||||
<div class="input-group-append">
|
|
||||||
<div class="input-group-text">
|
|
||||||
<span class="fas fa-envelope"></span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
@error('email')
|
|
||||||
<span class="invalid-feedback">{{ $message }}</span>
|
|
||||||
@enderror
|
|
||||||
</div>
|
|
||||||
<div class="input-group mb-3">
|
|
||||||
<input type="password" name="password" class="form-control @error('password') is-invalid @enderror" placeholder="Password" required>
|
|
||||||
<div class="input-group-append">
|
|
||||||
<div class="input-group-text">
|
|
||||||
<span class="fas fa-lock"></span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
@error('password')
|
|
||||||
<span class="invalid-feedback">{{ $message }}</span>
|
|
||||||
@enderror
|
|
||||||
</div>
|
|
||||||
<div class="input-group mb-3">
|
|
||||||
<input type="password" name="password_confirmation" class="form-control" placeholder="Conferma password" required>
|
|
||||||
<div class="input-group-append">
|
|
||||||
<div class="input-group-text">
|
|
||||||
<span class="fas fa-lock"></span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="row">
|
|
||||||
<div class="col-12">
|
|
||||||
<button type="submit" class="btn btn-success btn-block">Registrati</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</form>
|
|
||||||
<p class="mb-0 text-center mt-3">
|
|
||||||
<a href="{{ route('login') }}" class="text-center">Hai già un account? Accedi</a>
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<script src="https://cdn.jsdelivr.net/npm/jquery@3.7.1/dist/jquery.min.js"></script>
|
|
||||||
<script src="https://cdn.jsdelivr.net/npm/bootstrap@4.6.2/dist/js/bootstrap.bundle.min.js"></script>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
||||||
@@ -0,0 +1,63 @@
|
|||||||
|
<?php
|
||||||
|
use App\Models\AppSetting;
|
||||||
|
$appName = AppSetting::getAppName() ?? 'Glastree';
|
||||||
|
?>
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="it">
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||||
|
<title>Reimposta Password - {{ e($appName) }}</title>
|
||||||
|
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/admin-lte@3.2/dist/css/adminlte.min.css">
|
||||||
|
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
|
||||||
|
</head>
|
||||||
|
<body class="hold-transition login-page">
|
||||||
|
<div class="login-box">
|
||||||
|
<div class="login-logo">
|
||||||
|
<a href="/"><b>{{ e($appName) }}</b></a>
|
||||||
|
</div>
|
||||||
|
<div class="card">
|
||||||
|
<div class="card-body login-card-body">
|
||||||
|
<p class="login-box-msg">Scegli una nuova password</p>
|
||||||
|
|
||||||
|
@error('email')
|
||||||
|
<div class="alert alert-danger">{{ $message }}</div>
|
||||||
|
@enderror
|
||||||
|
|
||||||
|
<form action="{{ route('password.update') }}" method="POST">
|
||||||
|
@csrf
|
||||||
|
<input type="hidden" name="token" value="{{ $token }}">
|
||||||
|
<div class="input-group mb-3">
|
||||||
|
<input type="email" name="email" class="form-control" placeholder="Email" required value="{{ old('email') }}">
|
||||||
|
<div class="input-group-append">
|
||||||
|
<div class="input-group-text"><span class="fas fa-envelope"></span></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="input-group mb-3">
|
||||||
|
<input type="password" name="password" class="form-control @error('password') is-invalid @enderror" placeholder="Nuova password" required minlength="8">
|
||||||
|
<div class="input-group-append">
|
||||||
|
<div class="input-group-text"><span class="fas fa-lock"></span></div>
|
||||||
|
</div>
|
||||||
|
@error('password')
|
||||||
|
<span class="invalid-feedback">{{ $message }}</span>
|
||||||
|
@enderror
|
||||||
|
</div>
|
||||||
|
<div class="input-group mb-3">
|
||||||
|
<input type="password" name="password_confirmation" class="form-control" placeholder="Conferma password" required minlength="8">
|
||||||
|
<div class="input-group-append">
|
||||||
|
<div class="input-group-text"><span class="fas fa-lock"></span></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-12">
|
||||||
|
<button type="submit" class="btn btn-success btn-block">Reimposta Password</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<script src="https://cdn.jsdelivr.net/npm/jquery@3.7.1/dist/jquery.min.js"></script>
|
||||||
|
<script src="https://cdn.jsdelivr.net/npm/bootstrap@4.6.2/dist/js/bootstrap.bundle.min.js"></script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
@@ -42,7 +42,7 @@ $appName = \App\Models\AppSetting::getAppName() ?? 'Glastree';
|
|||||||
<i class="fas fa-user"></i> {{ Auth::user()->name }}
|
<i class="fas fa-user"></i> {{ Auth::user()->name }}
|
||||||
</a>
|
</a>
|
||||||
<div class="dropdown-menu dropdown-menu-right">
|
<div class="dropdown-menu dropdown-menu-right">
|
||||||
<a href="#" class="dropdown-item">
|
<a href="{{ route('profile') }}" class="dropdown-item">
|
||||||
<i class="fas fa-user-cog mr-2"></i> Profilo
|
<i class="fas fa-user-cog mr-2"></i> Profilo
|
||||||
</a>
|
</a>
|
||||||
<div class="dropdown-divider"></div>
|
<div class="dropdown-divider"></div>
|
||||||
|
|||||||
@@ -2,6 +2,37 @@
|
|||||||
@section('title', 'Report')
|
@section('title', 'Report')
|
||||||
@section('page_title', 'Report e Statistiche')
|
@section('page_title', 'Report e Statistiche')
|
||||||
|
|
||||||
|
@section('styles')
|
||||||
|
<link href="https://cdn.jsdelivr.net/npm/select2@4.1.0-rc.0/dist/css/select2.min.css" rel="stylesheet" />
|
||||||
|
<link href="https://cdn.jsdelivr.net/npm/select2-bootstrap4-theme@1.0.0/dist/select2-bootstrap4.min.css" rel="stylesheet" />
|
||||||
|
<style>
|
||||||
|
.select2-container--bootstrap4 .select2-selection--multiple .select2-selection__choice {
|
||||||
|
font-size: 0.8rem;
|
||||||
|
}
|
||||||
|
.card-indigo {
|
||||||
|
border-top: 3px solid #6610f2;
|
||||||
|
}
|
||||||
|
.card-indigo .card-title {
|
||||||
|
color: #6610f2;
|
||||||
|
}
|
||||||
|
.accordion .card-header .btn-link {
|
||||||
|
text-decoration: none;
|
||||||
|
font-weight: 600;
|
||||||
|
color: #495057;
|
||||||
|
}
|
||||||
|
.accordion .card-header .btn-link:hover {
|
||||||
|
color: #007bff;
|
||||||
|
}
|
||||||
|
.accordion .card-header .btn-link i {
|
||||||
|
transition: transform 0.2s ease;
|
||||||
|
width: 20px;
|
||||||
|
}
|
||||||
|
.accordion .card-header .btn-link[aria-expanded="true"] i.fa-chevron-right {
|
||||||
|
transform: rotate(90deg);
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
@endsection
|
||||||
|
|
||||||
@section('content')
|
@section('content')
|
||||||
|
|
||||||
@if(session('success'))
|
@if(session('success'))
|
||||||
@@ -18,12 +49,18 @@
|
|||||||
</div>
|
</div>
|
||||||
@endif
|
@endif
|
||||||
|
|
||||||
<div class="row">
|
<div class="accordion" id="reportAccordion">
|
||||||
<div class="col-12">
|
{{-- Sezione 1: Panoramica Database (sempre aperta) --}}
|
||||||
<div class="card">
|
<div class="card">
|
||||||
<div class="card-header">
|
<div class="card-header" id="headingOne">
|
||||||
<h3 class="card-title"><i class="fas fa-chart-bar mr-2"></i>Panoramica Database</h3>
|
<h2 class="mb-0">
|
||||||
</div>
|
<button class="btn btn-link btn-block text-left" type="button" data-toggle="collapse" data-target="#collapseOne" aria-expanded="true" aria-controls="collapseOne">
|
||||||
|
<i class="fas fa-chevron-down mr-2 text-primary"></i>
|
||||||
|
<i class="fas fa-chart-bar mr-2 text-primary"></i> Panoramica Database
|
||||||
|
</button>
|
||||||
|
</h2>
|
||||||
|
</div>
|
||||||
|
<div id="collapseOne" class="collapse show" aria-labelledby="headingOne" data-parent="#reportAccordion">
|
||||||
<div class="card-body">
|
<div class="card-body">
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col-md-2 col-4 text-center">
|
<div class="col-md-2 col-4 text-center">
|
||||||
@@ -84,29 +121,63 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="row">
|
{{-- Sezione 2: Report Predefiniti --}}
|
||||||
<div class="col-12">
|
<div class="card">
|
||||||
<div class="card">
|
<div class="card-header" id="headingTwo">
|
||||||
<div class="card-header">
|
<h2 class="mb-0">
|
||||||
<h3 class="card-title"><i class="fas fa-chart-pie mr-2"></i>Report Predefiniti</h3>
|
<button class="btn btn-link btn-block text-left collapsed" type="button" data-toggle="collapse" data-target="#collapseTwo" aria-expanded="false" aria-controls="collapseTwo">
|
||||||
</div>
|
<i class="fas fa-chevron-right mr-2 text-warning"></i>
|
||||||
|
<i class="fas fa-chart-pie mr-2 text-warning"></i> Report Predefiniti
|
||||||
|
<span class="badge badge-warning badge-pill ml-2">{{ count($prebuiltReports) }}</span>
|
||||||
|
</button>
|
||||||
|
</h2>
|
||||||
|
</div>
|
||||||
|
<div id="collapseTwo" class="collapse" aria-labelledby="headingTwo" data-parent="#reportAccordion">
|
||||||
<div class="card-body">
|
<div class="card-body">
|
||||||
<div class="row">
|
<div class="row">
|
||||||
@foreach($prebuiltReports as $report)
|
@foreach($prebuiltReports as $report)
|
||||||
<div class="col-md-4 col-lg-3 mb-3">
|
<div class="col-md-4 col-lg-3 mb-3">
|
||||||
<div class="card card-{{ $report['colore'] }} card-outline h-100">
|
@php
|
||||||
<div class="card-body text-center">
|
$cardColor = $report['colore'] === 'indigo' ? 'card-indigo' : 'card-' . $report['colore'];
|
||||||
<i class="fas {{ $report['icona'] }} fa-3x text-{{ $report['colore'] }} mb-3"></i>
|
$btnColor = $report['colore'] === 'indigo' ? 'primary' : $report['colore'];
|
||||||
|
@endphp
|
||||||
|
<div class="card {{ $cardColor }} card-outline h-100">
|
||||||
|
<div class="card-body text-center d-flex flex-column">
|
||||||
|
<i class="fas {{ $report['icona'] }} fa-3x text-{{ $btnColor }} mb-3"></i>
|
||||||
<h5>{{ $report['nome'] }}</h5>
|
<h5>{{ $report['nome'] }}</h5>
|
||||||
<p class="text-muted small mb-3">{{ $report['descrizione'] }}</p>
|
<p class="text-muted small mb-3 flex-grow-1">{{ $report['descrizione'] }}</p>
|
||||||
<a href="{{ route('report.run', ['report' => $report['id']]) }}" class="btn btn-sm btn-{{ $report['colore'] }}">
|
|
||||||
<i class="fas fa-play mr-1"></i> Genera
|
@if($report['has_tipologia_filter'] ?? false)
|
||||||
</a>
|
<form action="{{ route('report.run') }}" method="GET" class="mb-2">
|
||||||
<a href="{{ route('report.export', ['report' => $report['id']]) }}" class="btn btn-sm btn-outline-{{ $report['colore'] }}">
|
<div class="form-group mb-2">
|
||||||
<i class="fas fa-download mr-1"></i> CSV
|
<select name="tipologia_documento" class="form-control form-control-sm select2-tipologia" style="width: 100%;">
|
||||||
</a>
|
<option value="">Tutte le tipologie</option>
|
||||||
|
@foreach($tipologieDocumento as $tipologia)
|
||||||
|
<option value="{{ $tipologia }}">{{ ucfirst($tipologia) }}</option>
|
||||||
|
@endforeach
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
<input type="hidden" name="report" value="{{ $report['id'] }}">
|
||||||
|
<div class="btn-group btn-group-sm w-100">
|
||||||
|
<button type="submit" class="btn btn-sm btn-{{ $btnColor }}">
|
||||||
|
<i class="fas fa-play mr-1"></i> Genera
|
||||||
|
</button>
|
||||||
|
<a href="{{ route('report.export', ['report' => $report['id']]) }}" class="btn btn-sm btn-outline-{{ $btnColor }}">
|
||||||
|
<i class="fas fa-download mr-1"></i> CSV
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
@else
|
||||||
|
<div class="btn-group btn-group-sm w-100">
|
||||||
|
<a href="{{ route('report.run', ['report' => $report['id']]) }}" class="btn btn-sm btn-{{ $btnColor }}">
|
||||||
|
<i class="fas fa-play mr-1"></i> Genera
|
||||||
|
</a>
|
||||||
|
<a href="{{ route('report.export', ['report' => $report['id']]) }}" class="btn btn-sm btn-outline-{{ $btnColor }}">
|
||||||
|
<i class="fas fa-download mr-1"></i> CSV
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
@endif
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -115,109 +186,123 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="row">
|
{{-- Sezione 3: Report Personalizzati --}}
|
||||||
<div class="col-md-8">
|
<div class="card">
|
||||||
<div class="card">
|
<div class="card-header" id="headingThree">
|
||||||
<div class="card-header">
|
<h2 class="mb-0">
|
||||||
<h3 class="card-title"><i class="fas fa-bookmark mr-2"></i>Report Personalizzati</h3>
|
<button class="btn btn-link btn-block text-left collapsed" type="button" data-toggle="collapse" data-target="#collapseThree" aria-expanded="false" aria-controls="collapseThree">
|
||||||
</div>
|
<i class="fas fa-chevron-right mr-2 text-info"></i>
|
||||||
<div class="card-body p-0">
|
<i class="fas fa-bookmark mr-2 text-info"></i> Report Personalizzati
|
||||||
@if($customReports->count() > 0)
|
<span class="badge badge-info badge-pill ml-2">{{ $customReports->count() }}</span>
|
||||||
<table class="table table-striped mb-0">
|
</button>
|
||||||
<thead>
|
</h2>
|
||||||
<tr>
|
</div>
|
||||||
<th>Nome</th>
|
<div id="collapseThree" class="collapse" aria-labelledby="headingThree" data-parent="#reportAccordion">
|
||||||
<th>Tipo</th>
|
<div class="card-body">
|
||||||
<th>Creato il</th>
|
<div class="row">
|
||||||
<th style="width: 120px;">Azioni</th>
|
<div class="col-md-8">
|
||||||
</tr>
|
@if($customReports->count() > 0)
|
||||||
</thead>
|
<table class="table table-striped mb-0">
|
||||||
<tbody>
|
<thead>
|
||||||
@foreach($customReports as $report)
|
<tr>
|
||||||
<tr>
|
<th>Nome</th>
|
||||||
<td>
|
<th>Tipo</th>
|
||||||
<i class="fas fa-chart-line text-primary mr-1"></i>
|
<th>Creato il</th>
|
||||||
{{ $report->nome }}
|
<th style="width: 140px;">Azioni</th>
|
||||||
</td>
|
</tr>
|
||||||
<td><span class="badge badge-info">{{ ucfirst($report->tipo_report) }}</span></td>
|
</thead>
|
||||||
<td>{{ $report->created_at->format('d/m/Y H:i') }}</td>
|
<tbody>
|
||||||
<td>
|
@foreach($customReports as $report)
|
||||||
<a href="{{ route('report.run-custom', $report->id) }}" class="btn btn-xs btn-success" title="Esegui">
|
<tr>
|
||||||
<i class="fas fa-play"></i>
|
<td>
|
||||||
</a>
|
<i class="fas fa-chart-line text-primary mr-1"></i>
|
||||||
<form action="{{ route('report.destroy-custom', $report->id) }}" method="POST" class="d-inline">
|
{{ $report->nome }}
|
||||||
@csrf @method('DELETE')
|
</td>
|
||||||
<button type="submit" class="btn btn-xs btn-danger" onclick="return confirm('Eliminare questo report?')" title="Elimina">
|
<td><span class="badge badge-info">{{ ucfirst($report->tipo_report) }}</span></td>
|
||||||
<i class="fas fa-trash"></i>
|
<td>{{ $report->created_at->format('d/m/Y H:i') }}</td>
|
||||||
|
<td>
|
||||||
|
<a href="{{ route('report.run', ['custom_id' => $report->id]) }}" class="btn btn-xs btn-success" title="Esegui">
|
||||||
|
<i class="fas fa-play"></i>
|
||||||
|
</a>
|
||||||
|
<a href="{{ route('report.export', ['custom_id' => $report->id]) }}" class="btn btn-xs btn-info" title="Esporta CSV">
|
||||||
|
<i class="fas fa-file-csv"></i>
|
||||||
|
</a>
|
||||||
|
<form action="{{ route('report.destroy-custom', $report->id) }}" method="POST" class="d-inline">
|
||||||
|
@csrf @method('DELETE')
|
||||||
|
<button type="submit" class="btn btn-xs btn-danger" onclick="return confirm('Eliminare questo report?')" 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-chart-line fa-3x mb-3"></i>
|
||||||
|
<p>Nessun report personalizzato creato.</p>
|
||||||
|
</div>
|
||||||
|
@endif
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="col-md-4">
|
||||||
|
<div class="card">
|
||||||
|
<div class="card-header bg-primary">
|
||||||
|
<h3 class="card-title text-white"><i class="fas fa-plus mr-2"></i>Crea Report Personalizzato</h3>
|
||||||
|
</div>
|
||||||
|
<div class="card-body">
|
||||||
|
<form action="{{ route('report.store-custom') }}" method="POST">
|
||||||
|
@csrf
|
||||||
|
<div class="form-group">
|
||||||
|
<label>Nome Report *</label>
|
||||||
|
<input type="text" name="nome" class="form-control" required placeholder="Es: Individui attivi">
|
||||||
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<label>Descrizione</label>
|
||||||
|
<textarea name="descrizione" class="form-control" rows="2" placeholder="Descrizione opzionale..."></textarea>
|
||||||
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<label>Tipo Dato *</label>
|
||||||
|
<select name="tipo_report" id="tipo_report" class="form-control" required>
|
||||||
|
<option value="">Seleziona...</option>
|
||||||
|
@foreach($columnOptions as $type => $columns)
|
||||||
|
<option value="{{ $type }}">{{ ucfirst($type) }}</option>
|
||||||
|
@endforeach
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<label>Colonne *</label>
|
||||||
|
<select name="columns[]" id="columns_select" class="form-control select2-multi" multiple style="width: 100%;">
|
||||||
|
</select>
|
||||||
|
<small class="text-muted">Seleziona le colonne da includere nel report</small>
|
||||||
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<label>Ordina per</label>
|
||||||
|
<select name="sort_by" id="sort_by" class="form-control">
|
||||||
|
<option value="">Nessun ordinamento</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<label>Direzione ordinamento</label>
|
||||||
|
<select name="sort_direction" class="form-control">
|
||||||
|
<option value="asc">Ascendente (A-Z)</option>
|
||||||
|
<option value="desc" selected>Discendente (Z-A)</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<label>Limite risultati</label>
|
||||||
|
<input type="number" name="limit" class="form-control" placeholder="Es: 100" min="1">
|
||||||
|
</div>
|
||||||
|
<button type="submit" class="btn btn-primary btn-block">
|
||||||
|
<i class="fas fa-save mr-1"></i> Salva Report
|
||||||
</button>
|
</button>
|
||||||
</form>
|
</form>
|
||||||
</td>
|
</div>
|
||||||
</tr>
|
</div>
|
||||||
@endforeach
|
</div>
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
@else
|
|
||||||
<div class="text-center text-muted py-4">
|
|
||||||
<i class="fas fa-chart-line fa-3x mb-3"></i>
|
|
||||||
<p>Nessun report personalizzato creato.</p>
|
|
||||||
</div>
|
</div>
|
||||||
@endif
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="col-md-4">
|
|
||||||
<div class="card">
|
|
||||||
<div class="card-header bg-primary">
|
|
||||||
<h3 class="card-title text-white"><i class="fas fa-plus mr-2"></i>Crea Report Personalizzato</h3>
|
|
||||||
</div>
|
|
||||||
<div class="card-body">
|
|
||||||
<form action="{{ route('report.store-custom') }}" method="POST">
|
|
||||||
@csrf
|
|
||||||
<div class="form-group">
|
|
||||||
<label>Nome Report *</label>
|
|
||||||
<input type="text" name="nome" class="form-control" required placeholder="Es: Individui attivi">
|
|
||||||
</div>
|
|
||||||
<div class="form-group">
|
|
||||||
<label>Descrizione</label>
|
|
||||||
<textarea name="descrizione" class="form-control" rows="2" placeholder="Descrizione opzionale..."></textarea>
|
|
||||||
</div>
|
|
||||||
<div class="form-group">
|
|
||||||
<label>Tipo Dato *</label>
|
|
||||||
<select name="tipo_report" class="form-control" required>
|
|
||||||
<option value="">Seleziona...</option>
|
|
||||||
<option value="individui">Individui</option>
|
|
||||||
<option value="gruppi">Gruppi</option>
|
|
||||||
<option value="eventi">Eventi</option>
|
|
||||||
<option value="documenti">Documenti</option>
|
|
||||||
<option value="contatti">Contatti</option>
|
|
||||||
</select>
|
|
||||||
</div>
|
|
||||||
<div class="form-group">
|
|
||||||
<label>Colonne (separate da virgola)</label>
|
|
||||||
<input type="text" name="columns" class="form-control" placeholder="Es: cognome, nome, email">
|
|
||||||
<small class="text-muted">Lascia vuoto per tutte le colonne</small>
|
|
||||||
</div>
|
|
||||||
<div class="form-group">
|
|
||||||
<label>Ordina per</label>
|
|
||||||
<select name="sort_by" class="form-control">
|
|
||||||
<option value="">Nessun ordinamento</option>
|
|
||||||
<option value="created_at">Data creazione</option>
|
|
||||||
<option value="updated_at">Ultimo aggiornamento</option>
|
|
||||||
<option value="nome">Nome</option>
|
|
||||||
<option value="cognome">Cognome</option>
|
|
||||||
</select>
|
|
||||||
</div>
|
|
||||||
<div class="form-group">
|
|
||||||
<label>Limite risultati</label>
|
|
||||||
<input type="number" name="limit" class="form-control" placeholder="Es: 100" min="1">
|
|
||||||
</div>
|
|
||||||
<button type="submit" class="btn btn-primary btn-block">
|
|
||||||
<i class="fas fa-save mr-1"></i> Salva Report
|
|
||||||
</button>
|
|
||||||
</form>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -225,25 +310,105 @@
|
|||||||
@endsection
|
@endsection
|
||||||
|
|
||||||
@section('scripts')
|
@section('scripts')
|
||||||
|
<script src="https://cdn.jsdelivr.net/npm/select2@4.1.0-rc.0/dist/js/select2.min.js"></script>
|
||||||
<script>
|
<script>
|
||||||
document.addEventListener('DOMContentLoaded', function() {
|
document.addEventListener('DOMContentLoaded', function() {
|
||||||
const tipoSelect = document.querySelector('select[name="tipo_report"]');
|
const columnOptions = @json($columnOptions);
|
||||||
const columnsInput = document.querySelector('input[name="columns"]');
|
let columnsSelectInitialized = false;
|
||||||
const sortBySelect = document.querySelector('select[name="sort_by"]');
|
|
||||||
|
|
||||||
const columns = {
|
function initTipologiaSelect2(container) {
|
||||||
'individui': 'codice_id, cognome, nome, data_nascita, genere, email, telefono',
|
$(container).find('.select2-tipologia').select2({
|
||||||
'gruppi': 'nome, descrizione, diocesi_id',
|
theme: 'bootstrap4',
|
||||||
'eventi': 'nome_evento, data_inizio, tipo_recorrenza',
|
placeholder: 'Filtra per tipologia...',
|
||||||
'documenti': 'nome_file, tipologia, visibilita, created_at',
|
allowClear: true,
|
||||||
'contatti': 'tipo, valore, etichetta',
|
width: '100%',
|
||||||
};
|
});
|
||||||
|
}
|
||||||
|
|
||||||
tipoSelect.addEventListener('change', function() {
|
function destroyTipologiaSelect2(container) {
|
||||||
if (columns[this.value]) {
|
$(container).find('.select2-tipologia').select2('destroy');
|
||||||
columnsInput.placeholder = 'Suggerite: ' + columns[this.value];
|
}
|
||||||
|
|
||||||
|
function initColumnsSelect2() {
|
||||||
|
if (columnsSelectInitialized) return;
|
||||||
|
$('#columns_select').select2({
|
||||||
|
theme: 'bootstrap4',
|
||||||
|
placeholder: 'Seleziona le colonne...',
|
||||||
|
allowClear: true,
|
||||||
|
width: '100%',
|
||||||
|
});
|
||||||
|
columnsSelectInitialized = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
function destroyColumnsSelect2() {
|
||||||
|
if (!columnsSelectInitialized) return;
|
||||||
|
$('#columns_select').select2('destroy');
|
||||||
|
columnsSelectInitialized = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
function getSortOptions(tipoReport) {
|
||||||
|
const cols = columnOptions[tipoReport] || {};
|
||||||
|
const options = [];
|
||||||
|
for (const [key, col] of Object.entries(cols)) {
|
||||||
|
if (col.type !== 'relation') {
|
||||||
|
options.push({ value: key, label: col.label });
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
return options;
|
||||||
|
}
|
||||||
|
|
||||||
|
$('#collapseTwo').on('shown.bs.collapse', function () {
|
||||||
|
initTipologiaSelect2(this);
|
||||||
|
}).on('hidden.bs.collapse', function () {
|
||||||
|
destroyTipologiaSelect2(this);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
$('#collapseThree').on('shown.bs.collapse', function () {
|
||||||
|
initColumnsSelect2();
|
||||||
|
}).on('hidden.bs.collapse', function () {
|
||||||
|
destroyColumnsSelect2();
|
||||||
|
});
|
||||||
|
|
||||||
|
if ($('#collapseTwo').hasClass('show')) {
|
||||||
|
initTipologiaSelect2(document);
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($('#collapseThree').hasClass('show')) {
|
||||||
|
initColumnsSelect2();
|
||||||
|
}
|
||||||
|
|
||||||
|
$('#tipo_report').on('change', function() {
|
||||||
|
const tipo = this.value;
|
||||||
|
const $select = $('#columns_select');
|
||||||
|
if (columnsSelectInitialized) {
|
||||||
|
$select.empty().trigger('change');
|
||||||
|
}
|
||||||
|
$select.empty();
|
||||||
|
|
||||||
|
if (tipo && columnOptions[tipo]) {
|
||||||
|
const cols = columnOptions[tipo];
|
||||||
|
const fragment = document.createDocumentFragment();
|
||||||
|
for (const [key, col] of Object.entries(cols)) {
|
||||||
|
fragment.appendChild(new Option(col.label, key, false, false));
|
||||||
|
}
|
||||||
|
$select.append(fragment);
|
||||||
|
}
|
||||||
|
if (columnsSelectInitialized) {
|
||||||
|
$select.trigger('change');
|
||||||
|
}
|
||||||
|
|
||||||
|
const $sortBy = $('#sort_by');
|
||||||
|
$sortBy.empty();
|
||||||
|
$sortBy.append(new Option('Nessun ordinamento', '', false, false));
|
||||||
|
const sortOptions = getSortOptions(tipo);
|
||||||
|
sortOptions.forEach(function(opt) {
|
||||||
|
$sortBy.append(new Option(opt.label, opt.value, false, false));
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
if ($('#tipo_report').val()) {
|
||||||
|
$('#tipo_report').trigger('change');
|
||||||
|
}
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
@endsection
|
@endsection
|
||||||
|
|||||||
@@ -11,10 +11,14 @@
|
|||||||
<a href="{{ route('report.index') }}" class="btn btn-sm btn-secondary mr-2">
|
<a href="{{ route('report.index') }}" class="btn btn-sm btn-secondary mr-2">
|
||||||
<i class="fas fa-arrow-left mr-1"></i> Torna ai Report
|
<i class="fas fa-arrow-left mr-1"></i> Torna ai Report
|
||||||
</a>
|
</a>
|
||||||
<a href="{{ route('report.print-preview', ['report' => $reportType, 'custom_id' => $customReport->id ?? null]) }}" class="btn btn-sm btn-info mr-2" target="_blank">
|
@php
|
||||||
|
$printParams = isset($report) ? ['custom_id' => $report->id] : ['report' => $reportType ?? ''];
|
||||||
|
$exportParams = isset($report) ? ['custom_id' => $report->id] : ['report' => $reportType ?? ''];
|
||||||
|
@endphp
|
||||||
|
<a href="{{ route('report.print-preview', $printParams) }}" class="btn btn-sm btn-info mr-2" target="_blank">
|
||||||
<i class="fas fa-print mr-1"></i> Stampa Report
|
<i class="fas fa-print mr-1"></i> Stampa Report
|
||||||
</a>
|
</a>
|
||||||
<a href="{{ route('report.export', ['report' => $reportType]) }}" class="btn btn-sm btn-success">
|
<a href="{{ route('report.export', $exportParams) }}" class="btn btn-sm btn-success">
|
||||||
<i class="fas fa-file-csv mr-1"></i> Esporta CSV
|
<i class="fas fa-file-csv mr-1"></i> Esporta CSV
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
+9
-2
@@ -36,12 +36,19 @@ Route::get('/', function () {
|
|||||||
Route::middleware('guest')->group(function () {
|
Route::middleware('guest')->group(function () {
|
||||||
Route::get('/login', [AuthController::class, 'showLoginForm'])->name('login');
|
Route::get('/login', [AuthController::class, 'showLoginForm'])->name('login');
|
||||||
Route::post('/login', [AuthController::class, 'login']);
|
Route::post('/login', [AuthController::class, 'login']);
|
||||||
Route::get('/register', [AuthController::class, 'showRegisterForm'])->name('register');
|
Route::get('/password/reset', [AuthController::class, 'showForgotForm'])->name('password.request');
|
||||||
Route::post('/register', [AuthController::class, 'register']);
|
Route::post('/password/email', [AuthController::class, 'sendResetLink'])->name('password.email');
|
||||||
|
Route::get('/password/reset/{token}', [AuthController::class, 'showResetForm'])->name('password.reset');
|
||||||
|
Route::post('/password/reset', [AuthController::class, 'reset'])->name('password.update');
|
||||||
});
|
});
|
||||||
|
|
||||||
Route::post('/logout', [AuthController::class, 'logout'])->name('logout')->middleware('auth');
|
Route::post('/logout', [AuthController::class, 'logout'])->name('logout')->middleware('auth');
|
||||||
|
|
||||||
|
Route::middleware('auth')->group(function () {
|
||||||
|
Route::get('/profilo', [\App\Http\Controllers\Auth\ProfileController::class, 'show'])->name('profile');
|
||||||
|
Route::put('/profilo/password', [\App\Http\Controllers\Auth\ProfileController::class, 'updatePassword'])->name('profile.password');
|
||||||
|
});
|
||||||
|
|
||||||
// Dashboard
|
// Dashboard
|
||||||
Route::get('/dashboard', [HomeController::class, 'index'])->name('dashboard')->middleware('auth');
|
Route::get('/dashboard', [HomeController::class, 'index'])->name('dashboard')->middleware('auth');
|
||||||
|
|
||||||
|
|||||||
@@ -10,10 +10,14 @@
|
|||||||
<a href="<?php echo e(route('report.index')); ?>" class="btn btn-sm btn-secondary mr-2">
|
<a href="<?php echo e(route('report.index')); ?>" class="btn btn-sm btn-secondary mr-2">
|
||||||
<i class="fas fa-arrow-left mr-1"></i> Torna ai Report
|
<i class="fas fa-arrow-left mr-1"></i> Torna ai Report
|
||||||
</a>
|
</a>
|
||||||
<a href="<?php echo e(route('report.print-preview', ['report' => $reportType, 'custom_id' => $customReport->id ?? null])); ?>" class="btn btn-sm btn-info mr-2" target="_blank">
|
<?php
|
||||||
|
$printParams = isset($report) ? ['custom_id' => $report->id] : ['report' => $reportType ?? ''];
|
||||||
|
$exportParams = isset($report) ? ['custom_id' => $report->id] : ['report' => $reportType ?? ''];
|
||||||
|
?>
|
||||||
|
<a href="<?php echo e(route('report.print-preview', $printParams)); ?>" class="btn btn-sm btn-info mr-2" target="_blank">
|
||||||
<i class="fas fa-print mr-1"></i> Stampa Report
|
<i class="fas fa-print mr-1"></i> Stampa Report
|
||||||
</a>
|
</a>
|
||||||
<a href="<?php echo e(route('report.export', ['report' => $reportType])); ?>" class="btn btn-sm btn-success">
|
<a href="<?php echo e(route('report.export', $exportParams)); ?>" class="btn btn-sm btn-success">
|
||||||
<i class="fas fa-file-csv mr-1"></i> Esporta CSV
|
<i class="fas fa-file-csv mr-1"></i> Esporta CSV
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,6 +1,37 @@
|
|||||||
<?php $__env->startSection('title', 'Report'); ?>
|
<?php $__env->startSection('title', 'Report'); ?>
|
||||||
<?php $__env->startSection('page_title', 'Report e Statistiche'); ?>
|
<?php $__env->startSection('page_title', 'Report e Statistiche'); ?>
|
||||||
|
|
||||||
|
<?php $__env->startSection('styles'); ?>
|
||||||
|
<link href="https://cdn.jsdelivr.net/npm/select2@4.1.0-rc.0/dist/css/select2.min.css" rel="stylesheet" />
|
||||||
|
<link href="https://cdn.jsdelivr.net/npm/select2-bootstrap4-theme@1.0.0/dist/select2-bootstrap4.min.css" rel="stylesheet" />
|
||||||
|
<style>
|
||||||
|
.select2-container--bootstrap4 .select2-selection--multiple .select2-selection__choice {
|
||||||
|
font-size: 0.8rem;
|
||||||
|
}
|
||||||
|
.card-indigo {
|
||||||
|
border-top: 3px solid #6610f2;
|
||||||
|
}
|
||||||
|
.card-indigo .card-title {
|
||||||
|
color: #6610f2;
|
||||||
|
}
|
||||||
|
.accordion .card-header .btn-link {
|
||||||
|
text-decoration: none;
|
||||||
|
font-weight: 600;
|
||||||
|
color: #495057;
|
||||||
|
}
|
||||||
|
.accordion .card-header .btn-link:hover {
|
||||||
|
color: #007bff;
|
||||||
|
}
|
||||||
|
.accordion .card-header .btn-link i {
|
||||||
|
transition: transform 0.2s ease;
|
||||||
|
width: 20px;
|
||||||
|
}
|
||||||
|
.accordion .card-header .btn-link[aria-expanded="true"] i.fa-chevron-right {
|
||||||
|
transform: rotate(90deg);
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
<?php $__env->stopSection(); ?>
|
||||||
|
|
||||||
<?php $__env->startSection('content'); ?>
|
<?php $__env->startSection('content'); ?>
|
||||||
|
|
||||||
<?php if(session('success')): ?>
|
<?php if(session('success')): ?>
|
||||||
@@ -19,12 +50,18 @@
|
|||||||
</div>
|
</div>
|
||||||
<?php endif; ?>
|
<?php endif; ?>
|
||||||
|
|
||||||
<div class="row">
|
<div class="accordion" id="reportAccordion">
|
||||||
<div class="col-12">
|
|
||||||
<div class="card">
|
<div class="card">
|
||||||
<div class="card-header">
|
<div class="card-header" id="headingOne">
|
||||||
<h3 class="card-title"><i class="fas fa-chart-bar mr-2"></i>Panoramica Database</h3>
|
<h2 class="mb-0">
|
||||||
</div>
|
<button class="btn btn-link btn-block text-left" type="button" data-toggle="collapse" data-target="#collapseOne" aria-expanded="true" aria-controls="collapseOne">
|
||||||
|
<i class="fas fa-chevron-down mr-2 text-primary"></i>
|
||||||
|
<i class="fas fa-chart-bar mr-2 text-primary"></i> Panoramica Database
|
||||||
|
</button>
|
||||||
|
</h2>
|
||||||
|
</div>
|
||||||
|
<div id="collapseOne" class="collapse show" aria-labelledby="headingOne" data-parent="#reportAccordion">
|
||||||
<div class="card-body">
|
<div class="card-body">
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col-md-2 col-4 text-center">
|
<div class="col-md-2 col-4 text-center">
|
||||||
@@ -85,29 +122,63 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="row">
|
|
||||||
<div class="col-12">
|
<div class="card">
|
||||||
<div class="card">
|
<div class="card-header" id="headingTwo">
|
||||||
<div class="card-header">
|
<h2 class="mb-0">
|
||||||
<h3 class="card-title"><i class="fas fa-chart-pie mr-2"></i>Report Predefiniti</h3>
|
<button class="btn btn-link btn-block text-left collapsed" type="button" data-toggle="collapse" data-target="#collapseTwo" aria-expanded="false" aria-controls="collapseTwo">
|
||||||
</div>
|
<i class="fas fa-chevron-right mr-2 text-warning"></i>
|
||||||
|
<i class="fas fa-chart-pie mr-2 text-warning"></i> Report Predefiniti
|
||||||
|
<span class="badge badge-warning badge-pill ml-2"><?php echo e(count($prebuiltReports)); ?></span>
|
||||||
|
</button>
|
||||||
|
</h2>
|
||||||
|
</div>
|
||||||
|
<div id="collapseTwo" class="collapse" aria-labelledby="headingTwo" data-parent="#reportAccordion">
|
||||||
<div class="card-body">
|
<div class="card-body">
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<?php $__currentLoopData = $prebuiltReports; $__env->addLoop($__currentLoopData); foreach($__currentLoopData as $report): $__env->incrementLoopIndices(); $loop = $__env->getLastLoop(); ?>
|
<?php $__currentLoopData = $prebuiltReports; $__env->addLoop($__currentLoopData); foreach($__currentLoopData as $report): $__env->incrementLoopIndices(); $loop = $__env->getLastLoop(); ?>
|
||||||
<div class="col-md-4 col-lg-3 mb-3">
|
<div class="col-md-4 col-lg-3 mb-3">
|
||||||
<div class="card card-<?php echo e($report['colore']); ?> card-outline h-100">
|
<?php
|
||||||
<div class="card-body text-center">
|
$cardColor = $report['colore'] === 'indigo' ? 'card-indigo' : 'card-' . $report['colore'];
|
||||||
<i class="fas <?php echo e($report['icona']); ?> fa-3x text-<?php echo e($report['colore']); ?> mb-3"></i>
|
$btnColor = $report['colore'] === 'indigo' ? 'primary' : $report['colore'];
|
||||||
|
?>
|
||||||
|
<div class="card <?php echo e($cardColor); ?> card-outline h-100">
|
||||||
|
<div class="card-body text-center d-flex flex-column">
|
||||||
|
<i class="fas <?php echo e($report['icona']); ?> fa-3x text-<?php echo e($btnColor); ?> mb-3"></i>
|
||||||
<h5><?php echo e($report['nome']); ?></h5>
|
<h5><?php echo e($report['nome']); ?></h5>
|
||||||
<p class="text-muted small mb-3"><?php echo e($report['descrizione']); ?></p>
|
<p class="text-muted small mb-3 flex-grow-1"><?php echo e($report['descrizione']); ?></p>
|
||||||
<a href="<?php echo e(route('report.run', ['report' => $report['id']])); ?>" class="btn btn-sm btn-<?php echo e($report['colore']); ?>">
|
|
||||||
<i class="fas fa-play mr-1"></i> Genera
|
<?php if($report['has_tipologia_filter'] ?? false): ?>
|
||||||
</a>
|
<form action="<?php echo e(route('report.run')); ?>" method="GET" class="mb-2">
|
||||||
<a href="<?php echo e(route('report.export', ['report' => $report['id']])); ?>" class="btn btn-sm btn-outline-<?php echo e($report['colore']); ?>">
|
<div class="form-group mb-2">
|
||||||
<i class="fas fa-download mr-1"></i> CSV
|
<select name="tipologia_documento" class="form-control form-control-sm select2-tipologia" style="width: 100%;">
|
||||||
</a>
|
<option value="">Tutte le tipologie</option>
|
||||||
|
<?php $__currentLoopData = $tipologieDocumento; $__env->addLoop($__currentLoopData); foreach($__currentLoopData as $tipologia): $__env->incrementLoopIndices(); $loop = $__env->getLastLoop(); ?>
|
||||||
|
<option value="<?php echo e($tipologia); ?>"><?php echo e(ucfirst($tipologia)); ?></option>
|
||||||
|
<?php endforeach; $__env->popLoop(); $loop = $__env->getLastLoop(); ?>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
<input type="hidden" name="report" value="<?php echo e($report['id']); ?>">
|
||||||
|
<div class="btn-group btn-group-sm w-100">
|
||||||
|
<button type="submit" class="btn btn-sm btn-<?php echo e($btnColor); ?>">
|
||||||
|
<i class="fas fa-play mr-1"></i> Genera
|
||||||
|
</button>
|
||||||
|
<a href="<?php echo e(route('report.export', ['report' => $report['id']])); ?>" class="btn btn-sm btn-outline-<?php echo e($btnColor); ?>">
|
||||||
|
<i class="fas fa-download mr-1"></i> CSV
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
<?php else: ?>
|
||||||
|
<div class="btn-group btn-group-sm w-100">
|
||||||
|
<a href="<?php echo e(route('report.run', ['report' => $report['id']])); ?>" class="btn btn-sm btn-<?php echo e($btnColor); ?>">
|
||||||
|
<i class="fas fa-play mr-1"></i> Genera
|
||||||
|
</a>
|
||||||
|
<a href="<?php echo e(route('report.export', ['report' => $report['id']])); ?>" class="btn btn-sm btn-outline-<?php echo e($btnColor); ?>">
|
||||||
|
<i class="fas fa-download mr-1"></i> CSV
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
<?php endif; ?>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -116,110 +187,124 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="row">
|
|
||||||
<div class="col-md-8">
|
<div class="card">
|
||||||
<div class="card">
|
<div class="card-header" id="headingThree">
|
||||||
<div class="card-header">
|
<h2 class="mb-0">
|
||||||
<h3 class="card-title"><i class="fas fa-bookmark mr-2"></i>Report Personalizzati</h3>
|
<button class="btn btn-link btn-block text-left collapsed" type="button" data-toggle="collapse" data-target="#collapseThree" aria-expanded="false" aria-controls="collapseThree">
|
||||||
</div>
|
<i class="fas fa-chevron-right mr-2 text-info"></i>
|
||||||
<div class="card-body p-0">
|
<i class="fas fa-bookmark mr-2 text-info"></i> Report Personalizzati
|
||||||
<?php if($customReports->count() > 0): ?>
|
<span class="badge badge-info badge-pill ml-2"><?php echo e($customReports->count()); ?></span>
|
||||||
<table class="table table-striped mb-0">
|
</button>
|
||||||
<thead>
|
</h2>
|
||||||
<tr>
|
</div>
|
||||||
<th>Nome</th>
|
<div id="collapseThree" class="collapse" aria-labelledby="headingThree" data-parent="#reportAccordion">
|
||||||
<th>Tipo</th>
|
<div class="card-body">
|
||||||
<th>Creato il</th>
|
<div class="row">
|
||||||
<th style="width: 120px;">Azioni</th>
|
<div class="col-md-8">
|
||||||
</tr>
|
<?php if($customReports->count() > 0): ?>
|
||||||
</thead>
|
<table class="table table-striped mb-0">
|
||||||
<tbody>
|
<thead>
|
||||||
<?php $__currentLoopData = $customReports; $__env->addLoop($__currentLoopData); foreach($__currentLoopData as $report): $__env->incrementLoopIndices(); $loop = $__env->getLastLoop(); ?>
|
<tr>
|
||||||
<tr>
|
<th>Nome</th>
|
||||||
<td>
|
<th>Tipo</th>
|
||||||
<i class="fas fa-chart-line text-primary mr-1"></i>
|
<th>Creato il</th>
|
||||||
<?php echo e($report->nome); ?>
|
<th style="width: 140px;">Azioni</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
<?php $__currentLoopData = $customReports; $__env->addLoop($__currentLoopData); foreach($__currentLoopData as $report): $__env->incrementLoopIndices(); $loop = $__env->getLastLoop(); ?>
|
||||||
|
<tr>
|
||||||
|
<td>
|
||||||
|
<i class="fas fa-chart-line text-primary mr-1"></i>
|
||||||
|
<?php echo e($report->nome); ?>
|
||||||
|
|
||||||
</td>
|
</td>
|
||||||
<td><span class="badge badge-info"><?php echo e(ucfirst($report->tipo_report)); ?></span></td>
|
<td><span class="badge badge-info"><?php echo e(ucfirst($report->tipo_report)); ?></span></td>
|
||||||
<td><?php echo e($report->created_at->format('d/m/Y H:i')); ?></td>
|
<td><?php echo e($report->created_at->format('d/m/Y H:i')); ?></td>
|
||||||
<td>
|
<td>
|
||||||
<a href="<?php echo e(route('report.run-custom', $report->id)); ?>" class="btn btn-xs btn-success" title="Esegui">
|
<a href="<?php echo e(route('report.run', ['custom_id' => $report->id])); ?>" class="btn btn-xs btn-success" title="Esegui">
|
||||||
<i class="fas fa-play"></i>
|
<i class="fas fa-play"></i>
|
||||||
</a>
|
</a>
|
||||||
<form action="<?php echo e(route('report.destroy-custom', $report->id)); ?>" method="POST" class="d-inline">
|
<a href="<?php echo e(route('report.export', ['custom_id' => $report->id])); ?>" class="btn btn-xs btn-info" title="Esporta CSV">
|
||||||
<?php echo csrf_field(); ?> <?php echo method_field('DELETE'); ?>
|
<i class="fas fa-file-csv"></i>
|
||||||
<button type="submit" class="btn btn-xs btn-danger" onclick="return confirm('Eliminare questo report?')" title="Elimina">
|
</a>
|
||||||
<i class="fas fa-trash"></i>
|
<form action="<?php echo e(route('report.destroy-custom', $report->id)); ?>" method="POST" class="d-inline">
|
||||||
|
<?php echo csrf_field(); ?> <?php echo method_field('DELETE'); ?>
|
||||||
|
<button type="submit" class="btn btn-xs btn-danger" onclick="return confirm('Eliminare questo report?')" title="Elimina">
|
||||||
|
<i class="fas fa-trash"></i>
|
||||||
|
</button>
|
||||||
|
</form>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<?php endforeach; $__env->popLoop(); $loop = $__env->getLastLoop(); ?>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
<?php else: ?>
|
||||||
|
<div class="text-center text-muted py-4">
|
||||||
|
<i class="fas fa-chart-line fa-3x mb-3"></i>
|
||||||
|
<p>Nessun report personalizzato creato.</p>
|
||||||
|
</div>
|
||||||
|
<?php endif; ?>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="col-md-4">
|
||||||
|
<div class="card">
|
||||||
|
<div class="card-header bg-primary">
|
||||||
|
<h3 class="card-title text-white"><i class="fas fa-plus mr-2"></i>Crea Report Personalizzato</h3>
|
||||||
|
</div>
|
||||||
|
<div class="card-body">
|
||||||
|
<form action="<?php echo e(route('report.store-custom')); ?>" method="POST">
|
||||||
|
<?php echo csrf_field(); ?>
|
||||||
|
<div class="form-group">
|
||||||
|
<label>Nome Report *</label>
|
||||||
|
<input type="text" name="nome" class="form-control" required placeholder="Es: Individui attivi">
|
||||||
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<label>Descrizione</label>
|
||||||
|
<textarea name="descrizione" class="form-control" rows="2" placeholder="Descrizione opzionale..."></textarea>
|
||||||
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<label>Tipo Dato *</label>
|
||||||
|
<select name="tipo_report" id="tipo_report" class="form-control" required>
|
||||||
|
<option value="">Seleziona...</option>
|
||||||
|
<?php $__currentLoopData = $columnOptions; $__env->addLoop($__currentLoopData); foreach($__currentLoopData as $type => $columns): $__env->incrementLoopIndices(); $loop = $__env->getLastLoop(); ?>
|
||||||
|
<option value="<?php echo e($type); ?>"><?php echo e(ucfirst($type)); ?></option>
|
||||||
|
<?php endforeach; $__env->popLoop(); $loop = $__env->getLastLoop(); ?>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<label>Colonne *</label>
|
||||||
|
<select name="columns[]" id="columns_select" class="form-control select2-multi" multiple style="width: 100%;">
|
||||||
|
</select>
|
||||||
|
<small class="text-muted">Seleziona le colonne da includere nel report</small>
|
||||||
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<label>Ordina per</label>
|
||||||
|
<select name="sort_by" id="sort_by" class="form-control">
|
||||||
|
<option value="">Nessun ordinamento</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<label>Direzione ordinamento</label>
|
||||||
|
<select name="sort_direction" class="form-control">
|
||||||
|
<option value="asc">Ascendente (A-Z)</option>
|
||||||
|
<option value="desc" selected>Discendente (Z-A)</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<label>Limite risultati</label>
|
||||||
|
<input type="number" name="limit" class="form-control" placeholder="Es: 100" min="1">
|
||||||
|
</div>
|
||||||
|
<button type="submit" class="btn btn-primary btn-block">
|
||||||
|
<i class="fas fa-save mr-1"></i> Salva Report
|
||||||
</button>
|
</button>
|
||||||
</form>
|
</form>
|
||||||
</td>
|
</div>
|
||||||
</tr>
|
</div>
|
||||||
<?php endforeach; $__env->popLoop(); $loop = $__env->getLastLoop(); ?>
|
</div>
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
<?php else: ?>
|
|
||||||
<div class="text-center text-muted py-4">
|
|
||||||
<i class="fas fa-chart-line fa-3x mb-3"></i>
|
|
||||||
<p>Nessun report personalizzato creato.</p>
|
|
||||||
</div>
|
</div>
|
||||||
<?php endif; ?>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="col-md-4">
|
|
||||||
<div class="card">
|
|
||||||
<div class="card-header bg-primary">
|
|
||||||
<h3 class="card-title text-white"><i class="fas fa-plus mr-2"></i>Crea Report Personalizzato</h3>
|
|
||||||
</div>
|
|
||||||
<div class="card-body">
|
|
||||||
<form action="<?php echo e(route('report.store-custom')); ?>" method="POST">
|
|
||||||
<?php echo csrf_field(); ?>
|
|
||||||
<div class="form-group">
|
|
||||||
<label>Nome Report *</label>
|
|
||||||
<input type="text" name="nome" class="form-control" required placeholder="Es: Individui attivi">
|
|
||||||
</div>
|
|
||||||
<div class="form-group">
|
|
||||||
<label>Descrizione</label>
|
|
||||||
<textarea name="descrizione" class="form-control" rows="2" placeholder="Descrizione opzionale..."></textarea>
|
|
||||||
</div>
|
|
||||||
<div class="form-group">
|
|
||||||
<label>Tipo Dato *</label>
|
|
||||||
<select name="tipo_report" class="form-control" required>
|
|
||||||
<option value="">Seleziona...</option>
|
|
||||||
<option value="individui">Individui</option>
|
|
||||||
<option value="gruppi">Gruppi</option>
|
|
||||||
<option value="eventi">Eventi</option>
|
|
||||||
<option value="documenti">Documenti</option>
|
|
||||||
<option value="contatti">Contatti</option>
|
|
||||||
</select>
|
|
||||||
</div>
|
|
||||||
<div class="form-group">
|
|
||||||
<label>Colonne (separate da virgola)</label>
|
|
||||||
<input type="text" name="columns" class="form-control" placeholder="Es: cognome, nome, email">
|
|
||||||
<small class="text-muted">Lascia vuoto per tutte le colonne</small>
|
|
||||||
</div>
|
|
||||||
<div class="form-group">
|
|
||||||
<label>Ordina per</label>
|
|
||||||
<select name="sort_by" class="form-control">
|
|
||||||
<option value="">Nessun ordinamento</option>
|
|
||||||
<option value="created_at">Data creazione</option>
|
|
||||||
<option value="updated_at">Ultimo aggiornamento</option>
|
|
||||||
<option value="nome">Nome</option>
|
|
||||||
<option value="cognome">Cognome</option>
|
|
||||||
</select>
|
|
||||||
</div>
|
|
||||||
<div class="form-group">
|
|
||||||
<label>Limite risultati</label>
|
|
||||||
<input type="number" name="limit" class="form-control" placeholder="Es: 100" min="1">
|
|
||||||
</div>
|
|
||||||
<button type="submit" class="btn btn-primary btn-block">
|
|
||||||
<i class="fas fa-save mr-1"></i> Salva Report
|
|
||||||
</button>
|
|
||||||
</form>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -227,25 +312,105 @@
|
|||||||
<?php $__env->stopSection(); ?>
|
<?php $__env->stopSection(); ?>
|
||||||
|
|
||||||
<?php $__env->startSection('scripts'); ?>
|
<?php $__env->startSection('scripts'); ?>
|
||||||
|
<script src="https://cdn.jsdelivr.net/npm/select2@4.1.0-rc.0/dist/js/select2.min.js"></script>
|
||||||
<script>
|
<script>
|
||||||
document.addEventListener('DOMContentLoaded', function() {
|
document.addEventListener('DOMContentLoaded', function() {
|
||||||
const tipoSelect = document.querySelector('select[name="tipo_report"]');
|
const columnOptions = <?php echo json_encode($columnOptions, 15, 512) ?>;
|
||||||
const columnsInput = document.querySelector('input[name="columns"]');
|
let columnsSelectInitialized = false;
|
||||||
const sortBySelect = document.querySelector('select[name="sort_by"]');
|
|
||||||
|
|
||||||
const columns = {
|
function initTipologiaSelect2(container) {
|
||||||
'individui': 'codice_id, cognome, nome, data_nascita, genere, email, telefono',
|
$(container).find('.select2-tipologia').select2({
|
||||||
'gruppi': 'nome, descrizione, diocesi_id',
|
theme: 'bootstrap4',
|
||||||
'eventi': 'nome_evento, data_inizio, tipo_recorrenza',
|
placeholder: 'Filtra per tipologia...',
|
||||||
'documenti': 'nome_file, tipologia, visibilita, created_at',
|
allowClear: true,
|
||||||
'contatti': 'tipo, valore, etichetta',
|
width: '100%',
|
||||||
};
|
});
|
||||||
|
}
|
||||||
|
|
||||||
tipoSelect.addEventListener('change', function() {
|
function destroyTipologiaSelect2(container) {
|
||||||
if (columns[this.value]) {
|
$(container).find('.select2-tipologia').select2('destroy');
|
||||||
columnsInput.placeholder = 'Suggerite: ' + columns[this.value];
|
}
|
||||||
|
|
||||||
|
function initColumnsSelect2() {
|
||||||
|
if (columnsSelectInitialized) return;
|
||||||
|
$('#columns_select').select2({
|
||||||
|
theme: 'bootstrap4',
|
||||||
|
placeholder: 'Seleziona le colonne...',
|
||||||
|
allowClear: true,
|
||||||
|
width: '100%',
|
||||||
|
});
|
||||||
|
columnsSelectInitialized = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
function destroyColumnsSelect2() {
|
||||||
|
if (!columnsSelectInitialized) return;
|
||||||
|
$('#columns_select').select2('destroy');
|
||||||
|
columnsSelectInitialized = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
function getSortOptions(tipoReport) {
|
||||||
|
const cols = columnOptions[tipoReport] || {};
|
||||||
|
const options = [];
|
||||||
|
for (const [key, col] of Object.entries(cols)) {
|
||||||
|
if (col.type !== 'relation') {
|
||||||
|
options.push({ value: key, label: col.label });
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
return options;
|
||||||
|
}
|
||||||
|
|
||||||
|
$('#collapseTwo').on('shown.bs.collapse', function () {
|
||||||
|
initTipologiaSelect2(this);
|
||||||
|
}).on('hidden.bs.collapse', function () {
|
||||||
|
destroyTipologiaSelect2(this);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
$('#collapseThree').on('shown.bs.collapse', function () {
|
||||||
|
initColumnsSelect2();
|
||||||
|
}).on('hidden.bs.collapse', function () {
|
||||||
|
destroyColumnsSelect2();
|
||||||
|
});
|
||||||
|
|
||||||
|
if ($('#collapseTwo').hasClass('show')) {
|
||||||
|
initTipologiaSelect2(document);
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($('#collapseThree').hasClass('show')) {
|
||||||
|
initColumnsSelect2();
|
||||||
|
}
|
||||||
|
|
||||||
|
$('#tipo_report').on('change', function() {
|
||||||
|
const tipo = this.value;
|
||||||
|
const $select = $('#columns_select');
|
||||||
|
if (columnsSelectInitialized) {
|
||||||
|
$select.empty().trigger('change');
|
||||||
|
}
|
||||||
|
$select.empty();
|
||||||
|
|
||||||
|
if (tipo && columnOptions[tipo]) {
|
||||||
|
const cols = columnOptions[tipo];
|
||||||
|
const fragment = document.createDocumentFragment();
|
||||||
|
for (const [key, col] of Object.entries(cols)) {
|
||||||
|
fragment.appendChild(new Option(col.label, key, false, false));
|
||||||
|
}
|
||||||
|
$select.append(fragment);
|
||||||
|
}
|
||||||
|
if (columnsSelectInitialized) {
|
||||||
|
$select.trigger('change');
|
||||||
|
}
|
||||||
|
|
||||||
|
const $sortBy = $('#sort_by');
|
||||||
|
$sortBy.empty();
|
||||||
|
$sortBy.append(new Option('Nessun ordinamento', '', false, false));
|
||||||
|
const sortOptions = getSortOptions(tipo);
|
||||||
|
sortOptions.forEach(function(opt) {
|
||||||
|
$sortBy.append(new Option(opt.label, opt.value, false, false));
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
if ($('#tipo_report').val()) {
|
||||||
|
$('#tipo_report').trigger('change');
|
||||||
|
}
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
<?php $__env->stopSection(); ?>
|
<?php $__env->stopSection(); ?>
|
||||||
|
|||||||
@@ -0,0 +1,104 @@
|
|||||||
|
<?php $__env->startSection('title', 'Il Mio Profilo'); ?>
|
||||||
|
<?php $__env->startSection('page_title', 'Il Mio Profilo'); ?>
|
||||||
|
|
||||||
|
<?php $__env->startSection('breadcrumbs'); ?>
|
||||||
|
<li class="breadcrumb-item"><a href="<?php echo e(route('dashboard')); ?>">Dashboard</a></li>
|
||||||
|
<li class="breadcrumb-item active">Profilo</li>
|
||||||
|
<?php $__env->stopSection(); ?>
|
||||||
|
|
||||||
|
<?php $__env->startSection('content'); ?>
|
||||||
|
<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>Informazioni Account</h3>
|
||||||
|
</div>
|
||||||
|
<div class="card-body">
|
||||||
|
<dl class="row mb-0">
|
||||||
|
<dt class="col-sm-4">Nome</dt>
|
||||||
|
<dd class="col-sm-8"><?php echo e(e(Auth::user()->name)); ?></dd>
|
||||||
|
<dt class="col-sm-4">Email</dt>
|
||||||
|
<dd class="col-sm-8"><?php echo e(e(Auth::user()->email)); ?></dd>
|
||||||
|
<dt class="col-sm-4">Membro dal</dt>
|
||||||
|
<dd class="col-sm-8"><?php echo e(Auth::user()->created_at->format('d/m/Y')); ?></dd>
|
||||||
|
</dl>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="col-md-6">
|
||||||
|
<div class="card card-warning">
|
||||||
|
<div class="card-header">
|
||||||
|
<h3 class="card-title"><i class="fas fa-key mr-2"></i>Cambia Password</h3>
|
||||||
|
</div>
|
||||||
|
<form method="POST" action="<?php echo e(route('profile.password')); ?>">
|
||||||
|
<?php echo csrf_field(); ?>
|
||||||
|
<?php echo method_field('PUT'); ?>
|
||||||
|
<div class="card-body">
|
||||||
|
<?php if(session('success')): ?>
|
||||||
|
<div class="alert alert-success"><?php echo e(session('success')); ?></div>
|
||||||
|
<?php endif; ?>
|
||||||
|
<?php if(session('error')): ?>
|
||||||
|
<div class="alert alert-danger"><?php echo e(session('error')); ?></div>
|
||||||
|
<?php endif; ?>
|
||||||
|
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="current_password">Password attuale *</label>
|
||||||
|
<input type="password" name="current_password" id="current_password" class="form-control <?php $__errorArgs = ['current_password'];
|
||||||
|
$__bag = $errors->getBag($__errorArgs[1] ?? 'default');
|
||||||
|
if ($__bag->has($__errorArgs[0])) :
|
||||||
|
if (isset($message)) { $__messageOriginal = $message; }
|
||||||
|
$message = $__bag->first($__errorArgs[0]); ?> is-invalid <?php unset($message);
|
||||||
|
if (isset($__messageOriginal)) { $message = $__messageOriginal; }
|
||||||
|
endif;
|
||||||
|
unset($__errorArgs, $__bag); ?>" required>
|
||||||
|
<?php $__errorArgs = ['current_password'];
|
||||||
|
$__bag = $errors->getBag($__errorArgs[1] ?? 'default');
|
||||||
|
if ($__bag->has($__errorArgs[0])) :
|
||||||
|
if (isset($message)) { $__messageOriginal = $message; }
|
||||||
|
$message = $__bag->first($__errorArgs[0]); ?>
|
||||||
|
<span class="invalid-feedback"><?php echo e($message); ?></span>
|
||||||
|
<?php unset($message);
|
||||||
|
if (isset($__messageOriginal)) { $message = $__messageOriginal; }
|
||||||
|
endif;
|
||||||
|
unset($__errorArgs, $__bag); ?>
|
||||||
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="password">Nuova password *</label>
|
||||||
|
<input type="password" name="password" id="password" class="form-control <?php $__errorArgs = ['password'];
|
||||||
|
$__bag = $errors->getBag($__errorArgs[1] ?? 'default');
|
||||||
|
if ($__bag->has($__errorArgs[0])) :
|
||||||
|
if (isset($message)) { $__messageOriginal = $message; }
|
||||||
|
$message = $__bag->first($__errorArgs[0]); ?> is-invalid <?php unset($message);
|
||||||
|
if (isset($__messageOriginal)) { $message = $__messageOriginal; }
|
||||||
|
endif;
|
||||||
|
unset($__errorArgs, $__bag); ?>" required minlength="8">
|
||||||
|
<small class="text-muted">Minimo 8 caratteri</small>
|
||||||
|
<?php $__errorArgs = ['password'];
|
||||||
|
$__bag = $errors->getBag($__errorArgs[1] ?? 'default');
|
||||||
|
if ($__bag->has($__errorArgs[0])) :
|
||||||
|
if (isset($message)) { $__messageOriginal = $message; }
|
||||||
|
$message = $__bag->first($__errorArgs[0]); ?>
|
||||||
|
<span class="invalid-feedback"><?php echo e($message); ?></span>
|
||||||
|
<?php unset($message);
|
||||||
|
if (isset($__messageOriginal)) { $message = $__messageOriginal; }
|
||||||
|
endif;
|
||||||
|
unset($__errorArgs, $__bag); ?>
|
||||||
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="password_confirmation">Conferma nuova password *</label>
|
||||||
|
<input type="password" name="password_confirmation" id="password_confirmation" class="form-control" required minlength="8">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="card-footer">
|
||||||
|
<button type="submit" class="btn btn-warning">
|
||||||
|
<i class="fas fa-save mr-1"></i> Aggiorna Password
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<?php $__env->stopSection(); ?>
|
||||||
|
|
||||||
|
<?php echo $__env->make('layouts.adminlte', array_diff_key(get_defined_vars(), ['__data' => 1, '__path' => 1]))->render(); ?><?php /**PATH /var/www/html/glastree/resources/views/auth/profile.blade.php ENDPATH**/ ?>
|
||||||
@@ -0,0 +1,35 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="it">
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<style>
|
||||||
|
body { font-family: 'Segoe UI', Tahoma, sans-serif; line-height: 1.6; color: #333; }
|
||||||
|
.container { max-width: 600px; margin: 0 auto; padding: 20px; }
|
||||||
|
.header { background: #28a745; color: white; padding: 20px; text-align: center; border-radius: 5px 5px 0 0; }
|
||||||
|
.body { padding: 30px; background: #f9f9f9; border: 1px solid #ddd; }
|
||||||
|
.button { display: inline-block; padding: 12px 30px; background: #28a745; color: white; text-decoration: none; border-radius: 5px; font-weight: bold; }
|
||||||
|
.footer { margin-top: 20px; font-size: 12px; color: #999; text-align: center; }
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div class="container">
|
||||||
|
<div class="header">
|
||||||
|
<h2><?php echo e(e($appName)); ?></h2>
|
||||||
|
</div>
|
||||||
|
<div class="body">
|
||||||
|
<p>Ciao <strong><?php echo e(e($user->name)); ?></strong>,</p>
|
||||||
|
<p>Hai richiesto il reset della password per il tuo account.</p>
|
||||||
|
<p style="text-align: center; margin: 30px 0;">
|
||||||
|
<a href="<?php echo e($resetUrl); ?>" class="button">Reimposta Password</a>
|
||||||
|
</p>
|
||||||
|
<p>Se non hai richiesto tu il reset, ignora questa email.</p>
|
||||||
|
<p>Il link è valido per 60 minuti.</p>
|
||||||
|
</div>
|
||||||
|
<div class="footer">
|
||||||
|
© <?php echo e(date('Y')); ?> <?php echo e(e($appName)); ?>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
<?php /**PATH /var/www/html/glastree/resources/views/auth/emails/reset-password.blade.php ENDPATH**/ ?>
|
||||||
@@ -0,0 +1,85 @@
|
|||||||
|
<?php
|
||||||
|
use App\Models\AppSetting;
|
||||||
|
$appName = AppSetting::getAppName() ?? 'Glastree';
|
||||||
|
?>
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="it">
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||||
|
<title>Reimposta Password - <?php echo e(e($appName)); ?></title>
|
||||||
|
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/admin-lte@3.2/dist/css/adminlte.min.css">
|
||||||
|
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
|
||||||
|
</head>
|
||||||
|
<body class="hold-transition login-page">
|
||||||
|
<div class="login-box">
|
||||||
|
<div class="login-logo">
|
||||||
|
<a href="/"><b><?php echo e(e($appName)); ?></b></a>
|
||||||
|
</div>
|
||||||
|
<div class="card">
|
||||||
|
<div class="card-body login-card-body">
|
||||||
|
<p class="login-box-msg">Scegli una nuova password</p>
|
||||||
|
|
||||||
|
<?php $__errorArgs = ['email'];
|
||||||
|
$__bag = $errors->getBag($__errorArgs[1] ?? 'default');
|
||||||
|
if ($__bag->has($__errorArgs[0])) :
|
||||||
|
if (isset($message)) { $__messageOriginal = $message; }
|
||||||
|
$message = $__bag->first($__errorArgs[0]); ?>
|
||||||
|
<div class="alert alert-danger"><?php echo e($message); ?></div>
|
||||||
|
<?php unset($message);
|
||||||
|
if (isset($__messageOriginal)) { $message = $__messageOriginal; }
|
||||||
|
endif;
|
||||||
|
unset($__errorArgs, $__bag); ?>
|
||||||
|
|
||||||
|
<form action="<?php echo e(route('password.update')); ?>" method="POST">
|
||||||
|
<?php echo csrf_field(); ?>
|
||||||
|
<input type="hidden" name="token" value="<?php echo e($token); ?>">
|
||||||
|
<div class="input-group mb-3">
|
||||||
|
<input type="email" name="email" class="form-control" placeholder="Email" required value="<?php echo e(old('email')); ?>">
|
||||||
|
<div class="input-group-append">
|
||||||
|
<div class="input-group-text"><span class="fas fa-envelope"></span></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="input-group mb-3">
|
||||||
|
<input type="password" name="password" class="form-control <?php $__errorArgs = ['password'];
|
||||||
|
$__bag = $errors->getBag($__errorArgs[1] ?? 'default');
|
||||||
|
if ($__bag->has($__errorArgs[0])) :
|
||||||
|
if (isset($message)) { $__messageOriginal = $message; }
|
||||||
|
$message = $__bag->first($__errorArgs[0]); ?> is-invalid <?php unset($message);
|
||||||
|
if (isset($__messageOriginal)) { $message = $__messageOriginal; }
|
||||||
|
endif;
|
||||||
|
unset($__errorArgs, $__bag); ?>" placeholder="Nuova password" required minlength="8">
|
||||||
|
<div class="input-group-append">
|
||||||
|
<div class="input-group-text"><span class="fas fa-lock"></span></div>
|
||||||
|
</div>
|
||||||
|
<?php $__errorArgs = ['password'];
|
||||||
|
$__bag = $errors->getBag($__errorArgs[1] ?? 'default');
|
||||||
|
if ($__bag->has($__errorArgs[0])) :
|
||||||
|
if (isset($message)) { $__messageOriginal = $message; }
|
||||||
|
$message = $__bag->first($__errorArgs[0]); ?>
|
||||||
|
<span class="invalid-feedback"><?php echo e($message); ?></span>
|
||||||
|
<?php unset($message);
|
||||||
|
if (isset($__messageOriginal)) { $message = $__messageOriginal; }
|
||||||
|
endif;
|
||||||
|
unset($__errorArgs, $__bag); ?>
|
||||||
|
</div>
|
||||||
|
<div class="input-group mb-3">
|
||||||
|
<input type="password" name="password_confirmation" class="form-control" placeholder="Conferma password" required minlength="8">
|
||||||
|
<div class="input-group-append">
|
||||||
|
<div class="input-group-text"><span class="fas fa-lock"></span></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-12">
|
||||||
|
<button type="submit" class="btn btn-success btn-block">Reimposta Password</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<script src="https://cdn.jsdelivr.net/npm/jquery@3.7.1/dist/jquery.min.js"></script>
|
||||||
|
<script src="https://cdn.jsdelivr.net/npm/bootstrap@4.6.2/dist/js/bootstrap.bundle.min.js"></script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
<?php /**PATH /var/www/html/glastree/resources/views/auth/reset-password.blade.php ENDPATH**/ ?>
|
||||||
@@ -0,0 +1,75 @@
|
|||||||
|
<?php
|
||||||
|
use App\Models\AppSetting;
|
||||||
|
$appLogo = AppSetting::getLogoUrl();
|
||||||
|
$appName = AppSetting::getAppName() ?? 'Glastree';
|
||||||
|
?>
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="it">
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||||
|
<title>Reset Password - <?php echo e(e($appName)); ?></title>
|
||||||
|
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/admin-lte@3.2/dist/css/adminlte.min.css">
|
||||||
|
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
|
||||||
|
</head>
|
||||||
|
<body class="hold-transition login-page">
|
||||||
|
<div class="login-box">
|
||||||
|
<div class="login-logo">
|
||||||
|
<a href="/">
|
||||||
|
<?php if($appLogo): ?>
|
||||||
|
<img src="<?php echo e($appLogo); ?>" alt="Logo" style="max-height: 80px; max-width: 200px;">
|
||||||
|
<?php else: ?>
|
||||||
|
<i class="fas fa-tree text-success"></i> <b><?php echo e(e($appName)); ?></b>
|
||||||
|
<?php endif; ?>
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
<div class="card">
|
||||||
|
<div class="card-body login-card-body">
|
||||||
|
<p class="login-box-msg">Password dimenticata? Inserisci la tua email e ti invieremo il link per reimpostarla.</p>
|
||||||
|
|
||||||
|
<?php if(session('success')): ?>
|
||||||
|
<div class="alert alert-success"><?php echo e(session('success')); ?></div>
|
||||||
|
<?php endif; ?>
|
||||||
|
|
||||||
|
<form action="<?php echo e(route('password.email')); ?>" method="POST">
|
||||||
|
<?php echo csrf_field(); ?>
|
||||||
|
<div class="input-group mb-3">
|
||||||
|
<input type="email" name="email" class="form-control <?php $__errorArgs = ['email'];
|
||||||
|
$__bag = $errors->getBag($__errorArgs[1] ?? 'default');
|
||||||
|
if ($__bag->has($__errorArgs[0])) :
|
||||||
|
if (isset($message)) { $__messageOriginal = $message; }
|
||||||
|
$message = $__bag->first($__errorArgs[0]); ?> is-invalid <?php unset($message);
|
||||||
|
if (isset($__messageOriginal)) { $message = $__messageOriginal; }
|
||||||
|
endif;
|
||||||
|
unset($__errorArgs, $__bag); ?>" placeholder="Email" required value="<?php echo e(old('email')); ?>">
|
||||||
|
<div class="input-group-append">
|
||||||
|
<div class="input-group-text"><span class="fas fa-envelope"></span></div>
|
||||||
|
</div>
|
||||||
|
<?php $__errorArgs = ['email'];
|
||||||
|
$__bag = $errors->getBag($__errorArgs[1] ?? 'default');
|
||||||
|
if ($__bag->has($__errorArgs[0])) :
|
||||||
|
if (isset($message)) { $__messageOriginal = $message; }
|
||||||
|
$message = $__bag->first($__errorArgs[0]); ?>
|
||||||
|
<span class="invalid-feedback"><?php echo e($message); ?></span>
|
||||||
|
<?php unset($message);
|
||||||
|
if (isset($__messageOriginal)) { $message = $__messageOriginal; }
|
||||||
|
endif;
|
||||||
|
unset($__errorArgs, $__bag); ?>
|
||||||
|
</div>
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-12">
|
||||||
|
<button type="submit" class="btn btn-success btn-block">Invia link reset</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
<p class="mb-0 text-center mt-3">
|
||||||
|
<a href="<?php echo e(route('login')); ?>">Torna al login</a>
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<script src="https://cdn.jsdelivr.net/npm/jquery@3.7.1/dist/jquery.min.js"></script>
|
||||||
|
<script src="https://cdn.jsdelivr.net/npm/bootstrap@4.6.2/dist/js/bootstrap.bundle.min.js"></script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
<?php /**PATH /var/www/html/glastree/resources/views/auth/forgot-password.blade.php ENDPATH**/ ?>
|
||||||
@@ -97,7 +97,7 @@ unset($__errorArgs, $__bag); ?>
|
|||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
<p class="mb-0 text-center mt-3">
|
<p class="mb-0 text-center mt-3">
|
||||||
<a href="<?php echo e(route('register')); ?>" class="text-center">Non hai un account? Registrati</a>
|
<a href="<?php echo e(route('password.request')); ?>" class="text-center">Password dimenticata?</a>
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,122 +0,0 @@
|
|||||||
<?php
|
|
||||||
use App\Models\AppSetting;
|
|
||||||
$appName = AppSetting::getAppName() ?? 'Glastree';
|
|
||||||
?>
|
|
||||||
<!DOCTYPE html>
|
|
||||||
<html lang="it">
|
|
||||||
<head>
|
|
||||||
<meta charset="utf-8">
|
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
||||||
<title>Registrati - <?php echo e(e($appName)); ?></title>
|
|
||||||
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/admin-lte@3.2/dist/css/adminlte.min.css">
|
|
||||||
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
|
|
||||||
</head>
|
|
||||||
<body class="hold-transition register-page">
|
|
||||||
<div class="register-box">
|
|
||||||
<div class="login-logo">
|
|
||||||
<a href="/"><i class="fas fa-tree text-success"></i> <b><?php echo e(e($appName)); ?></b></a>
|
|
||||||
</div>
|
|
||||||
<div class="card">
|
|
||||||
<div class="card-body register-card-body">
|
|
||||||
<p class="login-box-msg">Crea un nuovo account</p>
|
|
||||||
<form action="<?php echo e(route('register')); ?>" method="POST">
|
|
||||||
<?php echo csrf_field(); ?>
|
|
||||||
<div class="input-group mb-3">
|
|
||||||
<input type="text" name="name" class="form-control <?php $__errorArgs = ['name'];
|
|
||||||
$__bag = $errors->getBag($__errorArgs[1] ?? 'default');
|
|
||||||
if ($__bag->has($__errorArgs[0])) :
|
|
||||||
if (isset($message)) { $__messageOriginal = $message; }
|
|
||||||
$message = $__bag->first($__errorArgs[0]); ?> is-invalid <?php unset($message);
|
|
||||||
if (isset($__messageOriginal)) { $message = $__messageOriginal; }
|
|
||||||
endif;
|
|
||||||
unset($__errorArgs, $__bag); ?>" placeholder="Nome completo" value="<?php echo e(old('name')); ?>" required>
|
|
||||||
<div class="input-group-append">
|
|
||||||
<div class="input-group-text">
|
|
||||||
<span class="fas fa-user"></span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<?php $__errorArgs = ['name'];
|
|
||||||
$__bag = $errors->getBag($__errorArgs[1] ?? 'default');
|
|
||||||
if ($__bag->has($__errorArgs[0])) :
|
|
||||||
if (isset($message)) { $__messageOriginal = $message; }
|
|
||||||
$message = $__bag->first($__errorArgs[0]); ?>
|
|
||||||
<span class="invalid-feedback"><?php echo e($message); ?></span>
|
|
||||||
<?php unset($message);
|
|
||||||
if (isset($__messageOriginal)) { $message = $__messageOriginal; }
|
|
||||||
endif;
|
|
||||||
unset($__errorArgs, $__bag); ?>
|
|
||||||
</div>
|
|
||||||
<div class="input-group mb-3">
|
|
||||||
<input type="email" name="email" class="form-control <?php $__errorArgs = ['email'];
|
|
||||||
$__bag = $errors->getBag($__errorArgs[1] ?? 'default');
|
|
||||||
if ($__bag->has($__errorArgs[0])) :
|
|
||||||
if (isset($message)) { $__messageOriginal = $message; }
|
|
||||||
$message = $__bag->first($__errorArgs[0]); ?> is-invalid <?php unset($message);
|
|
||||||
if (isset($__messageOriginal)) { $message = $__messageOriginal; }
|
|
||||||
endif;
|
|
||||||
unset($__errorArgs, $__bag); ?>" placeholder="Email" value="<?php echo e(old('email')); ?>" required>
|
|
||||||
<div class="input-group-append">
|
|
||||||
<div class="input-group-text">
|
|
||||||
<span class="fas fa-envelope"></span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<?php $__errorArgs = ['email'];
|
|
||||||
$__bag = $errors->getBag($__errorArgs[1] ?? 'default');
|
|
||||||
if ($__bag->has($__errorArgs[0])) :
|
|
||||||
if (isset($message)) { $__messageOriginal = $message; }
|
|
||||||
$message = $__bag->first($__errorArgs[0]); ?>
|
|
||||||
<span class="invalid-feedback"><?php echo e($message); ?></span>
|
|
||||||
<?php unset($message);
|
|
||||||
if (isset($__messageOriginal)) { $message = $__messageOriginal; }
|
|
||||||
endif;
|
|
||||||
unset($__errorArgs, $__bag); ?>
|
|
||||||
</div>
|
|
||||||
<div class="input-group mb-3">
|
|
||||||
<input type="password" name="password" class="form-control <?php $__errorArgs = ['password'];
|
|
||||||
$__bag = $errors->getBag($__errorArgs[1] ?? 'default');
|
|
||||||
if ($__bag->has($__errorArgs[0])) :
|
|
||||||
if (isset($message)) { $__messageOriginal = $message; }
|
|
||||||
$message = $__bag->first($__errorArgs[0]); ?> is-invalid <?php unset($message);
|
|
||||||
if (isset($__messageOriginal)) { $message = $__messageOriginal; }
|
|
||||||
endif;
|
|
||||||
unset($__errorArgs, $__bag); ?>" placeholder="Password" required>
|
|
||||||
<div class="input-group-append">
|
|
||||||
<div class="input-group-text">
|
|
||||||
<span class="fas fa-lock"></span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<?php $__errorArgs = ['password'];
|
|
||||||
$__bag = $errors->getBag($__errorArgs[1] ?? 'default');
|
|
||||||
if ($__bag->has($__errorArgs[0])) :
|
|
||||||
if (isset($message)) { $__messageOriginal = $message; }
|
|
||||||
$message = $__bag->first($__errorArgs[0]); ?>
|
|
||||||
<span class="invalid-feedback"><?php echo e($message); ?></span>
|
|
||||||
<?php unset($message);
|
|
||||||
if (isset($__messageOriginal)) { $message = $__messageOriginal; }
|
|
||||||
endif;
|
|
||||||
unset($__errorArgs, $__bag); ?>
|
|
||||||
</div>
|
|
||||||
<div class="input-group mb-3">
|
|
||||||
<input type="password" name="password_confirmation" class="form-control" placeholder="Conferma password" required>
|
|
||||||
<div class="input-group-append">
|
|
||||||
<div class="input-group-text">
|
|
||||||
<span class="fas fa-lock"></span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="row">
|
|
||||||
<div class="col-12">
|
|
||||||
<button type="submit" class="btn btn-success btn-block">Registrati</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</form>
|
|
||||||
<p class="mb-0 text-center mt-3">
|
|
||||||
<a href="<?php echo e(route('login')); ?>" class="text-center">Hai già un account? Accedi</a>
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<script src="https://cdn.jsdelivr.net/npm/jquery@3.7.1/dist/jquery.min.js"></script>
|
|
||||||
<script src="https://cdn.jsdelivr.net/npm/bootstrap@4.6.2/dist/js/bootstrap.bundle.min.js"></script>
|
|
||||||
</body>
|
|
||||||
</html><?php /**PATH /var/www/html/glastree/resources/views/auth/register.blade.php ENDPATH**/ ?>
|
|
||||||
@@ -43,7 +43,7 @@ $appName = \App\Models\AppSetting::getAppName() ?? 'Glastree';
|
|||||||
|
|
||||||
</a>
|
</a>
|
||||||
<div class="dropdown-menu dropdown-menu-right">
|
<div class="dropdown-menu dropdown-menu-right">
|
||||||
<a href="#" class="dropdown-item">
|
<a href="<?php echo e(route('profile')); ?>" class="dropdown-item">
|
||||||
<i class="fas fa-user-cog mr-2"></i> Profilo
|
<i class="fas fa-user-cog mr-2"></i> Profilo
|
||||||
</a>
|
</a>
|
||||||
<div class="dropdown-divider"></div>
|
<div class="dropdown-divider"></div>
|
||||||
|
|||||||
Reference in New Issue
Block a user