1- function formatDate ( d ) {
1+ const CHARTS = Object . create ( null ) ;
2+
3+ function toISODate ( d ) {
24 const y = d . getFullYear ( ) ;
35 const m = String ( d . getMonth ( ) + 1 ) . padStart ( 2 , "0" ) ;
46 const day = String ( d . getDate ( ) ) . padStart ( 2 , "0" ) ;
57 return `${ y } -${ m } -${ day } ` ;
68}
79
8- function subtractMonths ( date , n ) {
10+ function subMonths ( date , n ) {
911 const d = new Date ( date ) ;
1012 const origDay = d . getDate ( ) ;
1113 d . setDate ( 1 ) ;
@@ -15,36 +17,43 @@ function subtractMonths(date, n) {
1517 return d ;
1618}
1719
18- function buildQueryForRange ( range ) {
19- if ( range === ' all' ) return '' ;
20+ function computeRange ( type , value ) {
21+ if ( type === " all" ) return null ;
2022
21- const days = Number ( range ) ;
22- const now = new Date ( ) ;
23- const stop = new Date ( Date . UTC ( now . getUTCFullYear ( ) , now . getUTCMonth ( ) , now . getUTCDate ( ) ) ) ;
24- const start = new Date ( stop ) ;
25- start . setUTCDate ( start . getUTCDate ( ) - days ) ;
23+ const today = new Date ( ) ;
24+ let start , stop ;
2625
27- const params = new URLSearchParams ( {
28- start_date : formatDateUTC_YYYYMMDD ( start ) ,
29- stop_date : formatDateUTC_YYYYMMDD ( stop ) ,
30- } ) ;
31- return `?${ params . toString ( ) } ` ;
26+ if ( type === "days" ) {
27+ stop = today ;
28+ start = new Date ( today ) ;
29+ start . setDate ( start . getDate ( ) - Number ( value ) ) ;
30+ } else if ( type === "months" ) {
31+ stop = today ;
32+ start = subMonths ( today , Number ( value ) ) ;
33+ }
34+
35+ return { start : toISODate ( start ) , stop : toISODate ( stop ) } ;
3236}
3337
34- async function fetchStatisticsWithRange ( range ) {
35- const qs = buildQueryForRange ( range ) ;
36- const res = await fetch ( `/api/statistics${ qs } ` ) ;
37- if ( ! res . ok ) throw new Error ( 'Failed to fetch /api/statistics' ) ;
38- return await res . json ( ) ;
38+ async function fetchStatistics ( type , value ) {
39+ const range = computeRange ( type , value ) ;
40+ let url = "/api/statistics" ;
41+ if ( range ) {
42+ url += `?start_date=${ range . start } &stop_date=${ range . stop } ` ;
43+ }
44+ const res = await fetch ( url ) ;
45+ if ( ! res . ok ) throw new Error ( "Failed to fetch statistics" ) ;
46+ const data = await res . json ( ) ;
47+ return data ;
3948}
4049
41- async function loadAndRender ( range ) {
50+ async function loadAndRender ( type , value ) {
4251 try {
4352 // fetch stats
4453 const modalEl = document . getElementById ( "loadingModal" ) ;
4554 const modal = new bootstrap . Modal ( modalEl , { backdrop : "static" , keyboard : false } ) ;
4655 modal . show ( ) ;
47- const stats = await fetchStatisticsWithRange ( range ) ;
56+ const stats = await fetchStatistics ( type , value ) ;
4857 modal . hide ( ) ;
4958
5059 // render stats
@@ -80,13 +89,21 @@ function renderSummary(stats) {
8089 ` ;
8190}
8291
92+ function renderChart ( id , config ) {
93+ if ( CHARTS [ id ] ) {
94+ CHARTS [ id ] . destroy ( ) ;
95+ }
96+ const ctx = document . getElementById ( id ) . getContext ( "2d" ) ;
97+ CHARTS [ id ] = new Chart ( ctx , config ) ;
98+ }
99+
83100function buildAuthorsChart ( authorsData ) {
84101 const labels = Object . keys ( authorsData ) ;
85102 const dataValues = Object . values ( authorsData ) . map ( a => a . commits ) ;
86103
87104 const ctx = document . getElementById ( "chartAuthors" ) . getContext ( "2d" ) ;
88105
89- new Chart ( ctx , {
106+ renderChart ( "chartAuthors" , {
90107 type : "pie" ,
91108 data : {
92109 labels : labels ,
@@ -109,7 +126,7 @@ function buildAuthorsStackedChart(authorsData) {
109126
110127 const ctx = document . getElementById ( "chartAuthors2" ) . getContext ( "2d" ) ;
111128
112- new Chart ( ctx , {
129+ renderChart ( "chartAuthors2" , {
113130 type : "bar" ,
114131 data : {
115132 labels,
@@ -172,7 +189,8 @@ function buildHourByAuthorChart(hourOfDayData) {
172189
173190 if ( authors . size === 0 ) {
174191 const totals = HOUR_LABELS . map ( h => hourOfDayData [ h ] ?? 0 ) ;
175- new Chart ( ctx , {
192+
193+ renderChart ( "chartDay" , {
176194 type : "bar" ,
177195 data : { labels : HOUR_LABELS , datasets : [ { label : "Total" , data : totals , stack : "commits" , borderWidth : 1 } ] } ,
178196 options : {
@@ -198,7 +216,7 @@ function buildHourByAuthorChart(hourOfDayData) {
198216 borderWidth : 1
199217 } ) ) ;
200218
201- new Chart ( ctx , {
219+ renderChart ( "chartDay" , {
202220 type : "bar" ,
203221 data : { labels : HOUR_LABELS , datasets } ,
204222 options : {
@@ -231,7 +249,7 @@ function buildWeekByAuthorChart(dayOfWeekData) {
231249
232250 if ( authors . size === 0 ) {
233251 const totals = WEEK_LABELS . map ( d => dayOfWeekData [ d ] ?? 0 ) ;
234- new Chart ( ctx , {
252+ renderChart ( "chartWeek" , {
235253 type : "bar" ,
236254 data : {
237255 labels : WEEK_LABELS ,
@@ -259,7 +277,7 @@ function buildWeekByAuthorChart(dayOfWeekData) {
259277 stack : "commits"
260278 } ) ) ;
261279
262- new Chart ( ctx , {
280+ renderChart ( "chartWeek" , {
263281 type : "bar" ,
264282 data : { labels : WEEK_LABELS , datasets } ,
265283 options : {
@@ -292,7 +310,7 @@ function buildDayOfMonthByAuthorChart(dayOfMonthData) {
292310
293311 if ( authors . size === 0 ) {
294312 const totals = DAY_LABELS . map ( d => dayOfMonthData [ d ] ?? 0 ) ;
295- new Chart ( ctx , {
313+ renderChart ( "chartMonth" , {
296314 type : "bar" ,
297315 data : { labels : DAY_LABELS , datasets : [ { label : "Total" , data : totals , stack : "commits" , borderWidth : 1 } ] } ,
298316 options : {
@@ -320,7 +338,7 @@ function buildDayOfMonthByAuthorChart(dayOfMonthData) {
320338 borderWidth : 1
321339 } ) ) ;
322340
323- new Chart ( ctx , {
341+ renderChart ( "chartMonth" , {
324342 type : "bar" ,
325343 data : { labels : DAY_LABELS , datasets } ,
326344 options : {
@@ -348,7 +366,7 @@ function buildLinesChart(linesItems) {
348366
349367 const ctx = document . getElementById ( "chartLines" ) . getContext ( "2d" ) ;
350368
351- new Chart ( ctx , {
369+ renderChart ( "chartLines" , {
352370 type : "line" ,
353371 data : {
354372 labels,
@@ -401,7 +419,7 @@ function buildCommitTypeChart(commitTypeData) {
401419 stack : "commitTypes"
402420 } ) ) ;
403421
404- new Chart ( ctx , {
422+ renderChart ( "typesCommits" , {
405423 type : "bar" ,
406424 data : {
407425 labels : dates ,
@@ -448,20 +466,19 @@ function buildAuthorsTable(authorsData) {
448466}
449467
450468// start
451- document . addEventListener ( ' DOMContentLoaded' , ( ) => {
452- const menu = document . getElementById ( ' rangeMenu' ) ;
453- const btn = document . getElementById ( ' rangeDropdownBtn' ) ;
469+ document . addEventListener ( " DOMContentLoaded" , ( ) => {
470+ const menu = document . getElementById ( " rangeMenu" ) ;
471+ const btn = document . getElementById ( " rangeDropdownBtn" ) ;
454472
455- const DEFAULT_RANGE = '30' ;
456- loadAndRender ( DEFAULT_RANGE ) ;
473+ loadAndRender ( "months" , 1 ) ;
457474
458- menu . addEventListener ( ' click' , ( e ) => {
459- const item = e . target . closest ( ' .dropdown-item' ) ;
475+ menu . addEventListener ( " click" , ( e ) => {
476+ const item = e . target . closest ( " .dropdown-item" ) ;
460477 if ( ! item ) return ;
461478 e . preventDefault ( ) ;
462-
463- const range = item . dataset . range ;
464- btn . textContent = item . textContent ;
465- loadAndRender ( range ) ;
479+ const type = item . dataset . type ;
480+ const value = item . dataset . value ;
481+ btn . textContent = item . textContent . trim ( ) ;
482+ loadAndRender ( type , value ) ;
466483 } ) ;
467484} ) ;
0 commit comments