diff --git a/web/src/components/activity-time-cell.tsx b/web/src/components/activity-time-cell.tsx new file mode 100644 index 000000000..b66f57033 --- /dev/null +++ b/web/src/components/activity-time-cell.tsx @@ -0,0 +1,138 @@ +/* +Copyright (C) 2023-2026 QuantumNous + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU Affero General Public License as +published by the Free Software Foundation, either version 3 of the +License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU Affero General Public License for more details. + +You should have received a copy of the GNU Affero General Public License +along with this program. If not, see . + +For commercial licensing, please contact support@quantumnous.com +*/ +import { useTranslation } from 'react-i18next' + +import { + Tooltip, + TooltipContent, + TooltipTrigger, +} from '@/components/ui/tooltip' +import { toIntlLocale } from '@/i18n/languages' +import { formatTimestampRelative, formatTimestampToDate } from '@/lib/format' +import { cn } from '@/lib/utils' + +interface TimestampCellProps { + timestamp: number + now?: number + format?: 'relative' | 'absolute' + locale?: string + justNowLabel: string + className?: string +} + +export function TimestampCell(props: TimestampCellProps) { + if (!props.timestamp || props.timestamp === -1) { + return - + } + + const timestampMs = props.timestamp * 1000 + const absoluteTime = formatTimestampToDate(props.timestamp) + if (props.format === 'absolute') { + return ( + + ) + } + + const now = props.now ?? Date.now() + const isJustNow = timestampMs <= now && now - timestampMs < 60_000 + const relativeTime = isJustNow + ? props.justNowLabel + : formatTimestampRelative(props.timestamp, 'seconds', props.locale) + const [relativePrefix, relativeNumber, relativeSuffix] = relativeTime.split( + /(\p{Number}+(?:[.,\u00a0\u202f]\p{Number}+)*)/u + ) + + return ( + + + } + > + {relativePrefix} + {relativeNumber && ( + {relativeNumber} + )} + {relativeSuffix} + + + {absoluteTime} + + + ) +} + +export function ActivityTimeCell(props: { + createdAt: number + lastAt: number + lastLabel: string + lastClassName?: string + now?: number + format?: 'relative' | 'absolute' + layout?: 'rows' | 'columns' +}) { + const { t, i18n } = useTranslation() + const locale = toIntlLocale(i18n.resolvedLanguage || i18n.language) + + return ( +
+ {t('Created')} + + {props.lastLabel} + +
+ ) +} diff --git a/web/src/components/data-table/core/data-table-view.tsx b/web/src/components/data-table/core/data-table-view.tsx index fdf2977b0..08f1b70ff 100644 --- a/web/src/components/data-table/core/data-table-view.tsx +++ b/web/src/components/data-table/core/data-table-view.tsx @@ -145,12 +145,9 @@ function SplitHeaderTableView({ props.bodyContainerClassName )} > - {tableSizing.colgroup} @@ -162,7 +159,7 @@ function SplitHeaderTableView({ getColumnClassName={getColumnClassName} /> {renderTableBody(props, rows, colSpan, getColumnClassName)} -
+ ) diff --git a/web/src/components/group-badge.tsx b/web/src/components/group-badge.tsx index 444f419c1..5b170c990 100644 --- a/web/src/components/group-badge.tsx +++ b/web/src/components/group-badge.tsx @@ -42,8 +42,8 @@ export function GroupMultiplierBadge(props: { diff --git a/web/src/components/masked-value-display.tsx b/web/src/components/masked-value-display.tsx index ad4347ef8..16eccb786 100644 --- a/web/src/components/masked-value-display.tsx +++ b/web/src/components/masked-value-display.tsx @@ -16,6 +16,8 @@ along with this program. If not, see . For commercial licensing, please contact support@quantumnous.com */ +import type { ComponentProps } from 'react' + import { CopyButton } from '@/components/copy-button' import { Button } from '@/components/ui/button' import { @@ -23,6 +25,21 @@ import { PopoverContent, PopoverTrigger, } from '@/components/ui/popover' +import { cn } from '@/lib/utils' + +export function MaskedValueTrigger(props: ComponentProps) { + return ( +