Standardize date display

Signed-off-by: Sacha Al Himdani <sacha@getprobo.com>
This commit is contained in:
Sacha Al Himdani
2025-09-18 11:23:37 +02:00
parent 68feac5367
commit eabc34c3ac
19 changed files with 70 additions and 69 deletions

View File

@@ -3,7 +3,7 @@ import { useTranslate } from "@probo/i18n";
import { useLazyLoadQuery, graphql } from "react-relay";
import { useLocation } from "react-router";
import type { SnapshotBannerQuery } from "./__generated__/SnapshotBannerQuery.graphql";
import { getSnapshotTypeUrlPath, getSnapshotTypeLabel, sprintf } from "@probo/helpers";
import { getSnapshotTypeUrlPath, getSnapshotTypeLabel, sprintf, formatDate } from "@probo/helpers";
const snapshotQuery = graphql`
query SnapshotBannerQuery($snapshotId: ID!) {
@@ -28,7 +28,7 @@ type Props = {
};
export function SnapshotBanner({ snapshotId }: Props) {
const { __, dateFormat } = useTranslate();
const { __ } = useTranslate();
const location = useLocation();
const data = useLazyLoadQuery<SnapshotBannerQuery>(snapshotQuery, { snapshotId });
@@ -53,7 +53,7 @@ export function SnapshotBanner({ snapshotId }: Props) {
{sprintf(
__("You are viewing a %s snapshot from %s"),
getSnapshotTypeLabel(__, snapshot.type).toLocaleLowerCase(),
dateFormat(snapshot.createdAt, { year: "numeric", month: "short", day: "numeric" })
formatDate(snapshot.createdAt)
)}
</p>
</div>

View File

@@ -18,7 +18,7 @@ import { useTranslate } from "@probo/i18n";
import type { LinkedSnapshotsCardFragment$key } from "./__generated__/LinkedSnapshotsCardFragment.graphql";
import { useFragment } from "react-relay";
import { useMemo, useState } from "react";
import { sprintf, getSnapshotTypeLabel, getSnapshotTypeUrlPath } from "@probo/helpers";
import { sprintf, getSnapshotTypeLabel, getSnapshotTypeUrlPath, formatDate } from "@probo/helpers";
import { useOrganizationId } from "/hooks/useOrganizationId";
import { LinkedSnapshotsDialog } from "./LinkedSnapshotsDialog";
import clsx from "clsx";
@@ -165,7 +165,7 @@ function SnapshotRow(props: {
}) {
const snapshot = useFragment(linkedSnapshotFragment, props.snapshot);
const organizationId = useOrganizationId();
const { __, dateFormat } = useTranslate();
const { __ } = useTranslate();
const urlPath = getSnapshotTypeUrlPath(snapshot.type);
const snapshotUrl = `/organizations/${organizationId}/snapshots/${snapshot.id}${urlPath}`;
@@ -184,7 +184,7 @@ function SnapshotRow(props: {
</Td>
)}
<Td className="text-txt-tertiary">
{dateFormat(snapshot.createdAt, { year: "numeric", month: "short", day: "numeric" })}
{formatDate(snapshot.createdAt)}
</Td>
<Td noLink width={50} className="text-end">
<Button

View File

@@ -12,7 +12,7 @@ import {
IconTrashCan,
} from "@probo/ui";
import { useTranslate } from "@probo/i18n";
import { getSnapshotTypeLabel } from "@probo/helpers";
import { getSnapshotTypeLabel, formatDate } from "@probo/helpers";
import { Suspense, useMemo, useState, type ReactNode } from "react";
import { graphql } from "relay-runtime";
import { useLazyLoadQuery, usePaginationFragment } from "react-relay";
@@ -155,7 +155,7 @@ type RowProps = {
};
function SnapshotRow(props: RowProps) {
const { __, dateFormat } = useTranslate();
const { __ } = useTranslate();
const isLinked = props.linkedSnapshots.has(props.snapshot.id);
const onClick = isLinked ? props.onUnlink : props.onLink;
@@ -177,7 +177,7 @@ function SnapshotRow(props: RowProps) {
{props.snapshot.description || __("No description")}
</div>
<div className="text-sm text-txt-tertiary flex-shrink-0">
{dateFormat(props.snapshot.createdAt, { year: "numeric", month: "short", day: "numeric" })}
{formatDate(props.snapshot.createdAt)}
</div>
</div>
<Button

View File

@@ -16,7 +16,7 @@ import {
import { useTranslate } from "@probo/i18n";
import { useFragment } from "react-relay";
import { useMemo, useState } from "react";
import { sprintf, getAuditStateVariant, getAuditStateLabel } from "@probo/helpers";
import { sprintf, getAuditStateVariant, getAuditStateLabel, formatDate } from "@probo/helpers";
import { useOrganizationId } from "/hooks/useOrganizationId";
import clsx from "clsx";
import type { TrustCenterAuditsCardFragment$key } from "./__generated__/TrustCenterAuditsCardFragment.graphql";
@@ -131,7 +131,7 @@ function AuditRow(props: {
const { __ } = useTranslate();
const validUntilFormatted = audit.validUntil
? new Date(audit.validUntil).toLocaleDateString()
? formatDate(audit.validUntil)
: __("No expiry");
return (

View File

@@ -32,7 +32,7 @@ import { FrameworkLogo } from "/components/FrameworkLogo";
import { ControlledField } from "/components/form/ControlledField";
import { useFormWithSchema } from "/hooks/useFormWithSchema";
import z from "zod";
import { getAuditStateLabel, getAuditStateVariant, auditStates, fileSize, sprintf, formatDatetime } from "@probo/helpers";
import { getAuditStateLabel, getAuditStateVariant, auditStates, fileSize, sprintf, formatDatetime, formatDate } from "@probo/helpers";
import type { AuditGraphNodeQuery } from "/hooks/graph/__generated__/AuditGraphNodeQuery.graphql";
const updateAuditSchema = z.object({
@@ -49,7 +49,7 @@ type Props = {
export default function AuditDetailsPage(props: Props) {
const audit = usePreloadedQuery<AuditGraphNodeQuery>(auditNodeQuery, props.queryRef);
const auditEntry = audit.node;
const { __, dateFormat } = useTranslate();
const { __ } = useTranslate();
const organizationId = useOrganizationId();
if (!auditEntry || !auditEntry.id || !auditEntry.framework) {
@@ -209,7 +209,7 @@ export default function AuditDetailsPage(props: Props) {
{fileSize(__, auditEntry.report.size)}
</span>
<span>
{__("Uploaded")} {dateFormat(auditEntry.report.createdAt)}
{__("Uploaded")} {formatDate(auditEntry.report.createdAt)}
</span>
</div>
</div>

View File

@@ -25,7 +25,7 @@ import { CreateAuditDialog } from "./dialogs/CreateAuditDialog";
import { useDeleteAudit, auditsQuery } from "../../../hooks/graph/AuditGraph";
import type { AuditGraphListQuery } from "/hooks/graph/__generated__/AuditGraphListQuery.graphql";
import type { NodeOf } from "/types";
import { getAuditStateLabel, getAuditStateVariant } from "@probo/helpers";
import { getAuditStateLabel, getAuditStateVariant, formatDate } from "@probo/helpers";
import type {
AuditsPageFragment$data,
AuditsPageFragment$key,
@@ -141,7 +141,7 @@ function AuditRow({
connectionId: string;
}) {
const organizationId = useOrganizationId();
const { __, dateFormat } = useTranslate();
const { __ } = useTranslate();
const deleteAudit = useDeleteAudit(entry, connectionId);
return (
@@ -153,8 +153,8 @@ function AuditRow({
{getAuditStateLabel(__, entry.state)}
</Badge>
</Td>
<Td>{dateFormat(entry.validFrom, { year: "numeric", month: "short", day: "numeric" }) || __("Not set")}</Td>
<Td>{dateFormat(entry.validUntil, { year: "numeric", month: "short", day: "numeric" }) || __("Not set")}</Td>
<Td>{formatDate(entry.validFrom) || __("Not set")}</Td>
<Td>{formatDate(entry.validUntil) || __("Not set")}</Td>
<Td>
{entry.report ? (
<div className="flex flex-col">

View File

@@ -29,7 +29,7 @@ import { useOrganizationId } from "/hooks/useOrganizationId";
import { useParams } from "react-router";
import { CreateContinualImprovementDialog } from "./dialogs/CreateContinualImprovementDialog";
import { deleteContinualImprovementMutation, ContinualImprovementsConnectionKey } from "../../../hooks/graph/ContinualImprovementGraph";
import { sprintf, promisifyMutation, getStatusVariant, getStatusLabel } from "@probo/helpers";
import { sprintf, promisifyMutation, getStatusVariant, getStatusLabel, formatDate } from "@probo/helpers";
import { SnapshotBanner } from "/components/SnapshotBanner";
import type { NodeOf } from "/types";
import type { ContinualImprovementsPageQuery } from "./__generated__/ContinualImprovementsPageQuery.graphql";
@@ -214,9 +214,6 @@ function ImprovementRow({
const confirm = useConfirm();
const isSnapshotMode = Boolean(snapshotId);
const formatDate = (dateString: string) => {
return new Date(dateString).toLocaleDateString();
};
const handleDelete = () => {
confirm(

View File

@@ -45,7 +45,7 @@ import {
} from "@probo/ui";
import { useOrganizationId } from "/hooks/useOrganizationId";
import { useMutationWithToasts } from "/hooks/useMutationWithToasts";
import { getDocumentTypeLabel, sprintf, documentTypes } from "@probo/helpers";
import { getDocumentTypeLabel, sprintf, documentTypes, formatDate } from "@probo/helpers";
import {
Link,
Outlet,
@@ -195,7 +195,7 @@ export default function DocumentDetailPage(props: Props) {
documentFragment,
node
);
const { __, dateFormat } = useTranslate();
const { __ } = useTranslate();
const organizationId = useOrganizationId();
const navigate = useNavigate();
@@ -607,13 +607,13 @@ export default function DocumentDetailPage(props: Props) {
</PropertyRow>
<PropertyRow label={__("Last modified")}>
<div className="text-sm text-txt-secondary">
{dateFormat(currentVersion.updatedAt)}
{formatDate(currentVersion.updatedAt)}
</div>
</PropertyRow>
{currentVersion.publishedAt && (
<PropertyRow label={__("Published Date")}>
<div className="text-sm text-txt-secondary">
{dateFormat(currentVersion.publishedAt)}
{formatDate(currentVersion.publishedAt)}
</div>
</PropertyRow>
)}

View File

@@ -39,7 +39,7 @@ import {
} from "/hooks/graph/DocumentGraph";
import type { DocumentsPageListFragment$key } from "./__generated__/DocumentsPageListFragment.graphql";
import { useList, usePageTitle } from "@probo/hooks";
import { sprintf, getDocumentTypeLabel } from "@probo/helpers";
import { sprintf, getDocumentTypeLabel, formatDate } from "@probo/helpers";
import { CreateDocumentDialog } from "./dialogs/CreateDocumentDialog";
import type { DocumentsPageRowFragment$key } from "./__generated__/DocumentsPageRowFragment.graphql";
import { SortableTable, SortableTh } from "/components/SortableTable";
@@ -320,7 +320,7 @@ function DocumentRow({
}
const isDraft = lastVersion.status === "DRAFT";
const { __, dateFormat } = useTranslate();
const { __ } = useTranslate();
const signatures = lastVersion.signatures?.edges?.map((edge) => edge?.node)?.filter(Boolean) ?? [];
const signedCount = signatures.filter(
(signature) => signature.state === "SIGNED"
@@ -370,12 +370,7 @@ function DocumentRow({
</div>
</Td>
<Td className="w-60">
{dateFormat(document.updatedAt, {
year: "numeric",
month: "short",
day: "numeric",
weekday: "short",
})}
{formatDate(document.updatedAt)}
</Td>
<Td className="w-20">
{signedCount}/{signatures.length}

View File

@@ -26,7 +26,7 @@ import {
} from "react-relay";
import { SortableTable } from "/components/SortableTable";
import type { MeasureEvidencesTabFragment_evidence$key } from "./__generated__/MeasureEvidencesTabFragment_evidence.graphql";
import { fileSize, fileType, promisifyMutation, sprintf } from "@probo/helpers";
import { fileSize, fileType, promisifyMutation, sprintf, formatDate } from "@probo/helpers";
import { EvidencePreviewDialog } from "../dialog/EvidencePreviewDialog";
import { useOrganizationId } from "/hooks/useOrganizationId";
import { CreateEvidenceDialog } from "../dialog/CreateEvidenceDialog";
@@ -173,7 +173,7 @@ function EvidenceRow(props: {
snapshotId?: string;
}) {
const evidence = useFragment(evidenceFragment, props.evidenceKey);
const { __, dateFormat } = useTranslate();
const { __ } = useTranslate();
const [mutate, isDeleting] = useMutation(deleteEvidenceMutation);
const confirm = useConfirm();
@@ -229,7 +229,7 @@ function EvidenceRow(props: {
<Td>{evidence.filename}</Td>
<Td>{fileType(__, evidence)}</Td>
<Td>{fileSize(__, evidence.size)}</Td>
<Td>{dateFormat(evidence.createdAt)}</Td>
<Td>{formatDate(evidence.createdAt)}</Td>
<Td noLink>
{!props.hideActions && (
<div className="flex gap-2">

View File

@@ -28,7 +28,7 @@ import {
import { useOrganizationId } from "/hooks/useOrganizationId";
import { CreateNonconformityDialog } from "./dialogs/CreateNonconformityDialog";
import { deleteNonconformityMutation, NonconformitiesConnectionKey } from "../../../hooks/graph/NonconformityGraph";
import { sprintf, promisifyMutation, getStatusVariant, getStatusLabel } from "@probo/helpers";
import { sprintf, promisifyMutation, getStatusVariant, getStatusLabel, formatDate } from "@probo/helpers";
import { SnapshotBanner } from "/components/SnapshotBanner";
import { useParams } from "react-router";
import type { NonconformitiesPageQuery } from "./__generated__/NonconformitiesPageQuery.graphql";
@@ -220,9 +220,6 @@ function NonconformityRow({
const confirm = useConfirm();
const [deleteNonconformity] = useMutation(deleteNonconformityMutation);
const formatDate = (dateString: string) => {
return new Date(dateString).toLocaleDateString();
};
const nonconformityDetailUrl = isSnapshotMode
? `/organizations/${organizationId}/snapshots/${snapshotId}/nonconformities/${nonconformity.id}`

View File

@@ -28,7 +28,7 @@ import { useParams } from "react-router";
import { useOrganizationId } from "/hooks/useOrganizationId";
import { CreateObligationDialog } from "./dialogs/CreateObligationDialog";
import { deleteObligationMutation } from "../../../hooks/graph/ObligationGraph";
import { sprintf, promisifyMutation, getStatusVariant, getStatusLabel } from "@probo/helpers";
import { sprintf, promisifyMutation, getStatusVariant, getStatusLabel, formatDate } from "@probo/helpers";
import { SnapshotBanner } from "/components/SnapshotBanner";
import type { ObligationsPageQuery } from "./__generated__/ObligationsPageQuery.graphql";
import type {
@@ -205,9 +205,6 @@ function ObligationRow({
const confirm = useConfirm();
const isSnapshotMode = Boolean(snapshotId);
const formatDate = (dateString: string) => {
return new Date(dateString).toLocaleDateString();
};
const handleDelete = () => {
confirm(

View File

@@ -5,7 +5,7 @@ import {
type PreloadedQuery,
} from "react-relay";
import { useTranslate } from "@probo/i18n";
import { getSnapshotTypeLabel, getSnapshotTypeUrlPath } from "@probo/helpers";
import { getSnapshotTypeLabel, getSnapshotTypeUrlPath, formatDate } from "@probo/helpers";
import {
ActionDropdown,
Badge,
@@ -129,7 +129,7 @@ type SnapshotRowProps = {
};
function SnapshotRow(props: SnapshotRowProps) {
const { __, dateFormat } = useTranslate();
const { __ } = useTranslate();
const deleteSnapshot = useDeleteSnapshot(props.snapshot, props.connectionId);
const typePath = getSnapshotTypeUrlPath(props.snapshot.type);
@@ -146,7 +146,7 @@ function SnapshotRow(props: SnapshotRowProps) {
{props.snapshot.description || __("No description")}
</Td>
<Td className="text-txt-tertiary">
{dateFormat(props.snapshot.createdAt, { year: "numeric", month: "short", day: "numeric" })}
{formatDate(props.snapshot.createdAt)}
</Td>
<Td noLink width={50} className="text-end">
<ActionDropdown>

View File

@@ -19,6 +19,7 @@ import {
IconCheckmark1,
} from "@probo/ui";
import { useTranslate } from "@probo/i18n";
import { formatDate } from "@probo/helpers";
import { useOutletContext } from "react-router";
import { useState, useCallback } from "react";
import z from "zod";
@@ -87,8 +88,8 @@ export default function TrustCenterAccessTab() {
name: string;
active: boolean;
hasAcceptedNonDisclosureAgreement: boolean;
createdAt: Date;
};
createdAt: string;
};
const trustCenterData = useTrustCenterAccesses(organization.trustCenter?.id || "");
@@ -98,7 +99,7 @@ export default function TrustCenterAccessTab() {
name: edge.node.name,
active: edge.node.active,
hasAcceptedNonDisclosureAgreement: edge.node.hasAcceptedNonDisclosureAgreement,
createdAt: new Date(edge.node.createdAt)
createdAt: edge.node.createdAt
})) ?? [];
const handleInvite = inviteForm.handleSubmit(async (data) => {
@@ -216,7 +217,7 @@ export default function TrustCenterAccessTab() {
<Td className="font-medium">{access.name}</Td>
<Td>{access.email}</Td>
<Td>
{access.createdAt.toLocaleDateString()}
{formatDate(access.createdAt)}
</Td>
<Td>
<Checkbox

View File

@@ -22,7 +22,7 @@ import {
} from "react-relay";
import { useOrganizationId } from "/hooks/useOrganizationId";
import { useParams } from "react-router";
import { faviconUrl } from "@probo/helpers";
import { faviconUrl, formatDate } from "@probo/helpers";
import type { NodeOf } from "/types";
import { CreateVendorDialog } from "./dialogs/CreateVendorDialog";
import {
@@ -115,7 +115,7 @@ function VendorRow({
}) {
const { snapshotId } = useParams<{ snapshotId?: string }>();
const isSnapshotMode = Boolean(snapshotId);
const { __, dateFormat } = useTranslate();
const { __ } = useTranslate();
const latestAssessment = vendor.riskAssessments?.edges[0]?.node;
const deleteVendor = useDeleteVendor(vendor, connectionId);
@@ -135,11 +135,7 @@ function VendorRow({
</Td>
<Td>
{latestAssessment?.assessedAt
? dateFormat(latestAssessment.assessedAt, {
day: "2-digit",
weekday: "short",
month: "short",
})
? formatDate(latestAssessment.assessedAt)
: __("Not assessed")}
</Td>
<Td>

View File

@@ -18,7 +18,7 @@ import {
import { useFragment, useMutation, useRefetchableFragment } from "react-relay";
import type { VendorComplianceTabFragment_report$key } from "./__generated__/VendorComplianceTabFragment_report.graphql";
import { useMutationWithToasts } from "/hooks/useMutationWithToasts";
import { sprintf, fileSize } from "@probo/helpers";
import { sprintf, fileSize, formatDate } from "@probo/helpers";
import { SortableTable, SortableTh } from "/components/SortableTable";
export const complianceReportsFragment = graphql`
@@ -175,7 +175,6 @@ function ReportRow(props: ReportRowProps) {
complianceReportFragment,
props.reportKey
);
const { dateFormat } = useTranslate();
const confirm = useConfirm();
const [deleteReport] = useMutationWithToasts(deleteReportMutation, {
successMessage: __("Report deleted successfully"),
@@ -207,8 +206,8 @@ function ReportRow(props: ReportRowProps) {
return (
<Tr>
<Td>{report.reportName}</Td>
<Td>{dateFormat(report.reportDate)}</Td>
<Td>{dateFormat(report.validUntil)}</Td>
<Td>{formatDate(report.reportDate)}</Td>
<Td>{formatDate(report.validUntil)}</Td>
<Td>{fileSize(__, report.fileSize)}</Td>
{!props.isSnapshotMode && (
<Td width={50} className="text-end">

View File

@@ -8,7 +8,7 @@ import { CountriesField } from "/components/form/CountriesField";
import { useOrganizationId } from "/hooks/useOrganizationId";
import { useMemo } from "react";
import { usePageTitle } from "@probo/hooks";
import { downloadFile } from "@probo/helpers";
import { downloadFile, formatDate } from "@probo/helpers";
import { useFragment, graphql } from "react-relay";
import { UploadBusinessAssociateAgreementDialog } from "../dialogs/UploadBusinessAssociateAgreementDialog";
import { DeleteBusinessAssociateAgreementDialog } from "../dialogs/DeleteBusinessAssociateAgreementDialog";
@@ -268,10 +268,10 @@ export default function VendorOverviewTab() {
<p className="text-xs text-txt-secondary mt-1">
{__("Valid")}
{businessAssociateAgreement.validFrom &&
` ${__("from")} ${new Date(businessAssociateAgreement.validFrom).toLocaleDateString()}`
` ${__("from")} ${formatDate(businessAssociateAgreement.validFrom)}`
}
{businessAssociateAgreement.validUntil &&
` ${__("until")} ${new Date(businessAssociateAgreement.validUntil).toLocaleDateString()}`
` ${__("until")} ${formatDate(businessAssociateAgreement.validUntil)}`
}
</p>
)}
@@ -335,10 +335,10 @@ export default function VendorOverviewTab() {
<p className="text-xs text-txt-secondary mt-1">
{__("Valid")}
{dataPrivacyAgreement.validFrom &&
` ${__("from")} ${new Date(dataPrivacyAgreement.validFrom).toLocaleDateString()}`
` ${__("from")} ${formatDate(dataPrivacyAgreement.validFrom)}`
}
{dataPrivacyAgreement.validUntil &&
` ${__("until")} ${new Date(dataPrivacyAgreement.validUntil).toLocaleDateString()}`
` ${__("until")} ${formatDate(dataPrivacyAgreement.validUntil)}`
}
</p>
)}

View File

@@ -2,3 +2,22 @@ export function formatDatetime(dateString?: string | null): string | undefined {
if (!dateString) return undefined;
return `${dateString}T00:00:00Z`;
}
export function formatDate(dateInput?: string | null): string {
if (!dateInput) return '';
const date = parseDate(dateInput);
return date.toLocaleDateString();
}
function parseDate(dateString: string): Date {
if (dateString.includes('T')) {
return new Date(dateString);
}
const parts = dateString.split('-');
return new Date(
parseInt(parts[0], 10),
parts[1] ? parseInt(parts[1], 10) - 1 : 0,
parts[2] ? parseInt(parts[2], 10) : 1
);
}

View File

@@ -21,4 +21,4 @@ export { getAuditStateLabel, getAuditStateVariant, auditStates } from "./audits"
export { getStatusVariant, getStatusLabel, getStatusOptions } from "./registryStatus";
export { promisifyMutation } from "./relay";
export { fileType, fileSize } from "./file";
export { formatDatetime } from "./date";
export { formatDatetime, formatDate } from "./date";