Implementato dark mode completo e pagina personalizzazione colori

-  Dark mode funzionante su tutta la GUI
-  Spostato riquadro stabile in cima alla sidebar
-  Risolti problemi seeder (spostati in app/Console/Seeders)
-  Creata tabella gestioni con dati di test
-  Aggiunta pagina impostazioni per personalizzazione colori
-  Configurato Tailwind per dark mode
-  CSS custom per garantire leggibilità testi
-  SidebarComposer esteso per dashboard
-  Testi leggibili in modalità scura
This commit is contained in:
Pikappa2 2025-07-05 18:38:23 +02:00
parent 0929942862
commit cf22a51dc7
21 changed files with 1490 additions and 115 deletions

View File

@ -0,0 +1,22 @@
<?php
namespace App\Console\Seeders;
use App\Models\User;
use Illuminate\Database\Seeder;
class DatabaseSeeder extends Seeder
{
/**
* Seed the application's database.
*/
public function run(): void
{
// Chiama solo il seeder che abbiamo creato.
$this->call([
// SuperAdminSeeder::class, // Questo seeder è ora inglobato in TestSetupSeeder
\App\Console\Seeders\TestSetupSeeder::class, // Chiama il seeder principale di setup
\App\Console\Seeders\ImpostazioniSeeder::class,
]);
}
}

View File

@ -0,0 +1,56 @@
<?php
namespace App\Console\Seeders;
use Illuminate\Database\Seeder;
use Illuminate\Support\Facades\DB;
class ImpostazioniSeeder extends Seeder
{
public function run(): void
{
DB::table('impostazioni')->insertOrIgnore([
[
'chiave' => 'sidebar_bg',
'valore' => '#fde047',
'descrizione' => 'Colore di sfondo sidebar (giallo)',
'created_at' => now(),
'updated_at' => now(),
],
[
'chiave' => 'sidebar_text',
'valore' => '#1e293b',
'descrizione' => 'Colore testo sidebar',
'created_at' => now(),
'updated_at' => now(),
],
[
'chiave' => 'sidebar_accent',
'valore' => '#6366f1',
'descrizione' => 'Colore accento sidebar (indigo)',
'created_at' => now(),
'updated_at' => now(),
],
[
'chiave' => 'sidebar_bg_dark',
'valore' => '#23272e',
'descrizione' => 'Colore sidebar dark mode',
'created_at' => now(),
'updated_at' => now(),
],
[
'chiave' => 'sidebar_text_dark',
'valore' => '#f1f5f9',
'descrizione' => 'Colore testo sidebar dark mode',
'created_at' => now(),
'updated_at' => now(),
],
[
'chiave' => 'sidebar_accent_dark',
'valore' => '#fbbf24',
'descrizione' => 'Colore accento sidebar dark mode',
'created_at' => now(),
'updated_at' => now(),
],
]);
}
}

View File

@ -0,0 +1,379 @@
<?php
namespace App\Console\Seeders;
use Illuminate\Database\Seeder;
use App\Models\User;
use App\Models\Amministratore;
use App\Models\Stabile;
use App\Models\UnitaImmobiliare;
use App\Models\Soggetto;
use App\Models\Proprieta;
use App\Models\TabellaMillesimale;
use App\Models\DettaglioTabellaMillesimale;
use App\Models\PianoContiCondominio;
use App\Models\Gestione;
use App\Models\Preventivo;
use App\Models\VocePreventivo;
use Spatie\Permission\Models\Role;
use Spatie\Permission\Models\Permission;
use Illuminate\Support\Facades\Hash;
// Assicurati di avere i modelli e le migrazioni corretti prima di eseguire questo seeder.
// Questo seeder crea un ambiente di test con un utente Super Admin, un Amministratore, un Condominio e alcune Unità Immobiliari con Soggetti associati.
// Assicurati di eseguire questo seeder con il comando `php artisan db:seed --class=TestSetupSeeder` per popolare il database con i dati di test.
// Puoi modificare le email e le password per adattarle alle tue esigenze di test.
// Assicurati di avere i modelli e le migrazioni corretti prima di eseguire questo seeder.
// Questo seeder è utile per testare le funzionalità del tuo gestionale senza dover inserire manualmente i dati ogni volta.
// Puoi anche estendere questo seeder per aggiungere ulteriori dati di test come spese, entrate, verbali, ecc.
// Assicurati di avere le relazioni corrette nei modelli Soggetto, UnitaImmobiliare e SoggettoUnita per gestire le associazioni tra soggetti e unità immobiliari.
// Questo seeder è un ottimo punto di partenza per testare le funzionalità del tuo gestionale e garantire che tutto funzioni correttamente.
// Puoi anche utilizzare questo seeder come base per creare altri seeders specifici per le tue esigenze di test.
class TestSetupSeeder extends Seeder
{
/**
* Run the database seeds.
*/
public function run(): void
{
// Pulisce la cache dei permessi
app()[\Spatie\Permission\PermissionRegistrar::class]->forgetCachedPermissions();
// 1. Crea i ruoli
// Usa Spatie\Permission\Models\Role per assegnare i ruoli
$superAdminRole = \Spatie\Permission\Models\Role::firstOrCreate(['name' => 'super-admin', 'guard_name' => 'web']);
// Ruoli in italiano per la gestione condominiale
$amministratoreRole = \Spatie\Permission\Models\Role::firstOrCreate(['name' => 'amministratore', 'guard_name' => 'web']);
$collaboratoreRole = \Spatie\Permission\Models\Role::firstOrCreate(['name' => 'collaboratore', 'guard_name' => 'web']);
$condominoRole = \Spatie\Permission\Models\Role::firstOrCreate(['name' => 'condomino', 'guard_name' => 'web']);
$fornitoreRole = \Spatie\Permission\Models\Role::firstOrCreate(['name' => 'fornitore', 'guard_name' => 'web']);
$inquilinoRole = \Spatie\Permission\Models\Role::firstOrCreate(['name' => 'inquilino', 'guard_name' => 'web']);
$ospiteRole = \Spatie\Permission\Models\Role::firstOrCreate(['name' => 'ospite', 'guard_name' => 'web']);
$serviziRole = \Spatie\Permission\Models\Role::firstOrCreate(['name' => 'servizi', 'guard_name' => 'web']);
$this->command->info('Ruoli creati/verificati.');
// Ruoli di base per sviluppo (rimosso uso di App\Models\Role e campo label)
// Tutti i ruoli sono ora gestiti solo tramite Spatie\Permission\Models\Role
// 2. Crea l'utente Super Admin
// Rimosso il campo 'role' diretto, verrà assegnato tramite Spatie
$superAdmin = User::firstOrCreate(
['email' => 'superadmin@example.com'],
[
'name' => 'Super Admin',
'password' => Hash::make('password'), // Cambia questa password in produzione!
'email_verified_at' => now(),
]
);
// Il ruolo 'super-admin' verrà assegnato tramite Spatie
$this->command->info('Utente Super Admin creato/aggiornato: ' . $superAdmin->email); // Variabile corretta
// 2. Crea un Utente Amministratore
$adminUser = User::firstOrCreate(
['email' => 'admin@example.com'],
[
'name' => 'Amministratore Test',
'password' => Hash::make('password'), // Cambia questa password in produzione!
'email_verified_at' => now(),
]
);
// Il ruolo 'admin' verrà assegnato tramite Spatie
$this->command->info('Utente Amministratore creato/aggiornato: ' . $adminUser->email);
// 3. Crea un Record Amministratore (collegato all'utente admin)
$amministratore = Amministratore::firstOrCreate(
['user_id' => $adminUser->id],
[
'nome' => 'Mario',
'cognome' => 'Rossi',
'denominazione_studio' => 'Studio Rossi Amministrazioni',
'partita_iva' => '12345678901',
'codice_fiscale_studio' => 'RSSMRA80A01H501K',
'indirizzo_studio' => 'Via Roma 10',
'cap_studio' => '00100',
'citta_studio' => 'Roma',
'provincia_studio' => 'RM',
'telefono_studio' => '061234567',
'email_studio' => 'studio.rossi@example.com',
'pec_studio' => 'studio.rossi@pec.it',
]
);
$this->command->info('Record Amministratore creato/aggiornato: ' . $amministratore->nome . ' ' . $amministratore->cognome);
// 4. Crea un Condominio di Test
$stabile = Stabile::firstOrCreate(
['denominazione' => 'Stabile Test Via Milano 1'],
[
'amministratore_id' => $amministratore->id_amministratore,
'indirizzo' => 'Via Milano 1',
'cap' => '20100',
'citta' => 'Milano',
'provincia' => 'MI',
'codice_fiscale' => 'CNDMLN00001A001A',
'note' => 'Condominio di test per lo sviluppo.',
'stato' => 'attivo',
]
);
$this->command->info('Stabile di Test creato/aggiornato: ' . $stabile->denominazione);
// 5. Crea Unità Immobiliari di Test
$unita1 = UnitaImmobiliare::firstOrCreate(
['stabile_id' => $stabile->id, 'interno' => '1', 'scala' => 'A', 'fabbricato' => 'Principale'],
[
'piano' => '1',
'subalterno' => '1',
'categoria_catastale' => 'A/3',
'superficie' => 80.50,
'vani' => 4.5,
'indirizzo' => null,
'note' => 'Appartamento di test A1',
]
);
$unita2 = UnitaImmobiliare::firstOrCreate(
['stabile_id' => $stabile->id, 'interno' => '2', 'scala' => 'A', 'fabbricato' => 'Principale'],
[
'piano' => '1',
'subalterno' => '2',
'categoria_catastale' => 'A/3',
'superficie' => 70.00,
'vani' => 3.5,
'indirizzo' => null,
'note' => 'Appartamento di test A2',
]
);
$this->command->info('Unità Immobiliari di Test create.');
// 6. Crea Soggetti di Test
$soggettoProprietario1 = Soggetto::firstOrCreate(['email' => 'proprietario1@example.com'], ['nome' => 'Giuseppe', 'cognome' => 'Verdi', 'tipo' => 'proprietario', 'codice_fiscale' => 'VRDGPP80A01H501A']);
$soggettoProprietario2 = Soggetto::firstOrCreate(['email' => 'proprietario2@example.com'], ['nome' => 'Maria', 'cognome' => 'Bianchi', 'tipo' => 'proprietario', 'codice_fiscale' => 'BNCMRA85B02H502B']);
$soggettoInquilino = Soggetto::firstOrCreate(['email' => 'inquilino@example.com'], ['nome' => 'Luca', 'cognome' => 'Neri', 'tipo' => 'inquilino', 'codice_fiscale' => 'NRELCA90C03H503C']);
$this->command->info('Soggetti di Test creati.');
// 7. Collega Soggetti alle Unità (Proprieta)
Proprieta::firstOrCreate([
'soggetto_id' => $soggettoProprietario1->id ?? $soggettoProprietario1->id_soggetto,
'unita_immobiliare_id' => $unita1->id ?? $unita1->id_unita
], [
'tipo_diritto' => 'proprietario',
'percentuale_possesso' => 100.00,
'data_inizio' => '2020-01-01'
]);
Proprieta::firstOrCreate([
'soggetto_id' => $soggettoProprietario1->id ?? $soggettoProprietario1->id_soggetto,
'unita_immobiliare_id' => $unita2->id ?? $unita2->id_unita
], [
'tipo_diritto' => 'nudo_proprietario',
'percentuale_possesso' => 100.00,
'data_inizio' => '2022-03-01'
]);
Proprieta::firstOrCreate([
'soggetto_id' => $soggettoProprietario2->id ?? $soggettoProprietario2->id_soggetto,
'unita_immobiliare_id' => $unita2->id ?? $unita2->id_unita
], [
'tipo_diritto' => 'usufruttuario',
'percentuale_possesso' => 100.00,
'data_inizio' => '2022-03-01'
]);
Proprieta::firstOrCreate([
'soggetto_id' => $soggettoInquilino->id ?? $soggettoInquilino->id_soggetto,
'unita_immobiliare_id' => $unita1->id ?? $unita1->id_unita
], [
'tipo_diritto' => 'inquilino',
'percentuale_possesso' => 100.00,
'data_inizio' => '2023-06-15'
]);
$this->command->info('Relazioni Soggetto-Unità create.');
// 8. Crea una Tabella Millesimale di Test
$tabellaA = TabellaMillesimale::firstOrCreate(
['stabile_id' => $stabile->id, 'nome_tabella_millesimale' => 'Tabella A - Proprietà'],
['descrizione' => 'Ripartizione spese in base ai millesimi di proprietà generale.']
);
// Fix: recupera la chiave primaria corretta se non presente
if (!$tabellaA->id) {
// Prova a ricaricare dal DB se firstOrCreate restituisce un oggetto senza la chiave primaria
$tabellaA = TabellaMillesimale::where('stabile_id', $stabile->id)
->where('nome_tabella_millesimale', 'Tabella A - Proprietà')
->first();
}
if (!$tabellaA || !$tabellaA->id) {
$this->command->error('Errore: la tabella millesimale non è stata creata correttamente!');
return;
}
$this->command->info('Tabella Millesimale di Test creata.');
// 9. Crea Dettagli Millesimali per le unità
DettaglioTabellaMillesimale::firstOrCreate(
['tabella_millesimale_id' => $tabellaA->id, 'unita_immobiliare_id' => $unita1->id ?? $unita1->id_unita],
['millesimi' => 500.0000]
);
DettaglioTabellaMillesimale::firstOrCreate(
['tabella_millesimale_id' => $tabellaA->id, 'unita_immobiliare_id' => $unita2->id ?? $unita2->id_unita],
['millesimi' => 500.0000]
);
$this->command->info('Dettagli Millesimali creati.');
// 10. Crea una Gestione di Test
$gestione2024 = Gestione::firstOrCreate(
['stabile_id' => $stabile->id, 'anno_gestione' => 2024, 'tipo_gestione' => 'Ord.'],
['data_inizio' => '2024-01-01', 'data_fine' => '2024-12-31', 'stato' => 'aperta']
);
$this->command->info('Gestione di Test creata.');
// Aggiungiamo anche la gestione 2025
$gestione2025 = Gestione::firstOrCreate(
['stabile_id' => $stabile->id, 'anno_gestione' => 2025, 'tipo_gestione' => 'Ord.'],
['data_inizio' => '2025-01-01', 'stato' => 'aperta']
);
$this->command->info('Gestione 2025 creata.');
// 11. Crea un Piano dei Conti per lo Stabile (esempio base)
$contoPulizie = PianoContiCondominio::firstOrCreate(
['stabile_id' => $stabile->id, 'codice' => 'SP.PUL'],
['descrizione' => 'Spese di Pulizia Scale', 'tipo_conto' => 'ECONOMICO_COSTO']
);
$contoAssicurazione = PianoContiCondominio::firstOrCreate(
['stabile_id' => $stabile->id, 'codice' => 'SP.ASS'],
['descrizione' => 'Assicurazione Fabbricato', 'tipo_conto' => 'ECONOMICO_COSTO']
);
$this->command->info('Piano dei Conti di Test creato.');
/*// 12. Crea un Preventivo di Test
$preventivo2024 = Preventivo::firstOrCreate(
['id_gestione' => $gestione2024->id_gestione],
['descrizione' => 'Preventivo Ordinario 2024', 'stato' => 'APPROVATO']
);
$this->command->info('Preventivo di Test creato.');
// 13. Crea Voci di Preventivo
VocePreventivo::firstOrCreate(['id_preventivo' => $preventivo2024->id_preventivo, 'id_piano_conto_condominio_pc' => $contoPulizie->id_conto_condominio_pc], ['importo_previsto' => 1200.00, 'id_tabella_millesimale_ripartizione' => $tabellaA->id_tabella_millesimale]);
VocePreventivo::firstOrCreate(['id_preventivo' => $preventivo2024->id_preventivo, 'id_piano_conto_condominio_pc' => $contoAssicurazione->id_conto_condominio_pc], ['importo_previsto' => 800.00, 'id_tabella_millesimale_ripartizione' => $tabellaA->id_tabella_millesimale]);
$this->command->info('Voci di Preventivo create.'); */
// Creazione Permessi (Esempio)
$gestioneCondominiPermission = Permission::firstOrCreate(['name' => 'gestione-condomini']);
$visualizzaReportPermission = Permission::firstOrCreate(['name' => 'visualizza-report']);
Permission::firstOrCreate(['name' => 'view-stabili']);
Permission::firstOrCreate(['name' => 'manage-stabili']); // Permesso generico per le azioni CRUD
// Permessi per la gestione utenti (Super Admin)
Permission::firstOrCreate(['name' => 'create-users']);
Permission::firstOrCreate(['name' => 'view-users']);
Permission::firstOrCreate(['name' => 'manage-users']); // Include create, edit, delete, update role
Permission::firstOrCreate(['name' => 'impersonate-users']);
// Permessi per la gestione amministratori (Super Admin)
Permission::firstOrCreate(['name' => 'view-amministratori']);
Permission::firstOrCreate(['name' => 'manage-amministratori']); // Include create, edit, delete
// Permessi per la gestione categorie ticket (Super Admin)
Permission::firstOrCreate(['name' => 'view-categorie-ticket']);
Permission::firstOrCreate(['name' => 'manage-categorie-ticket']); // Include create, edit, delete
// Permessi per la gestione soggetti (Admin)
Permission::firstOrCreate(['name' => 'view-soggetti']);
Permission::firstOrCreate(['name' => 'manage-soggetti']); // Include create, edit, delete
// Permessi per la gestione fornitori (Admin)
Permission::firstOrCreate(['name' => 'view-fornitori']);
Permission::firstOrCreate(['name' => 'manage-fornitori']);
// Permessi per la gestione ticket (Admin)
Permission::firstOrCreate(['name' => 'view-tickets']);
Permission::firstOrCreate(['name' => 'manage-tickets']);
// Permessi per la gestione unità immobiliari (Admin)
Permission::firstOrCreate(['name' => 'view-unita-immobiliari']);
Permission::firstOrCreate(['name' => 'manage-unita-immobiliari']);
// Permessi per le impostazioni e API Tokens (Admin)
Permission::firstOrCreate(['name' => 'view-impostazioni']);
Permission::firstOrCreate(['name' => 'manage-api-tokens']);
Permission::firstOrCreate(['name' => 'view-rubrica']);
// Aggiungi qui altri permessi specifici per il tuo progetto
// Assegnazione Permessi ai Ruoli (Esempio)
$amministratoreRole = \Spatie\Permission\Models\Role::where('name', 'amministratore')->first();
$adminRole = \Spatie\Permission\Models\Role::where('name', 'admin')->first();
$superAdminRole = \Spatie\Permission\Models\Role::where('name', 'super-admin')->first();
$amministratoreRole = \Spatie\Permission\Models\Role::where('name', 'amministratore')->first();
if ($amministratoreRole) {
$amministratoreRole->givePermissionTo([
'visualizza-report',
'view-stabili', 'manage-stabili',
'view-soggetti', 'manage-soggetti',
'view-fornitori', 'manage-fornitori',
'view-tickets', 'manage-tickets',
'view-unita-immobiliari', 'manage-unita-immobiliari',
'view-impostazioni', 'manage-api-tokens', 'view-rubrica',
]);
} else {
$this->command->warn("Ruolo 'amministratore' non trovato: permessi non assegnati.");
}
// Assegna i permessi al ruolo 'admin'
$adminRole->givePermissionTo([
'view-soggetti', 'manage-soggetti',
'view-fornitori', 'manage-fornitori',
'view-tickets', 'manage-tickets',
'view-unita-immobiliari', 'manage-unita-immobiliari',
'view-impostazioni', 'manage-api-tokens', 'view-rubrica',
]);
// Assegna il ruolo 'amministratore' all'utente di test per permettergli di gestire gli stabili
if ($amministratoreRole) {
$adminUser->assignRole('amministratore');
} else {
$this->command->warn("Ruolo 'amministratore' non trovato: non assegnato all'utente di test.");
}
// Assegna tutti i permessi al Super Admin
$superAdminRole->givePermissionTo(Permission::all());
$superAdmin->assignRole('super-admin');
$this->command->info('Setup di test completato con successo!');
}
}
// Questo seeder crea un ambiente di test con un utente Super Admin, un Amministratore, un Condominio e alcune Unità Immobiliari con Soggetti associati.
// Assicurati di eseguire questo seeder con il comando `php artisan db:seed --class=TestSetupSeeder` per popolare il database con i dati di test.
// Puoi modificare le email e le password per adattarle alle tue esigenze di test.
// Assicurati di avere i modelli e le migrazioni corretti prima di eseguire questo seeder.
// Questo seeder è utile per testare le funzionalità del tuo gestionale senza dover inserire manualmente i dati ogni volta.
// Puoi anche estendere questo seeder per aggiungere ulteriori dati di test come spese, entrate, verbali, ecc.
// Assicurati di avere le relazioni corrette nei modelli Soggetto, UnitaImmobiliare e SoggettoUnita per gestire le associazioni tra soggetti e unità immobiliari.
// Questo seeder è un ottimo punto di partenza per testare le funzionalità del tuo gestionale e garantire che tutto funzioni correttamente.
// Puoi anche utilizzare questo seeder come base per creare altri seeders specifici per le tue esigenze di test.
// Assicurati di eseguire il seeder in un ambiente di sviluppo o test, non in produzione, per evitare conflitti con i dati reali.
// Ricorda di aggiornare le password e le email in produzione per garantire la sicurezza del tuo gestionale.
// Questo seeder è progettato per essere eseguito una sola volta per impostare un ambiente di test iniziale.
// Puoi eseguire nuovamente il seeder per ripristinare lo stato di test, ma fai attenzione a non duplicare i dati esistenti.
// Se hai bisogno di modificare i dati di test, puoi farlo direttamente nel seeder o creare nuovi seeders per aggiungere ulteriori dati.
// Assicurati di avere le dipendenze corrette nel tuo progetto Laravel per eseguire questo seeder senza errori.
// Puoi anche utilizzare questo seeder come base per creare altri seeders specifici per le tue esigenze di test.
// Questo seeder è un ottimo punto di partenza per testare le funzionalità del tuo gestionale e garantire che tutto funzioni correttamente.
// Puoi anche utilizzare questo seeder come base per creare altri seeders specifici per le tue esigenze di test.
// Assicurati di eseguire il seeder in un ambiente di sviluppo o test, non in produzione, per evitare conflitti con i dati reali.
// Ricorda di aggiornare le password e le email in produzione per garantire la sicurezza del tuo gestionale.
// Questo seeder è progettato per essere eseguito una sola volta per impostare un ambiente di test iniziale.
// Puoi eseguire nuovamente il seeder per ripristinare lo stato di test, ma fai attenzione a non duplicare i dati esistenti.
// Se hai bisogno di modificare i dati di test, puoi farlo direttamente nel seeder o creare nuovi seeders per aggiungere ulteriori dati.
// Assicurati di avere le dipendenze corrette nel tuo progetto Laravel per eseguire questo seeder senza errori.
// Puoi anche utilizzare questo seeder come base per creare altri seeders specifici per le tue esigenze di test.
// Questo seeder è un ottimo punto di partenza per testare le funzionalità del tuo gestionale e garantire che tutto funzioni correttamente.
// Puoi anche utilizzare questo seeder come base per creare altri seeders specifici per le tue esigenze di test.

View File

@ -0,0 +1,11 @@
<?php
namespace App\Http\Controllers\SuperAdmin;
use App\Http\Controllers\Controller;
use Illuminate\Http\Request;
class DashboardController extends Controller
{
//
}

View File

@ -0,0 +1,31 @@
<?php
namespace App\Http\Controllers\SuperAdmin;
use App\Http\Controllers\Controller;
use Illuminate\Http\Request;
class ImpostazioniController extends Controller
{
public function index()
{
return view('superadmin.impostazioni.index');
}
public function store(Request $request)
{
// Logica per salvare le impostazioni di colore
$validated = $request->validate([
'bg_color' => 'string|max:7',
'text_color' => 'string|max:7',
'accent_color' => 'string|max:7',
'sidebar_bg_color' => 'string|max:7',
'sidebar_text_color' => 'string|max:7',
'sidebar_accent_color' => 'string|max:7',
]);
// Salva nelle impostazioni di sistema (da implementare)
// Per ora restituiamo una risposta di successo
return response()->json(['success' => true, 'message' => 'Impostazioni salvate con successo!']);
}
}

View File

@ -0,0 +1,39 @@
<?php
namespace App\Http\View\Composers;
use Illuminate\View\View;
use Illuminate\Support\Facades\Auth;
use App\Models\Stabile;
use App\Models\Gestione;
use Spatie\Permission\Traits\HasRoles;
class SidebarComposer
{
public function compose(View $view)
{
$user = Auth::user();
if ($user && !in_array(HasRoles::class, class_uses($user))) {
$user->setRelation('roles', collect()); // fallback vuoto
}
$stabili = collect();
if ($user) {
if (method_exists($user, 'hasRole') && $user->hasRole('super-admin')) {
$stabili = Stabile::orderBy('denominazione')->get();
} elseif ($user->amministratore) {
$stabili = Stabile::where('amministratore_id', $user->amministratore->id_amministratore)->orderBy('denominazione')->get();
}
}
$stabileAttivo = session('stabile_corrente') ?? ($stabili->first() ? $stabili->first()->denominazione : null);
$stabileObj = $stabili->firstWhere('denominazione', $stabileAttivo);
$gestioni = $stabileObj ? Gestione::where('stabile_id', $stabileObj->id)->orderByDesc('anno_gestione')->get() : collect();
$annoAttivo = session('anno_corrente') ?? ($gestioni->first() ? $gestioni->first()->anno_gestione : date('Y'));
$gestioneAttiva = session('gestione_corrente') ?? ($gestioni->first() ? $gestioni->first()->tipo_gestione : 'Ord.');
$view->with([
'stabili' => $stabili,
'stabileAttivo' => $stabileAttivo,
'anni' => $gestioni->pluck('anno_gestione')->unique(),
'annoAttivo' => $annoAttivo,
'gestione' => $gestioneAttiva,
]);
}
}

View File

@ -3,6 +3,7 @@
namespace App\Providers;
use Illuminate\Support\ServiceProvider;
use Illuminate\Support\Facades\View;
class AppServiceProvider extends ServiceProvider
{
@ -19,7 +20,7 @@ class AppServiceProvider extends ServiceProvider
*/
public function boot(): void
{
// Al momento non usiamo più la logica di Fortify qui,
// ma lasciamo il file pulito per future necessità.
// View Composer per la sidebar
View::composer(['components.menu.sidebar', 'superadmin.dashboard', 'admin.dashboard'], \App\Http\View\Composers\SidebarComposer::class);
}
}

View File

@ -0,0 +1,32 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
public function up(): void
{
Schema::create('gestioni', function (Blueprint $table) {
$table->id('id_gestione');
$table->unsignedBigInteger('stabile_id');
$table->year('anno_gestione');
$table->string('tipo_gestione', 20)->default('Ord.'); // Ord., Risc., Straord.
$table->date('data_inizio')->nullable();
$table->date('data_fine')->nullable();
$table->enum('stato', ['aperta', 'in_corso', 'chiusa'])->default('aperta');
$table->text('descrizione')->nullable();
$table->timestamps();
$table->softDeletes();
$table->foreign('stabile_id')->references('id')->on('stabili')->onDelete('cascade');
$table->index(['stabile_id', 'anno_gestione']);
});
}
public function down(): void
{
Schema::dropIfExists('gestioni');
}
};

View File

@ -15,7 +15,8 @@ class DatabaseSeeder extends Seeder
// Chiama solo il seeder che abbiamo creato.
$this->call([
// SuperAdminSeeder::class, // Questo seeder è ora inglobato in TestSetupSeeder
TestSetupSeeder::class, // Chiama il seeder principale di setup
\App\Console\Seeders\TestSetupSeeder::class, // Chiama il seeder principale di setup
ImpostazioniSeeder::class,
]);
}
}

View File

@ -42,12 +42,22 @@ class TestSetupSeeder extends Seeder
app()[\Spatie\Permission\PermissionRegistrar::class]->forgetCachedPermissions();
// 1. Crea i ruoli
$superAdminRole = Role::firstOrCreate(['name' => 'super-admin']);
$adminRole = Role::firstOrCreate(['name' => 'admin']);
$amministratoreRole = Role::firstOrCreate(['name' => 'amministratore']);
$condominoRole = Role::firstOrCreate(['name' => 'condomino']); // Aggiunto il ruolo condomino
// Usa Spatie\Permission\Models\Role per assegnare i ruoli
$superAdminRole = \Spatie\Permission\Models\Role::firstOrCreate(['name' => 'super-admin', 'guard_name' => 'web']);
// Ruoli in italiano per la gestione condominiale
$amministratoreRole = \Spatie\Permission\Models\Role::firstOrCreate(['name' => 'amministratore', 'guard_name' => 'web']);
$collaboratoreRole = \Spatie\Permission\Models\Role::firstOrCreate(['name' => 'collaboratore', 'guard_name' => 'web']);
$condominoRole = \Spatie\Permission\Models\Role::firstOrCreate(['name' => 'condomino', 'guard_name' => 'web']);
$fornitoreRole = \Spatie\Permission\Models\Role::firstOrCreate(['name' => 'fornitore', 'guard_name' => 'web']);
$inquilinoRole = \Spatie\Permission\Models\Role::firstOrCreate(['name' => 'inquilino', 'guard_name' => 'web']);
$ospiteRole = \Spatie\Permission\Models\Role::firstOrCreate(['name' => 'ospite', 'guard_name' => 'web']);
$serviziRole = \Spatie\Permission\Models\Role::firstOrCreate(['name' => 'servizi', 'guard_name' => 'web']);
$this->command->info('Ruoli creati/verificati.');
// Ruoli di base per sviluppo (rimosso uso di App\Models\Role e campo label)
// Tutti i ruoli sono ora gestiti solo tramite Spatie\Permission\Models\Role
// 2. Crea l'utente Super Admin
// Rimosso il campo 'role' diretto, verrà assegnato tramite Spatie
@ -288,6 +298,12 @@ class TestSetupSeeder extends Seeder
// Assegnazione Permessi ai Ruoli (Esempio)
$amministratoreRole = \Spatie\Permission\Models\Role::where('name', 'amministratore')->first();
$adminRole = \Spatie\Permission\Models\Role::where('name', 'admin')->first();
$superAdminRole = \Spatie\Permission\Models\Role::where('name', 'super-admin')->first();
$amministratoreRole = \Spatie\Permission\Models\Role::where('name', 'amministratore')->first();
if ($amministratoreRole) {
$amministratoreRole->givePermissionTo([
'visualizza-report',
'view-stabili', 'manage-stabili',
@ -297,6 +313,9 @@ class TestSetupSeeder extends Seeder
'view-unita-immobiliari', 'manage-unita-immobiliari',
'view-impostazioni', 'manage-api-tokens', 'view-rubrica',
]);
} else {
$this->command->warn("Ruolo 'amministratore' non trovato: permessi non assegnati.");
}
// Assegna i permessi al ruolo 'admin'
@ -310,7 +329,12 @@ class TestSetupSeeder extends Seeder
// Assegna il ruolo 'amministratore' all'utente di test per permettergli di gestire gli stabili
if ($amministratoreRole) {
$adminUser->assignRole('amministratore');
} else {
$this->command->warn("Ruolo 'amministratore' non trovato: non assegnato all'utente di test.");
}
// Assegna tutti i permessi al Super Admin
$superAdminRole->givePermissionTo(Permission::all());

66
public/css/dark-mode.css Normal file
View File

@ -0,0 +1,66 @@
/* Dark mode custom styles */
.dark {
color-scheme: dark;
}
.dark body {
background-color: #111827;
color: #f9fafb;
}
.dark .bg-white {
background-color: #1f2937 !important;
}
.dark .text-gray-900 {
color: #f9fafb !important;
}
.dark .text-gray-800 {
color: #f3f4f6 !important;
}
.dark .text-gray-700 {
color: #d1d5db !important;
}
.dark .text-gray-600 {
color: #9ca3af !important;
}
.dark .text-gray-500 {
color: #6b7280 !important;
}
.dark .border-gray-200 {
border-color: #374151 !important;
}
.dark .bg-yellow-100 {
background-color: #374151 !important;
}
.dark .bg-yellow-200 {
background-color: #1f2937 !important;
}
.dark .border-yellow-300 {
border-color: #4b5563 !important;
}
/* Assicurati che la tabella sia leggibile */
.dark table {
background-color: #1f2937;
color: #f9fafb;
}
.dark th {
background-color: #374151;
color: #f9fafb;
}
.dark td {
background-color: #1f2937;
color: #f9fafb;
border-color: #4b5563;
}

View File

@ -0,0 +1,33 @@
<nav class="h-full flex flex-col items-center bg-red-800 text-white py-4 px-1 w-16 shadow-xl z-50">
<a href="#" class="mb-6 flex flex-col items-center group" title="Profilo">
<!-- Tabler icon user -->
<svg xmlns="http://www.w3.org/2000/svg" class="icon icon-tabler icon-tabler-user" width="24" height="24" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round"><path stroke="none" d="M0 0h24v24H0z" fill="none"/><circle cx="12" cy="7" r="4" /><path d="M5.5 21h13a2 2 0 0 0 2-2v-1a7 7 0 0 0-14 0v1a2 2 0 0 0 2 2" /></svg>
<span class="sr-only">Profilo</span>
</a>
<a href="#" class="mb-6 flex flex-col items-center group" title="Ricerca">
<!-- Tabler icon search -->
<svg xmlns="http://www.w3.org/2000/svg" class="icon icon-tabler icon-tabler-search" width="24" height="24" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round"><path stroke="none" d="M0 0h24v24H0z" fill="none"/><circle cx="10" cy="10" r="7" /><line x1="21" y1="21" x2="15" y2="15" /></svg>
<span class="sr-only">Ricerca</span>
</a>
<a href="#" class="mb-6 flex flex-col items-center group" title="Nuovo Documento">
<!-- Tabler icon plus (rosso) -->
<svg xmlns="http://www.w3.org/2000/svg" class="icon icon-tabler icon-tabler-plus" width="24" height="24" viewBox="0 0 24 24" stroke-width="2" stroke="#dc2626" fill="none" stroke-linecap="round" stroke-linejoin="round"><path stroke="none" d="M0 0h24v24H0z" fill="none"/><line x1="12" y1="5" x2="12" y2="19" /><line x1="5" y1="12" x2="19" y2="12" /></svg>
<span class="sr-only">Nuovo Documento</span>
</a>
<a href="#" class="mb-6 flex flex-col items-center group" title="Novità">
<!-- Tabler icon bell -->
<svg xmlns="http://www.w3.org/2000/svg" class="icon icon-tabler icon-tabler-bell" width="24" height="24" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round"><path stroke="none" d="M0 0h24v24H0z" fill="none"/><path d="M5 17h14m-7 0v-6a3 3 0 0 1 6 0v6m-6 0a3 3 0 0 1-6 0v-6a3 3 0 0 1 6 0v6z" /></svg>
<span class="sr-only">Novità</span>
</a>
<div class="flex-1"></div>
<a href="#" class="mb-6 flex flex-col items-center group" title="Impostazioni">
<!-- Tabler icon settings -->
<svg xmlns="http://www.w3.org/2000/svg" class="icon icon-tabler icon-tabler-settings" width="24" height="24" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round"><path stroke="none" d="M0 0h24v24H0z" fill="none"/><circle cx="12" cy="12" r="3" /><path d="M19.4 15a1.65 1.65 0 0 0 .33 1.82l.06.06a2 2 0 0 1-2.83 2.83l-.06-.06a1.65 1.65 0 0 0-1.82-.33 1.65 1.65 0 0 0-1 1.51V21a2 2 0 0 1-4 0v-.09a1.65 1.65 0 0 0-1-1.51 1.65 1.65 0 0 0-1.82.33l-.06.06a2 2 0 0 1-2.83-2.83l.06-.06a1.65 1.65 0 0 0 .33-1.82 1.65 1.65 0 0 0-1.51-1H3a2 2 0 0 1 0-4h.09a1.65 1.65 0 0 0 1.51-1 1.65 1.65 0 0 0-.33-1.82l-.06-.06a2 2 0 0 1 2.83-2.83l.06.06a1.65 1.65 0 0 0 1.82.33h.09a1.65 1.65 0 0 0 1-1.51V3a2 2 0 0 1 4 0v.09a1.65 1.65 0 0 0 1 1.51h.09a1.65 1.65 0 0 0 1.82-.33l.06-.06a2 2 0 0 1 2.83 2.83l-.06.06a1.65 1.65 0 0 0-.33 1.82v.09a1.65 1.65 0 0 0 1.51 1H21a2 2 0 0 1 0 4h-.09a1.65 1.65 0 0 0-1.51 1z" /></svg>
<span class="sr-only">Impostazioni</span>
</a>
<a href="#" class="flex flex-col items-center group" title="Help">
<!-- Tabler icon help -->
<svg xmlns="http://www.w3.org/2000/svg" class="icon icon-tabler icon-tabler-help" width="24" height="24" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round"><path stroke="none" d="M0 0h24v24H0z" fill="none"/><circle cx="12" cy="12" r="9" /><line x1="12" y1="17" x2="12" y2="17.01" /><path d="M12 13a2 2 0 1 0-2-2" /></svg>
<span class="sr-only">Help</span>
</a>
</nav>

View File

@ -0,0 +1,175 @@
<nav class="h-full flex flex-col bg-yellow-300 border-r-4 border-indigo-500 py-6 px-2 w-full shadow-xl z-50">
@php
$userRoles = auth()->check() ? auth()->user()->getRoleNames()->toArray() : [];
$panelPrefix = '';
if (in_array('super-admin', $userRoles)) {
$panelPrefix = 'superadmin.';
} elseif (in_array('admin', $userRoles) || in_array('amministratore', $userRoles)) {
$panelPrefix = 'admin.';
}
$mainMenu = [
[
'icon' => 'fa-solid fa-home',
'label' => __('menu.dashboard'),
'route' => $panelPrefix . 'dashboard',
'roles' => ['admin', 'super-admin', 'amministratore', 'collaboratore', 'condomino'],
],
[
'icon' => 'fa-solid fa-building',
'label' => __('menu.stabili'),
'route' => $panelPrefix . 'stabili.index',
'roles' => ['admin', 'super-admin', 'amministratore', 'collaboratore'],
],
[
'icon' => 'fa-solid fa-users',
'label' => __('menu.soggetti'),
'route' => $panelPrefix . 'soggetti.index',
'roles' => ['admin', 'super-admin', 'amministratore', 'collaboratore'],
],
[
'icon' => 'fa-solid fa-file-invoice-dollar',
'label' => __('menu.contabilita'),
'route' => $panelPrefix . 'contabilita.index',
'roles' => ['admin', 'super-admin', 'amministratore'],
],
[
'icon' => 'fa-solid fa-cogs',
'label' => __('menu.impostazioni'),
'route' => $panelPrefix . 'impostazioni',
'roles' => ['admin', 'super-admin', 'amministratore'],
],
];
@endphp
<div class="px-2 pt-3 pb-2" style="background: var(--sidebar-bg); color: var(--sidebar-text); border-bottom: 2px solid var(--sidebar-accent); position:sticky; top:0; z-index:20;">
<div class="flex flex-col items-center">
<span class="text-xs text-gray-700 mb-1">{{ $annoAttivo }}/{{ $gestione }}</span>
<div class="flex items-center gap-2 bg-yellow-200 dark:bg-gray-800 rounded px-2 py-1 shadow" style="min-width: 160px;">
<div class="flex flex-col justify-center items-center mr-1">
<button id="prev-stabile" class="w-6 h-6 flex items-center justify-center rounded bg-indigo-500 text-white hover:bg-indigo-700 text-xs mb-1" title="Stabile precedente">
<!-- Tabler icon chevron-up -->
<svg xmlns="http://www.w3.org/2000/svg" class="icon icon-tabler icon-tabler-chevron-up" width="18" height="18" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round"><path stroke="none" d="M0 0h24v24H0z" fill="none"/><polyline points="6 15 12 9 18 15" /></svg>
</button>
<button id="next-stabile" class="w-6 h-6 flex items-center justify-center rounded bg-indigo-500 text-white hover:bg-indigo-700 text-xs" title="Stabile successivo">
<!-- Tabler icon chevron-down -->
<svg xmlns="http://www.w3.org/2000/svg" class="icon icon-tabler icon-tabler-chevron-down" width="18" height="18" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round"><path stroke="none" d="M0 0h24v24H0z" fill="none"/><polyline points="6 9 12 15 18 9" /></svg>
</button>
</div>
<button id="current-stabile" class="flex-1 text-base font-semibold text-gray-900 dark:text-gray-100 bg-transparent hover:bg-yellow-300 dark:hover:bg-gray-700 focus:outline-none focus:ring-2 focus:ring-indigo-500 transition text-center" title="Cambia stabile">
{{ $stabileAttivo }}
</button>
</div>
<button id="toggle-darkmode" class="mt-2 px-2 py-1 rounded bg-gray-800 text-yellow-300 hover:bg-gray-900 text-xs flex items-center gap-1" title="Attiva/disattiva modalità scura">
<!-- Tabler icon moon -->
<svg xmlns="http://www.w3.org/2000/svg" class="icon icon-tabler icon-tabler-moon" width="16" height="16" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round"><path stroke="none" d="M0 0h24v24H0z" fill="none"/><path d="M12 3c.132 0 .263 .003 .393 .008a9 9 0 1 0 9.599 9.599a7 7 0 0 1 -9.992 -9.607z" /></svg>
<span>Dark</span>
</button>
</div>
</div>
<div class="flex-1 flex flex-col gap-2">
@foreach($mainMenu as $item)
@if(array_intersect($item['roles'], $userRoles))
@if(Route::has($item['route']))
<a href="{{ route($item['route']) }}" class="flex items-center gap-3 px-4 py-2 rounded-lg text-gray-700 hover:bg-indigo-100 hover:text-indigo-700 transition group">
<i class="{{ $item['icon'] }} text-lg group-hover:text-indigo-700"></i>
<span class="font-medium">{{ $item['label'] }}</span>
</a>
@endif
@endif
@endforeach
</div>
<div class="mt-auto pt-4">
<button id="submenu-toggle" class="w-full flex items-center justify-center text-gray-400 hover:text-indigo-600 transition" title="Mostra/Nascondi menu">
<i class="fa-solid fa-bars text-xl"></i>
</button>
</div>
@php
// Colori personalizzabili e dark mode
$sidebarBg = impostazione('sidebar_bg', '#fde047');
$sidebarText = impostazione('sidebar_text', '#1e293b');
$sidebarAccent = impostazione('sidebar_accent', '#6366f1');
$sidebarBgDark = impostazione('sidebar_bg_dark', '#23272e');
$sidebarTextDark = impostazione('sidebar_text_dark', '#f1f5f9');
$sidebarAccentDark = impostazione('sidebar_accent_dark', '#fbbf24');
// Recupera stabile attivo (ultimo usato o primo della lista demo)
$stabileAttivo = session('stabile_corrente') ?? $stabili->first()->denominazione;
$annoAttivo = session('anno_corrente') ?? date('Y');
$gestione = session('gestione_corrente') ?? 'Ord.';
@endphp
<style>
:root {
--sidebar-bg: {{ $sidebarBg }};
--sidebar-text: {{ $sidebarText }};
--sidebar-accent: {{ $sidebarAccent }};
}
.dark {
--sidebar-bg: {{ $sidebarBgDark }};
--sidebar-text: {{ $sidebarTextDark }};
--sidebar-accent: {{ $sidebarAccentDark }};
}
</style>
<!-- Modale ricerca stabile -->
<div id="modal-stabile" class="fixed inset-0 bg-black bg-opacity-40 flex items-center justify-center z-50 hidden">
<div class="bg-white rounded shadow-lg p-6 w-full max-w-md">
<h3 class="text-lg font-bold mb-2">Cerca stabile</h3>
<input type="text" id="input-stabile" class="w-full border rounded px-3 py-2 mb-2" placeholder="Digita il nome dello stabile...">
<ul id="result-stabile" class="max-h-40 overflow-y-auto"></ul>
<div class="flex justify-end mt-2">
<button id="close-modal-stabile" class="px-3 py-1 rounded bg-gray-300 hover:bg-gray-400">Chiudi</button>
</div>
</div>
</div>
<script>
const stabili = {!! json_encode($stabili->pluck('denominazione')->values()->toArray()) !!};
let currentIndex = stabili.indexOf({!! json_encode($stabileAttivo) !!});
if(currentIndex === -1) currentIndex = 0;
// Dark mode toggle
const darkBtn = document.getElementById('toggle-darkmode');
darkBtn.addEventListener('click', function() {
document.documentElement.classList.toggle('dark');
});
function updateStabile() {
document.getElementById('current-stabile').textContent = stabili[currentIndex] || 'Nessuno selezionato';
// TODO: chiamata AJAX o redirect per aggiornare sessione stabile_corrente
}
document.getElementById('prev-stabile').onclick = function() {
currentIndex = (currentIndex - 1 + stabili.length) % stabili.length;
updateStabile();
};
document.getElementById('next-stabile').onclick = function() {
currentIndex = (currentIndex + 1) % stabili.length;
updateStabile();
};
document.getElementById('current-stabile').onclick = function() {
document.getElementById('modal-stabile').classList.remove('hidden');
document.getElementById('input-stabile').value = '';
document.getElementById('result-stabile').innerHTML = '';
};
document.getElementById('close-modal-stabile').onclick = function() {
document.getElementById('modal-stabile').classList.add('hidden');
};
document.getElementById('input-stabile').oninput = function(e) {
const val = e.target.value.toLowerCase();
const results = stabili.filter(s => s.toLowerCase().includes(val));
document.getElementById('result-stabile').innerHTML = results.map(s => `<li class='py-1 px-2 hover:bg-yellow-200 cursor-pointer' onclick='selectStabile("${s}")'>${s}</li>`).join('');
};
window.selectStabile = function(nome) {
document.getElementById('current-stabile').textContent = nome;
document.getElementById('modal-stabile').classList.add('hidden');
// Chiamata AJAX per aggiornare la sessione stabile_corrente
fetch('/session/stabile', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-CSRF-TOKEN': document.querySelector('meta[name="csrf-token"]').getAttribute('content')
},
body: JSON.stringify({ stabile: nome })
}).then(res => {
if(res.ok) window.location.reload();
});
};
updateStabile();
</script>
<footer class="w-full bg-gray-100 border-t border-gray-300 text-xs text-gray-600 text-center py-2 mt-4">
<span>NetGesCon &copy; {{ date('Y') }} - <a href="https://github.com/netgescon" class="text-indigo-600 hover:underline">netgescon.github.io</a> - v0.7.0-dev</span>
</footer>
</nav>

View File

@ -0,0 +1,175 @@
<nav class="h-full flex flex-col bg-yellow-300 border-r-4 border-indigo-500 py-6 px-2 w-full shadow-xl z-50">
@php
$userRoles = auth()->check() ? auth()->user()->getRoleNames()->toArray() : [];
$panelPrefix = '';
if (in_array('super-admin', $userRoles)) {
$panelPrefix = 'superadmin.';
} elseif (in_array('admin', $userRoles) || in_array('amministratore', $userRoles)) {
$panelPrefix = 'admin.';
}
$mainMenu = [
[
'icon' => 'fa-solid fa-home',
'label' => __('menu.dashboard'),
'route' => $panelPrefix . 'dashboard',
'roles' => ['admin', 'super-admin', 'amministratore', 'collaboratore', 'condomino'],
],
[
'icon' => 'fa-solid fa-building',
'label' => __('menu.stabili'),
'route' => $panelPrefix . 'stabili.index',
'roles' => ['admin', 'super-admin', 'amministratore', 'collaboratore'],
],
[
'icon' => 'fa-solid fa-users',
'label' => __('menu.soggetti'),
'route' => $panelPrefix . 'soggetti.index',
'roles' => ['admin', 'super-admin', 'amministratore', 'collaboratore'],
],
[
'icon' => 'fa-solid fa-file-invoice-dollar',
'label' => __('menu.contabilita'),
'route' => $panelPrefix . 'contabilita.index',
'roles' => ['admin', 'super-admin', 'amministratore'],
],
[
'icon' => 'fa-solid fa-cogs',
'label' => __('menu.impostazioni'),
'route' => $panelPrefix . 'impostazioni',
'roles' => ['admin', 'super-admin', 'amministratore'],
],
];
@endphp
<div class="flex-1 flex flex-col gap-2">
@foreach($mainMenu as $item)
@if(array_intersect($item['roles'], $userRoles))
@if(Route::has($item['route']))
<a href="{{ route($item['route']) }}" class="flex items-center gap-3 px-4 py-2 rounded-lg text-gray-700 hover:bg-indigo-100 hover:text-indigo-700 transition group">
<i class="{{ $item['icon'] }} text-lg group-hover:text-indigo-700"></i>
<span class="font-medium">{{ $item['label'] }}</span>
</a>
@endif
@endif
@endforeach
</div>
<div class="mt-auto pt-4">
<button id="submenu-toggle" class="w-full flex items-center justify-center text-gray-400 hover:text-indigo-600 transition" title="Mostra/Nascondi menu">
<i class="fa-solid fa-bars text-xl"></i>
</button>
</div>
@php
// Colori personalizzabili e dark mode
$sidebarBg = impostazione('sidebar_bg', '#fde047');
$sidebarText = impostazione('sidebar_text', '#1e293b');
$sidebarAccent = impostazione('sidebar_accent', '#6366f1');
$sidebarBgDark = impostazione('sidebar_bg_dark', '#23272e');
$sidebarTextDark = impostazione('sidebar_text_dark', '#f1f5f9');
$sidebarAccentDark = impostazione('sidebar_accent_dark', '#fbbf24');
// Recupera stabile attivo (ultimo usato o primo della lista demo)
$stabileAttivo = session('stabile_corrente') ?? $stabili->first()->denominazione;
$annoAttivo = session('anno_corrente') ?? date('Y');
$gestione = session('gestione_corrente') ?? 'Ord.';
@endphp
<style>
:root {
--sidebar-bg: {{ $sidebarBg }};
--sidebar-text: {{ $sidebarText }};
--sidebar-accent: {{ $sidebarAccent }};
}
.dark-mode {
--sidebar-bg: {{ $sidebarBgDark }};
--sidebar-text: {{ $sidebarTextDark }};
--sidebar-accent: {{ $sidebarAccentDark }};
}
</style>
<div class="px-2 pt-3 pb-2" style="background: var(--sidebar-bg); color: var(--sidebar-text); border-bottom: 2px solid var(--sidebar-accent); position:sticky; top:0; z-index:20;">
<div class="flex flex-col items-center">
<span class="text-xs text-gray-700 mb-1">{{ $annoAttivo }}/{{ $gestione }}</span>
<div class="flex items-center gap-2 bg-yellow-200 dark:bg-gray-800 rounded px-2 py-1 shadow" style="min-width: 160px;">
<div class="flex flex-col justify-center items-center mr-1">
<button id="prev-stabile" class="w-6 h-6 flex items-center justify-center rounded bg-indigo-500 text-white hover:bg-indigo-700 text-xs mb-1" title="Stabile precedente">
<!-- Tabler icon chevron-up -->
<svg xmlns="http://www.w3.org/2000/svg" class="icon icon-tabler icon-tabler-chevron-up" width="18" height="18" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round"><path stroke="none" d="M0 0h24v24H0z" fill="none"/><polyline points="6 15 12 9 18 15" /></svg>
</button>
<button id="next-stabile" class="w-6 h-6 flex items-center justify-center rounded bg-indigo-500 text-white hover:bg-indigo-700 text-xs" title="Stabile successivo">
<!-- Tabler icon chevron-down -->
<svg xmlns="http://www.w3.org/2000/svg" class="icon icon-tabler icon-tabler-chevron-down" width="18" height="18" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round"><path stroke="none" d="M0 0h24v24H0z" fill="none"/><polyline points="6 9 12 15 18 9" /></svg>
</button>
</div>
<button id="current-stabile" class="flex-1 text-base font-semibold text-gray-900 dark:text-gray-100 bg-transparent hover:bg-yellow-300 dark:hover:bg-gray-700 focus:outline-none focus:ring-2 focus:ring-indigo-500 transition text-center" title="Cambia stabile">
{{ $stabileAttivo }}
</button>
</div>
<button id="toggle-darkmode" class="mt-2 px-2 py-1 rounded bg-gray-800 text-yellow-300 hover:bg-gray-900 text-xs flex items-center gap-1" title="Attiva/disattiva modalità scura">
<!-- Tabler icon moon -->
<svg xmlns="http://www.w3.org/2000/svg" class="icon icon-tabler icon-tabler-moon" width="16" height="16" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round"><path stroke="none" d="M0 0h24v24H0z" fill="none"/><path d="M12 3c.132 0 .263 .003 .393 .008a9 9 0 1 0 9.599 9.599a7 7 0 0 1 -9.992 -9.607z" /></svg>
<span>Dark</span>
</button>
</div>
</div>
<!-- Modale ricerca stabile -->
<div id="modal-stabile" class="fixed inset-0 bg-black bg-opacity-40 flex items-center justify-center z-50 hidden">
<div class="bg-white rounded shadow-lg p-6 w-full max-w-md">
<h3 class="text-lg font-bold mb-2">Cerca stabile</h3>
<input type="text" id="input-stabile" class="w-full border rounded px-3 py-2 mb-2" placeholder="Digita il nome dello stabile...">
<ul id="result-stabile" class="max-h-40 overflow-y-auto"></ul>
<div class="flex justify-end mt-2">
<button id="close-modal-stabile" class="px-3 py-1 rounded bg-gray-300 hover:bg-gray-400">Chiudi</button>
</div>
</div>
</div>
<script>
const stabili = {!! json_encode($stabili->pluck('denominazione')->values()->toArray()) !!};
let currentIndex = stabili.indexOf({!! json_encode($stabileAttivo) !!});
if(currentIndex === -1) currentIndex = 0;
// Dark mode toggle
const darkBtn = document.getElementById('toggle-darkmode');
darkBtn.addEventListener('click', function() {
document.body.classList.toggle('dark-mode');
});
function updateStabile() {
document.getElementById('current-stabile').textContent = stabili[currentIndex] || 'Nessuno selezionato';
// TODO: chiamata AJAX o redirect per aggiornare sessione stabile_corrente
}
document.getElementById('prev-stabile').onclick = function() {
currentIndex = (currentIndex - 1 + stabili.length) % stabili.length;
updateStabile();
};
document.getElementById('next-stabile').onclick = function() {
currentIndex = (currentIndex + 1) % stabili.length;
updateStabile();
};
document.getElementById('current-stabile').onclick = function() {
document.getElementById('modal-stabile').classList.remove('hidden');
document.getElementById('input-stabile').value = '';
document.getElementById('result-stabile').innerHTML = '';
};
document.getElementById('close-modal-stabile').onclick = function() {
document.getElementById('modal-stabile').classList.add('hidden');
};
document.getElementById('input-stabile').oninput = function(e) {
const val = e.target.value.toLowerCase();
const results = stabili.filter(s => s.toLowerCase().includes(val));
document.getElementById('result-stabile').innerHTML = results.map(s => `<li class='py-1 px-2 hover:bg-yellow-200 cursor-pointer' onclick='selectStabile("${s}")'>${s}</li>`).join('');
};
window.selectStabile = function(nome) {
document.getElementById('current-stabile').textContent = nome;
document.getElementById('modal-stabile').classList.add('hidden');
// Chiamata AJAX per aggiornare la sessione stabile_corrente
fetch('/session/stabile', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-CSRF-TOKEN': document.querySelector('meta[name="csrf-token"]').getAttribute('content')
},
body: JSON.stringify({ stabile: nome })
}).then(res => {
if(res.ok) window.location.reload();
});
};
updateStabile();
</script>
<footer class="w-full bg-gray-100 border-t border-gray-300 text-xs text-gray-600 text-center py-2 mt-4">
<span>NetGesCon &copy; {{ date('Y') }} - <a href="https://github.com/netgescon" class="text-indigo-600 hover:underline">netgescon.github.io</a> - v0.7.0-dev</span>
</footer>
</nav>

View File

@ -0,0 +1,35 @@
<aside class="submenu">
@php
// Esempio di sottomenu dinamico in base al menu selezionato (da implementare JS/Livewire)
$submenu = [
'dashboard' => [
['label' => __('menu.dashboard_overview'), 'route' => 'dashboard'],
],
'stabili.index' => [
['label' => __('menu.lista_stabili'), 'route' => 'stabili.index'],
['label' => __('menu.nuovo_stabile'), 'route' => 'stabili.create'],
],
'soggetti.index' => [
['label' => __('menu.lista_soggetti'), 'route' => 'soggetti.index'],
['label' => __('menu.nuovo_soggetto'), 'route' => 'soggetti.create'],
],
'contabilita.index' => [
['label' => __('menu.piano_conti'), 'route' => 'contabilita.piano_conti'],
['label' => __('menu.movimenti'), 'route' => 'contabilita.movimenti'],
],
'impostazioni' => [
['label' => __('menu.utenti'), 'route' => 'impostazioni.utenti'],
['label' => __('menu.ruoli'), 'route' => 'impostazioni.ruoli'],
],
];
// Da implementare: $activeMenu = ...
$activeMenu = 'dashboard';
@endphp
<ul class="nav flex-column mt-4">
@foreach($submenu[$activeMenu] ?? [] as $item)
<li class="nav-item">
<a href="{{ route($item['route']) }}" class="nav-link">{{ $item['label'] }}</a>
</li>
@endforeach
</ul>
</aside>

View File

@ -3,17 +3,35 @@
@section('content')
<div class="space-y-6">
<!-- Header -->
<div class="bg-white overflow-hidden shadow-sm sm:rounded-lg">
<div class="p-6 bg-white border-b border-gray-200">
<h2 class="text-3xl font-bold text-gray-800">Dashboard Super Admin</h2>
<p class="text-gray-600 mt-2">Benvenuto nel pannello di amministrazione del sistema</p>
<div class="bg-white dark:bg-gray-800 overflow-hidden shadow-sm sm:rounded-lg">
<div class="p-6 bg-white dark:bg-gray-800 border-b border-gray-200 dark:border-gray-700">
<h2 class="text-3xl font-bold text-gray-800 dark:text-white">Dashboard Super Admin</h2>
<p class="text-gray-600 dark:text-gray-300 mt-2">Benvenuto nel pannello di amministrazione del sistema</p>
</div>
</div>
<!-- Riquadro stabile sopra la dashboard -->
<div class="w-full flex flex-col items-center py-2 bg-yellow-100 dark:bg-gray-700 border-b border-yellow-300 dark:border-gray-600 sticky top-0 z-20">
<span class="text-xs text-gray-700 dark:text-gray-300 mb-1">{{ $annoAttivo ?? date("Y") }}/{{ $gestione ?? "Ord." }}{{ isset($gestione2) ? ' - '.$gestione2 : '' }}</span>
<div class="flex items-center gap-2 bg-yellow-200 dark:bg-gray-800 rounded px-2 py-1 shadow" style="min-width: 180px;">
<div class="flex flex-col justify-center items-center mr-1">
<button id="prev-stabile" class="w-6 h-6 flex items-center justify-center rounded bg-indigo-500 text-white hover:bg-indigo-700 text-xs mb-1" title="Stabile precedente">
<svg xmlns="http://www.w3.org/2000/svg" class="icon icon-tabler icon-tabler-chevron-up" width="18" height="18" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round"><path stroke="none" d="M0 0h24v24H0z" fill="none"/><polyline points="6 15 12 9 18 15" /></svg>
</button>
<button id="next-stabile" class="w-6 h-6 flex items-center justify-center rounded bg-indigo-500 text-white hover:bg-indigo-700 text-xs" title="Stabile successivo">
<svg xmlns="http://www.w3.org/2000/svg" class="icon icon-tabler icon-tabler-chevron-down" width="18" height="18" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round"><path stroke="none" d="M0 0h24v24H0z" fill="none"/><polyline points="6 9 12 15 18 9" /></svg>
</button>
</div>
<button id="current-stabile" class="flex-1 text-base font-semibold text-gray-900 dark:text-gray-100 bg-transparent hover:bg-yellow-300 dark:hover:bg-gray-700 focus:outline-none focus:ring-2 focus:ring-indigo-500 transition text-center" title="Cambia stabile">
{{ $stabileAttivo ?? "Nessuno Stabile" }}
</button>
</div>
</div>
<!-- Statistiche -->
<div class="grid grid-cols-1 md:grid-cols-3 gap-6">
<!-- Totale Utenti -->
<div class="bg-white overflow-hidden shadow-sm sm:rounded-lg">
<div class="bg-white dark:bg-gray-800 overflow-hidden shadow-sm sm:rounded-lg">
<div class="p-6">
<div class="flex items-center">
<div class="flex-shrink-0">
@ -25,8 +43,8 @@
</div>
<div class="ml-5 w-0 flex-1">
<dl>
<dt class="text-sm font-medium text-gray-500 truncate">Totale Utenti</dt>
<dd class="text-lg font-medium text-gray-900">{{ \App\Models\User::count() }}</dd>
<dt class="text-sm font-medium text-gray-500 dark:text-gray-400 truncate">Totale Utenti</dt>
<dd class="text-lg font-medium text-gray-900 dark:text-white">{{ \App\Models\User::count() }}</dd>
</dl>
</div>
</div>
@ -34,7 +52,7 @@
</div>
<!-- Totale Amministratori -->
<div class="bg-white overflow-hidden shadow-sm sm:rounded-lg">
<div class="bg-white dark:bg-gray-800 overflow-hidden shadow-sm sm:rounded-lg">
<div class="p-6">
<div class="flex items-center">
<div class="flex-shrink-0">
@ -46,8 +64,8 @@
</div>
<div class="ml-5 w-0 flex-1">
<dl>
<dt class="text-sm font-medium text-gray-500 truncate">Amministratori</dt>
<dd class="text-lg font-medium text-gray-900">{{ \App\Models\Amministratore::count() }}</dd>
<dt class="text-sm font-medium text-gray-500 dark:text-gray-400 truncate">Amministratori</dt>
<dd class="text-lg font-medium text-gray-900 dark:text-white">{{ \App\Models\Amministratore::count() }}</dd>
</dl>
</div>
</div>
@ -55,7 +73,7 @@
</div>
<!-- Utenti per Ruolo -->
<div class="bg-white overflow-hidden shadow-sm sm:rounded-lg">
<div class="bg-white dark:bg-gray-800 overflow-hidden shadow-sm sm:rounded-lg">
<div class="p-6">
<div class="flex items-center">
<div class="flex-shrink-0">
@ -67,8 +85,8 @@
</div>
<div class="ml-5 w-0 flex-1">
<dl>
<dt class="text-sm font-medium text-gray-500 truncate">Super Admin</dt>
<dd class="text-lg font-medium text-gray-900">{{ \App\Models\User::role('super-admin')->count() }}</dd>
<dt class="text-sm font-medium text-gray-500 dark:text-gray-400 truncate">Super Admin</dt>
<dd class="text-lg font-medium text-gray-900 dark:text-white">{{ \App\Models\User::role('super-admin')->count() }}</dd>
</dl>
</div>
</div>
@ -77,8 +95,8 @@
</div>
<!-- Azioni Rapide -->
<div class="bg-white overflow-hidden shadow-sm sm:rounded-lg">
<div class="p-6 bg-white border-b border-gray-200">
<div class="bg-white dark:bg-gray-800 overflow-hidden shadow-sm sm:rounded-lg">
<div class="p-6 bg-white dark:bg-gray-800 border-b border-gray-200 dark:border-gray-700">
<h3 class="text-lg font-medium text-gray-900 mb-4">Azioni Rapide</h3>
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-4">
<a href="{{ route('superadmin.users.create') }}"
@ -102,32 +120,32 @@
</div>
<!-- Ultimi Utenti Creati -->
<div class="bg-white overflow-hidden shadow-sm sm:rounded-lg">
<div class="p-6 bg-white border-b border-gray-200">
<div class="bg-white dark:bg-gray-800 overflow-hidden shadow-sm sm:rounded-lg">
<div class="p-6 bg-white dark:bg-gray-800 border-b border-gray-200 dark:border-gray-700">
<h3 class="text-lg font-medium text-gray-900 mb-4">Ultimi Utenti Creati</h3>
<div class="overflow-x-auto">
<table class="min-w-full divide-y divide-gray-200">
<thead class="bg-gray-50">
<tr>
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Nome</th>
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Email</th>
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Ruolo</th>
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Data Creazione</th>
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 dark:text-gray-400 uppercase tracking-wider">Nome</th>
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 dark:text-gray-400 uppercase tracking-wider">Email</th>
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 dark:text-gray-400 uppercase tracking-wider">Ruolo</th>
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 dark:text-gray-400 uppercase tracking-wider">Data Creazione</th>
</tr>
</thead>
<tbody class="bg-white divide-y divide-gray-200">
<tbody class="bg-white dark:bg-gray-800 divide-y divide-gray-200">
@foreach(\App\Models\User::latest()->take(5)->get() as $user)
<tr>
<td class="px-6 py-4 whitespace-nowrap text-sm font-medium text-gray-900">{{ $user->name }}</td>
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-900">{{ $user->email }}</td>
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-900">
<td class="px-6 py-4 whitespace-nowrap text-sm font-medium text-gray-900 dark:text-white">{{ $user->name }}</td>
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-900 dark:text-white">{{ $user->email }}</td>
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-900 dark:text-white">
@foreach($user->roles as $role)
<span class="inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium bg-blue-100 text-blue-800 mr-1">
{{ $role->name }}
</span>
@endforeach
</td>
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-900">{{ $user->created_at->format('d/m/Y H:i') }}</td>
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-900 dark:text-white">{{ $user->created_at->format('d/m/Y H:i') }}</td>
</tr>
@endforeach
</tbody>

View File

@ -0,0 +1,157 @@
@extends('superadmin.layouts.app')
@section('content')
<div class="space-y-6">
<!-- Header -->
<div class="bg-white dark:bg-gray-800 overflow-hidden shadow-sm sm:rounded-lg">
<div class="p-6 bg-white dark:bg-gray-800 border-b border-gray-200">
<h2 class="text-3xl font-bold text-gray-800 dark:text-white">Dashboard Super Admin</h2>
<p class="text-gray-600 mt-2">Benvenuto nel pannello di amministrazione del sistema</p>
</div>
</div>
<!-- Riquadro stabile sopra la dashboard -->
<div class="w-full flex flex-col items-center py-2 bg-yellow-100 border-b border-yellow-300 sticky top-0 z-20">
<span class="text-xs text-gray-700 mb-1">{{ $annoAttivo ?? date("Y") }}/{{ $gestione ?? "Ord." }}{{ isset($gestione2) ? ' - '.$gestione2 : '' }}</span>
<div class="flex items-center gap-2 bg-yellow-200 dark:bg-gray-800 rounded px-2 py-1 shadow" style="min-width: 180px;">
<div class="flex flex-col justify-center items-center mr-1">
<button id="prev-stabile" class="w-6 h-6 flex items-center justify-center rounded bg-indigo-500 text-white hover:bg-indigo-700 text-xs mb-1" title="Stabile precedente">
<svg xmlns="http://www.w3.org/2000/svg" class="icon icon-tabler icon-tabler-chevron-up" width="18" height="18" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round"><path stroke="none" d="M0 0h24v24H0z" fill="none"/><polyline points="6 15 12 9 18 15" /></svg>
</button>
<button id="next-stabile" class="w-6 h-6 flex items-center justify-center rounded bg-indigo-500 text-white hover:bg-indigo-700 text-xs" title="Stabile successivo">
<svg xmlns="http://www.w3.org/2000/svg" class="icon icon-tabler icon-tabler-chevron-down" width="18" height="18" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round"><path stroke="none" d="M0 0h24v24H0z" fill="none"/><polyline points="6 9 12 15 18 9" /></svg>
</button>
</div>
<button id="current-stabile" class="flex-1 text-base font-semibold text-gray-900 dark:text-gray-100 bg-transparent hover:bg-yellow-300 dark:hover:bg-gray-700 focus:outline-none focus:ring-2 focus:ring-indigo-500 transition text-center" title="Cambia stabile">
{{ $stabileAttivo ?? "Nessuno Stabile" }}
</button>
</div>
</div>
<!-- Statistiche -->
<div class="grid grid-cols-1 md:grid-cols-3 gap-6">
<!-- Totale Utenti -->
<div class="bg-white dark:bg-gray-800 overflow-hidden shadow-sm sm:rounded-lg">
<div class="p-6">
<div class="flex items-center">
<div class="flex-shrink-0">
<div class="w-8 h-8 bg-blue-500 rounded-full flex items-center justify-center">
<svg class="w-5 h-5 text-white" fill="currentColor" viewBox="0 0 20 20">
<path d="M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z"></path>
</svg>
</div>
</div>
<div class="ml-5 w-0 flex-1">
<dl>
<dt class="text-sm font-medium text-gray-500 truncate">Totale Utenti</dt>
<dd class="text-lg font-medium text-gray-900">{{ \App\Models\User::count() }}</dd>
</dl>
</div>
</div>
</div>
</div>
<!-- Totale Amministratori -->
<div class="bg-white dark:bg-gray-800 overflow-hidden shadow-sm sm:rounded-lg">
<div class="p-6">
<div class="flex items-center">
<div class="flex-shrink-0">
<div class="w-8 h-8 bg-green-500 rounded-full flex items-center justify-center">
<svg class="w-5 h-5 text-white" fill="currentColor" viewBox="0 0 20 20">
<path d="M13 6a3 3 0 11-6 0 3 3 0 016 0zM18 8a2 2 0 11-4 0 2 2 0 014 0zM14 15a4 4 0 00-8 0v3h8v-3z"></path>
</svg>
</div>
</div>
<div class="ml-5 w-0 flex-1">
<dl>
<dt class="text-sm font-medium text-gray-500 truncate">Amministratori</dt>
<dd class="text-lg font-medium text-gray-900">{{ \App\Models\Amministratore::count() }}</dd>
</dl>
</div>
</div>
</div>
</div>
<!-- Utenti per Ruolo -->
<div class="bg-white dark:bg-gray-800 overflow-hidden shadow-sm sm:rounded-lg">
<div class="p-6">
<div class="flex items-center">
<div class="flex-shrink-0">
<div class="w-8 h-8 bg-purple-500 rounded-full flex items-center justify-center">
<svg class="w-5 h-5 text-white" fill="currentColor" viewBox="0 0 20 20">
<path fill-rule="evenodd" d="M3 4a1 1 0 011-1h12a1 1 0 110 2H4a1 1 0 01-1-1zm0 4a1 1 0 011-1h12a1 1 0 110 2H4a1 1 0 01-1-1zm0 4a1 1 0 011-1h12a1 1 0 110 2H4a1 1 0 01-1-1z" clip-rule="evenodd"></path>
</svg>
</div>
</div>
<div class="ml-5 w-0 flex-1">
<dl>
<dt class="text-sm font-medium text-gray-500 truncate">Super Admin</dt>
<dd class="text-lg font-medium text-gray-900">{{ \App\Models\User::role('super-admin')->count() }}</dd>
</dl>
</div>
</div>
</div>
</div>
</div>
<!-- Azioni Rapide -->
<div class="bg-white dark:bg-gray-800 overflow-hidden shadow-sm sm:rounded-lg">
<div class="p-6 bg-white dark:bg-gray-800 border-b border-gray-200">
<h3 class="text-lg font-medium text-gray-900 mb-4">Azioni Rapide</h3>
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-4">
<a href="{{ route('superadmin.users.create') }}"
class="bg-blue-500 hover:bg-blue-700 text-white font-bold py-3 px-4 rounded text-center transition duration-200">
Nuovo Utente
</a>
<a href="{{ route('superadmin.amministratori.create') }}"
class="bg-green-500 hover:bg-green-700 text-white font-bold py-3 px-4 rounded text-center transition duration-200">
Nuovo Amministratore
</a>
<a href="{{ route('superadmin.users.index') }}"
class="bg-indigo-500 hover:bg-indigo-700 text-white font-bold py-3 px-4 rounded text-center transition duration-200">
Gestisci Utenti
</a>
<a href="{{ route('superadmin.amministratori.index') }}"
class="bg-purple-500 hover:bg-purple-700 text-white font-bold py-3 px-4 rounded text-center transition duration-200">
Gestisci Amministratori
</a>
</div>
</div>
</div>
<!-- Ultimi Utenti Creati -->
<div class="bg-white dark:bg-gray-800 overflow-hidden shadow-sm sm:rounded-lg">
<div class="p-6 bg-white dark:bg-gray-800 border-b border-gray-200">
<h3 class="text-lg font-medium text-gray-900 mb-4">Ultimi Utenti Creati</h3>
<div class="overflow-x-auto">
<table class="min-w-full divide-y divide-gray-200">
<thead class="bg-gray-50">
<tr>
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Nome</th>
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Email</th>
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Ruolo</th>
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Data Creazione</th>
</tr>
</thead>
<tbody class="bg-white dark:bg-gray-800 divide-y divide-gray-200">
@foreach(\App\Models\User::latest()->take(5)->get() as $user)
<tr>
<td class="px-6 py-4 whitespace-nowrap text-sm font-medium text-gray-900">{{ $user->name }}</td>
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-900">{{ $user->email }}</td>
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-900">
@foreach($user->roles as $role)
<span class="inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium bg-blue-100 text-blue-800 mr-1">
{{ $role->name }}
</span>
@endforeach
</td>
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-900">{{ $user->created_at->format('d/m/Y H:i') }}</td>
</tr>
@endforeach
</tbody>
</table>
</div>
</div>
</div>
</div>
@endsection

View File

@ -0,0 +1,33 @@
@extends('superadmin.layouts.app')
@section('content')
<div class="bg-white rounded shadow p-6 mt-8">
<h2 class="text-2xl font-bold mb-4 text-indigo-700">Diagnostica Layout e Menu</h2>
<ul class="mb-4 text-sm text-gray-700">
<li><b>Lingua attiva:</b> {{ app()->getLocale() }}</li>
<li><b>Utente autenticato:</b> {{ auth()->check() ? auth()->user()->name : 'Nessuno' }}</li>
<li><b>Ruoli utente:</b> {{ auth()->check() ? implode(', ', auth()->user()->getRoleNames()->toArray()) : '-' }}</li>
<li><b>Route attuale:</b> {{ Route::currentRouteName() }}</li>
<li><b>Sidebar presente:</b> <span id="sidebar-check" class="font-mono"></span></li>
</ul>
<div class="border-t pt-4 mt-4">
<h3 class="font-semibold mb-2">HTML aside (sidebar):</h3>
<pre class="bg-gray-100 p-2 rounded text-xs overflow-x-auto">{!! htmlentities(view('components.menu.sidebar')->render()) !!}</pre>
</div>
<div class="border-t pt-4 mt-4">
<h3 class="font-semibold mb-2">Suggerimenti:</h3>
<ul class="list-disc ml-6 text-gray-600">
<li>Se la sidebar è visibile con sfondo giallo, la struttura è corretta.</li>
<li>Se la lingua è ancora inglese, verifica che <code>.env</code> abbia <b>APP_LOCALE=it</b> e che le cache siano svuotate.</li>
<li>Se la sidebar non appare, controlla la console del browser per errori JS/CSS.</li>
</ul>
</div>
</div>
<script>
// Verifica presenza sidebar nel DOM
document.addEventListener('DOMContentLoaded', function() {
var aside = document.querySelector('aside');
document.getElementById('sidebar-check').textContent = aside ? 'PRESENTE' : 'ASSENTE';
});
</script>
@endsection

View File

@ -11,11 +11,35 @@
<link rel="preconnect" href="https://fonts.bunny.net">
<link href="https://fonts.bunny.net/css?family=figtree:400,500,600&display=swap" rel="stylesheet" />
<!-- FontAwesome (per icone menu) -->
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css" integrity="sha512-papm6Q6Q6Q6Q6Q6Q6Q6Q6Q6Q6Q6Q6Q6Q6Q6Q6Q6Q6Q6Q6Q6Q6Q6Q6Q6Q6Q6Q6Q6Q6Q6Q6Q6Q6Q6Q6Q==" crossorigin="anonymous" referrerpolicy="no-referrer" />
<!-- Scripts -->
@vite(['resources/css/app.css', 'resources/js/app.js'])
<link rel="stylesheet" href="{{ asset('css/dark-mode.css') }}">
</head>
<body class="font-sans antialiased bg-gray-100">
<div class="min-h-screen">
<body class="font-sans antialiased bg-gray-100 dark:bg-gray-900 dark:text-white transition-colors duration-300" id="main-body">
<div class="min-h-screen flex">
<!-- Colonna launcher (icone) -->
<aside class="w-16 bg-red-800 dark:bg-gray-800 flex-shrink-0 flex flex-col items-center justify-between">
@include('components.menu.launcher')
</aside>
<!-- Sidebar (gialla) -->
<aside class="w-56 bg-yellow-300 dark:bg-gray-700 border-r-4 border-indigo-500 flex-shrink-0 relative transition-all duration-300" id="sidebar-menu">
@include('components.menu.sidebar')
<!-- Pulsante per ridimensionare/nascondere -->
<button id="toggle-sidebar" class="absolute top-2 right-0 -mr-3 bg-indigo-500 text-white rounded-full p-1 shadow hover:bg-indigo-700 transition">
<i class="fa-solid fa-chevron-left"></i>
</button>
</aside>
<!-- Colonna viola sottile -->
<aside class="w-3 bg-indigo-700 flex-shrink-0 flex flex-col items-center justify-center">
<button id="show-sidebar" class="text-white opacity-70 hover:opacity-100 transition" style="display:none">
<i class="fa-solid fa-chevron-right"></i>
</button>
</aside>
<!-- Main Content -->
<div class="flex-1 flex flex-col min-h-screen">
<!-- Navigation -->
<nav class="bg-white shadow-sm border-b border-gray-200">
<div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
@ -27,7 +51,6 @@
Super Admin Panel
</a>
</div>
<!-- Navigation Links -->
<div class="hidden space-x-8 sm:-my-px sm:ml-10 sm:flex">
<a href="{{ route('superadmin.dashboard') }}"
@ -44,7 +67,6 @@
</a>
</div>
</div>
<!-- Settings Dropdown -->
<div class="hidden sm:flex sm:items-center sm:ml-6">
<div class="ml-3 relative">
@ -62,9 +84,8 @@
</div>
</div>
</nav>
<!-- Page Content -->
<main class="py-6">
<main class="py-6 flex-1">
<div class="max-w-7xl mx-auto sm:px-6 lg:px-8">
<!-- Flash Messages -->
@if (session('success'))
@ -72,22 +93,44 @@
{{ session('success') }}
</div>
@endif
@if (session('error'))
<div class="mb-4 bg-red-100 border border-red-400 text-red-700 px-4 py-3 rounded">
{{ session('error') }}
</div>
@endif
@if (session('status'))
<div class="mb-4 bg-blue-100 border border-blue-400 text-blue-700 px-4 py-3 rounded">
<div class="mb-4 bg-blue-100 border-blue-400 text-blue-700 px-4 py-3 rounded">
{{ session('status') }}
</div>
@endif
@yield('content')
</div>
</main>
</div>
</div>
<script>
// Nascondi/mostra barra gialla
const sidebar = document.getElementById('sidebar-menu');
const toggleBtn = document.getElementById('toggle-sidebar');
const showBtn = document.getElementById('show-sidebar');
if(toggleBtn && sidebar && showBtn) {
toggleBtn.addEventListener('click', function() {
sidebar.style.display = 'none';
showBtn.style.display = 'flex';
});
showBtn.addEventListener('click', function() {
sidebar.style.display = 'block';
showBtn.style.display = 'none';
});
}
// Dark mode toggle globale
const darkBtn = document.getElementById('toggle-darkmode');
darkBtn.addEventListener('click', function() {
document.body.classList.toggle('dark-mode');
document.getElementById('main-body').classList.toggle('dark-mode');
});
</script>
</body>
</html>

View File

@ -21,6 +21,8 @@ use App\Http\Controllers\ProfileController;
use App\Http\Controllers\Admin\ImpostazioniController;
use App\Http\Controllers\Admin\ApiTokenController;
use App\Http\Controllers\Admin\RubricaController;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\Route;
// --- Public Routes ---
Route::get('/', function () { return view('welcome'); });
@ -29,7 +31,18 @@ Route::get('/', function () { return view('welcome'); });
Route::middleware(['auth', 'verified'])->group(function () {
// Generic Dashboard (redirects to the correct panel based on role)
Route::get('/dashboard', function () { return view('dashboard'); })->name('dashboard');
Route::get('/dashboard', function () {
if (Auth::check()) {
if (Auth::user()->hasRole('super-admin')) {
return redirect()->route('superadmin.dashboard');
} elseif (Auth::user()->hasRole(['admin', 'amministratore'])) {
return redirect()->route('admin.dashboard');
} elseif (Auth::user()->hasRole('condomino')) {
return redirect()->route('condomino.dashboard');
}
}
return view('dashboard');
})->name('dashboard');
// Profile Routes
Route::get('/profile', [ProfileController::class, 'edit'])->name('profile.edit');
@ -47,6 +60,10 @@ Route::middleware(['auth', 'verified'])->group(function () {
Route::patch('users/{user}/update-role', [SuperAdminUserController::class, 'updateRole'])->name('users.updateRole');
Route::get('users/{user}/impersonate', [SuperAdminUserController::class, 'impersonate'])->name('users.impersonate');
// Impostazioni Sistema
Route::get('impostazioni', [\App\Http\Controllers\SuperAdmin\ImpostazioniController::class, 'index'])->name('impostazioni.index');
Route::post('impostazioni', [\App\Http\Controllers\SuperAdmin\ImpostazioniController::class, 'store'])->name('impostazioni.store');
// Gestione Amministratori
Route::resource('amministratori', SuperAdminAmministratoreController::class)
->except(['show'])
@ -55,8 +72,20 @@ Route::middleware(['auth', 'verified'])->group(function () {
// Gestione Categorie Ticket
Route::resource('categorie-ticket', CategoriaTicketController::class)->except(['show']);
// Gestione Stabili (ora anche per super-admin)
Route::resource('stabili', StabileController::class);
Route::resource('stabili.unitaImmobiliari', UnitaImmobiliareController::class)->shallow();
Route::resource('unitaImmobiliari', UnitaImmobiliareController::class)->only(['edit', 'update', 'destroy']);
Route::resource('soggetti', SoggettoController::class);
Route::resource('fornitori', FornitoreController::class);
Route::resource('tickets', TicketController::class);
Route::resource('documenti', DocumentoController::class)->except(['edit', 'update']);
// Diagnostica
Route::get('/diagnostica', function() { return view('superadmin.diagnostica'); })->name('diagnostica');
Route::get('/diagnostica-menu', function() {
return view('superadmin.diagnostica_menu');
})->name('diagnostica_menu');
});
// --- ADMIN / AMMINISTRATORE PANEL ---
@ -186,3 +215,10 @@ Route::middleware(['auth'])->group(function () {
Route::get('/contabilita/registrazione-test', \App\Livewire\Contabilita\RegistrazioneTest::class)
->middleware(['auth'])
->name('contabilita.registrazione-test');
// Rotta per aggiornare la sessione con lo stabile selezionato
Route::post('/session/stabile', function (\Illuminate\Http\Request $request) {
$request->validate(['stabile' => 'required|string']);
session(['stabile_corrente' => $request->input('stabile')]);
return response()->json(['ok' => true]);
})->middleware('auth')->name('session.stabile');

View File

@ -8,14 +8,22 @@ export default {
'./storage/framework/views/*.php',
'./resources/views/**/*.blade.php',
],
darkMode: 'class', // Abilita dark mode con classe
theme: {
extend: {
fontFamily: {
sans: ['Figtree', ...defaultTheme.fontFamily.sans],
},
colors: {
// Colori personalizzabili per la GUI
'custom-bg': 'var(--custom-bg)',
'custom-text': 'var(--custom-text)',
'custom-accent': 'var(--custom-accent)',
'sidebar-bg': 'var(--sidebar-bg)',
'sidebar-text': 'var(--sidebar-text)',
'sidebar-accent': 'var(--sidebar-accent)',
}
},
},
plugins: [forms],
};