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 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
+ &times;
17
+ </button>
18
+ {children}
19
+ </div>
20
+ </div>
21
+ );
22
+ }
src/app/components/Table.tsx CHANGED
@@ -1,4 +1,4 @@
1
- import React, { useState } from '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={handlePageChange}
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 sortedOrgData = orgData.sort((a, b) => {
 
 
 
 
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
- { key: 'ancestor', label: 'Model' },
 
 
 
239
  {
240
  key: 'direct_children_count',
241
  label: 'Direct Children',
242
- render: (value) => <span className="text-right">{value ?? 0}</span>,
 
 
 
 
 
 
 
243
  },
244
  {
245
  key: 'all_children_count',
246
  label: 'All Children',
247
- render: (value) => <span className="text-right">{value}</span>,
 
 
 
 
 
 
 
248
  },
249
  ]}
250
  orderBy={orderBy}
251
  onOrderByChange={(key) => {
252
- setOrderBy(key as 'all_children' | 'direct_children');
253
- setCurrentPage(1);
 
 
254
  }}
255
  pageSize={pageSize}
 
 
256
  />
257
  )}
258
  </>
259
  ) : (
260
  <>
 
 
 
 
 
 
 
 
 
261
  {isLoading ? (
262
  <p>Loading data...</p>
263
  ) : (
264
  <Table
265
- data={sortedOrgData}
266
  columns={[
267
- { key: 'org', label: 'Organization' },
 
 
 
268
  {
269
  key: 'family_model_count',
270
  label: 'Model Count',
271
- render: (value) => <span className="text-right">{value}</span>,
 
 
 
 
 
 
 
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
  }