mirror of
https://github.com/kremalicious/umami.git
synced 2024-11-22 09:57:00 +01:00
Merge branch 'data-table' into dev
# Conflicts: # src/components/messages.js
This commit is contained in:
commit
92ccc64e47
68
src/components/common/DataTable.js
Normal file
68
src/components/common/DataTable.js
Normal file
@ -0,0 +1,68 @@
|
||||
import { createContext } from 'react';
|
||||
import { SearchField } from 'react-basics';
|
||||
import { useDataTable } from 'components/hooks/useDataTable';
|
||||
import { useMessages } from 'components/hooks';
|
||||
import Empty from 'components/common/Empty';
|
||||
import Pager from 'components/common/Pager';
|
||||
import styles from './DataTable.module.css';
|
||||
|
||||
const DEFAULT_SEARCH_DELAY = 1000;
|
||||
|
||||
export const DataTableStyles = styles;
|
||||
|
||||
export const DataTableContext = createContext(null);
|
||||
|
||||
export function DataTable({
|
||||
searchDelay,
|
||||
showSearch = true,
|
||||
showPaging = true,
|
||||
children,
|
||||
onChange,
|
||||
}) {
|
||||
const { formatMessage, labels, messages } = useMessages();
|
||||
const dataTable = useDataTable();
|
||||
const { query, setQuery, data, pageInfo, setPageInfo } = dataTable;
|
||||
const { page, pageSize, count } = pageInfo || {};
|
||||
const noResults = Boolean(query && data?.length === 0);
|
||||
|
||||
const handleChange = () => {
|
||||
onChange?.({ query, page });
|
||||
};
|
||||
|
||||
const handleSearch = value => {
|
||||
setQuery(value);
|
||||
handleChange();
|
||||
};
|
||||
|
||||
const handlePageChange = page => {
|
||||
setPageInfo(state => ({ ...state, page }));
|
||||
};
|
||||
|
||||
return (
|
||||
<DataTableContext.Provider value={dataTable}>
|
||||
{showSearch && (
|
||||
<SearchField
|
||||
className={styles.search}
|
||||
value={query}
|
||||
onChange={handleSearch}
|
||||
delay={searchDelay || DEFAULT_SEARCH_DELAY}
|
||||
autoFocus={true}
|
||||
placeholder={formatMessage(labels.search)}
|
||||
/>
|
||||
)}
|
||||
{noResults && <Empty message={formatMessage(messages.noResultsFound)} />}
|
||||
<div className={styles.body}>{children}</div>
|
||||
{showPaging && (
|
||||
<Pager
|
||||
className={styles.pager}
|
||||
page={page}
|
||||
pageSize={pageSize}
|
||||
count={count}
|
||||
onPageChange={handlePageChange}
|
||||
/>
|
||||
)}
|
||||
</DataTableContext.Provider>
|
||||
);
|
||||
}
|
||||
|
||||
export default DataTable;
|
17
src/components/common/DataTable.module.css
Normal file
17
src/components/common/DataTable.module.css
Normal file
@ -0,0 +1,17 @@
|
||||
.search {
|
||||
max-width: 300px;
|
||||
margin: 20px 0;
|
||||
}
|
||||
|
||||
.action {
|
||||
justify-content: flex-end;
|
||||
gap: 5px;
|
||||
}
|
||||
|
||||
.body td {
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.pager {
|
||||
margin-top: 20px;
|
||||
}
|
@ -1,14 +1,15 @@
|
||||
import styles from './Pager.module.css';
|
||||
import classNames from 'classnames';
|
||||
import { Button, Flexbox, Icon, Icons } from 'react-basics';
|
||||
import useMessages from 'components/hooks/useMessages';
|
||||
import styles from './Pager.module.css';
|
||||
|
||||
export function Pager({ page, pageSize, count, onPageChange }) {
|
||||
export function Pager({ page, pageSize, count, onPageChange, className }) {
|
||||
const { formatMessage, labels } = useMessages();
|
||||
const maxPage = Math.ceil(count / pageSize);
|
||||
const maxPage = pageSize && count ? Math.ceil(count / pageSize) : 0;
|
||||
const lastPage = page === maxPage;
|
||||
const firstPage = page === 1;
|
||||
|
||||
if (count === 0) {
|
||||
if (count === 0 || !maxPage) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@ -24,7 +25,7 @@ export function Pager({ page, pageSize, count, onPageChange }) {
|
||||
}
|
||||
|
||||
return (
|
||||
<Flexbox justifyContent="center" className={styles.container}>
|
||||
<Flexbox justifyContent="center" className={classNames(styles.container, className)}>
|
||||
<Button onClick={() => handlePageChange(-1)} disabled={firstPage}>
|
||||
<Icon rotate={90}>
|
||||
<Icons.ChevronDown />
|
||||
|
@ -1,7 +1,4 @@
|
||||
.container {
|
||||
margin-top: 20px;
|
||||
}
|
||||
|
||||
.text {
|
||||
font-size: var(--font-size-md);
|
||||
margin: 0 16px;
|
||||
}
|
||||
|
13
src/components/hooks/useDataTable.js
Normal file
13
src/components/hooks/useDataTable.js
Normal file
@ -0,0 +1,13 @@
|
||||
import { useState } from 'react';
|
||||
import { usePaging } from 'components/hooks/usePaging';
|
||||
|
||||
export function useDataTable(config = {}) {
|
||||
const { initialData, initialQuery, initialPageInfo } = config;
|
||||
const [data, setData] = useState(initialData ?? null);
|
||||
const [query, setQuery] = useState(initialQuery ?? '');
|
||||
const { pageInfo, setPageInfo } = usePaging(initialPageInfo);
|
||||
|
||||
return { data, setData, query, setQuery, pageInfo, setPageInfo };
|
||||
}
|
||||
|
||||
export default useDataTable;
|
9
src/components/hooks/usePaging.js
Normal file
9
src/components/hooks/usePaging.js
Normal file
@ -0,0 +1,9 @@
|
||||
import { useState } from 'react';
|
||||
|
||||
const DEFAULT_PAGE_INFO = { page: 1, pageSize: 10, total: 0 };
|
||||
|
||||
export function usePaging(initialPageInfo) {
|
||||
const [pageInfo, setPageInfo] = useState(initialPageInfo ?? { ...DEFAULT_PAGE_INFO });
|
||||
|
||||
return { pageInfo, setPageInfo };
|
||||
}
|
@ -13,17 +13,17 @@ export function EventsChart({ websiteId, className, token }) {
|
||||
const { locale } = useLocale();
|
||||
const [timezone] = useTimezone();
|
||||
const {
|
||||
query: { url, eventName },
|
||||
query: { url, event },
|
||||
} = usePageQuery();
|
||||
|
||||
const { data, isLoading } = useQuery(['events', websiteId, modified, eventName], () =>
|
||||
const { data, isLoading } = useQuery(['events', websiteId, modified, event], () =>
|
||||
get(`/websites/${websiteId}/events`, {
|
||||
startAt: +startDate,
|
||||
endAt: +endDate,
|
||||
unit,
|
||||
timezone,
|
||||
url,
|
||||
eventName,
|
||||
event,
|
||||
token,
|
||||
}),
|
||||
);
|
||||
|
@ -6,7 +6,7 @@ import classNames from 'classnames';
|
||||
import Empty from 'components/common/Empty';
|
||||
import { formatNumber, formatLongNumber } from 'lib/format';
|
||||
import useMessages from 'components/hooks/useMessages';
|
||||
import styles from './DataTable.module.css';
|
||||
import styles from './ListTable.module.css';
|
||||
|
||||
export function ListTable({
|
||||
data = [],
|
||||
|
@ -3,11 +3,11 @@ import PageHeader from 'components/layout/PageHeader';
|
||||
import WebsiteAddForm from 'components/pages/settings/websites/WebsiteAddForm';
|
||||
import WebsitesTable from 'components/pages/settings/websites/WebsitesTable';
|
||||
import useApi from 'components/hooks/useApi';
|
||||
import useApiFilter from 'components/hooks/useApiFilter';
|
||||
import useMessages from 'components/hooks/useMessages';
|
||||
import useUser from 'components/hooks/useUser';
|
||||
import { ROLES } from 'lib/constants';
|
||||
import { Button, Icon, Icons, Modal, ModalTrigger, Text, useToasts } from 'react-basics';
|
||||
import { useState } from 'react';
|
||||
|
||||
export function WebsitesList({
|
||||
showTeam,
|
||||
@ -15,28 +15,27 @@ export function WebsitesList({
|
||||
showHeader = true,
|
||||
includeTeams,
|
||||
onlyTeams,
|
||||
fetch,
|
||||
}) {
|
||||
const { formatMessage, labels, messages } = useMessages();
|
||||
const { user } = useUser();
|
||||
|
||||
const { filter, page, pageSize, handleFilterChange, handlePageChange, handlePageSizeChange } =
|
||||
useApiFilter();
|
||||
const [params, setParams] = useState({});
|
||||
const { get, useQuery } = useApi();
|
||||
const { data, isLoading, error, refetch } = useQuery(
|
||||
['websites', fetch, user?.id, filter, page, pageSize, includeTeams, onlyTeams],
|
||||
['websites', includeTeams, onlyTeams],
|
||||
() =>
|
||||
get(`/users/${user?.id}/websites`, {
|
||||
filter,
|
||||
page,
|
||||
pageSize,
|
||||
includeTeams,
|
||||
onlyTeams,
|
||||
...params,
|
||||
}),
|
||||
{ enabled: !!user },
|
||||
);
|
||||
const { showToast } = useToasts();
|
||||
|
||||
const handleChange = params => {
|
||||
setParams(params);
|
||||
};
|
||||
|
||||
const handleSave = async () => {
|
||||
await refetch();
|
||||
showToast({ message: formatMessage(messages.saved), variant: 'success' });
|
||||
@ -67,10 +66,7 @@ export function WebsitesList({
|
||||
data={data}
|
||||
showTeam={showTeam}
|
||||
showEditButton={showEditButton}
|
||||
onFilterChange={handleFilterChange}
|
||||
onPageChange={handlePageChange}
|
||||
onPageSizeChange={handlePageSizeChange}
|
||||
filterValue={filter}
|
||||
onChange={handleChange}
|
||||
/>
|
||||
</Page>
|
||||
);
|
||||
|
@ -1,11 +1,78 @@
|
||||
import Link from 'next/link';
|
||||
import { Button, Text, Icon, Icons } from 'react-basics';
|
||||
import { Button, Text, Icon, Icons, GridTable, GridColumn } from 'react-basics';
|
||||
import SettingsTable from 'components/common/SettingsTable';
|
||||
import Empty from 'components/common/Empty';
|
||||
import useMessages from 'components/hooks/useMessages';
|
||||
import useUser from 'components/hooks/useUser';
|
||||
import DataTable, { DataTableStyles } from 'components/common/DataTable';
|
||||
|
||||
export function WebsitesTable({
|
||||
data = [],
|
||||
filterValue,
|
||||
showTeam,
|
||||
showEditButton,
|
||||
openExternal = false,
|
||||
onChange,
|
||||
}) {
|
||||
const { formatMessage, labels } = useMessages();
|
||||
const { user } = useUser();
|
||||
|
||||
const showTable = data && (filterValue || data?.data?.length !== 0);
|
||||
|
||||
return (
|
||||
<DataTable onChange={onChange}>
|
||||
{showTable && (
|
||||
<GridTable data={data?.data}>
|
||||
<GridColumn name="name" label={formatMessage(labels.name)} />
|
||||
<GridColumn name="domain" label={formatMessage(labels.domain)} />
|
||||
{showTeam && (
|
||||
<GridColumn name="teamName" label={formatMessage(labels.teamName)}>
|
||||
{row => row.teamWebsite[0]?.team.name}
|
||||
</GridColumn>
|
||||
)}
|
||||
{showTeam && (
|
||||
<GridColumn name="owner" label={formatMessage(labels.owner)}>
|
||||
{row => row.user.username}
|
||||
</GridColumn>
|
||||
)}
|
||||
<GridColumn name="action" label=" " className={DataTableStyles.action}>
|
||||
{row => {
|
||||
const {
|
||||
id,
|
||||
user: { id: ownerId },
|
||||
} = row;
|
||||
|
||||
return (
|
||||
<>
|
||||
{showEditButton && (!showTeam || ownerId === user.id) && (
|
||||
<Link href={`/settings/websites/${id}`}>
|
||||
<Button>
|
||||
<Icon>
|
||||
<Icons.Edit />
|
||||
</Icon>
|
||||
<Text>{formatMessage(labels.edit)}</Text>
|
||||
</Button>
|
||||
</Link>
|
||||
)}
|
||||
<Link href={`/websites/${id}`} target={openExternal ? '_blank' : null}>
|
||||
<Button>
|
||||
<Icon>
|
||||
<Icons.External />
|
||||
</Icon>
|
||||
<Text>{formatMessage(labels.view)}</Text>
|
||||
</Button>
|
||||
</Link>
|
||||
</>
|
||||
);
|
||||
}}
|
||||
</GridColumn>
|
||||
</GridTable>
|
||||
)}
|
||||
</DataTable>
|
||||
);
|
||||
}
|
||||
|
||||
export function WebsitesTable2({
|
||||
data = [],
|
||||
filterValue,
|
||||
onFilterChange,
|
||||
|
@ -18,16 +18,19 @@ import {
|
||||
useToasts,
|
||||
} from 'react-basics';
|
||||
|
||||
const TABS = {
|
||||
myWebsites: 'my-websites',
|
||||
teamWebsites: 'team-websites',
|
||||
};
|
||||
|
||||
export function WebsitesPage() {
|
||||
const { formatMessage, labels, messages } = useMessages();
|
||||
const [tab, setTab] = useState('my-websites');
|
||||
const [fetch, setFetch] = useState(1);
|
||||
const [tab, setTab] = useState(TABS.myWebsites);
|
||||
const { user } = useUser();
|
||||
const { showToast } = useToasts();
|
||||
const cloudMode = Boolean(process.env.cloudMode);
|
||||
|
||||
const handleSave = async () => {
|
||||
setFetch(fetch + 1);
|
||||
const handleSave = () => {
|
||||
showToast({ message: formatMessage(messages.saved), variant: 'success' });
|
||||
};
|
||||
|
||||
@ -53,18 +56,16 @@ export function WebsitesPage() {
|
||||
<Page>
|
||||
<PageHeader title={formatMessage(labels.websites)}>{!cloudMode && addButton}</PageHeader>
|
||||
<Tabs selectedKey={tab} onSelect={setTab} style={{ marginBottom: 30 }}>
|
||||
<Item key="my-websites">{formatMessage(labels.myWebsites)}</Item>
|
||||
<Item key="team-webaites">{formatMessage(labels.teamWebsites)}</Item>
|
||||
<Item key={TABS.myWebsites}>{formatMessage(labels.myWebsites)}</Item>
|
||||
<Item key={TABS.teamWebsites}>{formatMessage(labels.teamWebsites)}</Item>
|
||||
</Tabs>
|
||||
|
||||
{tab === 'my-websites' && (
|
||||
{tab === TABS.myWebsites && (
|
||||
<WebsiteList showEditButton={!cloudMode} showHeader={false} fetch={fetch} />
|
||||
)}
|
||||
{tab === 'team-webaites' && (
|
||||
{tab === TABS.teamWebsites && (
|
||||
<WebsiteList
|
||||
showEditButton={!cloudMode}
|
||||
showHeader={false}
|
||||
fetch={fetch}
|
||||
showTeam={true}
|
||||
onlyTeams={true}
|
||||
/>
|
||||
|
Loading…
Reference in New Issue
Block a user