Adjust UI to account for fuzzy dates

This commit is contained in:
WithoutPants 2025-12-02 18:45:55 +11:00
parent 71587b9dc9
commit 45c1c0b008
10 changed files with 93 additions and 30 deletions

View file

@ -6,7 +6,7 @@ import {
RouteComponentProps, RouteComponentProps,
Redirect, Redirect,
} from "react-router-dom"; } from "react-router-dom";
import { FormattedDate, FormattedMessage, useIntl } from "react-intl"; import { FormattedMessage, useIntl } from "react-intl";
import { Helmet } from "react-helmet"; import { Helmet } from "react-helmet";
import * as GQL from "src/core/generated-graphql"; import * as GQL from "src/core/generated-graphql";
import { import {
@ -44,6 +44,7 @@ import { useRatingKeybinds } from "src/hooks/keybinds";
import { useConfigurationContext } from "src/hooks/Config"; import { useConfigurationContext } from "src/hooks/Config";
import { TruncatedText } from "src/components/Shared/TruncatedText"; import { TruncatedText } from "src/components/Shared/TruncatedText";
import { goBackOrReplace } from "src/utils/history"; import { goBackOrReplace } from "src/utils/history";
import { FormattedDate } from "src/components/Shared/Date";
interface IProps { interface IProps {
gallery: GQL.GalleryDataFragment; gallery: GQL.GalleryDataFragment;
@ -410,11 +411,7 @@ export const GalleryPage: React.FC<IProps> = ({ gallery, add }) => {
<div className="gallery-subheader"> <div className="gallery-subheader">
{!!gallery.date && ( {!!gallery.date && (
<span className="date" data-value={gallery.date}> <span className="date" data-value={gallery.date}>
<FormattedDate <FormattedDate value={gallery.date} />
value={gallery.date}
format="long"
timeZone="utc"
/>
</span> </span>
)} )}
</div> </div>

View file

@ -102,7 +102,7 @@ const GalleryWallCard: React.FC<IProps> = ({ gallery }) => {
)} )}
<TruncatedText text={performers.join(", ")} /> <TruncatedText text={performers.join(", ")} />
<div> <div>
{gallery.date && TextUtils.formatDate(intl, gallery.date)} {gallery.date && TextUtils.formatFuzzyDate(intl, gallery.date)}
</div> </div>
</Link> </Link>
</footer> </footer>

View file

@ -65,7 +65,7 @@ export const GroupDetailsPanel: React.FC<IGroupDetailsPanel> = ({
/> />
<DetailItem <DetailItem
id="date" id="date"
value={group.date ? TextUtils.formatDate(intl, group.date) : ""} value={group.date ? TextUtils.formatFuzzyDate(intl, group.date) : ""}
fullWidth={fullWidth} fullWidth={fullWidth}
/> />
<DetailItem <DetailItem

View file

@ -1,6 +1,6 @@
import { Tab, Nav, Dropdown } from "react-bootstrap"; import { Tab, Nav, Dropdown } from "react-bootstrap";
import React, { useEffect, useMemo, useState } from "react"; import React, { useEffect, useMemo, useState } from "react";
import { FormattedDate, FormattedMessage, useIntl } from "react-intl"; import { FormattedMessage, useIntl } from "react-intl";
import { useHistory, Link, RouteComponentProps } from "react-router-dom"; import { useHistory, Link, RouteComponentProps } from "react-router-dom";
import { Helmet } from "react-helmet"; import { Helmet } from "react-helmet";
import { import {
@ -35,6 +35,7 @@ import { RatingSystem } from "src/components/Shared/Rating/RatingSystem";
import cx from "classnames"; import cx from "classnames";
import { TruncatedText } from "src/components/Shared/TruncatedText"; import { TruncatedText } from "src/components/Shared/TruncatedText";
import { goBackOrReplace } from "src/utils/history"; import { goBackOrReplace } from "src/utils/history";
import { FormattedDate } from "src/components/Shared/Date";
interface IProps { interface IProps {
image: GQL.ImageDataFragment; image: GQL.ImageDataFragment;
@ -319,13 +320,7 @@ const ImagePage: React.FC<IProps> = ({ image }) => {
<div className="image-subheader"> <div className="image-subheader">
<span className="date" data-value={image.date}> <span className="date" data-value={image.date}>
{!!image.date && ( {!!image.date && <FormattedDate value={image.date} />}
<FormattedDate
value={image.date}
format="long"
timeZone="utc"
/>
)}
</span> </span>
{resolution ? ( {resolution ? (
<span className="resolution" data-value={resolution}> <span className="resolution" data-value={resolution}>

View file

@ -89,7 +89,10 @@ export const PerformerDetailsPanel: React.FC<IPerformerDetails> =
} }
title={ title={
!fullWidth !fullWidth
? TextUtils.formatDate(intl, performer.birthdate ?? undefined) ? TextUtils.formatFuzzyDate(
intl,
performer.birthdate ?? undefined
)
: "" : ""
} }
fullWidth={fullWidth} fullWidth={fullWidth}
@ -218,7 +221,7 @@ export const CompressedPerformerDetailsPanel: React.FC<IPerformerDetails> =
<span className="detail-divider">/</span> <span className="detail-divider">/</span>
<span <span
className="performer-age" className="performer-age"
title={TextUtils.formatDate( title={TextUtils.formatFuzzyDate(
intl, intl,
performer.birthdate ?? undefined performer.birthdate ?? undefined
)} )}

View file

@ -116,7 +116,7 @@ export const PerformerListTable: React.FC<IPerformerListTableProps> = (
<span <span
title={ title={
performer.birthdate performer.birthdate
? TextUtils.formatDate(intl, performer.birthdate ?? undefined) ? TextUtils.formatFuzzyDate(intl, performer.birthdate ?? undefined)
: "" : ""
} }
> >

View file

@ -6,7 +6,7 @@ import React, {
useRef, useRef,
useLayoutEffect, useLayoutEffect,
} from "react"; } from "react";
import { FormattedDate, FormattedMessage, useIntl } from "react-intl"; import { FormattedMessage, useIntl } from "react-intl";
import { Link, RouteComponentProps } from "react-router-dom"; import { Link, RouteComponentProps } from "react-router-dom";
import { Helmet } from "react-helmet"; import { Helmet } from "react-helmet";
import * as GQL from "src/core/generated-graphql"; import * as GQL from "src/core/generated-graphql";
@ -51,6 +51,7 @@ import cx from "classnames";
import { TruncatedText } from "src/components/Shared/TruncatedText"; import { TruncatedText } from "src/components/Shared/TruncatedText";
import { PatchComponent, PatchContainerComponent } from "src/patch"; import { PatchComponent, PatchContainerComponent } from "src/patch";
import { goBackOrReplace } from "src/utils/history"; import { goBackOrReplace } from "src/utils/history";
import { FormattedDate } from "src/components/Shared/Date";
const SubmitStashBoxDraft = lazyComponent( const SubmitStashBoxDraft = lazyComponent(
() => import("src/components/Dialogs/SubmitDraft") () => import("src/components/Dialogs/SubmitDraft")
@ -613,13 +614,7 @@ const ScenePage: React.FC<IProps> = PatchComponent("ScenePage", (props) => {
<div className="scene-subheader"> <div className="scene-subheader">
<span className="date" data-value={scene.date}> <span className="date" data-value={scene.date}>
{!!scene.date && ( {!!scene.date && <FormattedDate value={scene.date} />}
<FormattedDate
value={scene.date}
format="long"
timeZone="utc"
/>
)}
</span> </span>
<VideoFrameRateResolution <VideoFrameRateResolution
width={file?.width} width={file?.width}

View file

@ -107,7 +107,9 @@ export const SceneWallItem: React.FC<
/> />
)} )}
<TruncatedText text={performers.join(", ")} /> <TruncatedText text={performers.join(", ")} />
<div>{scene.date && TextUtils.formatDate(intl, scene.date)}</div> <div>
{scene.date && TextUtils.formatFuzzyDate(intl, scene.date)}
</div>
</Link> </Link>
</footer> </footer>
</div> </div>

View file

@ -0,0 +1,36 @@
import React from "react";
import { FormattedDate as IntlDate } from "react-intl";
import { PatchComponent } from "src/patch";
// wraps FormattedDate to handle year or year/month dates
export const FormattedDate: React.FC<{
value: string | number | Date | undefined;
}> = PatchComponent("Date", ({ value }) => {
if (typeof value === "string") {
// try parsing as year or year/month
const yearMatch = value.match(/^(\d{4})$/);
if (yearMatch) {
const year = parseInt(yearMatch[1], 10);
return (
<IntlDate value={Date.UTC(year, 0)} year="numeric" timeZone="utc" />
);
}
const yearMonthMatch = value.match(/^(\d{4})-(\d{2})$/);
if (yearMonthMatch) {
const year = parseInt(yearMonthMatch[1], 10);
const month = parseInt(yearMonthMatch[2], 10) - 1;
return (
<IntlDate
value={Date.UTC(year, month)}
year="numeric"
month="long"
timeZone="utc"
/>
);
}
}
return <IntlDate value={value} format="long" timeZone="utc" />;
});

View file

@ -336,8 +336,10 @@ function dateTimeToString(date: Date) {
const getAge = (dateString?: string | null, fromDateString?: string | null) => { const getAge = (dateString?: string | null, fromDateString?: string | null) => {
if (!dateString) return 0; if (!dateString) return 0;
const birthdate = stringToDate(dateString); const birthdate = stringToFuzzyDate(dateString);
const fromDate = fromDateString ? stringToDate(fromDateString) : new Date(); const fromDate = fromDateString
? stringToFuzzyDate(fromDateString)
: new Date();
if (!birthdate || !fromDate) return 0; if (!birthdate || !fromDate) return 0;
@ -459,6 +461,38 @@ const formatDate = (intl: IntlShape, date?: string, utc = true) => {
}); });
}; };
const formatFuzzyDate = (intl: IntlShape, date?: string, utc = true) => {
if (!date) {
return "";
}
// handle year or year/month dates
const yearMatch = date.match(/^(\d{4})$/);
if (yearMatch) {
const year = parseInt(yearMatch[1], 10);
return intl.formatDate(Date.UTC(year, 0), {
year: "numeric",
timeZone: utc ? "utc" : undefined,
});
}
const yearMonthMatch = date.match(/^(\d{4})-(\d{2})$/);
if (yearMonthMatch) {
const year = parseInt(yearMonthMatch[1], 10);
const month = parseInt(yearMonthMatch[2], 10) - 1;
return intl.formatDate(Date.UTC(year, month), {
year: "numeric",
month: "long",
timeZone: utc ? "utc" : undefined,
});
}
return intl.formatDate(date, {
format: "long",
timeZone: utc ? "utc" : undefined,
});
};
const formatDateTime = (intl: IntlShape, dateTime?: string, utc = false) => const formatDateTime = (intl: IntlShape, dateTime?: string, utc = false) =>
`${formatDate(intl, dateTime, utc)} ${intl.formatTime(dateTime, { `${formatDate(intl, dateTime, utc)} ${intl.formatTime(dateTime, {
timeZone: utc ? "utc" : undefined, timeZone: utc ? "utc" : undefined,
@ -519,6 +553,7 @@ const TextUtils = {
sanitiseURL, sanitiseURL,
domainFromURL, domainFromURL,
formatDate, formatDate,
formatFuzzyDate,
formatDateTime, formatDateTime,
secondsAsTimeString, secondsAsTimeString,
abbreviateCounter, abbreviateCounter,