feat(frontend): polish remaining views + centralize time/status helpers

- Add formatDuration + formatRelativeTime to lib/format.ts and aiTone to
  lib/ai-status.ts; dedupe duplicated helpers in messages/analysis views.
- Recordings: upload-status badge (pending/processing/failed), channel chip,
  relative time, hover lift, surface upload errors.
- Media: mode pill + duration on now-playing, volume meter, per-track duration
  and queue total in header.
- Moderation: relative timestamps in action rows + executor hint.
- Analysis & Dashboard: rank bars for top reactors for scannable comparison.
This commit is contained in:
asepharyana
2026-08-18 09:05:12 +07:00
parent 6bd6ddcdca
commit ff8a9c50ae
8 changed files with 250 additions and 142 deletions
+13
View File
@@ -0,0 +1,13 @@
import type { AiStatus } from "@/lib/types";
export type AiTone = "signal" | "amber" | "vermilion" | "neutral";
/**
* Map an AI analysis status to a design-system tone.
*/
export function aiTone(s?: AiStatus | null): AiTone {
if (s === "clean") return "signal";
if (s === "warn") return "amber";
if (s === "flagged" || s === "error") return "vermilion";
return "neutral";
}
+35 -1
View File
@@ -17,7 +17,41 @@ export function formatBytes(bytes: number): string {
}
/**
* Safely parse a JSON string into an array.
* Human-readable duration, e.g. 850ms / 1.2s / 3m 12s.
*/
export function formatDuration(ms: number | null | undefined): string {
if (!ms || ms <= 0) return "";
if (ms < 1000) return `${Math.round(ms)}ms`;
const totalS = Math.floor(ms / 1000);
if (totalS < 60) return `${(ms / 1000).toFixed(1)}s`;
const m = Math.floor(totalS / 60);
const s = totalS % 60;
return s ? `${m}m ${s}s` : `${m}m`;
}
/**
* Compact relative time from an epoch-ms timestamp.
* e.g. "just now" / "5m" / "3h" / "2d" / "Apr 3".
*/
export function formatRelativeTime(ts?: number | null): string {
if (!ts) return "";
const diff = Date.now() - ts;
if (diff < 0) return new Date(ts).toLocaleDateString();
const m = Math.floor(diff / 60000);
if (m < 1) return "just now";
if (m < 60) return `${m}m`;
const h = Math.floor(m / 60);
if (h < 24) return `${h}h`;
const d = Math.floor(h / 24);
if (d < 7) return `${d}d`;
return new Date(ts).toLocaleDateString(undefined, {
month: "short",
day: "numeric",
});
}
/**
* Parse JSON string into array — safe.
*/
export function safeParseJsonArray(value: string | null | undefined): string[] {
if (!value) return [];