Primo commit
This commit is contained in:
99
app/Livewire/Assegnazioni/Assegna.php
Normal file
99
app/Livewire/Assegnazioni/Assegna.php
Normal file
@@ -0,0 +1,99 @@
|
||||
<?php
|
||||
|
||||
namespace App\Livewire\Assegnazioni;
|
||||
|
||||
use Livewire\Component;
|
||||
use App\Models\Territorio;
|
||||
use App\Models\Proclamatore;
|
||||
use App\Models\Assegnazione;
|
||||
use App\Models\AnnoTeocratico;
|
||||
|
||||
class Assegna extends Component
|
||||
{
|
||||
public ?int $territorio_id = null;
|
||||
public ?int $proclamatore_id = null;
|
||||
public string $assigned_at = '';
|
||||
|
||||
// Optional pre-selection from parent context
|
||||
public ?int $preselectedTerritorioId = null;
|
||||
|
||||
public function mount(?int $territorioId = null)
|
||||
{
|
||||
$this->preselectedTerritorioId = $territorioId;
|
||||
$this->territorio_id = $territorioId;
|
||||
$this->assigned_at = now()->format('Y-m-d');
|
||||
}
|
||||
|
||||
protected function rules(): array
|
||||
{
|
||||
return [
|
||||
'territorio_id' => 'required|exists:territori,id',
|
||||
'proclamatore_id' => 'required|exists:proclamatori,id',
|
||||
'assigned_at' => 'required|date|before_or_equal:today',
|
||||
];
|
||||
}
|
||||
|
||||
public function save()
|
||||
{
|
||||
$this->validate();
|
||||
|
||||
$territorio = Territorio::findOrFail($this->territorio_id);
|
||||
|
||||
// Check territory is available (not currently assigned)
|
||||
if ($territorio->assegnazioneCorrente) {
|
||||
session()->flash('error', "Il territorio {$territorio->numero} è già assegnato a {$territorio->assegnazioneCorrente->proclamatore->nome_completo}.");
|
||||
return;
|
||||
}
|
||||
|
||||
// Check territory is active
|
||||
if (!$territorio->attivo) {
|
||||
session()->flash('error', "Il territorio {$territorio->numero} è inattivo.");
|
||||
return;
|
||||
}
|
||||
|
||||
$proclamatore = Proclamatore::findOrFail($this->proclamatore_id);
|
||||
if (!$proclamatore->attivo) {
|
||||
session()->flash('error', "Il proclamatore {$proclamatore->nome_completo} è inattivo.");
|
||||
return;
|
||||
}
|
||||
|
||||
$assignedDate = \Carbon\Carbon::parse($this->assigned_at);
|
||||
$annoTeocratico = AnnoTeocratico::perData($assignedDate);
|
||||
|
||||
$assegnazione = Assegnazione::create([
|
||||
'territorio_id' => $this->territorio_id,
|
||||
'proclamatore_id' => $this->proclamatore_id,
|
||||
'anno_teocratico_id' => $annoTeocratico->id,
|
||||
'assigned_at' => $assignedDate,
|
||||
'created_by' => auth()->id(),
|
||||
]);
|
||||
|
||||
activity()->causedBy(auth()->user())
|
||||
->performedOn($assegnazione)
|
||||
->withProperties([
|
||||
'territorio' => $territorio->numero,
|
||||
'proclamatore' => $proclamatore->nome_completo,
|
||||
])
|
||||
->log('assigned');
|
||||
|
||||
session()->flash('success', "Territorio {$territorio->numero} assegnato a {$proclamatore->nome_completo}.");
|
||||
return $this->redirect(route('territori.show', $territorio), navigate: true);
|
||||
}
|
||||
|
||||
public function render()
|
||||
{
|
||||
$territoriDisponibili = Territorio::where('attivo', true)
|
||||
->whereDoesntHave('assegnazioni', fn($q) => $q->aperte())
|
||||
->orderBy('numero')
|
||||
->get();
|
||||
|
||||
$proclamatoriAttivi = Proclamatore::attivi()
|
||||
->get()
|
||||
->sortBy(fn($p) => mb_strtolower($p->cognome . ' ' . $p->nome));
|
||||
|
||||
return view('livewire.assegnazioni.assegna', [
|
||||
'territoriDisponibili' => $territoriDisponibili,
|
||||
'proclamatoriAttivi' => $proclamatoriAttivi,
|
||||
]);
|
||||
}
|
||||
}
|
||||
99
app/Livewire/Assegnazioni/Rientra.php
Normal file
99
app/Livewire/Assegnazioni/Rientra.php
Normal file
@@ -0,0 +1,99 @@
|
||||
<?php
|
||||
|
||||
namespace App\Livewire\Assegnazioni;
|
||||
|
||||
use Livewire\Component;
|
||||
use App\Models\Assegnazione;
|
||||
use App\Models\Campagna;
|
||||
|
||||
class Rientra extends Component
|
||||
{
|
||||
public Assegnazione $assegnazione;
|
||||
public string $returned_at = '';
|
||||
public bool $showCampaignPrompt = false;
|
||||
public ?int $campagna_id = null;
|
||||
public bool $counted_in_campaign = false;
|
||||
|
||||
public function mount(Assegnazione $assegnazione)
|
||||
{
|
||||
$this->assegnazione = $assegnazione->load(['territorio', 'proclamatore']);
|
||||
|
||||
if ($assegnazione->returned_at) {
|
||||
abort(404, 'Assegnazione già rientrata.');
|
||||
}
|
||||
|
||||
$this->returned_at = now()->format('Y-m-d');
|
||||
$this->checkCampaign();
|
||||
}
|
||||
|
||||
public function updatedReturnedAt()
|
||||
{
|
||||
$this->checkCampaign();
|
||||
}
|
||||
|
||||
protected function checkCampaign()
|
||||
{
|
||||
if (!$this->returned_at) {
|
||||
$this->showCampaignPrompt = false;
|
||||
return;
|
||||
}
|
||||
|
||||
$returnDate = \Carbon\Carbon::parse($this->returned_at);
|
||||
$campagna = $this->assegnazione->campagnaApplicabile($returnDate);
|
||||
|
||||
if ($campagna) {
|
||||
$this->showCampaignPrompt = true;
|
||||
$this->campagna_id = $campagna->id;
|
||||
} else {
|
||||
$this->showCampaignPrompt = false;
|
||||
$this->campagna_id = null;
|
||||
$this->counted_in_campaign = false;
|
||||
}
|
||||
}
|
||||
|
||||
protected function rules(): array
|
||||
{
|
||||
return [
|
||||
'returned_at' => 'required|date|after_or_equal:' . $this->assegnazione->assigned_at->format('Y-m-d') . '|before_or_equal:today',
|
||||
];
|
||||
}
|
||||
|
||||
public function save()
|
||||
{
|
||||
$this->validate();
|
||||
|
||||
$returnDate = \Carbon\Carbon::parse($this->returned_at);
|
||||
|
||||
$this->assegnazione->update([
|
||||
'returned_at' => $returnDate,
|
||||
'returned_by' => auth()->id(),
|
||||
'campaign_id' => $this->counted_in_campaign ? $this->campagna_id : null,
|
||||
'counted_in_campaign' => $this->counted_in_campaign,
|
||||
]);
|
||||
|
||||
$territorio = $this->assegnazione->territorio;
|
||||
$proclamatore = $this->assegnazione->proclamatore;
|
||||
|
||||
activity()->causedBy(auth()->user())
|
||||
->performedOn($this->assegnazione)
|
||||
->withProperties([
|
||||
'territorio' => $territorio->numero,
|
||||
'proclamatore' => $proclamatore->nome_completo,
|
||||
'giorni' => $this->assegnazione->giorni,
|
||||
'campagna' => $this->counted_in_campaign,
|
||||
])
|
||||
->log('returned');
|
||||
|
||||
session()->flash('success', "Territorio {$territorio->numero} rientrato da {$proclamatore->nome_completo} dopo {$this->assegnazione->giorni} giorni.");
|
||||
return $this->redirect(route('territori.show', $territorio), navigate: true);
|
||||
}
|
||||
|
||||
public function render()
|
||||
{
|
||||
$campagna = $this->campagna_id ? Campagna::find($this->campagna_id) : null;
|
||||
|
||||
return view('livewire.assegnazioni.rientra', [
|
||||
'campagna' => $campagna,
|
||||
]);
|
||||
}
|
||||
}
|
||||
57
app/Livewire/AuditLog.php
Normal file
57
app/Livewire/AuditLog.php
Normal file
@@ -0,0 +1,57 @@
|
||||
<?php
|
||||
|
||||
namespace App\Livewire;
|
||||
|
||||
use Livewire\Component;
|
||||
use Livewire\WithPagination;
|
||||
use Spatie\Activitylog\Models\Activity;
|
||||
|
||||
class AuditLog extends Component
|
||||
{
|
||||
use WithPagination;
|
||||
|
||||
public string $search = '';
|
||||
public string $filterEvent = '';
|
||||
public string $filterCauser = '';
|
||||
|
||||
protected $queryString = [
|
||||
'search' => ['except' => ''],
|
||||
'filterEvent' => ['except' => ''],
|
||||
'filterCauser' => ['except' => ''],
|
||||
];
|
||||
|
||||
public function updatingSearch()
|
||||
{
|
||||
$this->resetPage();
|
||||
}
|
||||
|
||||
public function render()
|
||||
{
|
||||
$query = Activity::with('causer')->latest();
|
||||
|
||||
if ($this->search) {
|
||||
$query->where(function ($q) {
|
||||
$q->where('description', 'like', "%{$this->search}%")
|
||||
->orWhere('subject_type', 'like', "%{$this->search}%")
|
||||
->orWhere('properties', 'like', "%{$this->search}%");
|
||||
});
|
||||
}
|
||||
|
||||
if ($this->filterEvent) {
|
||||
$query->where('description', $this->filterEvent);
|
||||
}
|
||||
|
||||
if ($this->filterCauser) {
|
||||
$query->where('causer_id', $this->filterCauser);
|
||||
}
|
||||
|
||||
$events = Activity::select('description')->distinct()->pluck('description');
|
||||
$users = \App\Models\User::orderBy('name')->get();
|
||||
|
||||
return view('livewire.audit-log', [
|
||||
'activities' => $query->paginate(30),
|
||||
'events' => $events,
|
||||
'users' => $users,
|
||||
]);
|
||||
}
|
||||
}
|
||||
54
app/Livewire/Auth/Login.php
Normal file
54
app/Livewire/Auth/Login.php
Normal file
@@ -0,0 +1,54 @@
|
||||
<?php
|
||||
|
||||
namespace App\Livewire\Auth;
|
||||
|
||||
use Livewire\Component;
|
||||
use Illuminate\Support\Facades\Auth;
|
||||
use Illuminate\Support\Facades\RateLimiter;
|
||||
use Illuminate\Support\Str;
|
||||
|
||||
class Login extends Component
|
||||
{
|
||||
public string $email = '';
|
||||
public string $password = '';
|
||||
public bool $remember = false;
|
||||
|
||||
protected function rules(): array
|
||||
{
|
||||
return [
|
||||
'email' => 'required|email',
|
||||
'password' => 'required|min:6',
|
||||
];
|
||||
}
|
||||
|
||||
public function login()
|
||||
{
|
||||
$this->validate();
|
||||
|
||||
$throttleKey = Str::transliterate(Str::lower($this->email) . '|' . request()->ip());
|
||||
|
||||
if (RateLimiter::tooManyAttempts($throttleKey, 5)) {
|
||||
$seconds = RateLimiter::availableIn($throttleKey);
|
||||
$this->addError('email', "Troppi tentativi. Riprova tra {$seconds} secondi.");
|
||||
return;
|
||||
}
|
||||
|
||||
if (!Auth::attempt(['email' => $this->email, 'password' => $this->password], $this->remember)) {
|
||||
RateLimiter::hit($throttleKey);
|
||||
$this->addError('email', 'Credenziali non valide.');
|
||||
return;
|
||||
}
|
||||
|
||||
RateLimiter::clear($throttleKey);
|
||||
session()->regenerate();
|
||||
activity()->causedBy(auth()->user())->log('login');
|
||||
|
||||
return redirect()->intended(route('dashboard'));
|
||||
}
|
||||
|
||||
public function render()
|
||||
{
|
||||
return view('livewire.auth.login')
|
||||
->layout('components.layouts.guest');
|
||||
}
|
||||
}
|
||||
44
app/Livewire/Campagne/CampagnaCreate.php
Normal file
44
app/Livewire/Campagne/CampagnaCreate.php
Normal file
@@ -0,0 +1,44 @@
|
||||
<?php
|
||||
|
||||
namespace App\Livewire\Campagne;
|
||||
|
||||
use Livewire\Component;
|
||||
use App\Models\Campagna;
|
||||
|
||||
class CampagnaCreate extends Component
|
||||
{
|
||||
public string $descrizione = '';
|
||||
public string $start_date = '';
|
||||
public string $end_date = '';
|
||||
|
||||
protected function rules(): array
|
||||
{
|
||||
return [
|
||||
'descrizione' => 'required|string|max:255',
|
||||
'start_date' => 'required|date',
|
||||
'end_date' => 'required|date|after:start_date',
|
||||
];
|
||||
}
|
||||
|
||||
public function save()
|
||||
{
|
||||
$this->validate();
|
||||
|
||||
$campagna = Campagna::create([
|
||||
'descrizione' => $this->descrizione,
|
||||
'start_date' => $this->start_date,
|
||||
'end_date' => $this->end_date,
|
||||
]);
|
||||
|
||||
session()->flash('success', "Campagna '{$campagna->descrizione}' creata.");
|
||||
return $this->redirect(route('campagne.index'), navigate: true);
|
||||
}
|
||||
|
||||
public function render()
|
||||
{
|
||||
return view('livewire.campagne.campagna-form', [
|
||||
'titolo' => 'Nuova Campagna',
|
||||
'btnLabel' => 'Crea Campagna',
|
||||
]);
|
||||
}
|
||||
}
|
||||
53
app/Livewire/Campagne/CampagnaEdit.php
Normal file
53
app/Livewire/Campagne/CampagnaEdit.php
Normal file
@@ -0,0 +1,53 @@
|
||||
<?php
|
||||
|
||||
namespace App\Livewire\Campagne;
|
||||
|
||||
use Livewire\Component;
|
||||
use App\Models\Campagna;
|
||||
|
||||
class CampagnaEdit extends Component
|
||||
{
|
||||
public Campagna $campagna;
|
||||
public string $descrizione = '';
|
||||
public string $start_date = '';
|
||||
public string $end_date = '';
|
||||
|
||||
public function mount(Campagna $campagna)
|
||||
{
|
||||
$this->campagna = $campagna;
|
||||
$this->descrizione = $campagna->descrizione;
|
||||
$this->start_date = $campagna->start_date->format('Y-m-d');
|
||||
$this->end_date = $campagna->end_date->format('Y-m-d');
|
||||
}
|
||||
|
||||
protected function rules(): array
|
||||
{
|
||||
return [
|
||||
'descrizione' => 'required|string|max:255',
|
||||
'start_date' => 'required|date',
|
||||
'end_date' => 'required|date|after:start_date',
|
||||
];
|
||||
}
|
||||
|
||||
public function save()
|
||||
{
|
||||
$this->validate();
|
||||
|
||||
$this->campagna->update([
|
||||
'descrizione' => $this->descrizione,
|
||||
'start_date' => $this->start_date,
|
||||
'end_date' => $this->end_date,
|
||||
]);
|
||||
|
||||
session()->flash('success', "Campagna aggiornata.");
|
||||
return $this->redirect(route('campagne.index'), navigate: true);
|
||||
}
|
||||
|
||||
public function render()
|
||||
{
|
||||
return view('livewire.campagne.campagna-form', [
|
||||
'titolo' => "Modifica: {$this->campagna->descrizione}",
|
||||
'btnLabel' => 'Salva Modifiche',
|
||||
]);
|
||||
}
|
||||
}
|
||||
26
app/Livewire/Campagne/CampagnaIndex.php
Normal file
26
app/Livewire/Campagne/CampagnaIndex.php
Normal file
@@ -0,0 +1,26 @@
|
||||
<?php
|
||||
|
||||
namespace App\Livewire\Campagne;
|
||||
|
||||
use Livewire\Component;
|
||||
use Livewire\WithPagination;
|
||||
use App\Models\Campagna;
|
||||
|
||||
class CampagnaIndex extends Component
|
||||
{
|
||||
use WithPagination;
|
||||
|
||||
public function deleteCampagna(int $id)
|
||||
{
|
||||
$campagna = Campagna::findOrFail($id);
|
||||
$campagna->delete();
|
||||
session()->flash('success', "Campagna '{$campagna->descrizione}' eliminata.");
|
||||
}
|
||||
|
||||
public function render()
|
||||
{
|
||||
return view('livewire.campagne.campagna-index', [
|
||||
'campagne' => Campagna::orderByDesc('start_date')->paginate(15),
|
||||
]);
|
||||
}
|
||||
}
|
||||
41
app/Livewire/Campagne/CampagnaShow.php
Normal file
41
app/Livewire/Campagne/CampagnaShow.php
Normal file
@@ -0,0 +1,41 @@
|
||||
<?php
|
||||
|
||||
namespace App\Livewire\Campagne;
|
||||
|
||||
use Livewire\Component;
|
||||
use App\Models\Campagna;
|
||||
use App\Models\Assegnazione;
|
||||
use App\Models\Territorio;
|
||||
|
||||
class CampagnaShow extends Component
|
||||
{
|
||||
public Campagna $campagna;
|
||||
|
||||
public function mount(Campagna $campagna)
|
||||
{
|
||||
$this->campagna = $campagna;
|
||||
}
|
||||
|
||||
public function render()
|
||||
{
|
||||
// All assignments with returned_at in campaign range that were counted
|
||||
$conteggiate = Assegnazione::where('campagna_id', $this->campagna->id)
|
||||
->where('counted_in_campaign', true)
|
||||
->with(['territorio', 'proclamatore'])
|
||||
->orderBy('returned_at')
|
||||
->get();
|
||||
|
||||
// All assignments that were active during this campaign range
|
||||
$assegnateNelRange = Assegnazione::where('assigned_at', '<=', $this->campagna->end_date)
|
||||
->where(function ($q) {
|
||||
$q->whereNull('returned_at')
|
||||
->orWhere('returned_at', '>=', $this->campagna->start_date);
|
||||
})
|
||||
->count();
|
||||
|
||||
return view('livewire.campagne.campagna-show', [
|
||||
'conteggiate' => $conteggiate,
|
||||
'assegnateNelRange' => $assegnateNelRange,
|
||||
]);
|
||||
}
|
||||
}
|
||||
81
app/Livewire/Home.php
Normal file
81
app/Livewire/Home.php
Normal file
@@ -0,0 +1,81 @@
|
||||
<?php
|
||||
|
||||
namespace App\Livewire;
|
||||
|
||||
use Livewire\Component;
|
||||
use App\Models\Territorio;
|
||||
use App\Models\Proclamatore;
|
||||
use App\Models\Assegnazione;
|
||||
use App\Models\AnnoTeocratico;
|
||||
use App\Models\Campagna;
|
||||
use App\Models\Setting;
|
||||
|
||||
class Home extends Component
|
||||
{
|
||||
public function render()
|
||||
{
|
||||
$settings = Setting::instance();
|
||||
$annoCorrente = AnnoTeocratico::corrente();
|
||||
$campagnaAttiva = Campagna::attiva();
|
||||
|
||||
// Territory counts
|
||||
$totTerritoriAttivi = Territorio::where('attivo', true)->count();
|
||||
$totAssegnati = Territorio::assegnato()->count();
|
||||
$totInReparto = Territorio::inReparto()->count();
|
||||
|
||||
// Coverage: returned territories per current theocratic year
|
||||
$territoriPercorsi = 0;
|
||||
if ($annoCorrente) {
|
||||
$territoriPercorsi = Assegnazione::where('anno_teocratico_id', $annoCorrente->id)
|
||||
->whereNotNull('returned_at')
|
||||
->distinct('territorio_id')
|
||||
->count('territorio_id');
|
||||
}
|
||||
|
||||
// Monthly average
|
||||
$mediaPercorrenzaMensile = 0;
|
||||
if ($annoCorrente && $annoCorrente->mesi_trascorsi > 0) {
|
||||
$mediaPercorrenzaMensile = round($territoriPercorsi / $annoCorrente->mesi_trascorsi, 1);
|
||||
}
|
||||
|
||||
// Campaign stats
|
||||
$campagnaStats = null;
|
||||
if ($campagnaAttiva) {
|
||||
$campagnaStats = [
|
||||
'descrizione' => $campagnaAttiva->descrizione,
|
||||
'percentuale' => $campagnaAttiva->percentuale_percorrenza,
|
||||
'fine' => $campagnaAttiva->end_date->format('d/m/Y'),
|
||||
];
|
||||
}
|
||||
|
||||
// Quick lists
|
||||
$daAssegnare = Territorio::daAssegnare()
|
||||
->with('zona', 'tipologia', 'ultimaAssegnazione')
|
||||
->take(10)
|
||||
->get();
|
||||
|
||||
$prioritari = Territorio::prioritari()
|
||||
->with('zona', 'tipologia', 'ultimaAssegnazione')
|
||||
->take(10)
|
||||
->get();
|
||||
|
||||
$daRientrare = Territorio::daRientrare()
|
||||
->with(['zona', 'assegnazioneCorrente.proclamatore'])
|
||||
->take(10)
|
||||
->get();
|
||||
|
||||
return view('livewire.home', [
|
||||
'congregazione' => $settings->congregazione_nome ?? 'TerManager2',
|
||||
'annoCorrente' => $annoCorrente,
|
||||
'totTerritoriAttivi' => $totTerritoriAttivi,
|
||||
'totAssegnati' => $totAssegnati,
|
||||
'totInReparto' => $totInReparto,
|
||||
'territoriPercorsi' => $territoriPercorsi,
|
||||
'mediaPercorrenzaMensile' => $mediaPercorrenzaMensile,
|
||||
'campagnaStats' => $campagnaStats,
|
||||
'daAssegnare' => $daAssegnare,
|
||||
'prioritari' => $prioritari,
|
||||
'daRientrare' => $daRientrare,
|
||||
]);
|
||||
}
|
||||
}
|
||||
19
app/Livewire/Privacy.php
Normal file
19
app/Livewire/Privacy.php
Normal file
@@ -0,0 +1,19 @@
|
||||
<?php
|
||||
|
||||
namespace App\Livewire;
|
||||
|
||||
use Livewire\Component;
|
||||
use App\Models\Setting;
|
||||
|
||||
class Privacy extends Component
|
||||
{
|
||||
public function render()
|
||||
{
|
||||
$settings = Setting::instance();
|
||||
|
||||
return view('livewire.privacy', [
|
||||
'congregazione' => $settings->congregazione_nome ?? 'Congregazione',
|
||||
'auditRetention' => $settings->audit_retention_days ?? 365,
|
||||
]);
|
||||
}
|
||||
}
|
||||
44
app/Livewire/Proclamatori/ProclamatoreCestino.php
Normal file
44
app/Livewire/Proclamatori/ProclamatoreCestino.php
Normal file
@@ -0,0 +1,44 @@
|
||||
<?php
|
||||
|
||||
namespace App\Livewire\Proclamatori;
|
||||
|
||||
use Livewire\Component;
|
||||
use Livewire\WithPagination;
|
||||
use App\Models\Proclamatore;
|
||||
|
||||
class ProclamatoreCestino extends Component
|
||||
{
|
||||
use WithPagination;
|
||||
|
||||
public function restore(int $id)
|
||||
{
|
||||
$proclamatore = Proclamatore::onlyTrashed()->findOrFail($id);
|
||||
$proclamatore->restore();
|
||||
activity()->causedBy(auth()->user())
|
||||
->performedOn($proclamatore)
|
||||
->log('restored');
|
||||
session()->flash('success', "Proclamatore ripristinato.");
|
||||
}
|
||||
|
||||
public function forceDelete(int $id)
|
||||
{
|
||||
$proclamatore = Proclamatore::onlyTrashed()->findOrFail($id);
|
||||
|
||||
if ($proclamatore->assegnazioni()->exists()) {
|
||||
session()->flash('error', 'Impossibile eliminare definitivamente: il proclamatore ha assegnazioni nello storico.');
|
||||
return;
|
||||
}
|
||||
|
||||
activity()->causedBy(auth()->user())
|
||||
->log('force_deleted_proclamatore');
|
||||
$proclamatore->forceDelete();
|
||||
session()->flash('success', 'Proclamatore eliminato definitivamente.');
|
||||
}
|
||||
|
||||
public function render()
|
||||
{
|
||||
return view('livewire.proclamatori.proclamatore-cestino', [
|
||||
'proclamatori' => Proclamatore::onlyTrashed()->orderByDesc('deleted_at')->paginate(20),
|
||||
]);
|
||||
}
|
||||
}
|
||||
48
app/Livewire/Proclamatori/ProclamatoreCreate.php
Normal file
48
app/Livewire/Proclamatori/ProclamatoreCreate.php
Normal file
@@ -0,0 +1,48 @@
|
||||
<?php
|
||||
|
||||
namespace App\Livewire\Proclamatori;
|
||||
|
||||
use Livewire\Component;
|
||||
use App\Models\Proclamatore;
|
||||
|
||||
class ProclamatoreCreate extends Component
|
||||
{
|
||||
public string $nome = '';
|
||||
public string $cognome = '';
|
||||
public bool $attivo = true;
|
||||
|
||||
protected function rules(): array
|
||||
{
|
||||
return [
|
||||
'nome' => 'required|string|max:100',
|
||||
'cognome' => 'required|string|max:100',
|
||||
'attivo' => 'boolean',
|
||||
];
|
||||
}
|
||||
|
||||
public function save()
|
||||
{
|
||||
$this->validate();
|
||||
|
||||
$proclamatore = Proclamatore::create([
|
||||
'nome' => $this->nome,
|
||||
'cognome' => $this->cognome,
|
||||
'attivo' => $this->attivo,
|
||||
]);
|
||||
|
||||
activity()->causedBy(auth()->user())
|
||||
->performedOn($proclamatore)
|
||||
->log('created');
|
||||
|
||||
session()->flash('success', "Proclamatore {$proclamatore->nome_completo} creato.");
|
||||
return $this->redirect(route('proclamatori.index'), navigate: true);
|
||||
}
|
||||
|
||||
public function render()
|
||||
{
|
||||
return view('livewire.proclamatori.proclamatore-form', [
|
||||
'titolo' => 'Nuovo Proclamatore',
|
||||
'btnLabel' => 'Crea Proclamatore',
|
||||
]);
|
||||
}
|
||||
}
|
||||
53
app/Livewire/Proclamatori/ProclamatoreEdit.php
Normal file
53
app/Livewire/Proclamatori/ProclamatoreEdit.php
Normal file
@@ -0,0 +1,53 @@
|
||||
<?php
|
||||
|
||||
namespace App\Livewire\Proclamatori;
|
||||
|
||||
use Livewire\Component;
|
||||
use App\Models\Proclamatore;
|
||||
|
||||
class ProclamatoreEdit extends Component
|
||||
{
|
||||
public Proclamatore $proclamatore;
|
||||
public string $nome = '';
|
||||
public string $cognome = '';
|
||||
public bool $attivo = true;
|
||||
|
||||
public function mount(Proclamatore $proclamatore)
|
||||
{
|
||||
$this->proclamatore = $proclamatore;
|
||||
$this->nome = $proclamatore->nome;
|
||||
$this->cognome = $proclamatore->cognome;
|
||||
$this->attivo = $proclamatore->attivo;
|
||||
}
|
||||
|
||||
protected function rules(): array
|
||||
{
|
||||
return [
|
||||
'nome' => 'required|string|max:100',
|
||||
'cognome' => 'required|string|max:100',
|
||||
'attivo' => 'boolean',
|
||||
];
|
||||
}
|
||||
|
||||
public function save()
|
||||
{
|
||||
$this->validate();
|
||||
|
||||
$this->proclamatore->update([
|
||||
'nome' => $this->nome,
|
||||
'cognome' => $this->cognome,
|
||||
'attivo' => $this->attivo,
|
||||
]);
|
||||
|
||||
session()->flash('success', "Proclamatore {$this->proclamatore->nome_completo} aggiornato.");
|
||||
return $this->redirect(route('proclamatori.index'), navigate: true);
|
||||
}
|
||||
|
||||
public function render()
|
||||
{
|
||||
return view('livewire.proclamatori.proclamatore-form', [
|
||||
'titolo' => "Modifica {$this->proclamatore->nome_completo}",
|
||||
'btnLabel' => 'Salva Modifiche',
|
||||
]);
|
||||
}
|
||||
}
|
||||
135
app/Livewire/Proclamatori/ProclamatoreIndex.php
Normal file
135
app/Livewire/Proclamatori/ProclamatoreIndex.php
Normal file
@@ -0,0 +1,135 @@
|
||||
<?php
|
||||
|
||||
namespace App\Livewire\Proclamatori;
|
||||
|
||||
use Livewire\Component;
|
||||
use Livewire\WithPagination;
|
||||
use App\Models\Proclamatore;
|
||||
|
||||
class ProclamatoreIndex extends Component
|
||||
{
|
||||
use WithPagination;
|
||||
|
||||
public string $search = '';
|
||||
public string $filtroStato = '';
|
||||
public string $sortField = 'cognome';
|
||||
public string $sortDirection = 'asc';
|
||||
|
||||
protected $queryString = [
|
||||
'search' => ['except' => ''],
|
||||
'filtroStato' => ['except' => ''],
|
||||
];
|
||||
|
||||
public function updatingSearch()
|
||||
{
|
||||
$this->resetPage();
|
||||
}
|
||||
|
||||
public function updatingFiltroStato()
|
||||
{
|
||||
$this->resetPage();
|
||||
}
|
||||
|
||||
public function sortBy(string $field)
|
||||
{
|
||||
if ($this->sortField === $field) {
|
||||
$this->sortDirection = $this->sortDirection === 'asc' ? 'desc' : 'asc';
|
||||
} else {
|
||||
$this->sortField = $field;
|
||||
$this->sortDirection = 'asc';
|
||||
}
|
||||
}
|
||||
|
||||
public function toggleActive(int $id)
|
||||
{
|
||||
$proclamatore = Proclamatore::findOrFail($id);
|
||||
$proclamatore->update(['attivo' => !$proclamatore->attivo]);
|
||||
}
|
||||
|
||||
public function deleteProclamatore(int $id)
|
||||
{
|
||||
$proclamatore = Proclamatore::findOrFail($id);
|
||||
|
||||
if ($proclamatore->assegnazioni()->aperte()->exists()) {
|
||||
session()->flash('error', "Il proclamatore {$proclamatore->nome_completo} ha assegnazioni aperte. Rientra prima i territori.");
|
||||
return;
|
||||
}
|
||||
|
||||
$proclamatore->delete();
|
||||
session()->flash('success', "Proclamatore {$proclamatore->nome_completo} spostato nel cestino.");
|
||||
}
|
||||
|
||||
public function anonimizza(int $id)
|
||||
{
|
||||
$proclamatore = Proclamatore::findOrFail($id);
|
||||
$proclamatore->anonimizza();
|
||||
session()->flash('success', "Dati personali del proclamatore anonimizzati (GDPR).");
|
||||
}
|
||||
|
||||
public function render()
|
||||
{
|
||||
$query = Proclamatore::query();
|
||||
|
||||
if ($this->search !== '') {
|
||||
// In-memory filter because nome/cognome are encrypted
|
||||
$all = $query->get();
|
||||
$filtered = $all->filter(function ($p) {
|
||||
return str_contains(
|
||||
mb_strtolower($p->nome . ' ' . $p->cognome),
|
||||
mb_strtolower($this->search)
|
||||
);
|
||||
});
|
||||
|
||||
if ($this->filtroStato === 'attivo') {
|
||||
$filtered = $filtered->where('attivo', true);
|
||||
} elseif ($this->filtroStato === 'inattivo') {
|
||||
$filtered = $filtered->where('attivo', false);
|
||||
}
|
||||
|
||||
// Sort in-memory
|
||||
$filtered = $filtered->sortBy(function ($p) {
|
||||
return match ($this->sortField) {
|
||||
'nome' => mb_strtolower($p->nome),
|
||||
'cognome' => mb_strtolower($p->cognome),
|
||||
default => mb_strtolower($p->cognome),
|
||||
};
|
||||
}, SORT_REGULAR, $this->sortDirection === 'desc');
|
||||
|
||||
// Manual pagination
|
||||
$page = $this->getPage();
|
||||
$perPage = 20;
|
||||
$items = $filtered->slice(($page - 1) * $perPage, $perPage)->values();
|
||||
$proclamatori = new \Illuminate\Pagination\LengthAwarePaginator(
|
||||
$items, $filtered->count(), $perPage, $page,
|
||||
['path' => request()->url()]
|
||||
);
|
||||
} else {
|
||||
if ($this->filtroStato === 'attivo') {
|
||||
$query->where('attivo', true);
|
||||
} elseif ($this->filtroStato === 'inattivo') {
|
||||
$query->where('attivo', false);
|
||||
}
|
||||
|
||||
// cognome/nome encrypted, so sort in-memory
|
||||
$all = $query->get()->sortBy(function ($p) {
|
||||
return match ($this->sortField) {
|
||||
'nome' => mb_strtolower($p->nome),
|
||||
'cognome' => mb_strtolower($p->cognome),
|
||||
default => mb_strtolower($p->cognome),
|
||||
};
|
||||
}, SORT_REGULAR, $this->sortDirection === 'desc');
|
||||
|
||||
$page = $this->getPage();
|
||||
$perPage = 20;
|
||||
$items = $all->slice(($page - 1) * $perPage, $perPage)->values();
|
||||
$proclamatori = new \Illuminate\Pagination\LengthAwarePaginator(
|
||||
$items, $all->count(), $perPage, $page,
|
||||
['path' => request()->url()]
|
||||
);
|
||||
}
|
||||
|
||||
return view('livewire.proclamatori.proclamatore-index', [
|
||||
'proclamatori' => $proclamatori,
|
||||
]);
|
||||
}
|
||||
}
|
||||
42
app/Livewire/Proclamatori/ProclamatoreShow.php
Normal file
42
app/Livewire/Proclamatori/ProclamatoreShow.php
Normal file
@@ -0,0 +1,42 @@
|
||||
<?php
|
||||
|
||||
namespace App\Livewire\Proclamatori;
|
||||
|
||||
use Livewire\Component;
|
||||
use App\Models\Proclamatore;
|
||||
use App\Models\Assegnazione;
|
||||
|
||||
class ProclamatoreShow extends Component
|
||||
{
|
||||
public Proclamatore $proclamatore;
|
||||
|
||||
public function mount(Proclamatore $proclamatore)
|
||||
{
|
||||
$this->proclamatore = $proclamatore;
|
||||
}
|
||||
|
||||
public function render()
|
||||
{
|
||||
$assegnazioni = Assegnazione::where('proclamatore_id', $this->proclamatore->id)
|
||||
->with(['territorio', 'annoTeocratico', 'campagna'])
|
||||
->orderByDesc('assigned_at')
|
||||
->get()
|
||||
->groupBy(fn($a) => $a->annoTeocratico->label);
|
||||
|
||||
$stats = [
|
||||
'totale_assegnazioni' => Assegnazione::where('proclamatore_id', $this->proclamatore->id)->count(),
|
||||
'attualmente_assegnati' => Assegnazione::where('proclamatore_id', $this->proclamatore->id)->aperte()->count(),
|
||||
'media_giorni' => round(
|
||||
Assegnazione::where('proclamatore_id', $this->proclamatore->id)
|
||||
->chiuse()
|
||||
->get()
|
||||
->avg(fn($a) => $a->giorni) ?? 0
|
||||
),
|
||||
];
|
||||
|
||||
return view('livewire.proclamatori.proclamatore-show', [
|
||||
'assegnazioniPerAnno' => $assegnazioni,
|
||||
'stats' => $stats,
|
||||
]);
|
||||
}
|
||||
}
|
||||
101
app/Livewire/Registro.php
Normal file
101
app/Livewire/Registro.php
Normal file
@@ -0,0 +1,101 @@
|
||||
<?php
|
||||
|
||||
namespace App\Livewire;
|
||||
|
||||
use Livewire\Component;
|
||||
use Livewire\WithPagination;
|
||||
use App\Models\Assegnazione;
|
||||
use App\Models\AnnoTeocratico;
|
||||
use App\Models\Zona;
|
||||
use App\Models\Tipologia;
|
||||
|
||||
class Registro extends Component
|
||||
{
|
||||
use WithPagination;
|
||||
|
||||
public string $search = '';
|
||||
public string $filtroAnno = '';
|
||||
public string $filtroZona = '';
|
||||
public string $filtroTipologia = '';
|
||||
public string $filtroStato = ''; // aperte, chiuse
|
||||
public string $sortField = 'assigned_at';
|
||||
public string $sortDirection = 'desc';
|
||||
|
||||
protected $queryString = [
|
||||
'search' => ['except' => ''],
|
||||
'filtroAnno' => ['except' => ''],
|
||||
'filtroZona' => ['except' => ''],
|
||||
'filtroTipologia' => ['except' => ''],
|
||||
'filtroStato' => ['except' => ''],
|
||||
];
|
||||
|
||||
public function updatingSearch()
|
||||
{
|
||||
$this->resetPage();
|
||||
}
|
||||
|
||||
public function sortBy(string $field)
|
||||
{
|
||||
if ($this->sortField === $field) {
|
||||
$this->sortDirection = $this->sortDirection === 'asc' ? 'desc' : 'asc';
|
||||
} else {
|
||||
$this->sortField = $field;
|
||||
$this->sortDirection = 'desc';
|
||||
}
|
||||
}
|
||||
|
||||
public function render()
|
||||
{
|
||||
$query = Assegnazione::with(['territorio.zona', 'proclamatore', 'annoTeocratico', 'campagna']);
|
||||
|
||||
if ($this->filtroAnno) {
|
||||
$query->where('anno_teocratico_id', $this->filtroAnno);
|
||||
}
|
||||
|
||||
if ($this->filtroStato === 'aperte') {
|
||||
$query->aperte();
|
||||
} elseif ($this->filtroStato === 'chiuse') {
|
||||
$query->chiuse();
|
||||
}
|
||||
|
||||
if ($this->filtroZona) {
|
||||
$query->whereHas('territorio', fn($q) => $q->where('zona_id', $this->filtroZona));
|
||||
}
|
||||
|
||||
if ($this->filtroTipologia) {
|
||||
$query->whereHas('territorio', fn($q) => $q->where('tipologia_id', $this->filtroTipologia));
|
||||
}
|
||||
|
||||
$query->orderBy($this->sortField, $this->sortDirection);
|
||||
|
||||
// In-memory search for encrypted proclamatore fields / territorio numero
|
||||
if ($this->search !== '') {
|
||||
$all = $query->get();
|
||||
$filtered = $all->filter(function ($a) {
|
||||
$haystack = mb_strtolower(
|
||||
($a->territorio?->numero ?? '') . ' ' .
|
||||
($a->proclamatore?->nome ?? '') . ' ' .
|
||||
($a->proclamatore?->cognome ?? '')
|
||||
);
|
||||
return str_contains($haystack, mb_strtolower($this->search));
|
||||
});
|
||||
|
||||
$page = $this->getPage();
|
||||
$perPage = 25;
|
||||
$items = $filtered->slice(($page - 1) * $perPage, $perPage)->values();
|
||||
$assegnazioni = new \Illuminate\Pagination\LengthAwarePaginator(
|
||||
$items, $filtered->count(), $perPage, $page,
|
||||
['path' => request()->url()]
|
||||
);
|
||||
} else {
|
||||
$assegnazioni = $query->paginate(25);
|
||||
}
|
||||
|
||||
return view('livewire.registro', [
|
||||
'assegnazioni' => $assegnazioni,
|
||||
'anni' => AnnoTeocratico::orderByDesc('start_date')->get(),
|
||||
'zone' => Zona::attive()->orderBy('nome')->get(),
|
||||
'tipologie' => Tipologia::orderBy('nome')->get(),
|
||||
]);
|
||||
}
|
||||
}
|
||||
53
app/Livewire/Settings/SettingsEdit.php
Normal file
53
app/Livewire/Settings/SettingsEdit.php
Normal file
@@ -0,0 +1,53 @@
|
||||
<?php
|
||||
|
||||
namespace App\Livewire\Settings;
|
||||
|
||||
use Livewire\Component;
|
||||
use App\Models\Setting;
|
||||
|
||||
class SettingsEdit extends Component
|
||||
{
|
||||
public string $congregazione_nome = '';
|
||||
public int $giorni_giacenza_prioritari = 180;
|
||||
public int $giorni_per_smarrito = 120;
|
||||
public int $audit_retention_days = 365;
|
||||
|
||||
public function mount()
|
||||
{
|
||||
$settings = Setting::instance();
|
||||
$this->congregazione_nome = $settings->congregazione_nome ?? '';
|
||||
$this->giorni_giacenza_prioritari = $settings->giorni_giacenza_prioritari ?? 180;
|
||||
$this->giorni_per_smarrito = $settings->giorni_per_smarrito ?? 120;
|
||||
$this->audit_retention_days = $settings->audit_retention_days ?? 365;
|
||||
}
|
||||
|
||||
protected function rules(): array
|
||||
{
|
||||
return [
|
||||
'congregazione_nome' => 'required|string|max:255',
|
||||
'giorni_giacenza_prioritari' => 'required|integer|min:1|max:730',
|
||||
'giorni_per_smarrito' => 'required|integer|min:30|max:365',
|
||||
'audit_retention_days' => 'required|integer|min:30|max:3650',
|
||||
];
|
||||
}
|
||||
|
||||
public function save()
|
||||
{
|
||||
$this->validate();
|
||||
|
||||
$settings = Setting::instance();
|
||||
$settings->update([
|
||||
'congregazione_nome' => $this->congregazione_nome,
|
||||
'giorni_giacenza_prioritari' => $this->giorni_giacenza_prioritari,
|
||||
'giorni_per_smarrito' => $this->giorni_per_smarrito,
|
||||
'audit_retention_days' => $this->audit_retention_days,
|
||||
]);
|
||||
|
||||
session()->flash('success', 'Impostazioni aggiornate.');
|
||||
}
|
||||
|
||||
public function render()
|
||||
{
|
||||
return view('livewire.settings.settings-edit');
|
||||
}
|
||||
}
|
||||
68
app/Livewire/Settings/TipologieIndex.php
Normal file
68
app/Livewire/Settings/TipologieIndex.php
Normal file
@@ -0,0 +1,68 @@
|
||||
<?php
|
||||
|
||||
namespace App\Livewire\Settings;
|
||||
|
||||
use Livewire\Component;
|
||||
use App\Models\Tipologia;
|
||||
|
||||
class TipologieIndex extends Component
|
||||
{
|
||||
public string $nuovaTipologia = '';
|
||||
public ?int $editingId = null;
|
||||
public string $editingNome = '';
|
||||
|
||||
public function addTipologia()
|
||||
{
|
||||
$this->validate(['nuovaTipologia' => 'required|string|max:100|unique:tipologie,nome']);
|
||||
Tipologia::create(['nome' => $this->nuovaTipologia, 'attivo' => true]);
|
||||
$this->nuovaTipologia = '';
|
||||
session()->flash('success', 'Tipologia aggiunta.');
|
||||
}
|
||||
|
||||
public function startEdit(int $id)
|
||||
{
|
||||
$tipo = Tipologia::findOrFail($id);
|
||||
$this->editingId = $id;
|
||||
$this->editingNome = $tipo->nome;
|
||||
}
|
||||
|
||||
public function saveEdit()
|
||||
{
|
||||
$this->validate(['editingNome' => "required|string|max:100|unique:tipologie,nome,{$this->editingId}"]);
|
||||
$tipo = Tipologia::findOrFail($this->editingId);
|
||||
$tipo->update(['nome' => $this->editingNome]);
|
||||
$this->editingId = null;
|
||||
$this->editingNome = '';
|
||||
session()->flash('success', 'Tipologia aggiornata.');
|
||||
}
|
||||
|
||||
public function cancelEdit()
|
||||
{
|
||||
$this->editingId = null;
|
||||
$this->editingNome = '';
|
||||
}
|
||||
|
||||
public function toggleActive(int $id)
|
||||
{
|
||||
$tipo = Tipologia::findOrFail($id);
|
||||
$tipo->update(['attivo' => !$tipo->attivo]);
|
||||
}
|
||||
|
||||
public function deleteTipologia(int $id)
|
||||
{
|
||||
$tipo = Tipologia::findOrFail($id);
|
||||
if ($tipo->territori()->exists()) {
|
||||
session()->flash('error', "Impossibile eliminare: la tipologia '{$tipo->nome}' ha territori associati.");
|
||||
return;
|
||||
}
|
||||
$tipo->delete();
|
||||
session()->flash('success', "Tipologia '{$tipo->nome}' eliminata.");
|
||||
}
|
||||
|
||||
public function render()
|
||||
{
|
||||
return view('livewire.settings.tipologie-index', [
|
||||
'tipologie' => Tipologia::orderBy('nome')->get(),
|
||||
]);
|
||||
}
|
||||
}
|
||||
68
app/Livewire/Settings/ZoneIndex.php
Normal file
68
app/Livewire/Settings/ZoneIndex.php
Normal file
@@ -0,0 +1,68 @@
|
||||
<?php
|
||||
|
||||
namespace App\Livewire\Settings;
|
||||
|
||||
use Livewire\Component;
|
||||
use App\Models\Zona;
|
||||
|
||||
class ZoneIndex extends Component
|
||||
{
|
||||
public string $nuovaZona = '';
|
||||
public ?int $editingId = null;
|
||||
public string $editingNome = '';
|
||||
|
||||
public function addZona()
|
||||
{
|
||||
$this->validate(['nuovaZona' => 'required|string|max:100|unique:zone,nome']);
|
||||
Zona::create(['nome' => $this->nuovaZona, 'attivo' => true]);
|
||||
$this->nuovaZona = '';
|
||||
session()->flash('success', 'Zona aggiunta.');
|
||||
}
|
||||
|
||||
public function startEdit(int $id)
|
||||
{
|
||||
$zona = Zona::findOrFail($id);
|
||||
$this->editingId = $id;
|
||||
$this->editingNome = $zona->nome;
|
||||
}
|
||||
|
||||
public function saveEdit()
|
||||
{
|
||||
$this->validate(['editingNome' => "required|string|max:100|unique:zone,nome,{$this->editingId}"]);
|
||||
$zona = Zona::findOrFail($this->editingId);
|
||||
$zona->update(['nome' => $this->editingNome]);
|
||||
$this->editingId = null;
|
||||
$this->editingNome = '';
|
||||
session()->flash('success', 'Zona aggiornata.');
|
||||
}
|
||||
|
||||
public function cancelEdit()
|
||||
{
|
||||
$this->editingId = null;
|
||||
$this->editingNome = '';
|
||||
}
|
||||
|
||||
public function toggleActive(int $id)
|
||||
{
|
||||
$zona = Zona::findOrFail($id);
|
||||
$zona->update(['attivo' => !$zona->attivo]);
|
||||
}
|
||||
|
||||
public function deleteZona(int $id)
|
||||
{
|
||||
$zona = Zona::findOrFail($id);
|
||||
if ($zona->territori()->exists()) {
|
||||
session()->flash('error', "Impossibile eliminare: la zona '{$zona->nome}' ha territori associati.");
|
||||
return;
|
||||
}
|
||||
$zona->delete();
|
||||
session()->flash('success', "Zona '{$zona->nome}' eliminata.");
|
||||
}
|
||||
|
||||
public function render()
|
||||
{
|
||||
return view('livewire.settings.zone-index', [
|
||||
'zone' => Zona::orderBy('nome')->get(),
|
||||
]);
|
||||
}
|
||||
}
|
||||
121
app/Livewire/Setup/Wizard.php
Normal file
121
app/Livewire/Setup/Wizard.php
Normal file
@@ -0,0 +1,121 @@
|
||||
<?php
|
||||
|
||||
namespace App\Livewire\Setup;
|
||||
|
||||
use Livewire\Component;
|
||||
use Livewire\WithFileUploads;
|
||||
use App\Models\Setting;
|
||||
use App\Models\User;
|
||||
use Illuminate\Support\Facades\Hash;
|
||||
|
||||
class Wizard extends Component
|
||||
{
|
||||
use WithFileUploads;
|
||||
|
||||
public int $step = 1;
|
||||
|
||||
// Step 1
|
||||
public string $congregazione_nome = '';
|
||||
public $logo;
|
||||
|
||||
// Step 2
|
||||
public int $giorni_giacenza_da_assegnare = 120;
|
||||
public int $giorni_giacenza_prioritari = 180;
|
||||
public int $giorni_per_smarrito = 120;
|
||||
public int $home_limit_list = 10;
|
||||
|
||||
// Step 3 (admin creation if no users)
|
||||
public string $admin_name = '';
|
||||
public string $admin_email = '';
|
||||
public string $admin_password = '';
|
||||
public string $admin_password_confirmation = '';
|
||||
public bool $needsAdmin = false;
|
||||
|
||||
public function mount()
|
||||
{
|
||||
if (Setting::isSetupComplete()) {
|
||||
return redirect()->route('dashboard');
|
||||
}
|
||||
|
||||
$this->needsAdmin = User::count() <= 1;
|
||||
|
||||
$setting = Setting::first();
|
||||
if ($setting) {
|
||||
$this->congregazione_nome = $setting->congregazione_nome ?? '';
|
||||
$this->giorni_giacenza_da_assegnare = $setting->giorni_giacenza_da_assegnare;
|
||||
$this->giorni_giacenza_prioritari = $setting->giorni_giacenza_prioritari;
|
||||
$this->giorni_per_smarrito = $setting->giorni_per_smarrito;
|
||||
$this->home_limit_list = $setting->home_limit_list;
|
||||
}
|
||||
}
|
||||
|
||||
public function nextStep()
|
||||
{
|
||||
if ($this->step === 1) {
|
||||
$this->validate([
|
||||
'congregazione_nome' => 'required|string|max:255',
|
||||
'logo' => 'nullable|image|max:2048',
|
||||
]);
|
||||
}
|
||||
|
||||
if ($this->step === 2) {
|
||||
$this->validate([
|
||||
'giorni_giacenza_da_assegnare' => 'required|integer|min:1|max:999',
|
||||
'giorni_giacenza_prioritari' => 'required|integer|min:1|max:999',
|
||||
'giorni_per_smarrito' => 'required|integer|min:1|max:999',
|
||||
'home_limit_list' => 'required|integer|min:1|max:100',
|
||||
]);
|
||||
}
|
||||
|
||||
$this->step++;
|
||||
}
|
||||
|
||||
public function previousStep()
|
||||
{
|
||||
$this->step = max(1, $this->step - 1);
|
||||
}
|
||||
|
||||
public function finish()
|
||||
{
|
||||
if ($this->needsAdmin) {
|
||||
$this->validate([
|
||||
'admin_name' => 'required|string|max:255',
|
||||
'admin_email' => 'required|email|unique:users,email',
|
||||
'admin_password' => 'required|min:8|confirmed',
|
||||
]);
|
||||
}
|
||||
|
||||
$setting = Setting::instance();
|
||||
$setting->congregazione_nome = $this->congregazione_nome;
|
||||
$setting->giorni_giacenza_da_assegnare = $this->giorni_giacenza_da_assegnare;
|
||||
$setting->giorni_giacenza_prioritari = $this->giorni_giacenza_prioritari;
|
||||
$setting->giorni_per_smarrito = $this->giorni_per_smarrito;
|
||||
$setting->home_limit_list = $this->home_limit_list;
|
||||
$setting->setup_completed = true;
|
||||
|
||||
if ($this->logo) {
|
||||
$path = $this->logo->store('logos', 'public');
|
||||
$setting->logo_path = $path;
|
||||
}
|
||||
|
||||
$setting->save();
|
||||
|
||||
if ($this->needsAdmin && $this->admin_email) {
|
||||
$admin = User::create([
|
||||
'name' => $this->admin_name,
|
||||
'email' => $this->admin_email,
|
||||
'password' => Hash::make($this->admin_password),
|
||||
]);
|
||||
$admin->assignRole('amministratore');
|
||||
}
|
||||
|
||||
session()->flash('success', 'Setup completato con successo!');
|
||||
return redirect()->route('dashboard');
|
||||
}
|
||||
|
||||
public function render()
|
||||
{
|
||||
return view('livewire.setup.wizard')
|
||||
->layout('components.layouts.guest', ['title' => 'Setup iniziale']);
|
||||
}
|
||||
}
|
||||
45
app/Livewire/Territori/TerritorioCestino.php
Normal file
45
app/Livewire/Territori/TerritorioCestino.php
Normal file
@@ -0,0 +1,45 @@
|
||||
<?php
|
||||
|
||||
namespace App\Livewire\Territori;
|
||||
|
||||
use Livewire\Component;
|
||||
use Livewire\WithPagination;
|
||||
use App\Models\Territorio;
|
||||
|
||||
class TerritorioCestino extends Component
|
||||
{
|
||||
use WithPagination;
|
||||
|
||||
public function restore(int $id)
|
||||
{
|
||||
$territorio = Territorio::onlyTrashed()->findOrFail($id);
|
||||
$territorio->restore();
|
||||
activity()->causedBy(auth()->user())
|
||||
->performedOn($territorio)
|
||||
->log('restored');
|
||||
session()->flash('success', "Territorio {$territorio->numero} ripristinato.");
|
||||
}
|
||||
|
||||
public function forceDelete(int $id)
|
||||
{
|
||||
$territorio = Territorio::onlyTrashed()->findOrFail($id);
|
||||
|
||||
if ($territorio->assegnazioni()->exists()) {
|
||||
session()->flash('error', 'Impossibile eliminare definitivamente: il territorio ha assegnazioni nello storico.');
|
||||
return;
|
||||
}
|
||||
|
||||
activity()->causedBy(auth()->user())
|
||||
->withProperties(['numero' => $territorio->numero])
|
||||
->log('force_deleted_territorio');
|
||||
$territorio->forceDelete();
|
||||
session()->flash('success', 'Territorio eliminato definitivamente.');
|
||||
}
|
||||
|
||||
public function render()
|
||||
{
|
||||
return view('livewire.territori.territorio-cestino', [
|
||||
'territori' => Territorio::onlyTrashed()->orderByDesc('deleted_at')->paginate(20),
|
||||
]);
|
||||
}
|
||||
}
|
||||
74
app/Livewire/Territori/TerritorioCreate.php
Normal file
74
app/Livewire/Territori/TerritorioCreate.php
Normal file
@@ -0,0 +1,74 @@
|
||||
<?php
|
||||
|
||||
namespace App\Livewire\Territori;
|
||||
|
||||
use Livewire\Component;
|
||||
use Livewire\WithFileUploads;
|
||||
use App\Models\Territorio;
|
||||
use App\Models\Zona;
|
||||
use App\Models\Tipologia;
|
||||
|
||||
class TerritorioCreate extends Component
|
||||
{
|
||||
use WithFileUploads;
|
||||
|
||||
public string $numero = '';
|
||||
public ?int $zona_id = null;
|
||||
public ?int $tipologia_id = null;
|
||||
public string $note = '';
|
||||
public string $confini = '';
|
||||
public $pdf;
|
||||
public bool $prioritario = false;
|
||||
|
||||
protected function rules(): array
|
||||
{
|
||||
return [
|
||||
'numero' => 'required|string|max:20|unique:territori,numero',
|
||||
'zona_id' => 'nullable|exists:zone,id',
|
||||
'tipologia_id' => 'nullable|exists:tipologie,id',
|
||||
'note' => 'nullable|string|max:5000',
|
||||
'confini' => 'nullable|string|max:5000',
|
||||
'pdf' => 'nullable|file|mimes:pdf|max:10240',
|
||||
'prioritario' => 'boolean',
|
||||
];
|
||||
}
|
||||
|
||||
public function save()
|
||||
{
|
||||
$this->validate();
|
||||
|
||||
$data = [
|
||||
'numero' => $this->numero,
|
||||
'zona_id' => $this->zona_id,
|
||||
'tipologia_id' => $this->tipologia_id,
|
||||
'note' => $this->note ?: null,
|
||||
'confini' => $this->confini ?: null,
|
||||
'prioritario' => $this->prioritario,
|
||||
'attivo' => true,
|
||||
];
|
||||
|
||||
if ($this->pdf) {
|
||||
$data['pdf_path'] = $this->pdf->store('territori-pdf', 'public');
|
||||
}
|
||||
|
||||
$territorio = Territorio::create($data);
|
||||
|
||||
activity()->causedBy(auth()->user())
|
||||
->performedOn($territorio)
|
||||
->withProperties(['numero' => $territorio->numero])
|
||||
->log('created');
|
||||
|
||||
session()->flash('success', "Territorio {$territorio->numero} creato.");
|
||||
return redirect()->route('territori.index');
|
||||
}
|
||||
|
||||
public function render()
|
||||
{
|
||||
return view('livewire.territori.territorio-form', [
|
||||
'zone' => Zona::attive()->get(),
|
||||
'tipologie' => Tipologia::attive()->get(),
|
||||
'isEdit' => false,
|
||||
'title' => 'Nuovo Territorio',
|
||||
]);
|
||||
}
|
||||
}
|
||||
96
app/Livewire/Territori/TerritorioEdit.php
Normal file
96
app/Livewire/Territori/TerritorioEdit.php
Normal file
@@ -0,0 +1,96 @@
|
||||
<?php
|
||||
|
||||
namespace App\Livewire\Territori;
|
||||
|
||||
use Livewire\Component;
|
||||
use Livewire\WithFileUploads;
|
||||
use App\Models\Territorio;
|
||||
use App\Models\Zona;
|
||||
use App\Models\Tipologia;
|
||||
use Illuminate\Support\Facades\Storage;
|
||||
|
||||
class TerritorioEdit extends Component
|
||||
{
|
||||
use WithFileUploads;
|
||||
|
||||
public Territorio $territorio;
|
||||
public string $numero = '';
|
||||
public ?int $zona_id = null;
|
||||
public ?int $tipologia_id = null;
|
||||
public string $note = '';
|
||||
public string $confini = '';
|
||||
public $pdf;
|
||||
public bool $prioritario = false;
|
||||
|
||||
public function mount(Territorio $territorio)
|
||||
{
|
||||
$this->territorio = $territorio;
|
||||
$this->numero = $territorio->numero;
|
||||
$this->zona_id = $territorio->zona_id;
|
||||
$this->tipologia_id = $territorio->tipologia_id;
|
||||
$this->note = $territorio->note ?? '';
|
||||
$this->confini = $territorio->confini ?? '';
|
||||
$this->prioritario = $territorio->prioritario;
|
||||
}
|
||||
|
||||
protected function rules(): array
|
||||
{
|
||||
return [
|
||||
'numero' => 'required|string|max:20|unique:territori,numero,' . $this->territorio->id,
|
||||
'zona_id' => 'nullable|exists:zone,id',
|
||||
'tipologia_id' => 'nullable|exists:tipologie,id',
|
||||
'note' => 'nullable|string|max:5000',
|
||||
'confini' => 'nullable|string|max:5000',
|
||||
'pdf' => 'nullable|file|mimes:pdf|max:10240',
|
||||
'prioritario' => 'boolean',
|
||||
];
|
||||
}
|
||||
|
||||
public function save()
|
||||
{
|
||||
$this->validate();
|
||||
|
||||
$data = [
|
||||
'numero' => $this->numero,
|
||||
'zona_id' => $this->zona_id,
|
||||
'tipologia_id' => $this->tipologia_id,
|
||||
'note' => $this->note ?: null,
|
||||
'confini' => $this->confini ?: null,
|
||||
'prioritario' => $this->prioritario,
|
||||
];
|
||||
|
||||
if ($this->pdf) {
|
||||
// Remove old PDF
|
||||
if ($this->territorio->pdf_path) {
|
||||
Storage::disk('public')->delete($this->territorio->pdf_path);
|
||||
}
|
||||
$data['pdf_path'] = $this->pdf->store('territori-pdf', 'public');
|
||||
}
|
||||
|
||||
$this->territorio->update($data);
|
||||
|
||||
session()->flash('success', "Territorio {$this->territorio->numero} aggiornato.");
|
||||
return redirect()->route('territori.show', $this->territorio);
|
||||
}
|
||||
|
||||
public function removePdf()
|
||||
{
|
||||
if ($this->territorio->pdf_path) {
|
||||
Storage::disk('public')->delete($this->territorio->pdf_path);
|
||||
$this->territorio->update(['pdf_path' => null]);
|
||||
activity()->causedBy(auth()->user())
|
||||
->performedOn($this->territorio)
|
||||
->log('removed_pdf');
|
||||
}
|
||||
}
|
||||
|
||||
public function render()
|
||||
{
|
||||
return view('livewire.territori.territorio-form', [
|
||||
'zone' => Zona::attive()->get(),
|
||||
'tipologie' => Tipologia::attive()->get(),
|
||||
'isEdit' => true,
|
||||
'title' => "Modifica Territorio {$this->territorio->numero}",
|
||||
]);
|
||||
}
|
||||
}
|
||||
105
app/Livewire/Territori/TerritorioIndex.php
Normal file
105
app/Livewire/Territori/TerritorioIndex.php
Normal file
@@ -0,0 +1,105 @@
|
||||
<?php
|
||||
|
||||
namespace App\Livewire\Territori;
|
||||
|
||||
use Livewire\Component;
|
||||
use Livewire\WithPagination;
|
||||
use App\Models\Territorio;
|
||||
use App\Models\Zona;
|
||||
use App\Models\Tipologia;
|
||||
|
||||
class TerritorioIndex extends Component
|
||||
{
|
||||
use WithPagination;
|
||||
|
||||
public string $search = '';
|
||||
public string $filterZona = '';
|
||||
public string $filterTipologia = '';
|
||||
public string $filterStato = '';
|
||||
public string $sortField = 'numero';
|
||||
public string $sortDirection = 'asc';
|
||||
|
||||
protected $queryString = [
|
||||
'search' => ['except' => ''],
|
||||
'filterZona' => ['except' => ''],
|
||||
'filterTipologia' => ['except' => ''],
|
||||
'filterStato' => ['except' => ''],
|
||||
];
|
||||
|
||||
public function updatingSearch()
|
||||
{
|
||||
$this->resetPage();
|
||||
}
|
||||
|
||||
public function sortBy(string $field)
|
||||
{
|
||||
if ($this->sortField === $field) {
|
||||
$this->sortDirection = $this->sortDirection === 'asc' ? 'desc' : 'asc';
|
||||
} else {
|
||||
$this->sortField = $field;
|
||||
$this->sortDirection = 'asc';
|
||||
}
|
||||
}
|
||||
|
||||
public function toggleActive(Territorio $territorio)
|
||||
{
|
||||
$territorio->update(['attivo' => !$territorio->attivo]);
|
||||
activity()->causedBy(auth()->user())
|
||||
->performedOn($territorio)
|
||||
->withProperties(['attivo' => $territorio->attivo])
|
||||
->log($territorio->attivo ? 'activated' : 'deactivated');
|
||||
}
|
||||
|
||||
public function togglePriority(Territorio $territorio)
|
||||
{
|
||||
$territorio->update(['prioritario' => !$territorio->prioritario]);
|
||||
activity()->causedBy(auth()->user())
|
||||
->performedOn($territorio)
|
||||
->withProperties(['prioritario' => $territorio->prioritario])
|
||||
->log($territorio->prioritario ? 'set_priority' : 'unset_priority');
|
||||
}
|
||||
|
||||
public function deleteTerritorio(Territorio $territorio)
|
||||
{
|
||||
activity()->causedBy(auth()->user())
|
||||
->performedOn($territorio)
|
||||
->log('soft_deleted');
|
||||
$territorio->delete();
|
||||
session()->flash('success', "Territorio {$territorio->numero} spostato nel cestino.");
|
||||
}
|
||||
|
||||
public function render()
|
||||
{
|
||||
$query = Territorio::with(['zona', 'tipologia', 'assegnazioneCorrente.proclamatore']);
|
||||
|
||||
if ($this->search) {
|
||||
$query->where('numero', 'like', "%{$this->search}%");
|
||||
}
|
||||
|
||||
if ($this->filterZona) {
|
||||
$query->where('zona_id', $this->filterZona);
|
||||
}
|
||||
|
||||
if ($this->filterTipologia) {
|
||||
$query->where('tipologia_id', $this->filterTipologia);
|
||||
}
|
||||
|
||||
if ($this->filterStato) {
|
||||
match ($this->filterStato) {
|
||||
'in_reparto' => $query->inReparto(),
|
||||
'assegnato' => $query->assegnato(),
|
||||
'da_rientrare' => $query->daRientrare(),
|
||||
'inattivo' => $query->where('attivo', false),
|
||||
default => null,
|
||||
};
|
||||
}
|
||||
|
||||
$query->orderBy($this->sortField, $this->sortDirection);
|
||||
|
||||
return view('livewire.territori.territorio-index', [
|
||||
'territori' => $query->paginate(20),
|
||||
'zone' => Zona::attive()->get(),
|
||||
'tipologie' => Tipologia::attive()->get(),
|
||||
]);
|
||||
}
|
||||
}
|
||||
31
app/Livewire/Territori/TerritorioShow.php
Normal file
31
app/Livewire/Territori/TerritorioShow.php
Normal file
@@ -0,0 +1,31 @@
|
||||
<?php
|
||||
|
||||
namespace App\Livewire\Territori;
|
||||
|
||||
use Livewire\Component;
|
||||
use App\Models\Territorio;
|
||||
use App\Models\Assegnazione;
|
||||
use App\Models\AnnoTeocratico;
|
||||
|
||||
class TerritorioShow extends Component
|
||||
{
|
||||
public Territorio $territorio;
|
||||
|
||||
public function mount(Territorio $territorio)
|
||||
{
|
||||
$this->territorio = $territorio->load(['zona', 'tipologia']);
|
||||
}
|
||||
|
||||
public function render()
|
||||
{
|
||||
$assegnazioni = Assegnazione::where('territorio_id', $this->territorio->id)
|
||||
->with(['proclamatore', 'annoTeocratico', 'campagna', 'creatoDa', 'rientratoDa'])
|
||||
->orderByDesc('assigned_at')
|
||||
->get()
|
||||
->groupBy(fn($a) => $a->annoTeocratico->label);
|
||||
|
||||
return view('livewire.territori.territorio-show', [
|
||||
'assegnazioniPerAnno' => $assegnazioni,
|
||||
]);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user