Spaces:
Sleeping
Sleeping
Xianbao QIAN
commited on
Commit
·
c524dfb
1
Parent(s):
58154f8
Both models and orgs page working!
Browse files- src/app/components/Pagination.tsx +79 -0
- src/app/components/Table.tsx +67 -0
- src/app/page.tsx +190 -103
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 |
+
<
|
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 |
+
>
|
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
|
|
|
|
|
|
|
|
|
|
|
86 |
|
87 |
-
const
|
88 |
-
(
|
89 |
-
|
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 |
-
<
|
102 |
-
|
103 |
-
|
104 |
-
|
105 |
-
|
106 |
-
|
107 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
108 |
</div>
|
109 |
-
{
|
110 |
-
<p>Loading data...</p>
|
111 |
-
) : paginatedModels.length > 0 ? (
|
112 |
<>
|
113 |
-
<
|
114 |
-
<
|
115 |
-
|
116 |
-
|
117 |
-
|
118 |
-
|
119 |
-
|
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 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
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 |
);
|