1
1
'use server' ;
2
- import { StatsChartData } from '@/types/Stats' ;
2
+ import { StatsChartData , StatsSteps } from '@/types/Stats' ;
3
3
import { prisma } from '@/utils/prisma' ;
4
+ import { getRange } from '@/utils/stats/get-range' ;
4
5
import { revalidateTag } from 'next/cache' ;
5
6
6
7
/**
7
8
* Method to get all the question data for the user and return it
8
9
* in a format to display in a chart.
9
10
*
10
11
* We return the data in the following format:
11
- * [month ]: { totalQuestions: number, tags: string[], tagCounts: Record<string, number> }
12
+ * [key ]: { totalQuestions: number, tags: string[], tagCounts: Record<string, number> }
12
13
*/
13
14
export const getStatsChartData = async ( opts : {
14
15
userUid : string ;
15
16
to : string ;
16
- from : string ;
17
+ from : StatsSteps ;
18
+ step : 'month' | 'week' | 'day' ;
17
19
} ) => {
18
- const { userUid, to, from } = opts ;
20
+ const { userUid, to, from, step } = opts ;
19
21
20
22
if ( ! userUid ) {
21
23
return null ;
22
24
}
23
25
26
+ const toDate = new Date ( to ) ;
27
+ const fromDate = getRange ( from ) ;
28
+
24
29
const questions = await prisma . answers . findMany ( {
25
30
where : {
26
31
userUid,
27
32
createdAt : {
28
- gte : new Date ( from ) ,
29
- lte : new Date ( to )
33
+ gte : fromDate ,
34
+ lte : toDate
30
35
}
31
36
} ,
32
37
include : {
33
38
question : {
34
39
select : {
35
- createdAt : true ,
36
40
tags : {
37
41
include : {
38
42
tag : true
@@ -46,20 +50,43 @@ export const getStatsChartData = async (opts: {
46
50
const data : StatsChartData = { } ;
47
51
48
52
questions . forEach ( ( answer ) => {
49
- const month = answer . question . createdAt . toISOString ( ) . slice ( 0 , 7 ) ;
53
+ let key : string ;
54
+ switch ( step ) {
55
+ case 'month' :
56
+ key = answer . createdAt . toISOString ( ) . slice ( 0 , 7 ) ;
57
+ break ;
58
+ case 'week' :
59
+ const weekStart = new Date ( answer . createdAt ) ;
60
+ weekStart . setDate (
61
+ answer . createdAt . getDate ( ) - answer . createdAt . getDay ( )
62
+ ) ;
63
+ key = weekStart . toISOString ( ) . slice ( 0 , 10 ) ;
64
+ break ;
65
+ case 'day' :
66
+ key = formatDayLabel ( answer . createdAt ) ;
67
+ break ;
68
+ }
69
+
50
70
const tags = answer . question . tags . map ( ( tag ) => tag . tag . name ) ;
51
71
52
- if ( data [ month ] ) {
53
- data [ month ] . totalQuestions ++ ;
72
+ if ( data [ key ] ) {
73
+ data [ key ] . totalQuestions ++ ;
54
74
tags . forEach ( ( tag ) => {
55
- data [ month ] . tagCounts [ tag ] = ( data [ month ] . tagCounts [ tag ] || 0 ) + 1 ;
75
+ data [ key ] . tagCounts [ tag ] = ( data [ key ] . tagCounts [ tag ] || 0 ) + 1 ;
76
+ if ( ! data [ key ] . tags . includes ( tag ) ) {
77
+ data [ key ] . tags . push ( tag ) ;
78
+ }
56
79
} ) ;
57
80
} else {
58
81
const tagCounts : Record < string , number > = { } ;
59
82
tags . forEach ( ( tag ) => {
60
83
tagCounts [ tag ] = 1 ;
61
84
} ) ;
62
- data [ month ] = { totalQuestions : 1 , tagCounts } ;
85
+ data [ key ] = {
86
+ totalQuestions : 1 ,
87
+ tagCounts,
88
+ tags : [ ...tags ]
89
+ } ;
63
90
}
64
91
} ) ;
65
92
@@ -69,19 +96,27 @@ export const getStatsChartData = async (opts: {
69
96
} ;
70
97
71
98
/**
72
- * Gets the total number of questions the user has answered.
73
- *
74
- * @param userUid
75
- * @returns
99
+ * Gets the total number of questions the user has answered within a specific range.
76
100
*/
77
- export const getTotalQuestionCount = async ( userUid : string ) => {
101
+ export const getTotalQuestionCount = async (
102
+ userUid : string ,
103
+ to : string ,
104
+ from : StatsSteps
105
+ ) => {
78
106
if ( ! userUid ) {
79
107
return null ;
80
108
}
81
109
110
+ const toDate = new Date ( to ) ;
111
+ const fromDate = getRange ( from ) ;
112
+
82
113
const questions = await prisma . answers . count ( {
83
114
where : {
84
- userUid
115
+ userUid,
116
+ createdAt : {
117
+ gte : fromDate ,
118
+ lte : toDate
119
+ }
85
120
}
86
121
} ) ;
87
122
@@ -90,34 +125,66 @@ export const getTotalQuestionCount = async (userUid: string) => {
90
125
return questions ;
91
126
} ;
92
127
93
- export const getTotalTimeTaken = async ( userUid : string ) => {
128
+ /**
129
+ * Gets the total time taken for questions answered within a specific range.
130
+ */
131
+ export const getTotalTimeTaken = async (
132
+ userUid : string ,
133
+ to : string ,
134
+ from : StatsSteps
135
+ ) => {
94
136
if ( ! userUid ) {
95
137
return null ;
96
138
}
97
139
140
+ const toDate = new Date ( to ) ;
141
+ const fromDate = getRange ( from ) ;
142
+
98
143
const answers = await prisma . answers . findMany ( {
99
144
where : {
100
- userUid
145
+ userUid,
146
+ createdAt : {
147
+ gte : fromDate ,
148
+ lte : toDate
149
+ }
150
+ } ,
151
+ select : {
152
+ timeTaken : true
101
153
}
102
154
} ) ;
103
155
104
- const totalTime = answers . reduce ( ( acc , answer ) => {
105
- return acc + ( answer . timeTaken || 0 ) ;
106
- } , 0 ) ;
156
+ const totalTime = answers . reduce (
157
+ ( acc , answer ) => acc + ( answer . timeTaken || 0 ) ,
158
+ 0
159
+ ) ;
107
160
108
161
revalidateTag ( 'statistics' ) ;
109
162
110
163
return totalTime ;
111
164
} ;
112
165
113
- export const getHighestScoringTag = async ( userUid : string ) => {
166
+ /**
167
+ * Gets the highest scoring tag within a specific range.
168
+ */
169
+ export const getHighestScoringTag = async (
170
+ userUid : string ,
171
+ to : string ,
172
+ from : StatsSteps
173
+ ) => {
114
174
if ( ! userUid ) {
115
175
return null ;
116
176
}
117
177
178
+ const toDate = new Date ( to ) ;
179
+ const fromDate = getRange ( from ) ;
180
+
118
181
const answers = await prisma . answers . findMany ( {
119
182
where : {
120
- userUid
183
+ userUid,
184
+ createdAt : {
185
+ gte : fromDate ,
186
+ lte : toDate
187
+ }
121
188
} ,
122
189
include : {
123
190
question : {
@@ -141,9 +208,10 @@ export const getHighestScoringTag = async (userUid: string) => {
141
208
} ) ;
142
209
} ) ;
143
210
144
- const highestScoringTag = Object . keys ( tagCounts ) . reduce ( ( acc , tag ) => {
145
- return tagCounts [ tag ] > tagCounts [ acc ] ? tag : acc ;
146
- } , '' ) ;
211
+ const highestScoringTag = Object . keys ( tagCounts ) . reduce (
212
+ ( a , b ) => ( tagCounts [ a ] > tagCounts [ b ] ? a : b ) ,
213
+ ''
214
+ ) ;
147
215
148
216
revalidateTag ( 'statistics' ) ;
149
217
@@ -152,3 +220,47 @@ export const getHighestScoringTag = async (userUid: string) => {
152
220
count : tagCounts [ highestScoringTag ]
153
221
} ;
154
222
} ;
223
+
224
+ export const getData = async ( opts : {
225
+ userUid : string ;
226
+ to : string ;
227
+ from : StatsSteps ;
228
+ step : 'month' | 'week' | 'day' ;
229
+ } ) => {
230
+ const { userUid, to, from } = opts ;
231
+
232
+ const [ stats , totalQuestions , totalTimeTaken , highestScoringTag ] =
233
+ await Promise . all ( [
234
+ getStatsChartData ( opts ) ,
235
+ getTotalQuestionCount ( userUid , to , from ) ,
236
+ getTotalTimeTaken ( userUid , to , from ) ,
237
+ getHighestScoringTag ( userUid , to , from )
238
+ ] ) ;
239
+
240
+ return { stats, totalQuestions, totalTimeTaken, highestScoringTag } ;
241
+ } ;
242
+
243
+ // Helper function to format day label
244
+ const formatDayLabel = ( date : Date ) => {
245
+ const days = [ 'Sun' , 'Mon' , 'Tue' , 'Wed' , 'Thu' , 'Fri' , 'Sat' ] ;
246
+ const months = [
247
+ 'Jan' ,
248
+ 'Feb' ,
249
+ 'Mar' ,
250
+ 'Apr' ,
251
+ 'May' ,
252
+ 'Jun' ,
253
+ 'Jul' ,
254
+ 'Aug' ,
255
+ 'Sep' ,
256
+ 'Oct' ,
257
+ 'Nov' ,
258
+ 'Dec'
259
+ ] ;
260
+
261
+ const dayOfWeek = days [ date . getDay ( ) ] ;
262
+ const month = months [ date . getMonth ( ) ] ;
263
+ const dayOfMonth = date . getDate ( ) ;
264
+
265
+ return `${ dayOfWeek } , ${ month } ${ dayOfMonth } ` ;
266
+ } ;
0 commit comments