saq1b commited on
Commit
276d6f7
·
verified ·
1 Parent(s): 1dcfc18

Create templates/index.html

Browse files
Files changed (1) hide show
  1. templates/index.html +504 -0
templates/index.html ADDED
@@ -0,0 +1,504 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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>Jailbreak Trading Data Analysis</title>
7
+ <script src="https://cdn.tailwindcss.com"></script>
8
+ <script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
9
+ <script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/cdn.min.js" defer></script>
10
+ <style>
11
+ [x-cloak] { display: none !important; }
12
+ .chart-container {
13
+ position: relative;
14
+ height: 300px;
15
+ width: 100%;
16
+ }
17
+ </style>
18
+ </head>
19
+ <body class="bg-gray-100 dark:bg-gray-900" x-data="{ darkMode: localStorage.getItem('darkMode') === 'true' }"
20
+ x-init="$watch('darkMode', val => localStorage.setItem('darkMode', val))"
21
+ :class="{ 'dark': darkMode }">
22
+
23
+ <div class="min-h-screen dark:text-gray-100">
24
+ <!-- Navigation -->
25
+ <nav class="bg-white shadow-md dark:bg-gray-800 p-4">
26
+ <div class="container mx-auto flex justify-between items-center">
27
+ <div class="flex items-center space-x-2">
28
+ <svg xmlns="http://www.w3.org/2000/svg" class="h-8 w-8 text-blue-600 dark:text-blue-400" fill="none" viewBox="0 0 24 24" stroke="currentColor">
29
+ <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 19v-6a2 2 0 00-2-2H5a2 2 0 00-2 2v6a2 2 0 002 2h2a2 2 0 002-2zm0 0V9a2 2 0 012-2h2a2 2 0 012 2v10m-6 0a2 2 0 002 2h2a2 2 0 002-2m0 0V5a2 2 0 012-2h2a2 2 0 012 2v14a2 2 0 01-2 2h-2a2 2 0 01-2-2z" />
30
+ </svg>
31
+ <h1 class="text-xl font-bold">Jailbreak Trading Analytics</h1>
32
+ </div>
33
+ <div class="flex items-center space-x-4">
34
+ <button @click="darkMode = !darkMode" class="p-2 rounded-full hover:bg-gray-200 dark:hover:bg-gray-700">
35
+ <svg x-show="!darkMode" xmlns="http://www.w3.org/2000/svg" class="h-6 w-6" fill="none" viewBox="0 0 24 24" stroke="currentColor">
36
+ <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M20.354 15.354A9 9 0 018.646 3.646 9.003 9.003 0 0012 21a9.003 9.003 0 008.354-5.646z" />
37
+ </svg>
38
+ <svg x-show="darkMode" xmlns="http://www.w3.org/2000/svg" class="h-6 w-6" fill="none" viewBox="0 0 24 24" stroke="currentColor">
39
+ <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 3v1m0 16v1m9-9h-1M4 12H3m15.364 6.364l-.707-.707M6.343 6.343l-.707-.707m12.728 0l-.707.707M6.343 17.657l-.707.707M16 12a4 4 0 11-8 0 4 4 0 018 0z" />
40
+ </svg>
41
+ </button>
42
+ <a href="https://github.com" target="_blank" class="text-blue-600 dark:text-blue-400 hover:underline">GitHub</a>
43
+ </div>
44
+ </div>
45
+ </nav>
46
+
47
+ <!-- Dashboard -->
48
+ <div class="container mx-auto px-4 py-6" x-data="dashboard()">
49
+ <!-- Stats Overview -->
50
+ <div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-6 mb-6">
51
+ <div class="bg-white dark:bg-gray-800 rounded-lg shadow-md p-6 transition hover:shadow-lg">
52
+ <div class="flex items-center justify-between">
53
+ <h2 class="text-lg font-semibold">Total Items</h2>
54
+ <svg xmlns="http://www.w3.org/2000/svg" class="h-8 w-8 text-blue-500" fill="none" viewBox="0 0 24 24" stroke="currentColor">
55
+ <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M20 7l-8-4-8 4m16 0l-8 4m8-4v10l-8 4m0-10L4 7m8 4v10" />
56
+ </svg>
57
+ </div>
58
+ <p class="text-3xl font-bold mt-2" x-text="stats.totalItems">--</p>
59
+ </div>
60
+
61
+ <div class="bg-white dark:bg-gray-800 rounded-lg shadow-md p-6 transition hover:shadow-lg">
62
+ <div class="flex items-center justify-between">
63
+ <h2 class="text-lg font-semibold">Total Trades</h2>
64
+ <svg xmlns="http://www.w3.org/2000/svg" class="h-8 w-8 text-green-500" fill="none" viewBox="0 0 24 24" stroke="currentColor">
65
+ <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M8 7h12m0 0l-4-4m4 4l-4 4m0 6H4m0 0l4 4m-4-4l4-4" />
66
+ </svg>
67
+ </div>
68
+ <p class="text-3xl font-bold mt-2" x-text="stats.totalTrades">--</p>
69
+ </div>
70
+
71
+ <div class="bg-white dark:bg-gray-800 rounded-lg shadow-md p-6 transition hover:shadow-lg">
72
+ <div class="flex items-center justify-between">
73
+ <h2 class="text-lg font-semibold">Unique Items</h2>
74
+ <svg xmlns="http://www.w3.org/2000/svg" class="h-8 w-8 text-purple-500" fill="none" viewBox="0 0 24 24" stroke="currentColor">
75
+ <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M17 20h5v-2a3 3 0 00-5.356-1.857M17 20H7m10 0v-2c0-.656-.126-1.283-.356-1.857M7 20H2v-2a3 3 0 015.356-1.857M7 20v-2c0-.656.126-1.283.356-1.857m0 0a5.002 5.002 0 019.288 0M15 7a3 3 0 11-6 0 3 3 0 016 0zm6 3a2 2 0 11-4 0 2 2 0 014 0zM7 10a2 2 0 11-4 0 2 2 0 014 0z" />
76
+ </svg>
77
+ </div>
78
+ <p class="text-3xl font-bold mt-2" x-text="stats.totalCirculation">--</p>
79
+ </div>
80
+
81
+ <div class="bg-white dark:bg-gray-800 rounded-lg shadow-md p-6 transition hover:shadow-lg">
82
+ <div class="flex items-center justify-between">
83
+ <h2 class="text-lg font-semibold">Avg Demand</h2>
84
+ <svg xmlns="http://www.w3.org/2000/svg" class="h-8 w-8 text-yellow-500" fill="none" viewBox="0 0 24 24" stroke="currentColor">
85
+ <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M13 7h8m0 0v8m0-8l-8 8-4-4-6 6" />
86
+ </svg>
87
+ </div>
88
+ <p class="text-3xl font-bold mt-2" x-text="stats.avgDemand">--</p>
89
+ </div>
90
+ </div>
91
+
92
+ <!-- Chart Section -->
93
+ <div class="grid grid-cols-1 lg:grid-cols-2 gap-6 mb-6">
94
+ <div class="bg-white dark:bg-gray-800 rounded-lg shadow-md p-4">
95
+ <h2 class="text-lg font-semibold mb-4">Top Items by Demand Multiple</h2>
96
+ <div class="chart-container">
97
+ <canvas id="demandChart"></canvas>
98
+ </div>
99
+ </div>
100
+ <div class="bg-white dark:bg-gray-800 rounded-lg shadow-md p-4">
101
+ <h2 class="text-lg font-semibold mb-4">Items by Type</h2>
102
+ <div class="chart-container">
103
+ <canvas id="typeChart"></canvas>
104
+ </div>
105
+ </div>
106
+ </div>
107
+
108
+ <!-- Data Table Section -->
109
+ <div class="bg-white dark:bg-gray-800 rounded-lg shadow-md mb-6">
110
+ <div class="p-4 border-b dark:border-gray-700">
111
+ <div class="flex flex-col md:flex-row md:items-center md:justify-between">
112
+ <h2 class="text-lg font-semibold">Jailbreak Trading Data</h2>
113
+ <div class="mt-3 md:mt-0 flex flex-col md:flex-row space-y-3 md:space-y-0 md:space-x-3">
114
+ <div class="relative">
115
+ <input
116
+ type="text"
117
+ placeholder="Search items..."
118
+ class="pl-10 pr-4 py-2 border rounded-lg w-full dark:bg-gray-700 dark:border-gray-600 focus:outline-none focus:ring-2 focus:ring-blue-500"
119
+ x-model="searchQuery"
120
+ @input="filterData()"
121
+ >
122
+ <svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5 text-gray-400 absolute left-3 top-2.5" fill="none" viewBox="0 0 24 24" stroke="currentColor">
123
+ <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z" />
124
+ </svg>
125
+ </div>
126
+ <div>
127
+ <select
128
+ class="py-2 px-3 border rounded-lg w-full dark:bg-gray-700 dark:border-gray-600 focus:outline-none focus:ring-2 focus:ring-blue-500"
129
+ x-model="selectedType"
130
+ @change="filterData()"
131
+ >
132
+ <option value="">All Types</option>
133
+ <template x-for="type in types" :key="type">
134
+ <option x-text="type"></option>
135
+ </template>
136
+ </select>
137
+ </div>
138
+ </div>
139
+ </div>
140
+ </div>
141
+ <div class="overflow-x-auto" x-show="!loading">
142
+ <table class="min-w-full divide-y divide-gray-200 dark:divide-gray-700">
143
+ <thead class="bg-gray-50 dark:bg-gray-700">
144
+ <tr>
145
+ <template x-for="(column, index) in columns" :key="index">
146
+ <th
147
+ scope="col"
148
+ class="px-6 py-3 text-left text-xs font-medium text-gray-500 dark:text-gray-300 uppercase tracking-wider cursor-pointer hover:bg-gray-100 dark:hover:bg-gray-600"
149
+ @click="sortBy(column.key)"
150
+ >
151
+ <div class="flex items-center space-x-1">
152
+ <span x-text="column.label"></span>
153
+ <span class="flex flex-col">
154
+ <svg xmlns="http://www.w3.org/2000/svg" class="h-3 w-3" :class="sortColumn === column.key && sortDirection === 'asc' ? 'text-blue-500' : 'text-gray-400'" fill="none" viewBox="0 0 24 24" stroke="currentColor">
155
+ <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M5 15l7-7 7 7" />
156
+ </svg>
157
+ <svg xmlns="http://www.w3.org/2000/svg" class="h-3 w-3" :class="sortColumn === column.key && sortDirection === 'desc' ? 'text-blue-500' : 'text-gray-400'" fill="none" viewBox="0 0 24 24" stroke="currentColor">
158
+ <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 9l-7 7-7-7" />
159
+ </svg>
160
+ </span>
161
+ </div>
162
+ </th>
163
+ </template>
164
+ </tr>
165
+ </thead>
166
+ <tbody class="bg-white dark:bg-gray-800 divide-y divide-gray-200 dark:divide-gray-700">
167
+ <template x-for="(item, index) in paginatedData" :key="index">
168
+ <tr class="hover:bg-gray-50 dark:hover:bg-gray-700">
169
+ <td class="px-6 py-4 whitespace-nowrap text-sm font-medium" x-text="item.Name"></td>
170
+ <td class="px-6 py-4 whitespace-nowrap text-sm" x-text="item.Type"></td>
171
+ <td class="px-6 py-4 whitespace-nowrap text-sm text-right" x-text="formatNumber(item.TimesTraded)"></td>
172
+ <td class="px-6 py-4 whitespace-nowrap text-sm text-right" x-text="formatNumber(item.UniqueCirculation)"></td>
173
+ <td class="px-6 py-4 whitespace-nowrap text-sm text-right">
174
+ <span
175
+ class="px-2 inline-flex text-xs leading-5 font-semibold rounded-full"
176
+ :class="getDemandClass(item.DemandMultiple)"
177
+ x-text="formatDecimal(item.DemandMultiple)"
178
+ ></span>
179
+ </td>
180
+ </tr>
181
+ </template>
182
+ </tbody>
183
+ </table>
184
+ </div>
185
+
186
+ <!-- Loading Spinner -->
187
+ <div class="flex justify-center items-center py-10" x-show="loading">
188
+ <svg class="animate-spin h-10 w-10 text-blue-500" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24">
189
+ <circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4"></circle>
190
+ <path class="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"></path>
191
+ </svg>
192
+ </div>
193
+
194
+ <!-- Pagination -->
195
+ <div class="px-4 py-3 flex items-center justify-between border-t dark:border-gray-700" x-show="!loading">
196
+ <div class="hidden sm:flex-1 sm:flex sm:items-center sm:justify-between">
197
+ <div>
198
+ <p class="text-sm text-gray-700 dark:text-gray-300">
199
+ Showing <span class="font-medium" x-text="(currentPage - 1) * pageSize + 1"></span> to
200
+ <span class="font-medium" x-text="Math.min(currentPage * pageSize, filteredData.length)"></span> of
201
+ <span class="font-medium" x-text="filteredData.length"></span> results
202
+ </p>
203
+ </div>
204
+ <div>
205
+ <nav class="relative z-0 inline-flex rounded-md shadow-sm -space-x-px" aria-label="Pagination">
206
+ <button
207
+ class="relative inline-flex items-center px-2 py-2 rounded-l-md border border-gray-300 dark:border-gray-600 bg-white dark:bg-gray-700 text-sm font-medium text-gray-500 dark:text-gray-300 hover:bg-gray-50 dark:hover:bg-gray-600"
208
+ :disabled="currentPage === 1"
209
+ @click="currentPage--"
210
+ :class="{'opacity-50 cursor-not-allowed': currentPage === 1}"
211
+ >
212
+ <span class="sr-only">Previous</span>
213
+ <svg class="h-5 w-5" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true">
214
+ <path fill-rule="evenodd" d="M12.707 5.293a1 1 0 010 1.414L9.414 10l3.293 3.293a1 1 0 01-1.414 1.414l-4-4a1 1 0 010-1.414l4-4a1 1 0 011.414 0z" clip-rule="evenodd" />
215
+ </svg>
216
+ </button>
217
+ <template x-for="page in totalPages" :key="page">
218
+ <button
219
+ class="relative inline-flex items-center px-4 py-2 border border-gray-300 dark:border-gray-600 bg-white dark:bg-gray-700 text-sm font-medium hover:bg-gray-50 dark:hover:bg-gray-600"
220
+ :class="{'bg-blue-50 dark:bg-blue-900 text-blue-600 dark:text-blue-300': currentPage === page, 'text-gray-700 dark:text-gray-300': currentPage !== page}"
221
+ @click="currentPage = page"
222
+ x-text="page"
223
+ ></button>
224
+ </template>
225
+ <button
226
+ class="relative inline-flex items-center px-2 py-2 rounded-r-md border border-gray-300 dark:border-gray-600 bg-white dark:bg-gray-700 text-sm font-medium text-gray-500 dark:text-gray-300 hover:bg-gray-50 dark:hover:bg-gray-600"
227
+ :disabled="currentPage === totalPages"
228
+ @click="currentPage++"
229
+ :class="{'opacity-50 cursor-not-allowed': currentPage === totalPages}"
230
+ >
231
+ <span class="sr-only">Next</span>
232
+ <svg class="h-5 w-5" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true">
233
+ <path fill-rule="evenodd" d="M7.293 14.707a1 1 0 010-1.414L10.586 10 7.293 6.707a1 1 0 011.414-1.414l4 4a1 1 0 010 1.414l-4 4a1 1 0 01-1.414 0z" clip-rule="evenodd" />
234
+ </svg>
235
+ </button>
236
+ </nav>
237
+ </div>
238
+ </div>
239
+ </div>
240
+ </div>
241
+
242
+ <!-- Analytics Insights -->
243
+ <div class="bg-white dark:bg-gray-800 rounded-lg shadow-md p-6">
244
+ <h2 class="text-lg font-semibold mb-4">Data Insights</h2>
245
+ <div class="grid grid-cols-1 md:grid-cols-2 gap-6">
246
+ <div>
247
+ <h3 class="font-medium mb-2">Top Demand Vehicles</h3>
248
+ <ul class="space-y-2">
249
+ <template x-for="(item, index) in topDemandVehicles.slice(0, 5)" :key="index">
250
+ <li class="flex justify-between items-center">
251
+ <span x-text="item.Name"></span>
252
+ <span class="font-semibold" x-text="formatDecimal(item.DemandMultiple)"></span>
253
+ </li>
254
+ </template>
255
+ </ul>
256
+ </div>
257
+ <div>
258
+ <h3 class="font-medium mb-2">Most Traded Items</h3>
259
+ <ul class="space-y-2">
260
+ <template x-for="(item, index) in mostTradedItems.slice(0, 5)" :key="index">
261
+ <li class="flex justify-between items-center">
262
+ <span x-text="item.Name"></span>
263
+ <span class="font-semibold" x-text="formatNumber(item.TimesTraded)"></span>
264
+ </li>
265
+ </template>
266
+ </ul>
267
+ </div>
268
+ </div>
269
+ </div>
270
+ </div>
271
+ </div>
272
+
273
+ <script>
274
+ function dashboard() {
275
+ return {
276
+ data: [],
277
+ filteredData: [],
278
+ loading: true,
279
+ columns: [
280
+ { key: 'Name', label: 'Item Name' },
281
+ { key: 'Type', label: 'Type' },
282
+ { key: 'TimesTraded', label: 'Times Traded' },
283
+ { key: 'UniqueCirculation', label: 'Circulation' },
284
+ { key: 'DemandMultiple', label: 'Demand' }
285
+ ],
286
+ sortColumn: 'DemandMultiple',
287
+ sortDirection: 'desc',
288
+ currentPage: 1,
289
+ pageSize: 10,
290
+ searchQuery: '',
291
+ selectedType: '',
292
+ types: [],
293
+ stats: {
294
+ totalItems: 0,
295
+ totalTrades: 0,
296
+ totalCirculation: 0,
297
+ avgDemand: 0
298
+ },
299
+
300
+ init() {
301
+ this.fetchData();
302
+ },
303
+
304
+ async fetchData() {
305
+ try {
306
+ const response = await fetch('/api/data');
307
+ this.data = await response.json();
308
+
309
+ // Extract unique types
310
+ this.types = [...new Set(this.data.map(item => item.Type))];
311
+
312
+ // Calculate stats
313
+ this.stats.totalItems = this.data.length;
314
+ this.stats.totalTrades = this.data.reduce((sum, item) => sum + item.TimesTraded, 0);
315
+ this.stats.totalCirculation = this.data.reduce((sum, item) => sum + item.UniqueCirculation, 0);
316
+ this.stats.avgDemand = this.formatDecimal(this.data.reduce((sum, item) => sum + item.DemandMultiple, 0) / this.data.length);
317
+
318
+ this.sortData();
319
+ this.filterData();
320
+
321
+ // Initialize charts
322
+ this.initCharts();
323
+
324
+ this.loading = false;
325
+ } catch (error) {
326
+ console.error("Error fetching data:", error);
327
+ this.loading = false;
328
+ }
329
+ },
330
+
331
+ sortData() {
332
+ const column = this.sortColumn;
333
+ const direction = this.sortDirection === 'asc' ? 1 : -1;
334
+
335
+ this.data.sort((a, b) => {
336
+ if (typeof a[column] === 'string') {
337
+ return direction * a[column].localeCompare(b[column]);
338
+ } else {
339
+ return direction * (a[column] - b[column]);
340
+ }
341
+ });
342
+ },
343
+
344
+ sortBy(column) {
345
+ if (this.sortColumn === column) {
346
+ this.sortDirection = this.sortDirection === 'asc' ? 'desc' : 'asc';
347
+ } else {
348
+ this.sortColumn = column;
349
+ this.sortDirection = 'desc';
350
+ }
351
+
352
+ this.sortBy(column);
353
+ this.filterData();
354
+ },
355
+
356
+ filterData() {
357
+ this.currentPage = 1;
358
+
359
+ this.filteredData = this.data.filter(item => {
360
+ const matchesSearch = this.searchQuery === '' ||
361
+ item.Name.toLowerCase().includes(this.searchQuery.toLowerCase()) ||
362
+ item.Type.toLowerCase().includes(this.searchQuery.toLowerCase());
363
+
364
+ const matchesType = this.selectedType === '' || item.Type === this.selectedType;
365
+
366
+ return matchesSearch && matchesType;
367
+ });
368
+ },
369
+
370
+ get paginatedData() {
371
+ const start = (this.currentPage - 1) * this.pageSize;
372
+ const end = start + this.pageSize;
373
+ return this.filteredData.slice(start, end);
374
+ },
375
+
376
+ get totalPages() {
377
+ return Math.max(1, Math.ceil(this.filteredData.length / this.pageSize));
378
+ },
379
+
380
+ formatNumber(num) {
381
+ return num.toLocaleString();
382
+ },
383
+
384
+ formatDecimal(num) {
385
+ return Number(num).toFixed(2);
386
+ },
387
+
388
+ getDemandClass(value) {
389
+ if (value >= 5) return 'bg-red-100 text-red-800 dark:bg-red-900 dark:text-red-200';
390
+ if (value >= 3) return 'bg-orange-100 text-orange-800 dark:bg-orange-900 dark:text-orange-200';
391
+ if (value >= 2) return 'bg-yellow-100 text-yellow-800 dark:bg-yellow-900 dark:text-yellow-200';
392
+ return 'bg-green-100 text-green-800 dark:bg-green-900 dark:text-green-200';
393
+ },
394
+
395
+ get topDemandVehicles() {
396
+ return [...this.data]
397
+ .filter(item => item.Type === 'Vehicle')
398
+ .sort((a, b) => b.DemandMultiple - a.DemandMultiple);
399
+ },
400
+
401
+ get mostTradedItems() {
402
+ return [...this.data].sort((a, b) => b.TimesTraded - a.TimesTraded);
403
+ },
404
+
405
+ initCharts() {
406
+ // Chart for top demand items
407
+ const topItems = [...this.data]
408
+ .sort((a, b) => b.DemandMultiple - a.DemandMultiple)
409
+ .slice(0, 10);
410
+
411
+ new Chart(document.getElementById('demandChart'), {
412
+ type: 'bar',
413
+ data: {
414
+ labels: topItems.map(item => item.Name),
415
+ datasets: [{
416
+ label: 'Demand Multiple',
417
+ data: topItems.map(item => item.DemandMultiple),
418
+ backgroundColor: 'rgba(59, 130, 246, 0.8)',
419
+ borderColor: 'rgb(59, 130, 246)',
420
+ borderWidth: 1
421
+ }]
422
+ },
423
+ options: {
424
+ indexAxis: 'y',
425
+ responsive: true,
426
+ maintainAspectRatio: false,
427
+ plugins: {
428
+ legend: {
429
+ display: false
430
+ },
431
+ tooltip: {
432
+ callbacks: {
433
+ label: function(context) {
434
+ return `Demand: ${context.raw.toFixed(2)}`;
435
+ }
436
+ }
437
+ }
438
+ },
439
+ scales: {
440
+ x: {
441
+ grid: {
442
+ color: 'rgba(156, 163, 175, 0.1)'
443
+ }
444
+ },
445
+ y: {
446
+ grid: {
447
+ display: false
448
+ }
449
+ }
450
+ }
451
+ }
452
+ });
453
+
454
+ // Chart for item types distribution
455
+ const typeData = {};
456
+ this.data.forEach(item => {
457
+ if (!typeData[item.Type]) {
458
+ typeData[item.Type] = 0;
459
+ }
460
+ typeData[item.Type]++;
461
+ });
462
+
463
+ const typeLabels = Object.keys(typeData);
464
+ const typeCounts = Object.values(typeData);
465
+
466
+ const colors = [
467
+ 'rgba(59, 130, 246, 0.8)',
468
+ 'rgba(16, 185, 129, 0.8)',
469
+ 'rgba(245, 158, 11, 0.8)',
470
+ 'rgba(239, 68, 68, 0.8)',
471
+ 'rgba(139, 92, 246, 0.8)',
472
+ 'rgba(236, 72, 153, 0.8)'
473
+ ];
474
+
475
+ new Chart(document.getElementById('typeChart'), {
476
+ type: 'pie',
477
+ data: {
478
+ labels: typeLabels,
479
+ datasets: [{
480
+ data: typeCounts,
481
+ backgroundColor: colors.slice(0, typeLabels.length),
482
+ borderColor: 'rgba(255, 255, 255, 0.5)',
483
+ borderWidth: 2
484
+ }]
485
+ },
486
+ options: {
487
+ responsive: true,
488
+ maintainAspectRatio: false,
489
+ plugins: {
490
+ legend: {
491
+ position: 'bottom',
492
+ labels: {
493
+ padding: 20
494
+ }
495
+ }
496
+ }
497
+ }
498
+ });
499
+ }
500
+ };
501
+ }
502
+ </script>
503
+ </body>
504
+ </html>