|
|
@@ -145,10 +145,8 @@ class HomePageController extends Controller
|
|
145
|
145
|
{
|
|
146
|
146
|
$now = Carbon::now();
|
|
147
|
147
|
$today = $now->copy()->startOfDay();
|
|
148
|
|
- $byDay = $this->tempoMinutiByDay(
|
|
149
|
|
- $now->copy()->subYear()->startOfYear(),
|
|
150
|
|
- $now->copy()->endOfDay()
|
|
151
|
|
- );
|
|
|
148
|
+ $lookbackStart = $now->copy()->subYears(3)->startOfYear();
|
|
|
149
|
+ $byDay = $this->tempoMinutiByDay($lookbackStart, $now->copy()->endOfDay());
|
|
152
|
150
|
|
|
153
|
151
|
$weekStart = $now->copy()->startOfWeek(Carbon::MONDAY)->startOfDay();
|
|
154
|
152
|
$weekOffsetDays = $now->isoWeekday() - 1;
|
|
|
@@ -162,6 +160,8 @@ class HomePageController extends Controller
|
|
162
|
160
|
$yearStart = $now->copy()->startOfYear();
|
|
163
|
161
|
$prevYearEnd = $now->copy()->subYear()->endOfDay();
|
|
164
|
162
|
|
|
|
163
|
+ $giorniBrevi = [1 => 'lun', 2 => 'mar', 3 => 'mer', 4 => 'gio', 5 => 'ven', 6 => 'sab', 7 => 'dom'];
|
|
|
164
|
+
|
|
165
|
165
|
return [
|
|
166
|
166
|
$this->buildTempoPeriodo(
|
|
167
|
167
|
'Oggi',
|
|
|
@@ -174,6 +174,8 @@ class HomePageController extends Controller
|
|
174
|
174
|
$now,
|
|
175
|
175
|
$today->copy()->subDay(),
|
|
176
|
176
|
$today->copy()->subDay()->endOfDay(),
|
|
|
177
|
+ $this->mediaMinutiGiornaliera($byDay, $lookbackStart, $today, (int) $now->isoWeekday()),
|
|
|
178
|
+ 'media '.$giorniBrevi[(int) $now->isoWeekday()],
|
|
177
|
179
|
),
|
|
178
|
180
|
$this->buildTempoPeriodo(
|
|
179
|
181
|
'Settimana',
|
|
|
@@ -186,6 +188,8 @@ class HomePageController extends Controller
|
|
186
|
188
|
$now,
|
|
187
|
189
|
$prevWeekStart,
|
|
188
|
190
|
$prevWeekEnd,
|
|
|
191
|
+ $this->mediaMinutiSettimanale($byDay, $lookbackStart, $weekStart, $weekOffsetDays),
|
|
|
192
|
+ 'media sett.',
|
|
189
|
193
|
),
|
|
190
|
194
|
$this->buildTempoPeriodo(
|
|
191
|
195
|
'Mese',
|
|
|
@@ -198,6 +202,8 @@ class HomePageController extends Controller
|
|
198
|
202
|
$now,
|
|
199
|
203
|
$prevMonthStart,
|
|
200
|
204
|
$prevMonthSameDay,
|
|
|
205
|
+ $this->mediaMinutiMensile($byDay, $lookbackStart, $monthStart, (int) $now->day),
|
|
|
206
|
+ 'media mesi',
|
|
201
|
207
|
),
|
|
202
|
208
|
$this->buildTempoPeriodo(
|
|
203
|
209
|
'Anno',
|
|
|
@@ -210,12 +216,15 @@ class HomePageController extends Controller
|
|
210
|
216
|
$now,
|
|
211
|
217
|
$yearStart->copy()->subYear(),
|
|
212
|
218
|
$prevYearEnd,
|
|
|
219
|
+ $this->mediaMinutiAnnuale($byDay, $lookbackStart, $yearStart, $now),
|
|
|
220
|
+ 'media anni',
|
|
213
|
221
|
),
|
|
214
|
222
|
];
|
|
215
|
223
|
}
|
|
216
|
224
|
|
|
217
|
225
|
/**
|
|
218
|
226
|
* @param array<string, int> $byDay
|
|
|
227
|
+ * @param array{minuti: int, campioni: int} $media
|
|
219
|
228
|
* @return array<string, mixed>
|
|
220
|
229
|
*/
|
|
221
|
230
|
private function buildTempoPeriodo(
|
|
|
@@ -229,6 +238,8 @@ class HomePageController extends Controller
|
|
229
|
238
|
Carbon $currentTo,
|
|
230
|
239
|
Carbon $previousFrom,
|
|
231
|
240
|
Carbon $previousTo,
|
|
|
241
|
+ array $media,
|
|
|
242
|
+ string $mediaLabel,
|
|
232
|
243
|
): array {
|
|
233
|
244
|
$minuti = $this->sumMinutiInRange($byDay, $currentFrom, $currentTo);
|
|
234
|
245
|
$precedente = $this->sumMinutiInRange($byDay, $previousFrom, $previousTo);
|
|
|
@@ -242,7 +253,12 @@ class HomePageController extends Controller
|
|
242
|
253
|
? (int) round(($minuti / $atteso) * 100)
|
|
243
|
254
|
: ($minuti > 0 ? 100 : 0);
|
|
244
|
255
|
|
|
245
|
|
- $deltaSign = $delta > 0 ? '+' : ($delta < 0 ? '−' : '');
|
|
|
256
|
+ $mediaMinuti = (int) ($media['minuti'] ?? 0);
|
|
|
257
|
+ $mediaCampioni = (int) ($media['campioni'] ?? 0);
|
|
|
258
|
+ $deltaMedia = $minuti - $mediaMinuti;
|
|
|
259
|
+ $pctMedia = $mediaMinuti > 0
|
|
|
260
|
+ ? round(($deltaMedia / $mediaMinuti) * 100, 1)
|
|
|
261
|
+ : ($minuti > 0 ? 100.0 : 0.0);
|
|
246
|
262
|
|
|
247
|
263
|
return [
|
|
248
|
264
|
'label' => $label,
|
|
|
@@ -258,10 +274,17 @@ class HomePageController extends Controller
|
|
258
|
274
|
'progress_width' => min(100, $pctAtteso),
|
|
259
|
275
|
'has_target' => $atteso > 0,
|
|
260
|
276
|
'vs_precedente_minuti' => $delta,
|
|
261
|
|
- 'vs_precedente_label' => $deltaSign.Config::formatTempo(abs($delta)),
|
|
|
277
|
+ 'vs_precedente_label' => $this->formatTempoDelta($delta),
|
|
262
|
278
|
'vs_precedente_percentuale' => $pctPrecedente,
|
|
263
|
279
|
'precedente_label' => $previousLabel,
|
|
264
|
280
|
'precedente_minuti_label' => Config::formatTempo($precedente),
|
|
|
281
|
+ 'has_media' => $mediaCampioni > 0,
|
|
|
282
|
+ 'media_campioni' => $mediaCampioni,
|
|
|
283
|
+ 'media_label' => $mediaLabel,
|
|
|
284
|
+ 'media_minuti_label' => Config::formatTempo($mediaMinuti),
|
|
|
285
|
+ 'vs_media_minuti' => $deltaMedia,
|
|
|
286
|
+ 'vs_media_label' => $this->formatTempoDelta($deltaMedia),
|
|
|
287
|
+ 'vs_media_percentuale' => $pctMedia,
|
|
265
|
288
|
];
|
|
266
|
289
|
}
|
|
267
|
290
|
|
|
|
@@ -301,6 +324,110 @@ class HomePageController extends Controller
|
|
301
|
324
|
return $sum;
|
|
302
|
325
|
}
|
|
303
|
326
|
|
|
|
327
|
+ /**
|
|
|
328
|
+ * Media dei giorni precedenti con lo stesso weekday (es. media dei martedì).
|
|
|
329
|
+ * I giorni senza registrazioni contano come 0.
|
|
|
330
|
+ *
|
|
|
331
|
+ * @param array<string, int> $byDay
|
|
|
332
|
+ * @return array{minuti: int, campioni: int}
|
|
|
333
|
+ */
|
|
|
334
|
+ private function mediaMinutiGiornaliera(array $byDay, Carbon $lookbackStart, Carbon $today, int $isoWeekday): array
|
|
|
335
|
+ {
|
|
|
336
|
+ $sum = 0;
|
|
|
337
|
+ $campioni = 0;
|
|
|
338
|
+ for ($d = $lookbackStart->copy()->startOfDay(); $d->lt($today); $d->addDay()) {
|
|
|
339
|
+ if ((int) $d->isoWeekday() !== $isoWeekday) {
|
|
|
340
|
+ continue;
|
|
|
341
|
+ }
|
|
|
342
|
+ $sum += $byDay[$d->toDateString()] ?? 0;
|
|
|
343
|
+ $campioni++;
|
|
|
344
|
+ }
|
|
|
345
|
+
|
|
|
346
|
+ return $this->mediaFromSum($sum, $campioni);
|
|
|
347
|
+ }
|
|
|
348
|
+
|
|
|
349
|
+ /**
|
|
|
350
|
+ * Media delle settimane precedenti, stessa porzione lun → weekday corrente.
|
|
|
351
|
+ *
|
|
|
352
|
+ * @param array<string, int> $byDay
|
|
|
353
|
+ * @return array{minuti: int, campioni: int}
|
|
|
354
|
+ */
|
|
|
355
|
+ private function mediaMinutiSettimanale(array $byDay, Carbon $lookbackStart, Carbon $weekStart, int $weekOffsetDays): array
|
|
|
356
|
+ {
|
|
|
357
|
+ $sum = 0;
|
|
|
358
|
+ $campioni = 0;
|
|
|
359
|
+ for ($cursor = $weekStart->copy()->subWeek(); $cursor->gte($lookbackStart); $cursor->subWeek()) {
|
|
|
360
|
+ $from = $cursor->copy()->startOfDay();
|
|
|
361
|
+ $to = $cursor->copy()->addDays($weekOffsetDays)->endOfDay();
|
|
|
362
|
+ $sum += $this->sumMinutiInRange($byDay, $from, $to);
|
|
|
363
|
+ $campioni++;
|
|
|
364
|
+ }
|
|
|
365
|
+
|
|
|
366
|
+ return $this->mediaFromSum($sum, $campioni);
|
|
|
367
|
+ }
|
|
|
368
|
+
|
|
|
369
|
+ /**
|
|
|
370
|
+ * Media dei mesi precedenti, dal 1 al medesimo giorno del mese.
|
|
|
371
|
+ *
|
|
|
372
|
+ * @param array<string, int> $byDay
|
|
|
373
|
+ * @return array{minuti: int, campioni: int}
|
|
|
374
|
+ */
|
|
|
375
|
+ private function mediaMinutiMensile(array $byDay, Carbon $lookbackStart, Carbon $monthStart, int $dayOfMonth): array
|
|
|
376
|
+ {
|
|
|
377
|
+ $sum = 0;
|
|
|
378
|
+ $campioni = 0;
|
|
|
379
|
+ for ($cursor = $monthStart->copy()->subMonth(); $cursor->gte($lookbackStart); $cursor->subMonth()) {
|
|
|
380
|
+ $from = $cursor->copy()->startOfDay();
|
|
|
381
|
+ $day = min($dayOfMonth, (int) $cursor->copy()->endOfMonth()->day);
|
|
|
382
|
+ $to = $cursor->copy()->day($day)->endOfDay();
|
|
|
383
|
+ $sum += $this->sumMinutiInRange($byDay, $from, $to);
|
|
|
384
|
+ $campioni++;
|
|
|
385
|
+ }
|
|
|
386
|
+
|
|
|
387
|
+ return $this->mediaFromSum($sum, $campioni);
|
|
|
388
|
+ }
|
|
|
389
|
+
|
|
|
390
|
+ /**
|
|
|
391
|
+ * Media degli anni precedenti, dal 1° gennaio alla medesima data.
|
|
|
392
|
+ *
|
|
|
393
|
+ * @param array<string, int> $byDay
|
|
|
394
|
+ * @return array{minuti: int, campioni: int}
|
|
|
395
|
+ */
|
|
|
396
|
+ private function mediaMinutiAnnuale(array $byDay, Carbon $lookbackStart, Carbon $yearStart, Carbon $now): array
|
|
|
397
|
+ {
|
|
|
398
|
+ $sum = 0;
|
|
|
399
|
+ $campioni = 0;
|
|
|
400
|
+ for ($yearsBack = 1; ; $yearsBack++) {
|
|
|
401
|
+ $from = $yearStart->copy()->subYears($yearsBack);
|
|
|
402
|
+ if ($from->lt($lookbackStart)) {
|
|
|
403
|
+ break;
|
|
|
404
|
+ }
|
|
|
405
|
+ $to = $now->copy()->subYears($yearsBack)->endOfDay();
|
|
|
406
|
+ $sum += $this->sumMinutiInRange($byDay, $from, $to);
|
|
|
407
|
+ $campioni++;
|
|
|
408
|
+ }
|
|
|
409
|
+
|
|
|
410
|
+ return $this->mediaFromSum($sum, $campioni);
|
|
|
411
|
+ }
|
|
|
412
|
+
|
|
|
413
|
+ /**
|
|
|
414
|
+ * @return array{minuti: int, campioni: int}
|
|
|
415
|
+ */
|
|
|
416
|
+ private function mediaFromSum(int $sum, int $campioni): array
|
|
|
417
|
+ {
|
|
|
418
|
+ return [
|
|
|
419
|
+ 'minuti' => $campioni > 0 ? (int) round($sum / $campioni) : 0,
|
|
|
420
|
+ 'campioni' => $campioni,
|
|
|
421
|
+ ];
|
|
|
422
|
+ }
|
|
|
423
|
+
|
|
|
424
|
+ private function formatTempoDelta(int $minuti): string
|
|
|
425
|
+ {
|
|
|
426
|
+ $sign = $minuti > 0 ? '+' : ($minuti < 0 ? '−' : '');
|
|
|
427
|
+
|
|
|
428
|
+ return $sign.Config::formatTempo(abs($minuti));
|
|
|
429
|
+ }
|
|
|
430
|
+
|
|
304
|
431
|
private function countWeekdays(Carbon $from, Carbon $to): int
|
|
305
|
432
|
{
|
|
306
|
433
|
$count = 0;
|