Implementato dark mode completo e salvataggio impostazioni utente

- Aggiunto dark mode completo alla sidebar con classi Tailwind
- Implementato sistema di salvataggio permanente delle impostazioni utente
- Creata tabella user_settings per gestire preferenze personalizzate
- Aggiunto model UserSetting con metodi helper get/set
- Esteso controller impostazioni per supportare salvataggio e temi predefiniti
- Applicato stesso tema anche al pannello amministratore
- Aggiornate route per gestione temi in admin e superadmin
- Integrato sistema impostazioni nel layout principale con variabili CSS
- Aggiornato AppServiceProvider con helper userSetting()
- Dark mode applicato a: sidebar, modali, footer, bottoni, hover states
- Temi predefiniti: Default, Dark, Ocean con preview tempo reale
- Compatibilità completa tra pannello admin e superadmin
This commit is contained in:
Pikappa2 2025-07-05 19:22:51 +02:00
parent cf22a51dc7
commit cb49fbfe70
32 changed files with 1445 additions and 188 deletions

View File

@ -0,0 +1,12 @@
<?php
use Illuminate\Support\Facades\Cache;
use Illuminate\Support\Facades\DB;
if (!function_exists('impostazione')) {
function impostazione($chiave, $default = null) {
return Cache::rememberForever('impostazione_' . $chiave, function() use ($chiave, $default) {
$val = DB::table('impostazioni')->where('chiave', $chiave)->value('valore');
return $val ?? $default;
});
}
}

View File

@ -3,6 +3,7 @@
namespace App\Http\Controllers\Admin; namespace App\Http\Controllers\Admin;
use App\Http\Controllers\Controller; use App\Http\Controllers\Controller;
use App\Models\UserSetting;
use Illuminate\Http\Request; use Illuminate\Http\Request;
class ImpostazioniController extends Controller class ImpostazioniController extends Controller
@ -14,6 +15,77 @@ class ImpostazioniController extends Controller
public function index() public function index()
{ {
return view('admin.impostazioni.index'); // Carica le impostazioni attuali dell'utente
$settings = [
'dark_mode' => UserSetting::get('dark_mode', 'false'),
'bg_color' => UserSetting::get('bg_color', '#ffffff'),
'text_color' => UserSetting::get('text_color', '#1e293b'),
'accent_color' => UserSetting::get('accent_color', '#6366f1'),
'sidebar_bg_color' => UserSetting::get('sidebar_bg_color', '#fde047'),
'sidebar_text_color' => UserSetting::get('sidebar_text_color', '#1e293b'),
'sidebar_accent_color' => UserSetting::get('sidebar_accent_color', '#6366f1'),
];
return view('admin.impostazioni.index', compact('settings'));
}
public function store(Request $request)
{
$validated = $request->validate([
'dark_mode' => 'string|in:true,false',
'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 le impostazioni per l'utente corrente
foreach ($validated as $key => $value) {
UserSetting::set($key, $value);
}
return response()->json(['success' => true, 'message' => 'Impostazioni salvate con successo!']);
}
public function theme(Request $request)
{
$theme = $request->input('theme', 'default');
$themes = [
'default' => [
'bg_color' => '#ffffff',
'text_color' => '#1e293b',
'accent_color' => '#6366f1',
'sidebar_bg_color' => '#fde047',
'sidebar_text_color' => '#1e293b',
'sidebar_accent_color' => '#6366f1',
],
'dark' => [
'bg_color' => '#1e293b',
'text_color' => '#f1f5f9',
'accent_color' => '#fbbf24',
'sidebar_bg_color' => '#374151',
'sidebar_text_color' => '#f1f5f9',
'sidebar_accent_color' => '#fbbf24',
],
'ocean' => [
'bg_color' => '#f0f9ff',
'text_color' => '#0c4a6e',
'accent_color' => '#0ea5e9',
'sidebar_bg_color' => '#0ea5e9',
'sidebar_text_color' => '#ffffff',
'sidebar_accent_color' => '#f0f9ff',
],
];
if (isset($themes[$theme])) {
foreach ($themes[$theme] as $key => $value) {
UserSetting::set($key, $value);
}
}
return response()->json(['success' => true, 'settings' => $themes[$theme] ?? []]);
} }
} }

View File

@ -3,19 +3,31 @@
namespace App\Http\Controllers\SuperAdmin; namespace App\Http\Controllers\SuperAdmin;
use App\Http\Controllers\Controller; use App\Http\Controllers\Controller;
use App\Models\UserSetting;
use Illuminate\Http\Request; use Illuminate\Http\Request;
class ImpostazioniController extends Controller class ImpostazioniController extends Controller
{ {
public function index() public function index()
{ {
return view('superadmin.impostazioni.index'); // Carica le impostazioni attuali dell'utente
$settings = [
'dark_mode' => UserSetting::get('dark_mode', 'false'),
'bg_color' => UserSetting::get('bg_color', '#ffffff'),
'text_color' => UserSetting::get('text_color', '#1e293b'),
'accent_color' => UserSetting::get('accent_color', '#6366f1'),
'sidebar_bg_color' => UserSetting::get('sidebar_bg_color', '#fde047'),
'sidebar_text_color' => UserSetting::get('sidebar_text_color', '#1e293b'),
'sidebar_accent_color' => UserSetting::get('sidebar_accent_color', '#6366f1'),
];
return view('superadmin.impostazioni.index', compact('settings'));
} }
public function store(Request $request) public function store(Request $request)
{ {
// Logica per salvare le impostazioni di colore
$validated = $request->validate([ $validated = $request->validate([
'dark_mode' => 'string|in:true,false',
'bg_color' => 'string|max:7', 'bg_color' => 'string|max:7',
'text_color' => 'string|max:7', 'text_color' => 'string|max:7',
'accent_color' => 'string|max:7', 'accent_color' => 'string|max:7',
@ -24,8 +36,51 @@ class ImpostazioniController extends Controller
'sidebar_accent_color' => 'string|max:7', 'sidebar_accent_color' => 'string|max:7',
]); ]);
// Salva nelle impostazioni di sistema (da implementare) // Salva le impostazioni per l'utente corrente
// Per ora restituiamo una risposta di successo foreach ($validated as $key => $value) {
UserSetting::set($key, $value);
}
return response()->json(['success' => true, 'message' => 'Impostazioni salvate con successo!']); return response()->json(['success' => true, 'message' => 'Impostazioni salvate con successo!']);
} }
public function theme(Request $request)
{
$theme = $request->input('theme', 'default');
$themes = [
'default' => [
'bg_color' => '#ffffff',
'text_color' => '#1e293b',
'accent_color' => '#6366f1',
'sidebar_bg_color' => '#fde047',
'sidebar_text_color' => '#1e293b',
'sidebar_accent_color' => '#6366f1',
],
'dark' => [
'bg_color' => '#1e293b',
'text_color' => '#f1f5f9',
'accent_color' => '#fbbf24',
'sidebar_bg_color' => '#374151',
'sidebar_text_color' => '#f1f5f9',
'sidebar_accent_color' => '#fbbf24',
],
'ocean' => [
'bg_color' => '#f0f9ff',
'text_color' => '#0c4a6e',
'accent_color' => '#0ea5e9',
'sidebar_bg_color' => '#0ea5e9',
'sidebar_text_color' => '#ffffff',
'sidebar_accent_color' => '#f0f9ff',
],
];
if (isset($themes[$theme])) {
foreach ($themes[$theme] as $key => $value) {
UserSetting::set($key, $value);
}
}
return response()->json(['success' => true, 'settings' => $themes[$theme] ?? []]);
}
} }

View File

@ -7,6 +7,7 @@ use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo; use Illuminate\Database\Eloquent\Relations\BelongsTo;
use Illuminate\Database\Eloquent\Relations\HasMany; // Aggiunto per condomini() use Illuminate\Database\Eloquent\Relations\HasMany; // Aggiunto per condomini()
use Illuminate\Database\Eloquent\SoftDeletes; // Aggiunto per soft deletes use Illuminate\Database\Eloquent\SoftDeletes; // Aggiunto per soft deletes
use Illuminate\Support\Str;
class Amministratore extends Model class Amministratore extends Model
{ {
@ -39,6 +40,7 @@ class Amministratore extends Model
'telefono_studio', 'telefono_studio',
'email_studio', 'email_studio',
'pec_studio', 'pec_studio',
'codice_univoco',
]; ];
/** /**
@ -63,4 +65,16 @@ class Amministratore extends Model
{ {
return $this->hasMany(Fornitore::class, 'amministratore_id', 'id_amministratore'); return $this->hasMany(Fornitore::class, 'amministratore_id', 'id_amministratore');
} }
protected static function booted()
{
static::creating(function ($amministratore) {
if (empty($amministratore->codice_univoco)) {
do {
$code = Str::upper(Str::random(8));
} while (self::where('codice_univoco', $code)->exists());
$amministratore->codice_univoco = $code;
}
});
}
} }

16
app/Models/Role.php Normal file
View File

@ -0,0 +1,16 @@
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
class Role extends Model
{
use HasFactory;
protected $fillable = ['name', 'label'];
public function users()
{
// return $this->belongsToMany(User::class, 'role_user')->withTimestamps();
// Relazione legacy rimossa: ora i ruoli sono gestiti solo tramite Spatie/Permission
}
}

View File

@ -6,6 +6,7 @@ use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsToMany; use Illuminate\Database\Eloquent\Relations\BelongsToMany;
use Illuminate\Database\Eloquent\Relations\HasMany; use Illuminate\Database\Eloquent\Relations\HasMany;
use Illuminate\Support\Str;
class Soggetto extends Model class Soggetto extends Model
{ {
@ -32,6 +33,7 @@ class Soggetto extends Model
'citta', 'citta',
'provincia', 'provincia',
'tipo', 'tipo',
'codice_univoco',
]; ];
/** /**
@ -51,4 +53,16 @@ class Soggetto extends Model
public function proprieta() { public function proprieta() {
return $this->hasMany(Proprieta::class); return $this->hasMany(Proprieta::class);
} }
protected static function booted()
{
static::creating(function ($soggetto) {
if (empty($soggetto->codice_univoco)) {
do {
$code = Str::upper(Str::random(8));
} while (self::where('codice_univoco', $code)->exists());
$soggetto->codice_univoco = $code;
}
});
}
} }

View File

@ -56,4 +56,16 @@ class User extends Authenticatable
// Solo gli utenti con ruolo 'admin' possono essere impersonati. // Solo gli utenti con ruolo 'admin' possono essere impersonati.
return $this->hasRole('admin'); return $this->hasRole('admin');
} }
// public function roles()
// {
// return $this->belongsToMany(\App\Models\Role::class, 'role_user')->withTimestamps();
// // Relazione legacy rimossa: ora i ruoli sono gestiti solo tramite Spatie/Permission
// }
// public function hasRole($role, $stabileId = null)
// {
// return $this->roles()->where('name', $role)
// ->when($stabileId, fn($q) => $q->wherePivot('stabile_id', $stabileId))
// ->exists();
// }
} }

View File

@ -0,0 +1,40 @@
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
class UserSetting extends Model
{
protected $fillable = ['user_id', 'key', 'value'];
public function user()
{
return $this->belongsTo(User::class);
}
public static function get($key, $default = null)
{
if (!auth()->check()) {
return $default;
}
$setting = static::where('user_id', auth()->id())
->where('key', $key)
->first();
return $setting ? $setting->value : $default;
}
public static function set($key, $value)
{
if (!auth()->check()) {
return false;
}
return static::updateOrCreate(
['user_id' => auth()->id(), 'key' => $key],
['value' => $value]
);
}
}

View File

@ -22,5 +22,12 @@ class AppServiceProvider extends ServiceProvider
{ {
// View Composer per la sidebar // View Composer per la sidebar
View::composer(['components.menu.sidebar', 'superadmin.dashboard', 'admin.dashboard'], \App\Http\View\Composers\SidebarComposer::class); View::composer(['components.menu.sidebar', 'superadmin.dashboard', 'admin.dashboard'], \App\Http\View\Composers\SidebarComposer::class);
// Helper per le impostazioni utente
if (!function_exists('userSetting')) {
function userSetting($key, $default = null) {
return \App\Models\UserSetting::get($key, $default);
}
}
} }
} }

View File

@ -0,0 +1,42 @@
<?php
namespace App\Providers;
use Illuminate\Support\Facades\View;
use Illuminate\Support\ServiceProvider;
use Illuminate\Support\Facades\Auth;
use App\Models\Stabile;
use App\Models\Gestione;
class SidebarComposer extends ServiceProvider
{
public function boot()
{
View::composer('components.menu.sidebar', function ($view) {
$user = Auth::user();
$stabili = [];
if ($user) {
// Example: fetch stabili for amministratore or all for super-admin
if ($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,
]);
});
}
public function register() {}
}

View File

@ -31,10 +31,11 @@
}, },
"autoload": { "autoload": {
"psr-4": { "psr-4": {
"App\\": "app/", "App\\": "app/"
"Database\\Factories\\": "database/factories/", },
"Database\\Seeders\\": "database/seeders/" "files": [
} "app/Helpers/impostazioni.php"
]
}, },
"autoload-dev": { "autoload-dev": {
"psr-4": { "psr-4": {

86
composer.json.backup Normal file
View File

@ -0,0 +1,86 @@
{
"$schema": "https://getcomposer.org/schema.json",
"name": "laravel/laravel",
"type": "project",
"description": "The skeleton application for the Laravel framework.",
"keywords": [
"laravel",
"framework"
],
"license": "MIT",
"require": {
"php": "^8.2",
"lab404/laravel-impersonate": "^1.7",
"laravel/fortify": "^1.26",
"laravel/framework": "^12.0",
"laravel/sanctum": "^4.1",
"laravel/tinker": "^2.10.1",
"livewire/livewire": "^3.6",
"spatie/laravel-permission": "^6.20"
},
"require-dev": {
"fakerphp/faker": "^1.23",
"laravel/breeze": "^2.3",
"laravel/pail": "^1.2.2",
"laravel/pint": "^1.13",
"laravel/sail": "^1.41",
"mockery/mockery": "^1.6",
"nunomaduro/collision": "^8.6",
"pestphp/pest": "^3.8",
"pestphp/pest-plugin-laravel": "^3.2"
},
"autoload": {
"psr-4": {
"App\\": "app/"
},
"files": [
"app/Helpers/impostazioni.php"
]
},
"autoload-dev": {
"psr-4": {
"Tests\\": "tests/"
}
},
"scripts": {
"post-autoload-dump": [
"Illuminate\\Foundation\\ComposerScripts::postAutoloadDump",
"@php artisan package:discover --ansi"
],
"post-update-cmd": [
"@php artisan vendor:publish --tag=laravel-assets --ansi --force"
],
"post-root-package-install": [
"@php -r \"file_exists('.env') || copy('.env.example', '.env');\""
],
"post-create-project-cmd": [
"@php artisan key:generate --ansi",
"@php -r \"file_exists('database/database.sqlite') || touch('database/database.sqlite');\"",
"@php artisan migrate --graceful --ansi"
],
"dev": [
"Composer\\Config::disableProcessTimeout",
"npx concurrently -c \"#93c5fd,#c4b5fd,#fb7185,#fdba74\" \"php artisan serve\" \"php artisan queue:listen --tries=1\" \"php artisan pail --timeout=0\" \"npm run dev\" --names=server,queue,logs,vite"
],
"test": [
"@php artisan config:clear --ansi",
"@php artisan test"
]
},
"extra": {
"laravel": {
"dont-discover": []
}
},
"config": {
"optimize-autoloader": true,
"preferred-install": "dist",
"sort-packages": true,
"allow-plugins": {
"pestphp/pest-plugin": true,
"php-http/discovery": true
}
},
"minimum-stability": "stable",
"prefer-stable": true
}

View File

@ -111,6 +111,18 @@ return new class extends Migration
$table->primary([$pivotPermission, $pivotRole], 'role_has_permissions_permission_id_role_id_primary'); $table->primary([$pivotPermission, $pivotRole], 'role_has_permissions_permission_id_role_id_primary');
}); });
// Popola ruoli di base
DB::table('roles')->insertOrIgnore([
['name' => 'super-admin', 'guard_name' => 'web', 'created_at' => now(), 'updated_at' => now()],
['name' => 'admin', 'guard_name' => 'web', 'created_at' => now(), 'updated_at' => now()],
['name' => 'collaboratore', 'guard_name' => 'web', 'created_at' => now(), 'updated_at' => now()],
['name' => 'condomino', 'guard_name' => 'web', 'created_at' => now(), 'updated_at' => now()],
['name' => 'fornitore', 'guard_name' => 'web', 'created_at' => now(), 'updated_at' => now()],
['name' => 'inquilino', 'guard_name' => 'web', 'created_at' => now(), 'updated_at' => now()],
['name' => 'ospite', 'guard_name' => 'web', 'created_at' => now(), 'updated_at' => now()],
['name' => 'servizi', 'guard_name' => 'web', 'created_at' => now(), 'updated_at' => now()],
]);
app('cache') app('cache')
->store(config('permission.cache.store') != 'default' ? config('permission.cache.store') : null) ->store(config('permission.cache.store') != 'default' ? config('permission.cache.store') : null)
->forget(config('permission.cache.key')); ->forget(config('permission.cache.key'));

View File

@ -28,6 +28,7 @@ return new class extends Migration
$table->string('telefono_studio')->nullable(); $table->string('telefono_studio')->nullable();
$table->string('email_studio')->nullable(); $table->string('email_studio')->nullable();
$table->string('pec_studio')->nullable(); $table->string('pec_studio')->nullable();
$table->string('codice_univoco', 8)->unique();
$table->timestamps(); $table->timestamps();
$table->foreign('user_id')->references('id')->on('users')->onDelete('cascade'); $table->foreign('user_id')->references('id')->on('users')->onDelete('cascade');
}); });
@ -67,6 +68,7 @@ return new class extends Migration
$table->string('citta', 60)->nullable(); $table->string('citta', 60)->nullable();
$table->string('provincia', 2)->nullable(); $table->string('provincia', 2)->nullable();
$table->enum('tipo', ['proprietario', 'inquilino', 'usufruttuario', 'altro']); $table->enum('tipo', ['proprietario', 'inquilino', 'usufruttuario', 'altro']);
$table->string('codice_univoco', 8)->unique();
$table->timestamps(); $table->timestamps();
}); });

View File

@ -0,0 +1,9 @@
<?php
// Migration svuotata: duplicata e non più necessaria, gestione ruoli/permessi demandata a Spatie/Permission.
use Illuminate\Database\Migrations\Migration;
return new class extends Migration {
public function up(): void {}
public function down(): void {}
};

View File

@ -0,0 +1,37 @@
<?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('documenti', function (Blueprint $table) {
$table->id();
$table->string('protocollo')->nullable()->unique(); // es. 2025-0001
$table->date('data_protocollo')->nullable();
$table->string('tipo_documento')->nullable(); // Fattura, Contratto, Verbale, ecc.
$table->unsignedBigInteger('stabile_id')->nullable();
$table->unsignedBigInteger('fornitore_id')->nullable();
$table->unsignedBigInteger('esercizio_contabile_id')->nullable();
$table->text('descrizione')->nullable();
$table->decimal('importo', 12, 2)->nullable();
$table->date('data_documento')->nullable();
$table->string('nome_file');
$table->string('path_file');
$table->text('testo_estratto_ocr')->nullable();
// Polimorfismo
$table->unsignedBigInteger('documentable_id')->nullable()->index();
$table->string('documentable_type')->nullable()->index();
$table->timestamps();
// FK opzionali
$table->foreign('stabile_id')->references('id')->on('stabili')->onDelete('set null');
$table->foreign('fornitore_id')->references('id')->on('fornitori')->onDelete('set null');
// $table->foreign('esercizio_contabile_id')->references('id')->on('esercizi_contabili')->onDelete('set null'); // FK disabilitata: tabella non ancora presente
});
}
public function down(): void
{
Schema::dropIfExists('documenti');
}
};

View File

@ -0,0 +1,33 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
/**
* Run the migrations.
*/
public function up(): void
{
Schema::create('user_settings', function (Blueprint $table) {
$table->id();
$table->unsignedBigInteger('user_id');
$table->string('key');
$table->text('value')->nullable();
$table->timestamps();
$table->foreign('user_id')->references('id')->on('users')->onDelete('cascade');
$table->unique(['user_id', 'key']);
});
}
/**
* Reverse the migrations.
*/
public function down(): void
{
Schema::dropIfExists('user_settings');
}
};

View File

@ -0,0 +1,22 @@
<?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('impostazioni', function (Blueprint $table) {
$table->id();
$table->string('chiave')->unique();
$table->string('valore')->nullable();
$table->string('descrizione')->nullable();
$table->timestamps();
});
}
public function down(): void
{
Schema::dropIfExists('impostazioni');
}
};

View File

@ -0,0 +1,56 @@
<?php
namespace Database\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,17 @@
<?php
namespace Database\Seeders;
use Illuminate\Database\Console\Seeds\WithoutModelEvents;
use Illuminate\Database\Seeder;
class NewTestSeeder extends Seeder
{
/**
* Run the database seeds.
*/
public function run(): void
{
//
}
}

View File

@ -0,0 +1,17 @@
<?php
namespace Database\Seeders;
use Illuminate\Database\Console\Seeds\WithoutModelEvents;
use Illuminate\Database\Seeder;
class TestSeeder extends Seeder
{
/**
* Run the database seeds.
*/
public function run(): void
{
//
}
}

View File

@ -0,0 +1,17 @@
<?php
namespace Database\Seeders;
use Illuminate\Database\Console\Seeds\WithoutModelEvents;
use Illuminate\Database\Seeder;
class TestSeeder2 extends Seeder
{
/**
* Run the database seeds.
*/
public function run(): void
{
//
}
}

17
lang/en/menu.php Normal file
View File

@ -0,0 +1,17 @@
<?php
return [
'dashboard' => 'Dashboard',
'dashboard_overview' => 'Overview',
'stabili' => 'Buildings',
'lista_stabili' => 'List buildings',
'nuovo_stabile' => 'New building',
'soggetti' => 'People',
'lista_soggetti' => 'List people',
'nuovo_soggetto' => 'New person',
'contabilita' => 'Accounting',
'piano_conti' => 'Chart of accounts',
'movimenti' => 'Transactions',
'impostazioni' => 'Settings',
'utenti' => 'Users',
'ruoli' => 'Roles',
];

17
lang/it/menu.php Normal file
View File

@ -0,0 +1,17 @@
<?php
return [
'dashboard' => 'Dashboard',
'dashboard_overview' => 'Panoramica',
'stabili' => 'Stabili',
'lista_stabili' => 'Elenco stabili',
'nuovo_stabile' => 'Nuovo stabile',
'soggetti' => 'Soggetti',
'lista_soggetti' => 'Elenco soggetti',
'nuovo_soggetto' => 'Nuovo soggetto',
'contabilita' => 'Contabilità',
'piano_conti' => 'Piano dei conti',
'movimenti' => 'Movimenti',
'impostazioni' => 'Impostazioni',
'utenti' => 'Utenti',
'ruoli' => 'Ruoli',
];

View File

@ -1,144 +1,291 @@
<x-app-layout> @extends('layouts.app')
<x-slot name="header">
<h2 class="font-semibold text-xl text-gray-800 dark:text-gray-200 leading-tight">
{{ __('Impostazioni') }}
</h2>
</x-slot>
<div class="py-12"> @section('content')
<div class="max-w-7xl mx-auto sm:px-6 lg:px-8"> <div class="container mx-auto px-6 py-8 bg-white dark:bg-gray-900 text-gray-900 dark:text-gray-100">
<div class="bg-white dark:bg-gray-800 overflow-hidden shadow-sm sm:rounded-lg"> <div class="mb-8">
<div class="p-6 text-gray-900 dark:text-gray-100"> <h1 class="text-3xl font-bold text-gray-900 dark:text-gray-100">Impostazioni Personalizzazione</h1>
<p class="text-gray-600 dark:text-gray-400 mt-2">Personalizza i colori e il tema dell'interfaccia</p>
</div>
<div class="mb-6"> <!-- Messaggi di successo/errore -->
<h3 class="text-2xl font-bold text-gray-800 dark:text-gray-200">Configurazione Sistema</h3> <div id="message-container" class="hidden mb-6">
<p class="text-gray-600 dark:text-gray-400 mt-2">Gestisci le impostazioni generali dell'applicazione</p> <div id="success-message" class="bg-green-100 dark:bg-green-900 border border-green-400 dark:border-green-600 text-green-700 dark:text-green-300 px-4 py-3 rounded mb-4 hidden">
<span id="success-text"></span>
</div>
<div id="error-message" class="bg-red-100 dark:bg-red-900 border border-red-400 dark:border-red-600 text-red-700 dark:text-red-300 px-4 py-3 rounded mb-4 hidden">
<span id="error-text"></span>
</div>
</div>
<div class="grid grid-cols-1 lg:grid-cols-2 gap-8">
<!-- Pannello di controllo colori -->
<div class="bg-gray-50 dark:bg-gray-800 p-6 rounded-lg shadow">
<h2 class="text-xl font-semibold mb-4 text-gray-900 dark:text-gray-100">Personalizzazione Colori</h2>
<form id="color-form" class="space-y-4">
@csrf
<!-- Dark Mode Toggle -->
<div class="flex items-center justify-between">
<label class="text-sm font-medium text-gray-700 dark:text-gray-300">Modalità Scura</label>
<input type="checkbox" id="dark_mode" name="dark_mode" value="true"
{{ $settings['dark_mode'] === 'true' ? 'checked' : '' }}
class="w-4 h-4 text-indigo-600 bg-gray-100 border-gray-300 rounded focus:ring-indigo-500 dark:focus:ring-indigo-600 dark:ring-offset-gray-800 focus:ring-2 dark:bg-gray-700 dark:border-gray-600">
</div>
<!-- Colori Principale -->
<h3 class="text-lg font-medium text-gray-900 dark:text-gray-100 mt-6">Tema Principale</h3>
<div class="grid grid-cols-3 gap-4">
<div>
<label class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-1">Sfondo</label>
<input type="color" id="bg_color" name="bg_color" value="{{ $settings['bg_color'] }}"
class="w-full h-10 rounded border border-gray-300 dark:border-gray-600">
</div>
<div>
<label class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-1">Testo</label>
<input type="color" id="text_color" name="text_color" value="{{ $settings['text_color'] }}"
class="w-full h-10 rounded border border-gray-300 dark:border-gray-600">
</div>
<div>
<label class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-1">Accento</label>
<input type="color" id="accent_color" name="accent_color" value="{{ $settings['accent_color'] }}"
class="w-full h-10 rounded border border-gray-300 dark:border-gray-600">
</div>
</div>
<!-- Colori Sidebar -->
<h3 class="text-lg font-medium text-gray-900 dark:text-gray-100 mt-6">Sidebar</h3>
<div class="grid grid-cols-3 gap-4">
<div>
<label class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-1">Sfondo Sidebar</label>
<input type="color" id="sidebar_bg_color" name="sidebar_bg_color" value="{{ $settings['sidebar_bg_color'] }}"
class="w-full h-10 rounded border border-gray-300 dark:border-gray-600">
</div>
<div>
<label class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-1">Testo Sidebar</label>
<input type="color" id="sidebar_text_color" name="sidebar_text_color" value="{{ $settings['sidebar_text_color'] }}"
class="w-full h-10 rounded border border-gray-300 dark:border-gray-600">
</div>
<div>
<label class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-1">Accento Sidebar</label>
<input type="color" id="sidebar_accent_color" name="sidebar_accent_color" value="{{ $settings['sidebar_accent_color'] }}"
class="w-full h-10 rounded border border-gray-300 dark:border-gray-600">
</div>
</div>
<!-- Temi Predefiniti -->
<h3 class="text-lg font-medium text-gray-900 dark:text-gray-100 mt-6">Temi Predefiniti</h3>
<div class="flex flex-wrap gap-2">
<button type="button" class="theme-btn px-4 py-2 rounded bg-blue-500 text-white hover:bg-blue-600" data-theme="default">Default</button>
<button type="button" class="theme-btn px-4 py-2 rounded bg-gray-800 text-white hover:bg-gray-900" data-theme="dark">Dark</button>
<button type="button" class="theme-btn px-4 py-2 rounded bg-blue-600 text-white hover:bg-blue-700" data-theme="ocean">Ocean</button>
</div>
<!-- Pulsanti di controllo -->
<div class="flex gap-4 mt-6">
<button type="submit" class="bg-green-600 hover:bg-green-700 text-white font-bold py-2 px-4 rounded">
Salva Impostazioni
</button>
<button type="button" id="reset-btn" class="bg-gray-600 hover:bg-gray-700 text-white font-bold py-2 px-4 rounded">
Reset
</button>
</div>
</form>
</div>
<!-- Anteprima -->
<div class="bg-gray-50 dark:bg-gray-800 p-6 rounded-lg shadow">
<h2 class="text-xl font-semibold mb-4 text-gray-900 dark:text-gray-100">Anteprima</h2>
<div id="preview-container" class="border rounded-lg p-4 transition-all duration-300" style="background-color: {{ $settings['bg_color'] }}; color: {{ $settings['text_color'] }};">
<div class="mb-4">
<h3 class="text-lg font-bold">Dashboard Amministratore</h3>
<p class="text-sm opacity-75">Benvenuto nel pannello di amministrazione</p>
</div>
<!-- Simulazione sidebar -->
<div class="flex gap-4">
<div id="sidebar-preview" class="w-32 h-40 rounded p-2 transition-all duration-300" style="background-color: {{ $settings['sidebar_bg_color'] }}; color: {{ $settings['sidebar_text_color'] }};">
<div class="text-xs font-bold mb-2">Menu</div>
<div class="space-y-1">
<div class="text-xs p-1 rounded" style="background-color: {{ $settings['sidebar_accent_color'] }}; opacity: 0.3;">Dashboard</div>
<div class="text-xs p-1">Stabili</div>
<div class="text-xs p-1">Utenti</div>
</div>
</div> </div>
<form method="POST" action="{{ route('admin.impostazioni.store') }}" enctype="multipart/form-data"> <!-- Contenuto principale -->
@csrf <div class="flex-1">
<div class="mb-2">
<!-- Sezione Applicazione --> <div class="w-full h-6 rounded" style="background-color: {{ $settings['accent_color'] }}; opacity: 0.2;"></div>
<div class="bg-blue-50 dark:bg-blue-900/20 p-6 rounded-lg mb-6">
<h4 class="text-lg font-medium text-gray-900 dark:text-gray-100 mb-4">Impostazioni Applicazione</h4>
<div class="grid grid-cols-1 md:grid-cols-2 gap-6">
<!-- Nome Applicazione -->
<div>
<x-input-label for="app_name" :value="__('Nome Applicazione')" />
<x-text-input id="app_name" name="app_name" type="text" class="mt-1 block w-full"
:value="old('app_name', config('app.name'))" />
<x-input-error class="mt-2" :messages="$errors->get('app_name')" />
</div>
<!-- URL Applicazione -->
<div>
<x-input-label for="app_url" :value="__('URL Applicazione')" />
<x-text-input id="app_url" name="app_url" type="url" class="mt-1 block w-full"
:value="old('app_url', config('app.url'))" />
<x-input-error class="mt-2" :messages="$errors->get('app_url')" />
</div>
</div>
</div> </div>
<div class="space-y-2">
<!-- Sezione Branding --> <div class="w-3/4 h-4 rounded" style="background-color: {{ $settings['text_color'] }}; opacity: 0.2;"></div>
<div class="bg-green-50 dark:bg-green-900/20 p-6 rounded-lg mb-6"> <div class="w-1/2 h-4 rounded" style="background-color: {{ $settings['text_color'] }}; opacity: 0.1;"></div>
<h4 class="text-lg font-medium text-gray-900 dark:text-gray-100 mb-4">Branding</h4>
<div class="grid grid-cols-1 md:grid-cols-2 gap-6">
<!-- Logo Applicazione -->
<div>
<x-input-label for="app_logo" :value="__('Logo Applicazione')" />
<input type="file" id="app_logo" name="app_logo" accept="image/*"
class="mt-1 block w-full border-gray-300 dark:border-gray-700 dark:bg-gray-900 dark:text-gray-300 focus:border-indigo-500 dark:focus:border-indigo-600 focus:ring-indigo-500 dark:focus:ring-indigo-600 rounded-md shadow-sm" />
<x-input-error class="mt-2" :messages="$errors->get('app_logo')" />
<p class="mt-1 text-sm text-gray-500 dark:text-gray-400">Formati supportati: JPG, PNG, SVG (max 2MB)</p>
</div>
<!-- Logo Dashboard -->
<div>
<x-input-label for="dashboard_logo" :value="__('Logo Dashboard')" />
<input type="file" id="dashboard_logo" name="dashboard_logo" accept="image/*"
class="mt-1 block w-full border-gray-300 dark:border-gray-700 dark:bg-gray-900 dark:text-gray-300 focus:border-indigo-500 dark:focus:border-indigo-600 focus:ring-indigo-500 dark:focus:ring-indigo-600 rounded-md shadow-sm" />
<x-input-error class="mt-2" :messages="$errors->get('dashboard_logo')" />
</div>
<!-- Favicon -->
<div>
<x-input-label for="favicon" :value="__('Favicon')" />
<input type="file" id="favicon" name="favicon" accept="image/*"
class="mt-1 block w-full border-gray-300 dark:border-gray-700 dark:bg-gray-900 dark:text-gray-300 focus:border-indigo-500 dark:focus:border-indigo-600 focus:ring-indigo-500 dark:focus:ring-indigo-600 rounded-md shadow-sm" />
<x-input-error class="mt-2" :messages="$errors->get('favicon')" />
<p class="mt-1 text-sm text-gray-500 dark:text-gray-400">Formato ICO o PNG 32x32px</p>
</div>
</div>
</div> </div>
</div>
<!-- Sezione Email -->
<div class="bg-yellow-50 dark:bg-yellow-900/20 p-6 rounded-lg mb-6">
<h4 class="text-lg font-medium text-gray-900 dark:text-gray-100 mb-4">Configurazione Email</h4>
<div class="grid grid-cols-1 md:grid-cols-2 gap-6">
<!-- Email Mittente -->
<div>
<x-input-label for="mail_from_address" :value="__('Email Mittente')" />
<x-text-input id="mail_from_address" name="mail_from_address" type="email" class="mt-1 block w-full"
:value="old('mail_from_address', config('mail.from.address'))" />
<x-input-error class="mt-2" :messages="$errors->get('mail_from_address')" />
</div>
<!-- Nome Mittente -->
<div>
<x-input-label for="mail_from_name" :value="__('Nome Mittente')" />
<x-text-input id="mail_from_name" name="mail_from_name" type="text" class="mt-1 block w-full"
:value="old('mail_from_name', config('mail.from.name'))" />
<x-input-error class="mt-2" :messages="$errors->get('mail_from_name')" />
</div>
</div>
</div>
<!-- Sezione Pagamenti -->
<div class="bg-purple-50 dark:bg-purple-900/20 p-6 rounded-lg mb-6">
<h4 class="text-lg font-medium text-gray-900 dark:text-gray-100 mb-4">Configurazione Pagamenti</h4>
<div class="grid grid-cols-1 md:grid-cols-2 gap-6">
<!-- Stripe Key -->
<div>
<x-input-label for="stripe_key" :value="__('Stripe Publishable Key')" />
<x-text-input id="stripe_key" name="stripe_key" type="text" class="mt-1 block w-full"
:value="old('stripe_key', config('services.stripe.key'))" />
<x-input-error class="mt-2" :messages="$errors->get('stripe_key')" />
</div>
<!-- PayPal Client ID -->
<div>
<x-input-label for="paypal_client_id" :value="__('PayPal Client ID')" />
<x-text-input id="paypal_client_id" name="paypal_client_id" type="text" class="mt-1 block w-full"
:value="old('paypal_client_id', config('services.paypal.client_id'))" />
<x-input-error class="mt-2" :messages="$errors->get('paypal_client_id')" />
</div>
</div>
</div>
<!-- Pulsanti -->
<div class="flex items-center justify-end space-x-4">
<x-secondary-button type="button" onclick="window.location.reload()">
{{ __('Ripristina') }}
</x-secondary-button>
<x-primary-button>
{{ __('Salva Impostazioni') }}
</x-primary-button>
</div>
</form>
@if ($errors->any())
<div class="mt-4 text-red-600 dark:text-red-400">
<ul>
@foreach ($errors->all() as $error)
<li>{{ $error }}</li>
@endforeach
</ul>
</div>
@endif
</div> </div>
<!-- Pulsante di esempio -->
<button class="mt-4 px-4 py-2 rounded text-white transition-all duration-300" style="background-color: {{ $settings['accent_color'] }};">
Pulsante di Esempio
</button>
</div> </div>
</div> </div>
</div> </div>
</x-app-layout> </div>
<script>
document.addEventListener('DOMContentLoaded', function() {
const form = document.getElementById('color-form');
const preview = document.getElementById('preview-container');
const sidebarPreview = document.getElementById('sidebar-preview');
const colorInputs = form.querySelectorAll('input[type="color"], input[type="checkbox"]');
const themeButtons = document.querySelectorAll('.theme-btn');
const resetBtn = document.getElementById('reset-btn');
// Applica dark mode se salvato
if (document.getElementById('dark_mode').checked) {
document.documentElement.classList.add('dark');
}
// Aggiorna anteprima in tempo reale
function updatePreview() {
const bgColor = document.getElementById('bg_color').value;
const textColor = document.getElementById('text_color').value;
const accentColor = document.getElementById('accent_color').value;
const sidebarBgColor = document.getElementById('sidebar_bg_color').value;
const sidebarTextColor = document.getElementById('sidebar_text_color').value;
const sidebarAccentColor = document.getElementById('sidebar_accent_color').value;
const darkMode = document.getElementById('dark_mode').checked;
preview.style.backgroundColor = bgColor;
preview.style.color = textColor;
sidebarPreview.style.backgroundColor = sidebarBgColor;
sidebarPreview.style.color = sidebarTextColor;
// Aggiorna pulsante e accent
const button = preview.querySelector('button');
button.style.backgroundColor = accentColor;
const accentDiv = sidebarPreview.querySelector('[style*="background-color"]');
if (accentDiv) {
accentDiv.style.backgroundColor = sidebarAccentColor;
}
// Aggiorna dark mode
if (darkMode) {
document.documentElement.classList.add('dark');
} else {
document.documentElement.classList.remove('dark');
}
}
// Event listeners per anteprima in tempo reale
colorInputs.forEach(input => {
input.addEventListener('input', updatePreview);
input.addEventListener('change', updatePreview);
});
// Temi predefiniti
themeButtons.forEach(btn => {
btn.addEventListener('click', async function() {
const theme = this.dataset.theme;
try {
const response = await fetch('{{ route("admin.impostazioni.theme") }}', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-CSRF-TOKEN': document.querySelector('meta[name="csrf-token"]').content
},
body: JSON.stringify({ theme: theme })
});
const data = await response.json();
if (data.success && data.settings) {
// Aggiorna i campi del form
Object.keys(data.settings).forEach(key => {
const input = document.getElementById(key);
if (input) input.value = data.settings[key];
});
updatePreview();
showMessage('Tema applicato con successo!', 'success');
// Ricarica la pagina per applicare completamente il tema
setTimeout(() => window.location.reload(), 1000);
}
} catch (error) {
showMessage('Errore nell\'applicazione del tema', 'error');
}
});
});
// Salvataggio del form
form.addEventListener('submit', async function(e) {
e.preventDefault();
const formData = new FormData(this);
try {
const response = await fetch('{{ route("admin.impostazioni.store") }}', {
method: 'POST',
body: formData
});
const data = await response.json();
if (data.success) {
showMessage(data.message, 'success');
// Ricarica la pagina per applicare le modifiche
setTimeout(() => window.location.reload(), 1000);
} else {
showMessage('Errore nel salvataggio delle impostazioni', 'error');
}
} catch (error) {
showMessage('Errore nel salvataggio delle impostazioni', 'error');
}
});
// Reset
resetBtn.addEventListener('click', function() {
if (confirm('Sei sicuro di voler ripristinare le impostazioni predefinite?')) {
themeButtons[0].click(); // Applica tema default
}
});
function showMessage(message, type) {
const messageContainer = document.getElementById('message-container');
const successDiv = document.getElementById('success-message');
const errorDiv = document.getElementById('error-message');
const successText = document.getElementById('success-text');
const errorText = document.getElementById('error-text');
// Nascondi tutti i messaggi
successDiv.classList.add('hidden');
errorDiv.classList.add('hidden');
if (type === 'success') {
successText.textContent = message;
successDiv.classList.remove('hidden');
} else {
errorText.textContent = message;
errorDiv.classList.remove('hidden');
}
messageContainer.classList.remove('hidden');
// Nascondi dopo 5 secondi
setTimeout(() => {
messageContainer.classList.add('hidden');
}, 5000);
}
// Anteprima iniziale
updatePreview();
});
</script>
@endsection

View File

@ -0,0 +1,144 @@
<x-app-layout>
<x-slot name="header">
<h2 class="font-semibold text-xl text-gray-800 dark:text-gray-200 leading-tight">
{{ __('Impostazioni') }}
</h2>
</x-slot>
<div class="py-12">
<div class="max-w-7xl mx-auto sm:px-6 lg:px-8">
<div class="bg-white dark:bg-gray-800 overflow-hidden shadow-sm sm:rounded-lg">
<div class="p-6 text-gray-900 dark:text-gray-100">
<div class="mb-6">
<h3 class="text-2xl font-bold text-gray-800 dark:text-gray-200">Configurazione Sistema</h3>
<p class="text-gray-600 dark:text-gray-400 mt-2">Gestisci le impostazioni generali dell'applicazione</p>
</div>
<form method="POST" action="{{ route('admin.impostazioni.store') }}" enctype="multipart/form-data">
@csrf
<!-- Sezione Applicazione -->
<div class="bg-blue-50 dark:bg-blue-900/20 p-6 rounded-lg mb-6">
<h4 class="text-lg font-medium text-gray-900 dark:text-gray-100 mb-4">Impostazioni Applicazione</h4>
<div class="grid grid-cols-1 md:grid-cols-2 gap-6">
<!-- Nome Applicazione -->
<div>
<x-input-label for="app_name" :value="__('Nome Applicazione')" />
<x-text-input id="app_name" name="app_name" type="text" class="mt-1 block w-full"
:value="old('app_name', config('app.name'))" />
<x-input-error class="mt-2" :messages="$errors->get('app_name')" />
</div>
<!-- URL Applicazione -->
<div>
<x-input-label for="app_url" :value="__('URL Applicazione')" />
<x-text-input id="app_url" name="app_url" type="url" class="mt-1 block w-full"
:value="old('app_url', config('app.url'))" />
<x-input-error class="mt-2" :messages="$errors->get('app_url')" />
</div>
</div>
</div>
<!-- Sezione Branding -->
<div class="bg-green-50 dark:bg-green-900/20 p-6 rounded-lg mb-6">
<h4 class="text-lg font-medium text-gray-900 dark:text-gray-100 mb-4">Branding</h4>
<div class="grid grid-cols-1 md:grid-cols-2 gap-6">
<!-- Logo Applicazione -->
<div>
<x-input-label for="app_logo" :value="__('Logo Applicazione')" />
<input type="file" id="app_logo" name="app_logo" accept="image/*"
class="mt-1 block w-full border-gray-300 dark:border-gray-700 dark:bg-gray-900 dark:text-gray-300 focus:border-indigo-500 dark:focus:border-indigo-600 focus:ring-indigo-500 dark:focus:ring-indigo-600 rounded-md shadow-sm" />
<x-input-error class="mt-2" :messages="$errors->get('app_logo')" />
<p class="mt-1 text-sm text-gray-500 dark:text-gray-400">Formati supportati: JPG, PNG, SVG (max 2MB)</p>
</div>
<!-- Logo Dashboard -->
<div>
<x-input-label for="dashboard_logo" :value="__('Logo Dashboard')" />
<input type="file" id="dashboard_logo" name="dashboard_logo" accept="image/*"
class="mt-1 block w-full border-gray-300 dark:border-gray-700 dark:bg-gray-900 dark:text-gray-300 focus:border-indigo-500 dark:focus:border-indigo-600 focus:ring-indigo-500 dark:focus:ring-indigo-600 rounded-md shadow-sm" />
<x-input-error class="mt-2" :messages="$errors->get('dashboard_logo')" />
</div>
<!-- Favicon -->
<div>
<x-input-label for="favicon" :value="__('Favicon')" />
<input type="file" id="favicon" name="favicon" accept="image/*"
class="mt-1 block w-full border-gray-300 dark:border-gray-700 dark:bg-gray-900 dark:text-gray-300 focus:border-indigo-500 dark:focus:border-indigo-600 focus:ring-indigo-500 dark:focus:ring-indigo-600 rounded-md shadow-sm" />
<x-input-error class="mt-2" :messages="$errors->get('favicon')" />
<p class="mt-1 text-sm text-gray-500 dark:text-gray-400">Formato ICO o PNG 32x32px</p>
</div>
</div>
</div>
<!-- Sezione Email -->
<div class="bg-yellow-50 dark:bg-yellow-900/20 p-6 rounded-lg mb-6">
<h4 class="text-lg font-medium text-gray-900 dark:text-gray-100 mb-4">Configurazione Email</h4>
<div class="grid grid-cols-1 md:grid-cols-2 gap-6">
<!-- Email Mittente -->
<div>
<x-input-label for="mail_from_address" :value="__('Email Mittente')" />
<x-text-input id="mail_from_address" name="mail_from_address" type="email" class="mt-1 block w-full"
:value="old('mail_from_address', config('mail.from.address'))" />
<x-input-error class="mt-2" :messages="$errors->get('mail_from_address')" />
</div>
<!-- Nome Mittente -->
<div>
<x-input-label for="mail_from_name" :value="__('Nome Mittente')" />
<x-text-input id="mail_from_name" name="mail_from_name" type="text" class="mt-1 block w-full"
:value="old('mail_from_name', config('mail.from.name'))" />
<x-input-error class="mt-2" :messages="$errors->get('mail_from_name')" />
</div>
</div>
</div>
<!-- Sezione Pagamenti -->
<div class="bg-purple-50 dark:bg-purple-900/20 p-6 rounded-lg mb-6">
<h4 class="text-lg font-medium text-gray-900 dark:text-gray-100 mb-4">Configurazione Pagamenti</h4>
<div class="grid grid-cols-1 md:grid-cols-2 gap-6">
<!-- Stripe Key -->
<div>
<x-input-label for="stripe_key" :value="__('Stripe Publishable Key')" />
<x-text-input id="stripe_key" name="stripe_key" type="text" class="mt-1 block w-full"
:value="old('stripe_key', config('services.stripe.key'))" />
<x-input-error class="mt-2" :messages="$errors->get('stripe_key')" />
</div>
<!-- PayPal Client ID -->
<div>
<x-input-label for="paypal_client_id" :value="__('PayPal Client ID')" />
<x-text-input id="paypal_client_id" name="paypal_client_id" type="text" class="mt-1 block w-full"
:value="old('paypal_client_id', config('services.paypal.client_id'))" />
<x-input-error class="mt-2" :messages="$errors->get('paypal_client_id')" />
</div>
</div>
</div>
<!-- Pulsanti -->
<div class="flex items-center justify-end space-x-4">
<x-secondary-button type="button" onclick="window.location.reload()">
{{ __('Ripristina') }}
</x-secondary-button>
<x-primary-button>
{{ __('Salva Impostazioni') }}
</x-primary-button>
</div>
</form>
@if ($errors->any())
<div class="mt-4 text-red-600 dark:text-red-400">
<ul>
@foreach ($errors->all() as $error)
<li>{{ $error }}</li>
@endforeach
</ul>
</div>
@endif
</div>
</div>
</div>
</div>
</x-app-layout>

View File

@ -1,4 +1,4 @@
<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"> <nav class="h-full flex flex-col bg-yellow-300 dark:bg-gray-800 border-r-4 border-indigo-500 dark:border-yellow-500 py-6 px-2 w-full shadow-xl z-50">
@php @php
$userRoles = auth()->check() ? auth()->user()->getRoleNames()->toArray() : []; $userRoles = auth()->check() ? auth()->user()->getRoleNames()->toArray() : [];
$panelPrefix = ''; $panelPrefix = '';
@ -40,9 +40,9 @@
], ],
]; ];
@endphp @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="px-2 pt-3 pb-2 bg-yellow-200 dark:bg-gray-700 border-b-2 border-indigo-500 dark:border-yellow-500 sticky top-0 z-20">
<div class="flex flex-col items-center"> <div class="flex flex-col items-center">
<span class="text-xs text-gray-700 mb-1">{{ $annoAttivo }}/{{ $gestione }}</span> <span class="text-xs text-gray-700 dark:text-gray-300 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 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"> <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"> <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">
@ -58,7 +58,7 @@
{{ $stabileAttivo }} {{ $stabileAttivo }}
</button> </button>
</div> </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"> <button id="toggle-darkmode" class="mt-2 px-2 py-1 rounded bg-gray-800 dark:bg-yellow-600 text-yellow-300 dark:text-gray-900 hover:bg-gray-900 dark:hover:bg-yellow-700 text-xs flex items-center gap-1" title="Attiva/disattiva modalità scura">
<!-- Tabler icon moon --> <!-- 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> <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> <span>Dark</span>
@ -69,8 +69,8 @@
@foreach($mainMenu as $item) @foreach($mainMenu as $item)
@if(array_intersect($item['roles'], $userRoles)) @if(array_intersect($item['roles'], $userRoles))
@if(Route::has($item['route'])) @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"> <a href="{{ route($item['route']) }}" class="flex items-center gap-3 px-4 py-2 rounded-lg text-gray-700 dark:text-gray-200 hover:bg-indigo-100 dark:hover:bg-gray-700 hover:text-indigo-700 dark:hover:text-yellow-300 transition group">
<i class="{{ $item['icon'] }} text-lg group-hover:text-indigo-700"></i> <i class="{{ $item['icon'] }} text-lg group-hover:text-indigo-700 dark:group-hover:text-yellow-300"></i>
<span class="font-medium">{{ $item['label'] }}</span> <span class="font-medium">{{ $item['label'] }}</span>
</a> </a>
@endif @endif
@ -78,7 +78,7 @@
@endforeach @endforeach
</div> </div>
<div class="mt-auto pt-4"> <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"> <button id="submenu-toggle" class="w-full flex items-center justify-center text-gray-400 dark:text-gray-500 hover:text-indigo-600 dark:hover:text-yellow-400 transition" title="Mostra/Nascondi menu">
<i class="fa-solid fa-bars text-xl"></i> <i class="fa-solid fa-bars text-xl"></i>
</button> </button>
</div> </div>
@ -109,12 +109,12 @@
</style> </style>
<!-- Modale ricerca stabile --> <!-- 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 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"> <div class="bg-white dark:bg-gray-800 rounded shadow-lg p-6 w-full max-w-md">
<h3 class="text-lg font-bold mb-2">Cerca stabile</h3> <h3 class="text-lg font-bold mb-2 text-gray-900 dark:text-gray-100">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..."> <input type="text" id="input-stabile" class="w-full border dark:border-gray-600 rounded px-3 py-2 mb-2 bg-white dark:bg-gray-700 text-gray-900 dark:text-gray-100" placeholder="Digita il nome dello stabile...">
<ul id="result-stabile" class="max-h-40 overflow-y-auto"></ul> <ul id="result-stabile" class="max-h-40 overflow-y-auto text-gray-900 dark:text-gray-100"></ul>
<div class="flex justify-end mt-2"> <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> <button id="close-modal-stabile" class="px-3 py-1 rounded bg-gray-300 dark:bg-gray-600 hover:bg-gray-400 dark:hover:bg-gray-500 text-gray-900 dark:text-gray-100">Chiudi</button>
</div> </div>
</div> </div>
</div> </div>
@ -150,7 +150,7 @@ document.getElementById('close-modal-stabile').onclick = function() {
document.getElementById('input-stabile').oninput = function(e) { document.getElementById('input-stabile').oninput = function(e) {
const val = e.target.value.toLowerCase(); const val = e.target.value.toLowerCase();
const results = stabili.filter(s => s.toLowerCase().includes(val)); 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(''); document.getElementById('result-stabile').innerHTML = results.map(s => `<li class='py-1 px-2 hover:bg-yellow-200 dark:hover:bg-gray-600 cursor-pointer' onclick='selectStabile("${s}")'>${s}</li>`).join('');
}; };
window.selectStabile = function(nome) { window.selectStabile = function(nome) {
document.getElementById('current-stabile').textContent = nome; document.getElementById('current-stabile').textContent = nome;
@ -169,7 +169,7 @@ window.selectStabile = function(nome) {
}; };
updateStabile(); updateStabile();
</script> </script>
<footer class="w-full bg-gray-100 border-t border-gray-300 text-xs text-gray-600 text-center py-2 mt-4"> <footer class="w-full bg-gray-100 dark:bg-gray-900 border-t border-gray-300 dark:border-gray-600 text-xs text-gray-600 dark:text-gray-400 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> <span>NetGesCon &copy; {{ date('Y') }} - <a href="https://github.com/netgescon" class="text-indigo-600 dark:text-yellow-400 hover:underline">netgescon.github.io</a> - v0.7.0-dev</span>
</footer> </footer>
</nav> </nav>

View File

@ -2,7 +2,6 @@
<x-slot name="header"> <x-slot name="header">
<h2 class="font-semibold text-xl text-gray-800 dark:text-gray-200 leading-tight"> <h2 class="font-semibold text-xl text-gray-800 dark:text-gray-200 leading-tight">
{{ __('Dashboard') }} {{ __('Dashboard') }}
</h2> </h2>
</x-slot> </x-slot>
@ -10,18 +9,11 @@
<div class="max-w-7xl mx-auto sm:px-6 lg:px-8"> <div class="max-w-7xl mx-auto sm:px-6 lg:px-8">
<div class="bg-white dark:bg-gray-800 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 text-gray-900 dark:text-gray-100"> <div class="p-6 text-gray-900 dark:text-gray-100">
{{-- Questa dashboard funge da reindirizzamento centrale in base al ruolo --}} {{-- Dashboard generica, nessun redirect JS --}}
@auth @auth
@if(Auth::user()->hasRole('super-admin')) <p>{{ __("Sei autenticato come: ") }} <strong>{{ Auth::user()->email }}</strong></p>
<script>window.location = "{{ route('superadmin.dashboard') }}";</script> <p>{{ __("Ruoli attivi: ") }} <strong>{{ Auth::user()->getRoleNames()->implode(', ') }}</strong></p>
@elseif(Auth::user()->hasRole(['admin', 'amministratore'])) <p>{{ __("Questa è la dashboard generica. Se vedi questa schermata, il tuo ruolo non ha una dashboard dedicata.") }}</p>
<script>window.location = "{{ route('admin.dashboard') }}";</script>
@elseif(Auth::user()->hasRole('condomino'))
<script>window.location = "{{ route('condomino.dashboard') }}";</script>
@else
<p>{{ __("You're logged in!") }}</p>
<p>Nessun pannello specifico per il tuo ruolo.</p>
@endif
@else @else
<p>{{ __("You are not logged in.") }}</p> <p>{{ __("You are not logged in.") }}</p>
@endauth @endauth

View File

@ -1,5 +1,5 @@
<!DOCTYPE html> <!DOCTYPE html>
<html lang="{{ str_replace('_', '-', app()->getLocale()) }}"> <html lang="{{ str_replace('_', '-', app()->getLocale()) }}" class="{{ auth()->check() && userSetting('dark_mode', 'false') === 'true' ? 'dark' : '' }}">
<head> <head>
<meta charset="utf-8"> <meta charset="utf-8">
@ -12,26 +12,59 @@
<link rel="preconnect" href="https://fonts.bunny.net"> <link rel="preconnect" href="https://fonts.bunny.net">
<link href="https://fonts.bunny.net/css?family=figtree:400,500,600&display=swap" rel="stylesheet" /> <link href="https://fonts.bunny.net/css?family=figtree:400,500,600&display=swap" rel="stylesheet" />
<!-- Custom CSS for dark mode -->
<link href="{{ asset('css/dark-mode.css') }}" rel="stylesheet">
<!-- Scripts --> <!-- Scripts -->
@vite(['resources/css/app.css', 'resources/js/app.js']) @vite(['resources/css/app.css', 'resources/js/app.js'])
@livewireStyles @livewireStyles
@php
// Impostazioni utente per colori personalizzati
$userBgColor = auth()->check() ? userSetting('bg_color', '#ffffff') : '#ffffff';
$userTextColor = auth()->check() ? userSetting('text_color', '#1e293b') : '#1e293b';
$userAccentColor = auth()->check() ? userSetting('accent_color', '#6366f1') : '#6366f1';
$userSidebarBg = auth()->check() ? userSetting('sidebar_bg_color', '#fde047') : '#fde047';
$userSidebarText = auth()->check() ? userSetting('sidebar_text_color', '#1e293b') : '#1e293b';
$userSidebarAccent = auth()->check() ? userSetting('sidebar_accent_color', '#6366f1') : '#6366f1';
@endphp
<style>
:root {
--user-bg-color: {{ $userBgColor }};
--user-text-color: {{ $userTextColor }};
--user-accent-color: {{ $userAccentColor }};
--user-sidebar-bg: {{ $userSidebarBg }};
--user-sidebar-text: {{ $userSidebarText }};
--user-sidebar-accent: {{ $userSidebarAccent }};
}
/* Applica automaticamente i colori personalizzati */
.bg-white { background-color: var(--user-bg-color) !important; }
.text-gray-900 { color: var(--user-text-color) !important; }
.bg-indigo-600 { background-color: var(--user-accent-color) !important; }
</style>
</head> </head>
<body class="font-sans antialiased"> <body class="font-sans antialiased">
<div class="min-h-screen bg-gray-100 dark:bg-gray-900"> <div class="min-h-screen bg-gray-100 dark:bg-gray-900 flex">
@include('layouts.navigation') @include('components.menu.sidebar')
<div class="flex-1 flex flex-col">
@include('layouts.navigation')
<!-- Page Heading --> <!-- Page Heading -->
@isset($header) @isset($header)
<header class="bg-white dark:bg-gray-800 shadow"> <header class="bg-white dark:bg-gray-800 shadow">
<div class="max-w-7xl mx-auto py-6 px-4 sm:px-6 lg:px-8"> <div class="max-w-7xl mx-auto py-6 px-4 sm:px-6 lg:px-8">
{{ $header }} {{ $header }}
</div> </div>
</header> </header>
@endisset @endisset
<!-- Page Content -->
<main> <!-- Page Content -->
{{ $slot }} <main>
</main> {{ $slot }}
</main>
</div>
</div> </div>
@livewireScripts @livewireScripts
</body> </body>

View File

@ -0,0 +1,291 @@
@extends('layouts.app')
@section('content')
<div class="container mx-auto px-6 py-8 bg-white dark:bg-gray-900 text-gray-900 dark:text-gray-100">
<div class="mb-8">
<h1 class="text-3xl font-bold text-gray-900 dark:text-gray-100">Impostazioni Personalizzazione</h1>
<p class="text-gray-600 dark:text-gray-400 mt-2">Personalizza i colori e il tema dell'interfaccia</p>
</div>
<!-- Messaggi di successo/errore -->
<div id="message-container" class="hidden mb-6">
<div id="success-message" class="bg-green-100 dark:bg-green-900 border border-green-400 dark:border-green-600 text-green-700 dark:text-green-300 px-4 py-3 rounded mb-4 hidden">
<span id="success-text"></span>
</div>
<div id="error-message" class="bg-red-100 dark:bg-red-900 border border-red-400 dark:border-red-600 text-red-700 dark:text-red-300 px-4 py-3 rounded mb-4 hidden">
<span id="error-text"></span>
</div>
</div>
<div class="grid grid-cols-1 lg:grid-cols-2 gap-8">
<!-- Pannello di controllo colori -->
<div class="bg-gray-50 dark:bg-gray-800 p-6 rounded-lg shadow">
<h2 class="text-xl font-semibold mb-4 text-gray-900 dark:text-gray-100">Personalizzazione Colori</h2>
<form id="color-form" class="space-y-4">
@csrf
<!-- Dark Mode Toggle -->
<div class="flex items-center justify-between">
<label class="text-sm font-medium text-gray-700 dark:text-gray-300">Modalità Scura</label>
<input type="checkbox" id="dark_mode" name="dark_mode" value="true"
{{ $settings['dark_mode'] === 'true' ? 'checked' : '' }}
class="w-4 h-4 text-indigo-600 bg-gray-100 border-gray-300 rounded focus:ring-indigo-500 dark:focus:ring-indigo-600 dark:ring-offset-gray-800 focus:ring-2 dark:bg-gray-700 dark:border-gray-600">
</div>
<!-- Colori Principale -->
<h3 class="text-lg font-medium text-gray-900 dark:text-gray-100 mt-6">Tema Principale</h3>
<div class="grid grid-cols-3 gap-4">
<div>
<label class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-1">Sfondo</label>
<input type="color" id="bg_color" name="bg_color" value="{{ $settings['bg_color'] }}"
class="w-full h-10 rounded border border-gray-300 dark:border-gray-600">
</div>
<div>
<label class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-1">Testo</label>
<input type="color" id="text_color" name="text_color" value="{{ $settings['text_color'] }}"
class="w-full h-10 rounded border border-gray-300 dark:border-gray-600">
</div>
<div>
<label class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-1">Accento</label>
<input type="color" id="accent_color" name="accent_color" value="{{ $settings['accent_color'] }}"
class="w-full h-10 rounded border border-gray-300 dark:border-gray-600">
</div>
</div>
<!-- Colori Sidebar -->
<h3 class="text-lg font-medium text-gray-900 dark:text-gray-100 mt-6">Sidebar</h3>
<div class="grid grid-cols-3 gap-4">
<div>
<label class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-1">Sfondo Sidebar</label>
<input type="color" id="sidebar_bg_color" name="sidebar_bg_color" value="{{ $settings['sidebar_bg_color'] }}"
class="w-full h-10 rounded border border-gray-300 dark:border-gray-600">
</div>
<div>
<label class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-1">Testo Sidebar</label>
<input type="color" id="sidebar_text_color" name="sidebar_text_color" value="{{ $settings['sidebar_text_color'] }}"
class="w-full h-10 rounded border border-gray-300 dark:border-gray-600">
</div>
<div>
<label class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-1">Accento Sidebar</label>
<input type="color" id="sidebar_accent_color" name="sidebar_accent_color" value="{{ $settings['sidebar_accent_color'] }}"
class="w-full h-10 rounded border border-gray-300 dark:border-gray-600">
</div>
</div>
<!-- Temi Predefiniti -->
<h3 class="text-lg font-medium text-gray-900 dark:text-gray-100 mt-6">Temi Predefiniti</h3>
<div class="flex flex-wrap gap-2">
<button type="button" class="theme-btn px-4 py-2 rounded bg-blue-500 text-white hover:bg-blue-600" data-theme="default">Default</button>
<button type="button" class="theme-btn px-4 py-2 rounded bg-gray-800 text-white hover:bg-gray-900" data-theme="dark">Dark</button>
<button type="button" class="theme-btn px-4 py-2 rounded bg-blue-600 text-white hover:bg-blue-700" data-theme="ocean">Ocean</button>
</div>
<!-- Pulsanti di controllo -->
<div class="flex gap-4 mt-6">
<button type="submit" class="bg-green-600 hover:bg-green-700 text-white font-bold py-2 px-4 rounded">
Salva Impostazioni
</button>
<button type="button" id="reset-btn" class="bg-gray-600 hover:bg-gray-700 text-white font-bold py-2 px-4 rounded">
Reset
</button>
</div>
</form>
</div>
<!-- Anteprima -->
<div class="bg-gray-50 dark:bg-gray-800 p-6 rounded-lg shadow">
<h2 class="text-xl font-semibold mb-4 text-gray-900 dark:text-gray-100">Anteprima</h2>
<div id="preview-container" class="border rounded-lg p-4 transition-all duration-300" style="background-color: {{ $settings['bg_color'] }}; color: {{ $settings['text_color'] }};">
<div class="mb-4">
<h3 class="text-lg font-bold">Dashboard NetGesCon</h3>
<p class="text-sm opacity-75">Benvenuto nel pannello di amministrazione</p>
</div>
<!-- Simulazione sidebar -->
<div class="flex gap-4">
<div id="sidebar-preview" class="w-32 h-40 rounded p-2 transition-all duration-300" style="background-color: {{ $settings['sidebar_bg_color'] }}; color: {{ $settings['sidebar_text_color'] }};">
<div class="text-xs font-bold mb-2">Menu</div>
<div class="space-y-1">
<div class="text-xs p-1 rounded" style="background-color: {{ $settings['sidebar_accent_color'] }}; opacity: 0.3;">Dashboard</div>
<div class="text-xs p-1">Stabili</div>
<div class="text-xs p-1">Utenti</div>
</div>
</div>
<!-- Contenuto principale -->
<div class="flex-1">
<div class="mb-2">
<div class="w-full h-6 rounded" style="background-color: {{ $settings['accent_color'] }}; opacity: 0.2;"></div>
</div>
<div class="space-y-2">
<div class="w-3/4 h-4 rounded" style="background-color: {{ $settings['text_color'] }}; opacity: 0.2;"></div>
<div class="w-1/2 h-4 rounded" style="background-color: {{ $settings['text_color'] }}; opacity: 0.1;"></div>
</div>
</div>
</div>
<!-- Pulsante di esempio -->
<button class="mt-4 px-4 py-2 rounded text-white transition-all duration-300" style="background-color: {{ $settings['accent_color'] }};">
Pulsante di Esempio
</button>
</div>
</div>
</div>
</div>
<script>
document.addEventListener('DOMContentLoaded', function() {
const form = document.getElementById('color-form');
const preview = document.getElementById('preview-container');
const sidebarPreview = document.getElementById('sidebar-preview');
const colorInputs = form.querySelectorAll('input[type="color"], input[type="checkbox"]');
const themeButtons = document.querySelectorAll('.theme-btn');
const resetBtn = document.getElementById('reset-btn');
// Applica dark mode se salvato
if (document.getElementById('dark_mode').checked) {
document.documentElement.classList.add('dark');
}
// Aggiorna anteprima in tempo reale
function updatePreview() {
const bgColor = document.getElementById('bg_color').value;
const textColor = document.getElementById('text_color').value;
const accentColor = document.getElementById('accent_color').value;
const sidebarBgColor = document.getElementById('sidebar_bg_color').value;
const sidebarTextColor = document.getElementById('sidebar_text_color').value;
const sidebarAccentColor = document.getElementById('sidebar_accent_color').value;
const darkMode = document.getElementById('dark_mode').checked;
preview.style.backgroundColor = bgColor;
preview.style.color = textColor;
sidebarPreview.style.backgroundColor = sidebarBgColor;
sidebarPreview.style.color = sidebarTextColor;
// Aggiorna pulsante e accent
const button = preview.querySelector('button');
button.style.backgroundColor = accentColor;
const accentDiv = sidebarPreview.querySelector('[style*="background-color"]');
if (accentDiv) {
accentDiv.style.backgroundColor = sidebarAccentColor;
}
// Aggiorna dark mode
if (darkMode) {
document.documentElement.classList.add('dark');
} else {
document.documentElement.classList.remove('dark');
}
}
// Event listeners per anteprima in tempo reale
colorInputs.forEach(input => {
input.addEventListener('input', updatePreview);
input.addEventListener('change', updatePreview);
});
// Temi predefiniti
themeButtons.forEach(btn => {
btn.addEventListener('click', async function() {
const theme = this.dataset.theme;
try {
const response = await fetch('{{ route("superadmin.impostazioni.theme") }}', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-CSRF-TOKEN': document.querySelector('meta[name="csrf-token"]').content
},
body: JSON.stringify({ theme: theme })
});
const data = await response.json();
if (data.success && data.settings) {
// Aggiorna i campi del form
Object.keys(data.settings).forEach(key => {
const input = document.getElementById(key);
if (input) input.value = data.settings[key];
});
updatePreview();
showMessage('Tema applicato con successo!', 'success');
// Ricarica la pagina per applicare completamente il tema
setTimeout(() => window.location.reload(), 1000);
}
} catch (error) {
showMessage('Errore nell\'applicazione del tema', 'error');
}
});
});
// Salvataggio del form
form.addEventListener('submit', async function(e) {
e.preventDefault();
const formData = new FormData(this);
try {
const response = await fetch('{{ route("superadmin.impostazioni.store") }}', {
method: 'POST',
body: formData
});
const data = await response.json();
if (data.success) {
showMessage(data.message, 'success');
// Ricarica la pagina per applicare le modifiche
setTimeout(() => window.location.reload(), 1000);
} else {
showMessage('Errore nel salvataggio delle impostazioni', 'error');
}
} catch (error) {
showMessage('Errore nel salvataggio delle impostazioni', 'error');
}
});
// Reset
resetBtn.addEventListener('click', function() {
if (confirm('Sei sicuro di voler ripristinare le impostazioni predefinite?')) {
themeButtons[0].click(); // Applica tema default
}
});
function showMessage(message, type) {
const messageContainer = document.getElementById('message-container');
const successDiv = document.getElementById('success-message');
const errorDiv = document.getElementById('error-message');
const successText = document.getElementById('success-text');
const errorText = document.getElementById('error-text');
// Nascondi tutti i messaggi
successDiv.classList.add('hidden');
errorDiv.classList.add('hidden');
if (type === 'success') {
successText.textContent = message;
successDiv.classList.remove('hidden');
} else {
errorText.textContent = message;
errorDiv.classList.remove('hidden');
}
messageContainer.classList.remove('hidden');
// Nascondi dopo 5 secondi
setTimeout(() => {
messageContainer.classList.add('hidden');
}, 5000);
}
// Anteprima iniziale
updatePreview();
});
</script>
@endsection

View File

@ -64,6 +64,7 @@ Route::middleware(['auth', 'verified'])->group(function () {
// Impostazioni Sistema // Impostazioni Sistema
Route::get('impostazioni', [\App\Http\Controllers\SuperAdmin\ImpostazioniController::class, 'index'])->name('impostazioni.index'); 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'); Route::post('impostazioni', [\App\Http\Controllers\SuperAdmin\ImpostazioniController::class, 'store'])->name('impostazioni.store');
Route::post('impostazioni/theme', [\App\Http\Controllers\SuperAdmin\ImpostazioniController::class, 'theme'])->name('impostazioni.theme');
// Gestione Amministratori // Gestione Amministratori
Route::resource('amministratori', SuperAdminAmministratoreController::class) Route::resource('amministratori', SuperAdminAmministratoreController::class)
->except(['show']) ->except(['show'])
@ -151,6 +152,7 @@ Route::middleware(['auth', 'verified'])->group(function () {
// Impostazioni e API Tokens // Impostazioni e API Tokens
Route::get('impostazioni', [ImpostazioniController::class, 'index'])->name('impostazioni.index'); Route::get('impostazioni', [ImpostazioniController::class, 'index'])->name('impostazioni.index');
Route::post('impostazioni', [ImpostazioniController::class, 'store'])->name('impostazioni.store'); Route::post('impostazioni', [ImpostazioniController::class, 'store'])->name('impostazioni.store');
Route::post('impostazioni/theme', [ImpostazioniController::class, 'theme'])->name('impostazioni.theme');
Route::get('api-tokens', [ApiTokenController::class, 'index'])->name('api-tokens.index'); Route::get('api-tokens', [ApiTokenController::class, 'index'])->name('api-tokens.index');
Route::post('api-tokens', [ApiTokenController::class, 'store'])->name('api-tokens.store'); Route::post('api-tokens', [ApiTokenController::class, 'store'])->name('api-tokens.store');
Route::delete('api-tokens/{token_id}', [ApiTokenController::class, 'destroy'])->name('api-tokens.destroy'); Route::delete('api-tokens/{token_id}', [ApiTokenController::class, 'destroy'])->name('api-tokens.destroy');

21
tailwind.config.js.backup Normal file
View File

@ -0,0 +1,21 @@
import defaultTheme from 'tailwindcss/defaultTheme';
import forms from '@tailwindcss/forms';
/** @type {import('tailwindcss').Config} */
export default {
content: [
'./vendor/laravel/framework/src/Illuminate/Pagination/resources/views/*.blade.php',
'./storage/framework/views/*.php',
'./resources/views/**/*.blade.php',
],
theme: {
extend: {
fontFamily: {
sans: ['Figtree', ...defaultTheme.fontFamily.sans],
},
},
},
plugins: [forms],
};