Skip to content

Commit

Permalink
feat: implement memo filters
Browse files Browse the repository at this point in the history
  • Loading branch information
boojack committed Jul 25, 2024
1 parent b3b4aa9 commit cd38ec9
Show file tree
Hide file tree
Showing 14 changed files with 175 additions and 141 deletions.
15 changes: 9 additions & 6 deletions web/src/components/MemoContent/Tag.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import clsx from "clsx";
import { useContext } from "react";
import { useFilterStore } from "@/store/module";
import { useMemoFilterStore } from "@/store/v1";
import { RendererContext } from "./types";

interface Props {
Expand All @@ -9,18 +9,21 @@ interface Props {

const Tag: React.FC<Props> = ({ content }: Props) => {
const context = useContext(RendererContext);
const filterStore = useFilterStore();
const memoFilterStore = useMemoFilterStore();

const handleTagClick = () => {
if (context.disableFilter) {
return;
}

const currTagQuery = filterStore.getState().tag;
if (currTagQuery === content) {
filterStore.setTagFilter(undefined);
const isActive = memoFilterStore.getFiltersByFactor("tag").some((filter) => filter.value === content);
if (isActive) {
memoFilterStore.removeFilter((f) => f.factor === "tag" && f.value === content);
} else {
filterStore.setTagFilter(content);
memoFilterStore.addFilter({
factor: "tag",
value: content,
});
}
};

Expand Down
40 changes: 40 additions & 0 deletions web/src/components/MemoFilters.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
import { isEqual } from "lodash-es";
import { useMemoFilterStore } from "@/store/v1";
import Icon from "./Icon";

const MemoFilters = () => {
const memoFilterStore = useMemoFilterStore();
const filters = memoFilterStore.filters;

if (filters.length === 0) {
return undefined;
}

return (
<div className="w-full mb-2 flex flex-row justify-start items-start gap-2">
<span className="flex flex-row items-center gap-0.5 text-gray-500 text-sm leading-6">
<Icon.Filter className="w-4 h-auto opacity-60 inline" />
Filters
</span>
<div className="flex flex-row justify-start items-center flex-wrap gap-2 leading-6">
{filters.map((filter) => (
<div
key={filter.factor}
className="flex flex-row items-center gap-1 bg-gray-100 dark:bg-zinc-800 border dark:border-zinc-700 px-1 rounded-md"
>
<span className="text-gray-600 dark:text-gray-500 text-sm">{filter.factor}</span>
{filter.value && <span className="text-gray-500 dark:text-gray-400 text-sm max-w-12 truncate">{filter.value}</span>}
<button
onClick={() => memoFilterStore.removeFilter((f) => isEqual(f, filter))}
className="text-gray-500 dark:text-gray-300 opacity-60 hover:opacity-100"
>
<Icon.X className="w-3 h-auto" />
</button>
</div>
))}
</div>
</div>
);
};

export default MemoFilters;
3 changes: 0 additions & 3 deletions web/src/components/RenameTagDialog.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ import { toast } from "react-hot-toast";
import { memoServiceClient } from "@/grpcweb";
import useCurrentUser from "@/hooks/useCurrentUser";
import useLoading from "@/hooks/useLoading";
import { useFilterStore } from "@/store/module";
import { useTagStore } from "@/store/v1";
import { useTranslate } from "@/utils/i18n";
import { generateDialog } from "./Dialog";
Expand All @@ -18,7 +17,6 @@ const RenameTagDialog: React.FC<Props> = (props: Props) => {
const { tag, destroy } = props;
const t = useTranslate();
const tagStore = useTagStore();
const filterStore = useFilterStore();
const [newName, setNewName] = useState(tag);
const requestState = useLoading(false);
const user = useCurrentUser();
Expand All @@ -44,7 +42,6 @@ const RenameTagDialog: React.FC<Props> = (props: Props) => {
newTag: newName,
});
toast.success("Rename tag successfully");
filterStore.setTagFilter(newName);
tagStore.fetchTags({ user }, { skipCache: true });
} catch (error: any) {
console.error(error);
Expand Down
38 changes: 19 additions & 19 deletions web/src/components/SearchBar.tsx
Original file line number Diff line number Diff line change
@@ -1,39 +1,39 @@
import { useEffect, useState } from "react";
import useDebounce from "react-use/lib/useDebounce";
import { useFilterStore } from "@/store/module";
import { useState } from "react";
import { useMemoFilterStore } from "@/store/v1";
import { useTranslate } from "@/utils/i18n";
import Icon from "./Icon";

const SearchBar = () => {
const t = useTranslate();
const filterStore = useFilterStore();
const memoFilterStore = useMemoFilterStore();
const [queryText, setQueryText] = useState("");

useEffect(() => {
const text = filterStore.getState().text;
setQueryText(text === undefined ? "" : text);
}, [filterStore.state.text]);

useDebounce(
() => {
filterStore.setTextFilter(queryText.length === 0 ? undefined : queryText);
},
1000,
[queryText],
);

const handleTextQueryInput = (event: React.FormEvent<HTMLInputElement>) => {
const onTextChange = (event: React.FormEvent<HTMLInputElement>) => {
setQueryText(event.currentTarget.value);
};

const onKeyDown = (e: React.KeyboardEvent<HTMLInputElement>) => {
if (e.key === "Enter") {
e.preventDefault();
if (queryText !== "") {
memoFilterStore.removeFilter((f) => f.factor === "contentSearch");
memoFilterStore.addFilter({
factor: "contentSearch",
value: queryText,
});
}
}
};

return (
<div className="relative w-full h-auto flex flex-row justify-start items-center">
<Icon.Search className="absolute left-3 w-4 h-auto opacity-30" />
<input
className="w-full text-gray-500 dark:text-gray-400 bg-zinc-50 dark:bg-zinc-900 border dark:border-zinc-800 text-sm leading-7 rounded-lg p-1 pl-8 outline-none"
placeholder={t("memo.search-placeholder")}
value={queryText}
onChange={handleTextQueryInput}
onChange={onTextChange}
onKeyDown={onKeyDown}
/>
</div>
);
Expand Down
23 changes: 12 additions & 11 deletions web/src/components/TagTree.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { useEffect, useState } from "react";
import useToggle from "react-use/lib/useToggle";
import { useFilterStore } from "@/store/module";
import { useMemoFilterStore } from "@/store/v1";
import Icon from "./Icon";

interface Tag {
Expand All @@ -14,8 +14,6 @@ interface Props {
}

const TagTree = ({ tags: rawTags }: Props) => {
const filterStore = useFilterStore();
const filter = filterStore.state;
const [tags, setTags] = useState<Tag[]>([]);

useEffect(() => {
Expand Down Expand Up @@ -67,29 +65,32 @@ const TagTree = ({ tags: rawTags }: Props) => {
return (
<div className="flex flex-col justify-start items-start relative w-full h-auto flex-nowrap gap-2 mt-1">
{tags.map((t, idx) => (
<TagItemContainer key={t.text + "-" + idx} tag={t} tagQuery={filter.tag} />
<TagItemContainer key={t.text + "-" + idx} tag={t} />
))}
</div>
);
};

interface TagItemContainerProps {
tag: Tag;
tagQuery?: string;
}

const TagItemContainer: React.FC<TagItemContainerProps> = (props: TagItemContainerProps) => {
const filterStore = useFilterStore();
const { tag, tagQuery } = props;
const isActive = tagQuery === tag.text;
const { tag } = props;
const memoFilterStore = useMemoFilterStore();
const tagFilters = memoFilterStore.getFiltersByFactor("tag");
const isActive = tagFilters.some((f) => f.value === tag.text);
const hasSubTags = tag.subTags.length > 0;
const [showSubTags, toggleSubTags] = useToggle(false);

const handleTagClick = () => {
if (isActive) {
filterStore.setTagFilter(undefined);
memoFilterStore.removeFilter((f) => f.factor === "tag" && f.value === tag.text);
} else {
filterStore.setTagFilter(tag.text);
memoFilterStore.addFilter({
factor: "tag",
value: tag.text,
});
}
};

Expand Down Expand Up @@ -131,7 +132,7 @@ const TagItemContainer: React.FC<TagItemContainerProps> = (props: TagItemContain
}`}
>
{tag.subTags.map((st, idx) => (
<TagItemContainer key={st.text + "-" + idx} tag={st} tagQuery={tagQuery} />
<TagItemContainer key={st.text + "-" + idx} tag={st} />
))}
</div>
) : null}
Expand Down
15 changes: 5 additions & 10 deletions web/src/components/UserStatisticsView.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,7 @@ import { memoServiceClient } from "@/grpcweb";
import useAsyncEffect from "@/hooks/useAsyncEffect";
import useCurrentUser from "@/hooks/useCurrentUser";
import i18n from "@/i18n";
import { useFilterStore } from "@/store/module";
import { useMemoStore } from "@/store/v1";
import { useMemoFilterStore, useMemoStore } from "@/store/v1";
import { useTranslate } from "@/utils/i18n";
import ActivityCalendar from "./ActivityCalendar";
import Icon from "./Icon";
Expand All @@ -25,14 +24,13 @@ const UserStatisticsView = () => {
const t = useTranslate();
const currentUser = useCurrentUser();
const memoStore = useMemoStore();
const filterStore = useFilterStore();
const memoFilterStore = useMemoFilterStore();
const [memoAmount, setMemoAmount] = useState(0);
const [memoStats, setMemoStats] = useState<UserMemoStats>({ link: 0, taskList: 0, code: 0, incompleteTasks: 0 });
const [activityStats, setActivityStats] = useState<Record<string, number>>({});
const [selectedDate] = useState(new Date());
const [monthString, setMonthString] = useState(dayjs(selectedDate.toDateString()).format("YYYY-MM"));
const days = Math.ceil((Date.now() - currentUser.createTime!.getTime()) / 86400000);
const filter = filterStore.state;

useAsyncEffect(async () => {
const { properties } = await memoServiceClient.listMemoProperties({
Expand Down Expand Up @@ -120,9 +118,8 @@ const UserStatisticsView = () => {
<div
className={clsx(
"w-auto border dark:border-zinc-800 pl-1 pr-1.5 rounded-md flex justify-between items-center cursor-pointer hover:shadow",
filter.memoPropertyFilter?.hasLink ? "bg-blue-50 dark:bg-blue-900 shadow" : "",
)}
onClick={() => filterStore.setMemoPropertyFilter({ hasLink: !filter.memoPropertyFilter?.hasLink })}
onClick={() => memoFilterStore.addFilter({ factor: "property.hasLink", value: "" })}
>
<div className="w-auto flex justify-start items-center mr-1">
<Icon.Link className="w-4 h-auto mr-1" />
Expand All @@ -133,9 +130,8 @@ const UserStatisticsView = () => {
<div
className={clsx(
"w-auto border dark:border-zinc-800 pl-1 pr-1.5 rounded-md flex justify-between items-center cursor-pointer hover:shadow",
filter.memoPropertyFilter?.hasTaskList ? "bg-blue-50 dark:bg-blue-900 shadow" : "",
)}
onClick={() => filterStore.setMemoPropertyFilter({ hasTaskList: !filter.memoPropertyFilter?.hasTaskList })}
onClick={() => memoFilterStore.addFilter({ factor: "property.hasTaskList", value: "" })}
>
<div className="w-auto flex justify-start items-center mr-1">
{memoStats.incompleteTasks > 0 ? (
Expand All @@ -160,9 +156,8 @@ const UserStatisticsView = () => {
<div
className={clsx(
"w-auto border dark:border-zinc-800 pl-1 pr-1.5 rounded-md flex justify-between items-center cursor-pointer hover:shadow",
filter.memoPropertyFilter?.hasCode ? "bg-blue-50 dark:bg-blue-900 shadow" : "",
)}
onClick={() => filterStore.setMemoPropertyFilter({ hasCode: !filter.memoPropertyFilter?.hasCode })}
onClick={() => memoFilterStore.addFilter({ factor: "property.hasCode", value: "" })}
>
<div className="w-auto flex justify-start items-center mr-1">
<Icon.Code2 className="w-4 h-auto mr-1" />
Expand Down
1 change: 0 additions & 1 deletion web/src/hooks/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,5 +2,4 @@ export * from "./useLoading";
export * from "./useCurrentUser";
export * from "./useNavigateTo";
export * from "./useAsyncEffect";
export * from "./useFilterWithUrlParams";
export * from "./useResponsiveWidth";
45 changes: 0 additions & 45 deletions web/src/hooks/useFilterWithUrlParams.ts

This file was deleted.

20 changes: 11 additions & 9 deletions web/src/pages/Archived.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,13 @@ import toast from "react-hot-toast";
import Empty from "@/components/Empty";
import Icon from "@/components/Icon";
import MemoContent from "@/components/MemoContent";
import MemoFilters from "@/components/MemoFilters";
import MobileHeader from "@/components/MobileHeader";
import SearchBar from "@/components/SearchBar";
import { DEFAULT_LIST_MEMOS_PAGE_SIZE } from "@/helpers/consts";
import { getTimeStampByDate } from "@/helpers/datetime";
import useCurrentUser from "@/hooks/useCurrentUser";
import useFilterWithUrlParams from "@/hooks/useFilterWithUrlParams";
import { useMemoList, useMemoStore } from "@/store/v1";
import { useMemoFilterStore, useMemoList, useMemoStore } from "@/store/v1";
import { RowStatus } from "@/types/proto/api/v1/common";
import { Memo } from "@/types/proto/api/v1/memo_service";
import { useTranslate } from "@/utils/i18n";
Expand All @@ -21,31 +21,32 @@ const Archived = () => {
const user = useCurrentUser();
const memoStore = useMemoStore();
const memoList = useMemoList();
const memoFilterStore = useMemoFilterStore();
const [isRequesting, setIsRequesting] = useState(true);
const [nextPageToken, setNextPageToken] = useState<string>("");
const { tag: tagQuery, text: textQuery } = useFilterWithUrlParams();
const sortedMemos = memoList.value
.filter((memo) => memo.rowStatus === RowStatus.ARCHIVED)
.sort((a, b) => getTimeStampByDate(b.displayTime) - getTimeStampByDate(a.displayTime));

useEffect(() => {
memoList.reset();
fetchMemos("");
}, [tagQuery, textQuery]);
}, [memoFilterStore.filters]);

const fetchMemos = async (nextPageToken: string) => {
setIsRequesting(true);
const filters = [`creator == "${user.name}"`, `row_status == "ARCHIVED"`];
const contentSearch: string[] = [];
if (textQuery) {
contentSearch.push(JSON.stringify(textQuery));
for (const filter of memoFilterStore.filters) {
if (filter.factor === "contentSearch") {
contentSearch.push(`"${filter.value}"`);
} else if (filter.factor === "tag") {
filters.push(`tag == "${filter.value}"`);
}
}
if (contentSearch.length > 0) {
filters.push(`content_search == [${contentSearch.join(", ")}]`);
}
if (tagQuery) {
filters.push(`tag == "${tagQuery}"`);
}
const response = await memoStore.fetchMemos({
pageSize: DEFAULT_LIST_MEMOS_PAGE_SIZE,
filter: filters.join(" && "),
Expand Down Expand Up @@ -92,6 +93,7 @@ const Archived = () => {
<SearchBar />
</div>
</div>
<MemoFilters />
{sortedMemos.map((memo) => (
<div
key={memo.name}
Expand Down
Loading

0 comments on commit cd38ec9

Please sign in to comment.