1
+ import { useEffect , useState } from 'react' ;
2
+ import { Flex , FormControl } from '@chakra-ui/react' ;
3
+ import { Select } from 'chakra-react-select' ;
4
+ import { TicketStats } from '../../server/trpc/router/stats' ;
5
+ import { LineChart , Line , CartesianGrid , Legend , XAxis , YAxis , Tooltip , ResponsiveContainer } from 'recharts' ;
6
+ import { TimeRange } from './StatsView' ;
7
+ import { TicketStatus } from '@prisma/client' ;
8
+ import { computeMean , computeMedian , helpTime , resolveTime } from '../../utils/utils' ;
9
+
10
+ export interface StatsGraphProps {
11
+ timeRange : TimeRange | undefined ;
12
+ stats : TicketStats [ ] ;
13
+ }
14
+
15
+ export enum StatType {
16
+ HELP_TIME = "helpTime" ,
17
+ RESOLVE_TIME = "resolveTime" ,
18
+ NUMBER_OF_TICKETS = "numberOfTickets"
19
+ }
20
+
21
+ const statTypeOptions = [
22
+ {
23
+ label : "Help Time" ,
24
+ value : StatType . HELP_TIME
25
+ } ,
26
+ {
27
+ label : "Resolve Time" ,
28
+ value : StatType . RESOLVE_TIME
29
+ } ,
30
+ {
31
+ label : "Number of Tickets" ,
32
+ value : StatType . NUMBER_OF_TICKETS
33
+ }
34
+ ]
35
+
36
+ const StatsGraph = ( props : StatsGraphProps ) => {
37
+ const { timeRange, stats } = props ;
38
+ const [ statType , setStatType ] = useState ( statTypeOptions [ 0 ] ) ;
39
+ const [ data , setData ] = useState < any [ ] > ( [ ] ) ;
40
+
41
+ const binStatsByTime = ( ) => {
42
+ if ( ! timeRange ) return { } ;
43
+
44
+ const filteredStatsInRange = stats . filter ( s => {
45
+ return s . resolvedAt && timeRange . startTime <= s . resolvedAt && s . resolvedAt < timeRange . endTime ;
46
+ } ) ;
47
+
48
+ const bins : { [ key : string ] : TicketStats [ ] } = { } ;
49
+ for ( let t = timeRange . startTime ; t <= timeRange . endTime ; t = timeRange . type ! . increment ( t ) ) {
50
+ const start = t ;
51
+ const end = timeRange . type ! . increment ( start ) ;
52
+ bins [ timeRange . type ! . formatString ( start ) ] = filteredStatsInRange . filter ( s => {
53
+ return s . resolvedAt && start <= s . resolvedAt && s . resolvedAt < end ;
54
+ } ) ;
55
+ }
56
+ return bins ;
57
+ } ;
58
+
59
+ const getResolveTimeStats = ( bins : { [ key : string ] : TicketStats [ ] } ) => {
60
+ const data = Object . keys ( bins ) . map ( b => ( {
61
+ name : b ,
62
+ data : bins [ b ] ! . filter ( s => s . createdAt && s . resolvedAt ) . map ( t => resolveTime ( t ) ) . sort ( )
63
+ } ) ) ;
64
+
65
+ return data . map ( d => ( {
66
+ name : d . name ,
67
+ meanResolveTime : computeMean ( d . data ) ,
68
+ medianResolveTime : computeMedian ( d . data )
69
+ } ) ) ;
70
+ } ;
71
+
72
+ const getHelpTimeStats = ( bins : { [ key : string ] : TicketStats [ ] } ) => {
73
+ const data = Object . keys ( bins ) . map ( b => ( {
74
+ name : b ,
75
+ data : bins [ b ] ! . filter ( s => s . createdAt && s . resolvedAt ) . map ( t => helpTime ( t ) ) . sort ( ( a , b ) => a - b )
76
+ } ) ) ;
77
+
78
+ return data . map ( d => ( {
79
+ name : d . name ,
80
+ meanHelpTime : computeMean ( d . data ) ,
81
+ medianHelpTime : computeMedian ( d . data )
82
+ } ) ) ;
83
+ } ;
84
+
85
+ const getNumberOfTicketStats = ( bins : { [ key : string ] : TicketStats [ ] } ) => {
86
+ return Object . keys ( bins ) . map ( b => ( {
87
+ name : b ,
88
+ numberOfTickets : bins [ b ] ?. length ,
89
+ numberOfUnresolvedTickets : bins [ b ] ?. filter ( s => s . status === TicketStatus . CLOSED ) . length
90
+ } ) ) ;
91
+ } ;
92
+
93
+ const getGraphLines = ( ) => {
94
+ if ( statType === undefined ) {
95
+ return < > </ > ;
96
+ }
97
+ switch ( statType . value ) {
98
+ case StatType . HELP_TIME :
99
+ return < >
100
+ < Line type = "monotone" dataKey = "meanHelpTime" name = "Mean Help Time" stroke = "#3486eb" />
101
+ < Line type = "monotone" dataKey = "medianHelpTime" name = "Median Help Time" stroke = "#8884d8" />
102
+ </ > ;
103
+ case StatType . RESOLVE_TIME :
104
+ return < >
105
+ < Line type = "monotone" dataKey = "meanResolveTime" name = "Mean Resolve Time" stroke = "#3486eb" />
106
+ < Line type = "monotone" dataKey = "medianResolveTime" name = "Median Resolve Time" stroke = "#8884d8" />
107
+ </ > ;
108
+ case StatType . NUMBER_OF_TICKETS :
109
+ return < >
110
+ < Line type = "monotone" dataKey = "numberOfTickets" name = "Number of Tickets" stroke = "#3486eb" />
111
+ < Line type = "monotone" dataKey = "numberOfUnresolvedTickets" name = "Number of Unresolved Tickets" stroke = "#8884d8" />
112
+ </ > ;
113
+ default :
114
+ return < > </ >
115
+ }
116
+ }
117
+
118
+ useEffect ( ( ) => {
119
+ if ( statType !== undefined && statType . value === StatType . HELP_TIME ) {
120
+ setData ( getHelpTimeStats ( binStatsByTime ( ) ) ) ;
121
+ } else if ( statType !== undefined && statType . value === StatType . RESOLVE_TIME ) {
122
+ setData ( getResolveTimeStats ( binStatsByTime ( ) ) )
123
+ } else if ( statType !== undefined && statType . value === StatType . NUMBER_OF_TICKETS ) {
124
+ setData ( getNumberOfTicketStats ( binStatsByTime ( ) ) ) ;
125
+ }
126
+ } , [ timeRange , statType ] ) ;
127
+
128
+ return (
129
+ < Flex direction = "column" w = "100%" h = "100%" >
130
+ < FormControl w = "100%" mt = { 6 } isRequired >
131
+ < Flex direction = { "row" } justifyContent = { "space-between" } >
132
+ < Select value = { statType } onChange = { val => setStatType ( val ?? undefined ) } options = { statTypeOptions }
133
+ chakraStyles = { {
134
+ container : ( provided ) => ( {
135
+ ...provided ,
136
+ width : "100%" ,
137
+ margin : "0 10px 0 0"
138
+ } )
139
+ } } />
140
+ </ Flex >
141
+ </ FormControl >
142
+ < ResponsiveContainer width = "100%" height = { 400 } >
143
+ < LineChart width = { 400 } height = { 400 } data = { data } margin = { { top : 20 , right : 20 , bottom : 5 , left : 0 } } >
144
+ < CartesianGrid stroke = "#ccc" strokeDasharray = "5 5" />
145
+ < Legend verticalAlign = "top" height = { 36 } />
146
+ < XAxis dataKey = "name" />
147
+ < YAxis />
148
+ < Tooltip labelStyle = { { color : "#454545" } } />
149
+ { getGraphLines ( ) }
150
+ </ LineChart >
151
+ </ ResponsiveContainer >
152
+ </ Flex >
153
+ ) ;
154
+ }
155
+
156
+ export default StatsGraph ;
0 commit comments