55 < meta name ="viewport " content ="width=device-width, initial-scale=1.0 " />
66 < title > Usage dashboard</ title >
77 < script src ="https://cdn.tailwindcss.com "> </ script >
8- < script src ="https://cdn.jsdelivr.net/npm/apexcharts "> </ script >
98</ head >
109< body class ="bg-gray-50 min-h-screen ">
1110
1413 < div class ="mx-auto max-w-7xl px-4 py-4 sm:px-6 lg:px-8 flex justify-between items-center ">
1514 < div class ="flex items-center space-x-3 ">
1615 < div class ="p-2 bg-indigo-100 rounded-lg ">
17- < svg class ="h-6 w-6 text-indigo-600 " fill ="none " stroke ="currentColor " viewBox ="0 0 24 24 ">
18- < 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 "/ >
16+ < svg class ="h-6 w-6 text-indigo-600 " fill ="none " stroke ="currentColor " viewBox ="0 0 24 24 " xmlns =" http://www.w3.org/2000/svg " >
17+ < 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 "> </ path >
1918 </ svg >
2019 </ div >
2120 < h1 class ="text-2xl font-bold tracking-tight text-gray-900 "> Usage Dashboard</ h1 >
2221 </ div >
2322 < div class ="flex items-center space-x-2 text-sm text-gray-500 ">
24- < svg class ="h-4 w-4 text-gray-400 " fill ="none " stroke ="currentColor " viewBox ="0 0 24 24 ">
25- < path stroke-linecap ="round " stroke-linejoin ="round " stroke-width ="2 " d ="M12 8v4l3 3m6-3a9 9 0 11-18 0 9 9 0 0118 0z "/ >
23+ < svg class ="h-4 w-4 text-gray-400 " fill ="none " stroke ="currentColor " viewBox ="0 0 24 24 " xmlns =" http://www.w3.org/2000/svg " >
24+ < path stroke-linecap ="round " stroke-linejoin ="round " stroke-width ="2 " d ="M12 8v4l3 3m6-3a9 9 0 11-18 0 9 9 0 0118 0z "> </ path >
2625 </ svg >
2726 < span > Last updated: < span id ="lastUpdate " class ="font-medium "> </ span > </ span >
2827 </ div >
2928 </ div >
3029</ header >
3130
32-
3331<!-- Main Content -->
3432< main class ="mx-auto max-w-7xl px-4 py-6 sm:px-6 lg:px-8 ">
3533 <!-- Stats Overview -->
36- < div class ="grid grid-cols-1 gap-6 sm:grid-cols-2 lg:grid-cols-4 mb-6 ">
37- <!-- Stat Box Template -->
34+ < div class ="grid grid-cols-1 gap-6 sm:grid-cols-2 lg:grid-cols-4 mb-8 ">
35+ <!-- Total Users -->
3836 < div class ="bg-white rounded-lg shadow p-6 ">
3937 < div class ="flex items-center ">
4038 < div class ="p-3 rounded-full bg-blue-100 mr-4 ">
41- < svg class ="h-6 w-6 text-blue-600 " fill ="none " stroke ="currentColor " viewBox ="0 0 24 24 ">
42- < path
43- stroke-linecap ="round "
44- stroke-linejoin ="round "
45- stroke-width ="2 "
46- 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 "
47- />
39+ < svg class ="h-6 w-6 text-blue-600 " fill ="none " stroke ="currentColor " viewBox ="0 0 24 24 " xmlns ="http://www.w3.org/2000/svg ">
40+ < 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 "> </ path >
4841 </ svg >
4942 </ div >
5043 < div >
@@ -53,11 +46,13 @@ <h3 class="text-gray-500 text-sm font-medium">Total users</h3>
5346 </ div >
5447 </ div >
5548 </ div >
49+
50+ <!-- Total Messages -->
5651 < div class ="bg-white rounded-lg shadow p-6 ">
5752 < div class ="flex items-center ">
5853 < div class ="p-3 rounded-full bg-purple-100 mr-4 ">
59- < svg class ="h-6 w-6 text-purple-600 " fill ="none " stroke ="currentColor " viewBox ="0 0 24 24 ">
60- < path stroke-linecap ="round " stroke-linejoin ="round " stroke-width ="2 " d ="M8 10h.01M12 10h.01M16 10h.01M9 16H5a2 2 0 01-2-2V6a2 2 0 012-2h14a2 2 0 012 2v8a2 2 0 01-2 2h-5l-5 5v-5z " / >
54+ < svg class ="h-6 w-6 text-purple-600 " fill ="none " stroke ="currentColor " viewBox ="0 0 24 24 " xmlns =" http://www.w3.org/2000/svg " >
55+ < path stroke-linecap ="round " stroke-linejoin ="round " stroke-width ="2 " d ="M8 10h.01M12 10h.01M16 10h.01M9 16H5a2 2 0 01-2-2V6a2 2 0 012-2h14a2 2 0 012 2v8a2 2 0 01-2 2h-5l-5 5v-5z "> </ path >
6156 </ svg >
6257 </ div >
6358 < div >
@@ -66,16 +61,13 @@ <h3 class="text-gray-500 text-sm font-medium">Total messages</h3>
6661 </ div >
6762 </ div >
6863 </ div >
64+
65+ <!-- Average Messages -->
6966 < div class ="bg-white rounded-lg shadow p-6 ">
7067 < div class ="flex items-center ">
7168 < div class ="p-3 rounded-full bg-green-100 mr-4 ">
72- < svg class ="h-6 w-6 text-green-600 " fill ="none " stroke ="currentColor " viewBox ="0 0 24 24 ">
73- < path
74- stroke-linecap ="round "
75- stroke-linejoin ="round "
76- stroke-width ="2 "
77- 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 "
78- />
69+ < svg class ="h-6 w-6 text-green-600 " fill ="none " stroke ="currentColor " viewBox ="0 0 24 24 " xmlns ="http://www.w3.org/2000/svg ">
70+ < 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 "> </ path >
7971 </ svg >
8072 </ div >
8173 < div >
@@ -84,33 +76,23 @@ <h3 class="text-gray-500 text-sm font-medium">Avg messages/user</h3>
8476 </ div >
8577 </ div >
8678 </ div >
79+
80+ <!-- Active Countries -->
8781 < div class ="bg-white rounded-lg shadow p-6 ">
8882 < div class ="flex items-center ">
8983 < div class ="p-3 rounded-full bg-yellow-100 mr-4 ">
90- < svg class ="h-6 w-6 text-yellow-600 " fill ="none " stroke ="currentColor " viewBox ="0 0 24 24 ">
91- < path stroke-linecap ="round " stroke-linejoin ="round " stroke-width ="2 " d ="M3 5h12M9 3v2m1.048 9.5A18.022 18.022 0 016.412 9m6.088 9h7M11 21l5-10 5 10M12.751 5C11.783 10.77 8.07 15.61 3 18.129 " / >
84+ < svg class ="h-6 w-6 text-yellow-600 " fill ="none " stroke ="currentColor " viewBox ="0 0 24 24 " xmlns =" http://www.w3.org/2000/svg " >
85+ < path stroke-linecap ="round " stroke-linejoin ="round " stroke-width ="2 " d ="M3 21v-4m0 0V5a2 2 0 012-2h6.5l1 1H21l-3 6 3 6h-8.5l-1-1H5a2 2 0 00-2 2zm9-13.5V9 " > </ path >
9286 </ svg >
9387 </ div >
9488 < div >
95- < h3 class ="text-gray-500 text-sm font-medium "> Active Countrys </ h3 >
96- < p class ="text-2xl font-bold text-gray-900 " id ="activeCountrys "> 0</ p >
89+ < h3 class ="text-gray-500 text-sm font-medium "> Active Countries </ h3 >
90+ < p class ="text-2xl font-bold text-gray-900 " id ="activeCountries "> 0</ p >
9791 </ div >
9892 </ div >
9993 </ div >
10094 </ div >
10195
102- <!-- Charts Row -->
103- < div class ="grid grid-cols-1 lg:grid-cols-2 gap-6 mb-6 ">
104- < div class ="bg-white rounded-lg shadow p-6 ">
105- < h3 class ="text-lg font-medium mb-4 "> Messages per user</ h3 >
106- < div id ="messagesPerUserChart "> </ div >
107- </ div >
108- < div class ="bg-white rounded-lg shadow p-6 ">
109- < h3 class ="text-lg font-medium mb-4 "> Country distribution</ h3 >
110- < div id ="CountryChart "> </ div >
111- </ div >
112- </ div >
113-
11496 <!-- Users Table -->
11597 < div class ="bg-white rounded-lg shadow p-6 ">
11698 < h2 class ="text-xl font-semibold mb-4 "> User activity</ h2 >
@@ -134,31 +116,30 @@ <h2 class="text-xl font-semibold mb-4">User activity</h2>
134116<!-- Scripts -->
135117< script >
136118 const users = { { ! user_data} }
119+
137120 document . addEventListener ( 'DOMContentLoaded' , ( ) => {
121+ updateLastUpdatedTime ( ) ;
122+ updateStats ( ) ;
123+ initTable ( ) ;
124+ } ) ;
125+
126+ function updateLastUpdatedTime ( ) {
138127 const lastUpdateElement = document . getElementById ( 'lastUpdate' ) ;
139128 if ( lastUpdateElement ) {
140129 lastUpdateElement . textContent = new Date ( ) . toLocaleString ( ) ;
141130 }
142- updateStats ( ) ;
143- initTable ( ) ;
144- initCharts ( ) ;
145- } ) ;
131+ }
146132
147133 function updateStats ( ) {
148134 const totalUsers = users . length ;
149135 const totalMessages = users . reduce ( ( sum , user ) => sum + user . messageCount , 0 ) ;
150136 const avgMessages = totalUsers > 0 ? Math . round ( totalMessages / totalUsers ) : 0 ;
151- const activeCountrys = new Set ( users . map ( u => u . languageCode ) ) . size ;
152-
153- const totalUsersElement = document . getElementById ( 'totalUsers' ) ;
154- const totalMessagesElement = document . getElementById ( 'totalMessages' ) ;
155- const avgMessagesElement = document . getElementById ( 'avgMessages' ) ;
156- const activeCountrysElement = document . getElementById ( 'activeCountrys' ) ;
137+ const activeCountries = new Set ( users . map ( u => u . languageCode ) ) . size ;
157138
158- if ( totalUsersElement ) totalUsersElement . textContent = totalUsers ;
159- if ( totalMessagesElement ) totalMessagesElement . textContent = totalMessages . toLocaleString ( ) ;
160- if ( avgMessagesElement ) avgMessagesElement . textContent = avgMessages ;
161- if ( activeCountrysElement ) activeCountrysElement . textContent = activeCountrys ;
139+ document . getElementById ( 'totalUsers' ) . textContent = totalUsers ;
140+ document . getElementById ( 'totalMessages' ) . textContent = totalMessages . toLocaleString ( ) ;
141+ document . getElementById ( 'avgMessages' ) . textContent = avgMessages ;
142+ document . getElementById ( 'activeCountries' ) . textContent = activeCountries ;
162143 }
163144
164145 function initTable ( ) {
@@ -184,69 +165,6 @@ <h2 class="text-xl font-semibold mb-4">User activity</h2>
184165 tableBody . appendChild ( row ) ;
185166 } ) ;
186167 }
187-
188- function initCharts ( ) {
189- // Messages per User Chart
190- const messagesPerUserOptions = {
191- series : [ {
192- name : 'Messages' ,
193- data : users . map ( user => user . messageCount )
194- } ] ,
195- chart : {
196- type : 'bar' ,
197- height : 350 ,
198- } ,
199- plotOptions : {
200- bar : {
201- borderRadius : 10 ,
202- }
203- } ,
204- colors : [ '#4F46E5' ] ,
205- xaxis : {
206- categories : users . map ( user => user . userName ) ,
207- position : 'bottom'
208- } ,
209- dataLabels : {
210- enabled : false
211- } ,
212- } ;
213-
214- const messagesPerUserChart = new ApexCharts ( document . querySelector ( "#messagesPerUserChart" ) , messagesPerUserOptions ) ;
215- messagesPerUserChart . render ( ) ;
216-
217- // Country Distribution Chart
218- const CountryCounts = users . reduce ( ( counts , user ) => {
219- counts [ user . languageCode ] = ( counts [ user . languageCode ] || 0 ) + 1 ;
220- return counts ;
221- } , { } ) ;
222-
223- const CountryChartOptions = {
224- series : [ {
225- name : 'Users' ,
226- data : Object . values ( CountryCounts )
227- } ] ,
228- chart : {
229- type : 'bar' ,
230- height : 350
231- } ,
232- plotOptions : {
233- bar : {
234- borderRadius : 10 ,
235- }
236- } ,
237- colors : [ '#4F46E5' ] ,
238- xaxis : {
239- categories : Object . keys ( CountryCounts ) . map ( lang => lang . toUpperCase ( ) ) ,
240- position : 'bottom'
241- } ,
242- dataLabels : {
243- enabled : false
244- }
245- } ;
246-
247- const CountryChart = new ApexCharts ( document . querySelector ( "#CountryChart" ) , CountryChartOptions ) ;
248- CountryChart . render ( ) ;
249- }
250168</ script >
251169</ body >
252170</ html >
0 commit comments