Publication initiale de RelaisSMS : documentation et sources nettoyees

This commit is contained in:
2026-10-21 09:29:39 +02:00
commit ac186b03b1
158 changed files with 24818 additions and 0 deletions
+292
View File
@@ -0,0 +1,292 @@
@extends('layouts.app')
@section('content')
@php
$reasonLabels = [
'opt_out' => 'Refus SMS',
'missing_phone' => 'Sans telephone',
'invalid' => 'Numero invalide',
'non_mobile' => 'Non mobile',
'duplicate' => 'Doublon',
];
$summary = $resolution?->summary();
$showSchedulePanel = old('send_mode', request('send_mode')) === 'scheduled' || old('scheduled_at', request('scheduled_at'));
@endphp
<section class="page-wrap">
<header class="topbar">
<div class="title">
<h1>Composer</h1>
<p>Selectionner les destinataires et saisir le SMS.</p>
</div>
<div class="actions">
<a href="{{ route('contacts.index') }}" class="secondary">
Contacts
</a>
</div>
</header>
@error('selection')
<div class="notice error">
{{ $message }}
</div>
@enderror
<form method="POST" action="{{ route('recipients.preview') }}" class="stack">
@csrf
<input type="hidden" name="q" value="{{ $search }}">
<section class="panel composer-panel" data-recipient-composer data-search-url="{{ route('recipients.search') }}">
<div class="panel-header">
<h2>Destination</h2>
</div>
<div class="panel-body stack">
<div class="field">
<label for="recipient-composer-input">Pour</label>
<div class="recipient-composer">
<div class="recipient-chipbox" data-recipient-selected>
@foreach ($selectedGroups as $group)
<span class="recipient-chip group {{ $group->is_favorite ? 'favorite' : '' }}" data-recipient-token data-type="group" data-id="{{ $group->id }}">
<span>{{ $group->name }}</span>
<button type="button" data-recipient-remove aria-label="Retirer {{ $group->name }}">×</button>
<input type="hidden" name="group_ids[]" value="{{ $group->id }}">
</span>
@endforeach
@foreach ($selectedContacts as $contact)
<span class="recipient-chip {{ $contact->is_favorite ? 'favorite' : '' }}" data-recipient-token data-type="contact" data-id="{{ $contact->id }}">
<span>{{ $contact->displayName() }}</span>
<button type="button" data-recipient-remove aria-label="Retirer {{ $contact->displayName() }}">×</button>
<input type="hidden" name="contact_ids[]" value="{{ $contact->id }}">
</span>
@endforeach
<input
id="recipient-composer-input"
type="search"
class="recipient-input"
autocomplete="off"
placeholder="Ajouter un contact ou un groupe..."
data-recipient-input
>
</div>
<div class="recipient-results" data-recipient-results hidden></div>
</div>
</div>
<div class="field">
<label for="message">Message SMS</label>
<textarea
id="message"
name="message"
rows="5"
maxlength="612"
required
class="textarea"
data-message-body
>{{ old('message', request('message')) }}</textarea>
@error('message')
<p class="notice error">{{ $message }}</p>
@enderror
</div>
@if ($smsHeader !== '' || $smsFooter !== '')
<div
class="stack-sm"
data-message-composer
data-header="{{ e($smsHeader) }}"
data-footer="{{ e($smsFooter) }}"
>
<div class="message-options">
@if ($smsHeader !== '')
<input type="hidden" name="include_sms_header" value="0">
<label class="check-option">
<input
name="include_sms_header"
type="checkbox"
value="1"
@checked(old('include_sms_header', $includeSmsHeader ? '1' : '0'))
data-message-include-header
>
Entete
</label>
@endif
@if ($smsFooter !== '')
<input type="hidden" name="include_sms_footer" value="0">
<label class="check-option">
<input
name="include_sms_footer"
type="checkbox"
value="1"
@checked(old('include_sms_footer', $includeSmsFooter ? '1' : '0'))
data-message-include-footer
>
Pied
</label>
@endif
</div>
<div class="message-preview">
<strong>SMS complet</strong>
<pre data-message-preview>{{ $composedMessage }}</pre>
</div>
</div>
@endif
<div class="form-grid cols-2" data-schedule-panel @unless ($showSchedulePanel) hidden @endunless>
<div class="field">
<label for="scheduled_at">Date et heure d'envoi</label>
<input
id="scheduled_at"
type="datetime-local"
name="scheduled_at"
value="{{ old('scheduled_at', request('scheduled_at')) }}"
class="input"
data-schedule-input
>
@error('scheduled_at')
<p class="notice error">{{ $message }}</p>
@enderror
</div>
</div>
<div class="actions">
<button type="submit" class="primary" formnovalidate>
Previsualiser
</button>
@if ($resolution && $resolution->recipients !== [])
<button
type="submit"
class="primary"
name="send_mode"
value="now"
formaction="{{ route('sms-campaigns.store') }}"
onclick="return confirm('Envoyer ce SMS a {{ count($resolution->recipients) }} numero(s) ?')"
>
Envoyer {{ count($resolution->recipients) }} SMS
</button>
<button
type="button"
class="secondary"
data-schedule-toggle
aria-expanded="{{ $showSchedulePanel ? 'true' : 'false' }}"
>
Programmer
</button>
<button
type="submit"
class="secondary"
name="send_mode"
value="scheduled"
formaction="{{ route('sms-campaigns.store') }}"
data-schedule-submit
@unless ($showSchedulePanel) hidden @endunless
onclick="return confirm('Programmer ce SMS pour {{ count($resolution->recipients) }} numero(s) ?')"
>
Confirmer la programmation
</button>
@endif
</div>
@if ($resolution)
<div class="summary-row" aria-label="Resume avant envoi">
<span class="pill">{{ count($resolution->recipients) }} numeros valides</span>
<span class="pill">1 SMS / destinataire</span>
<span class="pill warn">{{ $summary['excluded_total'] }} exclus</span>
<span class="pill warn">{{ $summary['duplicate'] }} doublons</span>
</div>
@endif
</div>
</section>
</form>
@if ($resolution)
<div class="stack">
<div class="stats-grid five">
<div class="stat-card">
<span>Numeros prets</span>
<strong>{{ $summary['ready_numbers'] }}</strong>
</div>
<div class="stat-card">
<span>Contacts</span>
<strong>{{ $summary['selected_contacts'] }}</strong>
</div>
<div class="stat-card">
<span>Groupes</span>
<strong>{{ $summary['selected_groups'] }}</strong>
</div>
<div class="stat-card">
<span>Exclus</span>
<strong>{{ $summary['excluded_total'] }}</strong>
</div>
<div class="stat-card">
<span>Doublons</span>
<strong>{{ $summary['duplicate'] }}</strong>
</div>
</div>
<div class="content-grid">
<div class="panel">
<div class="panel-header">
<h2>Liste finale</h2>
</div>
<div>
@forelse ($resolution->recipients as $recipient)
<div class="list-row final-recipient-row">
<div class="min-w-0">
<p class="truncate text-strong">{{ $recipient['contact_name'] }}</p>
<p class="muted truncate text-small">{{ implode(' · ', $recipient['selection']) }}</p>
</div>
<p class="text-small text-strong">{{ $recipient['number'] }}</p>
</div>
@empty
<div class="empty-state">Aucun numero pret.</div>
@endforelse
</div>
</div>
<div class="panel">
<div class="panel-header">
<h2>Ecarts</h2>
</div>
<div>
@foreach ($reasonLabels as $reason => $label)
@php($entries = $resolution->excludedByReason($reason))
@if ($entries !== [])
<div class="list-row">
<p class="text-section">{{ $label }} · {{ count($entries) }}</p>
<div class="stack-sm">
@foreach ($entries as $entry)
<div class="text-small">
<p class="text-strong">{{ $entry['contact_name'] }}</p>
<p class="muted">
{{ ucfirst($entry['contact_source'] ?? 'local') }} #{{ $entry['contact_id'] }}
</p>
<p class="muted">
{{ $entry['number'] ?: $entry['raw_number'] ?: 'Aucun numero' }}
@if (($entry['duplicate_of'] ?? null) !== null)
· deja retenu pour {{ $entry['duplicate_of'] }}
@endif
</p>
</div>
@endforeach
</div>
</div>
@endif
@endforeach
@if ($resolution->excluded === [])
<div class="empty-state">Aucun ecart.</div>
@endif
</div>
</div>
</div>
</div>
@endif
</section>
@endsection