Compare commits
2 Commits
2b54028495
...
b7abf51087
| Author | SHA1 | Date | |
|---|---|---|---|
| b7abf51087 | |||
| e405985c34 |
+1
-1
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@switchev/web-shared",
|
"name": "@switchev/web-shared",
|
||||||
"version": "0.2.0",
|
"version": "0.3.0",
|
||||||
"description": "Shared browser helpers (formatters, markdown, image polling) for the SwitchEV frontends",
|
"description": "Shared browser helpers (formatters, markdown, image polling) for the SwitchEV frontends",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"exports": {
|
"exports": {
|
||||||
|
|||||||
@@ -95,3 +95,29 @@ export function vehicleName({ year, make, model, commissioned } = {}, fallback =
|
|||||||
const v = [year, make, model].filter(Boolean).join(' ');
|
const v = [year, make, model].filter(Boolean).join(' ');
|
||||||
return v || fallback;
|
return v || fallback;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// SEV-378: our Go/Turso layer emits SQLite `datetime('now')` timestamps as
|
||||||
|
// "YYYY-MM-DD HH:MM:SS" (UTC, no zone), which `new Date()` can't reliably
|
||||||
|
// parse → "Invalid Date" in the admin UI. normalizeTs handles both that shape
|
||||||
|
// and RFC3339; returns null for falsy/invalid so callers render ''.
|
||||||
|
function normalizeTs(ts) {
|
||||||
|
if (!ts) return null;
|
||||||
|
const s = String(ts).trim();
|
||||||
|
const sqlite = /^\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}$/.test(s);
|
||||||
|
const d = new Date(sqlite ? s.replace(' ', 'T') + 'Z' : s);
|
||||||
|
return isNaN(d.getTime()) ? null : d;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Localized date (e.g. "Jun 10, 2026"). '' for missing/invalid input. */
|
||||||
|
export function formatDate(ts) {
|
||||||
|
const d = normalizeTs(ts);
|
||||||
|
return d ? d.toLocaleDateString('en-US', { month: 'short', day: 'numeric', year: 'numeric' }) : '';
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Localized date + time (e.g. "Jun 10, 2026, 2:42 PM"). '' for missing/invalid. */
|
||||||
|
export function formatDateTime(ts) {
|
||||||
|
const d = normalizeTs(ts);
|
||||||
|
return d
|
||||||
|
? d.toLocaleString('en-US', { month: 'short', day: 'numeric', year: 'numeric', hour: 'numeric', minute: '2-digit' })
|
||||||
|
: '';
|
||||||
|
}
|
||||||
|
|||||||
+19
-1
@@ -2,7 +2,7 @@ import { test } from 'node:test';
|
|||||||
import assert from 'node:assert/strict';
|
import assert from 'node:assert/strict';
|
||||||
import {
|
import {
|
||||||
formatLabel, formatStatus, formatTimeline, formatRange,
|
formatLabel, formatStatus, formatTimeline, formatRange,
|
||||||
formatBudget, formatBudgetBucket, vehicleName,
|
formatBudget, formatBudgetBucket, vehicleName, formatDate, formatDateTime,
|
||||||
} from './format.js';
|
} from './format.js';
|
||||||
|
|
||||||
test('formatLabel replaces _ and - and title-cases', () => {
|
test('formatLabel replaces _ and - and title-cases', () => {
|
||||||
@@ -68,3 +68,21 @@ test('vehicleName handles commissioned and sparse vehicles', () => {
|
|||||||
assert.equal(vehicleName({ make: 'BMW' }, 'Lead'), 'BMW');
|
assert.equal(vehicleName({ make: 'BMW' }, 'Lead'), 'BMW');
|
||||||
assert.equal(vehicleName(undefined), 'Project');
|
assert.equal(vehicleName(undefined), 'Project');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('formatDate handles SQLite and RFC3339 timestamps (SEV-378)', () => {
|
||||||
|
// SQLite "YYYY-MM-DD HH:MM:SS" (UTC)
|
||||||
|
assert.equal(formatDate('2026-06-10 14:42:27'), 'Jun 10, 2026');
|
||||||
|
// RFC3339
|
||||||
|
assert.equal(formatDate('2026-06-10T14:42:27Z'), 'Jun 10, 2026');
|
||||||
|
// falsy / invalid → ''
|
||||||
|
assert.equal(formatDate(''), '');
|
||||||
|
assert.equal(formatDate(null), '');
|
||||||
|
assert.equal(formatDate('not a date'), '');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('formatDateTime handles both formats + empties (SEV-378)', () => {
|
||||||
|
assert.match(formatDateTime('2026-06-10 14:42:27'), /Jun 10, 2026/);
|
||||||
|
assert.match(formatDateTime('2026-06-10T14:42:27Z'), /Jun 10, 2026/);
|
||||||
|
assert.equal(formatDateTime(''), '');
|
||||||
|
assert.equal(formatDateTime('garbage'), '');
|
||||||
|
});
|
||||||
|
|||||||
@@ -3,6 +3,7 @@
|
|||||||
export {
|
export {
|
||||||
formatLabel, formatStatus, formatTimeline, formatRange,
|
formatLabel, formatStatus, formatTimeline, formatRange,
|
||||||
formatBudget, formatBudgetBucket, vehicleName,
|
formatBudget, formatBudgetBucket, vehicleName,
|
||||||
|
formatDate, formatDateTime,
|
||||||
} from './format.js';
|
} from './format.js';
|
||||||
export { renderMarkdown } from './markdown.js';
|
export { renderMarkdown } from './markdown.js';
|
||||||
export { waitForImage } from './wait-image.js';
|
export { waitForImage } from './wait-image.js';
|
||||||
|
|||||||
Reference in New Issue
Block a user