@@ -3,41 +3,161 @@ import { prettifyNumber, toggleModalVisibility } from "/static/js/common/common.
33import { showErrorAlert , showInfoAlert } from "/static/js/common/alerts.js" ;
44
55/**
6- * Function to render the statistics chart for a job
7- * @param {string } id - The ID of the job to render stats for
6+ * Shows statistics for a specific job in a modal
7+ * @param {string } id - The ID of the job to display stats for
8+ */
9+ export const showStats = async ( id ) => {
10+ // Get loading spinner reference
11+ const spinnerStats = document . getElementById ( `spinner-stats-${ id } ` ) ;
12+
13+ // Fetch job statistics from the API
14+ const response = await fetch ( `/dashboard/employer/jobs/${ id } /stats` , {
15+ method : "GET" ,
16+ } ) ;
17+ if ( ! response . ok ) {
18+ if ( spinnerStats ) {
19+ spinnerStats . classList . add ( "hidden" ) ;
20+ }
21+ showErrorAlert ( "Something went wrong fetching the stats, please try again later." ) ;
22+ return ;
23+ }
24+ const data = await response . json ( ) ;
25+ if ( spinnerStats ) {
26+ spinnerStats . classList . add ( "hidden" ) ;
27+ }
28+
29+ // Process and display the statistics data
30+ if ( data ) {
31+ const hasViewsData = data . views_daily && data . views_daily . length > 0 ;
32+ const hasSearchAppearancesData =
33+ data . search_appearances_daily && data . search_appearances_daily . length > 0 ;
34+
35+ if ( hasViewsData || hasSearchAppearancesData ) {
36+ // Open the statistics modal if we have data for at least one chart
37+ toggleModalVisibility ( `stats-modal` , "open" ) ;
38+
39+ // Render views chart if data exists
40+ if ( hasViewsData ) {
41+ renderChart ( data . views_daily , "job-chart-views" , "views" ) ;
42+ if ( data . views_total_last_month !== undefined ) {
43+ const totalViewsElement = document . getElementById ( "total-views" ) ;
44+ if ( totalViewsElement ) {
45+ totalViewsElement . textContent = prettifyNumber ( data . views_total_last_month ) ;
46+ }
47+ }
48+ } else {
49+ // Hide views chart if no data is available
50+ const viewsChartWrapper = document . querySelector ( '[data-chart="views"]' ) ;
51+ if ( viewsChartWrapper ) {
52+ viewsChartWrapper . classList . add ( "hidden" ) ;
53+ }
54+ }
55+
56+ // Render search appearances chart if data exists
57+ if ( hasSearchAppearancesData ) {
58+ renderChart ( data . search_appearances_daily , "job-chart-search-appearances" , "search_appearances" ) ;
59+ if ( data . search_appearances_total_last_month !== undefined ) {
60+ const totalSearchElement = document . getElementById ( "total-search-appearances" ) ;
61+ if ( totalSearchElement ) {
62+ totalSearchElement . textContent = prettifyNumber ( data . search_appearances_total_last_month ) ;
63+ }
64+ }
65+ } else {
66+ // Hide search appearances chart if no data is available
67+ const searchAppearancesChartWrapper = document . querySelector ( '[data-chart="search-appearances"]' ) ;
68+ if ( searchAppearancesChartWrapper ) {
69+ searchAppearancesChartWrapper . classList . add ( "hidden" ) ;
70+ }
71+ }
72+ } else {
73+ // Show message when no data is available for either chart
74+ showInfoAlert (
75+ 'We don\'t have statistics data for this job yet.<div class="mt-2">Please check again later.</div>' ,
76+ true ,
77+ ) ;
78+ }
79+ }
80+ } ;
81+
82+ /**
83+ * Closes the statistics modal and cleans up resources
84+ */
85+ export const closeStats = ( ) => {
86+ // Dispose of all chart instances to free up memory
87+ const chartIds = [ "job-chart-views" , "job-chart-search-appearances" ] ;
88+ chartIds . forEach ( ( id ) => {
89+ const chartDom = document . getElementById ( id ) ;
90+ if ( chartDom ) {
91+ const chartInstance = echarts . getInstanceByDom ( chartDom ) ;
92+ if ( chartInstance ) {
93+ chartInstance . dispose ( ) ;
94+ }
95+ }
96+ } ) ;
97+
98+ // Close the modal
99+ toggleModalVisibility ( `stats-modal` , "close" ) ;
100+
101+ // Clear the statistics counters
102+ const totalViewsElement = document . getElementById ( `total-views` ) ;
103+ if ( totalViewsElement ) {
104+ totalViewsElement . textContent = "" ;
105+ }
106+ const totalSearchElement = document . getElementById ( `total-search-appearances` ) ;
107+ if ( totalSearchElement ) {
108+ totalSearchElement . textContent = "" ;
109+ }
110+
111+ // Display charts wrapper
112+ const viewsChartWrapper = document . querySelector ( '[data-chart="views"]' ) ;
113+ if ( viewsChartWrapper ) {
114+ viewsChartWrapper . classList . remove ( "hidden" ) ;
115+ }
116+ const searchAppearancesChartWrapper = document . querySelector ( '[data-chart="search-appearances"]' ) ;
117+ if ( searchAppearancesChartWrapper ) {
118+ searchAppearancesChartWrapper . classList . remove ( "hidden" ) ;
119+ }
120+ } ;
121+
122+ /**
123+ * Function to render a chart
124+ * @param {Array } data - The chart data
125+ * @param {string } chartId - The ID of the chart container
126+ * @param {string } chartType - The type of chart ('views' or 'search_appearances')
8127 * @private
9128 */
10- const renderStat = ( data ) => {
129+ const renderChart = ( data , chartId , chartType ) => {
130+ // Calculate date range for the chart (last 30 days)
11131 const today = Date . now ( ) ;
12- // Set the minimum date to one month ago
13132 const min = new Date ( ) ;
14133 const month = min . getMonth ( ) ;
15134 min . setMonth ( min . getMonth ( ) - 1 ) ;
16-
17- // If today is the first day of the month, set it to the last day of
18- // the previous month
135+ // Handle edge case when today is the first day of the month
19136 if ( min . getMonth ( ) == month ) min . setDate ( 0 ) ;
20- // Set the time to the start of the day
21137 min . setHours ( 0 , 0 , 0 , 0 ) ;
22138
23- const chartDom = document . getElementById ( `job-stats` ) ;
139+ // Get the chart container element
140+ const chartDom = document . getElementById ( chartId ) ;
24141 if ( ! chartDom ) return ;
25142
26- const myChart = echarts . init ( chartDom , "gitjobs" , {
143+ // Initialize the ECharts instance
144+ const chart = echarts . init ( chartDom , "gitjobs" , {
27145 renderer : "svg" ,
28146 useDirtyRect : false ,
29147 } ) ;
30- myChart . clear ( ) ;
148+ chart . clear ( ) ;
31149
150+ // Add responsive resize handler
32151 window . addEventListener ( "resize" , function ( ) {
33- myChart . resize ( ) ;
152+ chart . resize ( ) ;
34153 } ) ;
35154
155+ // Configure chart options
36156 const option = {
37157 ...getBarStatsOptions ( ) ,
38158 dataset : [
39159 {
40- dimensions : [ "timestamp" , "jobs " ] ,
160+ dimensions : [ "timestamp" , "count " ] ,
41161 source : data ,
42162 } ,
43163 {
@@ -51,7 +171,8 @@ const renderStat = (data) => {
51171 ...getBarStatsOptions ( ) . tooltip ,
52172 formatter : ( params ) => {
53173 const chartdate = echarts . time . format ( params . data [ 0 ] , "{dd} {MMM}'{yy}" ) ;
54- return `${ chartdate } <br />Views: ${ prettifyNumber ( params . data [ 1 ] ) } ` ;
174+ const label = chartType === "views" ? "Views" : "Search appearances" ;
175+ return `${ chartdate } <br />${ label } : ${ prettifyNumber ( params . data [ 1 ] ) } ` ;
55176 } ,
56177 } ,
57178 xAxis : {
@@ -65,79 +186,14 @@ const renderStat = (data) => {
65186 } ,
66187 } ;
67188
68- option && myChart . setOption ( option ) ;
69- } ;
70-
71- /**
72- * Fetches and renders statistics for a specific job
73- * @param {string } id - The ID of the job to fetch stats for
74- */
75- export const fetchStats = async ( id ) => {
76- const response = await fetch ( `/dashboard/employer/jobs/${ id } /stats` , {
77- method : "GET" ,
78- } ) ;
79- const spinnerStats = document . getElementById ( `spinner-stats-${ id } ` ) ;
80- if ( ! response . ok ) {
81- // Hide the spinner if it exists
82- if ( spinnerStats ) {
83- spinnerStats . classList . add ( "hidden" ) ;
84- }
85-
86- showErrorAlert ( "Something went wrong fetching the stats, please try again later." ) ;
87- return ;
88- }
89- const data = await response . json ( ) ;
90- // Hide the spinner if it exists
91- if ( spinnerStats ) {
92- spinnerStats . classList . add ( "hidden" ) ;
93- }
94- if ( data ) {
95- if ( data . views_daily . length > 0 ) {
96- // Show the stats modal
97- toggleModalVisibility ( `stats-modal` , "open" ) ;
98-
99- // Render the stats chart
100- renderStat ( data . views_daily ) ;
101- if ( data . views_total_last_month !== undefined ) {
102- const totalViewsElement = document . getElementById ( "total-views" ) ;
103- if ( totalViewsElement ) {
104- totalViewsElement . textContent = prettifyNumber ( data . views_total_last_month ) ;
105- }
106- }
107- } else {
108- showInfoAlert (
109- 'We don\'t have views data for this job yet.<div class="mt-2">Please check again later.</div>' ,
110- true ,
111- ) ;
112- }
113- }
114- } ;
115-
116- /**
117- * Closes the stats modal and clears its content
118- */
119- export const closeStatsModal = ( ) => {
120- const chartDom = document . getElementById ( "job-stats" ) ;
121- if ( chartDom ) {
122- const chartInstance = echarts . getInstanceByDom ( chartDom ) ;
123- // Dispose of the chart instance before closing modal
124- if ( chartInstance ) {
125- chartInstance . dispose ( ) ;
126- }
127- }
128- // Close the stats modal
129- toggleModalVisibility ( `stats-modal` , "close" ) ;
130- // Clear the total views element
131- const totalViewsElement = document . getElementById ( `total-views` ) ;
132- if ( totalViewsElement ) {
133- totalViewsElement . textContent = "" ;
134- }
189+ // Render the chart with the configured options
190+ option && chart . setOption ( option ) ;
135191} ;
136192
137193/**
138194 * Registers the GitJobs theme for ECharts
139195 */
140196export const registerEchartsTheme = ( ) => {
141- // Register the GitJobs theme
197+ // Register the custom GitJobs theme for consistent chart styling
142198 echarts . registerTheme ( "gitjobs" , gitjobsChartTheme ) ;
143199} ;
0 commit comments