Xianbao QIAN commited on
Commit
c524dfb
·
1 Parent(s): 58154f8

Both models and orgs page working!

Browse files
src/app/components/Pagination.tsx ADDED
@@ -0,0 +1,79 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import React from 'react';
2
+
3
+ type PaginationProps = {
4
+ currentPage: number;
5
+ totalPages: number;
6
+ onPageChange: (page: number) => void;
7
+ };
8
+
9
+ export default function Pagination({ currentPage, totalPages, onPageChange }: PaginationProps) {
10
+ return (
11
+ <div className="mt-4 flex items-center justify-between">
12
+ <button
13
+ onClick={() => onPageChange(Math.max(currentPage - 1, 1))}
14
+ disabled={currentPage === 1}
15
+ className={`px-4 py-2 rounded-md mr-2 ${
16
+ currentPage === 1
17
+ ? 'bg-gray-200 dark:bg-gray-700 text-gray-500 dark:text-gray-500 cursor-not-allowed'
18
+ : 'bg-blue-500 dark:bg-blue-600 text-white'
19
+ }`}
20
+ >
21
+ &lt;
22
+ </button>
23
+ <div className="flex items-center space-x-2">
24
+ {currentPage > 1 && (
25
+ <>
26
+ <button
27
+ onClick={() => onPageChange(1)}
28
+ className="px-2 py-1 bg-gray-200 dark:bg-gray-700 text-gray-700 dark:text-gray-200 rounded-md"
29
+ >
30
+ 1
31
+ </button>
32
+ {currentPage > 2 && <span className="text-gray-500">...</span>}
33
+ </>
34
+ )}
35
+ {[...Array(5)].map((_, i) => {
36
+ const page = currentPage + i - 2;
37
+ if (page >= 1 && page <= totalPages) {
38
+ return (
39
+ <button
40
+ key={page}
41
+ onClick={() => onPageChange(page)}
42
+ className={`px-2 py-1 rounded-md ${
43
+ page === currentPage
44
+ ? 'bg-blue-500 dark:bg-blue-600 text-white'
45
+ : 'bg-gray-200 dark:bg-gray-700 text-gray-700 dark:text-gray-200'
46
+ }`}
47
+ >
48
+ {page}
49
+ </button>
50
+ );
51
+ }
52
+ return null;
53
+ })}
54
+ {currentPage < totalPages && (
55
+ <>
56
+ {currentPage < totalPages - 1 && <span className="text-gray-500">...</span>}
57
+ <button
58
+ onClick={() => onPageChange(totalPages)}
59
+ className="px-2 py-1 bg-gray-200 dark:bg-gray-700 text-gray-700 dark:text-gray-200 rounded-md"
60
+ >
61
+ {totalPages}
62
+ </button>
63
+ </>
64
+ )}
65
+ </div>
66
+ <button
67
+ onClick={() => onPageChange(Math.min(currentPage + 1, totalPages))}
68
+ disabled={currentPage === totalPages}
69
+ className={`px-4 py-2 rounded-md ${
70
+ currentPage === totalPages
71
+ ? 'bg-gray-200 dark:bg-gray-700 text-gray-500 dark:text-gray-500 cursor-not-allowed'
72
+ : 'bg-blue-500 dark:bg-blue-600 text-white'
73
+ }`}
74
+ >
75
+ &gt;
76
+ </button>
77
+ </div>
78
+ );
79
+ }
src/app/components/Table.tsx ADDED
@@ -0,0 +1,67 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import React, { useState } from 'react';
2
+ import Pagination from './Pagination';
3
+
4
+ type TableProps<T> = {
5
+ data: 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(
22
+ (currentPage - 1) * pageSize,
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">
33
+ <thead>
34
+ <tr>
35
+ {columns.map((column) => (
36
+ <th
37
+ key={column.key as string}
38
+ className={`px-4 py-2 bg-gray-100 dark:bg-gray-800 text-left ${
39
+ onOrderByChange ? 'cursor-pointer' : ''
40
+ }`}
41
+ onClick={() => onOrderByChange && onOrderByChange(column.key)}
42
+ >
43
+ {column.label} {orderBy === column.key && '▼'}
44
+ </th>
45
+ ))}
46
+ </tr>
47
+ </thead>
48
+ <tbody>
49
+ {paginatedData.map((item, index) => (
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>
57
+ ))}
58
+ </tbody>
59
+ </table>
60
+ <Pagination
61
+ currentPage={currentPage}
62
+ totalPages={totalPages}
63
+ onPageChange={handlePageChange}
64
+ />
65
+ </>
66
+ );
67
+ }
src/app/page.tsx CHANGED
@@ -2,6 +2,7 @@
2
 
3
  import { useState, useEffect } from 'react';
4
  import * as duckdb from '@duckdb/duckdb-wasm';
 
5
 
6
  type ModelData = {
7
  ancestor: string;
@@ -11,13 +12,64 @@ type ModelData = {
11
  direct_children_count: number | null;
12
  };
13
 
 
 
 
 
 
 
 
14
  export default function Home() {
15
  const [allModels, setAllModels] = useState<ModelData[]>([]);
 
16
  const [currentPage, setCurrentPage] = useState(1);
17
  const [pageSize, setPageSize] = useState(100);
18
  const [filterText, setFilterText] = useState('');
19
  const [isLoading, setIsLoading] = useState(true);
20
  const [orderBy, setOrderBy] = useState<'all_children' | 'direct_children'>('all_children');
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
21
 
22
  useEffect(() => {
23
  async function fetchData() {
@@ -60,11 +112,28 @@ export default function Home() {
60
  // Convert the result to a JavaScript array
61
  const data: ModelData[] = result.toArray();
62
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
63
  // Close the connection and terminate the worker
64
  await conn.close();
65
  await db.terminate();
66
 
67
  setAllModels(data);
 
68
  setIsLoading(false);
69
  }
70
  fetchData();
@@ -82,124 +151,142 @@ export default function Home() {
82
  }
83
  });
84
 
85
- const totalPages = Math.ceil(sortedModels.length / pageSize);
 
 
 
 
 
86
 
87
- const paginatedModels = sortedModels.slice(
88
- (currentPage - 1) * pageSize,
89
- currentPage * pageSize
90
- );
 
 
 
91
 
92
  const handleOrderByClick = (column: 'all_children' | 'direct_children') => {
93
  setOrderBy(column);
94
  setCurrentPage(1);
95
  };
96
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
97
  return (
98
  <main className="container mx-auto py-8 bg-white dark:bg-gray-900 text-gray-900 dark:text-white">
99
  <h1 className="text-4xl font-bold mb-4">All Models</h1>
100
- <div className="mb-4">
101
- <input
102
- type="text"
103
- placeholder="Filter by model name"
104
- value={filterText}
105
- onChange={(e) => setFilterText(e.target.value)}
106
- 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"
107
- />
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
108
  </div>
109
- {isLoading ? (
110
- <p>Loading data...</p>
111
- ) : paginatedModels.length > 0 ? (
112
  <>
113
- <table className="table-auto border-collapse w-full">
114
- <thead>
115
- <tr>
116
- <th className="px-4 py-2 bg-gray-100 dark:bg-gray-800 text-left">Model</th>
117
- <th
118
- className="px-4 py-2 bg-gray-100 dark:bg-gray-800 text-right cursor-pointer"
119
- onClick={() => handleOrderByClick('direct_children')}
120
- >
121
- Direct Children {orderBy === 'direct_children' && '▼'}
122
- </th>
123
- <th
124
- className="px-4 py-2 bg-gray-100 dark:bg-gray-800 text-right cursor-pointer"
125
- onClick={() => handleOrderByClick('all_children')}
126
- >
127
- All Children {orderBy === 'all_children' && '▼'}
128
- </th>
129
- </tr>
130
- </thead>
131
- <tbody>
132
- {paginatedModels.map((model, index) => (
133
- <tr key={index} className="border-t border-gray-200 dark:border-gray-700">
134
- <td className="px-4 py-2">{model.ancestor}</td>
135
- <td className="px-4 py-2 text-right">{model.direct_children_count ?? 0}</td>
136
- <td className="px-4 py-2 text-right">{model.all_children_count}</td>
137
- </tr>
138
- ))}
139
- </tbody>
140
- </table>
141
- <div className="mt-4 flex items-center justify-between">
142
- <button
143
- onClick={() => setCurrentPage((prev) => Math.max(prev - 1, 1))}
144
- disabled={currentPage === 1}
145
- className="px-4 py-2 bg-blue-500 dark:bg-blue-600 text-white rounded-md mr-2"
146
- >
147
- Previous
148
- </button>
149
- <div className="flex items-center space-x-2">
150
- {currentPage > 1 && (
151
- <>
152
- <button
153
- onClick={() => setCurrentPage(1)}
154
- className="px-2 py-1 bg-gray-200 dark:bg-gray-700 text-gray-700 dark:text-gray-200 rounded-md"
155
- >
156
- 1
157
- </button>
158
- {currentPage > 2 && <span className="text-gray-500">...</span>}
159
- </>
160
- )}
161
- {[...Array(5)].map((_, i) => {
162
- const page = currentPage + i - 2;
163
- if (page >= 1 && page <= totalPages) {
164
- return (
165
- <button
166
- key={page}
167
- onClick={() => setCurrentPage(page)}
168
- className={`px-2 py-1 ${
169
- page === currentPage
170
- ? 'bg-blue-500 dark:bg-blue-600 text-white'
171
- : 'bg-gray-200 dark:bg-gray-700 text-gray-700 dark:text-gray-200'
172
- } rounded-md`}
173
- >
174
- {page}
175
- </button>
176
- );
177
- }
178
- return null;
179
- })}
180
- {currentPage < totalPages && (
181
- <>
182
- {currentPage < totalPages - 1 && <span className="text-gray-500">...</span>}
183
- <button
184
- onClick={() => setCurrentPage(totalPages)}
185
- className="px-2 py-1 bg-gray-200 dark:bg-gray-700 text-gray-700 dark:text-gray-200 rounded-md"
186
- >
187
- {totalPages}
188
- </button>
189
- </>
190
- )}
191
- </div>
192
- <button
193
- onClick={() => setCurrentPage((prev) => Math.min(prev + 1, totalPages))}
194
- disabled={currentPage === totalPages}
195
- className="px-4 py-2 bg-blue-500 dark:bg-blue-600 text-white rounded-md"
196
- >
197
- Next
198
- </button>
199
  </div>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
200
  </>
201
  ) : (
202
- <p>No data found.</p>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
203
  )}
204
  </main>
205
  );
 
2
 
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;
 
12
  direct_children_count: number | null;
13
  };
14
 
15
+ type OrgData = {
16
+ org: string;
17
+ family_model_count: number;
18
+ family_direct_children_count: number;
19
+ family_all_children_count: number;
20
+ };
21
+
22
  export default function Home() {
23
  const [allModels, setAllModels] = useState<ModelData[]>([]);
24
+ const [orgData, setOrgData] = useState<OrgData[]>([]);
25
  const [currentPage, setCurrentPage] = useState(1);
26
  const [pageSize, setPageSize] = useState(100);
27
  const [filterText, setFilterText] = useState('');
28
  const [isLoading, setIsLoading] = useState(true);
29
  const [orderBy, setOrderBy] = useState<'all_children' | 'direct_children'>('all_children');
30
+ const [activeTab, setActiveTab] = useState<'models' | 'orgs'>('models');
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);
37
+ const tab = urlParams.get('tab');
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');
44
+ }
45
+ if (page) {
46
+ setCurrentPage(parseInt(page, 10));
47
+ }
48
+ if (order === 'direct_children') {
49
+ setOrderBy('direct_children');
50
+ }
51
+ if (filter) {
52
+ setFilterText(filter);
53
+ }
54
+ }, []);
55
+
56
+ useEffect(() => {
57
+ const urlParams = new URLSearchParams();
58
+ if (activeTab === 'orgs') {
59
+ urlParams.set('tab', 'orgs');
60
+ }
61
+ if (currentPage > 1) {
62
+ urlParams.set('page', currentPage.toString());
63
+ }
64
+ if (orderBy === 'direct_children') {
65
+ urlParams.set('order', 'direct_children');
66
+ }
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() {
 
112
  // Convert the result to a JavaScript array
113
  const data: ModelData[] = result.toArray();
114
 
115
+ // Execute the SQL query to get the grouped org data with additional counts
116
+ const orgQuery = `
117
+ SELECT
118
+ SPLIT_PART(ancestor, '/', 1) AS org,
119
+ CAST(COUNT(DISTINCT ancestor) AS INTEGER) AS family_model_count,
120
+ CAST(SUM(direct_children_count) AS INTEGER) AS family_direct_children_count,
121
+ CAST(SUM(all_children_count) AS INTEGER) AS family_all_children_count
122
+ FROM 'ancestor_children.parquet'
123
+ GROUP BY org
124
+ ORDER BY family_all_children_count DESC
125
+ `;
126
+ const orgResult = await conn.query(orgQuery);
127
+
128
+ // Convert the org result to a JavaScript array
129
+ const orgData: OrgData[] = orgResult.toArray();
130
+
131
  // Close the connection and terminate the worker
132
  await conn.close();
133
  await db.terminate();
134
 
135
  setAllModels(data);
136
+ setOrgData(orgData);
137
  setIsLoading(false);
138
  }
139
  fetchData();
 
151
  }
152
  });
153
 
154
+ const handleTabChange = (tab: 'models' | 'orgs') => {
155
+ setActiveTab(tab);
156
+ setCurrentPage(1);
157
+ setOrderBy('all_children');
158
+ setFilterText('');
159
+ };
160
 
161
+ const handlePageChange = (page: number, tab: 'models' | 'orgs') => {
162
+ if (tab === 'models') {
163
+ setCurrentPage(page);
164
+ } else {
165
+ setOrgCurrentPage(page);
166
+ }
167
+ };
168
 
169
  const handleOrderByClick = (column: 'all_children' | 'direct_children') => {
170
  setOrderBy(column);
171
  setCurrentPage(1);
172
  };
173
 
174
+ const sortedOrgData = orgData.sort((a, b) => {
175
+ if (orgOrderBy === 'org') {
176
+ return a.org.localeCompare(b.org);
177
+ }
178
+ return b[orgOrderBy] - a[orgOrderBy];
179
+ });
180
+
181
+ const paginatedOrgData = sortedOrgData.slice(
182
+ (orgCurrentPage - 1) * orgPageSize,
183
+ orgCurrentPage * orgPageSize
184
+ );
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>
191
+ <div className="mb-4 flex space-x-4">
192
+ <a
193
+ href={`?tab=models`}
194
+ onClick={(e) => {
195
+ e.preventDefault();
196
+ handleTabChange('models');
197
+ }}
198
+ className={`px-4 py-2 rounded-md ${
199
+ activeTab === 'models'
200
+ ? 'bg-blue-500 dark:bg-blue-600 text-white'
201
+ : 'bg-gray-200 dark:bg-gray-700 text-gray-700 dark:text-gray-200'
202
+ }`}
203
+ >
204
+ Models
205
+ </a>
206
+ <a
207
+ href={`?tab=orgs`}
208
+ onClick={(e) => {
209
+ e.preventDefault();
210
+ handleTabChange('orgs');
211
+ }}
212
+ className={`px-4 py-2 rounded-md ${
213
+ activeTab === 'orgs'
214
+ ? 'bg-blue-500 dark:bg-blue-600 text-white'
215
+ : 'bg-gray-200 dark:bg-gray-700 text-gray-700 dark:text-gray-200'
216
+ }`}
217
+ >
218
+ Organizations
219
+ </a>
220
  </div>
221
+ {activeTab === 'models' ? (
 
 
222
  <>
223
+ <div className="mb-4">
224
+ <input
225
+ type="text"
226
+ placeholder="Filter by model name"
227
+ value={filterText}
228
+ onChange={(e) => setFilterText(e.target.value)}
229
+ 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"
230
+ />
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
231
  </div>
232
+ {isLoading ? (
233
+ <p>Loading data...</p>
234
+ ) : (
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',
275
+ label: 'Direct Children',
276
+ render: (value) => <span className="text-right">{value}</span>,
277
+ },
278
+ {
279
+ key: 'family_all_children_count',
280
+ label: 'All Children',
281
+ render: (value) => <span className="text-right">{value}</span>,
282
+ },
283
+ ]}
284
+ orderBy={orgOrderBy}
285
+ onOrderByChange={(key) => setOrgOrderBy(key)}
286
+ pageSize={orgPageSize}
287
+ />
288
+ )}
289
+ </>
290
  )}
291
  </main>
292
  );