@@ -161,6 +161,8 @@ function Dashboard() {
161161 const paymentsCanvas = Ref ( ) ;
162162 const paymentStatusCanvas = Ref ( ) ;
163163 const editorCanvas = Ref ( ) ;
164+ const topDevCanvas = Ref ( ) ;
165+ const providerStatusCanvas = Ref ( ) ;
164166
165167 ref . append (
166168 < div className = 'dashboard-grid' >
@@ -183,6 +185,18 @@ function Dashboard() {
183185 < canvas ref = { paymentsCanvas } />
184186 </ div >
185187 </ div >
188+ < div className = 'chart-card' >
189+ < h3 > Top Developers (INR)</ h3 >
190+ < div className = 'chart-container' >
191+ < canvas ref = { topDevCanvas } />
192+ </ div >
193+ </ div >
194+ < div className = 'chart-card' >
195+ < h3 > Orders: Provider vs Status</ h3 >
196+ < div className = 'chart-container' >
197+ < canvas ref = { providerStatusCanvas } />
198+ </ div >
199+ </ div >
186200 < div className = 'chart-card' >
187201 < h3 > Payment Status</ h3 >
188202 < div className = 'chart-container chart-container--small' >
@@ -200,6 +214,8 @@ function Dashboard() {
200214
201215 initChart ( revenueCanvas , lineChartConfig ( analytics . monthlyRevenue , 'Revenue (INR)' , '#22c55e' ) ) ;
202216 initChart ( paymentsCanvas , lineChartConfig ( analytics . monthlyPayments , 'Payments (INR)' , '#3b82f6' ) ) ;
217+ initChart ( topDevCanvas , horizontalBarChartConfig ( analytics . topDevelopers , 'name' , 'total' ) ) ;
218+ initChart ( providerStatusCanvas , providerStatusChartConfig ( analytics . providerStatus ) ) ;
203219 initChart ( paymentStatusCanvas , doughnutChartConfig ( analytics . paymentStatus , 'status' , 'count' ) ) ;
204220 initChart ( editorCanvas , doughnutChartConfig ( analytics . editorDistribution , 'editor' , 'count' ) ) ;
205221 } catch {
@@ -235,8 +251,8 @@ function lineChartConfig(rows, label, color = '#3b82f6') {
235251 type : 'line' ,
236252 data : {
237253 labels : months . map ( ( m ) => {
238- const [ _ , mo ] = m . split ( '-' ) ;
239- return monthNames [ Number ( mo ) - 1 ] . slice ( 0 , 3 ) ;
254+ const [ yr , mo ] = m . split ( '-' ) ;
255+ return ` ${ monthNames [ Number ( mo ) - 1 ] . slice ( 0 , 3 ) } ' ${ yr . slice ( 2 ) } ` ;
240256 } ) ,
241257 datasets : [
242258 {
@@ -259,6 +275,7 @@ function lineChartConfig(rows, label, color = '#3b82f6') {
259275function doughnutChartConfig ( rows , labelKey , valueKey ) {
260276 const labels = rows . map ( ( r ) => r [ labelKey ] ) ;
261277 const values = rows . map ( ( r ) => Number ( r [ valueKey ] ) || 0 ) ;
278+ const total = values . reduce ( ( a , b ) => a + b , 0 ) || 1 ;
262279 const colors = [ '#3b82f6' , '#22c55e' , '#f59e0b' , '#ef4444' , '#8b5cf6' ] ;
263280
264281 return {
@@ -275,14 +292,160 @@ function doughnutChartConfig(rows, labelKey, valueKey) {
275292 ] ,
276293 } ,
277294 options : {
278- ...chartBaseOptions ( ) ,
295+ responsive : true ,
296+ maintainAspectRatio : false ,
279297 plugins : {
280298 legend : {
281299 position : 'bottom' ,
282300 labels : { color : 'rgba(255,255,255,0.7)' , padding : 16 , font : { size : 12 } } ,
283301 } ,
284302 } ,
285303 } ,
304+ plugins : [
305+ {
306+ id : 'doughnutPercentLabels' ,
307+ afterDatasetsDraw ( chart ) {
308+ const { ctx, data : chartData } = chart ;
309+ const dataset = chartData . datasets [ 0 ] ;
310+ const meta = chart . getDatasetMeta ( 0 ) ;
311+ ctx . save ( ) ;
312+ ctx . font = 'bold 14px sans-serif' ;
313+ ctx . textAlign = 'center' ;
314+ ctx . textBaseline = 'middle' ;
315+ for ( let i = 0 ; i < dataset . data . length ; i ++ ) {
316+ const value = dataset . data [ i ] ;
317+ if ( value === 0 ) continue ;
318+ const pct = Math . round ( ( value / total ) * 100 ) ;
319+ const arc = meta . data [ i ] ;
320+ const { x, y } = arc . tooltipPosition ( true ) ;
321+ ctx . fillStyle = '#ffffff' ;
322+ ctx . shadowColor = 'rgba(0,0,0,0.6)' ;
323+ ctx . shadowBlur = 3 ;
324+ ctx . fillText ( `${ pct } %` , x , y ) ;
325+ ctx . shadowBlur = 0 ;
326+ }
327+ ctx . restore ( ) ;
328+ } ,
329+ } ,
330+ ] ,
331+ } ;
332+ }
333+
334+ function horizontalBarChartConfig ( rows , labelKey , valueKey ) {
335+ const names = rows . map ( ( r ) => r [ labelKey ] || 'Unknown' ) ;
336+ const values = rows . map ( ( r ) => Number ( r [ valueKey ] ) || 0 ) ;
337+ const colors = [ '#22c55e' , '#3b82f6' , '#f59e0b' , '#8b5cf6' , '#ef4444' , '#06b6d4' , '#a855f7' , '#ec4899' , '#84cc16' , '#f97316' ] ;
338+
339+ return {
340+ type : 'bar' ,
341+ data : {
342+ labels : names ,
343+ datasets : [
344+ {
345+ label : 'Earnings (INR)' ,
346+ data : values ,
347+ backgroundColor : names . map ( ( _ , i ) => colors [ i % colors . length ] ) ,
348+ borderColor : 'rgba(0,0,0,0.2)' ,
349+ borderWidth : 1 ,
350+ borderRadius : 4 ,
351+ } ,
352+ ] ,
353+ } ,
354+ options : {
355+ indexAxis : 'y' ,
356+ responsive : true ,
357+ maintainAspectRatio : false ,
358+ plugins : {
359+ legend : { display : false } ,
360+ } ,
361+ scales : {
362+ x : {
363+ ticks : { color : 'rgba(255,255,255,0.5)' , font : { size : 11 } } ,
364+ grid : { color : 'rgba(255,255,255,0.06)' } ,
365+ beginAtZero : true ,
366+ } ,
367+ y : {
368+ ticks : { color : 'rgba(255,255,255,0.7)' , font : { size : 12 } , padding : 8 } ,
369+ grid : { display : false } ,
370+ } ,
371+ } ,
372+ } ,
373+ } ;
374+ }
375+
376+ function providerStatusChartConfig ( rows ) {
377+ const providerLabels = [ ...new Set ( rows . map ( ( r ) => r . provider ) ) ] ;
378+ const statuses = [ ...new Set ( rows . map ( ( r ) => r . status ) ) ] ;
379+ const colorMap = { Successful : '#22c55e' , Failed : '#ef4444' , Other : '#f59e0b' } ;
380+ const providerNameMap = { google_play : 'Google Play' , razorpay : 'Razorpay' } ;
381+
382+ const dataMap = { } ;
383+ for ( const row of rows ) {
384+ if ( ! dataMap [ row . status ] ) dataMap [ row . status ] = { } ;
385+ dataMap [ row . status ] [ row . provider ] = ( dataMap [ row . status ] [ row . provider ] || 0 ) + Number ( row . count ) ;
386+ }
387+
388+ const datasets = statuses . map ( ( status ) => ( {
389+ label : status ,
390+ data : providerLabels . map ( ( p ) => dataMap [ status ] ?. [ p ] || 0 ) ,
391+ backgroundColor : colorMap [ status ] || '#8b5cf6' ,
392+ borderColor : 'rgba(0,0,0,0.2)' ,
393+ borderWidth : 1 ,
394+ } ) ) ;
395+
396+ return {
397+ type : 'bar' ,
398+ data : {
399+ labels : providerLabels . map ( ( p ) => providerNameMap [ p ] || p ) ,
400+ datasets,
401+ } ,
402+ options : {
403+ responsive : true ,
404+ maintainAspectRatio : false ,
405+ plugins : {
406+ legend : {
407+ position : 'bottom' ,
408+ labels : { color : 'rgba(255,255,255,0.7)' , padding : 16 , font : { size : 12 } } ,
409+ } ,
410+ } ,
411+ scales : {
412+ x : {
413+ ticks : { color : 'rgba(255,255,255,0.5)' , font : { size : 11 } } ,
414+ grid : { color : 'rgba(255,255,255,0.06)' } ,
415+ } ,
416+ y : {
417+ ticks : { color : 'rgba(255,255,255,0.5)' , font : { size : 11 } } ,
418+ grid : { color : 'rgba(255,255,255,0.06)' } ,
419+ beginAtZero : true ,
420+ } ,
421+ } ,
422+ } ,
423+ plugins : [
424+ {
425+ id : 'barValueLabels' ,
426+ afterDatasetsDraw ( chart ) {
427+ const { ctx } = chart ;
428+ ctx . save ( ) ;
429+ ctx . font = 'bold 11px sans-serif' ;
430+ ctx . textAlign = 'center' ;
431+ ctx . textBaseline = 'bottom' ;
432+ ctx . fillStyle = '#ffffff' ;
433+ ctx . shadowColor = 'rgba(0,0,0,0.5)' ;
434+ ctx . shadowBlur = 2 ;
435+ for ( const ds of chart . data . datasets ) {
436+ const meta = chart . getDatasetMeta ( chart . data . datasets . indexOf ( ds ) ) ;
437+ for ( let i = 0 ; i < ds . data . length ; i ++ ) {
438+ const value = ds . data [ i ] ;
439+ if ( ! value ) continue ;
440+ const { x, y } = meta . data [ i ] ;
441+ ctx . fillText ( value , x , y - 2 ) ;
442+ }
443+ }
444+ ctx . shadowBlur = 0 ;
445+ ctx . restore ( ) ;
446+ } ,
447+ } ,
448+ ] ,
286449 } ;
287450}
288451
0 commit comments