4
4
* See License-AGPL.txt in the project root for license information.
5
5
*/
6
6
7
- import { useEffect , useState } from "react" ;
7
+ import { forwardRef , useEffect , useState } from "react" ;
8
8
import { getGitpodService , gitpodHostUrl } from "../service/service" ;
9
9
import {
10
10
ListUsageRequest ,
@@ -25,6 +25,11 @@ import { toRemoteURL } from "../projects/render-utils";
25
25
import { WorkspaceType } from "@gitpod/gitpod-protocol" ;
26
26
import PillLabel from "./PillLabel" ;
27
27
import { SupportedWorkspaceClass } from "@gitpod/gitpod-protocol/lib/workspace-class" ;
28
+ import DatePicker from "react-datepicker" ;
29
+ import "react-datepicker/dist/react-datepicker.css" ;
30
+ import "./react-datepicker.css" ;
31
+ import { useLocation } from "react-router" ;
32
+ import dayjs from "dayjs" ;
28
33
29
34
interface UsageViewProps {
30
35
attributionId : AttributionId ;
@@ -33,25 +38,29 @@ interface UsageViewProps {
33
38
function UsageView ( { attributionId } : UsageViewProps ) {
34
39
const [ usagePage , setUsagePage ] = useState < ListUsageResponse | undefined > ( undefined ) ;
35
40
const [ errorMessage , setErrorMessage ] = useState ( "" ) ;
36
- const today = new Date ( ) ;
37
- const startOfCurrentMonth = new Date ( today . getFullYear ( ) , today . getMonth ( ) , 1 ) ;
38
- const timestampStartOfCurrentMonth = startOfCurrentMonth . getTime ( ) ;
39
- const [ startDateOfBillMonth , setStartDateOfBillMonth ] = useState ( timestampStartOfCurrentMonth ) ;
40
- const [ endDateOfBillMonth , setEndDateOfBillMonth ] = useState ( Date . now ( ) ) ;
41
+ const startOfCurrentMonth = dayjs ( ) . startOf ( "month" ) ;
42
+ const [ startDate , setStartDate ] = useState ( startOfCurrentMonth ) ;
43
+ const [ endDate , setEndDate ] = useState ( dayjs ( ) ) ;
41
44
const [ totalCreditsUsed , setTotalCreditsUsed ] = useState < number > ( 0 ) ;
42
45
const [ isLoading , setIsLoading ] = useState < boolean > ( true ) ;
43
46
const [ supportedClasses , setSupportedClasses ] = useState < SupportedWorkspaceClass [ ] > ( [ ] ) ;
44
47
48
+ const location = useLocation ( ) ;
45
49
useEffect ( ( ) => {
50
+ const match = / # ( \d { 4 } - \d { 2 } - \d { 2 } ) : ( \d { 4 } - \d { 2 } - \d { 2 } ) / . exec ( location . hash ) ;
51
+ if ( match ) {
52
+ try {
53
+ setStartDate ( dayjs ( match [ 1 ] , "YYYY-MM-DD" ) ) ;
54
+ setEndDate ( dayjs ( match [ 2 ] , "YYYY-MM-DD" ) ) ;
55
+ } catch ( e ) {
56
+ console . error ( e ) ;
57
+ }
58
+ }
46
59
( async ( ) => {
47
60
const classes = await getGitpodService ( ) . server . getSupportedWorkspaceClasses ( ) ;
48
61
setSupportedClasses ( classes ) ;
49
62
} ) ( ) ;
50
- } , [ ] ) ;
51
-
52
- useEffect ( ( ) => {
53
- loadPage ( 1 ) ;
54
- } , [ startDateOfBillMonth , endDateOfBillMonth ] ) ;
63
+ } , [ location ] ) ;
55
64
56
65
const loadPage = async ( page : number = 1 ) => {
57
66
if ( usagePage === undefined ) {
@@ -60,8 +69,8 @@ function UsageView({ attributionId }: UsageViewProps) {
60
69
}
61
70
const request : ListUsageRequest = {
62
71
attributionId : AttributionId . render ( attributionId ) ,
63
- from : startDateOfBillMonth ,
64
- to : endDateOfBillMonth ,
72
+ from : startDate . startOf ( "day" ) . valueOf ( ) ,
73
+ to : endDate . endOf ( "day" ) . valueOf ( ) ,
65
74
order : Ordering . ORDERING_DESCENDING ,
66
75
pagination : {
67
76
perPage : 50 ,
@@ -82,6 +91,15 @@ function UsageView({ attributionId }: UsageViewProps) {
82
91
setIsLoading ( false ) ;
83
92
}
84
93
} ;
94
+ useEffect ( ( ) => {
95
+ if ( startDate . isAfter ( endDate ) ) {
96
+ setErrorMessage ( "The start date needs to be before the end date." ) ;
97
+ return ;
98
+ } else {
99
+ setErrorMessage ( "" ) ;
100
+ }
101
+ loadPage ( 1 ) ;
102
+ } , [ startDate , endDate ] ) ;
85
103
86
104
const getType = ( type : WorkspaceType ) => {
87
105
if ( type === "regular" ) {
@@ -118,27 +136,24 @@ function UsageView({ attributionId }: UsageViewProps) {
118
136
return inMinutes + " min" ;
119
137
} ;
120
138
121
- const handleMonthClick = ( start : any , end : any ) => {
122
- setStartDateOfBillMonth ( start ) ;
123
- setEndDateOfBillMonth ( end ) ;
139
+ const handleMonthClick = ( start : dayjs . Dayjs , end : dayjs . Dayjs ) => {
140
+ setStartDate ( start ) ;
141
+ setEndDate ( end ) ;
124
142
} ;
125
143
126
144
const getBillingHistory = ( ) => {
127
145
let rows = [ ] ;
128
146
// This goes back 6 months from the current month
129
147
for ( let i = 1 ; i < 7 ; i ++ ) {
130
- const endDateVar = i - 1 ;
131
- const startDate = new Date ( today . getFullYear ( ) , today . getMonth ( ) - i ) ;
132
- const endDate = new Date ( today . getFullYear ( ) , today . getMonth ( ) - endDateVar ) ;
133
- const timeStampOfStartDate = startDate . getTime ( ) ;
134
- const timeStampOfEndDate = endDate . getTime ( ) ;
148
+ const startDate = dayjs ( ) . subtract ( i , "month" ) . startOf ( "month" ) ;
149
+ const endDate = startDate . endOf ( "month" ) ;
135
150
rows . push (
136
151
< div
137
152
key = { `billing${ i } ` }
138
153
className = "text-sm text-blue-500 hover:text-blue-600 dark:text-blue-400 dark:hover:text-blue-500 truncate cursor-pointer gp-link"
139
- onClick = { ( ) => handleMonthClick ( timeStampOfStartDate , timeStampOfEndDate ) }
154
+ onClick = { ( ) => handleMonthClick ( startDate , endDate ) }
140
155
>
141
- { startDate . toLocaleString ( "default" , { month : "long" } ) } { startDate . getFullYear ( ) }
156
+ { startDate . format ( "MMMM YYYY" ) }
142
157
</ div > ,
143
158
) ;
144
159
}
@@ -160,13 +175,67 @@ function UsageView({ attributionId }: UsageViewProps) {
160
175
161
176
const headerTitle = attributionId . kind === "team" ? "Team Usage" : "Personal Usage" ;
162
177
178
+ const DateDisplay = forwardRef ( ( arg : any , ref : any ) => (
179
+ < div
180
+ className = "px-2 p-1 text-gray-500 bg-gray-100 dark:text-gray-400 dark:bg-gray-800 rounded-md cursor-pointer flex items-center"
181
+ onClick = { arg . onClick }
182
+ ref = { ref }
183
+ >
184
+ < div className = "font-medium" > { arg . value } </ div >
185
+ < div >
186
+ < svg
187
+ width = "20"
188
+ height = "20"
189
+ fill = "currentColor"
190
+ xmlns = "http://www.w3.org/2000/svg"
191
+ onClick = { arg . onClick }
192
+ ref = { ref }
193
+ >
194
+ < path
195
+ fillRule = "evenodd"
196
+ clipRule = "evenodd"
197
+ d = "M5.293 7.293a1 1 0 0 1 1.414 0L10 10.586l3.293-3.293a1 1 0 1 1 1.414 1.414l-4 4a1 1 0 0 1-1.414 0l-4-4a1 1 0 0 1 0-1.414Z"
198
+ />
199
+ < title > Change Date</ title >
200
+ </ svg >
201
+ </ div >
202
+ </ div >
203
+ ) ) ;
204
+
163
205
return (
164
206
< >
165
207
< Header
166
- title = { headerTitle }
167
- subtitle = { `${ new Date ( startDateOfBillMonth ) . toLocaleDateString ( ) } - ${ new Date (
168
- endDateOfBillMonth ,
169
- ) . toLocaleDateString ( ) } (updated every 15 minutes).`}
208
+ title = {
209
+ < div className = "flex items-baseline" >
210
+ < h1 className = "tracking-tight" > { headerTitle } </ h1 >
211
+ </ div >
212
+ }
213
+ subtitle = {
214
+ < div className = "tracking-wide flex mt-3 items-center" >
215
+ < h2 className = "mr-2" > Showing usage from </ h2 >
216
+ < DatePicker
217
+ selected = { startDate . toDate ( ) }
218
+ onChange = { ( date ) => date && setStartDate ( dayjs ( date ) ) }
219
+ selectsStart
220
+ startDate = { startDate . toDate ( ) }
221
+ endDate = { endDate . toDate ( ) }
222
+ maxDate = { endDate . toDate ( ) }
223
+ customInput = { < DateDisplay /> }
224
+ dateFormat = { "MMM d, yyyy" }
225
+ />
226
+ < h2 className = "mx-2" > to</ h2 >
227
+ < DatePicker
228
+ selected = { endDate . toDate ( ) }
229
+ onChange = { ( date ) => date && setEndDate ( dayjs ( date ) ) }
230
+ selectsEnd
231
+ startDate = { startDate . toDate ( ) }
232
+ endDate = { endDate . toDate ( ) }
233
+ minDate = { startDate . toDate ( ) }
234
+ customInput = { < DateDisplay /> }
235
+ dateFormat = { "MMM d, yyyy" }
236
+ />
237
+ </ div >
238
+ }
170
239
/>
171
240
< div className = "app-container pt-5" >
172
241
{ errorMessage && < p className = "text-base" > { errorMessage } </ p > }
@@ -178,18 +247,17 @@ function UsageView({ attributionId }: UsageViewProps) {
178
247
< div className = "text-base text-gray-500 truncate" > Current Month</ div >
179
248
< div
180
249
className = "text-sm text-blue-500 hover:text-blue-600 dark:text-blue-400 dark:hover:text-blue-500 truncate cursor-pointer mb-5"
181
- onClick = { ( ) => handleMonthClick ( timestampStartOfCurrentMonth , Date . now ( ) ) }
250
+ onClick = { ( ) => handleMonthClick ( startOfCurrentMonth , dayjs ( ) ) }
182
251
>
183
- { startOfCurrentMonth . toLocaleString ( "default" , { month : "long" } ) } { " " }
184
- { startOfCurrentMonth . getFullYear ( ) }
252
+ { dayjs ( startOfCurrentMonth ) . format ( "MMMM YYYY" ) }
185
253
</ div >
186
254
< div className = "text-base text-gray-500 truncate" > Previous Months</ div >
187
255
{ getBillingHistory ( ) }
188
256
</ div >
189
257
{ ! isLoading && (
190
258
< div >
191
259
< div className = "flex flex-col truncate" >
192
- < div className = "text-base text-gray-500" > Total usage </ div >
260
+ < div className = "text-base text-gray-500" > Total Usage </ div >
193
261
< div className = "flex text-lg text-gray-600 font-semibold" >
194
262
< CreditsSvg className = "my-auto mr-1" />
195
263
< span > { totalCreditsUsed . toLocaleString ( ) } Credits</ span >
@@ -235,11 +303,7 @@ function UsageView({ attributionId }: UsageViewProps) {
235
303
{ " " }
236
304
workspaces
237
305
</ a > { " " }
238
- in{ " " }
239
- { new Date ( startDateOfBillMonth ) . toLocaleString ( "default" , {
240
- month : "long" ,
241
- } ) } { " " }
242
- { new Date ( startDateOfBillMonth ) . getFullYear ( ) } or checked your other teams?
306
+ in { startDate . format ( "MMMM YYYY" ) } or checked your other teams?
243
307
</ p >
244
308
</ div >
245
309
) }
0 commit comments