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:
1 parent
75436ad2f4
commit
f0670224f3
3 files changed
+87
-5
No files matched your search
@@ -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)
|
||||
}
|
||||
@@ -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>
|
||||
|
||||
@@ -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'))
|
||||
})
|
||||
})
|
||||
Reference in new issue
Block a user