feat: periodic inform utility + 3 dates on same line

- Extract computeNextPeriodicInform to app/utils/periodic_inform.ts
- 11 unit tests for the periodic computation logic
- DeviceDetail: 3 date columns on same row (created, last seen, next estimated)
This commit is contained in:
Kevin L'Hereec committed 2026-06-02 18:06:18 +02:00
1 parent 75436ad2f4
commit f0670224f3
3 files changed
+87 -5

No files matched your search

+27
View File
@@ -0,0 +1,27 @@
/**
* Computes the estimated next periodic inform date.
*
* @param lastPeriodicInformAt - Date of the last periodic inform
* @param periodicInformInterval - Interval in seconds (from device parameter)
* @returns The estimated next connection date, or null if cannot be computed
*/
export function computeNextPeriodicInform(
lastPeriodicInformAt: Date | string | null | undefined,
periodicInformInterval: number | string | null | undefined
): Date | null {
if (!lastPeriodicInformAt) return null
const intervalSeconds = typeof periodicInformInterval === 'string'
? parseInt(periodicInformInterval, 10)
: periodicInformInterval
if (!intervalSeconds || intervalSeconds <= 0) return null
const lastDate = lastPeriodicInformAt instanceof Date
? lastPeriodicInformAt
: new Date(lastPeriodicInformAt)
if (isNaN(lastDate.getTime())) return null
return new Date(lastDate.getTime() + intervalSeconds * 1000)
}
+5 -5
View File
@@ -310,11 +310,11 @@ async function resolveAllProviders() {
</div>
<!-- Zones 4+5 : dates côte à côte -->
<!-- Zones 4+5+5b : dates sur une ligne -->
<div class="row g-2">
<!-- Zone 4 : Créé le -->
<div class="col-sm-6">
<div class="col-sm-4">
<div class="rounded-3 px-3 py-3 d-flex align-items-center gap-3" style="background:#f6f7f9">
<div class="rounded-circle d-flex align-items-center justify-content-center flex-shrink-0"
style="width:34px;height:34px;background:#e4e6ea">
@@ -330,7 +330,7 @@ async function resolveAllProviders() {
</div>
<!-- Zone 5 : Dernière connexion -->
<div class="col-sm-6">
<div class="col-sm-4">
<div class="rounded-3 px-3 py-3 d-flex align-items-center gap-3" style="background:#f6f7f9">
<div class="rounded-circle d-flex align-items-center justify-content-center flex-shrink-0"
style="width:34px;height:34px;background:#e4e6ea">
@@ -346,7 +346,7 @@ async function resolveAllProviders() {
</div>
<!-- Zone 5b : Prochaine connexion estimée -->
<div v-if="estimatedNextConnection" class="col-sm-6 col-lg-4">
<div class="col-sm-4">
<div class="rounded-3 px-3 py-3 d-flex align-items-center gap-3" style="background:#f6f7f9">
<div class="rounded-circle d-flex align-items-center justify-content-center flex-shrink-0"
style="width:34px;height:34px;background:#e4e6ea">
@@ -357,7 +357,7 @@ async function resolveAllProviders() {
</div>
<div>
<div class="text-muted fw-bold text-uppercase mb-1" style="font-size:0.62rem;letter-spacing:.06em">Prochaine connexion</div>
<div class="font-monospace small fw-medium">{{ estimatedNextConnection }}</div>
<div class="font-monospace small fw-medium">{{ estimatedNextConnection || '—' }}</div>
</div>
</div>
</div>
+55
View File
@@ -0,0 +1,55 @@
import { describe, it, expect } from 'vitest'
import { computeNextPeriodicInform } from '../../../app/utils/periodic_inform.js'
describe('computeNextPeriodicInform', () => {
it('computes next date from last periodic + interval', () => {
const last = new Date('2026-06-02T10:00:00Z')
const result = computeNextPeriodicInform(last, 30)
expect(result).toEqual(new Date('2026-06-02T10:00:30Z'))
})
it('works with string interval', () => {
const last = new Date('2026-06-02T12:00:00Z')
const result = computeNextPeriodicInform(last, '60')
expect(result).toEqual(new Date('2026-06-02T12:01:00Z'))
})
it('works with ISO string date', () => {
const result = computeNextPeriodicInform('2026-06-02T08:00:00Z', 300)
expect(result).toEqual(new Date('2026-06-02T08:05:00Z'))
})
it('returns null if lastPeriodicInformAt is null', () => {
expect(computeNextPeriodicInform(null, 30)).toBeNull()
})
it('returns null if lastPeriodicInformAt is undefined', () => {
expect(computeNextPeriodicInform(undefined, 30)).toBeNull()
})
it('returns null if interval is 0', () => {
expect(computeNextPeriodicInform(new Date(), 0)).toBeNull()
})
it('returns null if interval is negative', () => {
expect(computeNextPeriodicInform(new Date(), -10)).toBeNull()
})
it('returns null if interval is null', () => {
expect(computeNextPeriodicInform(new Date(), null)).toBeNull()
})
it('returns null if interval is empty string', () => {
expect(computeNextPeriodicInform(new Date(), '')).toBeNull()
})
it('returns null if date string is invalid', () => {
expect(computeNextPeriodicInform('not-a-date', 30)).toBeNull()
})
it('handles large intervals (1 hour)', () => {
const last = new Date('2026-06-02T10:00:00Z')
const result = computeNextPeriodicInform(last, 3600)
expect(result).toEqual(new Date('2026-06-02T11:00:00Z'))
})
})