email_comunications.blade.php 42 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797
  1. <div class="col card--ui" id="card--dashboard">
  2. <header id="title--section" style="display:none !important" class="d-flex align-items-center justify-content-between">
  3. <div class="title--section_name d-flex align-items-center justify-content-between">
  4. <i class="ico--ui title_section utenti me-2"></i>
  5. <h2 class="primary">Emails</h2>
  6. </div>
  7. @if(!$showForm)
  8. <div class="title--section_addButton" wire:click="add()" style="cursor: pointer;" wire:ignore>
  9. <div class="btn--ui entrata d-flex justify-items-between">
  10. <a href="#" style="color:white;">Aggiungi</a>
  11. </div>
  12. </div>
  13. @endif
  14. </header>
  15. <a class="btn--ui lightGrey" @if(!$showForm) href="/settings?type=comunicazioni" @else href="/mail_comunications" @endif><i class="fa-solid fa-arrow-left"></i></a><br/><br/>
  16. {{-- LISTA MESSAGGI --}}
  17. <section id="resume-table" @if($showForm) style="display:none" @endif>
  18. <div wire:ignore>
  19. <table class="table tablesaw tableHead tablesaw-stack" id="tablesaw-350" width="100%">
  20. <thead>
  21. <tr>
  22. <th>ID</th>
  23. <th>Oggetto</th>
  24. <th># Attachments</th>
  25. <th># Destinatari</th>
  26. <th>Stato</th>
  27. <th>Programmata per</th>
  28. <th>Data invio</th>
  29. <th>Data creazione</th>
  30. <th>...</th>
  31. </tr>
  32. </thead>
  33. <tbody id="checkall-target">
  34. @foreach($records as $record)
  35. @php
  36. $state = $record->status;
  37. if (!$state) {
  38. if (($record->recipients_sent_count ?? 0) > 0 && ($record->recipients_failed_count ?? 0) > 0) {
  39. $state = 'partial';
  40. } elseif (($record->recipients_sent_count ?? 0) > 0) {
  41. $state = 'sent';
  42. } elseif (!empty($record->schedule_at)) {
  43. $state = 'scheduled';
  44. } else {
  45. $state = 'draft';
  46. }
  47. }
  48. $badgeMap = [
  49. 'draft' => 'secondary',
  50. 'processing' => 'info',
  51. 'scheduled' => 'primary',
  52. 'partial' => 'warning',
  53. 'sent' => 'success',
  54. 'failed' => 'danger',
  55. ];
  56. @endphp
  57. <tr id="row_email_{{ $record->id }}">
  58. <td>{{ $record->id }}</td>
  59. <td><strong>{{ $record->subject }}</strong></td>
  60. <td style="padding-right: 20px">{{ $record->attachments_count ?? $record->attachments()->count() }}</td>
  61. <td style="padding-right: 20px">{{ $record->recipients_count ?? $record->recipients()->count() }}</td>
  62. <td><span class="badge bg-{{$badgeMap[$state]}}">{{ $record->status }}</span></td>
  63. <td>
  64. @if(!empty($record->schedule_at))
  65. {{ optional($record->schedule_at)->setTimezone('Europe/Rome')->format('d M Y - H:i') }}
  66. @endif
  67. </td>
  68. <td>
  69. @if(!empty($record->sent_at))
  70. {{ optional($record->sent_at)->setTimezone('Europe/Rome')->format('d M Y - H:i') }}
  71. @endif
  72. </td>
  73. <td>{{ optional($record->created_at)->setTimezone('Europe/Rome')->format('d M Y - H:i') }}</td>
  74. <td class="d-flex gap-2">
  75. <button type="button" class="btn" wire:click="edit({{ $record->id }})" data-bs-toggle="tooltip" data-bs-trigger="hover focus" data-bs-placement="bottom" title="Modifica">
  76. <i class="fa-regular fa-pen-to-square"></i>
  77. </button>
  78. <button type="button" class="btn" wire:click="duplicate({{ $record->id }})" data-bs-toggle="tooltip" data-bs-trigger="hover focus" data-bs-placement="bottom" title="Duplica">
  79. <i class="fa-solid fa-copy"></i>
  80. </button>
  81. @if(in_array($record->status, ['draft','failed']))
  82. <button type="button" class="btn text-danger"
  83. onclick="if (confirm('Eliminare definitivamente questa email?')) { Livewire.find('{{ $this->id }}').call('deleteMessage', {{ $record->id }}); }"
  84. data-bs-toggle="tooltip" title="Elimina">
  85. <i class="fa-solid fa-trash"></i>
  86. </button>
  87. @endif
  88. </td>
  89. </tr>
  90. @endforeach
  91. </tbody>
  92. </table>
  93. </div>
  94. </section>
  95. {{-- FORM MESSAGGIO --}}
  96. <section wire:key="email-form" @if(!$showForm) style="display:none" @endif>
  97. <div class="container">
  98. @if ($error)
  99. <div class="alert alert-danger" role="alert">{{ $error }}</div>
  100. @endif
  101. @if ($success)
  102. <div class="alert alert-success" role="alert">{{ $success }}</div>
  103. @endif
  104. <div class="row">
  105. <div class="col">
  106. <form>
  107. {{-- Destinatari (selezionati) --}}
  108. <div class="row mb-5">
  109. <div class="col-12 mb-2">
  110. <h4>Destinatari</h4>
  111. <div class="recipients">
  112. @if (empty($recipients))
  113. <span>Nessun destinatario selezionato</span>
  114. @else
  115. @foreach ($recipients as $r)
  116. @php
  117. $fullName = trim(($r['last_name'] ?? '').' '.($r['first_name'] ?? ''));
  118. @endphp
  119. <div class="recipient">
  120. <span class="recipient-name">{{ $fullName !== '' ? $fullName : '—' }}</span>
  121. <span class="recipient-email">({{ $r['email_address'] }})</span>
  122. </div>
  123. @endforeach
  124. @endif
  125. </div>
  126. @error('recipients') <div class="invalid-feedback d-block">{{ $message }}</div> @enderror
  127. </div>
  128. <div class="col"></div>
  129. <div class="col-auto">
  130. <a style="cursor:pointer" class="addRecipients btn--ui"><i class="fa-solid fa-plus"></i></a>
  131. </div>
  132. </div>
  133. {{-- FILTRI + TABELLA DESTINATARI --}}
  134. <div class="row mb-5" wire:ignore id="addRecipientsRow" style="display: none">
  135. <div class="col-xs-12">
  136. <div class="showFilter" style="display: none">
  137. <hr size="1">
  138. <div class="row g-3">
  139. <div class="col-md-3">
  140. <div class="row">
  141. <div class="col-md-12 mb-2"><b>Età</b></div>
  142. <div class="col-12">
  143. <div class="row mb-2">
  144. <div class="col-3"><label class="form-check-label ms-2">Da</label></div>
  145. <div class="col-9"><input class="form-control" type="number" name="txtFromYear"></div>
  146. </div>
  147. </div>
  148. <div class="col-12">
  149. <div class="row">
  150. <div class="col-3"><label class="form-check-label ms-2">A</label></div>
  151. <div class="col-9"><input class="form-control" type="number" name="txtToYear"></div>
  152. </div>
  153. </div>
  154. </div>
  155. </div>
  156. {{-- Altri filtri come da tua UI esistente --}}
  157. <div class="col-md-3">
  158. <div class="row">
  159. <div class="col-md-12 mb-2"><b>Tipologia di tesseramento</b></div>
  160. <div class="col-12">
  161. <select name="filterCards" class="form-select filterCards">
  162. <option value="">Tutte
  163. @foreach(getCards() as $card)
  164. <option value="{{$card->id}}">{{$card->name}}
  165. @endforeach
  166. </select>
  167. </div>
  168. </div>
  169. </div>
  170. <div class="col-md-3">
  171. <div class="row">
  172. <div class="col-md-12 mb-2"><b>Stato tesseramento</b></div>
  173. <div class="col-12">
  174. <select name="filterStatus" class="form-select filterStatus" multiple="multiple">
  175. <option value="2">Attivo
  176. <option value="1">Sospeso
  177. <option value="0">Non tesserato
  178. </select>
  179. </div>
  180. </div>
  181. </div>
  182. <div class="col-md-3">
  183. <div class="row">
  184. <div class="col-md-12 mb-2"><b>Gruppo di interesse</b></div>
  185. <div class="col-12">
  186. <select name="filterCategories" class="form-select filterCategories" multiple="multiple">
  187. <option value="">Tutte</option>
  188. @foreach($categories as $category)
  189. <option value="{{$category["id"]}}">
  190. {!! str_repeat('&bull; ', $category["indentation"] ?? 0) !!}{{$category["name"]}}
  191. </option>
  192. @endforeach
  193. </select>
  194. </div>
  195. </div>
  196. </div>
  197. <div class="col-md-3">
  198. <div class="row">
  199. <div class="col-md-12 mb-2"><b>Anno di nascita</b></div>
  200. <div class="col-12">
  201. <div class="row mb-2">
  202. <div class="col-3"><label class="form-check-label ms-2" >Da</label></div>
  203. <div class="col-9"><input class="form-control " type="number" name="txtFromYearYear"></div>
  204. </div>
  205. </div>
  206. <div class="col-12">
  207. <div class="row">
  208. <div class="col-3"><label class="form-check-label ms-2" >A</label></div>
  209. <div class="col-9"><input class="form-control " type="number" name="txtToYearYear"></div>
  210. </div>
  211. </div>
  212. </div>
  213. </div>
  214. <div class="col-md-3">
  215. <div class="row">
  216. <div class="col-md-12 mb-2"><b>Corso</b></div>
  217. <div class="col-12">
  218. <select name="filterCourses" class="form-select filterCourses" multiple="multiple">
  219. <option value="">Tutti</option>
  220. @foreach($courses as $course)
  221. <option value="{{ $course['id'] }}">
  222. {!! str_repeat('&bull; ', $course['indentation'] ?? 0) !!}{{ $course['name'] }}
  223. </option>
  224. @endforeach
  225. </select>
  226. </div>
  227. </div>
  228. </div>
  229. <div class="col-md-3">
  230. <div class="row">
  231. <div class="col-md-12 mb-2"><b>Scadenza certificato medico</b></div>
  232. <div class="col-12">
  233. <select name="filterScadenza" class="form-select filterScadenza" multiple="multiple">
  234. <option value="1">Scaduti
  235. <option value="2">In scadenza
  236. <option value="3">Non consegnato
  237. <option value="4">Validi
  238. </select>
  239. </div>
  240. </div>
  241. </div>
  242. <div class="col-md-3">
  243. <div class="row">
  244. <div class="col-md-12 mb-2"><b>Tipologia certificato medico</b></div>
  245. <div class="col-12">
  246. <select name="filterCertificateType" class="form-select filterCertificateType" multiple="multiple">
  247. <option value="">Tutti
  248. <option value="N">Non agonistico
  249. <option value="A">Agonistico
  250. </select>
  251. </div>
  252. </div>
  253. </div>
  254. </div>
  255. <div class="row g-3">
  256. <div class="col-md-12" style="text-align:right">
  257. <button class="btn--ui lightGrey" onclick="resetFilters(event)">Reset</button>
  258. <button class="btn--ui" onclick="event.preventDefault();loadDataTable()">FILTRA</button>
  259. </div>
  260. </div>
  261. <hr size="1">
  262. </div>
  263. </div>
  264. <div class="col-xs-12">
  265. <table id="recipients-table" class="table tablesaw tableHead tablesaw-stack w-100">
  266. <thead>
  267. <tr>
  268. <th></th>
  269. <th>Cognome</th>
  270. <th>Nome</th>
  271. <th>Email</th>
  272. <th>Telefono</th>
  273. <th>Età</th>
  274. <th>Anno</th>
  275. <th>Stato</th>
  276. <th>Certificato</th>
  277. <th>Gruppi</th>
  278. {{-- <th>Corsi</th> --}}
  279. </tr>
  280. </thead>
  281. <tbody></tbody>
  282. </table>
  283. </div>
  284. </div>
  285. {{-- Oggetto --}}
  286. <div class="row mb-5">
  287. <div class="col">
  288. <div class="form--item">
  289. <h4>Oggetto</h4>
  290. <input type="text" class="form-control @error('subject') is-invalid @enderror" id="subject" wire:model.defer="subject" placeholder="Oggetto email" @if($locked) disabled @endif>
  291. @error('subject')
  292. <div class="invalid-feedback">{{ $message }}</div>
  293. @enderror
  294. </div>
  295. </div>
  296. </div>
  297. {{-- Messaggio (CKEditor → content_html) --}}
  298. <div class="row mb-5">
  299. <div class="col">
  300. <div wire:ignore class="form--item">
  301. <h4>Messaggio</h4>
  302. <textarea class="form-control" id="message"></textarea>
  303. @error('content_html')
  304. <div class="invalid-feedback d-block">{{ $message }}</div>
  305. @enderror
  306. </div>
  307. </div>
  308. </div>
  309. {{-- Allegati --}}
  310. <div class="row mb-5">
  311. <div class="col">
  312. <div class="form--item">
  313. <h4>Allegati</h4>
  314. <input type="file" class="form-control @error('newAttachments.*') is-invalid @enderror" wire:model="newAttachments" multiple @if($locked) disabled @endif>
  315. <small class="text-muted d-block mt-1">Formati: pdf, docx, jpg, png</small>
  316. @error('newAttachments.*')
  317. <div class="invalid-feedback d-block">{{ $message }}</div>
  318. @enderror
  319. @if(!empty($existingAttachments))
  320. <ul class="list-group mt-2">
  321. @foreach($existingAttachments as $att)
  322. <li class="list-group-item d-flex justify-content-between align-items-center gap-3">
  323. <div class="d-flex align-items-center gap-3 text-truncate" style="min-width:0">
  324. @if(!empty($att['img']) && !empty($att['url']))
  325. <img src="{{ $att['url'] }}" alt="" style="width:40px;height:40px;object-fit:cover;border-radius:6px;border:1px solid #eee">
  326. @else
  327. <i class="fa-regular fa-file" style="font-size:20px;width:40px;text-align:center"></i>
  328. @endif
  329. <div class="text-truncate d-flex ">
  330. @if(!empty($att['url']))
  331. <a href="{{ $att['url'] }}" target="_blank" class="text-decoration-none text-truncate d-inline-block" style="max-width:420px">
  332. {{ $att['name'] }}
  333. </a>
  334. @else
  335. <span class="text-truncate d-inline-block" style="max-width:420px">{{ $att['name'] }}</span>
  336. @endif
  337. @if(!empty($att['size']))
  338. <small class="text-muted ms-2">({{ $att['size'] }})</small>
  339. @endif
  340. </div>
  341. </div>
  342. @unless($locked)
  343. <button type="button"
  344. class="btn btn-sm btn-outline-danger"
  345. wire:click="removeExistingAttachment({{ (int)$att['id'] }})"
  346. title="Rimuovi">
  347. <i class="fa-solid fa-trash"></i>
  348. </button>
  349. @endunless
  350. </li>
  351. @endforeach
  352. </ul>
  353. @endif
  354. {{-- File appena selezionati (non salvati) --}}
  355. @if(is_array($newAttachments) && count($newAttachments))
  356. <ul class="list-group mt-2">
  357. @foreach($newAttachments as $idx => $tmp)
  358. <li class="list-group-item d-flex justify-content-between align-items-center">
  359. <div class="text-truncate d-flex ">
  360. <i class="fa-regular fa-file me-2"></i>
  361. {{ $tmp->getClientOriginalName() }}
  362. <small class="text-muted ms-2">({{ number_format($tmp->getSize()/1024,1) }} KB) — non ancora salvato</small>
  363. </div>
  364. <button type="button" class="btn btn-sm btn-outline-danger" wire:click="removeNewAttachment({{ $idx }})">
  365. <i class="fa-solid fa-xmark"></i>
  366. </button>
  367. </li>
  368. @endforeach
  369. </ul>
  370. @endif
  371. </div>
  372. </div>
  373. </div>
  374. {{-- Opzioni invio --}}
  375. <div class="row mb-5">
  376. <div class="col">
  377. <h4>Opzioni di Invio</h4>
  378. <div class="d-flex gap-3 comunication-send-options">
  379. <label class="form-check">
  380. <input class="form-check-input" type="radio" wire:model="mode" value="now">
  381. <i class="fas fa-envelope me-2"></i> <span>Invia subito</span>
  382. </label>
  383. <label class="form-check">
  384. <input class="form-check-input" type="radio" wire:model="mode" value="schedule">
  385. <i class="fas fa-clock me-2"></i> <span>Programma</span>
  386. </label>
  387. </div>
  388. </div>
  389. </div>
  390. @if(!$locked && $mode === 'schedule')
  391. <div class="row mb-5">
  392. <div class="col-md-6">
  393. <label for="scheduledDateTime" class="form-label">Data e Ora di Invio</label>
  394. <input type="datetime-local" class="form-control @error('schedule_at') is-invalid @enderror" id="scheduledDateTime" wire:model="schedule_at">
  395. @error('schedule_at') <div class="invalid-feedback">{{ $message }}</div> @enderror
  396. </div>
  397. </div>
  398. @endif
  399. <div class="form--item mt-5 mb-5 d-flex gap-2">
  400. @if(!$locked)
  401. <a class="btn--ui lightGrey" href="/mail_comunications">Annulla</a>
  402. <button type="button" class="btn--ui" onclick="submitEmail('draft')" style="margin-right: auto">Salva bozza</button>
  403. @if($mode==='now')
  404. <button type="button" class="btn--ui" onclick="submitEmail('send')">Invia ora</button>
  405. @else
  406. <button type="button" class="btn--ui" onclick="submitEmail('schedule')">Salva & Programma</button>
  407. @endif
  408. @else
  409. <a class="btn--ui lightGrey" href="/mail_comunications">Torna indietro</a>
  410. @endif
  411. </div>
  412. </form>
  413. </div>
  414. </div>
  415. </div>
  416. </section>
  417. <input type="hidden" name="timezone" id="timezone" wire:model="timezone">
  418. </div>
  419. @if (session()->has('success'))
  420. <div class="alert alert-success alert-dismissible fade show mt-3" role="alert">
  421. {{ session('success') }}
  422. <button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button>
  423. </div>
  424. @endif
  425. @if (session()->has('error'))
  426. <div class="alert alert-danger alert-dismissible fade show mt-3" role="alert">
  427. {{ session('error') }}
  428. <button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button>
  429. </div>
  430. @endif
  431. @push('scripts')
  432. <link href="/css/datatables.css" rel="stylesheet" />
  433. <script src="https://code.jquery.com/jquery-2.2.4.min.js" integrity="sha256-BbhdlvQf/xTY9gja0Dq3HiwQF8LaCRTXxZKRutelT44=" crossorigin="anonymous"></script>
  434. <script src="/assets/js/datatables.js"></script>
  435. <script src="https://cdn.datatables.net/buttons/3.0.2/js/buttons.dataTables.js"></script>
  436. <script src="https://cdnjs.cloudflare.com/ajax/libs/jszip/3.10.1/jszip.min.js"></script>
  437. <script src="https://cdnjs.cloudflare.com/ajax/libs/pdfmake/0.2.7/pdfmake.min.js"></script>
  438. <script src="https://cdnjs.cloudflare.com/ajax/libs/pdfmake/0.2.7/vfs_fonts.js"></script>
  439. <link href="https://cdn.jsdelivr.net/npm/select2@4.1.0-rc.0/dist/css/select2.min.css" rel="stylesheet" />
  440. <script src="https://cdn.jsdelivr.net/npm/select2@4.1.0-rc.0/dist/js/select2.min.js"></script>
  441. @endpush
  442. @push('scripts')
  443. <script type="text/javascript">
  444. document.addEventListener('livewire:load', () => {
  445. if (!$.fn.DataTable.isDataTable('#tablesaw-350')) {
  446. loadArchiveDataTable();
  447. }
  448. window.addEventListener('email-deleted', (e) => {
  449. const id = e.detail?.id;
  450. const table = $('#tablesaw-350');
  451. if (!id || !$.fn.DataTable.isDataTable(table)) return;
  452. const dt = table.DataTable();
  453. const rowEl = document.getElementById('row_email_' + id);
  454. if (rowEl) {
  455. dt.row(rowEl).remove().draw(false);
  456. }
  457. });
  458. const tz = Intl.DateTimeFormat().resolvedOptions().timeZone;
  459. @this.set('timezone', tz);
  460. });
  461. window.addEventListener('init-recipients-table', (e) => {
  462. const selected = e.detail?.selected || [];
  463. loadDataTable(selected);
  464. const tz = Intl.DateTimeFormat().resolvedOptions().timeZone;
  465. @this.set('timezone', tz);
  466. });
  467. function loadArchiveDataTable(){
  468. let date = new Date();
  469. let date_export = `${date.getFullYear()}${date.getMonth()}${date.getDate()}_`;
  470. let table = $('#tablesaw-350').DataTable();
  471. if ( $.fn.DataTable.isDataTable('#tablesaw-350') ) {
  472. table.destroy();
  473. }
  474. $('#tablesaw-350').DataTable({
  475. processing: true,
  476. thead: {
  477. 'th': {'background-color': 'blue'}
  478. },
  479. order: [[7, 'desc']],
  480. layout: {
  481. topStart : null,
  482. topEnd : null,
  483. top1A: {
  484. // buttons: [
  485. // {
  486. // extend: 'collection',
  487. // text: 'ESPORTA',
  488. buttons: [
  489. {
  490. extend: 'excelHtml5',
  491. text: '<i class="fa-solid fa-file-excel"></i>',
  492. action: newexportaction,
  493. title: date_export + 'Email',
  494. exportOptions: {
  495. columns: ":not(':last')"
  496. }
  497. },
  498. {
  499. extend: 'pdfHtml5',
  500. text: '<i class="fa-solid fa-file-pdf"></i>',
  501. action: newexportaction,
  502. title: date_export + 'Email',
  503. exportOptions: {
  504. columns: ":not(':last')"
  505. }
  506. },
  507. {
  508. extend: 'print',
  509. action: newexportaction,
  510. text: '<i class="fa-solid fa-print"></i>',
  511. title: date_export + 'Email',
  512. exportOptions: {
  513. columns: ":not(':last')"
  514. }
  515. }
  516. ],
  517. // dropup: true
  518. // }
  519. // ]
  520. },
  521. top1B : {
  522. pageLength: {
  523. menu: [[10, 25, 50, 100, 100000], [10, 25, 50, 100, "Tutti"]]
  524. }
  525. },
  526. top1C :'search',
  527. },
  528. pagingType: 'numbers',
  529. language: {
  530. "url": "/assets/js/Italian.json"
  531. },
  532. fnInitComplete: function (oSettings, json) {
  533. var html = '&nbsp;<a href="#" class="addData btn--ui"><i class="fa-solid fa-plus"></i></a>';
  534. $(".dt-search").append(html);
  535. }
  536. });
  537. $('#tablesaw-350 thead tr th').addClass('col').css("background-color", "#f6f8fa");
  538. $(document).on("click",".addData",function() {
  539. @this.add();
  540. });
  541. }
  542. $(document).on("click",".showHideFilter",function() {
  543. $(".showFilter").toggle();
  544. $('.filterCards,.filterStatus,.filterScadenza,.filterCertificateType,.filterCategories,.filterCourses').each(function(){
  545. $(this).select2({
  546. language: { noResults: ()=>"Nessun risultato" }
  547. });
  548. });
  549. });
  550. $(document).on("click", ".addRecipients", function() {
  551. $("#addRecipientsRow").toggle();
  552. });
  553. window.resetFilters = function(event){
  554. if (event) event.preventDefault();
  555. $('.filterCards').val('').trigger('change');
  556. $('.filterStatus').val('').trigger('change');
  557. $('.filterScadenza').val('-1').trigger('change');
  558. $('.filterCertificateType').val('-1').trigger('change');
  559. $('.filterCategories').val('-1').trigger('change');
  560. $('.filterCourses').val('-1').trigger('change');
  561. $('input[name="txtFromYear"]').val('');
  562. $('input[name="txtToYear"]').val('');
  563. $('input[name="txtFromYearYear"]').val('');
  564. $('input[name="txtToYearYear"]').val('');
  565. loadDataTable();
  566. }
  567. function loadDataTable(preselected = []) {
  568. const selectedIds = new Set((preselected || []).map(x => parseInt(x, 10)).filter(Boolean));
  569. if ($.fn.DataTable.isDataTable('#recipients-table')) {
  570. $('#recipients-table').DataTable().destroy();
  571. }
  572. var fromYear = $('input[name="txtFromYear"]').val();
  573. var toYear = $('input[name="txtToYear"]').val();
  574. var fromYearYear = $('input[name="txtFromYearYear"]').val();
  575. var toYearYear = $('input[name="txtToYearYear"]').val();
  576. var filterCards = $('.filterCards').val();
  577. var filterStatus = $('.filterStatus').val();
  578. var filterScadenza = $('.filterScadenza').val();
  579. var filterCertificateType = $('.filterCertificateType').val();
  580. var filterCategories = $('.filterCategories').val();
  581. var filterCourses = $('.filterCourses').val();
  582. const dataTable = $('#recipients-table').DataTable({
  583. serverSide: true,
  584. ajax: '/get_recipients?cards=' + filterCards + "&filterCategories=" + filterCategories + "&filterCertificateType=" + filterCertificateType + "&filterScadenza=" + filterScadenza + "&filterStatus=" + filterStatus + "&fromYear=" + fromYear + "&toYear=" + toYear + "&fromYearYear=" + fromYearYear + "&toYearYear=" + toYearYear + "&filterCourses=" + (filterCourses || ""),
  585. columns: [
  586. {
  587. orderable: false,
  588. data: "id",
  589. render: function (data){
  590. const id = parseInt(data, 10);
  591. const checked = selectedIds.has(id) ? 'checked' : '';
  592. return `<input type="checkbox" value="${id}" ${checked} onclick="toggleRecipient(${id})" id="recipient_${id}"/>`;
  593. }
  594. },
  595. {
  596. data: "last_name",
  597. render: function (data){
  598. const d = data.split("|");
  599. const id = d[1], value = d[0];
  600. return `<label for="recipient_${id}">${value}</label>`;
  601. }
  602. },
  603. {
  604. data: "first_name",
  605. render: function (data){
  606. const d = data.split("|");
  607. const id = d[1], value = d[0];
  608. return `<label for="recipient_${id}">${value}</label>`;
  609. }
  610. },
  611. {
  612. data: "email",
  613. render: function (data){
  614. const d = data.split("|");
  615. const id = d[1], value = d[0];
  616. return `<label for="recipient_${id}">${value}</label>`;
  617. }
  618. },
  619. { data: "phone"},
  620. { data: "age", "type": "num", className:"dt-type-numeric"},
  621. { data: "year", className:"dt-type-numeric"},
  622. {
  623. data: "status",
  624. render: function (data){
  625. const d = data.split("|");
  626. return '<span class="tablesaw-cell-content"><span class="badge tessera-badge ' + d[0] + '">' + d[1] + '</span></span>';
  627. }
  628. },
  629. {
  630. data: "certificate",
  631. render: function (data){
  632. if (!data) return '<span class="tablesaw-cell-content d-flex align-items-center"><i class="ico--ui check absent me-2"></i>Non consegnato</span>';
  633. const d = data.split("|");
  634. const icon = d[0] === "0" ? "suspended" : (d[0] === "1" ? "due" : "active");
  635. const label = d[0] === "0" ? "Scaduto" : (d[0] === "1" ? "In scadenza" : "Scadenza");
  636. return '<span class="tablesaw-cell-content d-flex align-items-center"><i class="ico--ui check '+icon+' me-2"></i>'+label+' : '+d[1]+'</span>';
  637. }
  638. },
  639. { data: "categories" },
  640. ],
  641. order: [
  642. [1, 'asc']
  643. ],
  644. fixedHeader: false,
  645. thead: {
  646. 'th': {'background-color': 'blue'}
  647. },
  648. layout: {
  649. topStart : null,
  650. topEnd : null,
  651. top1A: null,
  652. top1B : {
  653. pageLength: {
  654. menu: [[10, 25, 50, 100, 100000], [10, 25, 50, 100, "Tutti"]]
  655. }
  656. },
  657. top1C :'search',
  658. },
  659. pagingType: 'numbers',
  660. language: {
  661. "url": "/assets/js/Italian.json"
  662. },
  663. fnInitComplete: function (oSettings, json) {
  664. var html = '&nbsp;<a style="cursor:pointer" class="showHideFilter btn--ui"><i class="fa-solid fa-sliders"></i></a>';
  665. $(".dt-search").append(html);
  666. }
  667. });
  668. $('#recipients-table thead tr th').addClass('col').css("background-color", "#f6f8fa");
  669. $('#recipients-table').on('draw.dt', function() { $('[data-bs-toggle="popover"]').popover() });
  670. }
  671. window.toggleRecipient = function(id) { @this.toggleRecipient(id); }
  672. </script>
  673. {{-- CKEditor --}}
  674. <link rel="stylesheet" href="/assets/libraries/ckeditor5/ckeditor5.css" />
  675. <script type="importmap">
  676. {
  677. "imports": {
  678. "ckeditor5": "/assets/libraries/ckeditor5/ckeditor5.js",
  679. "ckeditor5/": "/assets/libraries/ckeditor5/"
  680. }
  681. }
  682. </script>
  683. <script type="module">
  684. import { ClassicEditor } from "ckeditor5";
  685. import { editorConfig } from "/assets/libraries/ckeditor5/config.js";
  686. window.addEventListener("load-editor", (e) => {
  687. let detail = e.detail || {};
  688. let latestLocked = !!detail.locked;
  689. let html = detail.html ?? '';
  690. let el = document.querySelector('#message');
  691. if (el.ckeditorInstance) el.ckeditorInstance.destroy();
  692. editorConfig.simpleUpload = {
  693. uploadUrl: "{{ route('ckeditor.upload', ['_token' => csrf_token()]) }}"
  694. };
  695. ClassicEditor
  696. .create(el, editorConfig)
  697. .then(editor => {
  698. el.ckeditorInstance = editor;
  699. editor.setData(html);
  700. if (latestLocked) {
  701. editor.enableReadOnlyMode('locked');
  702. return;
  703. }
  704. })
  705. .catch(console.error)
  706. });
  707. window.submitEmail = function(action){
  708. const ed = document.querySelector('#message')?.ckeditorInstance;
  709. const html = ed ? ed.getData() : '';
  710. if (action === 'draft') {
  711. @this.call('saveDraft', html);
  712. } else if (action === 'send') {
  713. @this.call('sendNow', html);
  714. } else {
  715. @this.call('scheduleMessage', html);
  716. }
  717. };
  718. window.addEventListener("scroll-top", (e) => {
  719. let wrapper = document.querySelector('#card--dashboard');
  720. if (wrapper) {
  721. wrapper.scrollTo({top: 0, behavior: 'smooth'});
  722. }
  723. });
  724. </script>
  725. {{-- END CKEditor --}}
  726. @endpush