feat: add date range download dialog
This commit is contained in:
parent
74d592d892
commit
57c0b7cca1
14 changed files with 696 additions and 62 deletions
66
new-lamassu-admin/package-lock.json
generated
66
new-lamassu-admin/package-lock.json
generated
|
|
@ -6508,8 +6508,7 @@
|
|||
"ansi-regex": {
|
||||
"version": "2.1.1",
|
||||
"bundled": true,
|
||||
"dev": true,
|
||||
"optional": true
|
||||
"dev": true
|
||||
},
|
||||
"aproba": {
|
||||
"version": "1.2.0",
|
||||
|
|
@ -6530,14 +6529,12 @@
|
|||
"balanced-match": {
|
||||
"version": "1.0.0",
|
||||
"bundled": true,
|
||||
"dev": true,
|
||||
"optional": true
|
||||
"dev": true
|
||||
},
|
||||
"brace-expansion": {
|
||||
"version": "1.1.11",
|
||||
"bundled": true,
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"requires": {
|
||||
"balanced-match": "^1.0.0",
|
||||
"concat-map": "0.0.1"
|
||||
|
|
@ -6552,20 +6549,17 @@
|
|||
"code-point-at": {
|
||||
"version": "1.1.0",
|
||||
"bundled": true,
|
||||
"dev": true,
|
||||
"optional": true
|
||||
"dev": true
|
||||
},
|
||||
"concat-map": {
|
||||
"version": "0.0.1",
|
||||
"bundled": true,
|
||||
"dev": true,
|
||||
"optional": true
|
||||
"dev": true
|
||||
},
|
||||
"console-control-strings": {
|
||||
"version": "1.1.0",
|
||||
"bundled": true,
|
||||
"dev": true,
|
||||
"optional": true
|
||||
"dev": true
|
||||
},
|
||||
"core-util-is": {
|
||||
"version": "1.0.2",
|
||||
|
|
@ -6682,8 +6676,7 @@
|
|||
"inherits": {
|
||||
"version": "2.0.3",
|
||||
"bundled": true,
|
||||
"dev": true,
|
||||
"optional": true
|
||||
"dev": true
|
||||
},
|
||||
"ini": {
|
||||
"version": "1.3.5",
|
||||
|
|
@ -6695,7 +6688,6 @@
|
|||
"version": "1.0.0",
|
||||
"bundled": true,
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"requires": {
|
||||
"number-is-nan": "^1.0.0"
|
||||
}
|
||||
|
|
@ -6710,7 +6702,6 @@
|
|||
"version": "3.0.4",
|
||||
"bundled": true,
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"requires": {
|
||||
"brace-expansion": "^1.1.7"
|
||||
}
|
||||
|
|
@ -6718,14 +6709,12 @@
|
|||
"minimist": {
|
||||
"version": "0.0.8",
|
||||
"bundled": true,
|
||||
"dev": true,
|
||||
"optional": true
|
||||
"dev": true
|
||||
},
|
||||
"minipass": {
|
||||
"version": "2.3.5",
|
||||
"bundled": true,
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"requires": {
|
||||
"safe-buffer": "^5.1.2",
|
||||
"yallist": "^3.0.0"
|
||||
|
|
@ -6744,7 +6733,6 @@
|
|||
"version": "0.5.1",
|
||||
"bundled": true,
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"requires": {
|
||||
"minimist": "0.0.8"
|
||||
}
|
||||
|
|
@ -6825,8 +6813,7 @@
|
|||
"number-is-nan": {
|
||||
"version": "1.0.1",
|
||||
"bundled": true,
|
||||
"dev": true,
|
||||
"optional": true
|
||||
"dev": true
|
||||
},
|
||||
"object-assign": {
|
||||
"version": "4.1.1",
|
||||
|
|
@ -6838,7 +6825,6 @@
|
|||
"version": "1.4.0",
|
||||
"bundled": true,
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"requires": {
|
||||
"wrappy": "1"
|
||||
}
|
||||
|
|
@ -6924,8 +6910,7 @@
|
|||
"safe-buffer": {
|
||||
"version": "5.1.2",
|
||||
"bundled": true,
|
||||
"dev": true,
|
||||
"optional": true
|
||||
"dev": true
|
||||
},
|
||||
"safer-buffer": {
|
||||
"version": "2.1.2",
|
||||
|
|
@ -6961,7 +6946,6 @@
|
|||
"version": "1.0.2",
|
||||
"bundled": true,
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"requires": {
|
||||
"code-point-at": "^1.0.0",
|
||||
"is-fullwidth-code-point": "^1.0.0",
|
||||
|
|
@ -6981,7 +6965,6 @@
|
|||
"version": "3.0.1",
|
||||
"bundled": true,
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"requires": {
|
||||
"ansi-regex": "^2.0.0"
|
||||
}
|
||||
|
|
@ -7025,14 +7008,12 @@
|
|||
"wrappy": {
|
||||
"version": "1.0.2",
|
||||
"bundled": true,
|
||||
"dev": true,
|
||||
"optional": true
|
||||
"dev": true
|
||||
},
|
||||
"yallist": {
|
||||
"version": "3.0.3",
|
||||
"bundled": true,
|
||||
"dev": true,
|
||||
"optional": true
|
||||
"dev": true
|
||||
}
|
||||
}
|
||||
},
|
||||
|
|
@ -8064,7 +8045,6 @@
|
|||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/d/-/d-1.0.1.tgz",
|
||||
"integrity": "sha512-m62ShEObQ39CfralilEQRjH6oAMtNCV1xJyEx5LpRYUVN+EviphDgUc/F3hnYbADmkiNs67Y+3ylmlG7Lnu+FA==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"es5-ext": "^0.10.50",
|
||||
"type": "^1.0.1"
|
||||
|
|
@ -8880,7 +8860,6 @@
|
|||
"version": "0.10.51",
|
||||
"resolved": "https://registry.npmjs.org/es5-ext/-/es5-ext-0.10.51.tgz",
|
||||
"integrity": "sha512-oRpWzM2WcLHVKpnrcyB7OW8j/s67Ba04JCm0WnNv3RiABSvs7mrQlutB8DBv793gKcp0XENR8Il8WxGTlZ73gQ==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"es6-iterator": "~2.0.3",
|
||||
"es6-symbol": "~3.1.1",
|
||||
|
|
@ -8897,7 +8876,6 @@
|
|||
"version": "2.0.3",
|
||||
"resolved": "https://registry.npmjs.org/es6-iterator/-/es6-iterator-2.0.3.tgz",
|
||||
"integrity": "sha1-p96IkUGgWpSwhUQDstCg+/qY87c=",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"d": "1",
|
||||
"es5-ext": "^0.10.35",
|
||||
|
|
@ -8914,7 +8892,6 @@
|
|||
"version": "3.1.2",
|
||||
"resolved": "https://registry.npmjs.org/es6-symbol/-/es6-symbol-3.1.2.tgz",
|
||||
"integrity": "sha512-/ZypxQsArlv+KHpGvng52/Iz8by3EQPxhmbuz8yFG89N/caTFBSbcXONDw0aMjy827gQg26XAjP4uXFvnfINmQ==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"d": "^1.0.1",
|
||||
"es5-ext": "^0.10.51"
|
||||
|
|
@ -12702,14 +12679,12 @@
|
|||
"balanced-match": {
|
||||
"version": "1.0.0",
|
||||
"bundled": true,
|
||||
"dev": true,
|
||||
"optional": true
|
||||
"dev": true
|
||||
},
|
||||
"brace-expansion": {
|
||||
"version": "1.1.11",
|
||||
"bundled": true,
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"requires": {
|
||||
"balanced-match": "^1.0.0",
|
||||
"concat-map": "0.0.1"
|
||||
|
|
@ -12729,8 +12704,7 @@
|
|||
"concat-map": {
|
||||
"version": "0.0.1",
|
||||
"bundled": true,
|
||||
"dev": true,
|
||||
"optional": true
|
||||
"dev": true
|
||||
},
|
||||
"console-control-strings": {
|
||||
"version": "1.1.0",
|
||||
|
|
@ -14733,6 +14707,14 @@
|
|||
"resolved": "https://registry.npmjs.org/moment/-/moment-2.24.0.tgz",
|
||||
"integrity": "sha512-bV7f+6l2QigeBBZSM/6yTNq4P2fNpSWj/0e7jQcy87A8e7o2nAfP/34/2ky5Vw4B9S446EtIhodAzkFCcR4dQg=="
|
||||
},
|
||||
"moment-range": {
|
||||
"version": "4.0.2",
|
||||
"resolved": "https://registry.npmjs.org/moment-range/-/moment-range-4.0.2.tgz",
|
||||
"integrity": "sha512-n8sceWwSTjmz++nFHzeNEUsYtDqjgXgcOBzsHi+BoXQU2FW+eU92LUaK8gqOiSu5PG57Q9sYj1Fz4LRDj4FtKA==",
|
||||
"requires": {
|
||||
"es6-symbol": "^3.1.0"
|
||||
}
|
||||
},
|
||||
"move-concurrently": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/move-concurrently/-/move-concurrently-1.0.1.tgz",
|
||||
|
|
@ -14842,8 +14824,7 @@
|
|||
"next-tick": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/next-tick/-/next-tick-1.0.0.tgz",
|
||||
"integrity": "sha1-yobR/ogoFpsBICCOPchCS524NCw=",
|
||||
"dev": true
|
||||
"integrity": "sha1-yobR/ogoFpsBICCOPchCS524NCw="
|
||||
},
|
||||
"nice-try": {
|
||||
"version": "1.0.5",
|
||||
|
|
@ -21979,8 +21960,7 @@
|
|||
"type": {
|
||||
"version": "1.2.0",
|
||||
"resolved": "https://registry.npmjs.org/type/-/type-1.2.0.tgz",
|
||||
"integrity": "sha512-+5nt5AAniqsCnu2cEQQdpzCAh33kVx8n0VoFidKpB1dVVLAN/F+bgVOqOJqOnEnrhp222clB5p3vUlD+1QAnfg==",
|
||||
"dev": true
|
||||
"integrity": "sha512-+5nt5AAniqsCnu2cEQQdpzCAh33kVx8n0VoFidKpB1dVVLAN/F+bgVOqOJqOnEnrhp222clB5p3vUlD+1QAnfg=="
|
||||
},
|
||||
"type-check": {
|
||||
"version": "0.3.2",
|
||||
|
|
|
|||
|
|
@ -15,6 +15,7 @@
|
|||
"jss-plugin-extend": "^10.0.0",
|
||||
"lodash": "4.17.15",
|
||||
"moment": "2.24.0",
|
||||
"moment-range": "^4.0.2",
|
||||
"qrcode.react": "0.9.3",
|
||||
"react": "^16.10.2",
|
||||
"react-dom": "^16.10.2",
|
||||
|
|
|
|||
44
new-lamassu-admin/src/components/Popover.js
Normal file
44
new-lamassu-admin/src/components/Popover.js
Normal file
|
|
@ -0,0 +1,44 @@
|
|||
import React from 'react'
|
||||
import { makeStyles, Popover as MaterialPopover } from '@material-ui/core'
|
||||
|
||||
const styles = {
|
||||
arrow: {
|
||||
width: 0,
|
||||
height: 0,
|
||||
position: 'absolute',
|
||||
borderStyle: 'solid',
|
||||
margin: 5,
|
||||
borderWidth: [[0, 15, 18, 15]],
|
||||
borderLeftColor: 'transparent',
|
||||
borderRightColor: 'transparent',
|
||||
borderTopColor: 'transparent',
|
||||
top: -18,
|
||||
left: 138,
|
||||
marginTop: 0,
|
||||
marginBottom: 0,
|
||||
borderColor: '#ffffff'
|
||||
},
|
||||
paper: {
|
||||
overflow: 'visible'
|
||||
}
|
||||
}
|
||||
|
||||
const useStyles = makeStyles(styles)
|
||||
|
||||
const Popover = ({ children, ...props }) => {
|
||||
const classes = useStyles()
|
||||
|
||||
return (
|
||||
<MaterialPopover
|
||||
classes={{
|
||||
paper: classes.paper
|
||||
}}
|
||||
{...props}
|
||||
>
|
||||
{children}
|
||||
<div class={classes.arrow} />
|
||||
</MaterialPopover>
|
||||
)
|
||||
}
|
||||
|
||||
export default Popover
|
||||
144
new-lamassu-admin/src/components/date-range-picker/Calendar.js
Normal file
144
new-lamassu-admin/src/components/date-range-picker/Calendar.js
Normal file
|
|
@ -0,0 +1,144 @@
|
|||
import React, { useState } from 'react'
|
||||
import moment from 'moment'
|
||||
import { toInteger } from 'lodash/fp'
|
||||
import { makeStyles } from '@material-ui/core/styles'
|
||||
import Tile from './Tile'
|
||||
import { ReactComponent as Arrow } from '../../styling/icons/arrow/month_change.svg'
|
||||
import { primaryColor, zircon, fontSecondary } from '../../styling/variables'
|
||||
import typographyStyles from '../typography/styles'
|
||||
const { label2 } = typographyStyles
|
||||
|
||||
const styles = {
|
||||
navbar: {
|
||||
display: 'flex',
|
||||
justifyContent: 'space-between',
|
||||
alignItems: 'center',
|
||||
padding: [[15, 15]],
|
||||
color: primaryColor,
|
||||
'& button': {
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
padding: 0,
|
||||
border: 'none',
|
||||
backgroundColor: zircon,
|
||||
cursor: 'pointer',
|
||||
borderRadius: '50%',
|
||||
width: 20,
|
||||
height: 20,
|
||||
position: 'relative',
|
||||
overflow: 'hidden',
|
||||
'& svg': {
|
||||
position: 'absolute',
|
||||
left: 0
|
||||
}
|
||||
},
|
||||
fontFamily: fontSecondary,
|
||||
fontSize: 16,
|
||||
fontWeight: 500
|
||||
},
|
||||
table: {
|
||||
borderCollapse: 'collapse',
|
||||
color: primaryColor,
|
||||
'& tr': {
|
||||
'&:first-child': {
|
||||
paddingLeft: 5
|
||||
},
|
||||
'&:last-child': {
|
||||
paddingRight: 5
|
||||
}
|
||||
},
|
||||
'& th, & td': {
|
||||
margin: 0,
|
||||
padding: [[3, 0, 3, 0]]
|
||||
},
|
||||
'& th': {
|
||||
extend: label2
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const useStyles = makeStyles(styles)
|
||||
|
||||
const Calendar = ({ minDate, maxDate, handleSelect, ...props }) => {
|
||||
const [currentDisplayedMonth, setCurrentDisplayedMonth] = useState(moment())
|
||||
|
||||
const classes = useStyles()
|
||||
|
||||
const weekdays = moment.weekdaysMin().map(day => day.slice(0, 1))
|
||||
const firstDayOfMonth = (month) => toInteger(moment(month).startOf('month').format('d'))
|
||||
const monthLength = (month) => toInteger(moment(month).endOf('month').format('D'))
|
||||
|
||||
const monthdays = (month) => {
|
||||
const days = []
|
||||
|
||||
const lastMonth = moment(month).subtract(1, 'month')
|
||||
for (let i = firstDayOfMonth(month) - 1; i >= 0; i--) {
|
||||
days.push(moment(lastMonth).endOf('month').subtract(i, 'days'))
|
||||
}
|
||||
for (let j = 0; j < monthLength(month); j++) {
|
||||
days.push(moment(month).startOf('month').add(j, 'days'))
|
||||
}
|
||||
const nextMonth = moment(month).add(1, 'month')
|
||||
for (let k = 0; days.length < 42; k++) {
|
||||
days.push(moment(nextMonth).startOf('month').add(k, 'days'))
|
||||
}
|
||||
|
||||
return days
|
||||
}
|
||||
|
||||
const getRow = (month, row) => monthdays(month).slice(row * 7 - 7, row * 7)
|
||||
|
||||
const handleNavPrev = (currentMonth) => {
|
||||
const prevMonth = moment(currentMonth).subtract(1, 'month')
|
||||
if (!minDate) setCurrentDisplayedMonth(prevMonth)
|
||||
else setCurrentDisplayedMonth(prevMonth.isSameOrAfter(minDate, 'month') ? prevMonth : currentDisplayedMonth)
|
||||
}
|
||||
const handleNavNext = (currentMonth) => {
|
||||
const nextMonth = moment(currentMonth).add(1, 'month')
|
||||
if (!maxDate) setCurrentDisplayedMonth(nextMonth)
|
||||
else setCurrentDisplayedMonth(nextMonth.isSameOrBefore(maxDate, 'month') ? nextMonth : currentDisplayedMonth)
|
||||
}
|
||||
|
||||
return (
|
||||
<div className={classes.wrapper}>
|
||||
<div className={classes.navbar}>
|
||||
<button onClick={() => handleNavPrev(currentDisplayedMonth)}>
|
||||
<Arrow />
|
||||
</button>
|
||||
<span>{`${currentDisplayedMonth.format('MMMM')} ${currentDisplayedMonth.format('YYYY')}`}</span>
|
||||
<button onClick={() => handleNavNext(currentDisplayedMonth)}>
|
||||
<Arrow transform='rotate(180)' />
|
||||
</button>
|
||||
</div>
|
||||
<table className={classes.table}>
|
||||
<thead>
|
||||
<tr>
|
||||
{weekdays.map((day, key) => (
|
||||
<th key={key}>{day}</th>
|
||||
))}
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{[1, 2, 3, 4, 5, 6, 7].map((row, key) => (
|
||||
<tr key={key}>
|
||||
{getRow(currentDisplayedMonth, row).map((day, key) => (
|
||||
<td key={key} onClick={() => handleSelect(day, minDate, maxDate)}>
|
||||
<Tile
|
||||
isDisabled={(maxDate && day.isAfter(maxDate, 'day')) || (minDate && day.isBefore(minDate, 'day'))}
|
||||
isLowerBound={day.isSame(props.from, 'day')}
|
||||
isUpperBound={day.isSame(props.to, 'day')}
|
||||
isBetween={day.isBetween(props.from, props.to, 'day', [])}
|
||||
>
|
||||
{day.format('D')}
|
||||
</Tile>
|
||||
</td>
|
||||
))}
|
||||
</tr>
|
||||
))}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default Calendar
|
||||
|
|
@ -0,0 +1,131 @@
|
|||
import React, { useState, useEffect } from 'react'
|
||||
import classnames from 'classnames'
|
||||
import { makeStyles } from '@material-ui/core/styles'
|
||||
|
||||
import Calendar from './Calendar'
|
||||
import { ReactComponent as Arrow } from '../../styling/icons/arrow/download_logs.svg'
|
||||
|
||||
import { primaryColor, offColor, zircon } from '../../styling/variables'
|
||||
|
||||
import typographyStyles from '../typography/styles'
|
||||
const { info1, label, label3 } = typographyStyles
|
||||
|
||||
const dateContainerStyles = {
|
||||
wrapper: {
|
||||
minWidth: 118
|
||||
},
|
||||
container: {
|
||||
display: 'flex'
|
||||
},
|
||||
monthWeekDayContainer: {
|
||||
display: 'flex',
|
||||
flexDirection: 'column'
|
||||
},
|
||||
label: {
|
||||
extend: label,
|
||||
color: primaryColor
|
||||
},
|
||||
bigNumber: {
|
||||
extend: info1,
|
||||
marginRight: 7
|
||||
},
|
||||
monthYear: {
|
||||
extend: label3,
|
||||
color: primaryColor
|
||||
},
|
||||
weekDay: {
|
||||
extend: label,
|
||||
lineHeight: 1,
|
||||
color: offColor
|
||||
}
|
||||
}
|
||||
|
||||
const dateContainerUseStyles = makeStyles(dateContainerStyles)
|
||||
|
||||
const DateContainer = ({ date, children, ...props }) => {
|
||||
const classes = dateContainerUseStyles()
|
||||
|
||||
return (
|
||||
<div className={classes.wrapper}>
|
||||
<div className={classes.label}>{children}</div>
|
||||
{date &&
|
||||
<>
|
||||
<div className={classes.container}>
|
||||
<div className={classes.bigNumber}>{date.format('D')}</div>
|
||||
<div className={classes.monthWeekDayContainer}>
|
||||
<span className={classes.monthYear}>{`${date.format('MMM')} ${date.format('YYYY')}`}</span>
|
||||
<span className={classes.weekDay}>{date.format('dddd')}</span>
|
||||
</div>
|
||||
</div>
|
||||
</>}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
const styles = {
|
||||
wrapper: {
|
||||
backgroundColor: 'white',
|
||||
borderRadius: 10
|
||||
},
|
||||
dateThingyContainer: {
|
||||
height: 80,
|
||||
display: 'flex',
|
||||
justifyContent: 'space-between',
|
||||
backgroundColor: zircon,
|
||||
padding: [[5, 15, 0, 15]]
|
||||
},
|
||||
arrowContainer: {
|
||||
width: 39,
|
||||
display: 'flex',
|
||||
alignSelf: 'center',
|
||||
alignItems: 'center'
|
||||
},
|
||||
arrow: {
|
||||
margin: 'auto'
|
||||
}
|
||||
}
|
||||
|
||||
const useStyles = makeStyles(styles)
|
||||
|
||||
const DateRangePicker = ({ minDate, maxDate, className, handleChange, ...props }) => {
|
||||
const [from, setFrom] = useState(null)
|
||||
const [to, setTo] = useState(null)
|
||||
|
||||
useEffect(() => {
|
||||
handleChange(from, to)
|
||||
}, [to])
|
||||
|
||||
const classes = useStyles()
|
||||
|
||||
const handleSelect = (day, minDate, maxDate) => {
|
||||
if ((maxDate && day.isAfter(maxDate, 'day')) || (minDate && day.isBefore(minDate, 'day'))) return
|
||||
if (from && !to) {
|
||||
if (day.isBefore(from, 'day')) {
|
||||
setTo(from)
|
||||
setFrom(day)
|
||||
} else {
|
||||
setTo(day)
|
||||
}
|
||||
} else {
|
||||
setFrom(day)
|
||||
setTo(null)
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className={classnames(classes.wrapper, className)}>
|
||||
<div className={classes.dateThingyContainer}>
|
||||
<DateContainer date={from}>From</DateContainer>
|
||||
<div className={classes.arrowContainer}>
|
||||
<Arrow className={classes.arrow} />
|
||||
</div>
|
||||
<DateContainer date={to}>To</DateContainer>
|
||||
</div>
|
||||
<Calendar from={from} to={to} minDate={minDate} maxDate={maxDate} handleSelect={handleSelect} />
|
||||
</div>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
export default DateRangePicker
|
||||
94
new-lamassu-admin/src/components/date-range-picker/Tile.js
Normal file
94
new-lamassu-admin/src/components/date-range-picker/Tile.js
Normal file
|
|
@ -0,0 +1,94 @@
|
|||
import React from 'react'
|
||||
import classnames from 'classnames'
|
||||
import { makeStyles } from '@material-ui/core/styles'
|
||||
import { primaryColor, spring2, spring3, fontSecondary, disabledColor } from '../../styling/variables'
|
||||
|
||||
const styles = {
|
||||
wrapper: {
|
||||
width: 45,
|
||||
height: 26,
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
position: 'relative'
|
||||
},
|
||||
button: {
|
||||
border: 'none',
|
||||
width: '100%',
|
||||
height: '100%',
|
||||
cursor: 'pointer',
|
||||
padding: 0,
|
||||
backgroundColor: 'transparent',
|
||||
color: primaryColor,
|
||||
zIndex: 2,
|
||||
fontFamily: fontSecondary,
|
||||
fontSize: 14,
|
||||
fontWeight: 500
|
||||
},
|
||||
lowerBound: {
|
||||
width: [['50%', '!important']],
|
||||
left: '50%'
|
||||
},
|
||||
upperBound: {
|
||||
width: [['50%', '!important']],
|
||||
right: '50%'
|
||||
},
|
||||
selected: {
|
||||
width: 26,
|
||||
height: 26,
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
backgroundColor: spring2,
|
||||
borderRadius: '50%',
|
||||
position: 'absolute',
|
||||
zIndex: 1
|
||||
},
|
||||
between: {
|
||||
position: 'absolute',
|
||||
width: '100%',
|
||||
height: '100%',
|
||||
zIndex: 0,
|
||||
backgroundColor: spring3
|
||||
},
|
||||
disabled: {
|
||||
color: disabledColor,
|
||||
cursor: 'default'
|
||||
}
|
||||
}
|
||||
|
||||
const useStyles = makeStyles(styles)
|
||||
|
||||
const Tile = ({ isLowerBound, isUpperBound, isBetween, isDisabled, children, ...props }) => {
|
||||
const classes = useStyles()
|
||||
const selected = isLowerBound || isUpperBound
|
||||
|
||||
const rangeClasses = {
|
||||
[classes.between]: isBetween && !(isLowerBound && isUpperBound),
|
||||
[classes.lowerBound]: isLowerBound && !isUpperBound,
|
||||
[classes.upperBound]: isUpperBound && !isLowerBound
|
||||
}
|
||||
|
||||
const buttonWrapperClasses = {
|
||||
[classes.wrapper]: true,
|
||||
[classes.selected]: selected
|
||||
}
|
||||
|
||||
const buttonClasses = {
|
||||
[classes.button]: true,
|
||||
[classes.disabled]: isDisabled
|
||||
}
|
||||
|
||||
return (
|
||||
<div className={classes.wrapper}>
|
||||
<div className={classnames(rangeClasses)} />
|
||||
<div className={classnames(buttonWrapperClasses)}>
|
||||
<button className={classnames(buttonClasses)}>
|
||||
{children}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default Tile
|
||||
32
new-lamassu-admin/src/components/inputs/base/RadioGroup.js
Normal file
32
new-lamassu-admin/src/components/inputs/base/RadioGroup.js
Normal file
|
|
@ -0,0 +1,32 @@
|
|||
import React from 'react'
|
||||
import classnames from 'classnames'
|
||||
import { withStyles } from '@material-ui/styles'
|
||||
import FormControlLabel from '@material-ui/core/FormControlLabel'
|
||||
import { Radio as MaterialRadio, RadioGroup as MaterialRadioGroup } from '@material-ui/core'
|
||||
import { secondaryColor } from '../../../styling/variables'
|
||||
|
||||
const GreenRadio = withStyles({
|
||||
root: {
|
||||
color: secondaryColor,
|
||||
'&$checked': {
|
||||
color: secondaryColor
|
||||
}
|
||||
},
|
||||
checked: {}
|
||||
})(props => <MaterialRadio color='default' {...props} />)
|
||||
|
||||
const RadioGroup = ({ name, value, labels, ariaLabel, onChange, className, ...props }) => {
|
||||
return (
|
||||
<>
|
||||
{labels && (
|
||||
<MaterialRadioGroup aria-label={ariaLabel} name={name} value={value} onChange={onChange} className={classnames(className)}>
|
||||
{labels.map((label, idx) => (
|
||||
<FormControlLabel key={idx} value={idx} control={<GreenRadio />} label={label} />
|
||||
))}
|
||||
</MaterialRadioGroup>
|
||||
)}
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
export default RadioGroup
|
||||
|
|
@ -5,5 +5,6 @@ import Radio from './base/Radio'
|
|||
import TextInput from './base/TextInput'
|
||||
import Switch from './base/Switch'
|
||||
import Select from './base/Select'
|
||||
import RadioGroup from './base/RadioGroup'
|
||||
|
||||
export { Autocomplete, AutocompleteMultiple, TextInput, Radio, Checkbox, Switch, Select }
|
||||
export { Autocomplete, AutocompleteMultiple, TextInput, Radio, Checkbox, Switch, Select, RadioGroup }
|
||||
|
|
|
|||
|
|
@ -119,6 +119,11 @@ export default {
|
|||
fontWeight: 500,
|
||||
color: fontColor
|
||||
},
|
||||
label3: {
|
||||
fontSize: fontSize5,
|
||||
fontFamily: fontSecondary,
|
||||
fontWeight: 700
|
||||
},
|
||||
select: {
|
||||
fontSize: fontSize3,
|
||||
fontFamily: fontSecondary,
|
||||
|
|
|
|||
|
|
@ -1,26 +1,28 @@
|
|||
import React, { useState } from 'react'
|
||||
import FileSaver from 'file-saver'
|
||||
import { concat, uniq } from 'lodash/fp'
|
||||
import classnames from 'classnames'
|
||||
import { concat, uniq, toInteger } from 'lodash/fp'
|
||||
import moment from 'moment'
|
||||
import useAxios from '@use-hooks/axios'
|
||||
import { makeStyles } from '@material-ui/core'
|
||||
|
||||
import Title from '../components/Title'
|
||||
import { Info3 } from '../components/typography'
|
||||
import { FeatureButton, SimpleButton } from '../components/buttons'
|
||||
import { FeatureButton, SimpleButton, Link } from '../components/buttons'
|
||||
import { Table, TableHead, TableRow, TableHeader, TableBody, TableCell } from '../components/table'
|
||||
import { Select } from '../components/inputs'
|
||||
import { Select, RadioGroup } from '../components/inputs'
|
||||
import Uptime from '../components/Uptime'
|
||||
import DateRangePicker from '../components/date-range-picker/DateRangePicker'
|
||||
import Popover from '../components/Popover'
|
||||
import { ReactComponent as Download } from '../styling/icons/button/download/zodiac.svg'
|
||||
import { ReactComponent as DownloadActive } from '../styling/icons/button/download/white.svg'
|
||||
|
||||
import { makeStyles } from '@material-ui/core'
|
||||
import typographyStyles from '../components/typography/styles'
|
||||
|
||||
import { comet } from '../styling/variables'
|
||||
import { primaryColor, comet } from '../styling/variables'
|
||||
import styles from './Logs.styles'
|
||||
import logPageHeaderStyles from './LogPageHeader.styles'
|
||||
|
||||
const { regularLabel } = typographyStyles
|
||||
import typographyStyles from '../components/typography/styles'
|
||||
const { regularLabel, h4 } = typographyStyles
|
||||
const { tableWrapper } = styles
|
||||
const { titleAndButtonsContainer, buttonsWrapper } = logPageHeaderStyles
|
||||
|
||||
|
|
@ -55,6 +57,40 @@ styles.uptimeContainer = {
|
|||
styles.titleAndButtonsContainer = titleAndButtonsContainer
|
||||
styles.buttonsWrapper = buttonsWrapper
|
||||
|
||||
styles.popoverContent = {
|
||||
minWidth: 315
|
||||
}
|
||||
|
||||
styles.popoverHeader = {
|
||||
extend: h4,
|
||||
padding: [[20, 15, 0, 15]]
|
||||
}
|
||||
|
||||
styles.radioButtonsContainer = {
|
||||
padding: [[10, 15, 10, 15]]
|
||||
}
|
||||
|
||||
styles.radioButtons = {
|
||||
display: 'flex',
|
||||
justifyContent: 'space-between',
|
||||
flexDirection: 'row',
|
||||
color: primaryColor
|
||||
}
|
||||
|
||||
styles.dateRangePickerShowing = {
|
||||
display: 'block',
|
||||
height: '100%'
|
||||
}
|
||||
|
||||
styles.dateRangePickerHidden = {
|
||||
display: 'none',
|
||||
height: 0
|
||||
}
|
||||
|
||||
styles.download = {
|
||||
padding: [[30, 15, 30, 15]]
|
||||
}
|
||||
|
||||
const useStyles = makeStyles(styles)
|
||||
|
||||
const SHOW_ALL = 'Show all'
|
||||
|
|
@ -68,6 +104,9 @@ const Logs = () => {
|
|||
const [logLevel, setLogLevel] = useState(SHOW_ALL)
|
||||
const [version, setVersion] = useState(null)
|
||||
const [processStates, setProcessStates] = useState(null)
|
||||
const [radioButtons, setRadioButtons] = useState(0)
|
||||
const [range, setRange] = useState(null)
|
||||
const [anchorEl, setAnchorEl] = useState(null)
|
||||
|
||||
const classes = useStyles()
|
||||
|
||||
|
|
@ -116,12 +155,34 @@ const Logs = () => {
|
|||
}
|
||||
})
|
||||
|
||||
const handleLogLevelChange = (item) => setLogLevel(item)
|
||||
|
||||
const formatDateFile = date => {
|
||||
return moment(date).format('YYYY-MM-DD_HH-mm')
|
||||
}
|
||||
|
||||
const dateRangePickerClasses = {
|
||||
[classes.dateRangePickerShowing]: radioButtons === 1,
|
||||
[classes.dateRangePickerHidden]: radioButtons === 0
|
||||
}
|
||||
|
||||
const handleOpenRangePicker = (event) => {
|
||||
setAnchorEl(event.currentTarget)
|
||||
}
|
||||
|
||||
const handleCloseRangePicker = () => {
|
||||
setAnchorEl(null)
|
||||
}
|
||||
|
||||
const handleRadioButtons = (event) => {
|
||||
setRadioButtons(toInteger(event.target.value))
|
||||
}
|
||||
|
||||
const handleRangeChange = (from, to) => {
|
||||
setRange({ from, to })
|
||||
}
|
||||
|
||||
const open = Boolean(anchorEl)
|
||||
const id = open ? 'date-range-popover' : undefined
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className={classes.titleWrapper}>
|
||||
|
|
@ -132,14 +193,67 @@ const Logs = () => {
|
|||
<FeatureButton
|
||||
Icon={Download}
|
||||
InverseIcon={DownloadActive}
|
||||
onClick={() => {
|
||||
const text = logsResponse.data.logs.map(it => JSON.stringify(it)).join('\n')
|
||||
const blob = new window.Blob([text], {
|
||||
type: 'text/plain;charset=utf-8'
|
||||
})
|
||||
FileSaver.saveAs(blob, `${formatDateFile(new Date())}_server`)
|
||||
}}
|
||||
aria-describedby={id}
|
||||
variant='contained'
|
||||
onClick={handleOpenRangePicker}
|
||||
/>
|
||||
<Popover
|
||||
id={id}
|
||||
open={open}
|
||||
anchorEl={anchorEl}
|
||||
onClose={handleCloseRangePicker}
|
||||
anchorOrigin={{
|
||||
vertical: 'bottom',
|
||||
horizontal: 'center'
|
||||
}}
|
||||
transformOrigin={{
|
||||
vertical: 'top',
|
||||
horizontal: 'center'
|
||||
}}
|
||||
>
|
||||
<div className={classes.popoverContent}>
|
||||
<div className={classes.popoverHeader}>
|
||||
Download logs
|
||||
</div>
|
||||
<div className={classes.radioButtonsContainer}>
|
||||
<RadioGroup
|
||||
name='logs-select'
|
||||
value={radioButtons}
|
||||
labels={['All logs', 'Date range']}
|
||||
ariaLabel='logs-select'
|
||||
onChange={handleRadioButtons}
|
||||
className={classes.radioButtons}
|
||||
/>
|
||||
</div>
|
||||
<DateRangePicker
|
||||
maxDate={moment()}
|
||||
handleChange={handleRangeChange}
|
||||
className={classnames(dateRangePickerClasses)}
|
||||
/>
|
||||
<div className={classes.download}>
|
||||
<Link
|
||||
color='primary'
|
||||
onClick={() => {
|
||||
if (radioButtons === 0) {
|
||||
const text = logsResponse.data.logs.map(it => JSON.stringify(it)).join('\n')
|
||||
const blob = new window.Blob([text], {
|
||||
type: 'text/plain;charset=utf-8'
|
||||
})
|
||||
FileSaver.saveAs(blob, `${formatDateFile(new Date())}_server`)
|
||||
} else if (radioButtons === 1 && range.from && range.to) {
|
||||
const text = logsResponse.data.logs.filter((log) => moment(log.timestamp).isBetween(range.from, range.to, 'day', '[]')).map(it => JSON.stringify(it)).join('\n')
|
||||
const blob = new window.Blob([text], {
|
||||
type: 'text/plain;charset=utf-8'
|
||||
})
|
||||
FileSaver.saveAs(blob, `${formatDateFile(range.from)}_${formatDateFile(range.to)}_server`)
|
||||
}
|
||||
}}
|
||||
>
|
||||
Download
|
||||
</Link>
|
||||
</div>
|
||||
</div>
|
||||
</Popover>
|
||||
<SimpleButton className={classes.button} disabled={loading} onClick={sendSnapshot}>
|
||||
Share with Lamassu
|
||||
</SimpleButton>
|
||||
|
|
@ -156,7 +270,7 @@ const Logs = () => {
|
|||
<div className={classes.headerLine2}>
|
||||
{logsResponse && (
|
||||
<Select
|
||||
onSelectedItemChange={handleLogLevelChange}
|
||||
onSelectedItemChange={setLogLevel}
|
||||
label='Level'
|
||||
items={concat([SHOW_ALL], uniq(logsResponse.data.logs.map(log => log.logLevel)))}
|
||||
default={SHOW_ALL}
|
||||
|
|
|
|||
14
new-lamassu-admin/src/styling/icons/arrow/download_logs.svg
Normal file
14
new-lamassu-admin/src/styling/icons/arrow/download_logs.svg
Normal file
|
|
@ -0,0 +1,14 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg width="23px" height="17px" viewBox="0 0 23 17" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||
<!-- Generator: Sketch 59.1 (86144) - https://sketch.com -->
|
||||
<title>arrow download logs</title>
|
||||
<desc>Created with Sketch.</desc>
|
||||
<g id="Symbols" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd" stroke-linecap="round" stroke-linejoin="round">
|
||||
<g id="pop-up/action/download-logs/date-range-copy-3" transform="translate(-117.000000, -116.000000)" stroke="#1B2559" stroke-width="2">
|
||||
<g id="arrow-download-logs" transform="translate(128.500000, 124.500000) rotate(-90.000000) translate(-128.500000, -124.500000) translate(121.000000, 114.000000)">
|
||||
<polyline id="Path-3" points="0 13.3571429 7.14285714 20.5 14.2857143 13.3571429"></polyline>
|
||||
<line x1="7.14285714" y1="0.142857143" x2="7.14285714" y2="20.1428571" id="Path-4"></line>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1 KiB |
20
new-lamassu-admin/src/styling/icons/arrow/month_change.svg
Normal file
20
new-lamassu-admin/src/styling/icons/arrow/month_change.svg
Normal file
|
|
@ -0,0 +1,20 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg width="20px" height="20px" viewBox="0 0 20 20" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||
<!-- Generator: Sketch 59.1 (86144) - https://sketch.com -->
|
||||
<title>icon/sf-contain-b copy 5</title>
|
||||
<desc>Created with Sketch.</desc>
|
||||
<g id="Symbols" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
|
||||
<g id="pop-up/action/download-logs/date-range-copy-2" transform="translate(-20.000000, -187.000000)">
|
||||
<g id="icon/sf-contain-b-copy-5" transform="translate(30.000000, 197.000000) rotate(-270.000000) translate(-30.000000, -197.000000) translate(20.000000, 187.000000)">
|
||||
<g id="icon/sf-small/wizzard" stroke-linecap="round" stroke-linejoin="round">
|
||||
<g transform="translate(6.666667, 6.000000)" id="Group">
|
||||
<g>
|
||||
<polyline id="Path-3" stroke="#1B2559" stroke-width="2" points="0 4.83333333 3.33333333 8.16666667 6.66666667 4.83333333"></polyline>
|
||||
<line x1="3.33333333" y1="0.25" x2="3.33333333" y2="6.5" id="Path-4" stroke="#1B2559" stroke-width="2"></line>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.3 KiB |
27
new-lamassu-admin/src/styling/icons/month arrows/left.svg
Normal file
27
new-lamassu-admin/src/styling/icons/month arrows/left.svg
Normal file
|
|
@ -0,0 +1,27 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg width="20px" height="20px" viewBox="0 0 20 20" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||
<!-- Generator: Sketch 59.1 (86144) - https://sketch.com -->
|
||||
<title>icon/sf-contain-b copy 5</title>
|
||||
<desc>Created with Sketch.</desc>
|
||||
<defs>
|
||||
<circle id="path-1" cx="10" cy="10" r="10"></circle>
|
||||
</defs>
|
||||
<g id="Symbols" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
|
||||
<g id="pop-up/action/download-logs/date-range-copy-2" transform="translate(-20.000000, -187.000000)">
|
||||
<g id="icon/sf-contain-b-copy-5" transform="translate(30.000000, 197.000000) rotate(-270.000000) translate(-30.000000, -197.000000) translate(20.000000, 187.000000)">
|
||||
<mask id="mask-2" fill="white">
|
||||
<use xlink:href="#path-1"></use>
|
||||
</mask>
|
||||
<use id="Mask" fill="#EBEFFF" fill-rule="nonzero" xlink:href="#path-1"></use>
|
||||
<g id="icon/sf-small/wizzard" mask="url(#mask-2)" stroke-linecap="round" stroke-linejoin="round">
|
||||
<g transform="translate(6.666667, 6.000000)" id="Group">
|
||||
<g>
|
||||
<polyline id="Path-3" stroke="#1B2559" stroke-width="2" points="0 4.83333333 3.33333333 8.16666667 6.66666667 4.83333333"></polyline>
|
||||
<line x1="3.33333333" y1="0.25" x2="3.33333333" y2="6.5" id="Path-4" stroke="#1B2559" stroke-width="2"></line>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.6 KiB |
27
new-lamassu-admin/src/styling/icons/month arrows/right.svg
Normal file
27
new-lamassu-admin/src/styling/icons/month arrows/right.svg
Normal file
|
|
@ -0,0 +1,27 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg width="20px" height="20px" viewBox="0 0 20 20" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||
<!-- Generator: Sketch 59.1 (86144) - https://sketch.com -->
|
||||
<title>icon/sf-contain-b copy 4</title>
|
||||
<desc>Created with Sketch.</desc>
|
||||
<defs>
|
||||
<circle id="path-1" cx="10" cy="10" r="10"></circle>
|
||||
</defs>
|
||||
<g id="Symbols" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
|
||||
<g id="pop-up/action/download-logs/date-range-copy-2" transform="translate(-232.000000, -187.000000)">
|
||||
<g id="icon/sf-contain-b-copy-4" transform="translate(242.000000, 197.000000) scale(-1, 1) rotate(-270.000000) translate(-242.000000, -197.000000) translate(232.000000, 187.000000)">
|
||||
<mask id="mask-2" fill="white">
|
||||
<use xlink:href="#path-1"></use>
|
||||
</mask>
|
||||
<use id="Mask" fill="#EBEFFF" fill-rule="nonzero" xlink:href="#path-1"></use>
|
||||
<g id="icon/sf-small/wizzard" mask="url(#mask-2)" stroke-linecap="round" stroke-linejoin="round">
|
||||
<g transform="translate(6.666667, 6.000000)" id="Group">
|
||||
<g>
|
||||
<polyline id="Path-3" stroke="#1B2559" stroke-width="2" points="0 4.83333333 3.33333333 8.16666667 6.66666667 4.83333333"></polyline>
|
||||
<line x1="3.33333333" y1="0.25" x2="3.33333333" y2="6.5" id="Path-4" stroke="#1B2559" stroke-width="2"></line>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.6 KiB |
Loading…
Add table
Add a link
Reference in a new issue