diff --git a/HANDOFF.md b/HANDOFF.md index 399a87c..c49372d 100644 --- a/HANDOFF.md +++ b/HANDOFF.md @@ -127,6 +127,36 @@ email di prova e di conferma arrivate, link aperto senza login, account sbloccato; riquadro dopo l'assegnazione con WhatsApp e scelta. Configurazione e dati di prova rimossi dopo. +### Proclamatori senza contatti; il link si copia o si condivide (19 ottobre 2026) + +Decisione dell'utente, dopo il rilascio: i proclamatori **non hanno più email +né telefono**, e WhatsApp ed email spariscono come canali. Restano: + +- **Copia link**; +- **Condividi…**: `navigator.share()` con il messaggio già scritto + (`Assegnazione::messaggioLink()`), cioè il menu di condivisione del telefono + (WhatsApp, Telegram, SMS…), da cui chi usa l'app sceglie app e contatto. + Compare solo dove il browser lo offre (telefoni, alcuni computer); il canale + si annota a condivisione fatta, non se la si annulla. Nell'elenco dei + territori assegnati "Condividi" andato a buon fine segna il link come + inviato (`ElencoAssegnati::segnaCondiviso`). + +Cosa è cambiato: +- Migrazione `2026_10_19_000020_togli_contatti_proclamatori`: elimina le + colonne `email` e `telefono`. In produzione erano vuote (0 su 111) al + momento della decisione; su dev c'era un solo valore di prova. +- Tolti dal form, dalla scheda e dall'elenco dei proclamatori; tolti + `Proclamatore::telefonoInternazionale()/whatsappUrl()`, il componente + ``, gli stili `tm-whatsapp`, la notifica `LinkTerritorio` e + `Assegna::inviaEmail()`. +- XML: non si esportano più; un file vecchio che li contiene si importa + ignorandoli. +- `Assegnazione::CANALI_ATTIVI` = copia, condividi. In `CANALI_LINK` restano + anche "whatsapp" ed "email" solo per le assegnazioni già inviate così. +- La posta resta, ma solo per le email agli utenti (conferma dell'indirizzo). +- Informativa privacy, README e test (`InvioLinkTest`, al posto di + `ContattiProclamatoriTest`) aggiornati. + ### Allineamento di main in dev e passaggio in produzione (19 ottobre 2026) `main` è stato unito in `dev` (merge, non rebase): da `main` arrivano solo diff --git a/README.md b/README.md index 392acd0..69717de 100644 --- a/README.md +++ b/README.md @@ -720,10 +720,13 @@ docker compose --profile dev up -d --build - Anno teocratico rilevato automaticamente dalla data - Validazione: territorio non già assegnato, entrambi attivi - **Invio del link dopo l'assegnazione**: una finestra chiede il canale — - *Copia link* sempre, *WhatsApp* se il proclamatore ha un numero, *Email* se - ha un indirizzo e la posta è configurata (la manda l'app) — e se il link è - stato inviato. Solo dopo la risposta si va avanti; altrimenti l'assegnazione - resta segnalata **"Link ancora da inviare"**, con "Invia link" per riprendere + *Copia link*, oppure *Condividi…* (il menu di condivisione del telefono: + WhatsApp, Telegram, SMS…, dove il browser lo offre) — e se il link è stato + inviato. Il destinatario lo sceglie chi usa l'app dai propri contatti: i + proclamatori non hanno numero né email. Solo dopo la risposta si va avanti; + altrimenti l'assegnazione resta segnalata **"Link ancora da inviare"**, con + "Invia link" per riprendere. Anche dall'elenco dei territori assegnati si + può copiare o condividere il link - L'elenco dei territori assegnati si carica subito dopo la pagina e si filtra nel browser, senza attese - Rientro con calcolo automatico dei giorni @@ -794,8 +797,8 @@ una revoca ha effetto immediato senza bisogno di ricaricare la pagina. ## Sicurezza e GDPR ### Sicurezza -- **Cifratura a riposo**: nome, cognome e i facoltativi email e telefono dei - proclamatori cifrati con AES-256-CBC tramite `APP_KEY` +- **Cifratura a riposo**: nome e cognome dei proclamatori cifrati con + AES-256-CBC tramite `APP_KEY` - **Password**: hash bcrypt - **RBAC**: middleware `permission:` sulle rotte, riapplicato anche alle richieste Livewire @@ -816,16 +819,15 @@ una revoca ha effetto immediato senza bisogno di ricaricare la pagina. download del PDF, server di posta, durata della sessione, indirizzo e HTTPS, ruoli con i loro permessi, quanti dati sono conservati — si leggono al momento da impostazioni e configurazione (`App\Livewire\Privacy`) -- **Minimizzazione**: dei proclamatori si conservano nome, cognome, stato e, - solo se servono per mandare il link, email e telefono +- **Minimizzazione**: dei proclamatori si conservano solo nome, cognome e + stato. Niente telefono né email: il link lo manda chi assegna, scegliendo il + destinatario dai propri contatti, e l'app non vede quei contatti - **Cifratura a riposo**: i dati sono illeggibili anche con accesso diretto al DB - **Audit trail**: registro completo delle operazioni -- **Diritto all'oblio**: funzione di anonimizzazione irreversibile, che - cancella anche email e telefono +- **Diritto all'oblio**: funzione di anonimizzazione irreversibile - **Retention**: cancellazione automatica dei log oltre il periodo configurato - **No dati sensibili nei log**: l'audit registra gli identificativi, non i nomi - in chiaro; l'email con il link parte subito, mai in coda, perché il job - salverebbe l'indirizzo in chiaro + in chiaro - **Attenzione ai backup**: il pacchetto `.zip` e l'export XML contengono i dati dei proclamatori **in chiaro** (l'export li legge decifrati). Vanno custoditi in un luogo protetto e cancellati quando non servono @@ -840,7 +842,7 @@ una revoca ha effetto immediato senza bisogno di ricaricare la pagina. | `settings` | Configurazione singleton | | `zone` | Zone territoriali | | `tipologie` | Tipologie di territorio | -| `proclamatori` | Assegnatari, con nome/cognome e i facoltativi email/telefono cifrati, soft delete | +| `proclamatori` | Assegnatari, con nome/cognome cifrati e soft delete (niente contatti) | | `territori` | Cartoline con zona, tipologia, PDF, miniatura, soft delete | | `anni_teocratici` | Periodi settembre → agosto | | `campagne` | Periodi speciali con date e descrizione | diff --git a/app/Livewire/Assegnazioni/Assegna.php b/app/Livewire/Assegnazioni/Assegna.php index c60447a..9a7b106 100644 --- a/app/Livewire/Assegnazioni/Assegna.php +++ b/app/Livewire/Assegnazioni/Assegna.php @@ -10,10 +10,7 @@ use App\Models\Territorio; use App\Models\Proclamatore; use App\Models\Assegnazione; use App\Models\AnnoTeocratico; -use App\Notifications\LinkTerritorio; -use App\Services\ConfigurazionePosta; use Illuminate\Support\Facades\DB; -use Illuminate\Support\Facades\Notification; class Assegna extends Component { @@ -22,10 +19,11 @@ class Assegna extends Component // ─── Invio del link ───────────────────────────────────────── // // Dopo l'assegnazione non si torna subito alla pagina: si sceglie il - // canale (copia link sempre; WhatsApp ed email solo se il proclamatore ha - // numero / indirizzo), si dice se il link è partito, e solo allora si va - // avanti. Se la procedura si interrompe, l'assegnazione resta con - // link_da_inviare e lo si segnala dove compare. + // canale (copia link o Condividi: il destinatario lo sceglie chi usa + // l'app dai suoi contatti, i proclamatori non hanno numero né email), + // si dice se il link è partito, e solo allora si va avanti. Se la + // procedura si interrompe, l'assegnazione resta con link_da_inviare e lo + // si segnala dove compare. /** L'assegnazione di cui si sta mandando il link. */ #[Locked] @@ -162,15 +160,14 @@ class Assegna extends Component $this->erroreInvio = ''; } - /** Copia link o WhatsApp: l'invio vero lo fa chi usa l'app, qui si annota il canale. */ + /** + * Copia link o Condividi: l'invio vero lo fa chi usa l'app (il + * destinatario lo sceglie lui dai suoi contatti), qui si annota il canale. + */ public function canale(string $canale): void { $assegnazione = $this->assegnazioneInInvio(); - if (! $assegnazione || ! in_array($canale, ['copia', 'whatsapp'], true)) { - return; - } - - if ($canale === 'whatsapp' && ! $assegnazione->proclamatore?->whatsappUrl()) { + if (! $assegnazione || ! in_array($canale, Assegnazione::CANALI_ATTIVI, true) || ! $assegnazione->shortPdfUrl()) { return; } @@ -178,30 +175,6 @@ class Assegna extends Component $this->erroreInvio = ''; } - /** Email: la manda l'applicazione, subito (vedi LinkTerritorio). */ - public function inviaEmail(): void - { - $assegnazione = $this->assegnazioneInInvio(); - $email = trim((string) $assegnazione?->proclamatore?->email); - $link = $assegnazione?->shortPdfUrl(); - - if (! $assegnazione || $email === '' || ! $link || ! app(ConfigurazionePosta::class)->funzionante()) { - return; - } - - try { - Notification::route('mail', $email)->notifyNow(new LinkTerritorio($assegnazione, $link)); - } catch (\Throwable $e) { - report($e); - $this->erroreInvio = "L'email non è partita: controlla il server di posta, oppure usa un altro canale."; - - return; - } - - $this->canaleUsato = 'email'; - $this->erroreInvio = ''; - } - /** * La risposta a "Hai inviato il link?": la si registra e solo adesso si * va avanti. "Sì" vuole un canale usato; "No" lascia il link da inviare. @@ -284,7 +257,6 @@ class Assegna extends Component return view('livewire.assegnazioni.assegna', [ 'invio' => $invio, - 'postaAttiva' => $invio ? app(ConfigurazionePosta::class)->funzionante() : false, 'opzioniTerritori' => $opzioniTerritori, 'opzioniProclamatori' => $opzioniProclamatori, 'anteprimaIniziale' => $this->territorio_id ? $territori->firstWhere('id', $this->territorio_id)?->miniaturaUrl() : null, diff --git a/app/Livewire/Assegnazioni/ElencoAssegnati.php b/app/Livewire/Assegnazioni/ElencoAssegnati.php index b9f57fb..6ab8e5f 100644 --- a/app/Livewire/Assegnazioni/ElencoAssegnati.php +++ b/app/Livewire/Assegnazioni/ElencoAssegnati.php @@ -29,6 +29,13 @@ class ElencoAssegnati extends Component $assegnazione->registraInvioLink(! $assegnazione->link_sent, $assegnazione->link_canale); } + /** "Condividi" dall'elenco andato a buon fine: il link risulta inviato. */ + public function segnaCondiviso(int $assegnazioneId): void + { + $this->authorize('territori.assign'); + Assegnazione::aperte()->find($assegnazioneId)?->registraInvioLink(true, 'condividi'); + } + public function placeholder() { return view('livewire.assegnazioni.elenco-assegnati-attesa'); diff --git a/app/Livewire/Privacy.php b/app/Livewire/Privacy.php index a97aaf0..cfe20e0 100644 --- a/app/Livewire/Privacy.php +++ b/app/Livewire/Privacy.php @@ -28,7 +28,7 @@ class Privacy extends Component /** I permessi spiegati a chi legge l'informativa. */ public const PERMESSI = [ 'settings.manage' => 'impostazioni, utenti, backup e import', - 'proclamatori.manage' => 'anagrafica dei proclamatori, contatti compresi', + 'proclamatori.manage' => 'anagrafica dei proclamatori', 'territori.manage' => 'gestione dei territori e delle piantine', 'campagne.manage' => 'campagne', 'registro.view' => 'consultazione del registro delle assegnazioni', @@ -68,13 +68,11 @@ class Privacy extends Component ->map(fn ($p) => self::PERMESSI[$p] ?? $p) ->all(), ]), - // Quanti dati sono conservati adesso. I campi cifrati restano NULL - // quando sono vuoti, quindi si contano in SQL senza decifrare. + // Quanti dati sono conservati adesso: solo conteggi, contati in + // SQL, senza decifrare né mostrare nomi. 'conteggi' => [ 'proclamatori' => Proclamatore::count(), 'proclamatoriAttivi' => Proclamatore::where('attivo', true)->count(), - 'conEmail' => Proclamatore::whereNotNull('email')->count(), - 'conTelefono' => Proclamatore::whereNotNull('telefono')->count(), 'anonimizzati' => Proclamatore::onlyTrashed()->count(), 'utenti' => User::count(), 'assegnazioni' => Assegnazione::count(), diff --git a/app/Livewire/Proclamatori/ProclamatoreCreate.php b/app/Livewire/Proclamatori/ProclamatoreCreate.php index d5de8b3..837089d 100644 --- a/app/Livewire/Proclamatori/ProclamatoreCreate.php +++ b/app/Livewire/Proclamatori/ProclamatoreCreate.php @@ -13,8 +13,6 @@ class ProclamatoreCreate extends Component public string $nome = ''; public string $cognome = ''; public bool $attivo = true; - public string $email = ''; - public string $telefono = ''; protected function rules(): array { @@ -22,17 +20,6 @@ class ProclamatoreCreate extends Component 'nome' => 'required|string|max:100', 'cognome' => 'required|string|max:100', 'attivo' => 'boolean', - // Facoltativi: si controlla il formato solo se compilati. - 'email' => 'nullable|email|max:255', - 'telefono' => ['nullable', 'string', 'max:30', 'regex:/^\+?[0-9 ().\/-]{6,25}$/'], - ]; - } - - protected function messages(): array - { - return [ - 'email.email' => "L'indirizzo email non è valido (puoi anche lasciarlo vuoto).", - 'telefono.regex' => 'Il numero può contenere solo cifre, spazi e un "+" iniziale (puoi anche lasciarlo vuoto).', ]; } @@ -46,8 +33,6 @@ class ProclamatoreCreate extends Component 'nome' => $this->nome, 'cognome' => $this->cognome, 'attivo' => $this->attivo, - 'email' => trim($this->email) ?: null, - 'telefono' => trim($this->telefono) ?: null, ]); activity()->causedBy(auth()->user()) diff --git a/app/Livewire/Proclamatori/ProclamatoreEdit.php b/app/Livewire/Proclamatori/ProclamatoreEdit.php index c745693..653561d 100644 --- a/app/Livewire/Proclamatori/ProclamatoreEdit.php +++ b/app/Livewire/Proclamatori/ProclamatoreEdit.php @@ -14,8 +14,6 @@ class ProclamatoreEdit extends Component public string $nome = ''; public string $cognome = ''; public bool $attivo = true; - public string $email = ''; - public string $telefono = ''; public function mount(Proclamatore $proclamatore) { @@ -23,8 +21,6 @@ class ProclamatoreEdit extends Component $this->nome = $proclamatore->nome; $this->cognome = $proclamatore->cognome; $this->attivo = $proclamatore->attivo; - $this->email = (string) $proclamatore->email; - $this->telefono = (string) $proclamatore->telefono; } protected function rules(): array @@ -33,17 +29,6 @@ class ProclamatoreEdit extends Component 'nome' => 'required|string|max:100', 'cognome' => 'required|string|max:100', 'attivo' => 'boolean', - // Facoltativi: si controlla il formato solo se compilati. - 'email' => 'nullable|email|max:255', - 'telefono' => ['nullable', 'string', 'max:30', 'regex:/^\+?[0-9 ().\/-]{6,25}$/'], - ]; - } - - protected function messages(): array - { - return [ - 'email.email' => "L'indirizzo email non è valido (puoi anche lasciarlo vuoto).", - 'telefono.regex' => 'Il numero può contenere solo cifre, spazi e un "+" iniziale (puoi anche lasciarlo vuoto).', ]; } @@ -57,8 +42,6 @@ class ProclamatoreEdit extends Component 'nome' => $this->nome, 'cognome' => $this->cognome, 'attivo' => $this->attivo, - 'email' => trim($this->email) ?: null, - 'telefono' => trim($this->telefono) ?: null, ]); session()->flash('success', "Proclamatore {$this->proclamatore->nome_completo} aggiornato."); diff --git a/app/Livewire/Settings/XmlExchange.php b/app/Livewire/Settings/XmlExchange.php index 7a822a6..59d70d0 100644 --- a/app/Livewire/Settings/XmlExchange.php +++ b/app/Livewire/Settings/XmlExchange.php @@ -629,9 +629,8 @@ class XmlExchange extends Component 'nome' => (string) $proclamatoreNode->nome, 'cognome' => (string) $proclamatoreNode->cognome, 'attivo' => ((int) ($proclamatoreNode->attivo ?? 1)) === 1, - // Facoltativi, e assenti nei file di prima dell'ottobre 2026. - 'email' => trim((string) ($proclamatoreNode->email ?? '')) ?: null, - 'telefono' => trim((string) ($proclamatoreNode->telefono ?? '')) ?: null, + // e , presenti nei file del 13-19 ottobre + // 2026, si ignorano: i proclamatori non li hanno più. ]); $this->applyRowMeta('proclamatori', $p->id, $proclamatoreNode, true); $stats['proclamatori_importati']++; @@ -830,7 +829,7 @@ class XmlExchange extends Component ])->all(), 'zones' => $this->righeEsportate(Zona::query()->orderBy('id'), ['id', 'nome', 'attivo', 'created_at', 'updated_at']), 'tipologie' => $this->righeEsportate(Tipologia::query()->orderBy('id'), ['id', 'nome', 'attivo', 'created_at', 'updated_at']), - 'proclamatori' => $this->righeEsportate(Proclamatore::query()->withTrashed()->orderBy('id'), ['id', 'nome', 'cognome', 'email', 'telefono', 'attivo', 'deleted_at', 'created_at', 'updated_at']), + 'proclamatori' => $this->righeEsportate(Proclamatore::query()->withTrashed()->orderBy('id'), ['id', 'nome', 'cognome', 'attivo', 'deleted_at', 'created_at', 'updated_at']), 'territori' => $this->righeEsportate(Territorio::query()->withTrashed()->orderBy('id'), ['id', 'numero', 'zona_id', 'tipologia_id', 'confini', 'note', 'attivo', 'prioritario', 'pdf_path', 'thumbnail_path', 'deleted_at', 'created_at', 'updated_at']), 'anni_teocratici' => $this->righeEsportate(AnnoTeocratico::query()->orderBy('id'), ['id', 'label', 'start_date', 'end_date', 'created_at', 'updated_at']), 'campagne' => $this->righeEsportate(Campagna::query()->orderBy('id'), ['id', 'descrizione', 'start_date', 'end_date', 'created_at', 'updated_at']), @@ -1265,8 +1264,6 @@ class XmlExchange extends Component $this->addLegacyIdAttribute($node, $proclamatore); $this->addXmlText($node, 'nome', (string) ($proclamatore['nome'] ?? '')); $this->addXmlText($node, 'cognome', (string) ($proclamatore['cognome'] ?? '')); - $this->addXmlText($node, 'email', (string) ($proclamatore['email'] ?? '')); - $this->addXmlText($node, 'telefono', (string) ($proclamatore['telefono'] ?? '')); $node->addChild('attivo', (string) ((int) ($proclamatore['attivo'] ?? 1))); $node->addChild('deleted_at', (string) ($proclamatore['deleted_at'] ?? '')); $this->addTimestampNodes($node, $proclamatore); diff --git a/app/Models/Assegnazione.php b/app/Models/Assegnazione.php index c78054d..3feb38b 100644 --- a/app/Models/Assegnazione.php +++ b/app/Models/Assegnazione.php @@ -220,8 +220,17 @@ class Assegnazione extends Model $this->registraInvioLink(true, $canale); } - /** Canali con cui si manda il link al proclamatore. */ - public const CANALI_LINK = ['copia' => 'Copia link', 'whatsapp' => 'WhatsApp', 'email' => 'Email']; + /** + * Canali con cui si manda il link al proclamatore. Dal 19 ottobre 2026 se + * ne usano due: copia link e Condividi (il menu di condivisione del + * telefono); il destinatario lo sceglie chi usa l'app. "whatsapp" ed + * "email" restano solo perché le assegnazioni già inviate così lo + * mostrino e l'XML lo accetti. + */ + public const CANALI_LINK = ['copia' => 'Copia link', 'condividi' => 'Condividi', 'whatsapp' => 'WhatsApp', 'email' => 'Email']; + + /** I canali che si possono scegliere oggi nella finestra di invio. */ + public const CANALI_ATTIVI = ['copia', 'condividi']; /** * Esito dell'invio del link: inviato (con il canale usato) oppure no. Un @@ -243,10 +252,14 @@ class Assegnazione extends Model return $this->link_da_inviare && ! $this->link_sent && $this->is_aperta; } - /** Il messaggio con il link, uguale per WhatsApp ed email. */ - public function messaggioLink(): ?string + /** + * Il messaggio con il link, per "Condividi": il menu di condivisione del + * telefono, da cui chi usa l'app sceglie app e contatto. L'app non vede + * quei contatti. + */ + public function messaggioLink(?string $link = null): ?string { - $link = $this->shortPdfUrl(); + $link ??= $this->shortPdfUrl(); if ($link === null) { return null; } diff --git a/app/Models/Proclamatore.php b/app/Models/Proclamatore.php index 1d86328..1fed1c5 100644 --- a/app/Models/Proclamatore.php +++ b/app/Models/Proclamatore.php @@ -13,16 +13,16 @@ class Proclamatore extends Model protected $table = 'proclamatori'; - protected $fillable = ['nome', 'cognome', 'email', 'telefono', 'attivo']; + // Niente email né telefono (tolti il 19 ottobre 2026): il link della + // piantina lo manda chi assegna, copiandolo o con "Condividi", e il + // destinatario lo sceglie dai propri contatti. + protected $fillable = ['nome', 'cognome', 'attivo']; protected function casts(): array { return [ 'nome' => 'encrypted', 'cognome' => 'encrypted', - // Facoltativi. Cifrati come nome e cognome: dati personali. - 'email' => 'encrypted', - 'telefono' => 'encrypted', 'attivo' => 'boolean', ]; } @@ -30,7 +30,7 @@ class Proclamatore extends Model public function getActivitylogOptions(): LogOptions { return LogOptions::defaults() - ->logOnly(['attivo']) // Do NOT log nome/cognome/email/telefono in audit (encrypted, GDPR) + ->logOnly(['attivo']) // Do NOT log nome/cognome in audit (encrypted, GDPR) ->logOnlyDirty() ->dontSubmitEmptyLogs(); } @@ -43,46 +43,6 @@ class Proclamatore extends Model return trim($this->cognome . ' ' . $this->nome); } - /** - * Il numero come lo vuole WhatsApp: solo cifre, con il prefisso - * internazionale. Un numero senza prefisso si considera italiano: un - * cellulare scritto "333 1234567" diventa 393331234567. Null se non c'è un - * numero utilizzabile. - */ - public function telefonoInternazionale(): ?string - { - $grezzo = trim((string) $this->telefono); - if ($grezzo === '') { - return null; - } - - $cifre = preg_replace('/\D+/', '', $grezzo); - - if (str_starts_with($cifre, '00') && ! str_starts_with($grezzo, '+')) { - $cifre = substr($cifre, 2); - } elseif (! str_starts_with($grezzo, '+') && (! str_starts_with($cifre, '39') || strlen($cifre) <= 10)) { - // Senza prefisso: cellulari (3…) e fissi (0…) italiani. Un - // "39…" di 11 cifre o più ha già il prefisso, scritto senza "+". - $cifre = '39' . $cifre; - } - - return strlen($cifre) >= 8 && strlen($cifre) <= 15 ? $cifre : null; - } - - /** - * Il link che apre una chat WhatsApp con il proclamatore, con un messaggio - * già scritto se c'è. Null se non ha un numero utilizzabile. - */ - public function whatsappUrl(?string $messaggio = null): ?string - { - $numero = $this->telefonoInternazionale(); - if ($numero === null) { - return null; - } - - return 'https://wa.me/' . $numero . ($messaggio !== null && $messaggio !== '' ? '?text=' . rawurlencode($messaggio) : ''); - } - /** * L'opzione per il campo di scelta con ricerca (): si cerca per * cognome e nome. Nome e cognome sono cifrati nel database: la ricerca @@ -134,8 +94,6 @@ class Proclamatore extends Model { $this->nome = 'Anonimo'; $this->cognome = 'Proclamatore #' . $this->id; - $this->email = null; - $this->telefono = null; $this->attivo = false; $this->save(); $this->delete(); // soft delete diff --git a/app/Notifications/LinkTerritorio.php b/app/Notifications/LinkTerritorio.php deleted file mode 100644 index 463e6eb..0000000 --- a/app/Notifications/LinkTerritorio.php +++ /dev/null @@ -1,45 +0,0 @@ -assegnazione->territorio?->numero; - $congregazione = trim((string) Setting::getValue('congregazione_nome', '')); - // Stessa regola di AssignmentPdfController::linkScaduto(). - $scadenza = $this->assegnazione->assigned_at->copy() - ->addDays(max(1, (int) Setting::getValue('giorni_per_smarrito', 120))); - - return (new MailMessage) - ->subject("Territorio N° {$numero} — piantina") - ->greeting('Ciao ' . $this->assegnazione->proclamatore?->nome . ',') - ->line("ti è stato assegnato il territorio N° {$numero}.") - ->action('Apri la piantina', $this->link) - ->line("Il link funziona finché il territorio è assegnato a te, al massimo fino al {$scadenza->format('d/m/Y')}.") - ->salutation($congregazione !== '' ? $congregazione : 'TerManager2'); - } -} diff --git a/database/migrations/2026_10_19_000020_togli_contatti_proclamatori.php b/database/migrations/2026_10_19_000020_togli_contatti_proclamatori.php new file mode 100644 index 0000000..fbfca4d --- /dev/null +++ b/database/migrations/2026_10_19_000020_togli_contatti_proclamatori.php @@ -0,0 +1,35 @@ + Schema::hasColumn('proclamatori', $c))); + + if ($colonne !== []) { + Schema::table('proclamatori', fn (Blueprint $table) => $table->dropColumn($colonne)); + } + } + + public function down(): void + { + // Le colonne tornano vuote: i valori non si recuperano. + Schema::table('proclamatori', function (Blueprint $table) { + $table->text('email')->nullable()->after('cognome'); + $table->text('telefono')->nullable()->after('email'); + }); + } +}; diff --git a/resources/css/app.css b/resources/css/app.css index 42fb74e..a33494e 100644 --- a/resources/css/app.css +++ b/resources/css/app.css @@ -557,33 +557,6 @@ html { scroll-behavior: smooth; } background: var(--tm-superficie-alt); } -/* ─── Numero di telefono che apre WhatsApp ───────────────────────────────── */ - -.tm-whatsapp { - display: inline-flex; - align-items: center; - gap: 6px; - padding: 4px 10px; - border: 1px solid #b7e1c6; - border-radius: 999px; - background: #eaf7ef; - font-size: 13px; - font-weight: 600; - color: #17703f; - text-decoration: none; - white-space: nowrap; -} -.tm-whatsapp:hover { background: #d8f0e1; } -.tm-whatsapp:focus-visible { outline: 2px solid #17703f; outline-offset: 2px; } -.tm-whatsapp svg { width: 16px; height: 16px; flex: none; } -.tm-whatsapp-compatto { padding: 5px; } -.tm-whatsapp-grande { - padding: 10px 18px; - font-size: 15px; -} -.tm-whatsapp-grande svg { width: 20px; height: 20px; } -.tm-whatsapp-numero { font-weight: 500; opacity: .75; } - /* ─── Assegnazione appena fatta: il link da mandare ──────────────────────── */ .tm-da-inviare { diff --git a/resources/js/app.js b/resources/js/app.js index d85aef4..7cd3230 100644 --- a/resources/js/app.js +++ b/resources/js/app.js @@ -13,6 +13,17 @@ document.addEventListener('alpine:init', () => { // ordine. Nessuna richiesta al server mentre si scrive. window.Alpine.data('elencoCercabile', () => ({ q: '', + condivisibile: !! navigator.share, + // Condividi il link della riga (data-messaggio): il menu del telefono + // fa scegliere app e contatto. A condivisione fatta il link risulta + // inviato; se la si annulla non cambia niente. + condividi(pulsante) { + const riga = pulsante.closest('[data-link]'); + if (!riga || !navigator.share) return; + navigator.share({ title: riga.dataset.titolo, text: riga.dataset.messaggio }) + .then(() => this.$wire.segnaCondiviso(Number(riga.dataset.id))) + .catch(() => {}); + }, corrisponde(testo) { const parole = normalizza(this.q).split(/\s+/).filter(Boolean); return parole.every((p) => (testo ?? '').includes(p)); diff --git a/resources/views/components/whatsapp.blade.php b/resources/views/components/whatsapp.blade.php deleted file mode 100644 index f1d8fa1..0000000 --- a/resources/views/components/whatsapp.blade.php +++ /dev/null @@ -1,26 +0,0 @@ -{{-- - Il numero di un proclamatore che, cliccato, apre WhatsApp (wa.me: app sul - telefono, WhatsApp Web sul computer). Niente se il numero manca o non è - utilizzabile. - - proclamatore: il Proclamatore. - messaggio: testo già scritto nella chat (facoltativo). - compatto: solo l'icona, per le righe di un elenco. ---}} -@props(['proclamatore', 'messaggio' => null, 'compatto' => false]) - -@php($url = $proclamatore?->whatsappUrl($messaggio)) - -@if($url) - merge(['class' => 'tm-whatsapp' . ($compatto ? ' tm-whatsapp-compatto' : '')]) }} - title="Scrivi su WhatsApp a {{ $proclamatore->nome_completo }}" - @if($compatto) aria-label="Scrivi su WhatsApp a {{ $proclamatore->nome_completo }}" @endif> - - @unless($compatto) - {{ $proclamatore->telefono }} - @endunless - -@elseif(! $compatto && trim((string) $proclamatore?->telefono) !== '') - {{ $proclamatore->telefono }} -@endif diff --git a/resources/views/livewire/assegnazioni/assegna.blade.php b/resources/views/livewire/assegnazioni/assegna.blade.php index 193bca9..59ad2e1 100644 --- a/resources/views/livewire/assegnazioni/assegna.blade.php +++ b/resources/views/livewire/assegnazioni/assegna.blade.php @@ -14,9 +14,7 @@ @if($invio) @php $link = $invio->shortPdfUrl(); - $messaggio = $invio->messaggioLink(); - $whatsapp = $messaggio ? $invio->proclamatore?->whatsappUrl($messaggio) : null; - $email = trim((string) $invio->proclamatore?->email); + $messaggio = $invio->messaggioLink($link); @endphp