feat: extract timestamp formatting and text truncation into separate utility functions
This commit is contained in:
@@ -22,6 +22,10 @@ import {
|
||||
type listBoardsApiV1BoardsGetResponse,
|
||||
useListBoardsApiV1BoardsGet,
|
||||
} from "@/api/generated/boards/boards";
|
||||
import {
|
||||
formatRelativeTimestamp as formatRelative,
|
||||
formatTimestamp,
|
||||
} from "@/lib/formatters";
|
||||
import { useOrganizationMembership } from "@/lib/use-organization-membership";
|
||||
import type {
|
||||
ActivityEventRead,
|
||||
@@ -41,39 +45,6 @@ import {
|
||||
DialogTitle,
|
||||
} from "@/components/ui/dialog";
|
||||
|
||||
const parseTimestamp = (value?: string | null) => {
|
||||
if (!value) return null;
|
||||
const hasTz = /[zZ]|[+-]\d\d:\d\d$/.test(value);
|
||||
const normalized = hasTz ? value : `${value}Z`;
|
||||
const date = new Date(normalized);
|
||||
if (Number.isNaN(date.getTime())) return null;
|
||||
return date;
|
||||
};
|
||||
|
||||
const formatTimestamp = (value?: string | null) => {
|
||||
const date = parseTimestamp(value);
|
||||
if (!date) return "—";
|
||||
return date.toLocaleString(undefined, {
|
||||
month: "short",
|
||||
day: "numeric",
|
||||
hour: "2-digit",
|
||||
minute: "2-digit",
|
||||
});
|
||||
};
|
||||
|
||||
const formatRelative = (value?: string | null) => {
|
||||
const date = parseTimestamp(value);
|
||||
if (!date) return "—";
|
||||
const diff = Date.now() - date.getTime();
|
||||
const minutes = Math.round(diff / 60000);
|
||||
if (minutes < 1) return "Just now";
|
||||
if (minutes < 60) return `${minutes}m ago`;
|
||||
const hours = Math.round(minutes / 60);
|
||||
if (hours < 24) return `${hours}h ago`;
|
||||
const days = Math.round(hours / 24);
|
||||
return `${days}d ago`;
|
||||
};
|
||||
|
||||
export default function AgentDetailPage() {
|
||||
const { isSignedIn } = useAuth();
|
||||
const router = useRouter();
|
||||
|
||||
@@ -41,48 +41,14 @@ import {
|
||||
getListBoardsApiV1BoardsGetQueryKey,
|
||||
useListBoardsApiV1BoardsGet,
|
||||
} from "@/api/generated/boards/boards";
|
||||
import {
|
||||
formatRelativeTimestamp as formatRelative,
|
||||
formatTimestamp,
|
||||
truncateText as truncate,
|
||||
} from "@/lib/formatters";
|
||||
import { useOrganizationMembership } from "@/lib/use-organization-membership";
|
||||
import type { AgentRead } from "@/api/generated/model";
|
||||
|
||||
const parseTimestamp = (value?: string | null) => {
|
||||
if (!value) return null;
|
||||
const hasTz = /[zZ]|[+-]\d\d:\d\d$/.test(value);
|
||||
const normalized = hasTz ? value : `${value}Z`;
|
||||
const date = new Date(normalized);
|
||||
if (Number.isNaN(date.getTime())) return null;
|
||||
return date;
|
||||
};
|
||||
|
||||
const formatTimestamp = (value?: string | null) => {
|
||||
const date = parseTimestamp(value);
|
||||
if (!date) return "—";
|
||||
return date.toLocaleString(undefined, {
|
||||
month: "short",
|
||||
day: "numeric",
|
||||
hour: "2-digit",
|
||||
minute: "2-digit",
|
||||
});
|
||||
};
|
||||
|
||||
const formatRelative = (value?: string | null) => {
|
||||
const date = parseTimestamp(value);
|
||||
if (!date) return "—";
|
||||
const diff = Date.now() - date.getTime();
|
||||
const minutes = Math.round(diff / 60000);
|
||||
if (minutes < 1) return "Just now";
|
||||
if (minutes < 60) return `${minutes}m ago`;
|
||||
const hours = Math.round(minutes / 60);
|
||||
if (hours < 24) return `${hours}h ago`;
|
||||
const days = Math.round(hours / 24);
|
||||
return `${days}d ago`;
|
||||
};
|
||||
|
||||
const truncate = (value?: string | null, max = 18) => {
|
||||
if (!value) return "—";
|
||||
if (value.length <= max) return value;
|
||||
return `${value.slice(0, max)}…`;
|
||||
};
|
||||
|
||||
export default function AgentsPage() {
|
||||
const { isSignedIn } = useAuth();
|
||||
const queryClient = useQueryClient();
|
||||
|
||||
@@ -45,21 +45,10 @@ import { BoardChatComposer } from "@/components/BoardChatComposer";
|
||||
import { Button, buttonVariants } from "@/components/ui/button";
|
||||
import { createExponentialBackoff } from "@/lib/backoff";
|
||||
import { apiDatetimeToMs } from "@/lib/datetime";
|
||||
import { formatTimestamp } from "@/lib/formatters";
|
||||
import { cn } from "@/lib/utils";
|
||||
import { usePageActive } from "@/hooks/usePageActive";
|
||||
|
||||
const formatTimestamp = (value?: string | null) => {
|
||||
if (!value) return "—";
|
||||
const date = new Date(`${value}${value.endsWith("Z") ? "" : "Z"}`);
|
||||
if (Number.isNaN(date.getTime())) return "—";
|
||||
return date.toLocaleString(undefined, {
|
||||
month: "short",
|
||||
day: "numeric",
|
||||
hour: "2-digit",
|
||||
minute: "2-digit",
|
||||
});
|
||||
};
|
||||
|
||||
const statusLabel = (value?: string | null) => {
|
||||
switch (value) {
|
||||
case "inbox":
|
||||
|
||||
@@ -34,18 +34,7 @@ import {
|
||||
DialogTitle,
|
||||
} from "@/components/ui/dialog";
|
||||
import { SignedOutPanel } from "@/components/auth/SignedOutPanel";
|
||||
|
||||
const formatTimestamp = (value?: string | null) => {
|
||||
if (!value) return "—";
|
||||
const date = new Date(`${value}${value.endsWith("Z") ? "" : "Z"}`);
|
||||
if (Number.isNaN(date.getTime())) return "—";
|
||||
return date.toLocaleString(undefined, {
|
||||
month: "short",
|
||||
day: "numeric",
|
||||
hour: "2-digit",
|
||||
minute: "2-digit",
|
||||
});
|
||||
};
|
||||
import { formatTimestamp } from "@/lib/formatters";
|
||||
|
||||
export default function BoardGroupsPage() {
|
||||
const { isSignedIn } = useAuth();
|
||||
|
||||
@@ -25,6 +25,7 @@ import {
|
||||
type listBoardGroupsApiV1BoardGroupsGetResponse,
|
||||
useListBoardGroupsApiV1BoardGroupsGet,
|
||||
} from "@/api/generated/board-groups/board-groups";
|
||||
import { formatTimestamp } from "@/lib/formatters";
|
||||
import { useOrganizationMembership } from "@/lib/use-organization-membership";
|
||||
import type { BoardGroupRead, BoardRead } from "@/api/generated/model";
|
||||
import { DashboardSidebar } from "@/components/organisms/DashboardSidebar";
|
||||
@@ -40,18 +41,6 @@ import {
|
||||
} from "@/components/ui/dialog";
|
||||
import { SignedOutPanel } from "@/components/auth/SignedOutPanel";
|
||||
|
||||
const formatTimestamp = (value?: string | null) => {
|
||||
if (!value) return "—";
|
||||
const date = new Date(`${value}${value.endsWith("Z") ? "" : "Z"}`);
|
||||
if (Number.isNaN(date.getTime())) return "—";
|
||||
return date.toLocaleString(undefined, {
|
||||
month: "short",
|
||||
day: "numeric",
|
||||
hour: "2-digit",
|
||||
minute: "2-digit",
|
||||
});
|
||||
};
|
||||
|
||||
const compactId = (value: string) =>
|
||||
value.length > 8 ? `${value.slice(0, 8)}…` : value;
|
||||
|
||||
|
||||
@@ -18,22 +18,11 @@ import {
|
||||
type listAgentsApiV1AgentsGetResponse,
|
||||
useListAgentsApiV1AgentsGet,
|
||||
} from "@/api/generated/agents/agents";
|
||||
import { formatTimestamp } from "@/lib/formatters";
|
||||
import { useOrganizationMembership } from "@/lib/use-organization-membership";
|
||||
import { DashboardPageLayout } from "@/components/templates/DashboardPageLayout";
|
||||
import { Button } from "@/components/ui/button";
|
||||
|
||||
const formatTimestamp = (value?: string | null) => {
|
||||
if (!value) return "—";
|
||||
const date = new Date(`${value}${value.endsWith("Z") ? "" : "Z"}`);
|
||||
if (Number.isNaN(date.getTime())) return "—";
|
||||
return date.toLocaleString(undefined, {
|
||||
month: "short",
|
||||
day: "numeric",
|
||||
hour: "2-digit",
|
||||
minute: "2-digit",
|
||||
});
|
||||
};
|
||||
|
||||
const maskToken = (value?: string | null) => {
|
||||
if (!value) return "—";
|
||||
if (value.length <= 8) return "••••";
|
||||
|
||||
@@ -34,27 +34,10 @@ import {
|
||||
useDeleteGatewayApiV1GatewaysGatewayIdDelete,
|
||||
useListGatewaysApiV1GatewaysGet,
|
||||
} from "@/api/generated/gateways/gateways";
|
||||
import { formatTimestamp, truncateText as truncate } from "@/lib/formatters";
|
||||
import { useOrganizationMembership } from "@/lib/use-organization-membership";
|
||||
import type { GatewayRead } from "@/api/generated/model";
|
||||
|
||||
const truncate = (value?: string | null, max = 24) => {
|
||||
if (!value) return "—";
|
||||
if (value.length <= max) return value;
|
||||
return `${value.slice(0, max)}…`;
|
||||
};
|
||||
|
||||
const formatTimestamp = (value?: string | null) => {
|
||||
if (!value) return "—";
|
||||
const date = new Date(`${value}${value.endsWith("Z") ? "" : "Z"}`);
|
||||
if (Number.isNaN(date.getTime())) return "—";
|
||||
return date.toLocaleString(undefined, {
|
||||
month: "short",
|
||||
day: "numeric",
|
||||
hour: "2-digit",
|
||||
minute: "2-digit",
|
||||
});
|
||||
};
|
||||
|
||||
export default function GatewaysPage() {
|
||||
const { isSignedIn } = useAuth();
|
||||
const queryClient = useQueryClient();
|
||||
|
||||
@@ -59,20 +59,9 @@ import {
|
||||
SelectValue,
|
||||
} from "@/components/ui/select";
|
||||
import { DashboardShell } from "@/components/templates/DashboardShell";
|
||||
import { formatTimestamp } from "@/lib/formatters";
|
||||
import { cn } from "@/lib/utils";
|
||||
|
||||
const formatTimestamp = (value?: string | null) => {
|
||||
if (!value) return "—";
|
||||
const date = new Date(`${value}${value.endsWith("Z") ? "" : "Z"}`);
|
||||
if (Number.isNaN(date.getTime())) return "—";
|
||||
return date.toLocaleString(undefined, {
|
||||
month: "short",
|
||||
day: "numeric",
|
||||
hour: "2-digit",
|
||||
minute: "2-digit",
|
||||
});
|
||||
};
|
||||
|
||||
type AccessScope = "all" | "custom";
|
||||
|
||||
type BoardAccessState = Record<string, { read: boolean; write: boolean }>;
|
||||
|
||||
49
frontend/src/lib/formatters.ts
Normal file
49
frontend/src/lib/formatters.ts
Normal file
@@ -0,0 +1,49 @@
|
||||
const DASH = "—";
|
||||
|
||||
export const truncateText = (
|
||||
value?: string | null,
|
||||
max = 24,
|
||||
fallback = DASH,
|
||||
): string => {
|
||||
if (!value) return fallback;
|
||||
if (value.length <= max) return value;
|
||||
return `${value.slice(0, max)}…`;
|
||||
};
|
||||
|
||||
export const parseTimestamp = (value?: string | null): Date | null => {
|
||||
if (!value) return null;
|
||||
const hasTimeZone = /[zZ]|[+-]\d\d:\d\d$/.test(value);
|
||||
const normalized = hasTimeZone ? value : `${value}Z`;
|
||||
const date = new Date(normalized);
|
||||
return Number.isNaN(date.getTime()) ? null : date;
|
||||
};
|
||||
|
||||
export const formatTimestamp = (
|
||||
value?: string | null,
|
||||
fallback = DASH,
|
||||
): string => {
|
||||
const date = parseTimestamp(value);
|
||||
if (!date) return fallback;
|
||||
return date.toLocaleString(undefined, {
|
||||
month: "short",
|
||||
day: "numeric",
|
||||
hour: "2-digit",
|
||||
minute: "2-digit",
|
||||
});
|
||||
};
|
||||
|
||||
export const formatRelativeTimestamp = (
|
||||
value?: string | null,
|
||||
fallback = DASH,
|
||||
): string => {
|
||||
const date = parseTimestamp(value);
|
||||
if (!date) return fallback;
|
||||
const diff = Date.now() - date.getTime();
|
||||
const minutes = Math.round(diff / 60000);
|
||||
if (minutes < 1) return "Just now";
|
||||
if (minutes < 60) return `${minutes}m ago`;
|
||||
const hours = Math.round(minutes / 60);
|
||||
if (hours < 24) return `${hours}h ago`;
|
||||
const days = Math.round(hours / 24);
|
||||
return `${days}d ago`;
|
||||
};
|
||||
Reference in New Issue
Block a user