export calendar - modifica impostazioni
This commit is contained in:
@@ -1 +1 @@
|
||||
{"version":2,"defects":{"Tests\\Feature\\ExampleTest::test_the_application_returns_a_successful_response":7,"Tests\\Feature\\CalendarEventsTest::test_generate_settimanale_events_produces_weekly_occurrences":7},"times":{"Tests\\Unit\\ExampleTest::test_that_true_is_true":0,"Tests\\Feature\\ExampleTest::test_the_application_returns_a_successful_response":0.111,"Tests\\Feature\\CalendarEventsTest::test_find_first_sunday_of_june_2026":0.015,"Tests\\Feature\\CalendarEventsTest::test_find_second_saturday_of_june_2026":0.001,"Tests\\Feature\\CalendarEventsTest::test_find_fifth_sunday_returns_null_when_not_in_month":0.001,"Tests\\Feature\\CalendarEventsTest::test_generate_mensile_events_for_all_months":0.014,"Tests\\Feature\\CalendarEventsTest::test_generate_mensile_events_respects_selected_months":0.002,"Tests\\Feature\\CalendarEventsTest::test_generate_annuale_events_produces_correct_dates":0.002,"Tests\\Feature\\CalendarEventsTest::test_generate_settimanale_events_produces_weekly_occurrences":0.004}}
|
||||
{"version":2,"defects":{"Tests\\Feature\\ExampleTest::test_the_application_returns_a_successful_response":7,"Tests\\Feature\\CalendarEventsTest::test_generate_settimanale_events_produces_weekly_occurrences":7},"times":{"Tests\\Unit\\ExampleTest::test_that_true_is_true":0,"Tests\\Feature\\ExampleTest::test_the_application_returns_a_successful_response":0.111,"Tests\\Feature\\CalendarEventsTest::test_find_first_sunday_of_june_2026":0.034,"Tests\\Feature\\CalendarEventsTest::test_find_second_saturday_of_june_2026":0.001,"Tests\\Feature\\CalendarEventsTest::test_find_fifth_sunday_returns_null_when_not_in_month":0.001,"Tests\\Feature\\CalendarEventsTest::test_generate_mensile_events_for_all_months":0.025,"Tests\\Feature\\CalendarEventsTest::test_generate_mensile_events_respects_selected_months":0.001,"Tests\\Feature\\CalendarEventsTest::test_generate_annuale_events_produces_correct_dates":0.001,"Tests\\Feature\\CalendarEventsTest::test_generate_settimanale_events_produces_weekly_occurrences":0.004}}
|
||||
@@ -548,4 +548,94 @@ php artisan route:list --name=email
|
||||
- `resources/views/eventi/show.blade.php`
|
||||
- `resources/views/eventi/index.blade.php`
|
||||
|
||||
(Last updated: 26 Maggio 2026 - Event Types system)
|
||||
### 26 Maggio 2026 - ACL Fixes & Form Modules Sync
|
||||
- **Problema**: Form admin utenti (create/edit) aveva lista moduli hardcoded e mancante di `'report'`; user 4 (fbarachino) non aveva permesso `report` né `is_admin`
|
||||
- **Fix form edit/create**: Sostituito `@foreach(['individui', 'gruppi', 'eventi', 'documenti', 'mailing', 'viste'] as $module)` con `@foreach(\App\Models\User::MODULES as $module)` in entrambe le view (`edit.blade.php:76`, `create.blade.php:66`) — ora la lista è sempre allineata alla costante `User::MODULES`
|
||||
- **Fix user 4**:
|
||||
- Aggiunto `'report' => 2` alle permissions (full access)
|
||||
- Settato `is_admin = true` → `isSuperAdmin()` = true, bypass totale ACL
|
||||
- `canAccess('report')` = true, tutti i menu sidebar ora visibili
|
||||
- **Nuovo comando**: `app/Console/Commands/SyncUserPermissions.php` — `php artisan users:sync-permissions`
|
||||
- Itera tutti gli utenti (inclusi superadmin) e aggiunge con default 0 ogni modulo mancante in `User::MODULES`
|
||||
- Previene discrepanze future tra permissions salvate e MODULES costante
|
||||
- **Files modificati**:
|
||||
- `resources/views/admin/utenti/edit.blade.php`: linea 76, hardcoded → `User::MODULES`
|
||||
- `resources/views/admin/utenti/create.blade.php`: linea 66, hardcoded → `User::MODULES`
|
||||
- **File creato**:
|
||||
- `app/Console/Commands/SyncUserPermissions.php`: nuovo comando artisan
|
||||
- **Stato sidebar user 4**: `isSuperAdmin()` = true → tutti i menu visibili, `canAccess('report')` = true
|
||||
|
||||
### 26 Maggio 2026 - ACL 'settings' Module (Impostazioni/Admin Permission)
|
||||
- **Problema**: Non esisteva un permesso ACL specifico per accedere al menu Impostazioni e Admin. L'accesso era legato esclusivamente a `isSuperAdmin()` bypass totale. Inoltre l'`ImpostazioniController` usava `authorizeWrite('viste')` come piggyback scorretto.
|
||||
- **Soluzione**: Nuovo modulo `settings` separato, slegato da `viste` e da `isSuperAdmin()`.
|
||||
- **Modifiche**:
|
||||
- `User.php`: aggiunto `'settings'` a `MODULES` (costante linea 36)
|
||||
- `adminlte.blade.php` sidebar: `@if(Auth::user()->isSuperAdmin())` → `@if(Auth::user()->canManage('settings'))` per menu Impostazioni e Admin (linee 205-226)
|
||||
- `AdminOnly.php` middleware: `isSuperAdmin()` → `isSuperAdmin() || canManage('settings')` — permette accesso a `/admin/*` anche a non-superadmin con permesso settings:2
|
||||
- `ImpostazioniController.php`: rimpiazzate tutte le 16 occorrenze di `authorizeWrite('viste')` e `authorizeDelete('viste')` con `'settings'`
|
||||
- `EmailSettingsController.php`: aggiunto `$this->authorizeWrite('settings')` in tutti i 5 metodi pubblici (index, save, testConnection, testSmtp, syncNow) — prima non avevano alcuna autorizzazione esplicita
|
||||
- `SyncUserPermissions.php` aggiornato: ora include anche superadmin nella sync (per consistenza dati)
|
||||
- Eseguito `php artisan users:sync-permissions` → user 1 e 4 aggiornati con `settings: 0`
|
||||
- **Comportamento risultante**:
|
||||
- `isSuperAdmin()` bypassa sempre tutto (base Controller.php)
|
||||
- Utente normale con `settings:2` (Completo) → vede menu Impostazioni+Admin, può operare
|
||||
- Utente normale con `settings:0` → menu nascosto, 403 su accesso diretto URL
|
||||
- `settings:1` (Lettura) non basta per operazioni di scrittura (sidebar non mostra il menu perché richiede `canManage`)
|
||||
- **Files modificati**:
|
||||
- `app/Models/User.php`: MODULES + 'settings'
|
||||
- `resources/views/layouts/adminlte.blade.php`: isSuperAdmin → canManage('settings')
|
||||
- `app/Http/Middleware/AdminOnly.php`: isSuperAdmin || canManage('settings')
|
||||
- `app/Http/Controllers/ImpostazioniController.php`: viste → settings in 16 authorize calls
|
||||
- `app/Http/Controllers/Admin/EmailSettingsController.php`: aggiunte 5 authorizeWrite('settings')
|
||||
- `app/Console/Commands/SyncUserPermissions.php`: superadmin inclusi nella sync
|
||||
|
||||
### 26 Maggio 2026 - Fix Salvataggio is_admin (Checkbox Superadmin)
|
||||
- **Problema**: Non era possibile togliere il flag "Superadmin (bypass ACL)" dall'interfaccia admin. Il flag rimaneva sempre true dopo il salvataggio.
|
||||
- **Cause** (3 bug concatenati):
|
||||
1. **`url()` assoluto** nell'action del form edit (`url('/admin/utenti/' . $user->id)`) → da accesso remoto generava `http://localhost/admin/utenti/4`, il POST falliva silenziosamente
|
||||
2. **Nessun hidden field** per unchecked checkbox → quando la checkbox veniva deselezionata, il campo `is_admin` non veniva inviato nella richiesta. Anche se il controller aveva `?? false`, il combinato col bug #1 impediva il salvataggio
|
||||
3. **Mancanza `old()`** per lo stato checkbox → su validation error redirect, la checkbox mostrava il valore DB invece del valore inviato
|
||||
- **Fix applicati**:
|
||||
- `edit.blade.php`: `url(...)` → path relativo `/admin/utenti/...` (linea 20); aggiunto hidden `<input type="hidden" name="is_admin" value="0">` prima della checkbox; `old()` per stato checkbox e status/role_preset_id
|
||||
- `create.blade.php`: aggiunto hidden field come sopra; default per `settings` = `'0'` (Nessuno) invece di `'1'` (Lettura) per nuovi utenti
|
||||
- `UtenteController.php` (store + update): rimosso `?? false` da `$user->is_admin` — ora usa direttamente `$validated['is_admin']` (sempre presente grazie all'hidden field)
|
||||
- `AuthController.php`: aggiunti `'report' => 0` e `'settings' => 0` ai default permissions per nuove registrazioni (sia first-user che non)
|
||||
- **Files modificati**:
|
||||
- `resources/views/admin/utenti/edit.blade.php`: form action, hidden field, old()
|
||||
- `resources/views/admin/utenti/create.blade.php`: hidden field, settings default 0
|
||||
- `app/Http/Controllers/Admin/UtenteController.php`: removed ?? false
|
||||
- `app/Http/Controllers/Auth/AuthController.php`: added report/settings defaults
|
||||
|
||||
(Last updated: 26 Maggio 2026 - Fix Salvataggio is_admin)
|
||||
|
||||
### 26 Maggio 2026 - Export ICS/iCal Eventi
|
||||
- **Nuovo Service**: `app/Services/IcsExportService.php` — generazione file ICS RFC 5545
|
||||
- `generateSingle(Evento)` — esporta un singolo evento
|
||||
- `generateCollection(iterable)` — esporta multipli eventi in un unico file ICS
|
||||
- Supporto **RRULE** completo per eventi ricorrenti:
|
||||
- `settimanale` → `FREQ=WEEKLY;BYDAY=MO`
|
||||
- `mensile` → `FREQ=MONTHLY;BYSETPOS=1;BYDAY=SU` (con opzionale `BYMONTH`)
|
||||
- `annuale` → `FREQ=YEARLY;BYMONTH=6;BYMONTHDAY=15`
|
||||
- `altro` → `FREQ=WEEKLY;BYDAY=MO`
|
||||
- Timezone `Europe/Rome` per eventi con ora, DATE (all-day) per eventi senza ora
|
||||
- LOCATION, DESCRIPTION, SUMMARY, UID univoci
|
||||
- Line folding a 75 caratteri RFC 5545
|
||||
- Escape di `;`, `,`, `\`, `\n`
|
||||
- **Nuovi metodi in `EventoController`**:
|
||||
- `exportIcs(int $id, IcsExportService $ics)` — StreamedResponse download singolo evento
|
||||
- `exportSelectedIcs(Request $request, IcsExportService $ics)` — StreamedResponse con filtro per ID selezionati o ricerca corrente
|
||||
- **Routes**:
|
||||
- `GET /eventi/{evento}/export-ics` → `eventi.export-ics`
|
||||
- `POST /eventi/export-ics` → `eventi.export-selected-ics`
|
||||
- **View updates**:
|
||||
- `show.blade.php`: pulsante "Esporta ICS" nella toolbar azioni
|
||||
- `index.blade.php`: pulsante "Esporta ICS" nella card-tools; se ci sono righe selezionate esporta solo quelle, altrimenti esporta tutti i risultati della ricerca corrente
|
||||
- `calendar.blade.php`: pulsante "Esporta ICS" che esporta tutti gli eventi
|
||||
- **Files creati**:
|
||||
- `app/Services/IcsExportService.php`
|
||||
- **Files modificati**:
|
||||
- `app/Http/Controllers/EventoController.php`
|
||||
- `routes/web.php`
|
||||
- `resources/views/eventi/show.blade.php`
|
||||
- `resources/views/eventi/index.blade.php`
|
||||
- `resources/views/eventi/calendar.blade.php`
|
||||
|
||||
@@ -0,0 +1,42 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Console\Commands;
|
||||
|
||||
use App\Models\User;
|
||||
use Illuminate\Console\Command;
|
||||
|
||||
class SyncUserPermissions extends Command
|
||||
{
|
||||
protected $signature = 'users:sync-permissions';
|
||||
protected $description = 'Sync all users permissions with MODULES, adding missing keys with default 0';
|
||||
|
||||
public function handle(): int
|
||||
{
|
||||
$count = 0;
|
||||
|
||||
foreach (User::all() as $user) {
|
||||
$permissions = $user->permissions ?? [];
|
||||
$changed = false;
|
||||
|
||||
foreach (User::MODULES as $module) {
|
||||
if (!array_key_exists($module, $permissions)) {
|
||||
$permissions[$module] = User::LEVEL_NONE;
|
||||
$changed = true;
|
||||
}
|
||||
}
|
||||
|
||||
if ($changed) {
|
||||
$user->permissions = $permissions;
|
||||
$user->save();
|
||||
$this->info("Synced permissions for user ID {$user->id} ({$user->email})");
|
||||
$count++;
|
||||
}
|
||||
}
|
||||
|
||||
$this->info("Done. {$count} users updated.");
|
||||
|
||||
return self::SUCCESS;
|
||||
}
|
||||
}
|
||||
@@ -11,12 +11,14 @@ class EmailSettingsController extends Controller
|
||||
{
|
||||
public function index()
|
||||
{
|
||||
$this->authorizeWrite('settings');
|
||||
$settings = EmailSetting::first() ?? new EmailSetting();
|
||||
return view('admin.email-settings.index', compact('settings'));
|
||||
}
|
||||
|
||||
public function save(Request $request)
|
||||
{
|
||||
$this->authorizeWrite('settings');
|
||||
$validated = $request->validate([
|
||||
'imap_host' => 'required|string|max:255',
|
||||
'imap_port' => 'required|integer|min:1|max:65535',
|
||||
@@ -62,6 +64,7 @@ class EmailSettingsController extends Controller
|
||||
|
||||
public function testConnection(Request $request)
|
||||
{
|
||||
$this->authorizeWrite('settings');
|
||||
$settings = EmailSetting::first();
|
||||
|
||||
if (!$settings) {
|
||||
@@ -87,6 +90,7 @@ class EmailSettingsController extends Controller
|
||||
|
||||
public function testSmtp(Request $request)
|
||||
{
|
||||
$this->authorizeWrite('settings');
|
||||
$request->validate([
|
||||
'email' => 'required|email',
|
||||
]);
|
||||
@@ -164,6 +168,7 @@ class EmailSettingsController extends Controller
|
||||
|
||||
public function syncNow()
|
||||
{
|
||||
$this->authorizeWrite('settings');
|
||||
$settings = EmailSetting::getActive();
|
||||
|
||||
if (!$settings) {
|
||||
|
||||
@@ -67,6 +67,8 @@ class AuthController extends Controller
|
||||
'documenti' => 1,
|
||||
'mailing' => 1,
|
||||
'viste' => 1,
|
||||
'report' => 0,
|
||||
'settings' => 0,
|
||||
];
|
||||
|
||||
if ($isFirstUser) {
|
||||
@@ -75,9 +77,11 @@ class AuthController extends Controller
|
||||
'gruppi' => 2,
|
||||
'eventi' => 2,
|
||||
'documenti' => 2,
|
||||
'mailing' => 2,
|
||||
'viste' => 2,
|
||||
];
|
||||
'mailing' => 2,
|
||||
'viste' => 2,
|
||||
'report' => 0,
|
||||
'settings' => 0,
|
||||
];
|
||||
}
|
||||
|
||||
$user = User::create([
|
||||
|
||||
@@ -6,7 +6,9 @@ use App\Models\Evento;
|
||||
use App\Models\Gruppo;
|
||||
use App\Models\Individuo;
|
||||
use App\Models\TipologiaEvento;
|
||||
use App\Services\IcsExportService;
|
||||
use Illuminate\Http\Request;
|
||||
use Symfony\Component\HttpFoundation\StreamedResponse;
|
||||
|
||||
class EventoController extends Controller
|
||||
{
|
||||
@@ -219,6 +221,55 @@ class EventoController extends Controller
|
||||
return redirect('/eventi')->with('success', count($eventi) . ' eventi eliminati con successo.');
|
||||
}
|
||||
|
||||
public function exportIcs(int $id, IcsExportService $ics): StreamedResponse
|
||||
{
|
||||
$this->authorizeRead('eventi');
|
||||
$evento = Evento::findOrFail($id);
|
||||
$content = $ics->generateSingle($evento);
|
||||
|
||||
return response()->streamDownload(function () use ($content) {
|
||||
echo $content;
|
||||
}, $ics->getFilename($evento), [
|
||||
'Content-Type' => 'text/calendar; charset=utf-8',
|
||||
'Content-Length' => strlen($content),
|
||||
]);
|
||||
}
|
||||
|
||||
public function exportSelectedIcs(Request $request, IcsExportService $ics): StreamedResponse
|
||||
{
|
||||
$this->authorizeRead('eventi');
|
||||
|
||||
$ids = $request->input('ids', []);
|
||||
if (!empty($ids) && is_array($ids)) {
|
||||
$eventi = Evento::whereIn('id', $ids)->get();
|
||||
} else {
|
||||
$query = Evento::query();
|
||||
if ($request->filled('search')) {
|
||||
$query->where('nome_evento', 'like', '%' . $request->search . '%');
|
||||
}
|
||||
if ($request->filled('gruppo_id')) {
|
||||
$query->whereHas('gruppi', fn($q) => $q->where('gruppi.id', $request->gruppo_id));
|
||||
}
|
||||
if ($request->filled('tipo')) {
|
||||
$query->where('tipo_recorrenza', $request->tipo);
|
||||
}
|
||||
$eventi = $query->get();
|
||||
}
|
||||
|
||||
if ($eventi->isEmpty()) {
|
||||
return redirect('/eventi')->with('error', 'Nessun evento da esportare.');
|
||||
}
|
||||
|
||||
$content = $ics->generateCollection($eventi);
|
||||
|
||||
return response()->streamDownload(function () use ($content) {
|
||||
echo $content;
|
||||
}, $ics->getCollectionFilename(), [
|
||||
'Content-Type' => 'text/calendar; charset=utf-8',
|
||||
'Content-Length' => strlen($content),
|
||||
]);
|
||||
}
|
||||
|
||||
public function calendar()
|
||||
{
|
||||
$this->authorizeRead('eventi');
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
use App\Models\AppSetting;
|
||||
use App\Models\EmailSetting;
|
||||
use App\Models\Ruolo;
|
||||
use App\Models\TipologiaDocumento;
|
||||
use App\Models\TipologiaEvento;
|
||||
@@ -13,19 +14,20 @@ class ImpostazioniController extends Controller
|
||||
{
|
||||
public function index()
|
||||
{
|
||||
$this->authorizeWrite('viste');
|
||||
$this->authorizeWrite('settings');
|
||||
|
||||
$tipologie = TipologiaDocumento::orderBy('ordine')->get();
|
||||
$tipologieEventi = TipologiaEvento::orderBy('ordine')->get();
|
||||
$ruoli = Ruolo::orderBy('ordine')->get();
|
||||
$appSettings = AppSetting::first();
|
||||
$emailSettings = EmailSetting::first() ?? new EmailSetting();
|
||||
|
||||
return view('impostazioni.index', compact('tipologie', 'tipologieEventi', 'ruoli', 'appSettings'));
|
||||
return view('impostazioni.index', compact('tipologie', 'tipologieEventi', 'ruoli', 'appSettings', 'emailSettings'));
|
||||
}
|
||||
|
||||
public function saveAppSettings(Request $request)
|
||||
{
|
||||
$this->authorizeWrite('viste');
|
||||
$this->authorizeWrite('settings');
|
||||
|
||||
$validated = $request->validate([
|
||||
'nome_applicazione' => 'nullable|string|max:100',
|
||||
@@ -52,7 +54,7 @@ class ImpostazioniController extends Controller
|
||||
|
||||
public function tipologieStore(Request $request)
|
||||
{
|
||||
$this->authorizeWrite('viste');
|
||||
$this->authorizeWrite('settings');
|
||||
|
||||
$validated = $request->validate([
|
||||
'nome' => 'required|string|max:50|unique:tipologie_documenti,nome',
|
||||
@@ -73,7 +75,7 @@ class ImpostazioniController extends Controller
|
||||
|
||||
public function tipologieUpdate(Request $request, $id)
|
||||
{
|
||||
$this->authorizeWrite('viste');
|
||||
$this->authorizeWrite('settings');
|
||||
|
||||
$tipologia = TipologiaDocumento::findOrFail($id);
|
||||
|
||||
@@ -90,7 +92,7 @@ class ImpostazioniController extends Controller
|
||||
|
||||
public function tipologieDestroy($id)
|
||||
{
|
||||
$this->authorizeDelete('viste');
|
||||
$this->authorizeDelete('settings');
|
||||
$tipologia = TipologiaDocumento::findOrFail($id);
|
||||
|
||||
$count = $tipologia->documenti()->count();
|
||||
@@ -105,7 +107,7 @@ class ImpostazioniController extends Controller
|
||||
|
||||
public function tipologieReorder(Request $request)
|
||||
{
|
||||
$this->authorizeWrite('viste');
|
||||
$this->authorizeWrite('settings');
|
||||
|
||||
$order = $request->input('order', []);
|
||||
|
||||
@@ -118,7 +120,7 @@ class ImpostazioniController extends Controller
|
||||
|
||||
public function ruoliStore(Request $request)
|
||||
{
|
||||
$this->authorizeWrite('viste');
|
||||
$this->authorizeWrite('settings');
|
||||
|
||||
$validated = $request->validate([
|
||||
'nome' => 'required|string|max:50|unique:ruoli,nome',
|
||||
@@ -139,7 +141,7 @@ class ImpostazioniController extends Controller
|
||||
|
||||
public function ruoliUpdate(Request $request, $id)
|
||||
{
|
||||
$this->authorizeWrite('viste');
|
||||
$this->authorizeWrite('settings');
|
||||
|
||||
$ruolo = Ruolo::findOrFail($id);
|
||||
|
||||
@@ -156,7 +158,7 @@ class ImpostazioniController extends Controller
|
||||
|
||||
public function ruoliDestroy($id)
|
||||
{
|
||||
$this->authorizeDelete('viste');
|
||||
$this->authorizeDelete('settings');
|
||||
|
||||
$ruolo = Ruolo::findOrFail($id);
|
||||
|
||||
@@ -176,7 +178,7 @@ class ImpostazioniController extends Controller
|
||||
|
||||
public function ruoliReorder(Request $request)
|
||||
{
|
||||
$this->authorizeWrite('viste');
|
||||
$this->authorizeWrite('settings');
|
||||
|
||||
$order = $request->input('order', []);
|
||||
|
||||
@@ -189,7 +191,7 @@ class ImpostazioniController extends Controller
|
||||
|
||||
public function tipologieEventiStore(Request $request)
|
||||
{
|
||||
$this->authorizeWrite('viste');
|
||||
$this->authorizeWrite('settings');
|
||||
|
||||
$validated = $request->validate([
|
||||
'nome' => 'required|string|max:50|unique:tipologie_eventi,nome',
|
||||
@@ -210,7 +212,7 @@ class ImpostazioniController extends Controller
|
||||
|
||||
public function tipologieEventiUpdate(Request $request, $id)
|
||||
{
|
||||
$this->authorizeWrite('viste');
|
||||
$this->authorizeWrite('settings');
|
||||
|
||||
$tipologia = TipologiaEvento::findOrFail($id);
|
||||
|
||||
@@ -227,7 +229,7 @@ class ImpostazioniController extends Controller
|
||||
|
||||
public function tipologieEventiDestroy($id)
|
||||
{
|
||||
$this->authorizeDelete('viste');
|
||||
$this->authorizeDelete('settings');
|
||||
$tipologia = TipologiaEvento::findOrFail($id);
|
||||
|
||||
$count = $tipologia->eventi()->count();
|
||||
@@ -242,7 +244,7 @@ class ImpostazioniController extends Controller
|
||||
|
||||
public function tipologieEventiReorder(Request $request)
|
||||
{
|
||||
$this->authorizeWrite('viste');
|
||||
$this->authorizeWrite('settings');
|
||||
|
||||
$order = $request->input('order', []);
|
||||
|
||||
@@ -255,7 +257,7 @@ class ImpostazioniController extends Controller
|
||||
|
||||
public function uploadLogo(Request $request)
|
||||
{
|
||||
$this->authorizeWrite('viste');
|
||||
$this->authorizeWrite('settings');
|
||||
|
||||
$request->validate([
|
||||
'logo' => 'nullable|image|mimes:png,jpg,jpeg,gif,svg|max:2048',
|
||||
@@ -285,7 +287,7 @@ class ImpostazioniController extends Controller
|
||||
|
||||
public function removeLogo(Request $request)
|
||||
{
|
||||
$this->authorizeWrite('viste');
|
||||
$this->authorizeWrite('settings');
|
||||
|
||||
$type = $request->input('type');
|
||||
|
||||
|
||||
@@ -14,7 +14,7 @@ class AdminOnly
|
||||
return redirect('/login');
|
||||
}
|
||||
|
||||
if (!auth()->user()->isSuperAdmin()) {
|
||||
if (!auth()->user()->isSuperAdmin() && !auth()->user()->canManage('settings')) {
|
||||
abort(403, 'Accesso riservato agli amministratori.');
|
||||
}
|
||||
|
||||
|
||||
+1
-1
@@ -33,7 +33,7 @@ class User extends Authenticatable
|
||||
public const STATUS_SUSPENDED = 'suspended';
|
||||
public const STATUS_PENDING = 'pending';
|
||||
|
||||
public const MODULES = ['individui', 'gruppi', 'eventi', 'documenti', 'mailing', 'viste', 'report'];
|
||||
public const MODULES = ['individui', 'gruppi', 'eventi', 'documenti', 'mailing', 'viste', 'report', 'settings'];
|
||||
public const LEVEL_NONE = 0;
|
||||
public const LEVEL_READ = 1;
|
||||
public const LEVEL_FULL = 2;
|
||||
|
||||
@@ -0,0 +1,212 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Services;
|
||||
|
||||
use App\Models\Evento;
|
||||
use Carbon\Carbon;
|
||||
|
||||
class IcsExportService
|
||||
{
|
||||
private const DATETIME_FORMAT = 'Ymd\THis';
|
||||
private const DATE_FORMAT = 'Ymd';
|
||||
private const TIMEZONE = 'Europe/Rome';
|
||||
|
||||
private const DAY_MAP = [
|
||||
0 => 'SU',
|
||||
1 => 'MO',
|
||||
2 => 'TU',
|
||||
3 => 'WE',
|
||||
4 => 'TH',
|
||||
5 => 'FR',
|
||||
6 => 'SA',
|
||||
];
|
||||
|
||||
public function generateSingle(Evento $evento): string
|
||||
{
|
||||
return $this->generateCollection(collect([$evento]));
|
||||
}
|
||||
|
||||
public function generateCollection(iterable $eventi): string
|
||||
{
|
||||
$lines = [
|
||||
'BEGIN:VCALENDAR',
|
||||
'VERSION:2.0',
|
||||
'PRODID:-//Glastree//Eventi//IT',
|
||||
'CALSCALE:GREGORIAN',
|
||||
'METHOD:PUBLISH',
|
||||
'X-WR-CALNAME:' . config('app.name') . ' - Eventi',
|
||||
];
|
||||
|
||||
foreach ($eventi as $evento) {
|
||||
array_push($lines, ...$this->buildVEvent($evento));
|
||||
}
|
||||
|
||||
$lines[] = 'END:VCALENDAR';
|
||||
|
||||
return $this->fold(implode("\r\n", $lines));
|
||||
}
|
||||
|
||||
private function buildVEvent(Evento $evento): array
|
||||
{
|
||||
$uid = 'evento-' . $evento->id . '@' . preg_replace('/[^a-z0-9]/', '', strtolower(config('app.name')));
|
||||
$lines = [
|
||||
'BEGIN:VEVENT',
|
||||
'UID:' . $uid,
|
||||
'DTSTAMP:' . now()->format(self::DATETIME_FORMAT) . 'Z',
|
||||
];
|
||||
|
||||
if ($evento->isSingolo() && $evento->data_specifica) {
|
||||
$date = $evento->data_specifica instanceof Carbon
|
||||
? $evento->data_specifica
|
||||
: Carbon::parse($evento->data_specifica);
|
||||
|
||||
if ($evento->ora_inizio) {
|
||||
$start = $date->copy()->setTimeFrom($evento->ora_inizio instanceof Carbon ? $evento->ora_inizio : Carbon::parse($evento->ora_inizio));
|
||||
$durata = $evento->durata_minuti ?? 60;
|
||||
$end = $start->copy()->addMinutes((int) $durata);
|
||||
$lines[] = 'DTSTART;TZID=' . self::TIMEZONE . ':' . $start->format(self::DATETIME_FORMAT);
|
||||
$lines[] = 'DTEND;TZID=' . self::TIMEZONE . ':' . $end->format(self::DATETIME_FORMAT);
|
||||
} else {
|
||||
$lines[] = 'DTSTART;VALUE=DATE:' . $date->format(self::DATE_FORMAT);
|
||||
$lines[] = 'DTEND;VALUE=DATE:' . $date->copy()->addDay()->format(self::DATE_FORMAT);
|
||||
}
|
||||
} else {
|
||||
$rrule = $this->buildRRule($evento);
|
||||
if ($rrule) {
|
||||
$lines[] = $rrule;
|
||||
}
|
||||
|
||||
if ($evento->ora_inizio) {
|
||||
$time = $evento->ora_inizio instanceof Carbon ? $evento->ora_inizio : Carbon::parse($evento->ora_inizio);
|
||||
$lines[] = 'DTSTART;TZID=' . self::TIMEZONE . ':' . $time->format('His');
|
||||
$durata = $evento->durata_minuti ?? 60;
|
||||
$endTime = $time->copy()->addMinutes((int) $durata);
|
||||
$lines[] = 'DTEND;TZID=' . self::TIMEZONE . ':' . $endTime->format('His');
|
||||
}
|
||||
}
|
||||
|
||||
$lines[] = 'SUMMARY:' . $this->escapeText($evento->nome_evento);
|
||||
|
||||
if ($evento->descrizione_evento || $evento->descrizione) {
|
||||
$desc = $evento->descrizione_evento
|
||||
? ($evento->descrizione ? $evento->descrizione_evento . '\n\n' . $evento->descrizione : $evento->descrizione_evento)
|
||||
: $evento->descrizione;
|
||||
$lines[] = 'DESCRIPTION:' . $this->escapeText($desc);
|
||||
}
|
||||
|
||||
if ($evento->luogo_indirizzo) {
|
||||
$lines[] = 'LOCATION:' . $this->escapeText($evento->luogo_indirizzo);
|
||||
}
|
||||
|
||||
$lines[] = 'END:VEVENT';
|
||||
|
||||
return $lines;
|
||||
}
|
||||
|
||||
private function buildRRule(Evento $evento): ?string
|
||||
{
|
||||
return match ($evento->tipo_recorrenza) {
|
||||
'settimanale' => $this->rruleSettimanale($evento),
|
||||
'mensile' => $this->rruleMensile($evento),
|
||||
'annuale' => $this->rruleAnnuale($evento),
|
||||
'altro' => $this->rruleAltro($evento),
|
||||
default => null,
|
||||
};
|
||||
}
|
||||
|
||||
private function rruleSettimanale(Evento $evento): ?string
|
||||
{
|
||||
if ($evento->giorno_settimana === null) {
|
||||
return null;
|
||||
}
|
||||
return 'RRULE:FREQ=WEEKLY;BYDAY=' . (self::DAY_MAP[(int) $evento->giorno_settimana] ?? 'MO');
|
||||
}
|
||||
|
||||
private function rruleMensile(Evento $evento): ?string
|
||||
{
|
||||
if (!$evento->occorrenza_mese) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$parts = explode(',', $evento->occorrenza_mese);
|
||||
if (count($parts) !== 2) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$occorrenza = (int) $parts[0];
|
||||
$giorno = (int) $parts[1];
|
||||
$byDay = self::DAY_MAP[$giorno] ?? 'MO';
|
||||
|
||||
$rrule = 'RRULE:FREQ=MONTHLY;BYSETPOS=' . $occorrenza . ';BYDAY=' . $byDay;
|
||||
|
||||
if ($evento->mesi_recorrenza) {
|
||||
$mesi = explode(',', $evento->mesi_recorrenza);
|
||||
$rrule .= ';BYMONTH=' . implode(',', $mesi);
|
||||
}
|
||||
|
||||
return $rrule;
|
||||
}
|
||||
|
||||
private function rruleAnnuale(Evento $evento): ?string
|
||||
{
|
||||
if (!$evento->mese_annuale) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$rrule = 'RRULE:FREQ=YEARLY;BYMONTH=' . $evento->mese_annuale;
|
||||
|
||||
$giorno = (int) ($evento->giorno_mese ?? 1);
|
||||
$rrule .= ';BYMONTHDAY=' . $giorno;
|
||||
|
||||
return $rrule;
|
||||
}
|
||||
|
||||
private function rruleAltro(Evento $evento): ?string
|
||||
{
|
||||
return $this->rruleSettimanale($evento);
|
||||
}
|
||||
|
||||
private function escapeText(?string $text): string
|
||||
{
|
||||
if ($text === null) {
|
||||
return '';
|
||||
}
|
||||
|
||||
$text = str_replace(
|
||||
['\\', ';', ',', "\n"],
|
||||
['\\\\', '\\;', '\\,', '\\n'],
|
||||
$text
|
||||
);
|
||||
|
||||
return $text;
|
||||
}
|
||||
|
||||
private function fold(string $ics): string
|
||||
{
|
||||
$lines = explode("\r\n", $ics);
|
||||
$folded = [];
|
||||
|
||||
foreach ($lines as $line) {
|
||||
while (mb_strlen($line) > 75) {
|
||||
$folded[] = mb_substr($line, 0, 75);
|
||||
$line = ' ' . mb_substr($line, 75);
|
||||
}
|
||||
$folded[] = $line;
|
||||
}
|
||||
|
||||
return implode("\r\n", $folded);
|
||||
}
|
||||
|
||||
public function getFilename(Evento $evento): string
|
||||
{
|
||||
$name = str_replace(['/', '\\', ' '], '_', $evento->nome_evento);
|
||||
return $name . '.ics';
|
||||
}
|
||||
|
||||
public function getCollectionFilename(): string
|
||||
{
|
||||
return 'eventi_' . now()->format('Ymd_His') . '.ics';
|
||||
}
|
||||
}
|
||||
@@ -44,9 +44,10 @@
|
||||
@endforeach
|
||||
</select>
|
||||
</div>
|
||||
<input type="hidden" name="is_admin" value="0">
|
||||
<div class="form-group">
|
||||
<div class="custom-control custom-switch">
|
||||
<input type="checkbox" class="custom-control-input" id="isAdmin" name="is_admin" value="1">
|
||||
<input type="checkbox" class="custom-control-input" id="isAdmin" name="is_admin" value="1" {{ old('is_admin') === '1' ? 'checked' : '' }}>
|
||||
<label class="custom-control-label" for="isAdmin">Superadmin (bypass ACL)</label>
|
||||
</div>
|
||||
</div>
|
||||
@@ -63,12 +64,12 @@
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
@foreach(['individui', 'gruppi', 'eventi', 'documenti', 'mailing', 'viste'] as $module)
|
||||
@foreach(\App\Models\User::MODULES as $module)
|
||||
<tr>
|
||||
<td><strong>{{ ucfirst($module) }}</strong></td>
|
||||
<td><input type="radio" name="permissions[{{ $module }}]" value="0" {{ old('permissions.' . $module, '1') == '0' ? 'checked' : '' }}></td>
|
||||
<td><input type="radio" name="permissions[{{ $module }}]" value="1" {{ old('permissions.' . $module, '1') == '1' ? 'checked' : '' }}></td>
|
||||
<td><input type="radio" name="permissions[{{ $module }}]" value="2" {{ old('permissions.' . $module, '1') == '2' ? 'checked' : '' }}></td>
|
||||
<td><input type="radio" name="permissions[{{ $module }}]" value="0" {{ old('permissions.' . $module, $module === 'settings' ? '0' : '1') == '0' ? 'checked' : '' }}></td>
|
||||
<td><input type="radio" name="permissions[{{ $module }}]" value="1" {{ old('permissions.' . $module, $module === 'settings' ? '0' : '1') == '1' ? 'checked' : '' }}></td>
|
||||
<td><input type="radio" name="permissions[{{ $module }}]" value="2" {{ old('permissions.' . $module, $module === 'settings' ? '0' : '1') == '2' ? 'checked' : '' }}></td>
|
||||
</tr>
|
||||
@endforeach
|
||||
</tbody>
|
||||
|
||||
@@ -17,7 +17,7 @@
|
||||
<h3 class="card-title">Dati Utente</h3>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<form method="POST" action="{{ url('/admin/utenti/' . $user->id) }}">
|
||||
<form method="POST" action="/admin/utenti/{{ $user->id }}">
|
||||
@csrf @method('PUT')
|
||||
<div class="form-group">
|
||||
<label>Nome *</label>
|
||||
@@ -38,9 +38,9 @@
|
||||
<div class="form-group">
|
||||
<label>Stato</label>
|
||||
<select name="status" class="form-control" required>
|
||||
<option value="active" {{ $user->status === 'active' ? 'selected' : '' }}>Attivo</option>
|
||||
<option value="suspended" {{ $user->status === 'suspended' ? 'selected' : '' }}>Sospeso</option>
|
||||
<option value="pending" {{ $user->status === 'pending' ? 'selected' : '' }}>In attesa</option>
|
||||
<option value="active" {{ old('status', $user->status) === 'active' ? 'selected' : '' }}>Attivo</option>
|
||||
<option value="suspended" {{ old('status', $user->status) === 'suspended' ? 'selected' : '' }}>Sospeso</option>
|
||||
<option value="pending" {{ old('status', $user->status) === 'pending' ? 'selected' : '' }}>In attesa</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
@@ -48,15 +48,16 @@
|
||||
<select name="role_preset_id" class="form-control" id="rolePresetSelect">
|
||||
<option value="">Personalizzato</option>
|
||||
@foreach($rolePresets as $preset)
|
||||
<option value="{{ $preset->id }}" {{ $user->role_preset_id == $preset->id ? 'selected' : '' }}>
|
||||
<option value="{{ $preset->id }}" {{ old('role_preset_id', $user->role_preset_id) == $preset->id ? 'selected' : '' }}>
|
||||
{{ $preset->name }} - {{ $preset->description }}
|
||||
</option>
|
||||
@endforeach
|
||||
</select>
|
||||
</div>
|
||||
<input type="hidden" name="is_admin" value="0">
|
||||
<div class="form-group">
|
||||
<div class="custom-control custom-switch">
|
||||
<input type="checkbox" class="custom-control-input" id="isAdmin" name="is_admin" value="1" {{ $user->isSuperAdmin() ? 'checked' : '' }}>
|
||||
<input type="checkbox" class="custom-control-input" id="isAdmin" name="is_admin" value="1" {{ old('is_admin', $user->isSuperAdmin() ? '1' : '0') === '1' ? 'checked' : '' }}>
|
||||
<label class="custom-control-label" for="isAdmin">Superadmin (bypass ACL)</label>
|
||||
</div>
|
||||
</div>
|
||||
@@ -73,7 +74,7 @@
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
@foreach(['individui', 'gruppi', 'eventi', 'documenti', 'mailing', 'viste'] as $module)
|
||||
@foreach(\App\Models\User::MODULES as $module)
|
||||
<tr>
|
||||
<td><strong>{{ ucfirst($module) }}</strong></td>
|
||||
<td><input type="radio" name="permissions[{{ $module }}]" value="0" {{ ($user->permissions[$module] ?? 0) == 0 ? 'checked' : '' }}></td>
|
||||
|
||||
@@ -17,6 +17,12 @@
|
||||
<a href="{{ route('eventi.create') }}" class="btn btn-success btn-sm">
|
||||
<i class="fas fa-plus mr-1"></i> Nuovo Evento
|
||||
</a>
|
||||
<form action="{{ route('eventi.export-selected-ics') }}" method="POST" class="d-inline">
|
||||
@csrf
|
||||
<button type="submit" class="btn btn-success btn-sm">
|
||||
<i class="fas fa-calendar-plus mr-1"></i> Esporta ICS
|
||||
</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
|
||||
@@ -23,6 +23,9 @@ $canDeleteEventi = Auth::user()->canDelete('eventi');
|
||||
<a href="/eventi/calendar" class="btn btn-sm btn-outline-primary mr-2">
|
||||
<i class="fas fa-calendar-alt mr-1"></i> Calendario
|
||||
</a>
|
||||
<button type="button" class="btn btn-sm btn-success mr-2" onclick="exportSelectedIcs()">
|
||||
<i class="fas fa-calendar-plus mr-1"></i> Esporta ICS
|
||||
</button>
|
||||
@if($canDeleteEventi)
|
||||
<div class="btn-group mr-2">
|
||||
<button type="button" class="btn btn-sm btn-danger" onclick="deleteSelected()">
|
||||
@@ -311,5 +314,33 @@ function deleteSelected() {
|
||||
|
||||
$('#deleteModal').modal('show');
|
||||
}
|
||||
|
||||
function exportSelectedIcs() {
|
||||
const selectedIds = getSelectedIds();
|
||||
const form = document.createElement('form');
|
||||
form.method = 'POST';
|
||||
form.action = '/eventi/export-ics';
|
||||
|
||||
const csrfToken = document.querySelector('meta[name="csrf-token"]')?.content || '{{ csrf_token() }}';
|
||||
|
||||
let html = `<input type="hidden" name="_token" value="${csrfToken}">`;
|
||||
|
||||
if (selectedIds.length > 0) {
|
||||
selectedIds.forEach(function(id) {
|
||||
html += `<input type="hidden" name="ids[]" value="${id}">`;
|
||||
});
|
||||
} else {
|
||||
const params = new URLSearchParams(window.location.search);
|
||||
params.forEach(function(value, key) {
|
||||
if (key === 'search') html += `<input type="hidden" name="search" value="${value}">`;
|
||||
if (key === 'gruppo_id') html += `<input type="hidden" name="gruppo_id" value="${value}">`;
|
||||
if (key === 'tipo') html += `<input type="hidden" name="tipo" value="${value}">`;
|
||||
});
|
||||
}
|
||||
|
||||
form.innerHTML = html;
|
||||
document.body.appendChild(form);
|
||||
form.submit();
|
||||
}
|
||||
</script>
|
||||
@endsection
|
||||
@@ -308,6 +308,9 @@
|
||||
<i class="fas fa-trash mr-1"></i> Elimina
|
||||
</button>
|
||||
</form>
|
||||
<a href="/eventi/{{ $evento->id }}/export-ics" class="btn btn-success">
|
||||
<i class="fas fa-calendar-plus mr-1"></i> Esporta ICS
|
||||
</a>
|
||||
<a href="/eventi" class="btn btn-secondary">
|
||||
<i class="fas fa-arrow-left mr-1"></i> Torna all'elenco
|
||||
</a>
|
||||
|
||||
@@ -23,20 +23,27 @@
|
||||
<a href="#logo" class="list-group-item list-group-item-action" data-toggle="tab">
|
||||
<i class="nav-icon fas fa-image mr-2"></i> Logo
|
||||
</a>
|
||||
<a href="#email" class="list-group-item list-group-item-action" data-toggle="tab">
|
||||
<i class="nav-icon fas fa-envelope mr-2"></i> Email
|
||||
</a>
|
||||
<a href="#calendario" class="list-group-item list-group-item-action" data-toggle="tab">
|
||||
<i class="nav-icon fas fa-calendar-alt mr-2"></i> Calendario
|
||||
</a>
|
||||
<a href="#tipologie" class="list-group-item list-group-item-action" data-toggle="tab">
|
||||
<i class="nav-icon fas fa-file mr-2"></i> Tipologie Documenti
|
||||
</a>
|
||||
<a href="#ruoli" class="list-group-item list-group-item-action" data-toggle="tab">
|
||||
<i class="nav-icon fas fa-users-cog mr-2"></i> Tipi di Ruolo
|
||||
</a>
|
||||
<a href="#tipologie-eventi" class="list-group-item list-group-item-action" data-toggle="tab">
|
||||
<i class="nav-icon fas fa-calendar mr-2"></i> Tipologie Eventi
|
||||
</a>
|
||||
<a href="#ruoli" class="list-group-item list-group-item-action" data-toggle="tab">
|
||||
<i class="nav-icon fas fa-users-cog mr-2"></i> Tipi di Ruolo
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-9">
|
||||
<div class="tab-content">
|
||||
{{-- === APPLICAZIONE === --}}
|
||||
<div class="tab-pane" id="applicazione">
|
||||
<div class="card card-primary">
|
||||
<div class="card-header">
|
||||
@@ -51,12 +58,11 @@
|
||||
|
||||
<form method="POST" action="{{ route('impostazioni.app-settings.save') }}">
|
||||
@csrf
|
||||
@method('POST')
|
||||
<div class="row">
|
||||
<div class="col-md-6">
|
||||
<div class="form-group">
|
||||
<label for="nome_applicazione">Nome Applicazione</label>
|
||||
<input type="text" name="nome_applicazione" id="nome_applicazione" class="form-control"
|
||||
<input type="text" name="nome_applicazione" id="nome_applicazione" class="form-control"
|
||||
value="{{ old('nome_applicazione', $appSettings->nome_applicazione ?? '') }}"
|
||||
placeholder="es. Glastree">
|
||||
<small class="text-muted">Nome mostrato nel titolo, sidebar e login</small>
|
||||
@@ -65,7 +71,7 @@
|
||||
<div class="col-md-6">
|
||||
<div class="form-group">
|
||||
<label for="nome_organizzazione">Nome Organizzazione</label>
|
||||
<input type="text" name="nome_organizzazione" id="nome_organizzazione" class="form-control"
|
||||
<input type="text" name="nome_organizzazione" id="nome_organizzazione" class="form-control"
|
||||
value="{{ old('nome_organizzazione', $appSettings->nome_organizzazione ?? '') }}"
|
||||
placeholder="es. Parrocchia Santa Maria">
|
||||
<small class="text-muted">Nome organizzazione mostrato sotto il logo nel login</small>
|
||||
@@ -75,7 +81,7 @@
|
||||
|
||||
<div class="form-group">
|
||||
<label for="footer_text">Testo Footer</label>
|
||||
<input type="text" name="footer_text" id="footer_text" class="form-control"
|
||||
<input type="text" name="footer_text" id="footer_text" class="form-control"
|
||||
value="{{ old('footer_text', $appSettings->footer_text ?? '') }}"
|
||||
placeholder="es. © 2026 Parrocchia Santa Maria">
|
||||
<small class="text-muted">Lascia vuoto per usare il formato predefinito: © {anno} {nome_applicazione}</small>
|
||||
@@ -85,7 +91,7 @@
|
||||
<div class="col-md-6">
|
||||
<div class="form-group">
|
||||
<label for="app_version">Versione Applicazione</label>
|
||||
<input type="text" name="app_version" id="app_version" class="form-control"
|
||||
<input type="text" name="app_version" id="app_version" class="form-control"
|
||||
value="{{ old('app_version', $appSettings->app_version ?? '') }}"
|
||||
placeholder="es. 1.0.0">
|
||||
<small class="text-muted">Versione mostrata nel footer</small>
|
||||
@@ -118,6 +124,7 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{{-- === LOGO === --}}
|
||||
<div class="tab-pane" id="logo">
|
||||
<div class="card card-primary">
|
||||
<div class="card-header">
|
||||
@@ -170,6 +177,297 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{{-- === EMAIL === --}}
|
||||
<div class="tab-pane" id="email">
|
||||
<div class="card card-secondary">
|
||||
<div class="card-header">
|
||||
<h3 class="card-title">Sezioni Email</h3>
|
||||
</div>
|
||||
<div class="card-body p-0">
|
||||
<ul class="nav nav-pills p-2">
|
||||
<li class="nav-item"><a href="#email-imap" class="nav-link active" data-toggle="tab">Server IMAP</a></li>
|
||||
<li class="nav-item"><a href="#email-smtp" class="nav-link" data-toggle="tab">Server SMTP</a></li>
|
||||
<li class="nav-item"><a href="#email-account" class="nav-link" data-toggle="tab">Account</a></li>
|
||||
<li class="nav-item"><a href="#email-sync" class="nav-link" data-toggle="tab">Sincronizzazione</a></li>
|
||||
<li class="nav-item"><a href="#email-firma" class="nav-link" data-toggle="tab">Firma</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<form method="POST" action="{{ route('impostazioni.email.save') }}">
|
||||
@csrf
|
||||
<input type="hidden" name="id" value="{{ $emailSettings->id ?? 1 }}">
|
||||
|
||||
<div class="tab-content">
|
||||
<div class="tab-pane active" id="email-imap">
|
||||
<div class="card card-primary">
|
||||
<div class="card-header">
|
||||
<h3 class="card-title">Configurazione Server IMAP</h3>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<div class="row">
|
||||
<div class="col-md-6">
|
||||
<div class="form-group">
|
||||
<label for="imap_host">Host IMAP</label>
|
||||
<input type="text" name="imap_host" id="imap_host" class="form-control"
|
||||
value="{{ $emailSettings->imap_host ?? 'imap.gmail.com' }}"
|
||||
placeholder="es. imap.gmail.com">
|
||||
<small class="text-muted">Gmail: imap.gmail.com | Outlook: outlook.office365.com</small>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-3">
|
||||
<div class="form-group">
|
||||
<label for="imap_port">Porta</label>
|
||||
<input type="number" name="imap_port" id="imap_port" class="form-control"
|
||||
value="{{ $emailSettings->imap_port ?? 993 }}">
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-3">
|
||||
<div class="form-group">
|
||||
<label for="imap_encryption">Crittografia</label>
|
||||
<select name="imap_encryption" id="imap_encryption" class="form-control">
|
||||
<option value="ssl" {{ ($emailSettings->imap_encryption ?? 'ssl') == 'ssl' ? 'selected' : '' }}>SSL</option>
|
||||
<option value="tls" {{ ($emailSettings->imap_encryption ?? '') == 'tls' ? 'selected' : '' }}>TLS</option>
|
||||
<option value="none" {{ ($emailSettings->imap_encryption ?? '') == 'none' ? 'selected' : '' }}>Nessuna</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="tab-pane" id="email-smtp">
|
||||
<div class="card card-primary">
|
||||
<div class="card-header">
|
||||
<h3 class="card-title">Configurazione Server SMTP (per invio email)</h3>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<div class="alert alert-info">
|
||||
<i class="fas fa-info-circle"></i>
|
||||
<strong>Nota:</strong> Per inviare email devi configurare il server SMTP.
|
||||
Per Gmail, usa <code>smtp.gmail.com</code> sulla porta <code>587</code> con TLS
|
||||
e genera una <a href="https://support.google.com/accounts/answer/185833" target="_blank">App Password</a>.
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-md-6">
|
||||
<div class="form-group">
|
||||
<label for="smtp_host">Host SMTP</label>
|
||||
<input type="text" name="smtp_host" id="smtp_host" class="form-control"
|
||||
value="{{ $emailSettings->smtp_host ?? 'smtp.gmail.com' }}"
|
||||
placeholder="es. smtp.gmail.com">
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-3">
|
||||
<div class="form-group">
|
||||
<label for="smtp_port">Porta</label>
|
||||
<input type="number" name="smtp_port" id="smtp_port" class="form-control"
|
||||
value="{{ $emailSettings->smtp_port ?? 587 }}">
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-3">
|
||||
<div class="form-group">
|
||||
<label for="smtp_encryption">Crittografia</label>
|
||||
<select name="smtp_encryption" id="smtp_encryption" class="form-control">
|
||||
<option value="tls" {{ ($emailSettings->smtp_encryption ?? 'tls') == 'tls' ? 'selected' : '' }}>TLS</option>
|
||||
<option value="ssl" {{ ($emailSettings->smtp_encryption ?? '') == 'ssl' ? 'selected' : '' }}>SSL</option>
|
||||
<option value="none" {{ ($emailSettings->smtp_encryption ?? '') == 'none' ? 'selected' : '' }}>Nessuna</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-md-6">
|
||||
<div class="form-group">
|
||||
<label for="smtp_username">Username SMTP</label>
|
||||
<input type="text" name="smtp_username" id="smtp_username" class="form-control"
|
||||
value="{{ $emailSettings->smtp_username ?? '' }}"
|
||||
placeholder="lascia vuoto per usare l'account IMAP">
|
||||
<small class="text-muted">Generalmente uguale all'account email</small>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<div class="form-group">
|
||||
<label for="smtp_password">Password SMTP / App Password</label>
|
||||
<input type="password" name="smtp_password" id="smtp_password" class="form-control"
|
||||
placeholder="{{ $emailSettings->smtp_password ? '••••••••' : '' }}">
|
||||
<small class="text-muted">Lascia vuoto per usare la password IMAP</small>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="tab-pane" id="email-account">
|
||||
<div class="card card-primary">
|
||||
<div class="card-header">
|
||||
<h3 class="card-title">Credenziali Account</h3>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<div class="row">
|
||||
<div class="col-md-6">
|
||||
<div class="form-group">
|
||||
<label for="imap_username">Username/Email</label>
|
||||
<input type="text" name="imap_username" id="imap_username" class="form-control"
|
||||
value="{{ $emailSettings->imap_username ?? '' }}"
|
||||
placeholder="es. tuo@gmail.com">
|
||||
<small class="text-muted">Per Gmail usa l'indirizzo completo</small>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<div class="form-group">
|
||||
<label for="imap_password">Password / App Password</label>
|
||||
<input type="password" name="imap_password" id="imap_password" class="form-control" placeholder="">
|
||||
<small class="text-muted">
|
||||
<a href="https://support.google.com/accounts/answer/185833" target="_blank">Per Gmail: genera una App Password</a>
|
||||
</small>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<hr>
|
||||
<div class="row">
|
||||
<div class="col-md-6">
|
||||
<div class="form-group">
|
||||
<label for="email_address">Email Indirizzo</label>
|
||||
<input type="email" name="email_address" id="email_address" class="form-control"
|
||||
value="{{ $emailSettings->email_address ?? '' }}">
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<div class="form-group">
|
||||
<label for="email_name">Nome Visualizzato</label>
|
||||
<input type="text" name="email_name" id="email_name" class="form-control"
|
||||
value="{{ $emailSettings->email_name ?? '' }}"
|
||||
placeholder="es. Nome Cognome">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-md-6">
|
||||
<div class="form-group">
|
||||
<label for="reply_to">Email Risposta (Reply-To)</label>
|
||||
<input type="email" name="reply_to" id="reply_to" class="form-control"
|
||||
value="{{ $emailSettings->reply_to ?? '' }}">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="tab-pane" id="email-sync">
|
||||
<div class="card card-primary">
|
||||
<div class="card-header">
|
||||
<h3 class="card-title">Sincronizzazione</h3>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<div class="row">
|
||||
<div class="col-md-6">
|
||||
<div class="form-group">
|
||||
<label for="sync_interval_minutes">Intervallo Sync (minuti)</label>
|
||||
<select name="sync_interval_minutes" id="sync_interval_minutes" class="form-control">
|
||||
<option value="1" {{ ($emailSettings->sync_interval_minutes ?? 5) == 1 ? 'selected' : '' }}>1 minuto</option>
|
||||
<option value="5" {{ ($emailSettings->sync_interval_minutes ?? 5) == 5 ? 'selected' : '' }}>5 minuti</option>
|
||||
<option value="15" {{ ($emailSettings->sync_interval_minutes ?? 5) == 15 ? 'selected' : '' }}>15 minuti</option>
|
||||
<option value="30" {{ ($emailSettings->sync_interval_minutes ?? 5) == 30 ? 'selected' : '' }}>30 minuti</option>
|
||||
<option value="60" {{ ($emailSettings->sync_interval_minutes ?? 5) == 60 ? 'selected' : '' }}>1 ora</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<div class="form-group">
|
||||
<label>Stato Connessione</label>
|
||||
<div class="mt-2">
|
||||
@if($emailSettings->last_sync_at)
|
||||
<span class="badge badge-success">
|
||||
<i class="fas fa-check-circle"></i> Ultimo sync: {{ $emailSettings->last_sync_at->format('d/m/Y H:i') }}
|
||||
</span>
|
||||
@else
|
||||
<span class="badge badge-secondary">
|
||||
<i class="fas fa-clock"></i> Mai sincronizzato
|
||||
</span>
|
||||
@endif
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<div class="custom-control custom-switch">
|
||||
<input type="checkbox" class="custom-control-input" id="is_active"
|
||||
name="is_active" value="1" {{ ($emailSettings->is_active ?? false) ? 'checked' : '' }}>
|
||||
<label class="custom-control-label" for="is_active">Account Email Attivo</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="tab-pane" id="email-firma">
|
||||
<div class="card card-primary">
|
||||
<div class="card-header">
|
||||
<h3 class="card-title">Firma Email</h3>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<div class="form-group">
|
||||
<div class="custom-control custom-switch">
|
||||
<input type="checkbox" class="custom-control-input" id="signature_enabled"
|
||||
name="signature_enabled" value="1" {{ ($emailSettings->signature_enabled ?? true) ? 'checked' : '' }}>
|
||||
<label class="custom-control-label" for="signature_enabled">Attiva firma automatica</label>
|
||||
</div>
|
||||
<small class="text-muted">Quando attivo, la firma verrà aggiunta in fondo a ogni email inviata</small>
|
||||
</div>
|
||||
<hr>
|
||||
<div class="form-group">
|
||||
<label for="signature">Testo Firma (formato HTML)</label>
|
||||
<div id="signature-editor" style="height: 200px;"></div>
|
||||
<input type="hidden" name="signature" id="signature_input" value="{{ $emailSettings->signature ?? '' }}">
|
||||
<small class="text-muted">Usa il riquadro sopra per comporre la tua firma. HTML supportato.</small>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="card-footer">
|
||||
<button type="submit" class="btn btn-primary">
|
||||
<i class="fas fa-save mr-1"></i> Salva Impostazioni Email
|
||||
</button>
|
||||
<button type="button" class="btn btn-info ml-2" onclick="testConnection()">
|
||||
<i class="fas fa-plug mr-1"></i> Test Connessione IMAP
|
||||
</button>
|
||||
<button type="button" class="btn btn-success ml-2" onclick="testSmtp()">
|
||||
<i class="fas fa-paper-plane mr-1"></i> Test Invio Email (SMTP)
|
||||
</button>
|
||||
<a href="{{ route('impostazioni.email.sync') }}" class="btn btn-warning ml-2">
|
||||
<i class="fas fa-sync mr-1"></i> Sincronizza Ora
|
||||
</a>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
{{-- === CALENDARIO === --}}
|
||||
<div class="tab-pane" id="calendario">
|
||||
<div class="card card-primary">
|
||||
<div class="card-header">
|
||||
<h3 class="card-title">Calendari Remoti</h3>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<p class="text-muted">Configura la sincronizzazione con calendari esterni (Google Calendar, CalDAV, ecc.).</p>
|
||||
<div class="alert alert-info">
|
||||
<i class="fas fa-info-circle"></i>
|
||||
<strong>Prossimamente:</strong> Potrai collegare calendari Google, Infomaniak o altri provider CalDAV per sincronizzare gli eventi del calendario.
|
||||
</div>
|
||||
<div class="text-center p-5 bg-light rounded">
|
||||
<i class="fas fa-calendar-plus fa-4x text-muted mb-3"></i>
|
||||
<p class="text-muted">Nessun calendario remoto configurato.</p>
|
||||
<p class="text-muted small">La funzionalità sarà disponibile in un prossimo aggiornamento.</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{{-- === TIPOLOGIE DOCUMENTI === --}}
|
||||
<div class="tab-pane active" id="tipologie">
|
||||
<div class="card card-primary">
|
||||
<div class="card-header">
|
||||
@@ -244,6 +542,82 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{{-- === TIPOLOGIE EVENTI === --}}
|
||||
<div class="tab-pane" id="tipologie-eventi">
|
||||
<div class="card card-primary">
|
||||
<div class="card-header">
|
||||
<h3 class="card-title">Tipologie Eventi</h3>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<p class="text-muted">Gestisci le tipologie disponibili per gli eventi del calendario.</p>
|
||||
|
||||
@if(session('success'))
|
||||
<div class="alert alert-success">{{ session('success') }}</div>
|
||||
@endif
|
||||
@if(session('error'))
|
||||
<div class="alert alert-danger">{{ session('error') }}</div>
|
||||
@endif
|
||||
|
||||
<div class="mb-3">
|
||||
<button type="button" class="btn btn-success btn-sm" data-toggle="modal" data-target="#addTipologiaEventoModal">
|
||||
<i class="fas fa-plus"></i> Nuova Tipologia
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<table class="table table-bordered table-striped" id="tipologie-eventi-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th style="width: 40px;">Ordine</th>
|
||||
<th>Nome</th>
|
||||
<th>Descrizione</th>
|
||||
<th style="width: 100px;">Stato</th>
|
||||
<th style="width: 150px;">Azioni</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody id="tipologie-eventi-sortable">
|
||||
@foreach($tipologieEventi as $tipologia)
|
||||
<tr data-id="{{ $tipologia->id }}">
|
||||
<td class="text-center">
|
||||
<i class="fas fa-arrows-alt handle" style="cursor: grab;"></i>
|
||||
</td>
|
||||
<td>
|
||||
<span class="tipologia-evento-nome">{{ $tipologia->nome }}</span>
|
||||
</td>
|
||||
<td>{{ $tipologia->descrizione ?? '-' }}</td>
|
||||
<td>
|
||||
@if($tipologia->attiva)
|
||||
<span class="badge badge-success">Attiva</span>
|
||||
@else
|
||||
<span class="badge badge-secondary">Disattivata</span>
|
||||
@endif
|
||||
</td>
|
||||
<td>
|
||||
<button type="button" class="btn btn-xs btn-warning" onclick="editTipologiaEvento({{ $tipologia->id }}, '{{ addslashes($tipologia->nome) }}', '{{ addslashes($tipologia->descrizione ?? '') }}', {{ $tipologia->attiva ? 'true' : 'false' }})">
|
||||
<i class="fas fa-edit"></i>
|
||||
</button>
|
||||
@if($tipologia->eventi()->count() == 0)
|
||||
<form method="POST" action="{{ route('impostazioni.tipologie-eventi.destroy', $tipologia->id) }}" style="display: inline;">
|
||||
@csrf
|
||||
@method('DELETE')
|
||||
<button type="submit" class="btn btn-xs btn-danger" onclick="return confirm('Eliminare questa tipologia?')">
|
||||
<i class="fas fa-trash"></i>
|
||||
</button>
|
||||
</form>
|
||||
@else
|
||||
<button type="button" class="btn btn-xs btn-danger" disabled title="Eventi associati: {{ $tipologia->eventi()->count() }}">
|
||||
<i class="fas fa-trash"></i>
|
||||
</button>
|
||||
@endif
|
||||
</td>
|
||||
</tr>
|
||||
@endforeach
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{{-- === TIPI DI RUOLO === --}}
|
||||
<div class="tab-pane" id="ruoli">
|
||||
<div class="card card-primary">
|
||||
<div class="card-header">
|
||||
@@ -318,84 +692,11 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="tab-pane" id="tipologie-eventi">
|
||||
<div class="card card-primary">
|
||||
<div class="card-header">
|
||||
<h3 class="card-title">Tipologie Eventi</h3>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<p class="text-muted">Gestisci le tipologie disponibili per gli eventi del calendario.</p>
|
||||
|
||||
@if(session('success'))
|
||||
<div class="alert alert-success">{{ session('success') }}</div>
|
||||
@endif
|
||||
@if(session('error'))
|
||||
<div class="alert alert-danger">{{ session('error') }}</div>
|
||||
@endif
|
||||
|
||||
<div class="mb-3">
|
||||
<button type="button" class="btn btn-success btn-sm" data-toggle="modal" data-target="#addTipologiaEventoModal">
|
||||
<i class="fas fa-plus"></i> Nuova Tipologia
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<table class="table table-bordered table-striped" id="tipologie-eventi-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th style="width: 40px;">Ordine</th>
|
||||
<th>Nome</th>
|
||||
<th>Descrizione</th>
|
||||
<th style="width: 100px;">Stato</th>
|
||||
<th style="width: 150px;">Azioni</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody id="tipologie-eventi-sortable">
|
||||
@foreach($tipologieEventi as $tipologia)
|
||||
<tr data-id="{{ $tipologia->id }}">
|
||||
<td class="text-center">
|
||||
<i class="fas fa-arrows-alt handle" style="cursor: grab;"></i>
|
||||
</td>
|
||||
<td>
|
||||
<span class="tipologia-evento-nome">{{ $tipologia->nome }}</span>
|
||||
</td>
|
||||
<td>{{ $tipologia->descrizione ?? '-' }}</td>
|
||||
<td>
|
||||
@if($tipologia->attiva)
|
||||
<span class="badge badge-success">Attiva</span>
|
||||
@else
|
||||
<span class="badge badge-secondary">Disattivata</span>
|
||||
@endif
|
||||
</td>
|
||||
<td>
|
||||
<button type="button" class="btn btn-xs btn-warning" onclick="editTipologiaEvento({{ $tipologia->id }}, '{{ addslashes($tipologia->nome) }}', '{{ addslashes($tipologia->descrizione ?? '') }}', {{ $tipologia->attiva ? 'true' : 'false' }})">
|
||||
<i class="fas fa-edit"></i>
|
||||
</button>
|
||||
@if($tipologia->eventi()->count() == 0)
|
||||
<form method="POST" action="{{ route('impostazioni.tipologie-eventi.destroy', $tipologia->id) }}" style="display: inline;">
|
||||
@csrf
|
||||
@method('DELETE')
|
||||
<button type="submit" class="btn btn-xs btn-danger" onclick="return confirm('Eliminare questa tipologia?')">
|
||||
<i class="fas fa-trash"></i>
|
||||
</button>
|
||||
</form>
|
||||
@else
|
||||
<button type="button" class="btn btn-xs btn-danger" disabled title="Eventi associati: {{ $tipologia->eventi()->count() }}">
|
||||
<i class="fas fa-trash"></i>
|
||||
</button>
|
||||
@endif
|
||||
</td>
|
||||
</tr>
|
||||
@endforeach
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{{-- ===== MODALI TIPOLOGIE DOCUMENTI ===== --}}
|
||||
<div class="modal fade" id="addTipologiaModal">
|
||||
<div class="modal-dialog">
|
||||
<div class="modal-content">
|
||||
@@ -460,6 +761,7 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{{-- ===== MODALI TIPI DI RUOLO ===== --}}
|
||||
<div class="modal fade" id="addRuoloModal">
|
||||
<div class="modal-dialog">
|
||||
<div class="modal-content">
|
||||
@@ -524,6 +826,7 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{{-- ===== MODALI TIPOLOGIE EVENTI ===== --}}
|
||||
<div class="modal fade" id="addTipologiaEventoModal">
|
||||
<div class="modal-dialog">
|
||||
<div class="modal-content">
|
||||
@@ -645,7 +948,6 @@ var sortableTipologie = Sortable.create(document.getElementById('tipologie-sorta
|
||||
$('#tipologie-sortable tr').each(function() {
|
||||
order.push($(this).data('id'));
|
||||
});
|
||||
|
||||
fetch('{{ route('impostazioni.tipologie.reorder') }}', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
@@ -665,7 +967,6 @@ var sortableRuoli = Sortable.create(document.getElementById('ruoli-sortable'), {
|
||||
$('#ruoli-sortable tr').each(function() {
|
||||
order.push($(this).data('id'));
|
||||
});
|
||||
|
||||
fetch('{{ route('impostazioni.ruoli.reorder') }}', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
@@ -685,7 +986,6 @@ var sortableTipologieEventi = Sortable.create(document.getElementById('tipologie
|
||||
$('#tipologie-eventi-sortable tr').each(function() {
|
||||
order.push($(this).data('id'));
|
||||
});
|
||||
|
||||
fetch('{{ route('impostazioni.tipologie-eventi.reorder') }}', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
@@ -706,5 +1006,148 @@ $('a[data-toggle="tab"]').on('shown.bs.tab', function (e) {
|
||||
sortableTipologieEventi.refresh();
|
||||
}
|
||||
});
|
||||
|
||||
// ===== EMAIL FUNCTIONS =====
|
||||
function testConnection() {
|
||||
$.ajax({
|
||||
url: '{{ route('impostazioni.email.test') }}',
|
||||
method: 'POST',
|
||||
headers: { 'X-CSRF-TOKEN': '{{ csrf_token() }}' },
|
||||
success: function(response) {
|
||||
if (response.success) {
|
||||
alert('✅ ' + response.message);
|
||||
} else {
|
||||
alert('❌ ' + response.message);
|
||||
}
|
||||
},
|
||||
error: function() {
|
||||
alert('❌ Errore nella richiesta');
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function testSmtp() {
|
||||
var email = prompt('Inserisci l\'indirizzo email a cui inviare il test:');
|
||||
if (!email) return;
|
||||
|
||||
if (!email.includes('@')) {
|
||||
alert('⚠️ Indirizzo email non valido');
|
||||
return;
|
||||
}
|
||||
|
||||
var btn = event.target;
|
||||
btn.disabled = true;
|
||||
btn.innerHTML = '<i class="fas fa-spinner fa-spin"></i> Invio...';
|
||||
|
||||
$.ajax({
|
||||
url: '{{ route('impostazioni.email.testSmtp') }}',
|
||||
method: 'POST',
|
||||
headers: { 'X-CSRF-TOKEN': '{{ csrf_token() }}' },
|
||||
data: { email: email },
|
||||
timeout: 30000,
|
||||
success: function(response) {
|
||||
btn.disabled = false;
|
||||
btn.innerHTML = '<i class="fas fa-paper-plane mr-1"></i> Test Invio Email (SMTP)';
|
||||
if (response.success) {
|
||||
alert('✅ ' + response.message);
|
||||
} else {
|
||||
alert('❌ ' + response.message);
|
||||
}
|
||||
},
|
||||
error: function(xhr, status, error) {
|
||||
btn.disabled = false;
|
||||
btn.innerHTML = '<i class="fas fa-paper-plane mr-1"></i> Test Invio Email (SMTP)';
|
||||
if (xhr.responseJSON && xhr.responseJSON.message) {
|
||||
alert('❌ ' + xhr.responseJSON.message);
|
||||
} else {
|
||||
alert('❌ Errore: ' + status + ' - ' + error);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function createEditor() {
|
||||
var container = document.querySelector('#signature-editor');
|
||||
if (!container || container.querySelector('.ql-toolbar')) return;
|
||||
|
||||
var quill = new Quill('#signature-editor', {
|
||||
theme: 'snow',
|
||||
modules: {
|
||||
toolbar: [
|
||||
['bold', 'italic', 'underline'],
|
||||
[{ 'color': [] }, { 'background': [] }],
|
||||
[{ 'header': 1 }, { 'header': 2 }, { 'header': 3 }],
|
||||
[{ 'align': [] }],
|
||||
[{ 'list': 'ordered'}, { 'list': 'bullet' }],
|
||||
['link', 'image'],
|
||||
['clean', 'code']
|
||||
]
|
||||
}
|
||||
});
|
||||
|
||||
var signatureInput = document.getElementById('signature_input');
|
||||
var isEditingHtml = false;
|
||||
|
||||
quill.getModule('toolbar').addHandler('code', function() {
|
||||
if (!isEditingHtml) {
|
||||
var textarea = document.createElement('textarea');
|
||||
textarea.className = 'ql-editor';
|
||||
textarea.style.cssText = 'font-family: monospace; min-height: 150px; width: 100%;';
|
||||
textarea.value = signatureInput.value;
|
||||
quill.root.innerHTML = '';
|
||||
quill.root.appendChild(textarea);
|
||||
textarea.focus();
|
||||
isEditingHtml = true;
|
||||
} else {
|
||||
var textarea = quill.root.querySelector('textarea');
|
||||
if (textarea) {
|
||||
signatureInput.value = textarea.value;
|
||||
quill.root.innerHTML = textarea.value;
|
||||
}
|
||||
isEditingHtml = false;
|
||||
}
|
||||
});
|
||||
|
||||
if (signatureInput.value) {
|
||||
quill.root.innerHTML = signatureInput.value;
|
||||
}
|
||||
|
||||
quill.on('text-change', function() {
|
||||
if (!isEditingHtml) {
|
||||
signatureInput.value = quill.root.innerHTML;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function initSignatureEditor() {
|
||||
if (document.querySelector('#signature-editor .ql-toolbar')) return;
|
||||
|
||||
if (typeof Quill === 'undefined') {
|
||||
var script = document.createElement('script');
|
||||
script.src = 'https://cdn.quilljs.com/1.3.7/quill.min.js';
|
||||
script.onload = createEditor;
|
||||
document.head.appendChild(script);
|
||||
|
||||
var link = document.createElement('link');
|
||||
link.rel = 'stylesheet';
|
||||
link.href = 'https://cdn.quilljs.com/1.3.7/quill.snow.css';
|
||||
document.head.appendChild(link);
|
||||
} else {
|
||||
createEditor();
|
||||
}
|
||||
}
|
||||
|
||||
$('a[data-toggle="tab"]').on('shown.bs.tab', function(e) {
|
||||
var target = $(e.target).attr('href');
|
||||
if (target === '#email-firma' || target === '#email') {
|
||||
setTimeout(initSignatureEditor, 100);
|
||||
}
|
||||
});
|
||||
|
||||
if (window.location.hash === '#email') {
|
||||
$(document).ready(function() {
|
||||
setTimeout(initSignatureEditor, 300);
|
||||
});
|
||||
}
|
||||
</script>
|
||||
@endsection
|
||||
@endsection
|
||||
|
||||
@@ -202,23 +202,15 @@ $appName = \App\Models\AppSetting::getAppName() ?? 'Glastree';
|
||||
</a>
|
||||
</li>
|
||||
@endif
|
||||
@if(Auth::user()->isSuperAdmin())
|
||||
<li class="nav-item has-treeview">
|
||||
<a href="/impostazioni" class="nav-link">
|
||||
@if(Auth::user()->canManage('settings'))
|
||||
<li class="nav-item">
|
||||
<a href="/impostazioni" class="nav-link {{ request()->is('impostazioni*') ? 'active' : '' }}">
|
||||
<i class="nav-icon fas fa-cog"></i>
|
||||
<p>Impostazioni <i class="right fas fa-angle-left"></i></p>
|
||||
<p>Impostazioni</p>
|
||||
</a>
|
||||
<ul class="nav nav-treeview">
|
||||
<li class="nav-item">
|
||||
<a href="/impostazioni/email" class="nav-link">
|
||||
<i class="nav-icon fas fa-envelope"></i>
|
||||
<p>Impostazione Email</p>
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a href="/admin/utenti" class="nav-link">
|
||||
<a href="/admin/utenti" class="nav-link {{ request()->is('admin*') ? 'active' : '' }}">
|
||||
<i class="nav-icon fas fa-users-cog"></i>
|
||||
<p>Admin</p>
|
||||
</a>
|
||||
|
||||
@@ -81,6 +81,8 @@ Route::delete('gruppi/{gruppo}/avatar', [\App\Http\Controllers\GruppoAvatarContr
|
||||
Route::resource('gruppi', GruppoController::class)->middleware('auth');
|
||||
Route::get('eventi/calendar', [EventoController::class, 'calendar'])->name('eventi.calendar')->middleware('auth');
|
||||
Route::get('eventi/calendar/events', [EventoController::class, 'calendarEvents'])->name('eventi.calendar.events')->middleware('auth');
|
||||
Route::get('eventi/{evento}/export-ics', [EventoController::class, 'exportIcs'])->name('eventi.export-ics')->middleware('auth');
|
||||
Route::post('eventi/export-ics', [EventoController::class, 'exportSelectedIcs'])->name('eventi.export-selected-ics')->middleware('auth');
|
||||
Route::post('eventi/mass-elimina', [EventoController::class, 'massElimina'])->middleware('auth');
|
||||
Route::resource('eventi', EventoController::class)->middleware('auth');
|
||||
Route::post('eventi/{evento}/documenti', [EventoDocumentoController::class, 'store'])->middleware('auth');
|
||||
|
||||
@@ -15,7 +15,7 @@
|
||||
<h3 class="card-title">Dati Utente</h3>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<form method="POST" action="<?php echo e(url('/admin/utenti/' . $user->id)); ?>">
|
||||
<form method="POST" action="/admin/utenti/<?php echo e($user->id); ?>">
|
||||
<?php echo csrf_field(); ?> <?php echo method_field('PUT'); ?>
|
||||
<div class="form-group">
|
||||
<label>Nome *</label>
|
||||
@@ -36,9 +36,9 @@
|
||||
<div class="form-group">
|
||||
<label>Stato</label>
|
||||
<select name="status" class="form-control" required>
|
||||
<option value="active" <?php echo e($user->status === 'active' ? 'selected' : ''); ?>>Attivo</option>
|
||||
<option value="suspended" <?php echo e($user->status === 'suspended' ? 'selected' : ''); ?>>Sospeso</option>
|
||||
<option value="pending" <?php echo e($user->status === 'pending' ? 'selected' : ''); ?>>In attesa</option>
|
||||
<option value="active" <?php echo e(old('status', $user->status) === 'active' ? 'selected' : ''); ?>>Attivo</option>
|
||||
<option value="suspended" <?php echo e(old('status', $user->status) === 'suspended' ? 'selected' : ''); ?>>Sospeso</option>
|
||||
<option value="pending" <?php echo e(old('status', $user->status) === 'pending' ? 'selected' : ''); ?>>In attesa</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
@@ -46,16 +46,17 @@
|
||||
<select name="role_preset_id" class="form-control" id="rolePresetSelect">
|
||||
<option value="">Personalizzato</option>
|
||||
<?php $__currentLoopData = $rolePresets; $__env->addLoop($__currentLoopData); foreach($__currentLoopData as $preset): $__env->incrementLoopIndices(); $loop = $__env->getLastLoop(); ?>
|
||||
<option value="<?php echo e($preset->id); ?>" <?php echo e($user->role_preset_id == $preset->id ? 'selected' : ''); ?>>
|
||||
<option value="<?php echo e($preset->id); ?>" <?php echo e(old('role_preset_id', $user->role_preset_id) == $preset->id ? 'selected' : ''); ?>>
|
||||
<?php echo e($preset->name); ?> - <?php echo e($preset->description); ?>
|
||||
|
||||
</option>
|
||||
<?php endforeach; $__env->popLoop(); $loop = $__env->getLastLoop(); ?>
|
||||
</select>
|
||||
</div>
|
||||
<input type="hidden" name="is_admin" value="0">
|
||||
<div class="form-group">
|
||||
<div class="custom-control custom-switch">
|
||||
<input type="checkbox" class="custom-control-input" id="isAdmin" name="is_admin" value="1" <?php echo e($user->isSuperAdmin() ? 'checked' : ''); ?>>
|
||||
<input type="checkbox" class="custom-control-input" id="isAdmin" name="is_admin" value="1" <?php echo e(old('is_admin', $user->isSuperAdmin() ? '1' : '0') === '1' ? 'checked' : ''); ?>>
|
||||
<label class="custom-control-label" for="isAdmin">Superadmin (bypass ACL)</label>
|
||||
</div>
|
||||
</div>
|
||||
@@ -72,7 +73,7 @@
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<?php $__currentLoopData = ['individui', 'gruppi', 'eventi', 'documenti', 'mailing', 'viste']; $__env->addLoop($__currentLoopData); foreach($__currentLoopData as $module): $__env->incrementLoopIndices(); $loop = $__env->getLastLoop(); ?>
|
||||
<?php $__currentLoopData = \App\Models\User::MODULES; $__env->addLoop($__currentLoopData); foreach($__currentLoopData as $module): $__env->incrementLoopIndices(); $loop = $__env->getLastLoop(); ?>
|
||||
<tr>
|
||||
<td><strong><?php echo e(ucfirst($module)); ?></strong></td>
|
||||
<td><input type="radio" name="permissions[<?php echo e($module); ?>]" value="0" <?php echo e(($user->permissions[$module] ?? 0) == 0 ? 'checked' : ''); ?>></td>
|
||||
|
||||
@@ -311,6 +311,9 @@
|
||||
<i class="fas fa-trash mr-1"></i> Elimina
|
||||
</button>
|
||||
</form>
|
||||
<a href="/eventi/<?php echo e($evento->id); ?>/export-ics" class="btn btn-success">
|
||||
<i class="fas fa-calendar-plus mr-1"></i> Esporta ICS
|
||||
</a>
|
||||
<a href="/eventi" class="btn btn-secondary">
|
||||
<i class="fas fa-arrow-left mr-1"></i> Torna all'elenco
|
||||
</a>
|
||||
|
||||
@@ -1,44 +0,0 @@
|
||||
<?php if($paginator->hasPages()): ?>
|
||||
<ul class="pagination justify-content-center" role="navigation">
|
||||
|
||||
<?php if($paginator->onFirstPage()): ?>
|
||||
<li class="page-item disabled" aria-disabled="true" aria-label="<?php echo app('translator')->get('pagination.previous'); ?>">
|
||||
<span class="page-link" aria-hidden="true">‹</span>
|
||||
</li>
|
||||
<?php else: ?>
|
||||
<li class="page-item">
|
||||
<a class="page-link" href="<?php echo e($paginator->previousPageUrl()); ?>" rel="prev" aria-label="<?php echo app('translator')->get('pagination.previous'); ?>">‹</a>
|
||||
</li>
|
||||
<?php endif; ?>
|
||||
|
||||
|
||||
<?php $__currentLoopData = $elements; $__env->addLoop($__currentLoopData); foreach($__currentLoopData as $element): $__env->incrementLoopIndices(); $loop = $__env->getLastLoop(); ?>
|
||||
|
||||
<?php if(is_string($element)): ?>
|
||||
<li class="page-item disabled" aria-disabled="true"><span class="page-link"><?php echo e($element); ?></span></li>
|
||||
<?php endif; ?>
|
||||
|
||||
|
||||
<?php if(is_array($element)): ?>
|
||||
<?php $__currentLoopData = $element; $__env->addLoop($__currentLoopData); foreach($__currentLoopData as $page => $url): $__env->incrementLoopIndices(); $loop = $__env->getLastLoop(); ?>
|
||||
<?php if($page == $paginator->currentPage()): ?>
|
||||
<li class="page-item active" aria-current="page"><span class="page-link"><?php echo e($page); ?></span></li>
|
||||
<?php elseif($page >= $paginator->currentPage() - 2 && $page <= $paginator->currentPage() + 2): ?>
|
||||
<li class="page-item"><a class="page-link" href="<?php echo e($url); ?>"><?php echo e($page); ?></a></li>
|
||||
<?php endif; ?>
|
||||
<?php endforeach; $__env->popLoop(); $loop = $__env->getLastLoop(); ?>
|
||||
<?php endif; ?>
|
||||
<?php endforeach; $__env->popLoop(); $loop = $__env->getLastLoop(); ?>
|
||||
|
||||
|
||||
<?php if($paginator->hasMorePages()): ?>
|
||||
<li class="page-item">
|
||||
<a class="page-link" href="<?php echo e($paginator->nextPageUrl()); ?>" rel="next" aria-label="<?php echo app('translator')->get('pagination.next'); ?>">›</a>
|
||||
</li>
|
||||
<?php else: ?>
|
||||
<li class="page-item disabled" aria-disabled="true" aria-label="<?php echo app('translator')->get('pagination.next'); ?>">
|
||||
<span class="page-link" aria-hidden="true">›</span>
|
||||
</li>
|
||||
<?php endif; ?>
|
||||
</ul>
|
||||
<?php endif; ?><?php /**PATH /var/www/html/glastree/resources/views/vendor/pagination/simple-bootstrap-4.blade.php ENDPATH**/ ?>
|
||||
@@ -16,6 +16,12 @@
|
||||
<a href="<?php echo e(route('eventi.create')); ?>" class="btn btn-success btn-sm">
|
||||
<i class="fas fa-plus mr-1"></i> Nuovo Evento
|
||||
</a>
|
||||
<form action="<?php echo e(route('eventi.export-selected-ics')); ?>" method="POST" class="d-inline">
|
||||
<?php echo csrf_field(); ?>
|
||||
<button type="submit" class="btn btn-success btn-sm">
|
||||
<i class="fas fa-calendar-plus mr-1"></i> Esporta ICS
|
||||
</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
|
||||
@@ -21,20 +21,27 @@
|
||||
<a href="#logo" class="list-group-item list-group-item-action" data-toggle="tab">
|
||||
<i class="nav-icon fas fa-image mr-2"></i> Logo
|
||||
</a>
|
||||
<a href="#email" class="list-group-item list-group-item-action" data-toggle="tab">
|
||||
<i class="nav-icon fas fa-envelope mr-2"></i> Email
|
||||
</a>
|
||||
<a href="#calendario" class="list-group-item list-group-item-action" data-toggle="tab">
|
||||
<i class="nav-icon fas fa-calendar-alt mr-2"></i> Calendario
|
||||
</a>
|
||||
<a href="#tipologie" class="list-group-item list-group-item-action" data-toggle="tab">
|
||||
<i class="nav-icon fas fa-file mr-2"></i> Tipologie Documenti
|
||||
</a>
|
||||
<a href="#ruoli" class="list-group-item list-group-item-action" data-toggle="tab">
|
||||
<i class="nav-icon fas fa-users-cog mr-2"></i> Tipi di Ruolo
|
||||
</a>
|
||||
<a href="#tipologie-eventi" class="list-group-item list-group-item-action" data-toggle="tab">
|
||||
<i class="nav-icon fas fa-calendar mr-2"></i> Tipologie Eventi
|
||||
</a>
|
||||
<a href="#ruoli" class="list-group-item list-group-item-action" data-toggle="tab">
|
||||
<i class="nav-icon fas fa-users-cog mr-2"></i> Tipi di Ruolo
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-9">
|
||||
<div class="tab-content">
|
||||
|
||||
<div class="tab-pane" id="applicazione">
|
||||
<div class="card card-primary">
|
||||
<div class="card-header">
|
||||
@@ -49,12 +56,11 @@
|
||||
|
||||
<form method="POST" action="<?php echo e(route('impostazioni.app-settings.save')); ?>">
|
||||
<?php echo csrf_field(); ?>
|
||||
<?php echo method_field('POST'); ?>
|
||||
<div class="row">
|
||||
<div class="col-md-6">
|
||||
<div class="form-group">
|
||||
<label for="nome_applicazione">Nome Applicazione</label>
|
||||
<input type="text" name="nome_applicazione" id="nome_applicazione" class="form-control"
|
||||
<input type="text" name="nome_applicazione" id="nome_applicazione" class="form-control"
|
||||
value="<?php echo e(old('nome_applicazione', $appSettings->nome_applicazione ?? '')); ?>"
|
||||
placeholder="es. Glastree">
|
||||
<small class="text-muted">Nome mostrato nel titolo, sidebar e login</small>
|
||||
@@ -63,7 +69,7 @@
|
||||
<div class="col-md-6">
|
||||
<div class="form-group">
|
||||
<label for="nome_organizzazione">Nome Organizzazione</label>
|
||||
<input type="text" name="nome_organizzazione" id="nome_organizzazione" class="form-control"
|
||||
<input type="text" name="nome_organizzazione" id="nome_organizzazione" class="form-control"
|
||||
value="<?php echo e(old('nome_organizzazione', $appSettings->nome_organizzazione ?? '')); ?>"
|
||||
placeholder="es. Parrocchia Santa Maria">
|
||||
<small class="text-muted">Nome organizzazione mostrato sotto il logo nel login</small>
|
||||
@@ -73,7 +79,7 @@
|
||||
|
||||
<div class="form-group">
|
||||
<label for="footer_text">Testo Footer</label>
|
||||
<input type="text" name="footer_text" id="footer_text" class="form-control"
|
||||
<input type="text" name="footer_text" id="footer_text" class="form-control"
|
||||
value="<?php echo e(old('footer_text', $appSettings->footer_text ?? '')); ?>"
|
||||
placeholder="es. © 2026 Parrocchia Santa Maria">
|
||||
<small class="text-muted">Lascia vuoto per usare il formato predefinito: © {anno} {nome_applicazione}</small>
|
||||
@@ -83,7 +89,7 @@
|
||||
<div class="col-md-6">
|
||||
<div class="form-group">
|
||||
<label for="app_version">Versione Applicazione</label>
|
||||
<input type="text" name="app_version" id="app_version" class="form-control"
|
||||
<input type="text" name="app_version" id="app_version" class="form-control"
|
||||
value="<?php echo e(old('app_version', $appSettings->app_version ?? '')); ?>"
|
||||
placeholder="es. 1.0.0">
|
||||
<small class="text-muted">Versione mostrata nel footer</small>
|
||||
@@ -116,6 +122,7 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<div class="tab-pane" id="logo">
|
||||
<div class="card card-primary">
|
||||
<div class="card-header">
|
||||
@@ -168,6 +175,298 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<div class="tab-pane" id="email">
|
||||
<div class="card card-secondary">
|
||||
<div class="card-header">
|
||||
<h3 class="card-title">Sezioni Email</h3>
|
||||
</div>
|
||||
<div class="card-body p-0">
|
||||
<ul class="nav nav-pills p-2">
|
||||
<li class="nav-item"><a href="#email-imap" class="nav-link active" data-toggle="tab">Server IMAP</a></li>
|
||||
<li class="nav-item"><a href="#email-smtp" class="nav-link" data-toggle="tab">Server SMTP</a></li>
|
||||
<li class="nav-item"><a href="#email-account" class="nav-link" data-toggle="tab">Account</a></li>
|
||||
<li class="nav-item"><a href="#email-sync" class="nav-link" data-toggle="tab">Sincronizzazione</a></li>
|
||||
<li class="nav-item"><a href="#email-firma" class="nav-link" data-toggle="tab">Firma</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<form method="POST" action="<?php echo e(route('impostazioni.email.save')); ?>">
|
||||
<?php echo csrf_field(); ?>
|
||||
<input type="hidden" name="id" value="<?php echo e($emailSettings->id ?? 1); ?>">
|
||||
|
||||
<div class="tab-content">
|
||||
<div class="tab-pane active" id="email-imap">
|
||||
<div class="card card-primary">
|
||||
<div class="card-header">
|
||||
<h3 class="card-title">Configurazione Server IMAP</h3>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<div class="row">
|
||||
<div class="col-md-6">
|
||||
<div class="form-group">
|
||||
<label for="imap_host">Host IMAP</label>
|
||||
<input type="text" name="imap_host" id="imap_host" class="form-control"
|
||||
value="<?php echo e($emailSettings->imap_host ?? 'imap.gmail.com'); ?>"
|
||||
placeholder="es. imap.gmail.com">
|
||||
<small class="text-muted">Gmail: imap.gmail.com | Outlook: outlook.office365.com</small>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-3">
|
||||
<div class="form-group">
|
||||
<label for="imap_port">Porta</label>
|
||||
<input type="number" name="imap_port" id="imap_port" class="form-control"
|
||||
value="<?php echo e($emailSettings->imap_port ?? 993); ?>">
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-3">
|
||||
<div class="form-group">
|
||||
<label for="imap_encryption">Crittografia</label>
|
||||
<select name="imap_encryption" id="imap_encryption" class="form-control">
|
||||
<option value="ssl" <?php echo e(($emailSettings->imap_encryption ?? 'ssl') == 'ssl' ? 'selected' : ''); ?>>SSL</option>
|
||||
<option value="tls" <?php echo e(($emailSettings->imap_encryption ?? '') == 'tls' ? 'selected' : ''); ?>>TLS</option>
|
||||
<option value="none" <?php echo e(($emailSettings->imap_encryption ?? '') == 'none' ? 'selected' : ''); ?>>Nessuna</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="tab-pane" id="email-smtp">
|
||||
<div class="card card-primary">
|
||||
<div class="card-header">
|
||||
<h3 class="card-title">Configurazione Server SMTP (per invio email)</h3>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<div class="alert alert-info">
|
||||
<i class="fas fa-info-circle"></i>
|
||||
<strong>Nota:</strong> Per inviare email devi configurare il server SMTP.
|
||||
Per Gmail, usa <code>smtp.gmail.com</code> sulla porta <code>587</code> con TLS
|
||||
e genera una <a href="https://support.google.com/accounts/answer/185833" target="_blank">App Password</a>.
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-md-6">
|
||||
<div class="form-group">
|
||||
<label for="smtp_host">Host SMTP</label>
|
||||
<input type="text" name="smtp_host" id="smtp_host" class="form-control"
|
||||
value="<?php echo e($emailSettings->smtp_host ?? 'smtp.gmail.com'); ?>"
|
||||
placeholder="es. smtp.gmail.com">
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-3">
|
||||
<div class="form-group">
|
||||
<label for="smtp_port">Porta</label>
|
||||
<input type="number" name="smtp_port" id="smtp_port" class="form-control"
|
||||
value="<?php echo e($emailSettings->smtp_port ?? 587); ?>">
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-3">
|
||||
<div class="form-group">
|
||||
<label for="smtp_encryption">Crittografia</label>
|
||||
<select name="smtp_encryption" id="smtp_encryption" class="form-control">
|
||||
<option value="tls" <?php echo e(($emailSettings->smtp_encryption ?? 'tls') == 'tls' ? 'selected' : ''); ?>>TLS</option>
|
||||
<option value="ssl" <?php echo e(($emailSettings->smtp_encryption ?? '') == 'ssl' ? 'selected' : ''); ?>>SSL</option>
|
||||
<option value="none" <?php echo e(($emailSettings->smtp_encryption ?? '') == 'none' ? 'selected' : ''); ?>>Nessuna</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-md-6">
|
||||
<div class="form-group">
|
||||
<label for="smtp_username">Username SMTP</label>
|
||||
<input type="text" name="smtp_username" id="smtp_username" class="form-control"
|
||||
value="<?php echo e($emailSettings->smtp_username ?? ''); ?>"
|
||||
placeholder="lascia vuoto per usare l'account IMAP">
|
||||
<small class="text-muted">Generalmente uguale all'account email</small>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<div class="form-group">
|
||||
<label for="smtp_password">Password SMTP / App Password</label>
|
||||
<input type="password" name="smtp_password" id="smtp_password" class="form-control"
|
||||
placeholder="<?php echo e($emailSettings->smtp_password ? '••••••••' : ''); ?>">
|
||||
<small class="text-muted">Lascia vuoto per usare la password IMAP</small>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="tab-pane" id="email-account">
|
||||
<div class="card card-primary">
|
||||
<div class="card-header">
|
||||
<h3 class="card-title">Credenziali Account</h3>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<div class="row">
|
||||
<div class="col-md-6">
|
||||
<div class="form-group">
|
||||
<label for="imap_username">Username/Email</label>
|
||||
<input type="text" name="imap_username" id="imap_username" class="form-control"
|
||||
value="<?php echo e($emailSettings->imap_username ?? ''); ?>"
|
||||
placeholder="es. tuo@gmail.com">
|
||||
<small class="text-muted">Per Gmail usa l'indirizzo completo</small>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<div class="form-group">
|
||||
<label for="imap_password">Password / App Password</label>
|
||||
<input type="password" name="imap_password" id="imap_password" class="form-control" placeholder="">
|
||||
<small class="text-muted">
|
||||
<a href="https://support.google.com/accounts/answer/185833" target="_blank">Per Gmail: genera una App Password</a>
|
||||
</small>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<hr>
|
||||
<div class="row">
|
||||
<div class="col-md-6">
|
||||
<div class="form-group">
|
||||
<label for="email_address">Email Indirizzo</label>
|
||||
<input type="email" name="email_address" id="email_address" class="form-control"
|
||||
value="<?php echo e($emailSettings->email_address ?? ''); ?>">
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<div class="form-group">
|
||||
<label for="email_name">Nome Visualizzato</label>
|
||||
<input type="text" name="email_name" id="email_name" class="form-control"
|
||||
value="<?php echo e($emailSettings->email_name ?? ''); ?>"
|
||||
placeholder="es. Nome Cognome">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-md-6">
|
||||
<div class="form-group">
|
||||
<label for="reply_to">Email Risposta (Reply-To)</label>
|
||||
<input type="email" name="reply_to" id="reply_to" class="form-control"
|
||||
value="<?php echo e($emailSettings->reply_to ?? ''); ?>">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="tab-pane" id="email-sync">
|
||||
<div class="card card-primary">
|
||||
<div class="card-header">
|
||||
<h3 class="card-title">Sincronizzazione</h3>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<div class="row">
|
||||
<div class="col-md-6">
|
||||
<div class="form-group">
|
||||
<label for="sync_interval_minutes">Intervallo Sync (minuti)</label>
|
||||
<select name="sync_interval_minutes" id="sync_interval_minutes" class="form-control">
|
||||
<option value="1" <?php echo e(($emailSettings->sync_interval_minutes ?? 5) == 1 ? 'selected' : ''); ?>>1 minuto</option>
|
||||
<option value="5" <?php echo e(($emailSettings->sync_interval_minutes ?? 5) == 5 ? 'selected' : ''); ?>>5 minuti</option>
|
||||
<option value="15" <?php echo e(($emailSettings->sync_interval_minutes ?? 5) == 15 ? 'selected' : ''); ?>>15 minuti</option>
|
||||
<option value="30" <?php echo e(($emailSettings->sync_interval_minutes ?? 5) == 30 ? 'selected' : ''); ?>>30 minuti</option>
|
||||
<option value="60" <?php echo e(($emailSettings->sync_interval_minutes ?? 5) == 60 ? 'selected' : ''); ?>>1 ora</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<div class="form-group">
|
||||
<label>Stato Connessione</label>
|
||||
<div class="mt-2">
|
||||
<?php if($emailSettings->last_sync_at): ?>
|
||||
<span class="badge badge-success">
|
||||
<i class="fas fa-check-circle"></i> Ultimo sync: <?php echo e($emailSettings->last_sync_at->format('d/m/Y H:i')); ?>
|
||||
|
||||
</span>
|
||||
<?php else: ?>
|
||||
<span class="badge badge-secondary">
|
||||
<i class="fas fa-clock"></i> Mai sincronizzato
|
||||
</span>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<div class="custom-control custom-switch">
|
||||
<input type="checkbox" class="custom-control-input" id="is_active"
|
||||
name="is_active" value="1" <?php echo e(($emailSettings->is_active ?? false) ? 'checked' : ''); ?>>
|
||||
<label class="custom-control-label" for="is_active">Account Email Attivo</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="tab-pane" id="email-firma">
|
||||
<div class="card card-primary">
|
||||
<div class="card-header">
|
||||
<h3 class="card-title">Firma Email</h3>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<div class="form-group">
|
||||
<div class="custom-control custom-switch">
|
||||
<input type="checkbox" class="custom-control-input" id="signature_enabled"
|
||||
name="signature_enabled" value="1" <?php echo e(($emailSettings->signature_enabled ?? true) ? 'checked' : ''); ?>>
|
||||
<label class="custom-control-label" for="signature_enabled">Attiva firma automatica</label>
|
||||
</div>
|
||||
<small class="text-muted">Quando attivo, la firma verrà aggiunta in fondo a ogni email inviata</small>
|
||||
</div>
|
||||
<hr>
|
||||
<div class="form-group">
|
||||
<label for="signature">Testo Firma (formato HTML)</label>
|
||||
<div id="signature-editor" style="height: 200px;"></div>
|
||||
<input type="hidden" name="signature" id="signature_input" value="<?php echo e($emailSettings->signature ?? ''); ?>">
|
||||
<small class="text-muted">Usa il riquadro sopra per comporre la tua firma. HTML supportato.</small>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="card-footer">
|
||||
<button type="submit" class="btn btn-primary">
|
||||
<i class="fas fa-save mr-1"></i> Salva Impostazioni Email
|
||||
</button>
|
||||
<button type="button" class="btn btn-info ml-2" onclick="testConnection()">
|
||||
<i class="fas fa-plug mr-1"></i> Test Connessione IMAP
|
||||
</button>
|
||||
<button type="button" class="btn btn-success ml-2" onclick="testSmtp()">
|
||||
<i class="fas fa-paper-plane mr-1"></i> Test Invio Email (SMTP)
|
||||
</button>
|
||||
<a href="<?php echo e(route('impostazioni.email.sync')); ?>" class="btn btn-warning ml-2">
|
||||
<i class="fas fa-sync mr-1"></i> Sincronizza Ora
|
||||
</a>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
|
||||
<div class="tab-pane" id="calendario">
|
||||
<div class="card card-primary">
|
||||
<div class="card-header">
|
||||
<h3 class="card-title">Calendari Remoti</h3>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<p class="text-muted">Configura la sincronizzazione con calendari esterni (Google Calendar, CalDAV, ecc.).</p>
|
||||
<div class="alert alert-info">
|
||||
<i class="fas fa-info-circle"></i>
|
||||
<strong>Prossimamente:</strong> Potrai collegare calendari Google, Infomaniak o altri provider CalDAV per sincronizzare gli eventi del calendario.
|
||||
</div>
|
||||
<div class="text-center p-5 bg-light rounded">
|
||||
<i class="fas fa-calendar-plus fa-4x text-muted mb-3"></i>
|
||||
<p class="text-muted">Nessun calendario remoto configurato.</p>
|
||||
<p class="text-muted small">La funzionalità sarà disponibile in un prossimo aggiornamento.</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<div class="tab-pane active" id="tipologie">
|
||||
<div class="card card-primary">
|
||||
<div class="card-header">
|
||||
@@ -242,6 +541,82 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<div class="tab-pane" id="tipologie-eventi">
|
||||
<div class="card card-primary">
|
||||
<div class="card-header">
|
||||
<h3 class="card-title">Tipologie Eventi</h3>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<p class="text-muted">Gestisci le tipologie disponibili per gli eventi del calendario.</p>
|
||||
|
||||
<?php if(session('success')): ?>
|
||||
<div class="alert alert-success"><?php echo e(session('success')); ?></div>
|
||||
<?php endif; ?>
|
||||
<?php if(session('error')): ?>
|
||||
<div class="alert alert-danger"><?php echo e(session('error')); ?></div>
|
||||
<?php endif; ?>
|
||||
|
||||
<div class="mb-3">
|
||||
<button type="button" class="btn btn-success btn-sm" data-toggle="modal" data-target="#addTipologiaEventoModal">
|
||||
<i class="fas fa-plus"></i> Nuova Tipologia
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<table class="table table-bordered table-striped" id="tipologie-eventi-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th style="width: 40px;">Ordine</th>
|
||||
<th>Nome</th>
|
||||
<th>Descrizione</th>
|
||||
<th style="width: 100px;">Stato</th>
|
||||
<th style="width: 150px;">Azioni</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody id="tipologie-eventi-sortable">
|
||||
<?php $__currentLoopData = $tipologieEventi; $__env->addLoop($__currentLoopData); foreach($__currentLoopData as $tipologia): $__env->incrementLoopIndices(); $loop = $__env->getLastLoop(); ?>
|
||||
<tr data-id="<?php echo e($tipologia->id); ?>">
|
||||
<td class="text-center">
|
||||
<i class="fas fa-arrows-alt handle" style="cursor: grab;"></i>
|
||||
</td>
|
||||
<td>
|
||||
<span class="tipologia-evento-nome"><?php echo e($tipologia->nome); ?></span>
|
||||
</td>
|
||||
<td><?php echo e($tipologia->descrizione ?? '-'); ?></td>
|
||||
<td>
|
||||
<?php if($tipologia->attiva): ?>
|
||||
<span class="badge badge-success">Attiva</span>
|
||||
<?php else: ?>
|
||||
<span class="badge badge-secondary">Disattivata</span>
|
||||
<?php endif; ?>
|
||||
</td>
|
||||
<td>
|
||||
<button type="button" class="btn btn-xs btn-warning" onclick="editTipologiaEvento(<?php echo e($tipologia->id); ?>, '<?php echo e(addslashes($tipologia->nome)); ?>', '<?php echo e(addslashes($tipologia->descrizione ?? '')); ?>', <?php echo e($tipologia->attiva ? 'true' : 'false'); ?>)">
|
||||
<i class="fas fa-edit"></i>
|
||||
</button>
|
||||
<?php if($tipologia->eventi()->count() == 0): ?>
|
||||
<form method="POST" action="<?php echo e(route('impostazioni.tipologie-eventi.destroy', $tipologia->id)); ?>" style="display: inline;">
|
||||
<?php echo csrf_field(); ?>
|
||||
<?php echo method_field('DELETE'); ?>
|
||||
<button type="submit" class="btn btn-xs btn-danger" onclick="return confirm('Eliminare questa tipologia?')">
|
||||
<i class="fas fa-trash"></i>
|
||||
</button>
|
||||
</form>
|
||||
<?php else: ?>
|
||||
<button type="button" class="btn btn-xs btn-danger" disabled title="Eventi associati: <?php echo e($tipologia->eventi()->count()); ?>">
|
||||
<i class="fas fa-trash"></i>
|
||||
</button>
|
||||
<?php endif; ?>
|
||||
</td>
|
||||
</tr>
|
||||
<?php endforeach; $__env->popLoop(); $loop = $__env->getLastLoop(); ?>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<div class="tab-pane" id="ruoli">
|
||||
<div class="card card-primary">
|
||||
<div class="card-header">
|
||||
@@ -316,84 +691,11 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="tab-pane" id="tipologie-eventi">
|
||||
<div class="card card-primary">
|
||||
<div class="card-header">
|
||||
<h3 class="card-title">Tipologie Eventi</h3>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<p class="text-muted">Gestisci le tipologie disponibili per gli eventi del calendario.</p>
|
||||
|
||||
<?php if(session('success')): ?>
|
||||
<div class="alert alert-success"><?php echo e(session('success')); ?></div>
|
||||
<?php endif; ?>
|
||||
<?php if(session('error')): ?>
|
||||
<div class="alert alert-danger"><?php echo e(session('error')); ?></div>
|
||||
<?php endif; ?>
|
||||
|
||||
<div class="mb-3">
|
||||
<button type="button" class="btn btn-success btn-sm" data-toggle="modal" data-target="#addTipologiaEventoModal">
|
||||
<i class="fas fa-plus"></i> Nuova Tipologia
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<table class="table table-bordered table-striped" id="tipologie-eventi-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th style="width: 40px;">Ordine</th>
|
||||
<th>Nome</th>
|
||||
<th>Descrizione</th>
|
||||
<th style="width: 100px;">Stato</th>
|
||||
<th style="width: 150px;">Azioni</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody id="tipologie-eventi-sortable">
|
||||
<?php $__currentLoopData = $tipologieEventi; $__env->addLoop($__currentLoopData); foreach($__currentLoopData as $tipologia): $__env->incrementLoopIndices(); $loop = $__env->getLastLoop(); ?>
|
||||
<tr data-id="<?php echo e($tipologia->id); ?>">
|
||||
<td class="text-center">
|
||||
<i class="fas fa-arrows-alt handle" style="cursor: grab;"></i>
|
||||
</td>
|
||||
<td>
|
||||
<span class="tipologia-evento-nome"><?php echo e($tipologia->nome); ?></span>
|
||||
</td>
|
||||
<td><?php echo e($tipologia->descrizione ?? '-'); ?></td>
|
||||
<td>
|
||||
<?php if($tipologia->attiva): ?>
|
||||
<span class="badge badge-success">Attiva</span>
|
||||
<?php else: ?>
|
||||
<span class="badge badge-secondary">Disattivata</span>
|
||||
<?php endif; ?>
|
||||
</td>
|
||||
<td>
|
||||
<button type="button" class="btn btn-xs btn-warning" onclick="editTipologiaEvento(<?php echo e($tipologia->id); ?>, '<?php echo e(addslashes($tipologia->nome)); ?>', '<?php echo e(addslashes($tipologia->descrizione ?? '')); ?>', <?php echo e($tipologia->attiva ? 'true' : 'false'); ?>)">
|
||||
<i class="fas fa-edit"></i>
|
||||
</button>
|
||||
<?php if($tipologia->eventi()->count() == 0): ?>
|
||||
<form method="POST" action="<?php echo e(route('impostazioni.tipologie-eventi.destroy', $tipologia->id)); ?>" style="display: inline;">
|
||||
<?php echo csrf_field(); ?>
|
||||
<?php echo method_field('DELETE'); ?>
|
||||
<button type="submit" class="btn btn-xs btn-danger" onclick="return confirm('Eliminare questa tipologia?')">
|
||||
<i class="fas fa-trash"></i>
|
||||
</button>
|
||||
</form>
|
||||
<?php else: ?>
|
||||
<button type="button" class="btn btn-xs btn-danger" disabled title="Eventi associati: <?php echo e($tipologia->eventi()->count()); ?>">
|
||||
<i class="fas fa-trash"></i>
|
||||
</button>
|
||||
<?php endif; ?>
|
||||
</td>
|
||||
</tr>
|
||||
<?php endforeach; $__env->popLoop(); $loop = $__env->getLastLoop(); ?>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<div class="modal fade" id="addTipologiaModal">
|
||||
<div class="modal-dialog">
|
||||
<div class="modal-content">
|
||||
@@ -458,6 +760,7 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<div class="modal fade" id="addRuoloModal">
|
||||
<div class="modal-dialog">
|
||||
<div class="modal-content">
|
||||
@@ -522,6 +825,7 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<div class="modal fade" id="addTipologiaEventoModal">
|
||||
<div class="modal-dialog">
|
||||
<div class="modal-content">
|
||||
@@ -643,7 +947,6 @@ var sortableTipologie = Sortable.create(document.getElementById('tipologie-sorta
|
||||
$('#tipologie-sortable tr').each(function() {
|
||||
order.push($(this).data('id'));
|
||||
});
|
||||
|
||||
fetch('<?php echo e(route('impostazioni.tipologie.reorder')); ?>', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
@@ -663,7 +966,6 @@ var sortableRuoli = Sortable.create(document.getElementById('ruoli-sortable'), {
|
||||
$('#ruoli-sortable tr').each(function() {
|
||||
order.push($(this).data('id'));
|
||||
});
|
||||
|
||||
fetch('<?php echo e(route('impostazioni.ruoli.reorder')); ?>', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
@@ -683,7 +985,6 @@ var sortableTipologieEventi = Sortable.create(document.getElementById('tipologie
|
||||
$('#tipologie-eventi-sortable tr').each(function() {
|
||||
order.push($(this).data('id'));
|
||||
});
|
||||
|
||||
fetch('<?php echo e(route('impostazioni.tipologie-eventi.reorder')); ?>', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
@@ -704,6 +1005,150 @@ $('a[data-toggle="tab"]').on('shown.bs.tab', function (e) {
|
||||
sortableTipologieEventi.refresh();
|
||||
}
|
||||
});
|
||||
|
||||
// ===== EMAIL FUNCTIONS =====
|
||||
function testConnection() {
|
||||
$.ajax({
|
||||
url: '<?php echo e(route('impostazioni.email.test')); ?>',
|
||||
method: 'POST',
|
||||
headers: { 'X-CSRF-TOKEN': '<?php echo e(csrf_token()); ?>' },
|
||||
success: function(response) {
|
||||
if (response.success) {
|
||||
alert('✅ ' + response.message);
|
||||
} else {
|
||||
alert('❌ ' + response.message);
|
||||
}
|
||||
},
|
||||
error: function() {
|
||||
alert('❌ Errore nella richiesta');
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function testSmtp() {
|
||||
var email = prompt('Inserisci l\'indirizzo email a cui inviare il test:');
|
||||
if (!email) return;
|
||||
|
||||
if (!email.includes('@')) {
|
||||
alert('⚠️ Indirizzo email non valido');
|
||||
return;
|
||||
}
|
||||
|
||||
var btn = event.target;
|
||||
btn.disabled = true;
|
||||
btn.innerHTML = '<i class="fas fa-spinner fa-spin"></i> Invio...';
|
||||
|
||||
$.ajax({
|
||||
url: '<?php echo e(route('impostazioni.email.testSmtp')); ?>',
|
||||
method: 'POST',
|
||||
headers: { 'X-CSRF-TOKEN': '<?php echo e(csrf_token()); ?>' },
|
||||
data: { email: email },
|
||||
timeout: 30000,
|
||||
success: function(response) {
|
||||
btn.disabled = false;
|
||||
btn.innerHTML = '<i class="fas fa-paper-plane mr-1"></i> Test Invio Email (SMTP)';
|
||||
if (response.success) {
|
||||
alert('✅ ' + response.message);
|
||||
} else {
|
||||
alert('❌ ' + response.message);
|
||||
}
|
||||
},
|
||||
error: function(xhr, status, error) {
|
||||
btn.disabled = false;
|
||||
btn.innerHTML = '<i class="fas fa-paper-plane mr-1"></i> Test Invio Email (SMTP)';
|
||||
if (xhr.responseJSON && xhr.responseJSON.message) {
|
||||
alert('❌ ' + xhr.responseJSON.message);
|
||||
} else {
|
||||
alert('❌ Errore: ' + status + ' - ' + error);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function createEditor() {
|
||||
var container = document.querySelector('#signature-editor');
|
||||
if (!container || container.querySelector('.ql-toolbar')) return;
|
||||
|
||||
var quill = new Quill('#signature-editor', {
|
||||
theme: 'snow',
|
||||
modules: {
|
||||
toolbar: [
|
||||
['bold', 'italic', 'underline'],
|
||||
[{ 'color': [] }, { 'background': [] }],
|
||||
[{ 'header': 1 }, { 'header': 2 }, { 'header': 3 }],
|
||||
[{ 'align': [] }],
|
||||
[{ 'list': 'ordered'}, { 'list': 'bullet' }],
|
||||
['link', 'image'],
|
||||
['clean', 'code']
|
||||
]
|
||||
}
|
||||
});
|
||||
|
||||
var signatureInput = document.getElementById('signature_input');
|
||||
var isEditingHtml = false;
|
||||
|
||||
quill.getModule('toolbar').addHandler('code', function() {
|
||||
if (!isEditingHtml) {
|
||||
var textarea = document.createElement('textarea');
|
||||
textarea.className = 'ql-editor';
|
||||
textarea.style.cssText = 'font-family: monospace; min-height: 150px; width: 100%;';
|
||||
textarea.value = signatureInput.value;
|
||||
quill.root.innerHTML = '';
|
||||
quill.root.appendChild(textarea);
|
||||
textarea.focus();
|
||||
isEditingHtml = true;
|
||||
} else {
|
||||
var textarea = quill.root.querySelector('textarea');
|
||||
if (textarea) {
|
||||
signatureInput.value = textarea.value;
|
||||
quill.root.innerHTML = textarea.value;
|
||||
}
|
||||
isEditingHtml = false;
|
||||
}
|
||||
});
|
||||
|
||||
if (signatureInput.value) {
|
||||
quill.root.innerHTML = signatureInput.value;
|
||||
}
|
||||
|
||||
quill.on('text-change', function() {
|
||||
if (!isEditingHtml) {
|
||||
signatureInput.value = quill.root.innerHTML;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function initSignatureEditor() {
|
||||
if (document.querySelector('#signature-editor .ql-toolbar')) return;
|
||||
|
||||
if (typeof Quill === 'undefined') {
|
||||
var script = document.createElement('script');
|
||||
script.src = 'https://cdn.quilljs.com/1.3.7/quill.min.js';
|
||||
script.onload = createEditor;
|
||||
document.head.appendChild(script);
|
||||
|
||||
var link = document.createElement('link');
|
||||
link.rel = 'stylesheet';
|
||||
link.href = 'https://cdn.quilljs.com/1.3.7/quill.snow.css';
|
||||
document.head.appendChild(link);
|
||||
} else {
|
||||
createEditor();
|
||||
}
|
||||
}
|
||||
|
||||
$('a[data-toggle="tab"]').on('shown.bs.tab', function(e) {
|
||||
var target = $(e.target).attr('href');
|
||||
if (target === '#email-firma' || target === '#email') {
|
||||
setTimeout(initSignatureEditor, 100);
|
||||
}
|
||||
});
|
||||
|
||||
if (window.location.hash === '#email') {
|
||||
$(document).ready(function() {
|
||||
setTimeout(initSignatureEditor, 300);
|
||||
});
|
||||
}
|
||||
</script>
|
||||
<?php $__env->stopSection(); ?>
|
||||
|
||||
<?php echo $__env->make('layouts.adminlte', array_diff_key(get_defined_vars(), ['__data' => 1, '__path' => 1]))->render(); ?><?php /**PATH /var/www/html/glastree/resources/views/impostazioni/index.blade.php ENDPATH**/ ?>
|
||||
@@ -42,9 +42,10 @@
|
||||
<?php endforeach; $__env->popLoop(); $loop = $__env->getLastLoop(); ?>
|
||||
</select>
|
||||
</div>
|
||||
<input type="hidden" name="is_admin" value="0">
|
||||
<div class="form-group">
|
||||
<div class="custom-control custom-switch">
|
||||
<input type="checkbox" class="custom-control-input" id="isAdmin" name="is_admin" value="1">
|
||||
<input type="checkbox" class="custom-control-input" id="isAdmin" name="is_admin" value="1" <?php echo e(old('is_admin') === '1' ? 'checked' : ''); ?>>
|
||||
<label class="custom-control-label" for="isAdmin">Superadmin (bypass ACL)</label>
|
||||
</div>
|
||||
</div>
|
||||
@@ -61,12 +62,12 @@
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<?php $__currentLoopData = ['individui', 'gruppi', 'eventi', 'documenti', 'mailing', 'viste']; $__env->addLoop($__currentLoopData); foreach($__currentLoopData as $module): $__env->incrementLoopIndices(); $loop = $__env->getLastLoop(); ?>
|
||||
<?php $__currentLoopData = \App\Models\User::MODULES; $__env->addLoop($__currentLoopData); foreach($__currentLoopData as $module): $__env->incrementLoopIndices(); $loop = $__env->getLastLoop(); ?>
|
||||
<tr>
|
||||
<td><strong><?php echo e(ucfirst($module)); ?></strong></td>
|
||||
<td><input type="radio" name="permissions[<?php echo e($module); ?>]" value="0" <?php echo e(old('permissions.' . $module, '1') == '0' ? 'checked' : ''); ?>></td>
|
||||
<td><input type="radio" name="permissions[<?php echo e($module); ?>]" value="1" <?php echo e(old('permissions.' . $module, '1') == '1' ? 'checked' : ''); ?>></td>
|
||||
<td><input type="radio" name="permissions[<?php echo e($module); ?>]" value="2" <?php echo e(old('permissions.' . $module, '1') == '2' ? 'checked' : ''); ?>></td>
|
||||
<td><input type="radio" name="permissions[<?php echo e($module); ?>]" value="0" <?php echo e(old('permissions.' . $module, $module === 'settings' ? '0' : '1') == '0' ? 'checked' : ''); ?>></td>
|
||||
<td><input type="radio" name="permissions[<?php echo e($module); ?>]" value="1" <?php echo e(old('permissions.' . $module, $module === 'settings' ? '0' : '1') == '1' ? 'checked' : ''); ?>></td>
|
||||
<td><input type="radio" name="permissions[<?php echo e($module); ?>]" value="2" <?php echo e(old('permissions.' . $module, $module === 'settings' ? '0' : '1') == '2' ? 'checked' : ''); ?>></td>
|
||||
</tr>
|
||||
<?php endforeach; $__env->popLoop(); $loop = $__env->getLastLoop(); ?>
|
||||
</tbody>
|
||||
|
||||
@@ -23,6 +23,9 @@ $canDeleteEventi = Auth::user()->canDelete('eventi');
|
||||
<a href="/eventi/calendar" class="btn btn-sm btn-outline-primary mr-2">
|
||||
<i class="fas fa-calendar-alt mr-1"></i> Calendario
|
||||
</a>
|
||||
<button type="button" class="btn btn-sm btn-success mr-2" onclick="exportSelectedIcs()">
|
||||
<i class="fas fa-calendar-plus mr-1"></i> Esporta ICS
|
||||
</button>
|
||||
<?php if($canDeleteEventi): ?>
|
||||
<div class="btn-group mr-2">
|
||||
<button type="button" class="btn btn-sm btn-danger" onclick="deleteSelected()">
|
||||
@@ -313,6 +316,34 @@ function deleteSelected() {
|
||||
|
||||
$('#deleteModal').modal('show');
|
||||
}
|
||||
|
||||
function exportSelectedIcs() {
|
||||
const selectedIds = getSelectedIds();
|
||||
const form = document.createElement('form');
|
||||
form.method = 'POST';
|
||||
form.action = '/eventi/export-ics';
|
||||
|
||||
const csrfToken = document.querySelector('meta[name="csrf-token"]')?.content || '<?php echo e(csrf_token()); ?>';
|
||||
|
||||
let html = `<input type="hidden" name="_token" value="${csrfToken}">`;
|
||||
|
||||
if (selectedIds.length > 0) {
|
||||
selectedIds.forEach(function(id) {
|
||||
html += `<input type="hidden" name="ids[]" value="${id}">`;
|
||||
});
|
||||
} else {
|
||||
const params = new URLSearchParams(window.location.search);
|
||||
params.forEach(function(value, key) {
|
||||
if (key === 'search') html += `<input type="hidden" name="search" value="${value}">`;
|
||||
if (key === 'gruppo_id') html += `<input type="hidden" name="gruppo_id" value="${value}">`;
|
||||
if (key === 'tipo') html += `<input type="hidden" name="tipo" value="${value}">`;
|
||||
});
|
||||
}
|
||||
|
||||
form.innerHTML = html;
|
||||
document.body.appendChild(form);
|
||||
form.submit();
|
||||
}
|
||||
</script>
|
||||
<?php $__env->stopSection(); ?>
|
||||
<?php echo $__env->make('layouts.adminlte', array_diff_key(get_defined_vars(), ['__data' => 1, '__path' => 1]))->render(); ?><?php /**PATH /var/www/html/glastree/resources/views/eventi/index.blade.php ENDPATH**/ ?>
|
||||
@@ -203,23 +203,15 @@ $appName = \App\Models\AppSetting::getAppName() ?? 'Glastree';
|
||||
</a>
|
||||
</li>
|
||||
<?php endif; ?>
|
||||
<?php if(Auth::user()->isSuperAdmin()): ?>
|
||||
<li class="nav-item has-treeview">
|
||||
<a href="/impostazioni" class="nav-link">
|
||||
<?php if(Auth::user()->canManage('settings')): ?>
|
||||
<li class="nav-item">
|
||||
<a href="/impostazioni" class="nav-link <?php echo e(request()->is('impostazioni*') ? 'active' : ''); ?>">
|
||||
<i class="nav-icon fas fa-cog"></i>
|
||||
<p>Impostazioni <i class="right fas fa-angle-left"></i></p>
|
||||
<p>Impostazioni</p>
|
||||
</a>
|
||||
<ul class="nav nav-treeview">
|
||||
<li class="nav-item">
|
||||
<a href="/impostazioni/email" class="nav-link">
|
||||
<i class="nav-icon fas fa-envelope"></i>
|
||||
<p>Impostazione Email</p>
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a href="/admin/utenti" class="nav-link">
|
||||
<a href="/admin/utenti" class="nav-link <?php echo e(request()->is('admin*') ? 'active' : ''); ?>">
|
||||
<i class="nav-icon fas fa-users-cog"></i>
|
||||
<p>Admin</p>
|
||||
</a>
|
||||
|
||||
Reference in New Issue
Block a user