Yoleo commited on
Commit
0563457
·
verified ·
1 Parent(s): 50a3d0e

Add 2 files

Browse files
Files changed (2) hide show
  1. README.md +7 -5
  2. index.html +1254 -19
README.md CHANGED
@@ -1,10 +1,12 @@
1
  ---
2
- title: Tabla Json
3
- emoji: 🏃
4
- colorFrom: purple
5
- colorTo: gray
6
  sdk: static
7
  pinned: false
 
 
8
  ---
9
 
10
- Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
 
1
  ---
2
+ title: tabla-json
3
+ emoji: 🐳
4
+ colorFrom: pink
5
+ colorTo: red
6
  sdk: static
7
  pinned: false
8
+ tags:
9
+ - deepsite
10
  ---
11
 
12
+ Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
index.html CHANGED
@@ -1,19 +1,1254 @@
1
- <!doctype html>
2
- <html>
3
- <head>
4
- <meta charset="utf-8" />
5
- <meta name="viewport" content="width=device-width" />
6
- <title>My static Space</title>
7
- <link rel="stylesheet" href="style.css" />
8
- </head>
9
- <body>
10
- <div class="card">
11
- <h1>Welcome to your static Space!</h1>
12
- <p>You can modify this app directly by editing <i>index.html</i> in the Files and versions tab.</p>
13
- <p>
14
- Also don't forget to check the
15
- <a href="https://huggingface.co/docs/hub/spaces" target="_blank">Spaces documentation</a>.
16
- </p>
17
- </div>
18
- </body>
19
- </html>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <title>Advanced Data Table</title>
7
+ <script src="https://cdn.tailwindcss.com"></script>
8
+ <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
9
+ <style>
10
+ /* Custom styles */
11
+ .table-container {
12
+ overflow-x: auto;
13
+ max-width: 100%;
14
+ }
15
+ .table-wrapper {
16
+ position: relative;
17
+ }
18
+ .table-header {
19
+ position: sticky;
20
+ top: 0;
21
+ background-color: white;
22
+ z-index: 10;
23
+ }
24
+ .resize-handle {
25
+ position: absolute;
26
+ top: 0;
27
+ right: 0;
28
+ width: 5px;
29
+ height: 100%;
30
+ background-color: #e5e7eb;
31
+ cursor: col-resize;
32
+ }
33
+ .resize-handle:hover {
34
+ background-color: #3b82f6;
35
+ }
36
+ .draggable-header {
37
+ cursor: move;
38
+ }
39
+ .column-options {
40
+ position: absolute;
41
+ right: 0;
42
+ top: 100%;
43
+ z-index: 20;
44
+ background: white;
45
+ border: 1px solid #e5e7eb;
46
+ box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1);
47
+ min-width: 200px;
48
+ }
49
+ .filter-panel {
50
+ position: absolute;
51
+ right: 0;
52
+ top: 100%;
53
+ z-index: 20;
54
+ background: white;
55
+ border: 1px solid #e5e7eb;
56
+ box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1);
57
+ min-width: 250px;
58
+ padding: 1rem;
59
+ }
60
+ .ellipsis-text {
61
+ white-space: nowrap;
62
+ overflow: hidden;
63
+ text-overflow: ellipsis;
64
+ max-width: 300px;
65
+ }
66
+ .tooltip {
67
+ position: absolute;
68
+ background: #333;
69
+ color: white;
70
+ padding: 5px 10px;
71
+ border-radius: 4px;
72
+ font-size: 14px;
73
+ z-index: 100;
74
+ display: none;
75
+ }
76
+ .sort-icon {
77
+ transition: transform 0.2s;
78
+ }
79
+ .sort-asc {
80
+ transform: rotate(180deg);
81
+ }
82
+ .file-drop-area {
83
+ border: 2px dashed #cbd5e1;
84
+ border-radius: 0.5rem;
85
+ padding: 2rem;
86
+ text-align: center;
87
+ cursor: pointer;
88
+ transition: all 0.3s;
89
+ }
90
+ .file-drop-area.active {
91
+ border-color: #3b82f6;
92
+ background-color: #eff6ff;
93
+ }
94
+ .modal {
95
+ display: none;
96
+ position: fixed;
97
+ top: 0;
98
+ left: 0;
99
+ width: 100%;
100
+ height: 100%;
101
+ background-color: rgba(0, 0, 0, 0.5);
102
+ z-index: 1000;
103
+ justify-content: center;
104
+ align-items: center;
105
+ }
106
+ .modal-content {
107
+ background-color: white;
108
+ padding: 2rem;
109
+ border-radius: 0.5rem;
110
+ max-width: 90%;
111
+ max-height: 90%;
112
+ overflow: auto;
113
+ }
114
+ .column-menu {
115
+ position: absolute;
116
+ background: white;
117
+ border: 1px solid #e5e7eb;
118
+ box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1);
119
+ min-width: 180px;
120
+ z-index: 30;
121
+ }
122
+ </style>
123
+ </head>
124
+ <body class="bg-gray-50 p-4">
125
+ <div class="max-w-7xl mx-auto">
126
+ <h1 class="text-2xl font-bold text-gray-800 mb-6">Advanced Data Table</h1>
127
+
128
+ <!-- Controls Section -->
129
+ <div class="bg-white rounded-lg shadow p-4 mb-6">
130
+ <div class="flex flex-wrap gap-4 items-center justify-between mb-4">
131
+ <div class="flex gap-2">
132
+ <button id="uploadBtn" class="bg-blue-500 hover:bg-blue-600 text-white px-4 py-2 rounded flex items-center gap-2">
133
+ <i class="fas fa-upload"></i> Upload JSON
134
+ </button>
135
+ <button id="downloadBtn" class="bg-green-500 hover:bg-green-600 text-white px-4 py-2 rounded flex items-center gap-2">
136
+ <i class="fas fa-download"></i> Download Data
137
+ </button>
138
+ <button id="pasteBtn" class="bg-purple-500 hover:bg-purple-600 text-white px-4 py-2 rounded flex items-center gap-2">
139
+ <i class="fas fa-paste"></i> Paste from Clipboard
140
+ </button>
141
+ <button id="loadConfigBtn" class="bg-yellow-500 hover:bg-yellow-600 text-white px-4 py-2 rounded flex items-center gap-2">
142
+ <i class="fas fa-cog"></i> Load Config
143
+ </button>
144
+ <button id="saveConfigBtn" class="bg-indigo-500 hover:bg-indigo-600 text-white px-4 py-2 rounded flex items-center gap-2">
145
+ <i class="fas fa-save"></i> Save Config
146
+ </button>
147
+ </div>
148
+ <div class="flex gap-2">
149
+ <button id="toggleFiltersBtn" class="bg-gray-200 hover:bg-gray-300 text-gray-800 px-4 py-2 rounded flex items-center gap-2">
150
+ <i class="fas fa-filter"></i> Toggle Filters
151
+ </button>
152
+ <button id="toggleColumnsBtn" class="bg-gray-200 hover:bg-gray-300 text-gray-800 px-4 py-2 rounded flex items-center gap-2">
153
+ <i class="fas fa-columns"></i> Columns
154
+ </button>
155
+ </div>
156
+ </div>
157
+
158
+ <!-- File Drop Area -->
159
+ <div id="fileDropArea" class="file-drop-area mb-4">
160
+ <div class="flex flex-col items-center justify-center">
161
+ <i class="fas fa-cloud-upload-alt text-4xl text-gray-400 mb-2"></i>
162
+ <p class="text-gray-600">Drag & drop your JSON file here</p>
163
+ <p class="text-sm text-gray-500 mt-1">or click to browse files</p>
164
+ </div>
165
+ <input type="file" id="fileInput" accept=".json" class="hidden">
166
+ </div>
167
+
168
+ <!-- URL Input -->
169
+ <div class="flex gap-2 mb-4">
170
+ <input type="text" id="jsonUrl" placeholder="Enter JSON URL" class="flex-1 border border-gray-300 rounded px-3 py-2 focus:outline-none focus:ring-2 focus:ring-blue-500">
171
+ <button id="loadUrlBtn" class="bg-blue-500 hover:bg-blue-600 text-white px-4 py-2 rounded">
172
+ Load from URL
173
+ </button>
174
+ </div>
175
+
176
+ <!-- Search Input -->
177
+ <div class="relative mb-4">
178
+ <input type="text" id="globalSearch" placeholder="Search across all columns..." class="w-full border border-gray-300 rounded px-3 py-2 pl-10 focus:outline-none focus:ring-2 focus:ring-blue-500">
179
+ <i class="fas fa-search absolute left-3 top-3 text-gray-400"></i>
180
+ </div>
181
+ </div>
182
+
183
+ <!-- Filter Panel -->
184
+ <div id="filterPanel" class="filter-panel hidden bg-white rounded-lg shadow mb-6">
185
+ <div class="flex justify-between items-center mb-4">
186
+ <h3 class="font-bold text-lg">Filters</h3>
187
+ <button id="closeFiltersBtn" class="text-gray-500 hover:text-gray-700">
188
+ <i class="fas fa-times"></i>
189
+ </button>
190
+ </div>
191
+ <div id="filterControls" class="space-y-4">
192
+ <!-- Filters will be dynamically added here -->
193
+ </div>
194
+ <div class="flex justify-end gap-2 mt-4">
195
+ <button id="applyFiltersBtn" class="bg-blue-500 hover:bg-blue-600 text-white px-4 py-2 rounded">
196
+ Apply Filters
197
+ </button>
198
+ <button id="resetFiltersBtn" class="bg-gray-200 hover:bg-gray-300 text-gray-800 px-4 py-2 rounded">
199
+ Reset
200
+ </button>
201
+ </div>
202
+ </div>
203
+
204
+ <!-- Column Options Panel -->
205
+ <div id="columnOptionsPanel" class="column-options hidden bg-white rounded-lg shadow">
206
+ <div class="p-3">
207
+ <h3 class="font-bold mb-2">Visible Columns</h3>
208
+ <div id="columnCheckboxes" class="space-y-2">
209
+ <!-- Column checkboxes will be dynamically added here -->
210
+ </div>
211
+ <button id="resetColumnsBtn" class="mt-3 text-blue-500 hover:text-blue-700 text-sm">
212
+ Reset to Default
213
+ </button>
214
+ </div>
215
+ </div>
216
+
217
+ <!-- Table Container -->
218
+ <div class="table-container bg-white rounded-lg shadow">
219
+ <div class="table-wrapper">
220
+ <table id="dataTable" class="w-full border-collapse">
221
+ <thead class="table-header">
222
+ <tr id="tableHeader" class="bg-gray-100 text-left">
223
+ <!-- Table headers will be dynamically added here -->
224
+ </tr>
225
+ </thead>
226
+ <tbody id="tableBody">
227
+ <!-- Table data will be dynamically added here -->
228
+ </tbody>
229
+ </table>
230
+ </div>
231
+
232
+ <!-- Pagination -->
233
+ <div id="pagination" class="flex items-center justify-between p-4 border-t border-gray-200">
234
+ <div class="flex items-center gap-2">
235
+ <span class="text-sm text-gray-600">Rows per page:</span>
236
+ <select id="rowsPerPage" class="border border-gray-300 rounded px-2 py-1 text-sm">
237
+ <option value="10">10</option>
238
+ <option value="25">25</option>
239
+ <option value="50">50</option>
240
+ <option value="100">100</option>
241
+ </select>
242
+ </div>
243
+ <div class="flex items-center gap-2">
244
+ <button id="firstPage" class="px-3 py-1 border rounded disabled:opacity-50" disabled>
245
+ <i class="fas fa-angle-double-left"></i>
246
+ </button>
247
+ <button id="prevPage" class="px-3 py-1 border rounded disabled:opacity-50" disabled>
248
+ <i class="fas fa-angle-left"></i>
249
+ </button>
250
+ <span id="pageInfo" class="text-sm text-gray-600">Page 1 of 1</span>
251
+ <button id="nextPage" class="px-3 py-1 border rounded disabled:opacity-50" disabled>
252
+ <i class="fas fa-angle-right"></i>
253
+ </button>
254
+ <button id="lastPage" class="px-3 py-1 border rounded disabled:opacity-50" disabled>
255
+ <i class="fas fa-angle-double-right"></i>
256
+ </button>
257
+ </div>
258
+ </div>
259
+ </div>
260
+ </div>
261
+
262
+ <!-- Tooltip -->
263
+ <div id="tooltip" class="tooltip"></div>
264
+
265
+ <!-- Modal for full text view -->
266
+ <div id="textModal" class="modal">
267
+ <div class="modal-content">
268
+ <div class="flex justify-between items-center mb-4">
269
+ <h3 class="font-bold text-lg" id="modalTitle">Full Text</h3>
270
+ <button id="closeModalBtn" class="text-gray-500 hover:text-gray-700">
271
+ <i class="fas fa-times"></i>
272
+ </button>
273
+ </div>
274
+ <div id="modalContent" class="max-w-4xl max-h-[70vh] overflow-auto"></div>
275
+ </div>
276
+ </div>
277
+
278
+ <script>
279
+ // Sample data
280
+ const sampleData = [
281
+ { id: 1, name: "John Doe", email: "john.doe@example.com", age: 32, status: "Active", joinDate: "2022-01-15", salary: 75000, department: "Engineering", description: "Senior software engineer with 8 years of experience in web development." },
282
+ { id: 2, name: "Jane Smith", email: "jane.smith@example.com", age: 28, status: "Active", joinDate: "2022-03-22", salary: 68000, department: "Marketing", description: "Digital marketing specialist focused on SEO and content strategy." },
283
+ { id: 3, name: "Robert Johnson", email: "robert.j@example.com", age: 45, status: "Inactive", joinDate: "2021-11-05", salary: 92000, department: "Management", description: "Project manager with extensive experience in agile methodologies." },
284
+ { id: 4, name: "Emily Davis", email: "emily.davis@example.com", age: 31, status: "Active", joinDate: "2022-05-18", salary: 71000, department: "Engineering", description: "Frontend developer specializing in React and Vue.js frameworks." },
285
+ { id: 5, name: "Michael Brown", email: "michael.b@example.com", age: 29, status: "Pending", joinDate: "2022-07-30", salary: 65000, department: "Sales", description: "Sales representative with strong customer relationship skills." },
286
+ { id: 6, name: "Sarah Wilson", email: "sarah.w@example.com", age: 36, status: "Active", joinDate: "2021-09-12", salary: 85000, department: "HR", description: "HR manager responsible for recruitment and employee relations." },
287
+ { id: 7, name: "David Taylor", email: "david.t@example.com", age: 42, status: "Inactive", joinDate: "2020-12-01", salary: 88000, department: "Finance", description: "Financial analyst with expertise in budgeting and forecasting." },
288
+ { id: 8, name: "Jessica Martinez", email: "jessica.m@example.com", age: 27, status: "Active", joinDate: "2022-04-05", salary: 69000, department: "Engineering", description: "Backend developer working primarily with Node.js and Python." },
289
+ { id: 9, name: "Thomas Anderson", email: "thomas.a@example.com", age: 38, status: "Active", joinDate: "2021-06-20", salary: 78000, department: "Product", description: "Product owner with background in UX design and business analysis." },
290
+ { id: 10, name: "Lisa Jackson", email: "lisa.j@example.com", age: 33, status: "Pending", joinDate: "2022-08-15", salary: 72000, department: "Marketing", description: "Social media manager creating engaging content for various platforms." },
291
+ { id: 11, name: "James White", email: "james.w@example.com", age: 40, status: "Active", joinDate: "2020-10-10", salary: 95000, department: "Management", description: "Director of operations overseeing multiple departments and projects." },
292
+ { id: 12, name: "Amanda Harris", email: "amanda.h@example.com", age: 26, status: "Active", joinDate: "2022-02-28", salary: 63000, department: "Sales", description: "Junior sales associate learning the ropes of the business." },
293
+ { id: 13, name: "Daniel Martin", email: "daniel.m@example.com", age: 35, status: "Inactive", joinDate: "2021-04-17", salary: 82000, department: "Engineering", description: "DevOps engineer managing cloud infrastructure and CI/CD pipelines." },
294
+ { id: 14, name: "Jennifer Lee", email: "jennifer.l@example.com", age: 30, status: "Active", joinDate: "2022-06-08", salary: 74000, department: "Product", description: "UX designer creating intuitive interfaces for web and mobile applications." },
295
+ { id: 15, name: "Christopher Walker", email: "chris.w@example.com", age: 44, status: "Active", joinDate: "2020-08-25", salary: 91000, department: "Finance", description: "Senior accountant handling corporate financial statements and audits." }
296
+ ];
297
+
298
+ // DOM elements
299
+ const tableHeader = document.getElementById('tableHeader');
300
+ const tableBody = document.getElementById('tableBody');
301
+ const pagination = document.getElementById('pagination');
302
+ const pageInfo = document.getElementById('pageInfo');
303
+ const firstPageBtn = document.getElementById('firstPage');
304
+ const prevPageBtn = document.getElementById('prevPage');
305
+ const nextPageBtn = document.getElementById('nextPage');
306
+ const lastPageBtn = document.getElementById('lastPage');
307
+ const rowsPerPageSelect = document.getElementById('rowsPerPage');
308
+ const globalSearchInput = document.getElementById('globalSearch');
309
+ const uploadBtn = document.getElementById('uploadBtn');
310
+ const downloadBtn = document.getElementById('downloadBtn');
311
+ const pasteBtn = document.getElementById('pasteBtn');
312
+ const loadConfigBtn = document.getElementById('loadConfigBtn');
313
+ const saveConfigBtn = document.getElementById('saveConfigBtn');
314
+ const toggleFiltersBtn = document.getElementById('toggleFiltersBtn');
315
+ const toggleColumnsBtn = document.getElementById('toggleColumnsBtn');
316
+ const filterPanel = document.getElementById('filterPanel');
317
+ const columnOptionsPanel = document.getElementById('columnOptionsPanel');
318
+ const closeFiltersBtn = document.getElementById('closeFiltersBtn');
319
+ const applyFiltersBtn = document.getElementById('applyFiltersBtn');
320
+ const resetFiltersBtn = document.getElementById('resetFiltersBtn');
321
+ const resetColumnsBtn = document.getElementById('resetColumnsBtn');
322
+ const columnCheckboxes = document.getElementById('columnCheckboxes');
323
+ const filterControls = document.getElementById('filterControls');
324
+ const fileDropArea = document.getElementById('fileDropArea');
325
+ const fileInput = document.getElementById('fileInput');
326
+ const jsonUrl = document.getElementById('jsonUrl');
327
+ const loadUrlBtn = document.getElementById('loadUrlBtn');
328
+ const tooltip = document.getElementById('tooltip');
329
+ const textModal = document.getElementById('textModal');
330
+ const modalContent = document.getElementById('modalContent');
331
+ const modalTitle = document.getElementById('modalTitle');
332
+ const closeModalBtn = document.getElementById('closeModalBtn');
333
+
334
+ // State variables
335
+ let data = [...sampleData];
336
+ let filteredData = [...data];
337
+ let currentPage = 1;
338
+ let rowsPerPage = parseInt(rowsPerPageSelect.value);
339
+ let sortColumn = null;
340
+ let sortDirection = 'asc';
341
+ let columnConfig = {};
342
+ let activeFilters = {};
343
+ let isDragging = false;
344
+ let dragStartX = 0;
345
+ let dragStartWidth = 0;
346
+ let dragColumnIndex = -1;
347
+ let dragColumnElement = null;
348
+ let isDraggingColumn = false;
349
+ let dragStartColumnX = 0;
350
+ let draggedColumnIndex = -1;
351
+ let columnOrder = [];
352
+
353
+ // Initialize the table
354
+ function initTable() {
355
+ if (data.length === 0) return;
356
+
357
+ // Initialize column configuration if not already set
358
+ if (Object.keys(columnConfig).length === 0) {
359
+ const firstItem = data[0];
360
+ columnOrder = Object.keys(firstItem);
361
+
362
+ Object.keys(firstItem).forEach(key => {
363
+ columnConfig[key] = {
364
+ visible: true,
365
+ width: 200, // Default width
366
+ type: detectType(firstItem[key])
367
+ };
368
+ });
369
+ }
370
+
371
+ renderTableHeaders();
372
+ renderTableBody();
373
+ renderPagination();
374
+ renderColumnOptions();
375
+ renderFilterControls();
376
+ }
377
+
378
+ // Detect data type for a value
379
+ function detectType(value) {
380
+ if (typeof value === 'number') return 'number';
381
+ if (typeof value === 'boolean') return 'boolean';
382
+ if (Date.parse(value)) return 'date';
383
+ if (typeof value === 'string') {
384
+ // Check if it's an enum (limited distinct values)
385
+ const distinctValues = [...new Set(data.map(item => item[Object.keys(item).find(k => k === Object.keys(item)[0])]))];
386
+ if (distinctValues.length <= data.length * 0.2) return 'enum';
387
+ return 'string';
388
+ }
389
+ return 'string';
390
+ }
391
+
392
+ // Render table headers
393
+ function renderTableHeaders() {
394
+ tableHeader.innerHTML = '';
395
+
396
+ columnOrder.forEach((key, index) => {
397
+ if (!columnConfig[key] || !columnConfig[key].visible) return;
398
+
399
+ const th = document.createElement('th');
400
+ th.className = 'p-3 border-b border-gray-200 font-semibold text-gray-700 relative';
401
+ th.style.width = `${columnConfig[key].width}px`;
402
+ th.dataset.column = key;
403
+
404
+ // Column header content
405
+ const headerContent = document.createElement('div');
406
+ headerContent.className = 'flex items-center justify-between';
407
+
408
+ const titleSpan = document.createElement('span');
409
+ titleSpan.className = 'draggable-header';
410
+ titleSpan.textContent = key;
411
+ titleSpan.dataset.column = key;
412
+
413
+ // Sort indicator
414
+ const sortIcon = document.createElement('i');
415
+ sortIcon.className = 'fas fa-sort sort-icon ml-2 text-gray-400';
416
+ if (sortColumn === key) {
417
+ sortIcon.classList.add(sortDirection === 'asc' ? 'fa-sort-up' : 'fa-sort-down');
418
+ sortIcon.classList.add('text-blue-500');
419
+ }
420
+
421
+ // Column menu button
422
+ const menuBtn = document.createElement('button');
423
+ menuBtn.className = 'ml-2 text-gray-400 hover:text-gray-600';
424
+ menuBtn.innerHTML = '<i class="fas fa-ellipsis-v"></i>';
425
+ menuBtn.onclick = (e) => {
426
+ e.stopPropagation();
427
+ showColumnMenu(e.target.closest('th'), key);
428
+ };
429
+
430
+ headerContent.appendChild(titleSpan);
431
+ headerContent.appendChild(sortIcon);
432
+ headerContent.appendChild(menuBtn);
433
+ th.appendChild(headerContent);
434
+
435
+ // Resize handle
436
+ const resizeHandle = document.createElement('div');
437
+ resizeHandle.className = 'resize-handle';
438
+ th.appendChild(resizeHandle);
439
+
440
+ // Add event listeners
441
+ th.addEventListener('click', () => sortTable(key));
442
+
443
+ // Drag and drop for column reordering
444
+ th.addEventListener('mousedown', (e) => {
445
+ if (e.target.classList.contains('resize-handle')) {
446
+ // Column resize
447
+ isDragging = true;
448
+ dragStartX = e.clientX;
449
+ dragStartWidth = th.offsetWidth;
450
+ dragColumnIndex = index;
451
+ dragColumnElement = th;
452
+ document.body.style.cursor = 'col-resize';
453
+ } else if (e.target.classList.contains('draggable-header') || e.target.closest('.draggable-header')) {
454
+ // Column reorder
455
+ isDraggingColumn = true;
456
+ draggedColumnIndex = index;
457
+ dragStartColumnX = e.clientX;
458
+ th.style.opacity = '0.7';
459
+ document.body.style.cursor = 'move';
460
+ }
461
+ });
462
+
463
+ tableHeader.appendChild(th);
464
+ });
465
+ }
466
+
467
+ // Show column menu
468
+ function showColumnMenu(headerElement, columnKey) {
469
+ // Close any existing menus
470
+ document.querySelectorAll('.column-menu').forEach(el => el.remove());
471
+
472
+ const menu = document.createElement('div');
473
+ menu.className = 'column-menu absolute bg-white shadow-lg rounded-md py-1 z-20';
474
+ menu.style.top = `${headerElement.offsetTop + headerElement.offsetHeight}px`;
475
+ menu.style.left = `${headerElement.offsetLeft}px`;
476
+
477
+ const menuItems = [
478
+ { label: 'Hide Column', icon: 'fa-eye-slash', action: () => toggleColumnVisibility(columnKey, false) },
479
+ { label: 'Auto Fit Width', icon: 'fa-arrows-alt-h', action: () => autoFitColumn(columnKey) },
480
+ { label: 'Reset Width', icon: 'fa-undo', action: () => resetColumnWidth(columnKey) },
481
+ { label: 'Filter', icon: 'fa-filter', action: () => showFilterForColumn(columnKey) }
482
+ ];
483
+
484
+ menuItems.forEach(item => {
485
+ const menuItem = document.createElement('button');
486
+ menuItem.className = 'w-full text-left px-4 py-2 hover:bg-gray-100 flex items-center gap-2';
487
+ menuItem.innerHTML = `<i class="fas ${item.icon}"></i> ${item.label}`;
488
+ menuItem.onclick = (e) => {
489
+ e.stopPropagation();
490
+ item.action();
491
+ menu.remove();
492
+ };
493
+ menu.appendChild(menuItem);
494
+ });
495
+
496
+ headerElement.appendChild(menu);
497
+
498
+ // Close menu when clicking elsewhere
499
+ setTimeout(() => {
500
+ const closeMenu = (e) => {
501
+ if (!headerElement.contains(e.target)) {
502
+ menu.remove();
503
+ document.removeEventListener('click', closeMenu);
504
+ }
505
+ };
506
+ document.addEventListener('click', closeMenu);
507
+ }, 0);
508
+ }
509
+
510
+ // Toggle column visibility
511
+ function toggleColumnVisibility(columnKey, visible) {
512
+ if (typeof visible === 'undefined') {
513
+ columnConfig[columnKey].visible = !columnConfig[columnKey].visible;
514
+ } else {
515
+ columnConfig[columnKey].visible = visible;
516
+ }
517
+ renderTableHeaders();
518
+ renderTableBody();
519
+ renderColumnOptions();
520
+ }
521
+
522
+ // Auto fit column width
523
+ function autoFitColumn(columnKey) {
524
+ // Simple implementation - could be enhanced
525
+ const maxContentWidth = Math.max(
526
+ ...data.map(item => {
527
+ const value = item[columnKey];
528
+ return measureTextWidth(value !== undefined && value !== null ? value.toString() : '', '14px Arial');
529
+ }),
530
+ measureTextWidth(columnKey, '14px Arial') // Include header width
531
+ );
532
+
533
+ columnConfig[columnKey].width = Math.min(Math.max(maxContentWidth + 40, 100), 500); // Min 100, max 500
534
+ renderTableHeaders();
535
+ renderTableBody();
536
+ }
537
+
538
+ // Reset column width
539
+ function resetColumnWidth(columnKey) {
540
+ columnConfig[columnKey].width = 200;
541
+ renderTableHeaders();
542
+ renderTableBody();
543
+ }
544
+
545
+ // Show filter for specific column
546
+ function showFilterForColumn(columnKey) {
547
+ filterPanel.classList.remove('hidden');
548
+ toggleFiltersBtn.classList.add('bg-blue-500', 'text-white');
549
+ toggleFiltersBtn.classList.remove('bg-gray-200', 'text-gray-800');
550
+
551
+ // Scroll to the filter if it exists
552
+ const existingFilter = document.querySelector(`.filter-control[data-column="${columnKey}"]`);
553
+ if (existingFilter) {
554
+ existingFilter.scrollIntoView({ behavior: 'smooth', block: 'nearest' });
555
+ return;
556
+ }
557
+
558
+ // Otherwise add the filter
559
+ addFilterControl(columnKey);
560
+ }
561
+
562
+ // Measure text width
563
+ function measureTextWidth(text, font) {
564
+ const canvas = document.createElement('canvas');
565
+ const context = canvas.getContext('2d');
566
+ context.font = font || '14px Arial';
567
+ return context.measureText(text).width;
568
+ }
569
+
570
+ // Render table body
571
+ function renderTableBody() {
572
+ tableBody.innerHTML = '';
573
+
574
+ if (filteredData.length === 0) {
575
+ const tr = document.createElement('tr');
576
+ const td = document.createElement('td');
577
+ td.className = 'p-4 text-center text-gray-500';
578
+ td.colSpan = columnOrder.filter(key => columnConfig[key]?.visible).length;
579
+ td.textContent = 'No data available';
580
+ tr.appendChild(td);
581
+ tableBody.appendChild(tr);
582
+ return;
583
+ }
584
+
585
+ const startIndex = (currentPage - 1) * rowsPerPage;
586
+ const endIndex = Math.min(startIndex + rowsPerPage, filteredData.length);
587
+
588
+ for (let i = startIndex; i < endIndex; i++) {
589
+ const item = filteredData[i];
590
+ const tr = document.createElement('tr');
591
+ tr.className = i % 2 === 0 ? 'bg-white' : 'bg-gray-50';
592
+
593
+ columnOrder.forEach(key => {
594
+ if (!columnConfig[key] || !columnConfig[key].visible) return;
595
+
596
+ const td = document.createElement('td');
597
+ td.className = 'p-3 border-b border-gray-200 text-gray-700 relative';
598
+
599
+ // Handle long text with ellipsis
600
+ const cellValue = item[key] !== undefined && item[key] !== null ? item[key].toString() : '';
601
+ const cellDiv = document.createElement('div');
602
+ cellDiv.className = 'ellipsis-text';
603
+ cellDiv.textContent = cellValue;
604
+ cellDiv.title = cellValue;
605
+
606
+ // Add click to view full text for long content
607
+ if (cellValue.length > 50) {
608
+ cellDiv.style.cursor = 'pointer';
609
+ cellDiv.onclick = () => showFullText(key, cellValue);
610
+ }
611
+
612
+ td.appendChild(cellDiv);
613
+ tr.appendChild(td);
614
+ });
615
+
616
+ tableBody.appendChild(tr);
617
+ }
618
+ }
619
+
620
+ // Show full text in modal
621
+ function showFullText(title, content) {
622
+ modalTitle.textContent = title;
623
+ modalContent.textContent = content;
624
+ textModal.style.display = 'flex';
625
+ }
626
+
627
+ // Render pagination
628
+ function renderPagination() {
629
+ const totalPages = Math.ceil(filteredData.length / rowsPerPage);
630
+
631
+ pageInfo.textContent = `Page ${currentPage} of ${totalPages}`;
632
+ firstPageBtn.disabled = currentPage === 1;
633
+ prevPageBtn.disabled = currentPage === 1;
634
+ nextPageBtn.disabled = currentPage === totalPages || totalPages === 0;
635
+ lastPageBtn.disabled = currentPage === totalPages || totalPages === 0;
636
+ }
637
+
638
+ // Sort table
639
+ function sortTable(columnKey) {
640
+ if (sortColumn === columnKey) {
641
+ sortDirection = sortDirection === 'asc' ? 'desc' : 'asc';
642
+ } else {
643
+ sortColumn = columnKey;
644
+ sortDirection = 'asc';
645
+ }
646
+
647
+ filteredData.sort((a, b) => {
648
+ const valA = a[columnKey];
649
+ const valB = b[columnKey];
650
+
651
+ if (valA === valB) return 0;
652
+ if (valA === undefined || valA === null) return 1;
653
+ if (valB === undefined || valB === null) return -1;
654
+
655
+ if (typeof valA === 'number' && typeof valB === 'number') {
656
+ return sortDirection === 'asc' ? valA - valB : valB - valA;
657
+ }
658
+
659
+ if (typeof valA === 'string' && typeof valB === 'string') {
660
+ const strA = valA.toString().toLowerCase();
661
+ const strB = valB.toString().toLowerCase();
662
+ return sortDirection === 'asc'
663
+ ? strA.localeCompare(strB)
664
+ : strB.localeCompare(strA);
665
+ }
666
+
667
+ if (Date.parse(valA) && Date.parse(valB)) {
668
+ const dateA = new Date(valA);
669
+ const dateB = new Date(valB);
670
+ return sortDirection === 'asc'
671
+ ? dateA - dateB
672
+ : dateB - dateA;
673
+ }
674
+
675
+ return 0;
676
+ });
677
+
678
+ renderTableHeaders();
679
+ renderTableBody();
680
+ }
681
+
682
+ // Filter table
683
+ function filterTable() {
684
+ filteredData = data.filter(item => {
685
+ return Object.entries(activeFilters).every(([columnKey, filter]) => {
686
+ if (!filter || !filter.value) return true;
687
+
688
+ const itemValue = item[columnKey];
689
+ if (itemValue === undefined || itemValue === null) return false;
690
+
691
+ const strValue = itemValue.toString().toLowerCase();
692
+ const filterValue = filter.value.toString().toLowerCase();
693
+
694
+ switch (filter.type) {
695
+ case 'string':
696
+ return strValue.includes(filterValue);
697
+ case 'number':
698
+ if (isNaN(itemValue) || isNaN(filter.value)) return false;
699
+ return filter.operator === '='
700
+ ? itemValue == filter.value
701
+ : filter.operator === '<'
702
+ ? itemValue < filter.value
703
+ : itemValue > filter.value;
704
+ case 'date':
705
+ const itemDate = new Date(itemValue);
706
+ const filterDate = new Date(filter.value);
707
+ return filter.operator === '='
708
+ ? itemDate.getTime() === filterDate.getTime()
709
+ : filter.operator === '<'
710
+ ? itemDate < filterDate
711
+ : itemDate > filterDate;
712
+ case 'enum':
713
+ return strValue === filterValue;
714
+ default:
715
+ return strValue.includes(filterValue);
716
+ }
717
+ });
718
+ });
719
+
720
+ // Apply global search if present
721
+ if (globalSearchInput.value.trim()) {
722
+ const searchTerm = globalSearchInput.value.trim().toLowerCase();
723
+ filteredData = filteredData.filter(item => {
724
+ return Object.entries(item).some(([key, value]) => {
725
+ if (!columnConfig[key]?.visible) return false;
726
+ return value !== undefined && value !== null &&
727
+ value.toString().toLowerCase().includes(searchTerm);
728
+ });
729
+ });
730
+ }
731
+
732
+ // Reset to first page after filtering
733
+ currentPage = 1;
734
+ renderTableBody();
735
+ renderPagination();
736
+ }
737
+
738
+ // Render column options
739
+ function renderColumnOptions() {
740
+ columnCheckboxes.innerHTML = '';
741
+
742
+ columnOrder.forEach(key => {
743
+ const div = document.createElement('div');
744
+ div.className = 'flex items-center';
745
+
746
+ const checkbox = document.createElement('input');
747
+ checkbox.type = 'checkbox';
748
+ checkbox.id = `col-${key}`;
749
+ checkbox.className = 'mr-2';
750
+ checkbox.checked = columnConfig[key]?.visible || false;
751
+ checkbox.onchange = () => toggleColumnVisibility(key, checkbox.checked);
752
+
753
+ const label = document.createElement('label');
754
+ label.htmlFor = `col-${key}`;
755
+ label.textContent = key;
756
+ label.className = 'text-sm';
757
+
758
+ div.appendChild(checkbox);
759
+ div.appendChild(label);
760
+ columnCheckboxes.appendChild(div);
761
+ });
762
+ }
763
+
764
+ // Render filter controls
765
+ function renderFilterControls() {
766
+ filterControls.innerHTML = '';
767
+
768
+ columnOrder.forEach(key => {
769
+ if (!activeFilters[key]) return;
770
+
771
+ const filter = activeFilters[key];
772
+ const div = document.createElement('div');
773
+ div.className = 'filter-control border-b border-gray-200 pb-4 mb-4';
774
+ div.dataset.column = key;
775
+
776
+ const header = document.createElement('div');
777
+ header.className = 'flex justify-between items-center mb-2';
778
+
779
+ const title = document.createElement('h4');
780
+ title.className = 'font-medium';
781
+ title.textContent = key;
782
+
783
+ const removeBtn = document.createElement('button');
784
+ removeBtn.className = 'text-red-500 hover:text-red-700';
785
+ removeBtn.innerHTML = '<i class="fas fa-times"></i>';
786
+ removeBtn.onclick = () => {
787
+ delete activeFilters[key];
788
+ renderFilterControls();
789
+ filterTable();
790
+ };
791
+
792
+ header.appendChild(title);
793
+ header.appendChild(removeBtn);
794
+ div.appendChild(header);
795
+
796
+ // Filter controls based on type
797
+ if (filter.type === 'number' || filter.type === 'date') {
798
+ const operatorSelect = document.createElement('select');
799
+ operatorSelect.className = 'border border-gray-300 rounded px-2 py-1 mr-2';
800
+ operatorSelect.value = filter.operator || '=';
801
+ operatorSelect.onchange = (e) => {
802
+ activeFilters[key].operator = e.target.value;
803
+ };
804
+
805
+ ['=', '<', '>'].forEach(op => {
806
+ const option = document.createElement('option');
807
+ option.value = op;
808
+ option.textContent = op === '=' ? 'equals' : op === '<' ? 'less than' : 'greater than';
809
+ operatorSelect.appendChild(option);
810
+ });
811
+
812
+ const valueInput = document.createElement('input');
813
+ valueInput.type = filter.type === 'date' ? 'date' : 'number';
814
+ valueInput.className = 'border border-gray-300 rounded px-2 py-1';
815
+ valueInput.value = filter.value || '';
816
+ valueInput.onchange = (e) => {
817
+ activeFilters[key].value = e.target.value;
818
+ };
819
+
820
+ div.appendChild(operatorSelect);
821
+ div.appendChild(valueInput);
822
+ }
823
+ else if (filter.type === 'enum') {
824
+ const distinctValues = [...new Set(data.map(item => item[key]))].sort();
825
+
826
+ const select = document.createElement('select');
827
+ select.className = 'w-full border border-gray-300 rounded px-2 py-1';
828
+ select.value = filter.value || '';
829
+
830
+ const emptyOption = document.createElement('option');
831
+ emptyOption.value = '';
832
+ emptyOption.textContent = 'Select a value';
833
+ select.appendChild(emptyOption);
834
+
835
+ distinctValues.forEach(value => {
836
+ const option = document.createElement('option');
837
+ option.value = value;
838
+ option.textContent = value;
839
+ select.appendChild(option);
840
+ });
841
+
842
+ select.value = filter.value || '';
843
+ select.onchange = (e) => {
844
+ activeFilters[key].value = e.target.value;
845
+ };
846
+
847
+ div.appendChild(select);
848
+ }
849
+ else { // string or other types
850
+ const input = document.createElement('input');
851
+ input.type = 'text';
852
+ input.className = 'w-full border border-gray-300 rounded px-2 py-1';
853
+ input.placeholder = `Filter by ${key}`;
854
+ input.value = filter.value || '';
855
+ input.onchange = (e) => {
856
+ activeFilters[key].value = e.target.value;
857
+ };
858
+
859
+ div.appendChild(input);
860
+ }
861
+
862
+ filterControls.appendChild(div);
863
+ });
864
+ }
865
+
866
+ // Add filter control
867
+ function addFilterControl(columnKey) {
868
+ if (!columnConfig[columnKey]) return;
869
+
870
+ // Initialize filter if it doesn't exist
871
+ if (!activeFilters[columnKey]) {
872
+ activeFilters[columnKey] = {
873
+ type: columnConfig[columnKey].type,
874
+ value: '',
875
+ operator: '='
876
+ };
877
+ }
878
+
879
+ renderFilterControls();
880
+
881
+ // Scroll to the new filter
882
+ const newFilter = document.querySelector(`.filter-control[data-column="${columnKey}"]`);
883
+ if (newFilter) {
884
+ newFilter.scrollIntoView({ behavior: 'smooth', block: 'nearest' });
885
+ }
886
+ }
887
+
888
+ // Event listeners
889
+ document.addEventListener('mousemove', (e) => {
890
+ // Column resize
891
+ if (isDragging && dragColumnElement) {
892
+ const width = dragStartWidth + (e.clientX - dragStartX);
893
+ const columnKey = dragColumnElement.dataset.column;
894
+ columnConfig[columnKey].width = Math.max(width, 50); // Minimum width
895
+ dragColumnElement.style.width = `${columnConfig[columnKey].width}px`;
896
+ }
897
+
898
+ // Column reordering
899
+ if (isDraggingColumn) {
900
+ // Visual feedback could be added here (like a placeholder or shadow)
901
+ }
902
+ });
903
+
904
+ document.addEventListener('mouseup', () => {
905
+ if (isDragging) {
906
+ isDragging = false;
907
+ document.body.style.cursor = '';
908
+ if (dragColumnElement) {
909
+ dragColumnElement.style.opacity = '1';
910
+ dragColumnElement = null;
911
+ }
912
+ }
913
+
914
+ if (isDraggingColumn) {
915
+ isDraggingColumn = false;
916
+ document.body.style.cursor = '';
917
+
918
+ // Update column order if the drag position has changed significantly
919
+ if (draggedColumnIndex >= 0) {
920
+ const thElements = tableHeader.querySelectorAll('th');
921
+ thElements.forEach(th => th.style.opacity = '1');
922
+
923
+ // Determine the new position based on mouse position
924
+ const targetIndex = calculateNewColumnPosition(draggedColumnIndex);
925
+ if (targetIndex !== draggedColumnIndex) {
926
+ // Update column order
927
+ const columnKey = columnOrder[draggedColumnIndex];
928
+ columnOrder.splice(draggedColumnIndex, 1);
929
+ columnOrder.splice(targetIndex, 0, columnKey);
930
+
931
+ // Re-render table
932
+ renderTableHeaders();
933
+ renderTableBody();
934
+ }
935
+ }
936
+ }
937
+ });
938
+
939
+ // Calculate new column position when dragging
940
+ function calculateNewColumnPosition(currentIndex) {
941
+ const thElements = tableHeader.querySelectorAll('th');
942
+ if (thElements.length === 0) return currentIndex;
943
+
944
+ // Simple implementation - could be enhanced
945
+ return currentIndex; // Placeholder
946
+ }
947
+
948
+ // Pagination controls
949
+ firstPageBtn.addEventListener('click', () => {
950
+ currentPage = 1;
951
+ renderTableBody();
952
+ renderPagination();
953
+ });
954
+
955
+ prevPageBtn.addEventListener('click', () => {
956
+ if (currentPage > 1) {
957
+ currentPage--;
958
+ renderTableBody();
959
+ renderPagination();
960
+ }
961
+ });
962
+
963
+ nextPageBtn.addEventListener('click', () => {
964
+ const totalPages = Math.ceil(filteredData.length / rowsPerPage);
965
+ if (currentPage < totalPages) {
966
+ currentPage++;
967
+ renderTableBody();
968
+ renderPagination();
969
+ }
970
+ });
971
+
972
+ lastPageBtn.addEventListener('click', () => {
973
+ const totalPages = Math.ceil(filteredData.length / rowsPerPage);
974
+ currentPage = totalPages;
975
+ renderTableBody();
976
+ renderPagination();
977
+ });
978
+
979
+ rowsPerPageSelect.addEventListener('change', () => {
980
+ rowsPerPage = parseInt(rowsPerPageSelect.value);
981
+ currentPage = 1;
982
+ renderTableBody();
983
+ renderPagination();
984
+ });
985
+
986
+ // Global search
987
+ globalSearchInput.addEventListener('input', () => {
988
+ currentPage = 1;
989
+ filterTable();
990
+ });
991
+
992
+ // Toggle filters panel
993
+ toggleFiltersBtn.addEventListener('click', () => {
994
+ filterPanel.classList.toggle('hidden');
995
+
996
+ if (filterPanel.classList.contains('hidden')) {
997
+ toggleFiltersBtn.classList.remove('bg-blue-500', 'text-white');
998
+ toggleFiltersBtn.classList.add('bg-gray-200', 'text-gray-800');
999
+ } else {
1000
+ toggleFiltersBtn.classList.add('bg-blue-500', 'text-white');
1001
+ toggleFiltersBtn.classList.remove('bg-gray-200', 'text-gray-800');
1002
+ columnOptionsPanel.classList.add('hidden');
1003
+ }
1004
+ });
1005
+
1006
+ // Toggle columns panel
1007
+ toggleColumnsBtn.addEventListener('click', (e) => {
1008
+ columnOptionsPanel.classList.toggle('hidden');
1009
+
1010
+ if (columnOptionsPanel.classList.contains('hidden')) {
1011
+ toggleColumnsBtn.classList.remove('bg-blue-500', 'text-white');
1012
+ toggleColumnsBtn.classList.add('bg-gray-200', 'text-gray-800');
1013
+ } else {
1014
+ toggleColumnsBtn.classList.add('bg-blue-500', 'text-white');
1015
+ toggleColumnsBtn.classList.remove('bg-gray-200', 'text-gray-800');
1016
+ filterPanel.classList.add('hidden');
1017
+
1018
+ // Position the panel near the button
1019
+ columnOptionsPanel.style.right = '0';
1020
+ columnOptionsPanel.style.left = 'auto';
1021
+ }
1022
+ });
1023
+
1024
+ // Close filters
1025
+ closeFiltersBtn.addEventListener('click', () => {
1026
+ filterPanel.classList.add('hidden');
1027
+ toggleFiltersBtn.classList.remove('bg-blue-500', 'text-white');
1028
+ toggleFiltersBtn.classList.add('bg-gray-200', 'text-gray-800');
1029
+ });
1030
+
1031
+ // Apply filters
1032
+ applyFiltersBtn.addEventListener('click', () => {
1033
+ filterTable();
1034
+ });
1035
+
1036
+ // Reset filters
1037
+ resetFiltersBtn.addEventListener('click', () => {
1038
+ activeFilters = {};
1039
+ globalSearchInput.value = '';
1040
+ renderFilterControls();
1041
+ filterTable();
1042
+ });
1043
+
1044
+ // Reset columns
1045
+ resetColumnsBtn.addEventListener('click', () => {
1046
+ const firstItem = data[0];
1047
+ columnOrder = Object.keys(firstItem);
1048
+
1049
+ Object.keys(firstItem).forEach(key => {
1050
+ columnConfig[key] = {
1051
+ visible: true,
1052
+ width: 200,
1053
+ type: detectType(firstItem[key])
1054
+ };
1055
+ });
1056
+
1057
+ renderTableHeaders();
1058
+ renderTableBody();
1059
+ renderColumnOptions();
1060
+ renderFilterControls();
1061
+ });
1062
+
1063
+ // File upload
1064
+ uploadBtn.addEventListener('click', () => {
1065
+ fileInput.click();
1066
+ });
1067
+
1068
+ fileInput.addEventListener('change', (e) => {
1069
+ const file = e.target.files[0];
1070
+ if (!file) return;
1071
+
1072
+ const reader = new FileReader();
1073
+ reader.onload = (event) => {
1074
+ try {
1075
+ data = JSON.parse(event.target.result);
1076
+ filteredData = [...data];
1077
+ columnConfig = {};
1078
+ columnOrder = [];
1079
+ activeFilters = {};
1080
+ currentPage = 1;
1081
+ initTable();
1082
+ } catch (error) {
1083
+ alert('Error parsing JSON file: ' + error.message);
1084
+ }
1085
+ };
1086
+ reader.readAsText(file);
1087
+ fileInput.value = ''; // Reset input
1088
+ });
1089
+
1090
+ // File drop area
1091
+ fileDropArea.addEventListener('dragover', (e) => {
1092
+ e.preventDefault();
1093
+ fileDropArea.classList.add('active');
1094
+ });
1095
+
1096
+ fileDropArea.addEventListener('dragleave', () => {
1097
+ fileDropArea.classList.remove('active');
1098
+ });
1099
+
1100
+ fileDropArea.addEventListener('drop', (e) => {
1101
+ e.preventDefault();
1102
+ fileDropArea.classList.remove('active');
1103
+
1104
+ const file = e.dataTransfer.files[0];
1105
+ if (!file) return;
1106
+
1107
+ if (file.name.endsWith('.json')) {
1108
+ fileInput.files = e.dataTransfer.files;
1109
+ const event = new Event('change');
1110
+ fileInput.dispatchEvent(event);
1111
+ } else {
1112
+ alert('Please upload a JSON file');
1113
+ }
1114
+ });
1115
+
1116
+ fileDropArea.addEventListener('click', () => {
1117
+ fileInput.click();
1118
+ });
1119
+
1120
+ // Download data
1121
+ downloadBtn.addEventListener('click', () => {
1122
+ const dataStr = 'data:text/json;charset=utf-8,' + encodeURIComponent(JSON.stringify(filteredData, null, 2));
1123
+ const downloadAnchorNode = document.createElement('a');
1124
+ downloadAnchorNode.setAttribute('href', dataStr);
1125
+ downloadAnchorNode.setAttribute('download', 'table_data.json');
1126
+ document.body.appendChild(downloadAnchorNode);
1127
+ downloadAnchorNode.click();
1128
+ downloadAnchorNode.remove();
1129
+ });
1130
+
1131
+ // Paste from clipboard
1132
+ pasteBtn.addEventListener('click', async () => {
1133
+ try {
1134
+ const text = await navigator.clipboard.readText();
1135
+ data = JSON.parse(text);
1136
+ filteredData = [...data];
1137
+ columnConfig = {};
1138
+ columnOrder = [];
1139
+ activeFilters = {};
1140
+ currentPage = 1;
1141
+ initTable();
1142
+ } catch (error) {
1143
+ alert('Error pasting from clipboard: ' + error.message);
1144
+ }
1145
+ });
1146
+
1147
+ // Load from URL
1148
+ loadUrlBtn.addEventListener('click', async () => {
1149
+ const url = jsonUrl.value.trim();
1150
+ if (!url) return;
1151
+
1152
+ try {
1153
+ const response = await fetch(url);
1154
+ if (!response.ok) throw new Error('Failed to fetch data');
1155
+
1156
+ data = await response.json();
1157
+ filteredData = [...data];
1158
+ columnConfig = {};
1159
+ columnOrder = [];
1160
+ activeFilters = {};
1161
+ currentPage = 1;
1162
+ initTable();
1163
+ } catch (error) {
1164
+ alert('Error loading from URL: ' + error.message);
1165
+ }
1166
+ });
1167
+
1168
+ // Save config
1169
+ saveConfigBtn.addEventListener('click', () => {
1170
+ const config = {
1171
+ columnConfig,
1172
+ columnOrder,
1173
+ sortColumn,
1174
+ sortDirection
1175
+ };
1176
+
1177
+ const dataStr = 'data:text/json;charset=utf-8,' + encodeURIComponent(JSON.stringify(config, null, 2));
1178
+ const downloadAnchorNode = document.createElement('a');
1179
+ downloadAnchorNode.setAttribute('href', dataStr);
1180
+ downloadAnchorNode.setAttribute('download', 'table_config.json');
1181
+ document.body.appendChild(downloadAnchorNode);
1182
+ downloadAnchorNode.click();
1183
+ downloadAnchorNode.remove();
1184
+ });
1185
+
1186
+ // Load config
1187
+ loadConfigBtn.addEventListener('click', () => {
1188
+ const input = document.createElement('input');
1189
+ input.type = 'file';
1190
+ input.accept = '.json';
1191
+
1192
+ input.onchange = e => {
1193
+ const file = e.target.files[0];
1194
+ if (!file) return;
1195
+
1196
+ const reader = new FileReader();
1197
+ reader.onload = event => {
1198
+ try {
1199
+ const config = JSON.parse(event.target.result);
1200
+
1201
+ if (config.columnConfig) columnConfig = config.columnConfig;
1202
+ if (config.columnOrder) columnOrder = config.columnOrder;
1203
+ if (config.sortColumn) sortColumn = config.sortColumn;
1204
+ if (config.sortDirection) sortDirection = config.sortDirection;
1205
+
1206
+ // Make sure all current columns are included in config
1207
+ if (data.length > 0) {
1208
+ const firstItem = data[0];
1209
+ Object.keys(firstItem).forEach(key => {
1210
+ if (!columnConfig[key]) {
1211
+ columnConfig[key] = {
1212
+ visible: true,
1213
+ width: 200,
1214
+ type: detectType(firstItem[key])
1215
+ };
1216
+ }
1217
+ if (!columnOrder.includes(key)) {
1218
+ columnOrder.push(key);
1219
+ }
1220
+ });
1221
+ }
1222
+
1223
+ renderTableHeaders();
1224
+ renderTableBody();
1225
+ renderColumnOptions();
1226
+ renderFilterControls();
1227
+ renderPagination();
1228
+ } catch (error) {
1229
+ alert('Error parsing config file: ' + error.message);
1230
+ }
1231
+ };
1232
+ reader.readAsText(file);
1233
+ };
1234
+
1235
+ input.click();
1236
+ });
1237
+
1238
+ // Close modal
1239
+ closeModalBtn.addEventListener('click', () => {
1240
+ textModal.style.display = 'none';
1241
+ });
1242
+
1243
+ // Click outside modal to close
1244
+ textModal.addEventListener('click', (e) => {
1245
+ if (e.target === textModal) {
1246
+ textModal.style.display = 'none';
1247
+ }
1248
+ });
1249
+
1250
+ // Initialize the table on load
1251
+ window.addEventListener('load', initTable);
1252
+ </script>
1253
+ <p style="border-radius: 8px; text-align: center; font-size: 12px; color: #fff; margin-top: 16px;position: fixed; left: 8px; bottom: 8px; z-index: 10; background: rgba(0, 0, 0, 0.8); padding: 4px 8px;">Made with <img src="https://enzostvs-deepsite.hf.space/logo.svg" alt="DeepSite Logo" style="width: 16px; height: 16px; vertical-align: middle;display:inline-block;margin-right:3px;filter:brightness(0) invert(1);"><a href="https://enzostvs-deepsite.hf.space" style="color: #fff;text-decoration: underline;" target="_blank" >DeepSite</a> - <a href="https://enzostvs-deepsite.hf.space?remix=weisanju/frontend" style="color: #fff;text-decoration: underline;" target="_blank" >🧬 Remix</a></p><p style="border-radius: 8px; text-align: center; font-size: 12px; color: #fff; margin-top: 16px;position: fixed; left: 8px; bottom: 8px; z-index: 10; background: rgba(0, 0, 0, 0.8); padding: 4px 8px;">Made with <img src="https://enzostvs-deepsite.hf.space/logo.svg" alt="DeepSite Logo" style="width: 16px; height: 16px; vertical-align: middle;display:inline-block;margin-right:3px;filter:brightness(0) invert(1);"><a href="https://enzostvs-deepsite.hf.space" style="color: #fff;text-decoration: underline;" target="_blank" >DeepSite</a> - 🧬 <a href="https://enzostvs-deepsite.hf.space?remix=Yoleo/tabla-json" style="color: #fff;text-decoration: underline;" target="_blank" >Remix</a></p></body>
1254
+ </html>