feat: extract timestamp formatting and text truncation into separate utility functions
This commit is contained in:
@@ -22,6 +22,10 @@ import {
|
|||||||
type listBoardsApiV1BoardsGetResponse,
|
type listBoardsApiV1BoardsGetResponse,
|
||||||
useListBoardsApiV1BoardsGet,
|
useListBoardsApiV1BoardsGet,
|
||||||
} from "@/api/generated/boards/boards";
|
} from "@/api/generated/boards/boards";
|
||||||
|
import {
|
||||||
|
formatRelativeTimestamp as formatRelative,
|
||||||
|
formatTimestamp,
|
||||||
|
} from "@/lib/formatters";
|
||||||
import { useOrganizationMembership } from "@/lib/use-organization-membership";
|
import { useOrganizationMembership } from "@/lib/use-organization-membership";
|
||||||
import type {
|
import type {
|
||||||
ActivityEventRead,
|
ActivityEventRead,
|
||||||
@@ -41,39 +45,6 @@ import {
|
|||||||
DialogTitle,
|
DialogTitle,
|
||||||
} from "@/components/ui/dialog";
|
} 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() {
|
export default function AgentDetailPage() {
|
||||||
const { isSignedIn } = useAuth();
|
const { isSignedIn } = useAuth();
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
|
|||||||
@@ -41,48 +41,14 @@ import {
|
|||||||
getListBoardsApiV1BoardsGetQueryKey,
|
getListBoardsApiV1BoardsGetQueryKey,
|
||||||
useListBoardsApiV1BoardsGet,
|
useListBoardsApiV1BoardsGet,
|
||||||
} from "@/api/generated/boards/boards";
|
} from "@/api/generated/boards/boards";
|
||||||
|
import {
|
||||||
|
formatRelativeTimestamp as formatRelative,
|
||||||
|
formatTimestamp,
|
||||||
|
truncateText as truncate,
|
||||||
|
} from "@/lib/formatters";
|
||||||
import { useOrganizationMembership } from "@/lib/use-organization-membership";
|
import { useOrganizationMembership } from "@/lib/use-organization-membership";
|
||||||
import type { AgentRead } from "@/api/generated/model";
|
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() {
|
export default function AgentsPage() {
|
||||||
const { isSignedIn } = useAuth();
|
const { isSignedIn } = useAuth();
|
||||||
const queryClient = useQueryClient();
|
const queryClient = useQueryClient();
|
||||||
|
|||||||
@@ -45,21 +45,10 @@ import { BoardChatComposer } from "@/components/BoardChatComposer";
|
|||||||
import { Button, buttonVariants } from "@/components/ui/button";
|
import { Button, buttonVariants } from "@/components/ui/button";
|
||||||
import { createExponentialBackoff } from "@/lib/backoff";
|
import { createExponentialBackoff } from "@/lib/backoff";
|
||||||
import { apiDatetimeToMs } from "@/lib/datetime";
|
import { apiDatetimeToMs } from "@/lib/datetime";
|
||||||
|
import { formatTimestamp } from "@/lib/formatters";
|
||||||
import { cn } from "@/lib/utils";
|
import { cn } from "@/lib/utils";
|
||||||
import { usePageActive } from "@/hooks/usePageActive";
|
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) => {
|
const statusLabel = (value?: string | null) => {
|
||||||
switch (value) {
|
switch (value) {
|
||||||
case "inbox":
|
case "inbox":
|
||||||
|
|||||||
@@ -34,18 +34,7 @@ import {
|
|||||||
DialogTitle,
|
DialogTitle,
|
||||||
} from "@/components/ui/dialog";
|
} from "@/components/ui/dialog";
|
||||||
import { SignedOutPanel } from "@/components/auth/SignedOutPanel";
|
import { SignedOutPanel } from "@/components/auth/SignedOutPanel";
|
||||||
|
import { formatTimestamp } from "@/lib/formatters";
|
||||||
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 BoardGroupsPage() {
|
export default function BoardGroupsPage() {
|
||||||
const { isSignedIn } = useAuth();
|
const { isSignedIn } = useAuth();
|
||||||
|
|||||||
@@ -25,6 +25,7 @@ import {
|
|||||||
type listBoardGroupsApiV1BoardGroupsGetResponse,
|
type listBoardGroupsApiV1BoardGroupsGetResponse,
|
||||||
useListBoardGroupsApiV1BoardGroupsGet,
|
useListBoardGroupsApiV1BoardGroupsGet,
|
||||||
} from "@/api/generated/board-groups/board-groups";
|
} from "@/api/generated/board-groups/board-groups";
|
||||||
|
import { formatTimestamp } from "@/lib/formatters";
|
||||||
import { useOrganizationMembership } from "@/lib/use-organization-membership";
|
import { useOrganizationMembership } from "@/lib/use-organization-membership";
|
||||||
import type { BoardGroupRead, BoardRead } from "@/api/generated/model";
|
import type { BoardGroupRead, BoardRead } from "@/api/generated/model";
|
||||||
import { DashboardSidebar } from "@/components/organisms/DashboardSidebar";
|
import { DashboardSidebar } from "@/components/organisms/DashboardSidebar";
|
||||||
@@ -40,18 +41,6 @@ import {
|
|||||||
} from "@/components/ui/dialog";
|
} from "@/components/ui/dialog";
|
||||||
import { SignedOutPanel } from "@/components/auth/SignedOutPanel";
|
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) =>
|
const compactId = (value: string) =>
|
||||||
value.length > 8 ? `${value.slice(0, 8)}…` : value;
|
value.length > 8 ? `${value.slice(0, 8)}…` : value;
|
||||||
|
|
||||||
|
|||||||
@@ -18,22 +18,11 @@ import {
|
|||||||
type listAgentsApiV1AgentsGetResponse,
|
type listAgentsApiV1AgentsGetResponse,
|
||||||
useListAgentsApiV1AgentsGet,
|
useListAgentsApiV1AgentsGet,
|
||||||
} from "@/api/generated/agents/agents";
|
} from "@/api/generated/agents/agents";
|
||||||
|
import { formatTimestamp } from "@/lib/formatters";
|
||||||
import { useOrganizationMembership } from "@/lib/use-organization-membership";
|
import { useOrganizationMembership } from "@/lib/use-organization-membership";
|
||||||
import { DashboardPageLayout } from "@/components/templates/DashboardPageLayout";
|
import { DashboardPageLayout } from "@/components/templates/DashboardPageLayout";
|
||||||
import { Button } from "@/components/ui/button";
|
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) => {
|
const maskToken = (value?: string | null) => {
|
||||||
if (!value) return "—";
|
if (!value) return "—";
|
||||||
if (value.length <= 8) return "••••";
|
if (value.length <= 8) return "••••";
|
||||||
|
|||||||
@@ -34,27 +34,10 @@ import {
|
|||||||
useDeleteGatewayApiV1GatewaysGatewayIdDelete,
|
useDeleteGatewayApiV1GatewaysGatewayIdDelete,
|
||||||
useListGatewaysApiV1GatewaysGet,
|
useListGatewaysApiV1GatewaysGet,
|
||||||
} from "@/api/generated/gateways/gateways";
|
} from "@/api/generated/gateways/gateways";
|
||||||
|
import { formatTimestamp, truncateText as truncate } from "@/lib/formatters";
|
||||||
import { useOrganizationMembership } from "@/lib/use-organization-membership";
|
import { useOrganizationMembership } from "@/lib/use-organization-membership";
|
||||||
import type { GatewayRead } from "@/api/generated/model";
|
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() {
|
export default function GatewaysPage() {
|
||||||
const { isSignedIn } = useAuth();
|
const { isSignedIn } = useAuth();
|
||||||
const queryClient = useQueryClient();
|
const queryClient = useQueryClient();
|
||||||
|
|||||||
@@ -59,20 +59,9 @@ import {
|
|||||||
SelectValue,
|
SelectValue,
|
||||||
} from "@/components/ui/select";
|
} from "@/components/ui/select";
|
||||||
import { DashboardShell } from "@/components/templates/DashboardShell";
|
import { DashboardShell } from "@/components/templates/DashboardShell";
|
||||||
|
import { formatTimestamp } from "@/lib/formatters";
|
||||||
import { cn } from "@/lib/utils";
|
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 AccessScope = "all" | "custom";
|
||||||
|
|
||||||
type BoardAccessState = Record<string, { read: boolean; write: boolean }>;
|
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