From 8d31f43f0fb860db2953e449ff0d0a3f46156875 Mon Sep 17 00:00:00 2001 From: Mike Cao Date: Sat, 9 Dec 2023 00:35:54 -0800 Subject: [PATCH] Convert realtime components to TS. --- ...bsiteEventData.js => WebsiteEventData.tsx} | 2 +- .../realtime/{Realtime.js => Realtime.tsx} | 22 +++++----- ...timeCountries.js => RealtimeCountries.tsx} | 0 .../{RealtimeHeader.js => RealtimeHeader.tsx} | 5 ++- .../{RealtimeHome.js => RealtimeHome.tsx} | 0 .../{RealtimeLog.js => RealtimeLog.tsx} | 4 +- .../{RealtimeUrls.js => RealtimeUrls.tsx} | 21 ++++++---- .../{WebsiteReports.js => WebsiteReports.tsx} | 0 src/components/hooks/useDateRange.ts | 14 ++++--- src/components/metrics/RealtimeChart.tsx | 7 ++-- src/lib/date.ts | 40 ++++++++++--------- src/lib/types.ts | 11 ++++- 12 files changed, 75 insertions(+), 51 deletions(-) rename src/app/(main)/websites/[id]/event-data/{WebsiteEventData.js => WebsiteEventData.tsx} (96%) rename src/app/(main)/websites/[id]/realtime/{Realtime.js => Realtime.tsx} (84%) rename src/app/(main)/websites/[id]/realtime/{RealtimeCountries.js => RealtimeCountries.tsx} (100%) rename src/app/(main)/websites/[id]/realtime/{RealtimeHeader.js => RealtimeHeader.tsx} (85%) rename src/app/(main)/websites/[id]/realtime/{RealtimeHome.js => RealtimeHome.tsx} (100%) rename src/app/(main)/websites/[id]/realtime/{RealtimeLog.js => RealtimeLog.tsx} (97%) rename src/app/(main)/websites/[id]/realtime/{RealtimeUrls.js => RealtimeUrls.tsx} (85%) rename src/app/(main)/websites/[id]/reports/{WebsiteReports.js => WebsiteReports.tsx} (100%) diff --git a/src/app/(main)/websites/[id]/event-data/WebsiteEventData.js b/src/app/(main)/websites/[id]/event-data/WebsiteEventData.tsx similarity index 96% rename from src/app/(main)/websites/[id]/event-data/WebsiteEventData.js rename to src/app/(main)/websites/[id]/event-data/WebsiteEventData.tsx index b67ee95e..61a4dc62 100644 --- a/src/app/(main)/websites/[id]/event-data/WebsiteEventData.js +++ b/src/app/(main)/websites/[id]/event-data/WebsiteEventData.tsx @@ -6,7 +6,7 @@ import { EventDataMetricsBar } from './EventDataMetricsBar'; import { useDateRange, useApi, useNavigation } from 'components/hooks'; import styles from './WebsiteEventData.module.css'; -function useData(websiteId, event) { +function useData(websiteId: string, event: string) { const [dateRange] = useDateRange(websiteId); const { startDate, endDate } = dateRange; const { get, useQuery } = useApi(); diff --git a/src/app/(main)/websites/[id]/realtime/Realtime.js b/src/app/(main)/websites/[id]/realtime/Realtime.tsx similarity index 84% rename from src/app/(main)/websites/[id]/realtime/Realtime.js rename to src/app/(main)/websites/[id]/realtime/Realtime.tsx index 37df458c..6de65d7a 100644 --- a/src/app/(main)/websites/[id]/realtime/Realtime.js +++ b/src/app/(main)/websites/[id]/realtime/Realtime.tsx @@ -1,7 +1,7 @@ 'use client'; import { useMemo, useState, useEffect } from 'react'; import { subMinutes, startOfMinute } from 'date-fns'; -import firstBy from 'thenby'; +import thenby from 'thenby'; import { Grid, GridRow } from 'components/layout/Grid'; import Page from 'components/layout/Page'; import RealtimeChart from 'components/metrics/RealtimeChart'; @@ -15,9 +15,10 @@ import useApi from 'components/hooks/useApi'; import { percentFilter } from 'lib/filters'; import { REALTIME_RANGE, REALTIME_INTERVAL } from 'lib/constants'; import { useWebsite } from 'components/hooks'; +import { RealtimeData } from 'lib/types'; import styles from './Realtime.module.css'; -function mergeData(state = [], data = [], time) { +function mergeData(state = [], data = [], time: number) { const ids = state.map(({ __id }) => __id); return state .concat(data.filter(({ __id }) => !ids.includes(__id))) @@ -25,7 +26,7 @@ function mergeData(state = [], data = [], time) { } export function Realtime({ websiteId }) { - const [currentData, setCurrentData] = useState(); + const [currentData, setCurrentData] = useState(); const { get, useQuery } = useApi(); const { data: website } = useWebsite(websiteId); const { data, isLoading, error } = useQuery({ @@ -33,7 +34,6 @@ export function Realtime({ websiteId }) { queryFn: () => get(`/realtime/${websiteId}`, { startAt: currentData?.timestamp || 0 }), enabled: !!(websiteId && website), refetchInterval: REALTIME_INTERVAL, - cache: false, }); useEffect(() => { @@ -50,9 +50,9 @@ export function Realtime({ websiteId }) { } }, [data]); - const realtimeData = useMemo(() => { + const realtimeData: RealtimeData = useMemo(() => { if (!currentData) { - return { pageviews: [], sessions: [], events: [], countries: [], visitors: [] }; + return { pageviews: [], sessions: [], events: [], countries: [], visitors: [], timestamp: 0 }; } currentData.countries = percentFilter( @@ -75,7 +75,7 @@ export function Realtime({ websiteId }) { } return arr; }, []) - .sort(firstBy('y', -1)), + .sort(thenby.firstBy('y', -1)), ); currentData.visitors = currentData.sessions.reduce((arr, val) => { @@ -89,18 +89,18 @@ export function Realtime({ websiteId }) { }, [currentData]); if (isLoading || error) { - return ; + return ; } return ( <> - + - - + + diff --git a/src/app/(main)/websites/[id]/realtime/RealtimeCountries.js b/src/app/(main)/websites/[id]/realtime/RealtimeCountries.tsx similarity index 100% rename from src/app/(main)/websites/[id]/realtime/RealtimeCountries.js rename to src/app/(main)/websites/[id]/realtime/RealtimeCountries.tsx diff --git a/src/app/(main)/websites/[id]/realtime/RealtimeHeader.js b/src/app/(main)/websites/[id]/realtime/RealtimeHeader.tsx similarity index 85% rename from src/app/(main)/websites/[id]/realtime/RealtimeHeader.js rename to src/app/(main)/websites/[id]/realtime/RealtimeHeader.tsx index 75f2f2d4..ad03efd1 100644 --- a/src/app/(main)/websites/[id]/realtime/RealtimeHeader.js +++ b/src/app/(main)/websites/[id]/realtime/RealtimeHeader.tsx @@ -1,10 +1,11 @@ import MetricCard from 'components/metrics/MetricCard'; import useMessages from 'components/hooks/useMessages'; +import { RealtimeData } from 'lib/types'; import styles from './RealtimeHeader.module.css'; -export function RealtimeHeader({ data = {} }) { +export function RealtimeHeader({ data }: { data: RealtimeData }) { const { formatMessage, labels } = useMessages(); - const { pageviews, visitors, events, countries } = data; + const { pageviews, visitors, events, countries } = data || {}; return (
diff --git a/src/app/(main)/websites/[id]/realtime/RealtimeHome.js b/src/app/(main)/websites/[id]/realtime/RealtimeHome.tsx similarity index 100% rename from src/app/(main)/websites/[id]/realtime/RealtimeHome.js rename to src/app/(main)/websites/[id]/realtime/RealtimeHome.tsx diff --git a/src/app/(main)/websites/[id]/realtime/RealtimeLog.js b/src/app/(main)/websites/[id]/realtime/RealtimeLog.tsx similarity index 97% rename from src/app/(main)/websites/[id]/realtime/RealtimeLog.js rename to src/app/(main)/websites/[id]/realtime/RealtimeLog.tsx index b388b37b..e33320ec 100644 --- a/src/app/(main)/websites/[id]/realtime/RealtimeLog.js +++ b/src/app/(main)/websites/[id]/realtime/RealtimeLog.tsx @@ -1,7 +1,7 @@ import { useMemo, useState } from 'react'; import { StatusLight, Icon, Text } from 'react-basics'; import { FixedSizeList } from 'react-window'; -import firstBy from 'thenby'; +import thenby from 'thenby'; import FilterButtons from 'components/common/FilterButtons'; import Empty from 'components/common/Empty'; import useLocale from 'components/hooks/useLocale'; @@ -130,7 +130,7 @@ export function RealtimeLog({ data, websiteDomain }) { } const { pageviews, visitors, events } = data; - const logs = [...pageviews, ...visitors, ...events].sort(firstBy('createdAt', -1)); + const logs = [...pageviews, ...visitors, ...events].sort(thenby.firstBy('createdAt', -1)); if (filter !== TYPE_ALL) { return logs.filter(({ __type }) => __type === filter); diff --git a/src/app/(main)/websites/[id]/realtime/RealtimeUrls.js b/src/app/(main)/websites/[id]/realtime/RealtimeUrls.tsx similarity index 85% rename from src/app/(main)/websites/[id]/realtime/RealtimeUrls.js rename to src/app/(main)/websites/[id]/realtime/RealtimeUrls.tsx index 674858b2..27a9ec5a 100644 --- a/src/app/(main)/websites/[id]/realtime/RealtimeUrls.js +++ b/src/app/(main)/websites/[id]/realtime/RealtimeUrls.tsx @@ -1,15 +1,22 @@ -import { useMemo, useState } from 'react'; +import { Key, useMemo, useState } from 'react'; import { ButtonGroup, Button, Flexbox } from 'react-basics'; -import firstBy from 'thenby'; +import thenby from 'thenby'; import { percentFilter } from 'lib/filters'; import ListTable from 'components/metrics/ListTable'; import { FILTER_PAGES, FILTER_REFERRERS } from 'lib/constants'; import useMessages from 'components/hooks/useMessages'; +import { RealtimeData } from 'lib/types'; -export function RealtimeUrls({ websiteDomain, data = {} }) { +export function RealtimeUrls({ + websiteDomain, + data, +}: { + websiteDomain: string; + data: RealtimeData; +}) { const { formatMessage, labels } = useMessages(); - const { pageviews } = data; - const [filter, setFilter] = useState(FILTER_REFERRERS); + const { pageviews } = data || {}; + const [filter, setFilter] = useState(FILTER_REFERRERS); const limit = 15; const buttons = [ @@ -48,7 +55,7 @@ export function RealtimeUrls({ websiteDomain, data = {} }) { } return arr; }, []) - .sort(firstBy('y', -1)) + .sort(thenby.firstBy('y', -1)) .slice(0, limit), ); @@ -64,7 +71,7 @@ export function RealtimeUrls({ websiteDomain, data = {} }) { } return arr; }, []) - .sort(firstBy('y', -1)) + .sort(thenby.firstBy('y', -1)) .slice(0, limit), ); diff --git a/src/app/(main)/websites/[id]/reports/WebsiteReports.js b/src/app/(main)/websites/[id]/reports/WebsiteReports.tsx similarity index 100% rename from src/app/(main)/websites/[id]/reports/WebsiteReports.js rename to src/app/(main)/websites/[id]/reports/WebsiteReports.tsx diff --git a/src/components/hooks/useDateRange.ts b/src/components/hooks/useDateRange.ts index 71361b69..d8a49331 100644 --- a/src/components/hooks/useDateRange.ts +++ b/src/components/hooks/useDateRange.ts @@ -1,9 +1,10 @@ import { getMinimumUnit, parseDateRange } from 'lib/date'; import { setItem } from 'next-basics'; import { DATE_RANGE_CONFIG, DEFAULT_DATE_RANGE } from 'lib/constants'; -import useLocale from './useLocale'; import websiteStore, { setWebsiteDateRange } from 'store/websites'; import appStore, { setDateRange } from 'store/app'; +import { DateRange } from 'lib/types'; +import useLocale from './useLocale'; import useApi from './useApi'; export function useDateRange(websiteId?: string) { @@ -14,9 +15,9 @@ export function useDateRange(websiteId?: string) { const globalConfig = appStore(state => state.dateRange); const dateRange = parseDateRange(websiteConfig || globalConfig || defaultConfig, locale); - const saveDateRange = async value => { + const saveDateRange = async (value: DateRange | string) => { if (websiteId) { - let dateRange = value; + let dateRange: DateRange | string = value; if (typeof value === 'string') { if (value === 'all') { @@ -37,14 +38,17 @@ export function useDateRange(websiteId?: string) { } } - setWebsiteDateRange(websiteId, dateRange); + setWebsiteDateRange(websiteId, dateRange as DateRange); } else { setItem(DATE_RANGE_CONFIG, value); setDateRange(value); } }; - return [dateRange, saveDateRange]; + return [dateRange, saveDateRange] as [ + { startDate: Date; endDate: Date }, + (value: string | DateRange) => void, + ]; } export default useDateRange; diff --git a/src/components/metrics/RealtimeChart.tsx b/src/components/metrics/RealtimeChart.tsx index f1a781bd..1ca0719a 100644 --- a/src/components/metrics/RealtimeChart.tsx +++ b/src/components/metrics/RealtimeChart.tsx @@ -3,6 +3,7 @@ import { format, startOfMinute, subMinutes, isBefore } from 'date-fns'; import PageviewsChart from './PageviewsChart'; import { getDateArray } from 'lib/date'; import { DEFAULT_ANIMATION_DURATION, REALTIME_RANGE } from 'lib/constants'; +import { RealtimeData } from 'lib/types'; function mapData(data: any[]) { let last = 0; @@ -24,11 +25,9 @@ function mapData(data: any[]) { } export interface RealtimeChartProps { - data: { - pageviews: any[]; - visitors: any[]; - }; + data: RealtimeData; unit: string; + className?: string; } export function RealtimeChart({ data, unit, ...props }: RealtimeChartProps) { diff --git a/src/lib/date.ts b/src/lib/date.ts index 51057309..81c37e69 100644 --- a/src/lib/date.ts +++ b/src/lib/date.ts @@ -32,6 +32,7 @@ import { subWeeks, } from 'date-fns'; import { getDateLocale } from 'lib/lang'; +import { DateRange } from 'lib/types'; export const TIME_UNIT = { minute: 'minute', @@ -54,13 +55,13 @@ export function getTimezone() { return moment.tz.guess(); } -export function getLocalTime(t) { +export function getLocalTime(t: string | number | Date) { return addMinutes(new Date(t), new Date().getTimezoneOffset()); } -export function parseDateRange(value, locale = 'en-US') { +export function parseDateRange(value: string | object, locale = 'en-US'): DateRange { if (typeof value === 'object') { - return value; + return value as DateRange; } if (value === 'all') { @@ -93,7 +94,7 @@ export function parseDateRange(value, locale = 'en-US') { if (!match) return null; const { num, unit } = match.groups; - const selectedUnit = { num, unit }; + const selectedUnit = { num: +num, unit }; if (+num === 1) { switch (unit) { @@ -172,7 +173,7 @@ export function parseDateRange(value, locale = 'en-US') { switch (unit) { case 'day': return { - startDate: subDays(startOfDay(now), num - 1), + startDate: subDays(startOfDay(now), +num - 1), endDate: endOfDay(now), unit, value, @@ -180,7 +181,7 @@ export function parseDateRange(value, locale = 'en-US') { }; case 'hour': return { - startDate: subHours(startOfHour(now), num - 1), + startDate: subHours(startOfHour(now), +num - 1), endDate: endOfHour(now), unit, value, @@ -189,7 +190,10 @@ export function parseDateRange(value, locale = 'en-US') { } } -export function incrementDateRange(value, increment) { +export function incrementDateRange( + value: { startDate: any; endDate: any; selectedUnit: any }, + increment: number, +) { const { startDate, endDate, selectedUnit } = value; const { num, unit } = selectedUnit; @@ -235,7 +239,7 @@ export function incrementDateRange(value, increment) { } } -export function getAllowedUnits(startDate, endDate) { +export function getAllowedUnits(startDate: Date, endDate: Date) { const units = ['minute', 'hour', 'day', 'month', 'year']; const minUnit = getMinimumUnit(startDate, endDate); const index = units.indexOf(minUnit === 'year' ? 'month' : minUnit); @@ -243,7 +247,7 @@ export function getAllowedUnits(startDate, endDate) { return index >= 0 ? units.splice(index) : []; } -export function getMinimumUnit(startDate, endDate) { +export function getMinimumUnit(startDate: number | Date, endDate: number | Date) { if (differenceInMinutes(endDate, startDate) <= 60) { return 'minute'; } else if (differenceInHours(endDate, startDate) <= 48) { @@ -257,25 +261,25 @@ export function getMinimumUnit(startDate, endDate) { return 'year'; } -export function getDateFromString(str) { +export function getDateFromString(str: string) { const [ymd, hms] = str.split(' '); const [year, month, day] = ymd.split('-'); if (hms) { const [hour, min, sec] = hms.split(':'); - return new Date(year, month - 1, day, hour, min, sec); + return new Date(+year, +month - 1, +day, +hour, +min, +sec); } - return new Date(year, month - 1, day); + return new Date(+year, +month - 1, +day); } -export function getDateArray(data, startDate, endDate, unit) { +export function getDateArray(data: any[], startDate: Date, endDate: Date, unit: string) { const arr = []; const [diff, add, normalize] = dateFuncs[unit]; const n = diff(endDate, startDate) + 1; - function findData(date) { + function findData(date: Date) { const d = data.find(({ x }) => { return normalize(getDateFromString(x)).getTime() === date.getTime(); }); @@ -293,7 +297,7 @@ export function getDateArray(data, startDate, endDate, unit) { return arr; } -export function getDateLength(startDate, endDate, unit) { +export function getDateLength(startDate: Date, endDate: Date, unit: string | number) { const [diff] = dateFuncs[unit]; return diff(endDate, startDate) + 1; } @@ -310,7 +314,7 @@ export const CUSTOM_FORMATS = { }, }; -export function formatDate(date, str, locale = 'en-US') { +export function formatDate(date: string | number | Date, str: string, locale = 'en-US') { return format( typeof date === 'string' ? new Date(date) : date, CUSTOM_FORMATS?.[locale]?.[str] || str, @@ -320,10 +324,10 @@ export function formatDate(date, str, locale = 'en-US') { ); } -export function maxDate(...args) { +export function maxDate(...args: Date[]) { return max(args.filter(n => isDate(n))); } -export function minDate(...args) { +export function minDate(...args: any[]) { return min(args.filter(n => isDate(n))); } diff --git a/src/lib/types.ts b/src/lib/types.ts index a7fab1e7..900f2e5a 100644 --- a/src/lib/types.ts +++ b/src/lib/types.ts @@ -180,7 +180,7 @@ export interface DateRange { endDate: Date; value: string; unit?: TimeUnit; - selectedUnit?: TimeUnit; + selectedUnit?: { num: number; unit: TimeUnit }; } export interface QueryFilters { @@ -207,3 +207,12 @@ export interface QueryOptions { joinSession?: boolean; columns?: { [key: string]: string }; } + +export interface RealtimeData { + pageviews: any[]; + sessions: any[]; + events: any[]; + timestamp: number; + countries?: any[]; + visitors?: any[]; +}