Xianbao QIAN
commited on
Commit
•
afa132e
1
Parent(s):
c524dfb
org page: pop up for models under this family
Browse files- src/app/components/Modal.tsx +22 -0
- src/app/components/Table.tsx +7 -11
- src/app/page.tsx +152 -10
src/app/components/Modal.tsx
ADDED
@@ -0,0 +1,22 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import React, { ReactNode } from 'react';
|
2 |
+
|
3 |
+
type ModalProps = {
|
4 |
+
onClose: () => void;
|
5 |
+
children: ReactNode;
|
6 |
+
};
|
7 |
+
|
8 |
+
export default function Modal({ onClose, children }: ModalProps) {
|
9 |
+
return (
|
10 |
+
<div className="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center z-50">
|
11 |
+
<div className="bg-white dark:bg-gray-800 p-6 rounded-lg max-w-2xl w-full max-h-[80vh] overflow-auto">
|
12 |
+
<button
|
13 |
+
onClick={onClose}
|
14 |
+
className="float-right text-gray-500 hover:text-gray-700 dark:text-gray-400 dark:hover:text-gray-200"
|
15 |
+
>
|
16 |
+
×
|
17 |
+
</button>
|
18 |
+
{children}
|
19 |
+
</div>
|
20 |
+
</div>
|
21 |
+
);
|
22 |
+
}
|
src/app/components/Table.tsx
CHANGED
@@ -1,4 +1,4 @@
|
|
1 |
-
import React
|
2 |
import Pagination from './Pagination';
|
3 |
|
4 |
type TableProps<T> = {
|
@@ -6,16 +6,16 @@ type TableProps<T> = {
|
|
6 |
columns: {
|
7 |
key: keyof T;
|
8 |
label: string;
|
9 |
-
render?: (value: T[keyof T]) => React.ReactNode;
|
10 |
}[];
|
11 |
orderBy?: keyof T;
|
12 |
onOrderByChange?: (key: keyof T) => void;
|
13 |
pageSize?: number;
|
|
|
|
|
14 |
};
|
15 |
|
16 |
-
export default function Table<T>({ data, columns, orderBy, onOrderByChange, pageSize = 100 }: TableProps<T>) {
|
17 |
-
const [currentPage, setCurrentPage] = useState(1);
|
18 |
-
|
19 |
const totalPages = Math.ceil(data.length / pageSize);
|
20 |
|
21 |
const paginatedData = data.slice(
|
@@ -23,10 +23,6 @@ export default function Table<T>({ data, columns, orderBy, onOrderByChange, page
|
|
23 |
currentPage * pageSize
|
24 |
);
|
25 |
|
26 |
-
const handlePageChange = (page: number) => {
|
27 |
-
setCurrentPage(page);
|
28 |
-
};
|
29 |
-
|
30 |
return (
|
31 |
<>
|
32 |
<table className="table-auto border-collapse w-full">
|
@@ -50,7 +46,7 @@ export default function Table<T>({ data, columns, orderBy, onOrderByChange, page
|
|
50 |
<tr key={index} className="border-t border-gray-200 dark:border-gray-700">
|
51 |
{columns.map((column) => (
|
52 |
<td key={column.key as string} className="px-4 py-2">
|
53 |
-
{column.render ? column.render(item[column.key]) : String(item[column.key])}
|
54 |
</td>
|
55 |
))}
|
56 |
</tr>
|
@@ -60,7 +56,7 @@ export default function Table<T>({ data, columns, orderBy, onOrderByChange, page
|
|
60 |
<Pagination
|
61 |
currentPage={currentPage}
|
62 |
totalPages={totalPages}
|
63 |
-
onPageChange={
|
64 |
/>
|
65 |
</>
|
66 |
);
|
|
|
1 |
+
import React from 'react';
|
2 |
import Pagination from './Pagination';
|
3 |
|
4 |
type TableProps<T> = {
|
|
|
6 |
columns: {
|
7 |
key: keyof T;
|
8 |
label: string;
|
9 |
+
render?: (value: T[keyof T], row: T) => React.ReactNode;
|
10 |
}[];
|
11 |
orderBy?: keyof T;
|
12 |
onOrderByChange?: (key: keyof T) => void;
|
13 |
pageSize?: number;
|
14 |
+
currentPage: number;
|
15 |
+
onPageChange: (page: number) => void;
|
16 |
};
|
17 |
|
18 |
+
export default function Table<T>({ data, columns, orderBy, onOrderByChange, pageSize = 100, currentPage, onPageChange }: TableProps<T>) {
|
|
|
|
|
19 |
const totalPages = Math.ceil(data.length / pageSize);
|
20 |
|
21 |
const paginatedData = data.slice(
|
|
|
23 |
currentPage * pageSize
|
24 |
);
|
25 |
|
|
|
|
|
|
|
|
|
26 |
return (
|
27 |
<>
|
28 |
<table className="table-auto border-collapse w-full">
|
|
|
46 |
<tr key={index} className="border-t border-gray-200 dark:border-gray-700">
|
47 |
{columns.map((column) => (
|
48 |
<td key={column.key as string} className="px-4 py-2">
|
49 |
+
{column.render ? column.render(item[column.key], item) : String(item[column.key])}
|
50 |
</td>
|
51 |
))}
|
52 |
</tr>
|
|
|
56 |
<Pagination
|
57 |
currentPage={currentPage}
|
58 |
totalPages={totalPages}
|
59 |
+
onPageChange={onPageChange}
|
60 |
/>
|
61 |
</>
|
62 |
);
|
src/app/page.tsx
CHANGED
@@ -3,6 +3,7 @@
|
|
3 |
import { useState, useEffect } from 'react';
|
4 |
import * as duckdb from '@duckdb/duckdb-wasm';
|
5 |
import Table from './components/Table';
|
|
|
6 |
|
7 |
type ModelData = {
|
8 |
ancestor: string;
|
@@ -31,6 +32,15 @@ export default function Home() {
|
|
31 |
const [orgCurrentPage, setOrgCurrentPage] = useState(1);
|
32 |
const [orgPageSize, setOrgPageSize] = useState(100);
|
33 |
const [orgOrderBy, setOrgOrderBy] = useState<keyof OrgData>('family_all_children_count');
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
34 |
|
35 |
useEffect(() => {
|
36 |
const urlParams = new URLSearchParams(window.location.search);
|
@@ -38,6 +48,7 @@ export default function Home() {
|
|
38 |
const page = urlParams.get('page');
|
39 |
const order = urlParams.get('order');
|
40 |
const filter = urlParams.get('filter');
|
|
|
41 |
|
42 |
if (tab === 'orgs') {
|
43 |
setActiveTab('orgs');
|
@@ -47,10 +58,15 @@ export default function Home() {
|
|
47 |
}
|
48 |
if (order === 'direct_children') {
|
49 |
setOrderBy('direct_children');
|
|
|
|
|
50 |
}
|
51 |
if (filter) {
|
52 |
setFilterText(filter);
|
53 |
}
|
|
|
|
|
|
|
54 |
}, []);
|
55 |
|
56 |
useEffect(() => {
|
@@ -67,9 +83,12 @@ export default function Home() {
|
|
67 |
if (filterText) {
|
68 |
urlParams.set('filter', filterText);
|
69 |
}
|
|
|
|
|
|
|
70 |
const newUrl = `${window.location.pathname}?${urlParams.toString()}`;
|
71 |
window.history.replaceState(null, '', newUrl);
|
72 |
-
}, [activeTab, currentPage, orderBy, filterText]);
|
73 |
|
74 |
useEffect(() => {
|
75 |
async function fetchData() {
|
@@ -156,6 +175,7 @@ export default function Home() {
|
|
156 |
setCurrentPage(1);
|
157 |
setOrderBy('all_children');
|
158 |
setFilterText('');
|
|
|
159 |
};
|
160 |
|
161 |
const handlePageChange = (page: number, tab: 'models' | 'orgs') => {
|
@@ -171,7 +191,11 @@ export default function Home() {
|
|
171 |
setCurrentPage(1);
|
172 |
};
|
173 |
|
174 |
-
const
|
|
|
|
|
|
|
|
|
175 |
if (orgOrderBy === 'org') {
|
176 |
return a.org.localeCompare(b.org);
|
177 |
}
|
@@ -185,6 +209,27 @@ export default function Home() {
|
|
185 |
|
186 |
const orgTotalPages = Math.ceil(sortedOrgData.length / orgPageSize);
|
187 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
188 |
return (
|
189 |
<main className="container mx-auto py-8 bg-white dark:bg-gray-900 text-gray-900 dark:text-white">
|
190 |
<h1 className="text-4xl font-bold mb-4">All Models</h1>
|
@@ -235,40 +280,80 @@ export default function Home() {
|
|
235 |
<Table
|
236 |
data={sortedModels}
|
237 |
columns={[
|
238 |
-
{
|
|
|
|
|
|
|
239 |
{
|
240 |
key: 'direct_children_count',
|
241 |
label: 'Direct Children',
|
242 |
-
render: (value) =>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
243 |
},
|
244 |
{
|
245 |
key: 'all_children_count',
|
246 |
label: 'All Children',
|
247 |
-
render: (value) =>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
248 |
},
|
249 |
]}
|
250 |
orderBy={orderBy}
|
251 |
onOrderByChange={(key) => {
|
252 |
-
|
253 |
-
|
|
|
|
|
254 |
}}
|
255 |
pageSize={pageSize}
|
|
|
|
|
256 |
/>
|
257 |
)}
|
258 |
</>
|
259 |
) : (
|
260 |
<>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
261 |
{isLoading ? (
|
262 |
<p>Loading data...</p>
|
263 |
) : (
|
264 |
<Table
|
265 |
-
data={
|
266 |
columns={[
|
267 |
-
{
|
|
|
|
|
|
|
268 |
{
|
269 |
key: 'family_model_count',
|
270 |
label: 'Model Count',
|
271 |
-
render: (value) =>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
272 |
},
|
273 |
{
|
274 |
key: 'family_direct_children_count',
|
@@ -284,10 +369,67 @@ export default function Home() {
|
|
284 |
orderBy={orgOrderBy}
|
285 |
onOrderByChange={(key) => setOrgOrderBy(key)}
|
286 |
pageSize={orgPageSize}
|
|
|
|
|
287 |
/>
|
288 |
)}
|
289 |
</>
|
290 |
)}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
291 |
</main>
|
292 |
);
|
293 |
}
|
|
|
3 |
import { useState, useEffect } from 'react';
|
4 |
import * as duckdb from '@duckdb/duckdb-wasm';
|
5 |
import Table from './components/Table';
|
6 |
+
import Modal from './components/Modal';
|
7 |
|
8 |
type ModelData = {
|
9 |
ancestor: string;
|
|
|
32 |
const [orgCurrentPage, setOrgCurrentPage] = useState(1);
|
33 |
const [orgPageSize, setOrgPageSize] = useState(100);
|
34 |
const [orgOrderBy, setOrgOrderBy] = useState<keyof OrgData>('family_all_children_count');
|
35 |
+
const [orgFilterText, setOrgFilterText] = useState('');
|
36 |
+
const [selectedModel, setSelectedModel] = useState<ModelData | null>(null);
|
37 |
+
const [selectedOrg, setSelectedOrg] = useState<string | null>(null);
|
38 |
+
const [selectedOrgModels, setSelectedOrgModels] = useState<ModelData[]>([]);
|
39 |
+
const [selectedModelChildren, setSelectedModelChildren] = useState<string[]>([]);
|
40 |
+
const [selectedModelChildrenType, setSelectedModelChildrenType] = useState<'direct' | 'all'>('all');
|
41 |
+
const [modelChildrenPage, setModelChildrenPage] = useState(1);
|
42 |
+
const [orgModelsPage, setOrgModelsPage] = useState(1);
|
43 |
+
const modalPageSize = 10;
|
44 |
|
45 |
useEffect(() => {
|
46 |
const urlParams = new URLSearchParams(window.location.search);
|
|
|
48 |
const page = urlParams.get('page');
|
49 |
const order = urlParams.get('order');
|
50 |
const filter = urlParams.get('filter');
|
51 |
+
const orgFilter = urlParams.get('orgFilter');
|
52 |
|
53 |
if (tab === 'orgs') {
|
54 |
setActiveTab('orgs');
|
|
|
58 |
}
|
59 |
if (order === 'direct_children') {
|
60 |
setOrderBy('direct_children');
|
61 |
+
} else {
|
62 |
+
setOrderBy('all_children');
|
63 |
}
|
64 |
if (filter) {
|
65 |
setFilterText(filter);
|
66 |
}
|
67 |
+
if (orgFilter) {
|
68 |
+
setOrgFilterText(orgFilter);
|
69 |
+
}
|
70 |
}, []);
|
71 |
|
72 |
useEffect(() => {
|
|
|
83 |
if (filterText) {
|
84 |
urlParams.set('filter', filterText);
|
85 |
}
|
86 |
+
if (orgFilterText) {
|
87 |
+
urlParams.set('orgFilter', orgFilterText);
|
88 |
+
}
|
89 |
const newUrl = `${window.location.pathname}?${urlParams.toString()}`;
|
90 |
window.history.replaceState(null, '', newUrl);
|
91 |
+
}, [activeTab, currentPage, orderBy, filterText, orgFilterText]);
|
92 |
|
93 |
useEffect(() => {
|
94 |
async function fetchData() {
|
|
|
175 |
setCurrentPage(1);
|
176 |
setOrderBy('all_children');
|
177 |
setFilterText('');
|
178 |
+
setOrgFilterText('');
|
179 |
};
|
180 |
|
181 |
const handlePageChange = (page: number, tab: 'models' | 'orgs') => {
|
|
|
191 |
setCurrentPage(1);
|
192 |
};
|
193 |
|
194 |
+
const filteredOrgData = orgData.filter((org) =>
|
195 |
+
org.org.toLowerCase().includes(orgFilterText.toLowerCase())
|
196 |
+
);
|
197 |
+
|
198 |
+
const sortedOrgData = filteredOrgData.sort((a, b) => {
|
199 |
if (orgOrderBy === 'org') {
|
200 |
return a.org.localeCompare(b.org);
|
201 |
}
|
|
|
209 |
|
210 |
const orgTotalPages = Math.ceil(sortedOrgData.length / orgPageSize);
|
211 |
|
212 |
+
const handleModelChildrenClick = (model: ModelData, type: 'direct' | 'all') => {
|
213 |
+
setSelectedModel(model);
|
214 |
+
setSelectedModelChildrenType(type);
|
215 |
+
const children = type === 'direct' ? model.direct_children || [] : model.all_children || [];
|
216 |
+
setSelectedModelChildren(children);
|
217 |
+
};
|
218 |
+
|
219 |
+
const handleOrgModelsClick = (org: string) => {
|
220 |
+
setSelectedOrg(org);
|
221 |
+
const orgModels = allModels.filter((model) => model.ancestor.split('/')[0] === org);
|
222 |
+
setSelectedOrgModels(orgModels);
|
223 |
+
};
|
224 |
+
|
225 |
+
const handleModelChildrenPageChange = (page: number) => {
|
226 |
+
setModelChildrenPage(page);
|
227 |
+
};
|
228 |
+
|
229 |
+
const handleOrgModelsPageChange = (page: number) => {
|
230 |
+
setOrgModelsPage(page);
|
231 |
+
};
|
232 |
+
|
233 |
return (
|
234 |
<main className="container mx-auto py-8 bg-white dark:bg-gray-900 text-gray-900 dark:text-white">
|
235 |
<h1 className="text-4xl font-bold mb-4">All Models</h1>
|
|
|
280 |
<Table
|
281 |
data={sortedModels}
|
282 |
columns={[
|
283 |
+
{
|
284 |
+
key: 'ancestor',
|
285 |
+
label: 'Model',
|
286 |
+
},
|
287 |
{
|
288 |
key: 'direct_children_count',
|
289 |
label: 'Direct Children',
|
290 |
+
render: (value, row) => (
|
291 |
+
<button
|
292 |
+
className="text-right text-blue-500 hover:underline"
|
293 |
+
onClick={() => handleModelChildrenClick(row, 'direct')}
|
294 |
+
>
|
295 |
+
{value ?? 0}
|
296 |
+
</button>
|
297 |
+
),
|
298 |
},
|
299 |
{
|
300 |
key: 'all_children_count',
|
301 |
label: 'All Children',
|
302 |
+
render: (value, row) => (
|
303 |
+
<button
|
304 |
+
className="text-right text-blue-500 hover:underline"
|
305 |
+
onClick={() => handleModelChildrenClick(row, 'all')}
|
306 |
+
>
|
307 |
+
{value}
|
308 |
+
</button>
|
309 |
+
),
|
310 |
},
|
311 |
]}
|
312 |
orderBy={orderBy}
|
313 |
onOrderByChange={(key) => {
|
314 |
+
if (key === 'all_children' || key === 'direct_children') {
|
315 |
+
setOrderBy(key);
|
316 |
+
setCurrentPage(1);
|
317 |
+
}
|
318 |
}}
|
319 |
pageSize={pageSize}
|
320 |
+
currentPage={currentPage}
|
321 |
+
onPageChange={(page) => handlePageChange(page, 'models')}
|
322 |
/>
|
323 |
)}
|
324 |
</>
|
325 |
) : (
|
326 |
<>
|
327 |
+
<div className="mb-4">
|
328 |
+
<input
|
329 |
+
type="text"
|
330 |
+
placeholder="Filter by organization name"
|
331 |
+
value={orgFilterText}
|
332 |
+
onChange={(e) => setOrgFilterText(e.target.value)}
|
333 |
+
className="px-4 py-2 border border-gray-300 dark:border-gray-700 rounded-md bg-white dark:bg-gray-800 text-gray-900 dark:text-white"
|
334 |
+
/>
|
335 |
+
</div>
|
336 |
{isLoading ? (
|
337 |
<p>Loading data...</p>
|
338 |
) : (
|
339 |
<Table
|
340 |
+
data={paginatedOrgData}
|
341 |
columns={[
|
342 |
+
{
|
343 |
+
key: 'org',
|
344 |
+
label: 'Organization',
|
345 |
+
},
|
346 |
{
|
347 |
key: 'family_model_count',
|
348 |
label: 'Model Count',
|
349 |
+
render: (value, row) => (
|
350 |
+
<button
|
351 |
+
className="text-right text-blue-500 hover:underline"
|
352 |
+
onClick={() => handleOrgModelsClick(row.org)}
|
353 |
+
>
|
354 |
+
{value}
|
355 |
+
</button>
|
356 |
+
),
|
357 |
},
|
358 |
{
|
359 |
key: 'family_direct_children_count',
|
|
|
369 |
orderBy={orgOrderBy}
|
370 |
onOrderByChange={(key) => setOrgOrderBy(key)}
|
371 |
pageSize={orgPageSize}
|
372 |
+
currentPage={orgCurrentPage}
|
373 |
+
onPageChange={(page) => handlePageChange(page, 'orgs')}
|
374 |
/>
|
375 |
)}
|
376 |
</>
|
377 |
)}
|
378 |
+
{selectedModel && (
|
379 |
+
<Modal onClose={() => {
|
380 |
+
setSelectedModel(null);
|
381 |
+
setModelChildrenPage(1);
|
382 |
+
}}>
|
383 |
+
<h2 className="text-2xl font-bold mb-4">
|
384 |
+
{selectedModelChildrenType === 'direct' ? 'Direct Children' : 'All Children'} of {selectedModel.ancestor}
|
385 |
+
</h2>
|
386 |
+
{selectedModelChildren.length > 0 ? (
|
387 |
+
<Table
|
388 |
+
data={selectedModelChildren.map((child) => ({ model: child }))}
|
389 |
+
columns={[{ key: 'model', label: 'Model' }]}
|
390 |
+
pageSize={modalPageSize}
|
391 |
+
currentPage={modelChildrenPage}
|
392 |
+
onPageChange={handleModelChildrenPageChange}
|
393 |
+
/>
|
394 |
+
) : (
|
395 |
+
<p>No children found for this model.</p>
|
396 |
+
)}
|
397 |
+
</Modal>
|
398 |
+
)}
|
399 |
+
{selectedOrg && (
|
400 |
+
<Modal onClose={() => {
|
401 |
+
setSelectedOrg(null);
|
402 |
+
setOrgModelsPage(1);
|
403 |
+
}}>
|
404 |
+
<h2 className="text-2xl font-bold mb-4">Models under {selectedOrg}</h2>
|
405 |
+
{selectedOrgModels.length > 0 ? (
|
406 |
+
<Table
|
407 |
+
data={selectedOrgModels}
|
408 |
+
columns={[
|
409 |
+
{
|
410 |
+
key: 'ancestor',
|
411 |
+
label: 'Model',
|
412 |
+
},
|
413 |
+
{
|
414 |
+
key: 'direct_children_count',
|
415 |
+
label: 'Direct Children',
|
416 |
+
render: (value) => <span className="text-right">{value ?? 0}</span>,
|
417 |
+
},
|
418 |
+
{
|
419 |
+
key: 'all_children_count',
|
420 |
+
label: 'All Children',
|
421 |
+
render: (value) => <span className="text-right">{value}</span>,
|
422 |
+
},
|
423 |
+
]}
|
424 |
+
pageSize={modalPageSize}
|
425 |
+
currentPage={orgModelsPage}
|
426 |
+
onPageChange={handleOrgModelsPageChange}
|
427 |
+
/>
|
428 |
+
) : (
|
429 |
+
<p>No models found for this organization.</p>
|
430 |
+
)}
|
431 |
+
</Modal>
|
432 |
+
)}
|
433 |
</main>
|
434 |
);
|
435 |
}
|