reports.blade.php 37 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913
  1. {{-- resources/views/livewire/reports.blade.php --}}
  2. <div>
  3. <script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
  4. <style>
  5. :root {
  6. --primary-color: #3b5bdb;
  7. --secondary-color: #495057;
  8. --success-color: #00b894;
  9. --info-color: #22b8cf;
  10. --warning-color: #ffd43b;
  11. --danger-color: #ff6b6b;
  12. --dark-color: #212529;
  13. --border-color: #e9ecef;
  14. --shadow-sm: 0 0.125rem 0.25rem rgba(0, 0, 0, 0.075);
  15. --shadow: 0 0.5rem 1rem rgba(0, 0, 0, 0.15);
  16. }
  17. .dashboard-container {
  18. padding: 1rem;
  19. max-width: 1400px;
  20. margin: 0 auto;
  21. }
  22. .dashboard-header {
  23. text-align: center;
  24. margin-bottom: 2rem;
  25. padding: 2rem;
  26. background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
  27. border-radius: 16px;
  28. color: white;
  29. }
  30. .dashboard-header h1 {
  31. font-size: 2.5rem;
  32. font-weight: 700;
  33. margin-bottom: 0.5rem;
  34. }
  35. .dashboard-header p {
  36. font-size: 1.1rem;
  37. opacity: 0.9;
  38. }
  39. .controls-section {
  40. background: white;
  41. border-radius: 12px;
  42. padding: 1.5rem;
  43. box-shadow: var(--shadow-sm);
  44. border: 1px solid var(--border-color);
  45. margin-bottom: 2rem;
  46. display: flex;
  47. gap: 2rem;
  48. align-items: center;
  49. flex-wrap: wrap;
  50. }
  51. .control-group {
  52. display: flex;
  53. flex-direction: column;
  54. gap: 0.5rem;
  55. }
  56. .control-group label {
  57. font-weight: 600;
  58. font-size: 0.875rem;
  59. color: var(--secondary-color);
  60. }
  61. .form-select {
  62. border-radius: 8px;
  63. border: 2px solid var(--border-color);
  64. padding: 0.75rem 1rem;
  65. font-size: 1rem;
  66. transition: all 0.3s ease;
  67. min-width: 200px;
  68. }
  69. .form-select:focus {
  70. border-color: var(--primary-color);
  71. box-shadow: 0 0 0 0.2rem rgba(59, 91, 219, 0.25);
  72. outline: none;
  73. }
  74. .summary-cards {
  75. display: grid;
  76. grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
  77. gap: 1rem;
  78. margin-bottom: 2rem;
  79. }
  80. .summary-card {
  81. background: white;
  82. border-radius: 16px;
  83. padding: 1.5rem;
  84. box-shadow: var(--shadow-sm);
  85. border: 1px solid var(--border-color);
  86. text-align: center;
  87. transition: transform 0.3s ease;
  88. }
  89. .summary-card:hover {
  90. transform: translateY(-2px);
  91. }
  92. .summary-card h3 {
  93. font-size: 0.875rem;
  94. font-weight: 600;
  95. color: var(--secondary-color);
  96. text-transform: uppercase;
  97. letter-spacing: 0.5px;
  98. margin-bottom: 0.5rem;
  99. }
  100. .summary-card .value {
  101. font-size: 2rem;
  102. font-weight: 700;
  103. margin-bottom: 0.25rem;
  104. }
  105. .summary-card.income .value { color: var(--success-color); }
  106. .summary-card.expense .value { color: var(--danger-color); }
  107. .summary-card.delta .value { color: var(--primary-color); }
  108. .summary-card.delta.negative .value { color: var(--danger-color); }
  109. .chart-row {
  110. width: 100%;
  111. margin-bottom: 2rem;
  112. }
  113. .chart-card {
  114. background: white;
  115. border-radius: 16px;
  116. box-shadow: var(--shadow-sm);
  117. border: 1px solid var(--border-color);
  118. overflow: hidden;
  119. transition: all 0.3s ease;
  120. }
  121. .chart-card:hover {
  122. box-shadow: var(--shadow);
  123. }
  124. .chart-header {
  125. padding: 1.5rem 2rem 1rem;
  126. border-bottom: 1px solid var(--border-color);
  127. background: linear-gradient(135deg, #f8f9fa 0%, #ffffff 100%);
  128. }
  129. .chart-title {
  130. font-size: 1.25rem;
  131. font-weight: 600;
  132. color: var(--dark-color);
  133. margin: 0;
  134. }
  135. .chart-body {
  136. padding: 2rem;
  137. }
  138. .chart-container {
  139. position: relative;
  140. height: 400px !important;
  141. width: 100%;
  142. }
  143. .chart-container canvas {
  144. max-height: 400px !important;
  145. height: 400px !important;
  146. }
  147. .course-controls {
  148. background: #f8f9fa;
  149. border-radius: 12px;
  150. padding: 1.5rem;
  151. margin-bottom: 1.5rem;
  152. }
  153. .legend-container {
  154. display: flex;
  155. gap: 2rem;
  156. margin-top: 1rem;
  157. flex-wrap: wrap;
  158. }
  159. .legend-item {
  160. display: flex;
  161. align-items: center;
  162. gap: 0.5rem;
  163. font-size: 0.875rem;
  164. }
  165. .legend-color {
  166. width: 12px;
  167. height: 12px;
  168. border-radius: 50%;
  169. }
  170. .monthly-table-container {
  171. background: #f8f9fa;
  172. border-radius: 12px;
  173. padding: 1.5rem;
  174. box-shadow: var(--shadow-sm);
  175. }
  176. .members-table-container {
  177. background: #f8f9fa;
  178. border-radius: 12px;
  179. padding: 1.5rem;
  180. box-shadow: var(--shadow-sm);
  181. }
  182. .monthly-table, .members-table {
  183. font-size: 0.875rem;
  184. }
  185. .members-table .table-header {
  186. display: grid;
  187. grid-template-columns: 1.2fr 0.8fr 1fr;
  188. gap: 0.5rem;
  189. margin-bottom: 0.5rem;
  190. padding-bottom: 0.5rem;
  191. border-bottom: 2px solid var(--border-color);
  192. }
  193. .members-table .table-row {
  194. display: grid;
  195. grid-template-columns: 1.2fr 0.8fr 1fr;
  196. gap: 0.5rem;
  197. padding: 0.5rem 0;
  198. border-bottom: 1px solid #e9ecef;
  199. transition: background-color 0.2s ease;
  200. }
  201. .table-cell.season-name {
  202. text-align: left;
  203. font-weight: 600;
  204. font-size: 0.8rem;
  205. }
  206. .table-cell.members-count {
  207. text-align: center;
  208. font-weight: 600;
  209. color: var(--primary-color);
  210. }
  211. .table-cell.variation {
  212. text-align: right;
  213. font-size: 0.8rem;
  214. }
  215. .variation-positive {
  216. color: var(--success-color);
  217. font-weight: 600;
  218. }
  219. .variation-negative {
  220. color: var(--danger-color);
  221. font-weight: 600;
  222. }
  223. .variation-neutral {
  224. color: var(--secondary-color);
  225. font-weight: 500;
  226. }
  227. .monthly-table {
  228. font-size: 0.875rem;
  229. }
  230. .table-header {
  231. display: grid;
  232. grid-template-columns: 1fr 1fr 1fr 1fr;
  233. gap: 0.5rem;
  234. margin-bottom: 0.5rem;
  235. padding-bottom: 0.5rem;
  236. border-bottom: 2px solid var(--border-color);
  237. }
  238. .table-row {
  239. display: grid;
  240. grid-template-columns: 1fr 1fr 1fr 1fr;
  241. gap: 0.5rem;
  242. padding: 0.5rem 0;
  243. border-bottom: 1px solid #e9ecef;
  244. transition: background-color 0.2s ease;
  245. }
  246. .table-row:hover {
  247. background-color: rgba(0, 0, 0, 0.02);
  248. }
  249. .table-row.positive {
  250. background-color: rgba(0, 184, 148, 0.05);
  251. }
  252. .table-row.negative {
  253. background-color: rgba(255, 107, 107, 0.05);
  254. }
  255. .table-row.neutral {
  256. background-color: rgba(73, 80, 87, 0.02);
  257. }
  258. .table-cell {
  259. padding: 0.25rem 0.5rem;
  260. text-align: right;
  261. }
  262. .table-header .table-cell {
  263. font-weight: 600;
  264. color: var(--secondary-color);
  265. text-align: center;
  266. text-transform: uppercase;
  267. font-size: 0.75rem;
  268. letter-spacing: 0.5px;
  269. }
  270. .table-cell.month-name {
  271. text-align: left;
  272. font-weight: 600;
  273. }
  274. .table-cell.income {
  275. color: var(--success-color);
  276. font-weight: 500;
  277. }
  278. .table-cell.expense {
  279. color: var(--danger-color);
  280. font-weight: 500;
  281. }
  282. .table-cell.net {
  283. font-weight: 600;
  284. }
  285. .table-row.positive .table-cell.net {
  286. color: var(--success-color);
  287. }
  288. .table-row.negative .table-cell.net {
  289. color: var(--danger-color);
  290. }
  291. .table-row.neutral .table-cell.net {
  292. color: var(--secondary-color);
  293. }
  294. @media (max-width: 1024px) {
  295. .chart-body > div[style*="grid-template-columns"] {
  296. grid-template-columns: 1fr !important;
  297. gap: 1rem !important;
  298. }
  299. .monthly-table-container {
  300. order: 2;
  301. }
  302. }
  303. .loading {
  304. text-align: center;
  305. padding: 2rem;
  306. color: var(--secondary-color);
  307. }
  308. .error-message {
  309. background: #fff5f5;
  310. border: 1px solid #fed7d7;
  311. color: #c53030;
  312. padding: 1rem;
  313. border-radius: 8px;
  314. margin: 1rem 0;
  315. }
  316. @media (max-width: 768px) {
  317. .dashboard-container {
  318. padding: 0.5rem;
  319. }
  320. .dashboard-header h1 {
  321. font-size: 2rem;
  322. }
  323. .controls-section {
  324. flex-direction: column;
  325. align-items: stretch;
  326. gap: 1rem;
  327. }
  328. .chart-body {
  329. padding: 1rem;
  330. }
  331. .chart-container {
  332. height: 300px !important;
  333. }
  334. .chart-container canvas {
  335. max-height: 300px !important;
  336. height: 300px !important;
  337. }
  338. .legend-container {
  339. gap: 1rem;
  340. }
  341. }
  342. </style>
  343. <div class="dashboard-container">
  344. <div class="controls-section">
  345. <div class="control-group">
  346. <label for="season-filter">Stagione di Riferimento:</label>
  347. <select class="form-select" wire:model="seasonFilter" wire:change="updateCharts">
  348. @foreach($this->getAvailableSeasons() as $season)
  349. <option value="{{ $season }}">{{ $season }}</option>
  350. @endforeach
  351. </select>
  352. </div>
  353. </div>
  354. @php
  355. $summary = $this->getYearlySummary();
  356. @endphp
  357. <div class="summary-cards">
  358. <div class="summary-card income">
  359. <h3>Entrate Totali</h3>
  360. <div class="value">€{{ number_format($summary['totalIncome'], 2, ',', '.') }}</div>
  361. </div>
  362. <div class="summary-card expense">
  363. <h3>Uscite Totali</h3>
  364. <div class="value">€{{ number_format($summary['totalExpenses'], 2, ',', '.') }}</div>
  365. </div>
  366. <div class="summary-card delta {{ $summary['delta'] < 0 ? 'negative' : '' }}">
  367. <h3>Bilancio Netto</h3>
  368. <div class="value">€{{ number_format($summary['delta'], 2, ',', '.') }}</div>
  369. </div>
  370. </div>
  371. <!-- Charts Section - Force complete re-render with wire:key -->
  372. <div wire:key="season-{{ $seasonFilter }}-course-{{ $selectedCourse ?? 'none' }}">
  373. <div class="chart-row">
  374. <div class="chart-card">
  375. <div class="chart-header">
  376. <h3 class="chart-title">Entrate e Uscite Mensili - {{ $seasonFilter }}</h3>
  377. </div>
  378. <div class="chart-body">
  379. <div style="display: grid; grid-template-columns: 1fr 300px; gap: 2rem; align-items: start;">
  380. <div class="chart-container">
  381. <canvas id="monthly-chart-{{ str_replace('-', '', $seasonFilter) }}"></canvas>
  382. </div>
  383. @php
  384. $monthlyTotals = $this->getMonthlyTotals();
  385. $incomeData = $monthlyTotals['datasets'][0]['data'];
  386. $expenseData = $monthlyTotals['datasets'][1]['data'];
  387. $monthNames = $monthlyTotals['labels'];
  388. @endphp
  389. <div class="monthly-table-container">
  390. <h4 style="margin-bottom: 1rem; font-size: 1rem; font-weight: 600; color: var(--dark-color);">Riepilogo Mensile</h4>
  391. <div class="monthly-table">
  392. <div class="table-header">
  393. <div class="table-cell">Mese</div>
  394. <div class="table-cell">Entrate</div>
  395. <div class="table-cell">Uscite</div>
  396. <div class="table-cell">Netto</div>
  397. </div>
  398. @foreach($monthNames as $index => $month)
  399. @php
  400. $income = floatval($incomeData[$index] ?? 0);
  401. $expense = floatval($expenseData[$index] ?? 0);
  402. $net = $income - $expense;
  403. @endphp
  404. <div class="table-row {{ $net < 0 ? 'negative' : ($net > 0 ? 'positive' : 'neutral') }}">
  405. <div class="table-cell month-name">{{ $month }}</div>
  406. <div class="table-cell income">€{{ number_format($income, 0, ',', '.') }}</div>
  407. <div class="table-cell expense">€{{ number_format($expense, 0, ',', '.') }}</div>
  408. <div class="table-cell net">€{{ number_format($net, 0, ',', '.') }}</div>
  409. </div>
  410. @endforeach
  411. </div>
  412. </div>
  413. </div>
  414. </div>
  415. </div>
  416. </div>
  417. <div class="chart-row">
  418. <div class="chart-card">
  419. <div class="chart-header">
  420. <h3 class="chart-title">Causali Performanti - {{ $seasonFilter }}</h3>
  421. </div>
  422. <div class="chart-body">
  423. <div class="chart-container">
  424. <canvas id="causals-chart-{{ str_replace('-', '', $seasonFilter) }}"></canvas>
  425. </div>
  426. </div>
  427. </div>
  428. </div>
  429. <div class="chart-row">
  430. <div class="chart-card">
  431. <div class="chart-header">
  432. <h3 class="chart-title">Tesserati per Stagione</h3>
  433. </div>
  434. <div class="chart-body">
  435. <div style="display: grid; grid-template-columns: 1fr 300px; gap: 2rem; align-items: start;">
  436. <div class="chart-container">
  437. <canvas id="members-chart-{{ str_replace('-', '', $seasonFilter) }}"></canvas>
  438. </div>
  439. @php
  440. $membersData = $this->getTesseratiData();
  441. $seasonLabels = $membersData['labels'];
  442. $memberCounts = $membersData['datasets'][0]['data'];
  443. @endphp
  444. <div class="members-table-container">
  445. <h4 style="margin-bottom: 1rem; font-size: 1rem; font-weight: 600; color: var(--dark-color);">Riepilogo Tesserati</h4>
  446. <div class="members-table">
  447. <div class="table-header">
  448. <div class="table-cell">Stagione</div>
  449. <div class="table-cell">Tesserati</div>
  450. <div class="table-cell">Variazione</div>
  451. </div>
  452. @foreach($seasonLabels as $index => $season)
  453. @php
  454. $current = intval($memberCounts[$index] ?? 0);
  455. $previous = $index > 0 ? intval($memberCounts[$index - 1] ?? 0) : 0;
  456. $variation = $index > 0 ? $current - $previous : 0;
  457. $variationPercent = $previous > 0 ? round(($variation / $previous) * 100, 1) : 0;
  458. @endphp
  459. <div class="table-row {{ $variation > 0 ? 'positive' : ($variation < 0 ? 'negative' : 'neutral') }}">
  460. <div class="table-cell season-name">{{ $season }}</div>
  461. <div class="table-cell members-count">{{ number_format($current, 0, ',', '.') }}</div>
  462. <div class="table-cell variation">
  463. @if($index > 0)
  464. @if($variation > 0)
  465. <span class="variation-positive">+{{ $variation }} (+{{ $variationPercent }}%)</span>
  466. @elseif($variation < 0)
  467. <span class="variation-negative">{{ $variation }} ({{ $variationPercent }}%)</span>
  468. @else
  469. <span class="variation-neutral">{{ $variation }}</span>
  470. @endif
  471. @else
  472. <span class="variation-neutral">—</span>
  473. @endif
  474. </div>
  475. </div>
  476. @endforeach
  477. </div>
  478. </div>
  479. </div>
  480. </div>
  481. </div>
  482. </div>
  483. <div class="chart-row">
  484. <div class="chart-card">
  485. <div class="chart-header">
  486. <h3 class="chart-title">Analisi Corsi</h3>
  487. </div>
  488. <div class="chart-body">
  489. <div class="course-controls">
  490. <div class="control-group">
  491. <label>Seleziona Corso:</label>
  492. <select class="form-select" wire:model="selectedCourse" wire:change="updateCourseChart">
  493. <option value="">Seleziona un Corso</option>
  494. @foreach($courses as $course)
  495. <option value="{{ $course['id'] }}">{{ $course['full_name'] }}</option>
  496. @endforeach
  497. </select>
  498. </div>
  499. <div class="legend-container">
  500. <div class="legend-item">
  501. <div class="legend-color" style="background: rgba(0, 184, 148, 1);"></div>
  502. <span>Pagamenti Effettuati</span>
  503. </div>
  504. <div class="legend-item">
  505. <div class="legend-color" style="background: rgba(48, 51, 107, 1);"></div>
  506. <span>Pagamenti Attesi</span>
  507. </div>
  508. </div>
  509. </div>
  510. <div class="chart-container">
  511. <canvas id="courses-chart-{{ str_replace('-', '', $seasonFilter) }}-{{ $selectedCourse ?? 'none' }}"></canvas>
  512. </div>
  513. </div>
  514. </div>
  515. </div>
  516. <script>
  517. (function() {
  518. const seasonFilter = '{{ $seasonFilter }}';
  519. const selectedCourse = '{{ $selectedCourse ?? 'none' }}';
  520. const seasonKey = '{{ str_replace('-', '', $seasonFilter) }}';
  521. console.log('Creating charts for season:', seasonFilter);
  522. // Get the data fresh from PHP
  523. const monthlyData = @json($this->getMonthlyTotals());
  524. const causalsData = @json($this->getTopCausalsByAmount());
  525. const membersData = @json($this->getTesseratiData());
  526. const courseData = @json($this->getCourseMonthlyEarnings());
  527. console.log('Monthly data for', seasonFilter, ':', monthlyData);
  528. console.log('May value should be:', monthlyData.datasets[0].data[8]);
  529. // Wait for DOM to be ready
  530. if (document.readyState === 'loading') {
  531. document.addEventListener('DOMContentLoaded', initCharts);
  532. } else {
  533. setTimeout(initCharts, 100);
  534. }
  535. function initCharts() {
  536. createMonthlyChart();
  537. createCausalsChart();
  538. createMembersChart();
  539. createCoursesChart();
  540. }
  541. function createMonthlyChart() {
  542. const canvasId = `monthly-chart-${seasonKey}`;
  543. const canvas = document.getElementById(canvasId);
  544. if (!canvas) {
  545. console.log('Canvas not found:', canvasId);
  546. return;
  547. }
  548. const ctx = canvas.getContext('2d');
  549. const incomeGradient = ctx.createLinearGradient(0, 0, 0, 400);
  550. incomeGradient.addColorStop(0, 'rgba(0, 184, 148, 0.8)');
  551. incomeGradient.addColorStop(1, 'rgba(0, 184, 148, 0.2)');
  552. const expenseGradient = ctx.createLinearGradient(0, 0, 0, 400);
  553. expenseGradient.addColorStop(0, 'rgba(255, 107, 107, 0.8)');
  554. expenseGradient.addColorStop(1, 'rgba(255, 107, 107, 0.2)');
  555. new Chart(ctx, {
  556. type: 'bar',
  557. data: {
  558. labels: monthlyData.labels,
  559. datasets: [
  560. {
  561. label: 'Entrate',
  562. data: monthlyData.datasets[0].data,
  563. backgroundColor: incomeGradient,
  564. borderColor: '#00b894',
  565. borderWidth: 2,
  566. },
  567. {
  568. label: 'Uscite',
  569. data: monthlyData.datasets[1].data,
  570. backgroundColor: expenseGradient,
  571. borderColor: '#ff6b6b',
  572. borderWidth: 2,
  573. }
  574. ]
  575. },
  576. options: {
  577. responsive: true,
  578. maintainAspectRatio: false,
  579. plugins: {
  580. legend: {
  581. position: 'top',
  582. labels: {
  583. usePointStyle: true,
  584. padding: 20,
  585. font: { weight: '500' }
  586. }
  587. },
  588. tooltip: {
  589. backgroundColor: 'rgba(255, 255, 255, 0.95)',
  590. titleColor: '#212529',
  591. bodyColor: '#495057',
  592. borderColor: '#e9ecef',
  593. borderWidth: 1,
  594. cornerRadius: 8,
  595. callbacks: {
  596. label: function(context) {
  597. return context.dataset.label + ': €' +
  598. new Intl.NumberFormat('it-IT').format(context.parsed.y);
  599. }
  600. }
  601. }
  602. },
  603. scales: {
  604. x: {
  605. grid: { display: false },
  606. ticks: { font: { weight: '500' } }
  607. },
  608. y: {
  609. beginAtZero: true,
  610. grid: { color: 'rgba(0, 0, 0, 0.05)' },
  611. ticks: {
  612. callback: function(value) {
  613. return '€' + new Intl.NumberFormat('it-IT').format(value);
  614. }
  615. }
  616. }
  617. },
  618. animation: {
  619. duration: 1000,
  620. easing: 'easeOutQuart'
  621. }
  622. }
  623. });
  624. console.log('Monthly chart created for', seasonFilter);
  625. }
  626. function createCausalsChart() {
  627. const canvasId = `causals-chart-${seasonKey}`;
  628. const canvas = document.getElementById(canvasId);
  629. if (!canvas) return;
  630. const ctx = canvas.getContext('2d');
  631. const colors = [
  632. 'rgba(59, 91, 219, 0.8)',
  633. 'rgba(0, 184, 148, 0.8)',
  634. 'rgba(34, 184, 207, 0.8)',
  635. 'rgba(255, 212, 59, 0.8)',
  636. 'rgba(255, 107, 107, 0.8)',
  637. 'rgba(142, 68, 173, 0.8)',
  638. 'rgba(230, 126, 34, 0.8)',
  639. 'rgba(149, 165, 166, 0.8)',
  640. 'rgba(241, 196, 15, 0.8)',
  641. 'rgba(231, 76, 60, 0.8)'
  642. ];
  643. new Chart(ctx, {
  644. type: 'doughnut',
  645. data: {
  646. labels: causalsData.inLabels,
  647. datasets: [{
  648. label: 'Importo',
  649. data: causalsData.inData.map(item => item.value),
  650. backgroundColor: colors,
  651. borderColor: colors.map(color => color.replace('0.8', '1')),
  652. borderWidth: 2,
  653. hoverOffset: 8
  654. }]
  655. },
  656. options: {
  657. responsive: true,
  658. maintainAspectRatio: false,
  659. cutout: '60%',
  660. plugins: {
  661. legend: {
  662. position: 'left',
  663. labels: {
  664. usePointStyle: true,
  665. padding: 15,
  666. font: { size: 11, weight: '500' }
  667. }
  668. },
  669. tooltip: {
  670. backgroundColor: 'rgba(255, 255, 255, 0.95)',
  671. titleColor: '#212529',
  672. bodyColor: '#495057',
  673. borderColor: '#e9ecef',
  674. borderWidth: 1,
  675. cornerRadius: 8,
  676. callbacks: {
  677. label: function(context) {
  678. const value = context.raw;
  679. const total = context.dataset.data.reduce((a, b) => a + b, 0);
  680. const percentage = Math.round((value / total) * 100);
  681. return context.label + ': €' +
  682. new Intl.NumberFormat('it-IT').format(value) +
  683. ` (${percentage}%)`;
  684. }
  685. }
  686. }
  687. },
  688. animation: {
  689. animateRotate: true,
  690. duration: 1000
  691. }
  692. }
  693. });
  694. }
  695. function createMembersChart() {
  696. const canvasId = `members-chart-${seasonKey}`;
  697. const canvas = document.getElementById(canvasId);
  698. if (!canvas) return;
  699. const ctx = canvas.getContext('2d');
  700. const gradient = ctx.createLinearGradient(0, 0, 0, 400);
  701. gradient.addColorStop(0, 'rgba(59, 91, 219, 0.3)');
  702. gradient.addColorStop(1, 'rgba(59, 91, 219, 0.05)');
  703. new Chart(ctx, {
  704. type: 'line',
  705. data: {
  706. labels: membersData.labels,
  707. datasets: [{
  708. label: 'Membri Tesserati',
  709. data: membersData.datasets[0].data,
  710. borderColor: '#3b5bdb',
  711. backgroundColor: gradient,
  712. borderWidth: 3,
  713. fill: true,
  714. tension: 0.4,
  715. pointBackgroundColor: '#3b5bdb',
  716. pointBorderColor: '#ffffff',
  717. pointBorderWidth: 2,
  718. pointRadius: 6,
  719. pointHoverRadius: 8
  720. }]
  721. },
  722. options: {
  723. responsive: true,
  724. maintainAspectRatio: false,
  725. plugins: {
  726. legend: { display: false },
  727. tooltip: {
  728. backgroundColor: 'rgba(255, 255, 255, 0.95)',
  729. titleColor: '#212529',
  730. bodyColor: '#495057',
  731. borderColor: '#e9ecef',
  732. borderWidth: 1,
  733. cornerRadius: 8,
  734. callbacks: {
  735. label: function(context) {
  736. return 'Tesserati: ' + context.parsed.y;
  737. }
  738. }
  739. }
  740. },
  741. scales: {
  742. x: { grid: { display: false } },
  743. y: {
  744. beginAtZero: true,
  745. grid: { color: 'rgba(0, 0, 0, 0.05)' },
  746. ticks: { precision: 0 }
  747. }
  748. },
  749. animation: {
  750. duration: 1000,
  751. easing: 'easeOutQuart'
  752. }
  753. }
  754. });
  755. }
  756. function createCoursesChart() {
  757. const canvasId = `courses-chart-${seasonKey}-${selectedCourse}`;
  758. const canvas = document.getElementById(canvasId);
  759. if (!canvas) return;
  760. const ctx = canvas.getContext('2d');
  761. new Chart(ctx, {
  762. type: 'bar',
  763. data: {
  764. labels: courseData.labels,
  765. datasets: courseData.datasets
  766. },
  767. options: {
  768. responsive: true,
  769. maintainAspectRatio: false,
  770. scales: {
  771. x: { grid: { display: false } },
  772. y: {
  773. beginAtZero: true,
  774. grid: {
  775. color: 'rgba(0, 0, 0, 0.1)',
  776. borderDash: [5, 5]
  777. },
  778. ticks: {
  779. callback: function(value) {
  780. return '€' + new Intl.NumberFormat('it-IT').format(value);
  781. }
  782. }
  783. }
  784. },
  785. plugins: {
  786. legend: { display: false },
  787. tooltip: {
  788. backgroundColor: 'rgba(255, 255, 255, 0.95)',
  789. titleColor: '#212529',
  790. bodyColor: '#495057',
  791. borderColor: '#e9ecef',
  792. borderWidth: 1,
  793. cornerRadius: 8,
  794. callbacks: {
  795. label: function(context) {
  796. return context.dataset.label + ': €' +
  797. new Intl.NumberFormat('it-IT').format(context.parsed.y);
  798. }
  799. }
  800. }
  801. },
  802. animation: {
  803. duration: 1000,
  804. easing: 'easeOutQuart'
  805. }
  806. }
  807. });
  808. }
  809. })();
  810. </script>
  811. </div>
  812. </div>
  813. </div>