diff options
| -rw-r--r-- | renderer/src/components/Header.tsx | 3 | ||||
| -rw-r--r-- | renderer/src/utils/timeFormat.ts | 61 |
2 files changed, 63 insertions, 1 deletions
diff --git a/renderer/src/components/Header.tsx b/renderer/src/components/Header.tsx index 1d954ec..dc88a73 100644 --- a/renderer/src/components/Header.tsx +++ b/renderer/src/components/Header.tsx | |||
| @@ -1,5 +1,6 @@ | |||
| 1 | import React, { useState, useEffect } from "react"; | 1 | import React, { useState, useEffect } from "react"; |
| 2 | import type { Project, Session, Phase } from "../types"; | 2 | import type { Project, Session, Phase } from "../types"; |
| 3 | import { formatSessionLabel } from "../utils/timeFormat"; | ||
| 3 | 4 | ||
| 4 | const api = window.api; | 5 | const api = window.api; |
| 5 | 6 | ||
| @@ -165,7 +166,7 @@ export function Header({ | |||
| 165 | <option value="">Select Session...</option> | 166 | <option value="">Select Session...</option> |
| 166 | {sessions.map((s) => ( | 167 | {sessions.map((s) => ( |
| 167 | <option key={s.id} value={s.id}> | 168 | <option key={s.id} value={s.id}> |
| 168 | {s.name} | 169 | {formatSessionLabel(s.name, s.updated_at)} |
| 169 | </option> | 170 | </option> |
| 170 | ))} | 171 | ))} |
| 171 | </select> | 172 | </select> |
diff --git a/renderer/src/utils/timeFormat.ts b/renderer/src/utils/timeFormat.ts new file mode 100644 index 0000000..db0f99b --- /dev/null +++ b/renderer/src/utils/timeFormat.ts | |||
| @@ -0,0 +1,61 @@ | |||
| 1 | /** | ||
| 2 | * Format a Unix timestamp (seconds) into a relative time string. | ||
| 3 | * Examples: "now", "5m", "2h", "3d", "1w", "2mo", "1y" | ||
| 4 | */ | ||
| 5 | export function formatRelativeTime(timestamp: number): string { | ||
| 6 | const now = Math.floor(Date.now() / 1000); // Current time in seconds | ||
| 7 | const diffSeconds = now - timestamp; | ||
| 8 | |||
| 9 | // Handle future timestamps (shouldn't happen, but be defensive) | ||
| 10 | if (diffSeconds < 0) { | ||
| 11 | return "now"; | ||
| 12 | } | ||
| 13 | |||
| 14 | // Less than 1 minute | ||
| 15 | if (diffSeconds < 60) { | ||
| 16 | return "now"; | ||
| 17 | } | ||
| 18 | |||
| 19 | // Less than 1 hour (show minutes) | ||
| 20 | if (diffSeconds < 3600) { | ||
| 21 | const minutes = Math.floor(diffSeconds / 60); | ||
| 22 | return `${minutes}m`; | ||
| 23 | } | ||
| 24 | |||
| 25 | // Less than 24 hours (show hours) | ||
| 26 | if (diffSeconds < 86400) { | ||
| 27 | const hours = Math.floor(diffSeconds / 3600); | ||
| 28 | return `${hours}h`; | ||
| 29 | } | ||
| 30 | |||
| 31 | // Less than 7 days (show days) | ||
| 32 | if (diffSeconds < 604800) { | ||
| 33 | const days = Math.floor(diffSeconds / 86400); | ||
| 34 | return `${days}d`; | ||
| 35 | } | ||
| 36 | |||
| 37 | // Less than 4 weeks (show weeks) | ||
| 38 | if (diffSeconds < 2419200) { | ||
| 39 | const weeks = Math.floor(diffSeconds / 604800); | ||
| 40 | return `${weeks}w`; | ||
| 41 | } | ||
| 42 | |||
| 43 | // Less than 12 months (show months) | ||
| 44 | if (diffSeconds < 31536000) { | ||
| 45 | const months = Math.floor(diffSeconds / 2592000); // Approximate: 30 days per month | ||
| 46 | return `${months}mo`; | ||
| 47 | } | ||
| 48 | |||
| 49 | // 12 months or more (show years) | ||
| 50 | const years = Math.floor(diffSeconds / 31536000); | ||
| 51 | return `${years}y`; | ||
| 52 | } | ||
| 53 | |||
| 54 | /** | ||
| 55 | * Format session name with relative time indicator. | ||
| 56 | * Example: "Session Name • 2h" | ||
| 57 | */ | ||
| 58 | export function formatSessionLabel(name: string, updatedAt: number): string { | ||
| 59 | const relativeTime = formatRelativeTime(updatedAt); | ||
| 60 | return `${name} • ${relativeTime}`; | ||
| 61 | } | ||
