Publication initiale de RelaisSMS : documentation et sources nettoyees
This commit is contained in:
@@ -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
|
||||
Reference in New Issue
Block a user