-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
- ) : null}
-
+
+ ) : null;
+
+ return (
+
+ {children}
+
);
}
@@ -155,21 +129,6 @@ export function CatalogNoData(): ReactElement {
return
No data available.
;
}
-export function Field({
- label,
- children,
-}: {
- label: ReactNode;
- children: ReactNode;
-}): ReactElement {
- return (
- <>
-
{label}
-
{children}
- >
- );
-}
-
export function CatalogDetailSection({
title,
children,
diff --git a/src/components/ui/Card.tsx b/src/components/ui/Card.tsx
new file mode 100644
index 0000000..b6b2fce
--- /dev/null
+++ b/src/components/ui/Card.tsx
@@ -0,0 +1,87 @@
+import classNames from "classnames";
+import { ReactElement, ReactNode } from "react";
+import { useAnchoredElement } from "../../hooks/useAnchoredElement";
+import { CardActionsMenu, CardAction } from "./CardActionsMenu";
+import { CardAnchorLink } from "./CardAnchorLink";
+
+export type { CardAction };
+
+export function Card({
+ title,
+ children,
+ actions,
+ headerControls,
+ afterChildren,
+ anchorId,
+ className,
+ variant = "fields",
+}: {
+ title: string;
+ children: ReactNode;
+ actions?: CardAction[];
+ headerControls?: ReactNode;
+ afterChildren?: ReactNode;
+ anchorId?: string;
+ className?: string;
+ variant?: "fields" | "block";
+}): ReactElement {
+ const { ref, highlighted } = useAnchoredElement(anchorId ?? "");
+ const cardActions = actions ?? [];
+ const hasActions = cardActions.length > 0;
+ const hasHeaderControls =
+ hasActions || Boolean(headerControls) || Boolean(anchorId);
+
+ return (
+
+
+
+ {title}
+ {anchorId && }
+
+ {hasHeaderControls ? (
+
+ {headerControls}
+ {hasActions ? : null}
+
+ ) : null}
+
+ {variant === "fields" ? (
+
+ {children}
+
+ ) : (
+ children
+ )}
+ {afterChildren}
+
+ );
+}
+
+export function Field({
+ label,
+ children,
+}: {
+ label: ReactNode;
+ children: ReactNode;
+}): ReactElement {
+ return (
+ <>
+
{label}
+
{children}
+ >
+ );
+}
diff --git a/src/components/ui/CardActionsMenu.tsx b/src/components/ui/CardActionsMenu.tsx
index 97a56e3..4e41f64 100644
--- a/src/components/ui/CardActionsMenu.tsx
+++ b/src/components/ui/CardActionsMenu.tsx
@@ -4,18 +4,18 @@ import { MdMoreVert } from "react-icons/md";
import classNames from "classnames";
import { Button } from "../core/Button";
-type CatalogCardActionCommon = {
+type CardActionCommon = {
title: string;
description?: string;
icon?: IconType;
};
-export type CatalogCardAction =
- | (CatalogCardActionCommon & { href: string; onClick?: never })
- | (CatalogCardActionCommon & { onClick: () => void; href?: never });
+export type CardAction =
+ | (CardActionCommon & { href: string; onClick?: never })
+ | (CardActionCommon & { onClick: () => void; href?: never });
interface CardActionsMenuProps {
- actions: CatalogCardAction[];
+ actions: CardAction[];
}
const menuItemClassName =
@@ -24,7 +24,7 @@ const menuItemClassName =
function ActionMenuItemContent({
action,
}: {
- action: CatalogCardAction;
+ action: CardAction;
}): ReactElement {
const Icon = action.icon;
@@ -77,7 +77,7 @@ export function CardActionsMenu({
return () => document.removeEventListener("mousedown", handlePointerDown);
}, [open]);
- function runAction(action: CatalogCardAction): void {
+ function runAction(action: CardAction): void {
if ("onClick" in action && action.onClick) {
action.onClick();
}
diff --git a/src/pages/TableDetails.tsx b/src/pages/TableDetails.tsx
index 7701c81..8c9f8fa 100644
--- a/src/pages/TableDetails.tsx
+++ b/src/pages/TableDetails.tsx
@@ -1,10 +1,10 @@
+import classNames from "classnames";
import { KeyboardEvent, ReactElement, useEffect, useState } from "react";
import {
Bibliography,
- CrossmatchTriageStatus,
DataType,
GetTableResponse,
- TableCrossmatchResultStatus,
+ TableProgress,
} from "../clients/admin/types.gen";
import { getTable, patchTable } from "../clients/admin/sdk.gen";
import { useNavigate, useParams } from "react-router-dom";
@@ -14,12 +14,13 @@ import {
Column,
CommonTable,
} from "../components/ui/CommonTable";
-import { Button } from "../components/core/Button";
import { CopyButton } from "../components/ui/CopyButton";
-import { Badge, BadgeType } from "../components/ui/Badge";
+import { Badge } from "../components/ui/Badge";
import { Link } from "../components/core/Link";
import { Loading } from "../components/core/Loading";
+import { Card, CardAction, Field } from "../components/ui/Card";
import { ErrorPage } from "../components/ui/ErrorPage";
+import { Hint } from "../components/ui/Hint";
import { useDataFetching } from "../hooks/useDataFetching";
import { adminClient } from "../clients/config";
import { isLoggedIn } from "../auth/token";
@@ -44,36 +45,36 @@ function asDataType(value: unknown): DataType {
}
function renderBibliography(bib: Bibliography): ReactElement {
- let authors = "";
-
- if (bib.authors.length >= 1) {
- authors += bib.authors[0];
- }
- if (bib.authors.length >= 2) {
- authors += " et al.";
- }
-
- authors += ` ${bib.year}`;
-
- const targetLink =
- "https://ui.adsabs.harvard.edu/abs/" + bib.bibcode + "/abstract";
+ const targetLink = `https://ui.adsabs.harvard.edu/abs/${bib.bibcode}/abstract`;
return (
-
-
-
- {bib.bibcode}
- {" "}
- | {authors}: "{bib.title}"
-
-
+
+
+ {bib.bibcode}
+
+
+ "{bib.title}". {bib.authors.join(", ")}. {bib.year}
+
+ }
+ className="gap-1"
+ />
+
);
}
function renderTime(time: string): string {
- const dt = new Date(time as string);
-
- return dt.toString();
+ const dt = new Date(time);
+
+ return dt.toLocaleString(undefined, {
+ year: "numeric",
+ month: "short",
+ day: "numeric",
+ hour: "numeric",
+ minute: "2-digit",
+ timeZoneName: "short",
+ });
}
function renderUCD(ucd: CellPrimitive): ReactElement {
@@ -106,6 +107,7 @@ interface TableMetaProps {
tableName: string;
table: GetTableResponse;
onAfterPatch: () => void;
+ className?: string;
}
function TableMeta(props: TableMetaProps): ReactElement {
@@ -113,7 +115,8 @@ function TableMeta(props: TableMetaProps): ReactElement {
const canEdit = isLoggedIn();
const [editingName, setEditingName] = useState(false);
const [editingDescription, setEditingDescription] = useState(false);
- const showEditPencils = canEdit && !editingName && !editingDescription;
+ const showNameEdit = canEdit && !editingDescription;
+ const showSlugEdit = canEdit && !editingName;
const [draftName, setDraftName] = useState(props.tableName);
const [draftDescription, setDraftDescription] = useState(
props.table.description,
@@ -252,9 +255,7 @@ function TableMeta(props: TableMetaProps): ReactElement {
);
}
- const columns = [{ name: "Parameter" }, { name: "Value" }];
-
- const datatypeValue: CellPrimitive = canEdit ? (
+ const datatypeControl = canEdit ? (