[in progress][WebsiteList] add table, fix ui
parent
32d2a5830e
commit
2d591a7906
|
@ -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 (
|
||||||
|
<Link href="/website/[...id]" as={`/website/${website_id}/${name}`}>
|
||||||
|
<Favicon domain={domain} />
|
||||||
|
{name}
|
||||||
|
</Link>
|
||||||
|
);
|
||||||
|
}
|
|
@ -1,28 +1,217 @@
|
||||||
import React from 'react';
|
import React, { useLayoutEffect, useMemo, useState } from 'react';
|
||||||
import { FormattedMessage } from 'react-intl';
|
import { FormattedMessage } from 'react-intl';
|
||||||
import Link from 'components/common/Link';
|
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 Page from 'components/layout/Page';
|
||||||
import EmptyPlaceholder from 'components/common/EmptyPlaceholder';
|
import EmptyPlaceholder from 'components/common/EmptyPlaceholder';
|
||||||
import useFetch from 'hooks/useFetch';
|
import useFetch from 'hooks/useFetch';
|
||||||
|
import useDateRange from 'hooks/useDateRange';
|
||||||
|
import useShareToken from 'hooks/useShareToken';
|
||||||
import Arrow from 'assets/arrow-right.svg';
|
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 }) {
|
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) {
|
const websitesIds = useMemo(() => {
|
||||||
return null;
|
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 }) => (
|
||||||
|
<Link
|
||||||
|
href="/website/[...id]"
|
||||||
|
as={`/website/${website_id}/${name}`}
|
||||||
|
icon={<Arrow />}
|
||||||
|
size="small"
|
||||||
|
iconRight
|
||||||
|
>
|
||||||
|
Details
|
||||||
|
</Link>
|
||||||
|
),
|
||||||
|
},
|
||||||
|
],
|
||||||
|
[],
|
||||||
|
);
|
||||||
|
|
||||||
|
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 (
|
return (
|
||||||
<Page>
|
<Page>
|
||||||
{data.map(({ website_id, name, domain }) => (
|
<DateFilter value={value} startDate={startDate} endDate={endDate} onChange={setDateRange} />
|
||||||
|
<table {...getTableProps()}>
|
||||||
|
<thead>
|
||||||
|
{headerGroups.map(headerGroup => (
|
||||||
|
<tr key={headerGroup} {...headerGroup.getHeaderGroupProps()}>
|
||||||
|
{headerGroup.headers.map(column => (
|
||||||
|
<th key={column} {...column.getHeaderProps()}>
|
||||||
|
{column.render('Header')}
|
||||||
|
</th>
|
||||||
|
))}
|
||||||
|
</tr>
|
||||||
|
))}
|
||||||
|
</thead>
|
||||||
|
<tbody {...getTableBodyProps()}>
|
||||||
|
{page.map(row => {
|
||||||
|
prepareRow(row);
|
||||||
|
return (
|
||||||
|
<tr key={row} {...row.getRowProps()}>
|
||||||
|
{row.cells.map(cell => {
|
||||||
|
return (
|
||||||
|
<td key={cell} {...cell.getCellProps()}>
|
||||||
|
{cell.render('Cell', { ...row.original })}
|
||||||
|
</td>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</tr>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
<div className="pagination">
|
||||||
|
<button onClick={() => gotoPage(0)} disabled={!canPreviousPage}>
|
||||||
|
{'<<'}
|
||||||
|
</button>{' '}
|
||||||
|
<button onClick={() => previousPage()} disabled={!canPreviousPage}>
|
||||||
|
{'<'}
|
||||||
|
</button>{' '}
|
||||||
|
<button onClick={() => nextPage()} disabled={!canNextPage}>
|
||||||
|
{'>'}
|
||||||
|
</button>{' '}
|
||||||
|
<button onClick={() => gotoPage(pageCount - 1)} disabled={!canNextPage}>
|
||||||
|
{'>>'}
|
||||||
|
</button>{' '}
|
||||||
|
<span>
|
||||||
|
Page{' '}
|
||||||
|
<strong>
|
||||||
|
{pageIndex + 1} of {pageOptions.length}
|
||||||
|
</strong>{' '}
|
||||||
|
</span>
|
||||||
|
<span>
|
||||||
|
| Go to page:{' '}
|
||||||
|
<input
|
||||||
|
type="number"
|
||||||
|
defaultValue={pageIndex + 1}
|
||||||
|
onChange={e => {
|
||||||
|
const page = e.target.value ? Number(e.target.value) - 1 : 0;
|
||||||
|
gotoPage(page);
|
||||||
|
}}
|
||||||
|
style={{ width: '100px' }}
|
||||||
|
/>
|
||||||
|
</span>{' '}
|
||||||
|
<select
|
||||||
|
value={pageSize}
|
||||||
|
onChange={e => {
|
||||||
|
setPageSize(Number(e.target.value));
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{[10, 20, 30, 40, 50].map(pageSize => (
|
||||||
|
<option key={pageSize} value={pageSize}>
|
||||||
|
Show {pageSize}
|
||||||
|
</option>
|
||||||
|
))}
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
{/* <Table columns={columns} rows={data} empty={empty} /> */}
|
||||||
|
{/* {data.map(({ website_id, name, domain }) => (
|
||||||
<div key={website_id} className={styles.website}>
|
<div key={website_id} className={styles.website}>
|
||||||
<WebsiteChart websiteId={website_id} title={name} domain={domain} showLink />
|
<WebsiteChart websiteId={website_id} title={name} domain={domain} showLink />
|
||||||
</div>
|
</div>
|
||||||
))}
|
))} */}
|
||||||
{data.length === 0 && (
|
{fetchedData.data?.length === 0 && (
|
||||||
<EmptyPlaceholder
|
<EmptyPlaceholder
|
||||||
msg={
|
msg={
|
||||||
<FormattedMessage
|
<FormattedMessage
|
||||||
|
|
|
@ -1,9 +1,9 @@
|
||||||
import React, { useState } from 'react';
|
import React, { useState } from 'react';
|
||||||
import { FormattedMessage } from 'react-intl';
|
import { FormattedMessage } from 'react-intl';
|
||||||
import classNames from 'classnames';
|
import classNames from 'classnames';
|
||||||
import Link from 'components/common/Link';
|
|
||||||
import Table from 'components/common/Table';
|
import Table from 'components/common/Table';
|
||||||
import Button from 'components/common/Button';
|
import Button from 'components/common/Button';
|
||||||
|
import DetailsLink from 'components/common/DetailsLink';
|
||||||
import PageHeader from 'components/layout/PageHeader';
|
import PageHeader from 'components/layout/PageHeader';
|
||||||
import Modal from 'components/common/Modal';
|
import Modal from 'components/common/Modal';
|
||||||
import WebsiteEditForm from 'components/forms/WebsiteEditForm';
|
import WebsiteEditForm from 'components/forms/WebsiteEditForm';
|
||||||
|
@ -13,7 +13,6 @@ import ShareUrlForm from 'components/forms/ShareUrlForm';
|
||||||
import EmptyPlaceholder from 'components/common/EmptyPlaceholder';
|
import EmptyPlaceholder from 'components/common/EmptyPlaceholder';
|
||||||
import ButtonLayout from 'components/layout/ButtonLayout';
|
import ButtonLayout from 'components/layout/ButtonLayout';
|
||||||
import Toast from 'components/common/Toast';
|
import Toast from 'components/common/Toast';
|
||||||
import Favicon from 'components/common/Favicon';
|
|
||||||
import Pen from 'assets/pen.svg';
|
import Pen from 'assets/pen.svg';
|
||||||
import Trash from 'assets/trash.svg';
|
import Trash from 'assets/trash.svg';
|
||||||
import Plus from 'assets/plus.svg';
|
import Plus from 'assets/plus.svg';
|
||||||
|
@ -61,13 +60,6 @@ export default function WebsiteSettings() {
|
||||||
</ButtonLayout>
|
</ButtonLayout>
|
||||||
);
|
);
|
||||||
|
|
||||||
const DetailsLink = ({ website_id, name, domain }) => (
|
|
||||||
<Link href="/website/[...id]" as={`/website/${website_id}/${name}`}>
|
|
||||||
<Favicon domain={domain} />
|
|
||||||
{name}
|
|
||||||
</Link>
|
|
||||||
);
|
|
||||||
|
|
||||||
const columns = [
|
const columns = [
|
||||||
{
|
{
|
||||||
key: 'name',
|
key: 'name',
|
||||||
|
|
|
@ -14,7 +14,7 @@ services:
|
||||||
db:
|
db:
|
||||||
image: postgres:12-alpine
|
image: postgres:12-alpine
|
||||||
ports:
|
ports:
|
||||||
- "127.0.0.1:5432:5432"
|
- "127.0.0.1:12345:5432"
|
||||||
environment:
|
environment:
|
||||||
POSTGRES_DB: umami
|
POSTGRES_DB: umami
|
||||||
POSTGRES_USER: umami
|
POSTGRES_USER: umami
|
||||||
|
|
|
@ -13618,6 +13618,11 @@
|
||||||
"prop-types": "^15.5.8"
|
"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": {
|
"react-tooltip": {
|
||||||
"version": "4.2.10",
|
"version": "4.2.10",
|
||||||
"resolved": "https://registry.npmjs.org/react-tooltip/-/react-tooltip-4.2.10.tgz",
|
"resolved": "https://registry.npmjs.org/react-tooltip/-/react-tooltip-4.2.10.tgz",
|
||||||
|
|
|
@ -73,6 +73,7 @@
|
||||||
"is-localhost-ip": "^1.4.0",
|
"is-localhost-ip": "^1.4.0",
|
||||||
"isbot-fast": "^1.2.0",
|
"isbot-fast": "^1.2.0",
|
||||||
"jose": "^2.0.3",
|
"jose": "^2.0.3",
|
||||||
|
"lodash.find": "^4.6.0",
|
||||||
"maxmind": "^4.3.1",
|
"maxmind": "^4.3.1",
|
||||||
"moment-timezone": "^0.5.31",
|
"moment-timezone": "^0.5.31",
|
||||||
"next": "^10.0.0",
|
"next": "^10.0.0",
|
||||||
|
@ -83,6 +84,7 @@
|
||||||
"react-redux": "^7.2.2",
|
"react-redux": "^7.2.2",
|
||||||
"react-simple-maps": "^2.3.0",
|
"react-simple-maps": "^2.3.0",
|
||||||
"react-spring": "^8.0.27",
|
"react-spring": "^8.0.27",
|
||||||
|
"react-table": "^7.6.2",
|
||||||
"react-tooltip": "^4.2.10",
|
"react-tooltip": "^4.2.10",
|
||||||
"react-use-measure": "^2.0.2",
|
"react-use-measure": "^2.0.2",
|
||||||
"react-window": "^1.8.6",
|
"react-window": "^1.8.6",
|
||||||
|
|
10
yarn.lock
10
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"
|
resolved "https://registry.yarnpkg.com/lodash._reinterpolate/-/lodash._reinterpolate-3.0.0.tgz#0ccf2d89166af03b3663c796538b75ac6e114d9d"
|
||||||
integrity sha1-DM8tiRZq8Ds2Y8eWU4t1rG4RTZ0=
|
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:
|
lodash.merge@^4.6.0, lodash.merge@^4.6.2:
|
||||||
version "4.6.2"
|
version "4.6.2"
|
||||||
resolved "https://registry.yarnpkg.com/lodash.merge/-/lodash.merge-4.6.2.tgz#558aa53b43b661e1925a0afdfa36a9a1085fe57a"
|
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"
|
"@babel/runtime" "^7.3.1"
|
||||||
prop-types "^15.5.8"
|
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:
|
react-tooltip@^4.2.10:
|
||||||
version "4.2.10"
|
version "4.2.10"
|
||||||
resolved "https://registry.yarnpkg.com/react-tooltip/-/react-tooltip-4.2.10.tgz#ed1a1acd388940c96f4b6309f4fd4dcce5e01bdc"
|
resolved "https://registry.yarnpkg.com/react-tooltip/-/react-tooltip-4.2.10.tgz#ed1a1acd388940c96f4b6309f4fd4dcce5e01bdc"
|
||||||
|
|
Loading…
Reference in New Issue