From 2d591a790639515415c6513347af7fe825c3662e Mon Sep 17 00:00:00 2001 From: Sergey Nikiforov Date: Fri, 20 Nov 2020 23:04:30 +0300 Subject: [PATCH] [in progress][WebsiteList] add table, fix ui --- components/common/DetailsLink.js | 12 ++ components/pages/WebsiteList.js | 209 +++++++++++++++++++++++-- components/settings/WebsiteSettings.js | 10 +- docker-compose.yml | 2 +- package-lock.json | 5 + package.json | 2 + yarn.lock | 10 ++ 7 files changed, 230 insertions(+), 20 deletions(-) create mode 100644 components/common/DetailsLink.js diff --git a/components/common/DetailsLink.js b/components/common/DetailsLink.js new file mode 100644 index 00000000..01dc3657 --- /dev/null +++ b/components/common/DetailsLink.js @@ -0,0 +1,12 @@ +import React from 'react'; +import Link from 'components/common/Link'; +import Favicon from 'components/common/Favicon'; + +export default function DetailsLink({ website_id, name, domain }) { + return ( + + + {name} + + ); +} diff --git a/components/pages/WebsiteList.js b/components/pages/WebsiteList.js index 03552470..747ad24f 100644 --- a/components/pages/WebsiteList.js +++ b/components/pages/WebsiteList.js @@ -1,28 +1,217 @@ -import React from 'react'; +import React, { useLayoutEffect, useMemo, useState } from 'react'; import { FormattedMessage } from 'react-intl'; import Link from 'components/common/Link'; -import WebsiteChart from 'components/metrics/WebsiteChart'; +import DateFilter from 'components/common/DateFilter'; import Page from 'components/layout/Page'; import EmptyPlaceholder from 'components/common/EmptyPlaceholder'; import useFetch from 'hooks/useFetch'; +import useDateRange from 'hooks/useDateRange'; +import useShareToken from 'hooks/useShareToken'; import Arrow from 'assets/arrow-right.svg'; -import styles from './WebsiteList.module.css'; +import { get } from 'lib/web'; +import { TOKEN_HEADER } from 'lib/constants'; +import { useRouter } from 'next/router'; +import { useTable, usePagination } from 'react-table'; +import find from 'lodash.find'; export default function WebsiteList({ userId }) { - const { data } = useFetch('/api/websites', { params: { user_id: userId } }); + const [stats, setStats] = useState([]); + const fetchedData = useFetch('/api/websites', { params: { user_id: userId } }); + const { basePath } = useRouter(); + const shareToken = useShareToken(); + const [dateRange, setDateRange] = useDateRange(); + const { startDate, endDate, value } = dateRange; - if (!data) { - return null; - } + const websitesIds = useMemo(() => { + if (!fetchedData.data) return []; + return fetchedData.data.map(site => site.website_id); + }, [fetchedData.data]); + + const tableData = useMemo(() => { + if (!fetchedData.data || !stats.length) return []; + + const _data = []; + fetchedData.data.forEach(i => { + const stat = find(stats, { id: i.website_id }) || {}; + _data.push({ ...i, ...stat.data }); + }); + return _data; + }, [fetchedData.data, stats.length]); + + const getStats = async () => { + const websitesData = []; + for (let id of websitesIds) { + const url = `/api/website/${id}/stats`; + const _data = await get( + `${basePath}${url}`, + { + start_at: +startDate, + end_at: +endDate, + url, + }, + { + [TOKEN_HEADER]: shareToken?.token, + }, + ); + websitesData.push({ data: _data.data, id }); + } + return Promise.all(websitesData).then(res => { + setStats(res); + }); + }; + + useLayoutEffect(() => { + getStats(); + }, [fetchedData.data, stats.length]); + + const tableColumns = useMemo( + () => [ + { + Header: 'Name', + accessor: 'name', + }, + { + Header: 'Domain', + accessor: 'domain', + }, + { + Header: 'Views', + accessor: 'pageviews', + }, + { + Header: 'Visitors', + accessor: 'uniques', + }, + { + Header: 'Bounce rate', + accessor: 'bounces', + }, + { + Header: 'Details', + accessor: 'details', + Cell: ({ website_id }) => ( + } + size="small" + iconRight + > + Details + + ), + }, + ], + [], + ); + + const { + getTableProps, + getTableBodyProps, + headerGroups, + page, + prepareRow, + canPreviousPage, + canNextPage, + pageOptions, + pageCount, + gotoPage, + nextPage, + previousPage, + setPageSize, + state: { pageIndex, pageSize }, + } = useTable( + { + columns: tableColumns, + data: tableData, + initialState: { pageIndex: 0, pageSize: 10 }, + }, + usePagination, + ); return ( - {data.map(({ website_id, name, domain }) => ( + + + + {headerGroups.map(headerGroup => ( + + {headerGroup.headers.map(column => ( + + ))} + + ))} + + + {page.map(row => { + prepareRow(row); + return ( + + {row.cells.map(cell => { + return ( + + ); + })} + + ); + })} + +
+ {column.render('Header')} +
+ {cell.render('Cell', { ...row.original })} +
+
+ {' '} + {' '} + {' '} + {' '} + + Page{' '} + + {pageIndex + 1} of {pageOptions.length} + {' '} + + + | Go to page:{' '} + { + const page = e.target.value ? Number(e.target.value) - 1 : 0; + gotoPage(page); + }} + style={{ width: '100px' }} + /> + {' '} + +
+ {/* */} + {/* {data.map(({ website_id, name, domain }) => (
- ))} - {data.length === 0 && ( + ))} */} + {fetchedData.data?.length === 0 && ( ); - const DetailsLink = ({ website_id, name, domain }) => ( - - - {name} - - ); - const columns = [ { key: 'name', diff --git a/docker-compose.yml b/docker-compose.yml index 590e2726..4e9b0580 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -14,7 +14,7 @@ services: db: image: postgres:12-alpine ports: - - "127.0.0.1:5432:5432" + - "127.0.0.1:12345:5432" environment: POSTGRES_DB: umami POSTGRES_USER: umami diff --git a/package-lock.json b/package-lock.json index 9f2f119e..ac5dc567 100644 --- a/package-lock.json +++ b/package-lock.json @@ -13618,6 +13618,11 @@ "prop-types": "^15.5.8" } }, + "react-table": { + "version": "7.6.2", + "resolved": "https://registry.npmjs.org/react-table/-/react-table-7.6.2.tgz", + "integrity": "sha512-urwNZTieb+xg/+BITUIrqdH5jZfJlw7rKVAAq25iXpBPwbQojLCEKJuGycLbVwn8fzU+Ovly3y8HHNaLNrPCvQ==" + }, "react-tooltip": { "version": "4.2.10", "resolved": "https://registry.npmjs.org/react-tooltip/-/react-tooltip-4.2.10.tgz", diff --git a/package.json b/package.json index cfab3adf..795617b5 100644 --- a/package.json +++ b/package.json @@ -73,6 +73,7 @@ "is-localhost-ip": "^1.4.0", "isbot-fast": "^1.2.0", "jose": "^2.0.3", + "lodash.find": "^4.6.0", "maxmind": "^4.3.1", "moment-timezone": "^0.5.31", "next": "^10.0.0", @@ -83,6 +84,7 @@ "react-redux": "^7.2.2", "react-simple-maps": "^2.3.0", "react-spring": "^8.0.27", + "react-table": "^7.6.2", "react-tooltip": "^4.2.10", "react-use-measure": "^2.0.2", "react-window": "^1.8.6", diff --git a/yarn.lock b/yarn.lock index 01277268..0fb8f8ed 100644 --- a/yarn.lock +++ b/yarn.lock @@ -5540,6 +5540,11 @@ lodash._reinterpolate@^3.0.0: resolved "https://registry.yarnpkg.com/lodash._reinterpolate/-/lodash._reinterpolate-3.0.0.tgz#0ccf2d89166af03b3663c796538b75ac6e114d9d" integrity sha1-DM8tiRZq8Ds2Y8eWU4t1rG4RTZ0= +lodash.find@^4.6.0: + version "4.6.0" + resolved "https://registry.yarnpkg.com/lodash.find/-/lodash.find-4.6.0.tgz#cb0704d47ab71789ffa0de8b97dd926fb88b13b1" + integrity sha1-ywcE1Hq3F4n/oN6Ll92Sb7iLE7E= + lodash.merge@^4.6.0, lodash.merge@^4.6.2: version "4.6.2" resolved "https://registry.yarnpkg.com/lodash.merge/-/lodash.merge-4.6.2.tgz#558aa53b43b661e1925a0afdfa36a9a1085fe57a" @@ -7515,6 +7520,11 @@ react-spring@^8.0.27: "@babel/runtime" "^7.3.1" prop-types "^15.5.8" +react-table@^7.6.2: + version "7.6.2" + resolved "https://registry.yarnpkg.com/react-table/-/react-table-7.6.2.tgz#b60932fa6d457c2bca0da49815cd6a8fe9451f77" + integrity sha512-urwNZTieb+xg/+BITUIrqdH5jZfJlw7rKVAAq25iXpBPwbQojLCEKJuGycLbVwn8fzU+Ovly3y8HHNaLNrPCvQ== + react-tooltip@^4.2.10: version "4.2.10" resolved "https://registry.yarnpkg.com/react-tooltip/-/react-tooltip-4.2.10.tgz#ed1a1acd388940c96f4b6309f4fd4dcce5e01bdc"