Skip to content

Commit e7bdbea

Browse files
authored
#18: add tables list page (#34)
* add tables list page * change table name * realtime page refreshing * add loading spinner for common table * formatters * add agents md file
1 parent 24d6886 commit e7bdbea

13 files changed

Lines changed: 350 additions & 66 deletions

AGENTS.md

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
## Development
2+
3+
### Running checks
4+
5+
To check the code against static checks, run:
6+
7+
```shell
8+
make check
9+
```
10+
11+
Sometimes errors this command produces (such as import sorting) can be fixed automatically using:
12+
13+
```shell
14+
make fix
15+
```
16+
17+
If the check command fails, make sure to always run the fix command first prior to trying to fix changes yourself.

configs/config.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
11
window.__APP_CONFIG__ = {
2-
backendBaseUrl: "http://leda.kraysent.dev",
3-
adminBaseUrl: "http://leda.kraysent.dev"
2+
backendBaseUrl: "http://leda.sao.ru",
3+
adminBaseUrl: "http://leda.sao.ru"
44
};

src/App.tsx

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import { NotFoundPage } from "./pages/NotFound";
66
import { TableDetailsPage } from "./pages/TableDetails";
77
import { CrossmatchResultsPage } from "./pages/CrossmatchResults";
88
import { RecordCrossmatchDetailsPage } from "./pages/RecordCrossmatchDetails";
9+
import { TablesPage } from "./pages/Tables";
910
import { Layout } from "./components/ui/layout";
1011
import { SearchBar } from "./components/ui/searchbar";
1112

@@ -48,6 +49,15 @@ function App() {
4849
</Layout>
4950
}
5051
/>
52+
<Route
53+
path="/tables"
54+
element={
55+
<Layout>
56+
<SearchBar />
57+
<TablesPage />
58+
</Layout>
59+
}
60+
/>
5161
<Route
5262
path="/crossmatch"
5363
element={

src/components/ui/common-table.tsx

Lines changed: 64 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import React, { ReactElement, ReactNode } from "react";
22
import classNames from "classnames";
33
import { Hint } from "./hint";
4+
import { Loading } from "./loading";
45

56
export type CellPrimitive = ReactElement | string | number;
67

@@ -13,6 +14,7 @@ export interface Column {
1314
interface CommonTableProps {
1415
columns: Column[];
1516
data: Record<string, CellPrimitive>[];
17+
loading?: boolean;
1618
className?: string;
1719
tableClassName?: string;
1820
headerClassName?: string;
@@ -25,6 +27,7 @@ interface CommonTableProps {
2527
export function CommonTable({
2628
columns,
2729
data,
30+
loading = false,
2831
className = "",
2932
tableClassName = "",
3033
headerClassName = "bg-gray-700 border-gray-600",
@@ -62,58 +65,71 @@ export function CommonTable({
6265
</div>
6366
)}
6467

65-
<div className={classNames("overflow-x-auto", tableClassName)}>
66-
<table className="w-full border-collapse border border-gray-300 rounded-sm">
67-
<thead>
68-
<tr className="bg-gray-100">
69-
{columns.map((column) => (
70-
<th
71-
key={column.name}
68+
<div className="relative">
69+
<div
70+
className={classNames(
71+
"overflow-x-auto",
72+
tableClassName,
73+
loading && "opacity-50 pointer-events-none",
74+
)}
75+
>
76+
<table className="w-full border-collapse border border-gray-300 rounded-sm">
77+
<thead>
78+
<tr className="bg-gray-100">
79+
{columns.map((column) => (
80+
<th
81+
key={column.name}
82+
className={classNames(
83+
"border border-gray-300 px-2 py-1 text-center font-semibold text-gray-700",
84+
columnHeaderClassName,
85+
)}
86+
>
87+
{column.hint ? (
88+
<Hint hintContent={column.hint}>
89+
<span>{column.name}</span>
90+
</Hint>
91+
) : (
92+
column.name
93+
)}
94+
</th>
95+
))}
96+
</tr>
97+
</thead>
98+
99+
<tbody>
100+
{data.map((row, rowIndex) => (
101+
<tr
102+
key={rowIndex}
72103
className={classNames(
73-
"border border-gray-300 px-2 py-1 text-center font-semibold text-gray-700",
74-
columnHeaderClassName,
104+
"bg-gray-700 hover:bg-gray-800 transition-colors duration-150",
105+
onRowClick && "cursor-pointer",
75106
)}
107+
onClick={() => onRowClick?.(row, rowIndex)}
76108
>
77-
{column.hint ? (
78-
<Hint hintContent={column.hint}>
79-
<span>{column.name}</span>
80-
</Hint>
81-
) : (
82-
column.name
83-
)}
84-
</th>
109+
{columns.map((column) => {
110+
const cellValue = row[column.name];
111+
return (
112+
<td
113+
key={column.name}
114+
className={classNames(
115+
"border border-gray-300 px-2 py-1",
116+
cellClassName,
117+
)}
118+
>
119+
{renderCell(cellValue, column)}
120+
</td>
121+
);
122+
})}
123+
</tr>
85124
))}
86-
</tr>
87-
</thead>
88-
89-
<tbody>
90-
{data.map((row, rowIndex) => (
91-
<tr
92-
key={rowIndex}
93-
className={classNames(
94-
"bg-gray-700 hover:bg-gray-800 transition-colors duration-150",
95-
onRowClick && "cursor-pointer",
96-
)}
97-
onClick={() => onRowClick?.(row, rowIndex)}
98-
>
99-
{columns.map((column) => {
100-
const cellValue = row[column.name];
101-
return (
102-
<td
103-
key={column.name}
104-
className={classNames(
105-
"border border-gray-300 px-2 py-1",
106-
cellClassName,
107-
)}
108-
>
109-
{renderCell(cellValue, column)}
110-
</td>
111-
);
112-
})}
113-
</tr>
114-
))}
115-
</tbody>
116-
</table>
125+
</tbody>
126+
</table>
127+
</div>
128+
{loading && (
129+
<div className="absolute inset-0 flex items-center justify-center bg-gray-900/60">
130+
<Loading />
131+
</div>
132+
)}
117133
</div>
118134
</div>
119135
);

src/config.ts

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,17 @@ declare global {
1010
}
1111

1212
function getConfig(): AppConfig {
13-
if (typeof window === "undefined" || !window.__APP_CONFIG__) {
13+
if (typeof window === "undefined") {
14+
throw new Error(
15+
"App configuration is required. Please set window.__APP_CONFIG__",
16+
);
17+
}
18+
19+
if (import.meta.env.DEV) {
20+
return { backendBaseUrl: "", adminBaseUrl: "" };
21+
}
22+
23+
if (!window.__APP_CONFIG__) {
1424
throw new Error(
1525
"App configuration is required. Please set window.__APP_CONFIG__",
1626
);

src/hooks/useDataFetching.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,8 @@ export function useDataFetching<T>(
1515
const [error, setError] = useState<string | null>(null);
1616

1717
useEffect(() => {
18+
setLoading(true);
19+
setError(null);
1820
async function fetchData(): Promise<void> {
1921
try {
2022
const result = await fetcher();

src/pages/CrossmatchResults.tsx

Lines changed: 11 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ import {
88
import { Badge } from "../components/ui/badge";
99
import { DropdownFilter } from "../components/ui/dropdown-filter";
1010
import { TextFilter } from "../components/ui/text-filter";
11-
import { getCrossmatchRecordsAdminApiV1RecordsCrossmatchGet } from "../clients/admin/sdk.gen";
11+
import { getCrossmatchRecords } from "../clients/admin/sdk.gen";
1212
import type {
1313
GetRecordsCrossmatchResponse,
1414
RecordCrossmatch,
@@ -93,9 +93,13 @@ function CrossmatchFilters({
9393

9494
interface CrossmatchResultsProps {
9595
data: GetRecordsCrossmatchResponse | null;
96+
loading?: boolean;
9697
}
9798

98-
function CrossmatchResults({ data }: CrossmatchResultsProps): ReactElement {
99+
function CrossmatchResults({
100+
data,
101+
loading,
102+
}: CrossmatchResultsProps): ReactElement {
99103
function getRecordName(record: RecordCrossmatch): ReactElement {
100104
const displayName = record.catalogs.designation?.name || record.record_id;
101105
return (
@@ -158,7 +162,7 @@ function CrossmatchResults({ data }: CrossmatchResultsProps): ReactElement {
158162
Candidates: index,
159163
})) || [];
160164

161-
return <CommonTable columns={columns} data={tableData} />;
165+
return <CommonTable columns={columns} data={tableData} loading={loading} />;
162166
}
163167

164168
async function fetcher(
@@ -171,7 +175,7 @@ async function fetcher(
171175
throw new Error("Table name is required");
172176
}
173177

174-
const response = await getCrossmatchRecordsAdminApiV1RecordsCrossmatchGet({
178+
const response = await getCrossmatchRecords({
175179
client: adminClient,
176180
query: {
177181
table_name: tableName,
@@ -245,13 +249,13 @@ export function CrossmatchResultsPage(): ReactElement {
245249
}
246250

247251
function Content(): ReactElement {
248-
if (loading) return <Loading />;
249-
if (error) return <ErrorPage title="Error" message={error} />;
252+
if (error && !data) return <ErrorPage title="Error" message={error} />;
253+
if (!data?.records && loading) return <Loading />;
250254
if (!data?.records) return <ErrorPage title="Error" message="No records" />;
251255

252256
return (
253257
<>
254-
<CrossmatchResults data={data} />
258+
<CrossmatchResults data={data} loading={loading} />
255259
<Pagination
256260
page={page}
257261
pageSize={pageSize}

src/pages/ObjectDetails.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ import { Loading } from "../components/ui/loading";
55
import { ErrorPage } from "../components/ui/error-page";
66
import { CatalogData } from "../components/ui/catalog-data";
77
import { Link } from "../components/ui/link";
8-
import { querySimpleApiV1QuerySimpleGet } from "../clients/backend/sdk.gen";
8+
import { querySimple } from "../clients/backend/sdk.gen";
99
import { PgcObject, Schema } from "../clients/backend/types.gen";
1010
import { useDataFetching } from "../hooks/useDataFetching";
1111
import { backendClient } from "../clients/config";
@@ -55,7 +55,7 @@ async function fetcher(
5555
throw new Error(`Invalid PGC number: ${pgcId}`);
5656
}
5757

58-
const response = await querySimpleApiV1QuerySimpleGet({
58+
const response = await querySimple({
5959
client: backendClient,
6060
query: {
6161
pgcs: [Number(pgcId)],

src/pages/RecordCrossmatchDetails.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import { AladinViewer } from "../components/ui/aladin";
44
import { Loading } from "../components/ui/loading";
55
import { ErrorPage } from "../components/ui/error-page";
66
import { CatalogData } from "../components/ui/catalog-data";
7-
import { getRecordCrossmatchAdminApiV1RecordCrossmatchGet } from "../clients/admin/sdk.gen";
7+
import { getRecordCrossmatch } from "../clients/admin/sdk.gen";
88
import {
99
GetRecordCrossmatchResponse,
1010
RecordCrossmatch,
@@ -188,7 +188,7 @@ async function fetcher(
188188
throw new Error("Record ID is required");
189189
}
190190

191-
const response = await getRecordCrossmatchAdminApiV1RecordCrossmatchGet({
191+
const response = await getRecordCrossmatch({
192192
client: adminClient,
193193
query: {
194194
record_id: recordId,

src/pages/SearchResults.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ import { CommonTable, Column } from "../components/ui/common-table";
99
import { Loading } from "../components/ui/loading";
1010
import { ErrorPage, ErrorPageHomeButton } from "../components/ui/error-page";
1111
import { useDataFetching } from "../hooks/useDataFetching";
12-
import { querySimpleApiV1QuerySimpleGet } from "../clients/backend/sdk.gen";
12+
import { querySimple } from "../clients/backend/sdk.gen";
1313
import { QuerySimpleResponse } from "../clients/backend/types.gen";
1414
import { Link } from "../components/ui/link";
1515
import { Declination, RightAscension } from "../components/ui/astronomy";
@@ -125,7 +125,7 @@ async function fetcher(
125125
throw new Error("Empty query");
126126
}
127127

128-
const response = await querySimpleApiV1QuerySimpleGet({
128+
const response = await querySimple({
129129
client: backendClient,
130130
query: {
131131
name: query,

0 commit comments

Comments
 (0)