From be13ff5f7a6768b3b748cd9a12137451ca4e3c5a Mon Sep 17 00:00:00 2001 From: sylenien Date: Fri, 13 May 2022 13:32:01 +0200 Subject: [PATCH] fix(ui): fixed sessionitem and timezone dropdown connection to mobx --- .../shared/SessionItem/SessionItem.js | 162 ----------------- .../shared/SessionItem/SessionItem.tsx | 163 ++++++++++-------- .../shared/SessionItem/{index.js => index.ts} | 0 .../shared/SessionItem/sessionItem.css | 20 ++- .../components/DefaultTimezone.tsx | 47 +++-- frontend/app/date.ts | 20 ++- 6 files changed, 155 insertions(+), 257 deletions(-) delete mode 100644 frontend/app/components/shared/SessionItem/SessionItem.js rename frontend/app/components/shared/SessionItem/{index.js => index.ts} (100%) diff --git a/frontend/app/components/shared/SessionItem/SessionItem.js b/frontend/app/components/shared/SessionItem/SessionItem.js deleted file mode 100644 index 6222327f3..000000000 --- a/frontend/app/components/shared/SessionItem/SessionItem.js +++ /dev/null @@ -1,162 +0,0 @@ -import { connect } from 'react-redux'; -import cn from 'classnames'; -import { - Link, - Icon, - CountryFlag, - Avatar, - TextEllipsis, - Label, -} from 'UI'; -import { toggleFavorite, setSessionPath } from 'Duck/sessions'; -import { session as sessionRoute, liveSession as liveSessionRoute, withSiteId } from 'App/routes'; -import { durationFormatted, formatTimeOrDate } from 'App/date'; -import stl from './sessionItem.css'; -import Counter from './Counter' -import { withRouter } from 'react-router-dom'; -import SessionMetaList from './SessionMetaList'; -import PlayLink from './PlayLink'; -import ErrorBars from './ErrorBars'; -import { assist as assistRoute, liveSession, sessions as sessionsRoute, isRoute } from "App/routes"; -import { capitalize } from 'App/utils'; - -const ASSIST_ROUTE = assistRoute(); -const ASSIST_LIVE_SESSION = liveSession() -const SESSIONS_ROUTE = sessionsRoute(); - -@connect(state => ({ - timezone: state.getIn(['sessions', 'timezone']), - siteId: state.getIn([ 'site', 'siteId' ]), -}), { toggleFavorite, setSessionPath }) -@withRouter -export default class SessionItem extends React.PureComponent { - // eslint-disable-next-line complexity - render() { - const { - session: { - sessionId, - userBrowser, - userOs, - userId, - userAnonymousId, - userDisplayName, - userCountry, - startedAt, - duration, - eventsCount, - errorsCount, - pagesCount, - viewed, - favorite, - userDeviceType, - userUuid, - userNumericHash, - live, - metadata, - userSessionsCount, - issueTypes, - active, - }, - timezone, - onUserClick = () => null, - hasUserFilter = false, - disableUser = false, - metaList = [], - showActive = false, - lastPlayedSessionId, - } = this.props; - const formattedDuration = durationFormatted(duration); - const hasUserId = userId || userAnonymousId; - const isSessions = isRoute(SESSIONS_ROUTE, this.props.location.pathname); - const isAssist = isRoute(ASSIST_ROUTE, this.props.location.pathname) || isRoute(ASSIST_LIVE_SESSION, this.props.location.pathname); - const isLastPlayed = lastPlayedSessionId === sessionId; - - const _metaList = Object.keys(metadata).filter(i => metaList.includes(i)).map(key => { - const value = metadata[key]; - return { label: key, value }; - }); - - return ( -
-
-
-
-
-
-
(!disableUser && !hasUserFilter) && onUserClick(userId, userAnonymousId)} - > - -
-
-
-
-
{formatTimeOrDate(startedAt, timezone) }
-
- {!isAssist && ( - <> -
- { eventsCount } - { eventsCount === 0 || eventsCount > 1 ? 'Events' : 'Event' } -
-
·
- - )} -
{ live ? : formattedDuration }
-
-
-
- -
- - - -
·
- - - -
·
- - - -
-
- { isSessions && ( -
- -
- )} -
- -
- { isAssist && showActive && ( - - )} -
- { isSessions && ( -
- { isLastPlayed && ( - - )} -
- )} - -
-
-
- { _metaList.length > 0 && ( - - )} -
- ); - } -} \ No newline at end of file diff --git a/frontend/app/components/shared/SessionItem/SessionItem.tsx b/frontend/app/components/shared/SessionItem/SessionItem.tsx index dc51bfc17..f00f58c82 100644 --- a/frontend/app/components/shared/SessionItem/SessionItem.tsx +++ b/frontend/app/components/shared/SessionItem/SessionItem.tsx @@ -1,90 +1,112 @@ -import React from 'react'; -import { connect } from 'react-redux'; +import React from 'react' import cn from 'classnames'; -import { - Link, - Icon, +import { CountryFlag, Avatar, TextEllipsis, Label, } from 'UI'; -import { toggleFavorite, setSessionPath } from 'Duck/sessions'; -import { session as sessionRoute, liveSession as liveSessionRoute, withSiteId } from 'App/routes'; +import { useStore } from 'App/mstore'; +import { observer } from 'mobx-react-lite'; import { durationFormatted, formatTimeOrDate } from 'App/date'; import stl from './sessionItem.css'; import Counter from './Counter' -import { withRouter } from 'react-router-dom'; +import { withRouter, RouteComponentProps } from 'react-router-dom'; import SessionMetaList from './SessionMetaList'; +import PlayLink from './PlayLink'; import ErrorBars from './ErrorBars'; import { assist as assistRoute, liveSession, sessions as sessionsRoute, isRoute } from "App/routes"; import { capitalize } from 'App/utils'; -import { SKIP_TO_ISSUE, TIMEZONE, DURATION_FILTER } from 'App/constants/storageKeys' const ASSIST_ROUTE = assistRoute(); const ASSIST_LIVE_SESSION = liveSession() const SESSIONS_ROUTE = sessionsRoute(); -// @connect(state => ({ -// timezone: state.getIn(['sessions', 'timezone']), -// siteId: state.getIn([ 'site', 'siteId' ]), -// }), { toggleFavorite, setSessionPath }) -// @withRouter -function SessionItem(props) { - // render() { - const { - session: { - sessionId, - userBrowser, - userOs, - userId, - userAnonymousId, - userDisplayName, - userCountry, - startedAt, - duration, - eventsCount, - errorsCount, - pagesCount, - viewed, - favorite, - userDeviceType, - userUuid, - userNumericHash, - live, - metadata, - userSessionsCount, - issueTypes, - active, - }, - timezone, - onUserClick = () => null, - hasUserFilter = false, - disableUser = false, - metaList = [], - showActive = false, - lastPlayedSessionId, - } = props; - const formattedDuration = durationFormatted(duration); - const hasUserId = userId || userAnonymousId; - const isSessions = isRoute(SESSIONS_ROUTE, props.location.pathname); - const isAssist = isRoute(ASSIST_ROUTE, props.location.pathname) || isRoute(ASSIST_LIVE_SESSION, props.location.pathname); - const isLastPlayed = lastPlayedSessionId === sessionId; +interface Props { + session: { + sessionId: string; + userBrowser: string; + userOs: string; + userId: string; + userAnonymousId: string; + userDisplayName: string; + userCountry: string; + startedAt: number; + duration: string; + eventsCount: number; + errorsCount: number; + pagesCount: number; + viewed: boolean; + favorite: boolean; + userDeviceType: string; + userUuid: string; + userNumericHash: number; + live: boolean + metadata: Record; + userSessionsCount: number + issueTypes: []; + active: boolean; + onUserClick: (userId: string, userAnonymousId: string) => null; + hasUserFilter: boolean; + disableUser: boolean; + metaList: Array; + showActive: boolean; + lastPlayedSessionId: string; + }, +} - const _metaList = Object.keys(metadata).filter(i => metaList.includes(i)).map(key => { - const value = metadata[key]; - return { label: key, value }; - }); +function SessionItem(props: RouteComponentProps) { + const { settingsStore } = useStore(); + const { timezone } = settingsStore.sessionSettings; - return ( -
+ const { + session: { + sessionId, + userBrowser, + userOs, + userId, + userAnonymousId, + userDisplayName, + userCountry, + startedAt, + duration, + eventsCount, + viewed, + userDeviceType, + userNumericHash, + live, + metadata, + issueTypes, + active, + }, + onUserClick = () => null, + hasUserFilter = false, + disableUser = false, + metaList = [], + showActive = false, + lastPlayedSessionId, + } = props; + + const formattedDuration = durationFormatted(duration); + const hasUserId = userId || userAnonymousId; + const isSessions = isRoute(SESSIONS_ROUTE, props.location.pathname); + const isAssist = isRoute(ASSIST_ROUTE, props.location.pathname) || isRoute(ASSIST_LIVE_SESSION, props.location.pathname); + const isLastPlayed = lastPlayedSessionId === sessionId; + + const _metaList = Object.keys(metadata).filter(i => metaList.includes(i)).map(key => { + const value = metadata[key]; + return { label: key, value }; + }); + + return ( +
(!disableUser && !hasUserFilter) && onUserClick(userId, userAnonymousId)} > @@ -111,7 +133,7 @@ function SessionItem(props) {
- +
·
@@ -145,9 +167,11 @@ function SessionItem(props) { )}
)} - - - +
@@ -155,10 +179,7 @@ function SessionItem(props) { )}
- ); - } + ) +} -export default connect(state => ({ - timezone: localStorage.getItem(TIMEZONE) || '', - siteId: state.getIn([ 'site', 'siteId' ]), -}), { toggleFavorite, setSessionPath })(withRouter(SessionItem)); \ No newline at end of file +export default withRouter(observer(SessionItem)) diff --git a/frontend/app/components/shared/SessionItem/index.js b/frontend/app/components/shared/SessionItem/index.ts similarity index 100% rename from frontend/app/components/shared/SessionItem/index.js rename to frontend/app/components/shared/SessionItem/index.ts diff --git a/frontend/app/components/shared/SessionItem/sessionItem.css b/frontend/app/components/shared/SessionItem/sessionItem.css index cb7b87c3a..897ee327f 100644 --- a/frontend/app/components/shared/SessionItem/sessionItem.css +++ b/frontend/app/components/shared/SessionItem/sessionItem.css @@ -1,11 +1,9 @@ -@import 'mixins.css'; - .sessionItem { background-color: #fff; - @mixin defaultHover; user-select: none; border-radius: 3px; border: solid thin #EEEEEE; + transition: all 0.4s; & .favorite { opacity: 0; @@ -15,6 +13,10 @@ } &:hover { + background-color: $active-blue; + border: solid thin $active-blue-border; + transition: all 0.2s; + & .playLink { transition: all 0.4s; opacity: 1; @@ -103,7 +105,11 @@ letter-spacing: 1px; } -.userName:hover { - text-decoration: underline; - text-decoration-color: $teal; -} \ No newline at end of file +.userName { + text-decoration: none; + + &:hover { + text-decoration: underline; + text-decoration-color: $teal; + } +} diff --git a/frontend/app/components/shared/SessionSettings/components/DefaultTimezone.tsx b/frontend/app/components/shared/SessionSettings/components/DefaultTimezone.tsx index 8e6edee88..1f1299025 100644 --- a/frontend/app/components/shared/SessionSettings/components/DefaultTimezone.tsx +++ b/frontend/app/components/shared/SessionSettings/components/DefaultTimezone.tsx @@ -5,24 +5,49 @@ import { useStore } from 'App/mstore'; import { useObserver } from 'mobx-react-lite'; const str = new Date().toString().match(/([A-Z]+[\+-][0-9]+)/) -const d = str && str[1] || 'UTC'; -const localMachineFormat = new Date().toString().match(/([A-Z]+[\+-][0-9]+)/) -const middlePoint = localMachineFormat && localMachineFormat[1].length - 2 -const readableLocalTimezone = localMachineFormat && middlePoint ? - `${localMachineFormat[1].substring(0, 3)} ${localMachineFormat[1].substring(3, middlePoint)}:${localMachineFormat[1].substring(middlePoint)}` - : null +interface TimezonesDropdownValue { + label: string; + value: string; +} +type TimezonesDropdown = TimezonesDropdownValue[] -const timezoneOptions = [ - { label: readableLocalTimezone, value: 'local' }, - { label: 'UTC', value: 'UTC' }, -] +const generateGMTZones = (): TimezonesDropdown => { + const timezones: TimezonesDropdown = [] + + const positiveNumbers = [...Array(12).keys()]; + const negativeNumbers = [...Array(12).keys()].reverse(); + negativeNumbers.pop(); // remove trailing zero since we have on in positive numbers array + + const combinedArray = [...negativeNumbers, ...positiveNumbers]; + + for (let i = 0; i < 23; i++) { + let symbol = i < 11 ? '-' : '+'; + let isUTC = i === 11 + let prefix = isUTC ? 'UTC / GMT' : 'GMT'; + let value = String(combinedArray[i]).padStart(2, '0'); + + let tz = `${prefix} ${symbol}${String(combinedArray[i]).padStart(2, '0')}:00` + + let dropdownValue = `UTC${symbol}${value}` + timezones.push({ label: tz, value: isUTC ? 'UTC' : dropdownValue }) + } + + timezones.splice(17, 0, { label: 'GMT +05:30', value: 'GMT +05:30' }) + return timezones +} + +const timezoneOptions: TimezonesDropdown = [...generateGMTZones()] function DefaultTimezone(props) { const [changed, setChanged] = React.useState(false); const { settingsStore } = useStore(); const [timezone, setTimezone] = React.useState(settingsStore.sessionSettings.timezone); - const sessionSettings = useObserver(() => settingsStore.sessionSettings) + const sessionSettings = useObserver(() => settingsStore.sessionSettings); + + useEffect(() => { + if (!timezone) setTimezone('local'); + }, []); return ( <> diff --git a/frontend/app/date.ts b/frontend/app/date.ts index 8a9501a86..e32704db1 100644 --- a/frontend/app/date.ts +++ b/frontend/app/date.ts @@ -20,7 +20,7 @@ export const durationFormatted = (duration: Duration):string => { export function durationFromMsFormatted(ms: number): string { return durationFormatted(Duration.fromMillis(ms || 0)); -} +} export const durationFormattedFull = (duration: Duration): string => { if (duration.as('minutes') < 1) { // show in seconds @@ -35,7 +35,7 @@ export const durationFormattedFull = (duration: Duration): string => { } else if (duration.as('months') < 1) { // show in days and hours let d = duration.toFormat('d'); duration = d + (d > 1 ? ' days' : ' day'); - } else { + } else { let d = Math.trunc(duration.as('months')); duration = d + (d > 1 ? ' months' : ' month');; } @@ -49,7 +49,7 @@ export const msToSec = (ms:number): number => Math.round(ms / 1000); export const diffFromNowString = (ts:number): string => durationFormattedFull(DateTime.fromMillis(Date.now()).diff(DateTime.fromMillis(ts))); -export const diffFromNowShortString = (ts: number): string => +export const diffFromNowShortString = (ts: number): string => durationFormatted(DateTime.fromMillis(Date.now()).diff(DateTime.fromMillis(ts))); export const getDateFromMill = date => @@ -69,11 +69,19 @@ export function formatDateTimeDefault(timestamp: number): string { return isToday(date) ? 'Today' : date.toFormat('LLL dd, yyyy') + ', ' + date.toFormat('hh:mm a') } +/** + * Formats timestamps into readable date + * @param {Number} timestamp + * @param {String} timezone fixed offset like UTC+6 + * @returns {String} formatted date (or time if its today) + */ export function formatTimeOrDate(timestamp: number, timezone: string): string { var date = DateTime.fromMillis(timestamp) - if (timezone === 'UTC') - date = date.toUTC(); - + if (timezone) { + if (timezone === 'UTC') date = date.toUTC(); + date = date.setZone(timezone) + } + return isToday(date) ? date.toFormat('hh:mm a') : date.toFormat('LLL dd, yyyy, hh:mm a'); }