feat: add filters to drafts (#1631)

* feat: add filters to drafts

Co-authored-by: Guilherme Diniz <guilhermedassumpcao@gmail.com>
This commit is contained in:
Tom Moor
2020-11-08 22:33:52 -08:00
committed by GitHub
parent 14e0ed8108
commit 4e7a1cd121
7 changed files with 184 additions and 28 deletions

View File

@ -1,5 +1,6 @@
// @flow
import ArrowKeyNavigation from "boundless-arrow-key-navigation";
import { isEqual } from "lodash";
import { observable, action } from "mobx";
import { observer } from "mobx-react";
import * as React from "react";
@ -40,6 +41,9 @@ class PaginatedList extends React.Component<Props> {
if (prevProps.fetch !== this.props.fetch) {
this.fetchResults();
}
if (!isEqual(prevProps.options, this.props.options)) {
this.fetchResults();
}
}
fetchResults = async () => {

View File

@ -69,7 +69,6 @@ class MainSidebar extends React.Component<Props> {
const { user, team } = auth;
if (!user || !team) return null;
const draftDocumentsCount = documents.drafts.length;
const can = policies.abilities(team.id);
return (
@ -123,8 +122,8 @@ class MainSidebar extends React.Component<Props> {
label={
<Drafts align="center">
Drafts
{draftDocumentsCount > 0 && (
<Bubble count={draftDocumentsCount} />
{documents.totalDrafts > 0 && (
<Bubble count={documents.totalDrafts} />
)}
</Drafts>
}

View File

@ -11,12 +11,11 @@ const H3 = styled.h3`
margin-top: 22px;
margin-bottom: 12px;
line-height: 1;
position: relative;
`;
const Underline = styled("span")`
position: relative;
top: 1px;
margin-top: -1px;
display: inline-block;
font-weight: 500;
font-size: 14px;

View File

@ -1,37 +1,107 @@
// @flow
import { observer, inject } from "mobx-react";
import { observable } from "mobx";
import { inject, observer } from "mobx-react";
import queryString from "query-string";
import * as React from "react";
import { type RouterHistory } from "react-router-dom";
import styled from "styled-components";
import DocumentsStore from "stores/DocumentsStore";
import CollectionFilter from "scenes/Search/components/CollectionFilter";
import DateFilter from "scenes/Search/components/DateFilter";
import Actions, { Action } from "components/Actions";
import CenteredContent from "components/CenteredContent";
import Empty from "components/Empty";
import Flex from "components/Flex";
import Heading from "components/Heading";
import InputSearch from "components/InputSearch";
import PageTitle from "components/PageTitle";
import PaginatedDocumentList from "components/PaginatedDocumentList";
import Subheading from "components/Subheading";
import NewDocumentMenu from "menus/NewDocumentMenu";
import { type LocationWithState } from "types";
type Props = {
type Props = {|
documents: DocumentsStore,
};
history: RouterHistory,
location: LocationWithState,
|};
@observer
class Drafts extends React.Component<Props> {
@observable params: URLSearchParams = new URLSearchParams(
this.props.location.search
);
componentDidUpdate(prevProps) {
if (prevProps.location.search !== this.props.location.search) {
this.handleQueryChange();
}
}
handleQueryChange = () => {
this.params = new URLSearchParams(this.props.location.search);
};
handleFilterChange = (search) => {
this.props.history.replace({
pathname: this.props.location.pathname,
search: queryString.stringify({
...queryString.parse(this.props.location.search),
...search,
}),
});
};
get collectionId() {
const id = this.params.get("collectionId");
return id ? id : undefined;
}
get dateFilter() {
const id = this.params.get("dateFilter");
return id ? id : undefined;
}
render() {
const { fetchDrafts, drafts } = this.props.documents;
const { drafts, fetchDrafts } = this.props.documents;
const isFiltered = this.collectionId || this.dateFilter;
const options = {
dateFilter: this.dateFilter,
collectionId: this.collectionId,
};
return (
<CenteredContent column auto>
<PageTitle title="Drafts" />
<Heading>Drafts</Heading>
<Subheading>
Documents
<Filters>
<CollectionFilter
collectionId={this.collectionId}
onSelect={(collectionId) =>
this.handleFilterChange({ collectionId })
}
/>
<DateFilter
dateFilter={this.dateFilter}
onSelect={(dateFilter) => this.handleFilterChange({ dateFilter })}
/>
</Filters>
</Subheading>
<PaginatedDocumentList
heading={<Subheading>Documents</Subheading>}
empty={<Empty>Youve not got any drafts at the moment.</Empty>}
empty={
<Empty>
{isFiltered
? "No documents found for your filters."
: "Youve not got any drafts at the moment."}
</Empty>
}
fetch={fetchDrafts}
documents={drafts}
showDraft={false}
documents={drafts(options)}
options={options}
showCollection
/>
@ -48,4 +118,17 @@ class Drafts extends React.Component<Props> {
}
}
const Filters = styled(Flex)`
opacity: 0.85;
transition: opacity 100ms ease-in-out;
position: absolute;
right: -8px;
bottom: 0;
padding: 0 0 6px;
&:hover {
opacity: 1;
}
`;
export default inject("documents")(Drafts);

View File

@ -3,6 +3,7 @@ import invariant from "invariant";
import { find, orderBy, filter, compact, omitBy } from "lodash";
import { observable, action, computed, runInAction } from "mobx";
import { MAX_TITLE_LENGTH } from "shared/constants";
import { subtractDate } from "shared/utils/date";
import naturalSort from "shared/utils/naturalSort";
import BaseStore from "stores/BaseStore";
import RootStore from "stores/RootStore";
@ -168,12 +169,31 @@ export default class DocumentsStore extends BaseStore<Document> {
}
@computed
get drafts(): Document[] {
return filter(
get totalDrafts(): number {
return this.drafts().length;
}
drafts = (options = {}): Document[] => {
let drafts = filter(
orderBy(this.all, "updatedAt", "desc"),
(doc) => !doc.publishedAt
);
}
if (options.dateFilter) {
drafts = filter(
drafts,
(draft) =>
new Date(draft.updatedAt) >=
subtractDate(new Date(), options.dateFilter)
);
}
if (options.collectionId) {
drafts = filter(drafts, { collectionId: options.collectionId });
}
return drafts;
};
@computed
get active(): ?Document {

View File

@ -1,6 +1,7 @@
// @flow
import Router from "koa-router";
import Sequelize from "sequelize";
import { subtractDate } from "../../shared/utils/date";
import documentImporter from "../commands/documentImporter";
import documentMover from "../commands/documentMover";
import { NotFoundError, InvalidRequestError } from "../errors";
@ -359,24 +360,51 @@ router.post("documents.starred", auth(), pagination(), async (ctx) => {
});
router.post("documents.drafts", auth(), pagination(), async (ctx) => {
let { sort = "updatedAt", direction } = ctx.body;
let { collectionId, dateFilter, sort = "updatedAt", direction } = ctx.body;
if (direction !== "ASC") direction = "DESC";
const user = ctx.state.user;
const collectionIds = await user.collectionIds();
if (collectionId) {
ctx.assertUuid(collectionId, "collectionId must be a UUID");
const collection = await Collection.scope({
method: ["withMembership", user.id],
}).findByPk(collectionId);
authorize(user, "read", collection);
}
const collectionIds = !!collectionId
? [collectionId]
: await user.collectionIds();
const whereConditions = {
userId: user.id,
collectionId: collectionIds,
publishedAt: { [Op.eq]: null },
updatedAt: undefined,
};
if (dateFilter) {
ctx.assertIn(
dateFilter,
["day", "week", "month", "year"],
"dateFilter must be one of day,week,month,year"
);
whereConditions.updatedAt = {
[Op.gte]: subtractDate(new Date(), dateFilter),
};
} else {
delete whereConditions.updatedAt;
}
const collectionScope = { method: ["withCollection", user.id] };
const viewScope = { method: ["withViews", user.id] };
const documents = await Document.scope(
"defaultScope",
collectionScope,
viewScope
collectionScope
).findAll({
where: {
userId: user.id,
collectionId: collectionIds,
publishedAt: { [Op.eq]: null },
},
where: whereConditions,
order: [[sort, direction]],
offset: ctx.state.pagination.offset,
limit: ctx.state.pagination.limit,

23
shared/utils/date.js Normal file
View File

@ -0,0 +1,23 @@
// @flow
import subDays from "date-fns/sub_days";
import subMonth from "date-fns/sub_months";
import subWeek from "date-fns/sub_weeks";
import subYear from "date-fns/sub_years";
export function subtractDate(
date: Date,
period: "day" | "week" | "month" | "year"
) {
switch (period) {
case "day":
return subDays(date, 1);
case "week":
return subWeek(date, 1);
case "month":
return subMonth(date, 1);
case "year":
return subYear(date, 1);
default:
return date;
}
}