diff --git a/src/components/ui/astronomy.tsx b/src/components/ui/astronomy.tsx new file mode 100644 index 0000000..bc1bc43 --- /dev/null +++ b/src/components/ui/astronomy.tsx @@ -0,0 +1,101 @@ +import React, { ReactElement, ReactNode } from "react"; + +interface QuantityProps { + value: string | number; + unit?: string; + className?: string; + spaced?: boolean; +} + +export function Quantity({ + value, + unit, + className, + spaced = true, +}: QuantityProps): React.ReactElement { + return ( + + {value} + {unit ? ( + + {spaced ? " " : ""} + {unit} + + ) : ( + "" + )} + + ); +} + +interface QuantityWithErrorProps { + children: ReactNode; + error: number; + unit?: string; + decimalPlaces?: number; +} + +export function QuantityWithError({ + children, + error, + unit, + decimalPlaces = 2, +}: QuantityWithErrorProps): ReactElement { + return ( +
+ {children} ± +
+ ); +} + +interface AstronomicalCoordinateProps { + value: number; + className?: string; +} + +export function RightAscension({ + value, + className, +}: AstronomicalCoordinateProps): React.ReactElement { + if (isNaN(value)) { + return N/A; + } + + const totalSeconds = value * 240; + const hours = Math.floor(totalSeconds / 3600); + const minutes = Math.floor((totalSeconds % 3600) / 60); + const seconds = +(totalSeconds % 60).toFixed(2); + + return ( + + {" "} + {" "} + + + ); +} + +export function Declination({ + value, + className, +}: AstronomicalCoordinateProps): React.ReactElement { + if (isNaN(value)) { + return N/A; + } + + const sign = value < 0 ? "-" : "+"; + const absDec = Math.abs(value); + const degrees = Math.floor(absDec); + const minutesFloat = (absDec - degrees) * 60; + const minutes = Math.floor(minutesFloat); + const seconds = +(minutesFloat - minutes) * 60; + + return ( + + {sign} + {" "} + {" "} + + + ); +} diff --git a/src/components/ui/catalog-data.tsx b/src/components/ui/catalog-data.tsx index f66134e..ee2c0a4 100644 --- a/src/components/ui/catalog-data.tsx +++ b/src/components/ui/catalog-data.tsx @@ -1,37 +1,25 @@ import { ReactElement } from "react"; -import { CommonTable } from "./common-table"; +import { CommonTable, Column } from "./common-table"; import { Catalogs, Schema } from "../../clients/backend/types.gen"; +import { + Declination, + RightAscension, + Quantity, + QuantityWithError, +} from "./astronomy"; interface CatalogDataProps { catalogs: Catalogs; schema: Schema; } -function formatValueWithError( - value: number | undefined, - error: number | undefined, - unit?: string, - decimalPlaces: number = 0, -): string { - if (value === undefined) return "NULL"; - const formattedValue = value.toFixed(decimalPlaces); - const formattedError = - error?.toFixed(decimalPlaces) || "0".padEnd(decimalPlaces + 1, "0"); - - if (!unit) { - return `${formattedValue} ± ${formattedError}`; - } - - return `${formattedValue} ${unit} ± ${formattedError} ${unit}`; -} - export function CatalogData({ catalogs, schema, }: CatalogDataProps): ReactElement { if (!catalogs) return
; - const columns = [{ name: "Parameter" }, { name: "Value" }]; + const columns: Column[] = [{ name: "Parameter" }, { name: "Value" }]; const data = []; @@ -43,41 +31,61 @@ export function CatalogData({ } if (catalogs?.coordinates) { - data.push( - { + if (catalogs.coordinates.equatorial?.ra !== undefined) { + data.push({ Parameter: "Equatorial RA", - Value: formatValueWithError( - catalogs.coordinates.equatorial?.ra, - catalogs.coordinates.equatorial?.e_ra, - schema.units.coordinates?.equatorial?.ra, - 2, + Value: ( + + + ), - }, - { + }); + } + + if (catalogs.coordinates.equatorial?.dec !== undefined) { + data.push({ Parameter: "Equatorial Dec", - Value: formatValueWithError( - catalogs.coordinates.equatorial?.dec, - catalogs.coordinates.equatorial?.e_dec, - schema.units.coordinates?.equatorial?.dec, - 2, + Value: ( + + + ), - }, + }); + } + + data.push( { Parameter: "Galactic l", - Value: formatValueWithError( - catalogs.coordinates.galactic?.lon, - catalogs.coordinates.galactic?.e_lon, - schema.units.coordinates?.galactic?.lon, - 2, + Value: ( + + + ), }, { Parameter: "Galactic b", - Value: formatValueWithError( - catalogs.coordinates.galactic?.lat, - catalogs.coordinates.galactic?.e_lat, - schema.units.coordinates?.galactic?.lat, - 2, + Value: ( + + + ), }, ); @@ -86,11 +94,10 @@ export function CatalogData({ if (catalogs?.redshift) { data.push({ Parameter: "Redshift z", - Value: formatValueWithError( - catalogs.redshift.z, - catalogs.redshift.e_z, - undefined, - 5, + Value: ( + + {catalogs.redshift.z?.toFixed(5) || "N/A"} + ), }); } @@ -99,34 +106,58 @@ export function CatalogData({ data.push( { Parameter: "Heliocentric Velocity", - Value: formatValueWithError( - catalogs.velocity.heliocentric?.v, - catalogs.velocity.heliocentric?.e_v, - schema.units.velocity?.heliocentric?.v, + Value: ( + + + ), }, { Parameter: "Local Group Velocity", - Value: formatValueWithError( - catalogs.velocity.local_group?.v, - catalogs.velocity.local_group?.e_v, - schema.units.velocity?.local_group?.v, + Value: ( + + + ), }, { Parameter: "CMB (old) Velocity", - Value: formatValueWithError( - catalogs.velocity.cmb_old?.v, - catalogs.velocity.cmb_old?.e_v, - schema.units.velocity?.cmb_old?.v, + Value: ( + + + ), }, { Parameter: "CMB Velocity", - Value: formatValueWithError( - catalogs.velocity.cmb?.v, - catalogs.velocity.cmb?.e_v, - schema.units.velocity?.cmb?.v, + Value: ( + + + ), }, ); diff --git a/src/components/ui/common-table.tsx b/src/components/ui/common-table.tsx index b9a942e..4ed1ba8 100644 --- a/src/components/ui/common-table.tsx +++ b/src/components/ui/common-table.tsx @@ -1,4 +1,4 @@ -import React, { ReactElement } from "react"; +import React, { ReactElement, ReactNode } from "react"; import classNames from "classnames"; import { Hint } from "./hint"; @@ -6,7 +6,7 @@ export type CellPrimitive = ReactElement | string | number; export interface Column { name: string; - renderCell?: (value: CellPrimitive) => ReactElement; + renderCell?: (value: CellPrimitive) => ReactNode; hint?: ReactElement; } @@ -33,7 +33,7 @@ export function CommonTable({ children, onRowClick, }: CommonTableProps): ReactElement { - function renderCell(value: CellPrimitive, column: Column): ReactElement { + function renderCell(value: CellPrimitive, column: Column): ReactNode { if (column.renderCell) { return column.renderCell(value); } diff --git a/src/components/ui/pagination.tsx b/src/components/ui/pagination.tsx new file mode 100644 index 0000000..029036f --- /dev/null +++ b/src/components/ui/pagination.tsx @@ -0,0 +1,33 @@ +import { ReactElement } from "react"; +import { Button } from "./button"; + +type PaginationProps = { + page: number; + pageSize: number; + records: unknown[]; + handlePageChange: (newPage: number) => void; +}; + +export function Pagination({ + page, + pageSize, + records, + handlePageChange, +}: PaginationProps): ReactElement { + return ( +
+ + + Page {page + 1} (showing {records.length} records) + + +
+ ); +} diff --git a/src/pages/CrossmatchResults.tsx b/src/pages/CrossmatchResults.tsx index 97e2c11..44f956e 100644 --- a/src/pages/CrossmatchResults.tsx +++ b/src/pages/CrossmatchResults.tsx @@ -21,6 +21,7 @@ import { Loading } from "../components/ui/loading"; import { ErrorPage } from "../components/ui/error-page"; import { Link } from "../components/ui/link"; import { useDataFetching } from "../hooks/useDataFetching"; +import { Pagination } from "../components/ui/pagination"; interface CrossmatchFiltersProps { tableName: string | null; @@ -244,27 +245,17 @@ export function CrossmatchResultsPage(): ReactElement { function Content(): ReactElement { if (loading) return ; if (error) return ; + if (!data?.records) return ; return ( <> -
- - - Page {page + 1} (showing {data?.records.length} records) - - -
+ ); } diff --git a/src/pages/SearchResults.tsx b/src/pages/SearchResults.tsx index f3a8b98..936bb8b 100644 --- a/src/pages/SearchResults.tsx +++ b/src/pages/SearchResults.tsx @@ -11,6 +11,9 @@ import { ErrorPage, ErrorPageHomeButton } from "../components/ui/error-page"; import { useDataFetching } from "../hooks/useDataFetching"; import { querySimpleApiV1QuerySimpleGet } from "../clients/backend/sdk.gen"; import { QuerySimpleResponse } from "../clients/backend/types.gen"; +import { Link } from "../components/ui/link"; +import { Declination, RightAscension } from "../components/ui/astronomy"; +import { Pagination } from "../components/ui/pagination"; function searchHandler(navigate: NavigateFunction) { return function f(query: string) { @@ -48,74 +51,56 @@ function SearchResults({ { name: "PGC", renderCell: (value: React.ReactElement | string | number) => ( - - {value} - + {value} ), }, + { name: "Name" }, { - name: "Name", + name: "RA", renderCell: (value: React.ReactElement | string | number) => ( - {value || "N/A"} - ), - }, - { - name: "RA (deg)", - renderCell: (value: React.ReactElement | string | number) => ( - - {typeof value === "number" ? value.toFixed(6) : value} - + ), }, { - name: "Dec (deg)", + name: "Dec", renderCell: (value: React.ReactElement | string | number) => ( - - {typeof value === "number" ? value.toFixed(6) : value} - + ), }, ]; + function handlePageChange(newPage: number): void { + pageChangeHandler(navigate, query, pageSize, newPage); + } + if (results.objects.length > 0) { return ( -
+ <> ({ PGC: object.pgc, Name: object.catalogs.designation?.name || "N/A", - "RA (deg)": object.catalogs.coordinates?.equatorial.ra || 0, - "Dec (deg)": object.catalogs.coordinates?.equatorial.dec || 0, + RA: object.catalogs.coordinates?.equatorial.ra || 0, + Dec: object.catalogs.coordinates?.equatorial.dec || 0, }))} className="w-full" - onRowClick={(row) => { - const pgc = row.PGC as number; - navigate(`/object/${pgc}`); - }} /> -
- - Page {page} - -
-
+ + ); } @@ -163,7 +148,7 @@ export function SearchResultsPage(): ReactElement { const navigate = useNavigate(); const query = searchParams.get("q") || ""; const page = parseInt(searchParams.get("page") || "1"); - const pageSize = parseInt(searchParams.get("pagesize") || "10"); + const pageSize = parseInt(searchParams.get("pagesize") || "25"); useEffect(() => { document.title = `${query} | HyperLEDA`;