mrwhy06 commited on
Commit
a8c9706
·
verified ·
1 Parent(s): b2d9fef

Add 2 files

Browse files
Files changed (2) hide show
  1. README.md +7 -5
  2. index.html +1287 -19
README.md CHANGED
@@ -1,10 +1,12 @@
1
  ---
2
- title: Monitor
3
- emoji: 🏢
4
- colorFrom: yellow
5
- colorTo: purple
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: monitor
3
+ emoji: 🐳
4
+ colorFrom: blue
5
+ colorTo: blue
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,1287 @@
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>Fruit Ripeness Classification Dashboard</title>
7
+ <script src="https://cdn.tailwindcss.com"></script>
8
+ <script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
9
+ <script src="https://cdnjs.cloudflare.com/ajax/libs/moment.js/2.29.1/moment.min.js"></script>
10
+ <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0/css/all.min.css">
11
+ <style>
12
+ .ripeness-unripe {
13
+ background-color: #fef9c3;
14
+ color: #ca8a04;
15
+ }
16
+ .ripeness-ripe {
17
+ background-color: #dcfce7;
18
+ color: #15803d;
19
+ }
20
+ .ripeness-overripe {
21
+ background-color: #fee2e2;
22
+ color: #b91c1c;
23
+ }
24
+ .confidence-bar {
25
+ position: relative;
26
+ height: 8px;
27
+ background-color: #e5e7eb;
28
+ border-radius: 4px;
29
+ overflow: hidden;
30
+ }
31
+ .confidence-fill {
32
+ position: absolute;
33
+ height: 100%;
34
+ left: 0;
35
+ top: 0;
36
+ border-radius: 4px;
37
+ }
38
+ .high-confidence {
39
+ background-color: #10b981;
40
+ }
41
+ .medium-confidence {
42
+ background-color: #f59e0b;
43
+ }
44
+ .low-confidence {
45
+ background-color: #ef4444;
46
+ }
47
+ .mismatch-highlight {
48
+ border-left: 4px solid #ef4444;
49
+ animation: pulse 2s infinite;
50
+ }
51
+ @keyframes pulse {
52
+ 0% { background-color: white; }
53
+ 50% { background-color: #fee2e2; }
54
+ 100% { background-color: white; }
55
+ }
56
+ .scrollbar-hide::-webkit-scrollbar {
57
+ display: none;
58
+ }
59
+ </style>
60
+ </head>
61
+ <body class="bg-gray-50 min-h-screen">
62
+ <!-- Header -->
63
+ <header class="bg-white shadow">
64
+ <div class="max-w-7xl mx-auto px-4 py-4 sm:px-6 lg:px-8 flex justify-between items-center">
65
+ <div class="flex items-center">
66
+ <div class="flex-shrink-0 flex items-center">
67
+ <i class="fas fa-apple-alt text-3xl text-green-600 mr-2"></i>
68
+ <span class="text-xl font-bold text-gray-900">FruitAI Monitor</span>
69
+ </div>
70
+ </div>
71
+ <div class="flex items-center space-x-4">
72
+ <button id="exportBtn" class="bg-blue-600 hover:bg-blue-700 text-white px-4 py-2 rounded-md text-sm font-medium flex items-center">
73
+ <i class="fas fa-file-export mr-2"></i> Export Data
74
+ </button>
75
+ <div class="relative">
76
+ <div class="absolute inset-y-0 left-0 flex items-center pl-3 pointer-events-none">
77
+ <i class="fas fa-calendar text-gray-500"></i>
78
+ </div>
79
+ <input type="text" id="dateRangePicker" class="bg-gray-50 border border-gray-300 text-gray-900 text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block w-full pl-10 p-2.5" placeholder="Select date range">
80
+ </div>
81
+ </div>
82
+ </div>
83
+ </header>
84
+
85
+ <!-- Main Content -->
86
+ <main class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-6">
87
+ <!-- Summary Cards -->
88
+ <div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-4 mb-6">
89
+ <div class="bg-white rounded-lg shadow p-4">
90
+ <div class="flex items-center justify-between">
91
+ <div>
92
+ <h3 class="text-gray-500 text-sm font-medium">Total Classifications</h3>
93
+ <p id="totalClassifications" class="text-2xl font-bold text-gray-900">0</p>
94
+ </div>
95
+ <div class="p-3 rounded-full bg-blue-100 text-blue-600">
96
+ <i class="fas fa-list-ol text-lg"></i>
97
+ </div>
98
+ </div>
99
+ </div>
100
+
101
+ <div class="bg-white rounded-lg shadow p-4">
102
+ <div class="flex items-center justify-between">
103
+ <div>
104
+ <h3 class="text-gray-500 text-sm font-medium">Accuracy Rate</h3>
105
+ <p id="accuracyRate" class="text-2xl font-bold text-gray-900">0%</p>
106
+ </div>
107
+ <div class="p-3 rounded-full bg-green-100 text-green-600">
108
+ <i class="fas fa-check-circle text-lg"></i>
109
+ </div>
110
+ </div>
111
+ </div>
112
+
113
+ <div class="bg-white rounded-lg shadow p-4">
114
+ <div class="flex items-center justify-between">
115
+ <div>
116
+ <h3 class="text-gray-500 text-sm font-medium">Mismatch Rate</h3>
117
+ <p id="mismatchRate" class="text-2xl font-bold text-gray-900">0%</p>
118
+ </div>
119
+ <div class="p-3 rounded-full bg-red-100 text-red-600">
120
+ <i class="fas fa-times-circle text-lg"></i>
121
+ </div>
122
+ </div>
123
+ </div>
124
+
125
+ <div class="bg-white rounded-lg shadow p-4">
126
+ <div class="flex items-center justify-between">
127
+ <div>
128
+ <h3 class="text-gray-500 text-sm font-medium">Avg Confidence</h3>
129
+ <p id="avgConfidence" class="text-2xl font-bold text-gray-900">0%</p>
130
+ </div>
131
+ <div class="p-3 rounded-full bg-yellow-100 text-yellow-600">
132
+ <i class="fas fa-chart-line text-lg"></i>
133
+ </div>
134
+ </div>
135
+ </div>
136
+ </div>
137
+
138
+ <!-- Charts Section -->
139
+ <div class="grid grid-cols-1 lg:grid-cols-2 gap-6 mb-6">
140
+ <div class="bg-white p-4 rounded-lg shadow">
141
+ <h3 class="text-lg font-medium text-gray-900 mb-4">Accuracy Over Time</h3>
142
+ <canvas id="accuracyChart" height="300"></canvas>
143
+ </div>
144
+
145
+ <div class="bg-white p-4 rounded-lg shadow">
146
+ <h3 class="text-lg font-medium text-gray-900 mb-4">Confidence Distribution</h3>
147
+ <canvas id="confidenceChart" height="300"></canvas>
148
+ </div>
149
+
150
+ <div class="bg-white p-4 rounded-lg shadow">
151
+ <h3 class="text-lg font-medium text-gray-900 mb-4">Classification Distribution</h3>
152
+ <canvas id="classificationChart" height="300"></canvas>
153
+ </div>
154
+
155
+ <div class="bg-white p-4 rounded-lg shadow">
156
+ <h3 class="text-lg font-medium text-gray-900 mb-4">Mismatch Analysis</h3>
157
+ <canvas id="mismatchChart" height="300"></canvas>
158
+ </div>
159
+ </div>
160
+
161
+ <!-- Filters Section -->
162
+ <div class="bg-white rounded-lg shadow p-4 mb-6">
163
+ <h3 class="text-lg font-medium text-gray-900 mb-4">Filters</h3>
164
+ <div class="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-5 gap-4">
165
+ <div>
166
+ <label for="fruitTypeFilter" class="block text-sm font-medium text-gray-700 mb-1">Fruit Type</label>
167
+ <select id="fruitTypeFilter" class="bg-gray-50 border border-gray-300 text-gray-900 text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block w-full p-2.5">
168
+ <option value="">All Fruits</option>
169
+ </select>
170
+ </div>
171
+
172
+ <div>
173
+ <label for="predictionFilter" class="block text-sm font-medium text-gray-700 mb-1">Prediction</label>
174
+ <select id="predictionFilter" class="bg-gray-50 border border-gray-300 text-gray-900 text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block w-full p-2.5">
175
+ <option value="">All</option>
176
+ <option value="unripe">Unripe</option>
177
+ <option value="ripe">Ripe</option>
178
+ <option value="overripe">Overripe</option>
179
+ </select>
180
+ </div>
181
+
182
+ <div>
183
+ <label for="feedbackFilter" class="block text-sm font-medium text-gray-700 mb-1">Feedback</label>
184
+ <select id="feedbackFilter" class="bg-gray-50 border border-gray-300 text-gray-900 text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block w-full p-2.5">
185
+ <option value="">All</option>
186
+ <option value="confirmed">Confirmed</option>
187
+ <option value="overridden">Overridden</option>
188
+ </select>
189
+ </div>
190
+
191
+ <div>
192
+ <label for="confidenceFilter" class="block text-sm font-medium text-gray-700 mb-1">Confidence</label>
193
+ <select id="confidenceFilter" class="bg-gray-50 border border-gray-300 text-gray-900 text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block w-full p-2.5">
194
+ <option value="">All</option>
195
+ <option value="high">High (≥80%)</option>
196
+ <option value="medium">Medium (60-79%)</option>
197
+ <option value="low">Low (<60%)</option>
198
+ </select>
199
+ </div>
200
+
201
+ <div class="flex items-end">
202
+ <button id="applyFilters" class="bg-blue-600 hover:bg-blue-700 text-white px-4 py-2 rounded-md text-sm font-medium w-full flex items-center justify-center">
203
+ <i class="fas fa-filter mr-2"></i> Apply Filters
204
+ </button>
205
+ </div>
206
+ </div>
207
+ </div>
208
+
209
+ <!-- Data Table -->
210
+ <div class="bg-white rounded-lg shadow overflow-hidden">
211
+ <div class="flex justify-between items-center p-4 border-b">
212
+ <h3 class="text-lg font-medium text-gray-900">Classification Results</h3>
213
+ <div class="flex space-x-2">
214
+ <div class="relative">
215
+ <div class="absolute inset-y-0 left-0 flex items-center pl-3 pointer-events-none">
216
+ <i class="fas fa-search text-gray-400"></i>
217
+ </div>
218
+ <input type="text" id="searchInput" class="bg-gray-50 border border-gray-300 text-gray-900 text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block w-full pl-10 p-2.5" placeholder="Search...">
219
+ </div>
220
+ <button id="resetFilters" class="bg-gray-200 hover:bg-gray-300 text-gray-800 px-4 py-2 rounded-md text-sm font-medium flex items-center">
221
+ <i class="fas fa-redo mr-2"></i> Reset
222
+ </button>
223
+ </div>
224
+ </div>
225
+
226
+ <div class="overflow-x-auto">
227
+ <table class="min-w-full divide-y divide-gray-200">
228
+ <thead class="bg-gray-50">
229
+ <tr>
230
+ <th scope="col" class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider cursor-pointer sort" data-sort="fruit">
231
+ <div class="flex items-center">
232
+ Fruit <i class="fas fa-sort ml-1 text-gray-400"></i>
233
+ </div>
234
+ </th>
235
+ <th scope="col" class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider cursor-pointer sort" data-sort="ai_prediction">
236
+ <div class="flex items-center">
237
+ AI Prediction <i class="fas fa-sort ml-1 text-gray-400"></i>
238
+ </div>
239
+ </th>
240
+ <th scope="col" class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Image</th>
241
+ <th scope="col" class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider cursor-pointer sort" data-sort="confidence">
242
+ <div class="flex items-center">
243
+ Confidence <i class="fas fa-sort ml-1 text-gray-400"></i>
244
+ </div>
245
+ </th>
246
+ <th scope="col" class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider cursor-pointer sort" data-sort="user_feedback">
247
+ <div class="flex items-center">
248
+ User Feedback <i class="fas fa-sort ml-1 text-gray-400"></i>
249
+ </div>
250
+ </th>
251
+ <th scope="col" class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider cursor-pointer sort" data-sort="feedback_status">
252
+ <div class="flex items-center">
253
+ Status <i class="fas fa-sort ml-1 text-gray-400"></i>
254
+ </div>
255
+ </th>
256
+ <th scope="col" class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider cursor-pointer sort" data-sort="timestamp">
257
+ <div class="flex items-center">
258
+ Timestamp <i class="fas fa-sort ml-1 text-gray-400"></i>
259
+ </div>
260
+ </th>
261
+ <th scope="col" class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Actions</th>
262
+ </tr>
263
+ </thead>
264
+ <tbody id="resultsTable" class="bg-white divide-y divide-gray-200">
265
+ <!-- Results will be populated here -->
266
+ </tbody>
267
+ </table>
268
+ </div>
269
+
270
+ <div class="bg-white px-4 py-3 flex items-center justify-between border-t border-gray-200 sm:px-6">
271
+ <div class="flex-1 flex justify-between sm:hidden">
272
+ <button id="prevPageMobile" class="relative inline-flex items-center px-4 py-2 border border-gray-300 text-sm font-medium rounded-md text-gray-700 bg-white hover:bg-gray-50">
273
+ Previous
274
+ </button>
275
+ <button id="nextPageMobile" class="ml-3 relative inline-flex items-center px-4 py-2 border border-gray-300 text-sm font-medium rounded-md text-gray-700 bg-white hover:bg-gray-50">
276
+ Next
277
+ </button>
278
+ </div>
279
+ <div class="hidden sm:flex-1 sm:flex sm:items-center sm:justify-between">
280
+ <div>
281
+ <p id="paginationText" class="text-sm text-gray-700">
282
+ Showing <span class="font-medium">1</span> to <span class="font-medium">10</span> of <span class="font-medium">200</span> results
283
+ </p>
284
+ </div>
285
+ <div>
286
+ <nav class="relative z-0 inline-flex rounded-md shadow-sm -space-x-px" aria-label="Pagination">
287
+ <button id="firstPage" class="relative inline-flex items-center px-2 py-2 rounded-l-md border border-gray-300 bg-white text-sm font-medium text-gray-500 hover:bg-gray-50">
288
+ <span class="sr-only">First</span>
289
+ <i class="fas fa-angle-double-left"></i>
290
+ </button>
291
+ <button id="prevPage" class="relative inline-flex items-center px-2 py-2 border border-gray-300 bg-white text-sm font-medium text-gray-500 hover:bg-gray-50">
292
+ <span class="sr-only">Previous</span>
293
+ <i class="fas fa-angle-left"></i>
294
+ </button>
295
+ <div id="pageNumbers" class="flex items-center">
296
+ <!-- Page numbers will be inserted here -->
297
+ </div>
298
+ <button id="nextPage" class="relative inline-flex items-center px-2 py-2 border border-gray-300 bg-white text-sm font-medium text-gray-500 hover:bg-gray-50">
299
+ <span class="sr-only">Next</span>
300
+ <i class="fas fa-angle-right"></i>
301
+ </button>
302
+ <button id="lastPage" class="relative inline-flex items-center px-2 py-2 rounded-r-md border border-gray-300 bg-white text-sm font-medium text-gray-500 hover:bg-gray-50">
303
+ <span class="sr-only">Last</span>
304
+ <i class="fas fa-angle-double-right"></i>
305
+ </button>
306
+ </nav>
307
+ </div>
308
+ </div>
309
+ </div>
310
+ </div>
311
+ </main>
312
+
313
+ <!-- Detail Modal -->
314
+ <div id="detailModal" class="fixed z-10 inset-0 overflow-y-auto hidden" aria-labelledby="modal-title" role="dialog" aria-modal="true">
315
+ <div class="flex items-end justify-center min-h-screen pt-4 px-4 pb-20 text-center sm:block sm:p-0">
316
+ <div class="fixed inset-0 bg-gray-500 bg-opacity-75 transition-opacity" aria-hidden="true"></div>
317
+ <span class="hidden sm:inline-block sm:align-middle sm:h-screen" aria-hidden="true">&#8203;</span>
318
+ <div class="inline-block align-bottom bg-white rounded-lg text-left overflow-hidden shadow-xl transform transition-all sm:my-8 sm:align-middle sm:max-w-4xl sm:w-full">
319
+ <div class="bg-white px-4 pt-5 pb-4 sm:p-6 sm:pb-4">
320
+ <div class="sm:flex sm:items-start">
321
+ <div class="mt-3 text-center sm:mt-0 sm:ml-4 sm:text-left w-full">
322
+ <h3 class="text-lg leading-6 font-medium text-gray-900" id="modalTitle">Classification Details</h3>
323
+ <div class="mt-2">
324
+ <div class="grid grid-cols-1 md:grid-cols-3 gap-4">
325
+ <div>
326
+ <div id="detailImage" class="w-full h-48 bg-gray-200 rounded-md overflow-hidden flex items-center justify-center">
327
+ <i class="fas fa-image text-gray-400 text-4xl"></i>
328
+ </div>
329
+ <div class="mt-2 text-sm">
330
+ <p><span class="font-medium">Classification ID:</span> <span id="detailId"></span></p>
331
+ <p><span class="font-medium">Uploaded by:</span> <span id="detailUploadedBy"></span></p>
332
+ <p><span class="font-medium">Location:</span> <span id="detailLocation"></span></p>
333
+ <p><span class="font-medium">Harvested on:</span> <span id="detailHarvestDate"></span></p>
334
+ </div>
335
+ </div>
336
+
337
+ <div>
338
+ <div class="bg-gray-50 p-3 rounded-md">
339
+ <h4 class="font-medium text-gray-900 mb-2">AI Analysis</h4>
340
+ <div class="mb-2">
341
+ <span class="font-medium">Prediction:</span> <span id="detailAiPrediction" class="px-2 py-1 rounded-full text-xs font-medium"></span>
342
+ </div>
343
+ <div class="mb-2">
344
+ <div class="flex justify-between mb-1">
345
+ <span class="font-medium">Confidence:</span>
346
+ <span id="detailConfidence" class="font-medium"></span>
347
+ </div>
348
+ <div class="w-full bg-gray-200 rounded-full h-2.5">
349
+ <div id="detailConfidenceBar" class="h-2.5 rounded-full"></div>
350
+ </div>
351
+ </div>
352
+ <div class="mb-2">
353
+ <span class="font-medium">Temperature:</span> <span id="detailTemperature"></span>°C
354
+ </div>
355
+ <div class="mb-2">
356
+ <span class="font-medium">Humidity:</span> <span id="detailHumidity"></span>%
357
+ </div>
358
+ <div>
359
+ <span class="font-medium">Weight:</span> <span id="detailWeight"></span>g
360
+ </div>
361
+ </div>
362
+ </div>
363
+
364
+ <div>
365
+ <div class="bg-gray-50 p-3 rounded-md mb-3">
366
+ <h4 class="font-medium text-gray-900 mb-2">Human Feedback</h4>
367
+ <div class="mb-2">
368
+ <span class="font-medium">Feedback:</span> <span id="detailUserFeedback" class="px-2 py-1 rounded-full text-xs font-medium"></span>
369
+ </div>
370
+ <div class="mb-2">
371
+ <span class="font-medium">Status:</span> <span id="detailFeedbackStatus" class="px-2 py-1 rounded-full text-xs font-medium"></span>
372
+ </div>
373
+ <div class="mb-2">
374
+ <span class="font-medium">Timestamp:</span> <span id="detailTimestamp"></span>
375
+ </div>
376
+ </div>
377
+ <div class="bg-gray-50 p-3 rounded-md">
378
+ <h4 class="font-medium text-gray-900 mb-2">Notes</h4>
379
+ <p id="detailNotes" class="text-sm italic"></p>
380
+ </div>
381
+ </div>
382
+ </div>
383
+
384
+ <div class="mt-4 grid grid-cols-2 gap-4">
385
+ <button type="button" id="editBtn" class="w-full inline-flex justify-center rounded-md border border-transparent shadow-sm px-4 py-2 bg-blue-600 text-base font-medium text-white hover:bg-blue-700 focus:outline-none sm:text-sm">
386
+ <i class="fas fa-edit mr-2"></i> Edit Feedback
387
+ </button>
388
+ <button type="button" class="w-full inline-flex justify-center rounded-md border border-gray-300 shadow-sm px-4 py-2 bg-white text-base font-medium text-gray-700 hover:bg-gray-50 focus:outline-none sm:text-sm" onclick="document.getElementById('detailModal').classList.add('hidden')">
389
+ <i class="fas fa-times mr-2"></i> Close
390
+ </button>
391
+ </div>
392
+ </div>
393
+ </div>
394
+ </div>
395
+ </div>
396
+ </div>
397
+ </div>
398
+ </div>
399
+
400
+ <script>
401
+ // Enhanced mock data generation with more variety
402
+ function generateMockData(count = 200) {
403
+ const fruits = [
404
+ 'apple', 'banana', 'mango', 'orange', 'strawberry',
405
+ 'peach', 'pear', 'grape', 'kiwi', 'pineapple',
406
+ 'watermelon', 'blueberry', 'raspberry', 'blackberry', 'cherry'
407
+ ];
408
+
409
+ const ripeness = ['unripe', 'ripe', 'overripe'];
410
+ const feedbackOptions = ['confirmed', 'overridden'];
411
+ const locations = [
412
+ 'Farm A', 'Farm B', 'Farm C', 'Farm D', 'Farm E',
413
+ 'Orchard X', 'Greenhouse Y', 'Plantation Z',
414
+ 'Organic Valley', 'Mountain View Farms'
415
+ ];
416
+
417
+ const users = [
418
+ 'User1', 'User2', 'User3', 'User4', 'User5',
419
+ 'InspectorA', 'QualityB', 'TesterC', 'AgentD', 'ManagerE'
420
+ ];
421
+
422
+ const notesOptions = [
423
+ "Color grading matches maturity level",
424
+ "Slight bruising detected",
425
+ "Perfect specimen",
426
+ "Irregular shape but good quality",
427
+ "Sun-exposed side shows advanced ripeness",
428
+ "Harvested slightly early",
429
+ "Optimal sweetness level",
430
+ "Needs immediate processing",
431
+ "Excellent for long-term storage",
432
+ "Best for juicing"
433
+ ];
434
+
435
+ const data = [];
436
+ const startDate = new Date();
437
+ startDate.setDate(startDate.getDate() - 60); // 2 months range
438
+
439
+ for (let i = 0; i < count; i++) {
440
+ const fruit = fruits[Math.floor(Math.random() * fruits.length)];
441
+ const aiPrediction = ripeness[Math.floor(Math.random() * ripeness.length)];
442
+
443
+ // More realistic confidence distribution
444
+ let confidence;
445
+ if (Math.random() > 0.1) { // 90% high confidence
446
+ confidence = Math.floor(Math.random() * 20) + 75; // 75-95%
447
+ } else {
448
+ confidence = Math.floor(Math.random() * 50) + 30; // 30-80%
449
+ }
450
+
451
+ let feedback = feedbackOptions[Math.floor(Math.random() * feedbackOptions.length)];
452
+ let userFeedback;
453
+
454
+ // Generate some mismatches (about 15%)
455
+ if (Math.random() < 0.15) {
456
+ feedback = 'overridden';
457
+ }
458
+
459
+ if (feedback === 'confirmed') {
460
+ userFeedback = aiPrediction;
461
+ } else {
462
+ // When overridden, choose a different ripeness level
463
+ userFeedback = ripeness.filter(r => r !== aiPrediction)[Math.floor(Math.random() * (ripeness.length - 1))];
464
+
465
+ // When confidence is low, increase chance of override
466
+ if (confidence < 60 && Math.random() < 0.7) {
467
+ feedback = 'overridden';
468
+ userFeedback = ripeness.filter(r => r !== aiPrediction)[Math.floor(Math.random() * (ripeness.length - 1))];
469
+ }
470
+ }
471
+
472
+ // Create a more natural timestamp distribution
473
+ const timestamp = new Date(
474
+ startDate.getTime() +
475
+ Math.random() * 60 * 24 * 60 * 60 * 1000 + // up to 60 days
476
+ Math.random() * 8 * 60 * 60 * 1000 // random time in workday
477
+ );
478
+
479
+ data.push({
480
+ id: `FR-${('0000' + i).slice(-4)}-${timestamp.getFullYear().toString().slice(-2)}`,
481
+ fruit: fruit,
482
+ image: getFruitImage(fruit),
483
+ ai_prediction: aiPrediction,
484
+ confidence: confidence,
485
+ user_feedback: userFeedback,
486
+ feedback_status: feedback,
487
+ timestamp: timestamp.toISOString(),
488
+ location: locations[Math.floor(Math.random() * locations.length)],
489
+ uploaded_by: users[Math.floor(Math.random() * users.length)],
490
+ notes: notesOptions[Math.floor(Math.random() * notesOptions.length)],
491
+ // Additional metrics for detailed analysis
492
+ harvest_date: new Date(
493
+ timestamp.getTime() -
494
+ Math.random() * 7 * 24 * 60 * 60 * 1000 // 0-7 days before classification
495
+ ).toISOString(),
496
+ temperature: Math.round((20 + Math.random() * 15) * 10) / 10, // 20-35°C
497
+ humidity: Math.round((60 + Math.random() * 30) * 10) / 10, // 60-90%
498
+ weight: Math.round((100 + Math.random() * 400) * 10) / 10 // 100-500g
499
+ });
500
+ }
501
+
502
+ return data;
503
+ }
504
+
505
+ // Expanded fruit images
506
+ function getFruitImage(fruit) {
507
+ const images = {
508
+ apple: 'https://images.unsplash.com/photo-1568702846914-96b305d2aaeb?ixlib=rb-1.2.1&auto=format&fit=crop&w=500&q=80',
509
+ banana: 'https://images.unsplash.com/photo-1603833665858-e61bb17a7218?ixlib=rb-1.2.1&auto=format&fit=crop&w=500&q=80',
510
+ mango: 'https://images.unsplash.com/photo-1553279768-865429fa0078?ixlib=rb-1.2.1&auto=format&fit=crop&w=500&q=80',
511
+ orange: 'https://images.unsplash.com/photo-1547514701-42782101795e?ixlib=rb-1.2.1&auto=format&fit=crop&w=500&q=80',
512
+ strawberry: 'https://images.unsplash.com/photo-1464965911861-746a04b4bca6?ixlib=rb-1.2.1&auto=format&fit=crop&w=500&q=80',
513
+ peach: 'https://images.unsplash.com/photo-1559181567-c3190ca9959b?ixlib=rb-1.2.1&auto=format&fit=crop&w=500&q=80',
514
+ pear: 'https://images.unsplash.com/photo-1530893609605-72d7600779ae?ixlib=rb-1.2.1&auto=format&fit=crop&w=500&q=80',
515
+ grape: 'https://images.unsplash.com/photo-1517587171378-24a6fba3c2af?ixlib=rb-1.2.1&auto=format&fit=crop&w=500&q=80',
516
+ kiwi: 'https://images.unsplash.com/photo-1598283027164-78bd17e81663?ixlib=rb-1.2.1&auto=format&fit=crop&w=500&q=80',
517
+ pineapple: 'https://images.unsplash.com/photo-1490885578164-de435ba9c64b?ixlib=rb-1.2.1&auto=format&fit=crop&w=500&q=80',
518
+ watermelon: 'https://images.unsplash.com/photo-1571575173700-afb9492e6a50?ixlib=rb-1.2.1&auto=format&fit=crop&w=500&q=80',
519
+ blueberry: 'https://images.unsplash.com/photo-1498557850523-fd3d118b962e?ixlib=rb-1.2.1&auto=format&fit=crop&w=500&q=80',
520
+ raspberry: 'https://images.unsplash.com/photo-1518633626590-c51b881e2c96?ixlib=rb-1.2.1&auto=format&fit=crop&w=500&q=80',
521
+ blackberry: 'https://images.unsplash.com/photo-1493925415034-d6f1a3f436b1?ixlib=rb-1.2.1&auto=format&fit=crop&w=500&q=80',
522
+ cherry: 'https://images.unsplash.com/photo-1533158313479-c24d2f16f3b5?ixlib=rb-1.2.1&auto=format&fit=crop&w=500&q=80'
523
+ };
524
+ return images[fruit] || images['apple']; // default to apple if fruit not found
525
+ }
526
+
527
+ // Update fruit color mapping
528
+ function getFruitColor(fruit) {
529
+ const colors = {
530
+ apple: '#db2777',
531
+ banana: '#f59e0b',
532
+ mango: '#ea580c',
533
+ orange: '#ea580c',
534
+ strawberry: '#e11d48',
535
+ peach: '#f97316',
536
+ pear: '#84cc16',
537
+ grape: '#7e22ce',
538
+ kiwi: '#22c55e',
539
+ pineapple: '#facc15',
540
+ watermelon: '#ef4444',
541
+ blueberry: '#3b82f6',
542
+ raspberry: '#ec4899',
543
+ blackberry: '#6b7280',
544
+ cherry: '#b91c1c'
545
+ };
546
+ return colors[fruit] || '#6b7280';
547
+ }
548
+
549
+ // Ripeness styling
550
+ function getRipenessClass(ripeness) {
551
+ return `ripeness-${ripeness}`;
552
+ }
553
+
554
+ function getRipenessText(ripeness) {
555
+ return ripeness.charAt(0).toUpperCase() + ripeness.slice(1);
556
+ }
557
+
558
+ // Status styling
559
+ function getStatusClass(status) {
560
+ return {
561
+ 'confirmed': 'bg-green-100 text-green-800',
562
+ 'overridden': 'bg-red-100 text-red-800'
563
+ }[status];
564
+ }
565
+
566
+ function getStatusText(status) {
567
+ return {
568
+ 'confirmed': 'Confirmed',
569
+ 'overridden': 'Overridden'
570
+ }[status];
571
+ }
572
+
573
+ // Confidence styling
574
+ function getConfidenceClass(confidence) {
575
+ if (confidence >= 80) {
576
+ return 'high-confidence';
577
+ } else if (confidence >= 60) {
578
+ return 'medium-confidence';
579
+ } else {
580
+ return 'low-confidence';
581
+ }
582
+ }
583
+
584
+ // Format date
585
+ function formatDate(dateString) {
586
+ return moment(dateString).format('MMM D, YYYY h:mm A');
587
+ }
588
+
589
+ // Initialize dashboard
590
+ function initDashboard() {
591
+ // Generate mock data
592
+ const allData = generateMockData(200);
593
+
594
+ // Initialize state
595
+ let currentPage = 1;
596
+ let itemsPerPage = 10;
597
+ let filteredData = [...allData];
598
+ let sortColumn = null;
599
+ let sortDirection = 'asc';
600
+ let currentDetail = null;
601
+
602
+ // DOM elements
603
+ const resultsTable = document.getElementById('resultsTable');
604
+ const paginationText = document.getElementById('paginationText');
605
+ const pageNumbers = document.getElementById('pageNumbers');
606
+ const fruitTypeFilter = document.getElementById('fruitTypeFilter');
607
+ const predictionFilter = document.getElementById('predictionFilter');
608
+ const feedbackFilter = document.getElementById('feedbackFilter');
609
+ const confidenceFilter = document.getElementById('confidenceFilter');
610
+ const applyFilters = document.getElementById('applyFilters');
611
+ const resetFilters = document.getElementById('resetFilters');
612
+ const searchInput = document.getElementById('searchInput');
613
+ const exportBtn = document.getElementById('exportBtn');
614
+ const totalClassifications = document.getElementById('totalClassifications');
615
+ const accuracyRate = document.getElementById('accuracyRate');
616
+ const mismatchRate = document.getElementById('mismatchRate');
617
+ const avgConfidence = document.getElementById('avgConfidence');
618
+ const detailModal = document.getElementById('detailModal');
619
+
620
+ // Charts
621
+ let accuracyChart, confidenceChart, classificationChart, mismatchChart;
622
+
623
+ // Initialize date range picker
624
+ const dateRangePicker = document.getElementById('dateRangePicker');
625
+ dateRangePicker.addEventListener('input', applyFiltersFunction);
626
+
627
+ // Populate fruit type filter
628
+ const uniqueFruits = [...new Set(allData.map(item => item.fruit))];
629
+ uniqueFruits.forEach(fruit => {
630
+ const option = document.createElement('option');
631
+ option.value = fruit;
632
+ option.textContent = fruit.charAt(0).toUpperCase() + fruit.slice(1);
633
+ fruitTypeFilter.appendChild(option);
634
+ });
635
+
636
+ // Render table
637
+ function renderTable() {
638
+ const startIndex = (currentPage - 1) * itemsPerPage;
639
+ const endIndex = Math.min(startIndex + itemsPerPage, filteredData.length);
640
+ const currentData = filteredData.slice(startIndex, endIndex);
641
+
642
+ resultsTable.innerHTML = '';
643
+
644
+ if (currentData.length === 0) {
645
+ resultsTable.innerHTML = `
646
+ <tr>
647
+ <td colspan="8" class="px-6 py-4 text-center text-gray-500">
648
+ No results found matching your filters.
649
+ </td>
650
+ </tr>
651
+ `;
652
+ return;
653
+ }
654
+
655
+ currentData.forEach(item => {
656
+ const row = document.createElement('tr');
657
+
658
+ // Highlight mismatches
659
+ if (item.feedback_status === 'overridden') {
660
+ row.classList.add('mismatch-highlight');
661
+ }
662
+
663
+ row.innerHTML = `
664
+ <td class="px-6 py-4 whitespace-nowrap">
665
+ <div class="flex items-center">
666
+ <div class="w-4 h-4 rounded-full mr-2" style="background-color: ${getFruitColor(item.fruit)}"></div>
667
+ <div class="text-sm font-medium text-gray-900 capitalize">${item.fruit}</div>
668
+ </div>
669
+ </td>
670
+
671
+ <td class="px-6 py-4 whitespace-nowrap">
672
+ <span class="px-2 py-1 text-xs font-medium rounded-full capitalize ${getRipenessClass(item.ai_prediction)}">
673
+ ${getRipenessText(item.ai_prediction)}
674
+ </span>
675
+ </td>
676
+
677
+ <td class="px-6 py-4 whitespace-nowrap">
678
+ <div class="w-10 h-10 rounded-md overflow-hidden">
679
+ <img src="${item.image}" alt="${item.fruit}" class="w-full h-full object-cover">
680
+ </div>
681
+ </td>
682
+
683
+ <td class="px-6 py-4 whitespace-nowrap">
684
+ <div class="flex items-center">
685
+ <div class="flex-shrink-0 mr-2 text-sm font-medium">${item.confidence}%</div>
686
+ <div class="w-full max-w-xs">
687
+ <div class="confidence-bar">
688
+ <div class="confidence-fill ${getConfidenceClass(item.confidence)}" style="width: ${item.confidence}%"></div>
689
+ </div>
690
+ </div>
691
+ </div>
692
+ </td>
693
+
694
+ <td class="px-6 py-4 whitespace-nowrap">
695
+ <span class="px-2 py-1 text-xs font-medium rounded-full capitalize ${getRipenessClass(item.user_feedback)}">
696
+ ${getRipenessText(item.user_feedback)}
697
+ </span>
698
+ </td>
699
+
700
+ <td class="px-6 py-4 whitespace-nowrap">
701
+ <span class="px-2 py-1 text-xs font-medium rounded-full ${getStatusClass(item.feedback_status)}">
702
+ ${getStatusText(item.feedback_status)}
703
+ </span>
704
+ </td>
705
+
706
+ <td class="px-6 py-4 whitespace-nowrap text-sm text-gray-500">
707
+ ${formatDate(item.timestamp)}
708
+ </td>
709
+
710
+ <td class="px-6 py-4 whitespace-nowrap text-right text-sm font-medium">
711
+ <button class="text-indigo-600 hover:text-indigo-900 mr-2 view-detail" data-id="${item.id}">
712
+ <i class="fas fa-eye"></i>
713
+ </button>
714
+ </td>
715
+ `;
716
+
717
+ resultsTable.appendChild(row);
718
+ });
719
+
720
+ // Update pagination info
721
+ paginationText.textContent = `Showing ${startIndex + 1} to ${endIndex} of ${filteredData.length} results`;
722
+
723
+ // Update pagination buttons
724
+ renderPagination();
725
+
726
+ // Add event listeners to detail buttons
727
+ document.querySelectorAll('.view-detail').forEach(btn => {
728
+ btn.addEventListener('click', function() {
729
+ const id = this.getAttribute('data-id');
730
+ showDetail(id);
731
+ });
732
+ });
733
+ }
734
+
735
+ // Render pagination buttons
736
+ function renderPagination() {
737
+ pageNumbers.innerHTML = '';
738
+ const totalPages = Math.ceil(filteredData.length / itemsPerPage);
739
+
740
+ // Always show first page
741
+ if (currentPage > 3) {
742
+ const pageItem = document.createElement('button');
743
+ pageItem.textContent = '1';
744
+ pageItem.className = `relative inline-flex items-center px-4 py-2 border border-gray-300 text-sm font-medium ${currentPage === 1 ? 'bg-blue-50 text-blue-600' : 'bg-white text-gray-500 hover:bg-gray-50'}`;
745
+ pageItem.addEventListener('click', () => {
746
+ currentPage = 1;
747
+ renderTable();
748
+ });
749
+ pageNumbers.appendChild(pageItem);
750
+
751
+ if (currentPage > 4) {
752
+ const ellipsis = document.createElement('span');
753
+ ellipsis.className = 'relative inline-flex items-center px-4 py-2 border border-gray-300 bg-white text-sm font-medium text-gray-700';
754
+ ellipsis.textContent = '...';
755
+ pageNumbers.appendChild(ellipsis);
756
+ }
757
+ }
758
+
759
+ // Show surrounding pages
760
+ const startPage = Math.max(1, currentPage - 2);
761
+ const endPage = Math.min(totalPages, currentPage + 2);
762
+
763
+ for (let i = startPage; i <= endPage; i++) {
764
+ const pageItem = document.createElement('button');
765
+ pageItem.textContent = i;
766
+ pageItem.className = `relative inline-flex items-center px-4 py-2 border border-gray-300 text-sm font-medium ${currentPage === i ? 'bg-blue-50 text-blue-600' : 'bg-white text-gray-500 hover:bg-gray-50'}`;
767
+ pageItem.addEventListener('click', () => {
768
+ currentPage = i;
769
+ renderTable();
770
+ });
771
+ pageNumbers.appendChild(pageItem);
772
+ }
773
+
774
+ // Always show last page
775
+ if (currentPage < totalPages - 2) {
776
+ if (currentPage < totalPages - 3) {
777
+ const ellipsis = document.createElement('span');
778
+ ellipsis.className = 'relative inline-flex items-center px-4 py-2 border border-gray-300 bg-white text-sm font-medium text-gray-700';
779
+ ellipsis.textContent = '...';
780
+ pageNumbers.appendChild(ellipsis);
781
+ }
782
+
783
+ const pageItem = document.createElement('button');
784
+ pageItem.textContent = totalPages;
785
+ pageItem.className = `relative inline-flex items-center px-4 py-2 border border-gray-300 text-sm font-medium ${currentPage === totalPages ? 'bg-blue-50 text-blue-600' : 'bg-white text-gray-500 hover:bg-gray-50'}`;
786
+ pageItem.addEventListener('click', () => {
787
+ currentPage = totalPages;
788
+ renderTable();
789
+ });
790
+ pageNumbers.appendChild(pageItem);
791
+ }
792
+ }
793
+
794
+ // Apply filters
795
+ function applyFiltersFunction() {
796
+ currentPage = 1;
797
+ filteredData = [...allData];
798
+
799
+ // Fruit type filter
800
+ if (fruitTypeFilter.value) {
801
+ filteredData = filteredData.filter(item => item.fruit === fruitTypeFilter.value);
802
+ }
803
+
804
+ // Prediction filter
805
+ if (predictionFilter.value) {
806
+ filteredData = filteredData.filter(item => item.ai_prediction === predictionFilter.value);
807
+ }
808
+
809
+ // Feedback filter
810
+ if (feedbackFilter.value) {
811
+ filteredData = filteredData.filter(item => item.feedback_status === feedbackFilter.value);
812
+ }
813
+
814
+ // Confidence filter
815
+ if (confidenceFilter.value) {
816
+ filteredData = filteredData.filter(item => {
817
+ if (confidenceFilter.value === 'high') return item.confidence >= 80;
818
+ if (confidenceFilter.value === 'medium') return item.confidence >= 60 && item.confidence < 80;
819
+ if (confidenceFilter.value === 'low') return item.confidence < 60;
820
+ return true;
821
+ });
822
+ }
823
+
824
+ // Date range filter (simplified for this example)
825
+ if (dateRangePicker.value) {
826
+ const [startDateStr, endDateStr] = dateRangePicker.value.split(' - ');
827
+ const startDate = new Date(startDateStr);
828
+ const endDate = new Date(endDateStr);
829
+
830
+ filteredData = filteredData.filter(item => {
831
+ const itemDate = new Date(item.timestamp);
832
+ return itemDate >= startDate && itemDate <= endDate;
833
+ });
834
+ }
835
+
836
+ // Search text
837
+ if (searchInput.value) {
838
+ const searchTerm = searchInput.value.toLowerCase();
839
+ filteredData = filteredData.filter(item =>
840
+ item.fruit.toLowerCase().includes(searchTerm) ||
841
+ item.ai_prediction.toLowerCase().includes(searchTerm) ||
842
+ item.user_feedback.toLowerCase().includes(searchTerm) ||
843
+ item.location.toLowerCase().includes(searchTerm) ||
844
+ item.uploaded_by.toLowerCase().includes(searchTerm) ||
845
+ item.id.toLowerCase().includes(searchTerm)
846
+ );
847
+ }
848
+
849
+ // Sorting
850
+ if (sortColumn) {
851
+ filteredData.sort((a, b) => {
852
+ let aValue = a[sortColumn];
853
+ let bValue = b[sortColumn];
854
+
855
+ // Special handling for dates
856
+ if (sortColumn === 'timestamp') {
857
+ aValue = new Date(a.timestamp).getTime();
858
+ bValue = new Date(b.timestamp).getTime();
859
+ }
860
+
861
+ if (typeof aValue === 'string') aValue = aValue.toLowerCase();
862
+ if (typeof bValue === 'string') bValue = bValue.toLowerCase();
863
+
864
+ if (aValue < bValue) return sortDirection === 'asc' ? -1 : 1;
865
+ if (aValue > bValue) return sortDirection === 'asc' ? 1 : -1;
866
+ return 0;
867
+ });
868
+ }
869
+
870
+ // Update metrics
871
+ updateMetrics();
872
+
873
+ // Render charts
874
+ renderCharts();
875
+
876
+ // Render table with filtered data
877
+ renderTable();
878
+ }
879
+
880
+ // Reset filters
881
+ function resetFiltersFunction() {
882
+ fruitTypeFilter.value = '';
883
+ predictionFilter.value = '';
884
+ feedbackFilter.value = '';
885
+ confidenceFilter.value = '';
886
+ dateRangePicker.value = '';
887
+ searchInput.value = '';
888
+ sortColumn = null;
889
+ sortDirection = 'asc';
890
+
891
+ // Reset sort indicators
892
+ document.querySelectorAll('.sort i').forEach(icon => {
893
+ icon.classList.remove('fa-sort-up', 'fa-sort-down');
894
+ icon.classList.add('fa-sort');
895
+ });
896
+
897
+ applyFiltersFunction();
898
+ }
899
+
900
+ // Show detail modal
901
+ function showDetail(id) {
902
+ const item = allData.find(item => item.id === id);
903
+ if (!item) return;
904
+
905
+ currentDetail = item;
906
+
907
+ // Update modal content
908
+ document.getElementById('modalTitle').textContent = `${item.fruit.charAt(0).toUpperCase() + item.fruit.slice(1)} Classification`;
909
+ document.getElementById('detailId').textContent = item.id;
910
+ document.getElementById('detailAiPrediction').textContent = getRipenessText(item.ai_prediction);
911
+ document.getElementById('detailAiPrediction').className = `px-2 py-1 rounded-full text-xs font-medium capitalize ${getRipenessClass(item.ai_prediction)}`;
912
+ document.getElementById('detailConfidence').textContent = `${item.confidence}%`;
913
+ document.getElementById('detailConfidenceBar').className = `h-2.5 rounded-full ${getConfidenceClass(item.confidence)}`;
914
+ document.getElementById('detailConfidenceBar').style.width = `${item.confidence}%`;
915
+ document.getElementById('detailUserFeedback').textContent = getRipenessText(item.user_feedback);
916
+ document.getElementById('detailUserFeedback').className = `px-2 py-1 rounded-full text-xs font-medium capitalize ${getRipenessClass(item.user_feedback)}`;
917
+ document.getElementById('detailFeedbackStatus').textContent = getStatusText(item.feedback_status);
918
+ document.getElementById('detailFeedbackStatus').className = `px-2 py-1 rounded-full text-xs font-medium ${getStatusClass(item.feedback_status)}`;
919
+ document.getElementById('detailTimestamp').textContent = formatDate(item.timestamp);
920
+ document.getElementById('detailUploadedBy').textContent = item.uploaded_by;
921
+ document.getElementById('detailLocation').textContent = item.location;
922
+ document.getElementById('detailHarvestDate').textContent = formatDate(item.harvest_date);
923
+ document.getElementById('detailTemperature').textContent = item.temperature;
924
+ document.getElementById('detailHumidity').textContent = item.humidity;
925
+ document.getElementById('detailWeight').textContent = item.weight;
926
+ document.getElementById('detailNotes').textContent = item.notes;
927
+
928
+ // Update image
929
+ const detailImage = document.getElementById('detailImage');
930
+ detailImage.innerHTML = ''; // Clear previous content
931
+ const img = document.createElement('img');
932
+ img.src = item.image;
933
+ img.alt = item.fruit;
934
+ img.className = 'w-full h-full object-cover';
935
+ detailImage.appendChild(img);
936
+
937
+ // Show modal
938
+ detailModal.classList.remove('hidden');
939
+ }
940
+
941
+ // Update metrics
942
+ function updateMetrics() {
943
+ totalClassifications.textContent = filteredData.length;
944
+
945
+ // Calculate accuracy rate (percentage of confirmed feedback)
946
+ const confirmedCount = filteredData.filter(item => item.feedback_status === 'confirmed').length;
947
+ const accuracy = filteredData.length > 0 ? Math.round((confirmedCount / filteredData.length) * 100) : 0;
948
+ accuracyRate.textContent = `${accuracy}%`;
949
+
950
+ // Calculate mismatch rate (percentage of overridden feedback)
951
+ const mismatchCount = filteredData.filter(item => item.feedback_status === 'overridden').length;
952
+ const mismatchRateValue = filteredData.length > 0 ? Math.round((mismatchCount / filteredData.length) * 100) : 0;
953
+ mismatchRate.textContent = `${mismatchRateValue}%`;
954
+
955
+ // Calculate average confidence
956
+ const avgConfidenceValue = filteredData.length > 0
957
+ ? Math.round(filteredData.reduce((sum, item) => sum + item.confidence, 0) / filteredData.length)
958
+ : 0;
959
+ avgConfidence.textContent = `${avgConfidenceValue}%`;
960
+ }
961
+
962
+ // Render charts
963
+ function renderCharts() {
964
+ // Destroy existing charts if they exist
965
+ if (accuracyChart) accuracyChart.destroy();
966
+ if (confidenceChart) confidenceChart.destroy();
967
+ if (classificationChart) classificationChart.destroy();
968
+ if (mismatchChart) mismatchChart.destroy();
969
+
970
+ // Group data by week for trend analysis
971
+ const weeklyGroups = {};
972
+ filteredData.forEach(item => {
973
+ const week = moment(item.timestamp).startOf('week').format('MMM D');
974
+ if (!weeklyGroups[week]) {
975
+ weeklyGroups[week] = {
976
+ total: 0,
977
+ confirmed: 0,
978
+ overridden: 0
979
+ };
980
+ }
981
+ weeklyGroups[week].total++;
982
+ if (item.feedback_status === 'confirmed') {
983
+ weeklyGroups[week].confirmed++;
984
+ } else {
985
+ weeklyGroups[week].overridden++;
986
+ }
987
+ });
988
+
989
+ const weeks = Object.keys(weeklyGroups);
990
+ const confirmedData = weeks.map(week => weeklyGroups[week].confirmed);
991
+ const overriddenData = weeks.map(week => weeklyGroups[week].overridden);
992
+
993
+ // Accuracy Over Time Chart
994
+ const accuracyCtx = document.getElementById('accuracyChart').getContext('2d');
995
+ accuracyChart = new Chart(accuracyCtx, {
996
+ type: 'line',
997
+ data: {
998
+ labels: weeks,
999
+ datasets: [
1000
+ {
1001
+ label: 'Accuracy Rate (%)',
1002
+ data: weeks.map(week => {
1003
+ const group = weeklyGroups[week];
1004
+ return group.total > 0 ? Math.round((group.confirmed / group.total) * 100) : 0;
1005
+ }),
1006
+ borderColor: '#10b981',
1007
+ backgroundColor: 'rgba(16, 185, 129, 0.1)',
1008
+ borderWidth: 2,
1009
+ fill: true,
1010
+ tension: 0.4
1011
+ }
1012
+ ]
1013
+ },
1014
+ options: {
1015
+ responsive: true,
1016
+ plugins: {
1017
+ legend: {
1018
+ display: true,
1019
+ position: 'top'
1020
+ },
1021
+ tooltip: {
1022
+ callbacks: {
1023
+ label: function(context) {
1024
+ return `${context.dataset.label}: ${context.raw}%`;
1025
+ }
1026
+ }
1027
+ }
1028
+ },
1029
+ scales: {
1030
+ y: {
1031
+ beginAtZero: true,
1032
+ max: 100,
1033
+ ticks: {
1034
+ callback: function(value) {
1035
+ return `${value}%`;
1036
+ }
1037
+ }
1038
+ }
1039
+ }
1040
+ }
1041
+ });
1042
+
1043
+ // Confidence Distribution Chart
1044
+ const confidenceCtx = document.getElementById('confidenceChart').getContext('2d');
1045
+ confidenceChart = new Chart(confidenceCtx, {
1046
+ type: 'bar',
1047
+ data: {
1048
+ labels: ['<60%', '60-69%', '70-79%', '80-89%', '90%+'],
1049
+ datasets: [
1050
+ {
1051
+ label: 'Confirmed',
1052
+ data: [
1053
+ filteredData.filter(item => item.feedback_status === 'confirmed' && item.confidence < 60).length,
1054
+ filteredData.filter(item => item.feedback_status === 'confirmed' && item.confidence >= 60 && item.confidence < 70).length,
1055
+ filteredData.filter(item => item.feedback_status === 'confirmed' && item.confidence >= 70 && item.confidence < 80).length,
1056
+ filteredData.filter(item => item.feedback_status === 'confirmed' && item.confidence >= 80 && item.confidence < 90).length,
1057
+ filteredData.filter(item => item.feedback_status === 'confirmed' && item.confidence >= 90).length
1058
+ ],
1059
+ backgroundColor: '#10b981',
1060
+ borderColor: '#10b981',
1061
+ borderWidth: 1
1062
+ },
1063
+ {
1064
+ label: 'Overridden',
1065
+ data: [
1066
+ filteredData.filter(item => item.feedback_status === 'overridden' && item.confidence < 60).length,
1067
+ filteredData.filter(item => item.feedback_status === 'overridden' && item.confidence >= 60 && item.confidence < 70).length,
1068
+ filteredData.filter(item => item.feedback_status === 'overridden' && item.confidence >= 70 && item.confidence < 80).length,
1069
+ filteredData.filter(item => item.feedback_status === 'overridden' && item.confidence >= 80 && item.confidence < 90).length,
1070
+ filteredData.filter(item => item.feedback_status === 'overridden' && item.confidence >= 90).length
1071
+ ],
1072
+ backgroundColor: '#ef4444',
1073
+ borderColor: '#ef4444',
1074
+ borderWidth: 1
1075
+ }
1076
+ ]
1077
+ },
1078
+ options: {
1079
+ responsive: true,
1080
+ plugins: {
1081
+ legend: {
1082
+ display: true,
1083
+ position: 'top'
1084
+ }
1085
+ },
1086
+ scales: {
1087
+ x: {
1088
+ stacked: false
1089
+ },
1090
+ y: {
1091
+ stacked: false
1092
+ }
1093
+ }
1094
+ }
1095
+ });
1096
+
1097
+ // Classification Distribution Chart
1098
+ const classificationCtx = document.getElementById('classificationChart').getContext('2d');
1099
+ classificationChart = new Chart(classificationCtx, {
1100
+ type: 'pie',
1101
+ data: {
1102
+ labels: ['Unripe', 'Ripe', 'Overripe'],
1103
+ datasets: [
1104
+ {
1105
+ data: [
1106
+ filteredData.filter(item => item.ai_prediction === 'unripe').length,
1107
+ filteredData.filter(item => item.ai_prediction === 'ripe').length,
1108
+ filteredData.filter(item => item.ai_prediction === 'overripe').length
1109
+ ],
1110
+ backgroundColor: ['#f59e0b', '#10b981', '#ef4444'],
1111
+ borderColor: ['#f59e0b', '#10b981', '#ef4444'],
1112
+ borderWidth: 1
1113
+ }
1114
+ ]
1115
+ },
1116
+ options: {
1117
+ responsive: true,
1118
+ plugins: {
1119
+ legend: {
1120
+ display: true,
1121
+ position: 'right'
1122
+ }
1123
+ }
1124
+ }
1125
+ });
1126
+
1127
+ // Mismatch Analysis Chart
1128
+ const mismatchCtx = document.getElementById('mismatchChart').getContext('2d');
1129
+
1130
+ // Count mismatches by fruit type
1131
+ const mismatchByFruit = {};
1132
+ filteredData.filter(item => item.feedback_status === 'overridden').forEach(item => {
1133
+ if (!mismatchByFruit[item.fruit]) {
1134
+ mismatchByFruit[item.fruit] = 0;
1135
+ }
1136
+ mismatchByFruit[item.fruit]++;
1137
+ });
1138
+
1139
+ const fruits = Object.keys(mismatchByFruit).map(fruit => fruit.charAt(0).toUpperCase() + fruit.slice(1));
1140
+ const mismatchCounts = Object.values(mismatchByFruit);
1141
+
1142
+ mismatchChart = new Chart(mismatchCtx, {
1143
+ type: 'bar',
1144
+ data: {
1145
+ labels: fruits,
1146
+ datasets: [
1147
+ {
1148
+ label: 'Mismatch Count',
1149
+ data: mismatchCounts,
1150
+ backgroundColor: fruits.map(fruit => getFruitColor(fruit.toLowerCase())),
1151
+ borderColor: fruits.map(fruit => getFruitColor(fruit.toLowerCase())),
1152
+ borderWidth: 1
1153
+ }
1154
+ ]
1155
+ },
1156
+ options: {
1157
+ responsive: true,
1158
+ plugins: {
1159
+ legend: {
1160
+ display: false
1161
+ }
1162
+ },
1163
+ scales: {
1164
+ y: {
1165
+ beginAtZero: true
1166
+ }
1167
+ }
1168
+ }
1169
+ });
1170
+ }
1171
+
1172
+ // Export data
1173
+ function exportData() {
1174
+ const dataStr = JSON.stringify(filteredData, null, 2);
1175
+ const dataUri = 'data:application/json;charset=utf-8,' + encodeURIComponent(dataStr);
1176
+
1177
+ const exportFileDefaultName = `fruit-ai-export-${moment().format('YYYY-MM-DD')}.json`;
1178
+
1179
+ const linkElement = document.createElement('a');
1180
+ linkElement.setAttribute('href', dataUri);
1181
+ linkElement.setAttribute('download', exportFileDefaultName);
1182
+ linkElement.click();
1183
+ }
1184
+
1185
+ // Sorting functionality
1186
+ document.querySelectorAll('.sort').forEach(header => {
1187
+ header.addEventListener('click', function() {
1188
+ const column = this.getAttribute('data-sort');
1189
+ const icon = this.querySelector('i');
1190
+
1191
+ // Reset other sort icons
1192
+ document.querySelectorAll('.sort i').forEach(otherIcon => {
1193
+ if (otherIcon !== icon) {
1194
+ otherIcon.classList.remove('fa-sort-up', 'fa-sort-down');
1195
+ otherIcon.classList.add('fa-sort');
1196
+ }
1197
+ });
1198
+
1199
+ // Toggle sort direction
1200
+ if (sortColumn === column) {
1201
+ sortDirection = sortDirection === 'asc' ? 'desc' : 'asc';
1202
+ } else {
1203
+ sortColumn = column;
1204
+ sortDirection = 'asc';
1205
+ }
1206
+
1207
+ // Update icon
1208
+ icon.classList.remove('fa-sort');
1209
+ icon.classList.add(sortDirection === 'asc' ? 'fa-sort-up' : 'fa-sort-down');
1210
+
1211
+ applyFiltersFunction();
1212
+ });
1213
+ });
1214
+
1215
+ // Pagination buttons
1216
+ document.getElementById('firstPage').addEventListener('click', () => {
1217
+ currentPage = 1;
1218
+ renderTable();
1219
+ });
1220
+
1221
+ document.getElementById('prevPage').addEventListener('click', () => {
1222
+ if (currentPage > 1) {
1223
+ currentPage--;
1224
+ renderTable();
1225
+ }
1226
+ });
1227
+
1228
+ document.getElementById('nextPage').addEventListener('click', () => {
1229
+ if (currentPage < Math.ceil(filteredData.length / itemsPerPage)) {
1230
+ currentPage++;
1231
+ renderTable();
1232
+ }
1233
+ });
1234
+
1235
+ document.getElementById('lastPage').addEventListener('click', () => {
1236
+ currentPage = Math.ceil(filteredData.length / itemsPerPage);
1237
+ renderTable();
1238
+ });
1239
+
1240
+ document.getElementById('prevPageMobile').addEventListener('click', () => {
1241
+ if (currentPage > 1) {
1242
+ currentPage--;
1243
+ renderTable();
1244
+ }
1245
+ });
1246
+
1247
+ document.getElementById('nextPageMobile').addEventListener('click', () => {
1248
+ if (currentPage < Math.ceil(filteredData.length / itemsPerPage)) {
1249
+ currentPage++;
1250
+ renderTable();
1251
+ }
1252
+ });
1253
+
1254
+ // Event listeners
1255
+ applyFilters.addEventListener('click', applyFiltersFunction);
1256
+ resetFilters.addEventListener('click', resetFiltersFunction);
1257
+ searchInput.addEventListener('keyup', function(e) {
1258
+ if (e.key === 'Enter') {
1259
+ applyFiltersFunction();
1260
+ }
1261
+ });
1262
+ exportBtn.addEventListener('click', exportData);
1263
+
1264
+ // Close modal when clicking outside
1265
+ detailModal.addEventListener('click', function(e) {
1266
+ if (e.target === this) {
1267
+ detailModal.classList.add('hidden');
1268
+ }
1269
+ });
1270
+
1271
+ // Close modal with X button
1272
+ document.querySelector('#detailModal button[onclick]').addEventListener('click', function() {
1273
+ detailModal.classList.add('hidden');
1274
+ });
1275
+
1276
+ // Initialize dashboard
1277
+ applyFiltersFunction();
1278
+
1279
+ // Simulate date range picker functionality
1280
+ dateRangePicker.value = `${moment().subtract(30, 'days').format('MM/DD/YYYY')} - ${moment().format('MM/DD/YYYY')}`;
1281
+ }
1282
+
1283
+ // Initialize the dashboard
1284
+ document.addEventListener('DOMContentLoaded', initDashboard);
1285
+ </script>
1286
+ <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=mrwhy06/monitor" style="color: #fff;text-decoration: underline;" target="_blank" >🧬 Remix</a></p></body>
1287
+ </html>