Refactored website ordering feature.

This commit is contained in:
Mike Cao 2022-08-04 03:56:30 -07:00
parent 62dce0a8d1
commit 1d4aa7c535
96 changed files with 518 additions and 174 deletions

View file

@ -1,5 +1,5 @@
import { useState } from 'react';
import { FormattedMessage } from 'react-intl';
import { defineMessages, useIntl } from 'react-intl';
import { useRouter } from 'next/router';
import Page from 'components/layout/Page';
import PageHeader from 'components/layout/PageHeader';
@ -7,19 +7,24 @@ import WebsiteList from 'components/pages/WebsiteList';
import Button from 'components/common/Button';
import DashboardSettingsButton from 'components/settings/DashboardSettingsButton';
import useFetch from 'hooks/useFetch';
import useStore from 'store/app';
import useDashboard from 'store/dashboard';
import DashboardEdit from './DashboardEdit';
import styles from './WebsiteList.module.css';
const selector = state => state.dashboard;
const messages = defineMessages({
dashboard: { id: 'label.dashboard', defaultMessage: 'Dashboard' },
more: { id: 'label.more', defaultMessage: 'More' },
});
export default function Dashboard() {
const router = useRouter();
const { id } = router.query;
const userId = id?.[0];
const store = useStore(selector);
const { showCharts, limit } = store;
const dashboard = useDashboard();
const { showCharts, limit, editing } = dashboard;
const [max, setMax] = useState(limit);
const { data } = useFetch('/websites', { params: { user_id: userId } });
const { formatMessage } = useIntl();
function handleMore() {
setMax(max + limit);
@ -32,15 +37,14 @@ export default function Dashboard() {
return (
<Page>
<PageHeader>
<div>
<FormattedMessage id="label.dashboard" defaultMessage="Dashboard" />
</div>
<DashboardSettingsButton />
<div>{formatMessage(messages.dashboard)}</div>
{!editing && <DashboardSettingsButton />}
</PageHeader>
<WebsiteList websites={data} showCharts={showCharts} limit={max} />
{editing && <DashboardEdit data={data} />}
{!editing && <WebsiteList data={data} showCharts={showCharts} limit={max} />}
{max < data.length && (
<Button className={styles.button} onClick={handleMore}>
<FormattedMessage id="label.more" defaultMessage="More" />
{formatMessage(messages.more)}
</Button>
)}
</Page>

View file

@ -0,0 +1,100 @@
import { useState } from 'react';
import { defineMessages, useIntl } from 'react-intl';
import { DragDropContext, Draggable, Droppable } from 'react-beautiful-dnd';
import useDashboard, { saveDashboard } from 'store/dashboard';
import Button from 'components/common/Button';
import { useMemo } from 'react';
import { orderByWebsiteMap } from 'lib/format';
import styles from './DashboardEdit.module.css';
const messages = defineMessages({
save: { id: 'label.save', defaultMessage: 'Save' },
reset: { id: 'label.reset', defaultMessage: 'Reset' },
cancel: { id: 'label.cancel', defaultMessage: 'Cancel' },
});
const dragId = 'dashboard-website-ordering';
export default function DashboardEdit({ data: websites }) {
const settings = useDashboard();
const { websiteOrder } = settings;
const { formatMessage } = useIntl();
const [order, setOrder] = useState(websiteOrder);
const ordered = useMemo(() => orderByWebsiteMap(websites, order), [websites, order]);
console.log({ order, ordered });
function handleWebsiteDrag({ destination, source }) {
if (!destination || destination.index === source.index) return;
const orderedWebsites = [...ordered];
const [removed] = orderedWebsites.splice(source.index, 1);
orderedWebsites.splice(destination.index, 0, removed);
setOrder(
orderedWebsites.map((i, k) => ({ [i.website_uuid]: k })).reduce((a, b) => ({ ...a, ...b })),
);
}
function handleSave() {
saveDashboard({
editing: false,
websiteOrder: order,
});
}
function handleCancel() {
saveDashboard({ editing: false });
}
function handleReset() {
setOrder({});
saveDashboard({ websiteOrder: {} });
}
return (
<>
<div className={styles.buttons}>
<Button onClick={handleSave} size="small">
{formatMessage(messages.save)}
</Button>
<Button onClick={handleCancel} size="small">
{formatMessage(messages.cancel)}
</Button>
<Button onClick={handleReset} size="small">
{formatMessage(messages.reset)}
</Button>
</div>
<div className={styles.dragActive}>
<DragDropContext onDragEnd={handleWebsiteDrag}>
<Droppable droppableId={dragId}>
{(provided, snapshot) => (
<div
{...provided.droppableProps}
ref={provided.innerRef}
style={{ marginBottom: snapshot.isDraggingOver ? 260 : null }}
>
{ordered.map(({ website_id, name, domain }, index) => (
<Draggable key={website_id} draggableId={`${dragId}-${website_id}`} index={index}>
{provided => (
<div
ref={provided.innerRef}
{...provided.draggableProps}
{...provided.dragHandleProps}
className={styles.item}
>
<h1>{name}</h1>
<h2>{domain}</h2>
</div>
)}
</Draggable>
))}
</div>
)}
</Droppable>
</DragDropContext>
</div>
</>
);
}

View file

@ -0,0 +1,34 @@
.buttons {
display: flex;
justify-content: flex-end;
gap: 10px;
margin-bottom: 20px;
}
.item {
padding: 20px;
border-radius: 5px;
border: 1px solid var(--gray300);
}
.item + .item {
margin-top: 10px;
}
.item h1 {
font-weight: 600;
font-size: 16px;
}
.item h2 {
font-size: 14px;
color: var(--gray700);
}
.dragActive {
cursor: grab;
}
.dragActive:active {
cursor: grabbing;
}

View file

@ -1,4 +1,3 @@
import { DragDropContext, Draggable, Droppable } from 'react-beautiful-dnd';
import { FormattedMessage } from 'react-intl';
import Link from 'components/common/Link';
import WebsiteChart from 'components/metrics/WebsiteChart';
@ -6,37 +5,14 @@ import Page from 'components/layout/Page';
import EmptyPlaceholder from 'components/common/EmptyPlaceholder';
import Arrow from 'assets/arrow-right.svg';
import styles from './WebsiteList.module.css';
import useDashboard from 'store/dashboard';
import { orderByWebsiteMap } from 'lib/format';
import { useMemo } from 'react';
import useStore, { setDashboard } from 'store/app';
const selector = state => state.dashboard;
export default function WebsiteList({ data, showCharts, limit }) {
const { websiteOrder } = useDashboard();
export default function WebsiteList({ websites, showCharts, limit }) {
const store = useStore(selector);
const { websiteOrdering, changeOrderMode } = store;
const ordered = useMemo(
() => orderByWebsiteMap(websites, websiteOrdering),
[websites, websiteOrdering],
);
const dragId = 'dashboard-website-ordering';
function handleWebsiteDrag({ destination, source }) {
if (!destination || destination.index === source.index) return;
const orderedWebsites = [...ordered];
const [removed] = orderedWebsites.splice(source.index, 1);
orderedWebsites.splice(destination.index, 0, removed);
setDashboard({
...store,
websiteOrdering: orderedWebsites
.map((i, k) => ({ [i.website_uuid]: k }))
.reduce((a, b) => ({ ...a, ...b })),
});
}
const websites = useMemo(() => orderByWebsiteMap(data, websiteOrder), [data, websiteOrder]);
if (websites.length === 0) {
return (
@ -58,60 +34,19 @@ export default function WebsiteList({ websites, showCharts, limit }) {
}
return (
<div className={changeOrderMode && styles.websiteDragActive}>
{changeOrderMode ? (
<DragDropContext onDragEnd={handleWebsiteDrag}>
<Droppable droppableId={dragId}>
{(provided, snapshot) => (
<div
{...provided.droppableProps}
ref={provided.innerRef}
style={{ marginBottom: snapshot.isDraggingOver ? 260 : null }}
>
{ordered.map(({ website_id, name, domain }, index) =>
index < limit ? (
<Draggable
key={website_id}
draggableId={`${dragId}-${website_id}`}
index={index}
>
{provided => (
<div
ref={provided.innerRef}
{...provided.draggableProps}
{...provided.dragHandleProps}
className={styles.website}
>
<WebsiteChart
websiteId={website_id}
title={name}
domain={domain}
showChart={changeOrderMode ? false : showCharts}
showLink
/>
</div>
)}
</Draggable>
) : null,
)}
</div>
)}
</Droppable>
</DragDropContext>
) : (
ordered.map(({ website_id, name, domain }, index) =>
index < limit ? (
<div key={website_id} className={styles.website}>
<WebsiteChart
websiteId={website_id}
title={name}
domain={domain}
showChart={showCharts}
showLink
/>
</div>
) : null,
)
<div>
{websites.map(({ website_id, name, domain }, index) =>
index < limit ? (
<div key={website_id} className={styles.website}>
<WebsiteChart
websiteId={website_id}
title={name}
domain={domain}
showChart={showCharts}
showLink
/>
</div>
) : null,
)}
</div>
);

View file

@ -9,12 +9,3 @@
border-bottom: 0;
margin-bottom: 20px;
}
.websiteDragActive {
opacity: 0.6;
cursor: grab;
}
.websiteDragActive:active {
cursor: grabbing;
}