diff --git a/server/router/api/v1/user_service_stats.go b/server/router/api/v1/user_service_stats.go index bac9d879..0dc9f370 100644 --- a/server/router/api/v1/user_service_stats.go +++ b/server/router/api/v1/user_service_stats.go @@ -29,11 +29,13 @@ func (s *APIV1Service) ListAllUserStats(ctx context.Context, _ *v1pb.ListAllUser return nil, errors.Wrap(err, "failed to get workspace memo related setting") } + normalStatus := store.Normal memoFind := &store.FindMemo{ // Exclude comments by default. ExcludeComments: true, ExcludeContent: true, VisibilityList: visibilities, + RowStatus: &normalStatus, } memos, err := s.Store.ListMemos(ctx, memoFind) if err != nil { @@ -92,21 +94,13 @@ func (s *APIV1Service) GetUserStats(ctx context.Context, request *v1pb.GetUserSt return nil, status.Errorf(codes.Internal, "failed to get user: %v", err) } - workspaceMemoRelatedSetting, err := s.Store.GetWorkspaceMemoRelatedSetting(ctx) - if err != nil { - return nil, errors.Wrap(err, "failed to get workspace memo related setting") - } - userStats := &v1pb.UserStats{ - Name: fmt.Sprintf("%s%d", UserNamePrefix, user.ID), - MemoDisplayTimestamps: []*timestamppb.Timestamp{}, - MemoTypeStats: &v1pb.UserStats_MemoTypeStats{}, - TagCount: map[string]int32{}, - } + normalStatus := store.Normal memoFind := &store.FindMemo{ // Exclude comments by default. ExcludeComments: true, ExcludeContent: true, CreatorID: &userID, + RowStatus: &normalStatus, } currentUser, err := s.GetCurrentUser(ctx) @@ -125,6 +119,17 @@ func (s *APIV1Service) GetUserStats(ctx context.Context, request *v1pb.GetUserSt if err != nil { return nil, status.Errorf(codes.Internal, "failed to list memos: %v", err) } + + workspaceMemoRelatedSetting, err := s.Store.GetWorkspaceMemoRelatedSetting(ctx) + if err != nil { + return nil, errors.Wrap(err, "failed to get workspace memo related setting") + } + userStats := &v1pb.UserStats{ + Name: fmt.Sprintf("%s%d", UserNamePrefix, user.ID), + MemoDisplayTimestamps: []*timestamppb.Timestamp{}, + MemoTypeStats: &v1pb.UserStats_MemoTypeStats{}, + TagCount: map[string]int32{}, + } for _, memo := range memos { displayTs := memo.CreatedTs if workspaceMemoRelatedSetting.DisplayWithUpdateTime { diff --git a/web/src/components/ExploreSidebar/ExploreSidebar.tsx b/web/src/components/ExploreSidebar/ExploreSidebar.tsx index 0265d73d..4c1af790 100644 --- a/web/src/components/ExploreSidebar/ExploreSidebar.tsx +++ b/web/src/components/ExploreSidebar/ExploreSidebar.tsx @@ -14,10 +14,10 @@ const ExploreSidebar = (props: Props) => { useDebounce( async () => { - userStatsStore.listUserStats(); + await userStatsStore.listUserStats(); }, 300, - [], + [userStatsStore.stateId], ); return ( diff --git a/web/src/components/HomeSidebar/HomeSidebar.tsx b/web/src/components/HomeSidebar/HomeSidebar.tsx index 6f99c188..a17eaf05 100644 --- a/web/src/components/HomeSidebar/HomeSidebar.tsx +++ b/web/src/components/HomeSidebar/HomeSidebar.tsx @@ -20,7 +20,7 @@ const HomeSidebar = (props: Props) => { await userStatsStore.listUserStats(currentUser.name); }, 300, - [memoList.size(), currentUser], + [memoList.size(), userStatsStore.stateId, currentUser], ); return ( diff --git a/web/src/components/HomeSidebar/TagsSection.tsx b/web/src/components/HomeSidebar/TagsSection.tsx index eda92789..d94ab55e 100644 --- a/web/src/components/HomeSidebar/TagsSection.tsx +++ b/web/src/components/HomeSidebar/TagsSection.tsx @@ -4,7 +4,6 @@ import { Edit3Icon, HashIcon, MoreVerticalIcon, TagsIcon, TrashIcon } from "luci import toast from "react-hot-toast"; import useLocalStorage from "react-use/lib/useLocalStorage"; import { memoServiceClient } from "@/grpcweb"; -import useCurrentUser from "@/hooks/useCurrentUser"; import { useMemoFilterStore, useUserStatsStore, useUserStatsTags } from "@/store/v1"; import { useTranslate } from "@/utils/i18n"; import showRenameTagDialog from "../RenameTagDialog"; @@ -17,7 +16,6 @@ interface Props { const TagsSection = (props: Props) => { const t = useTranslate(); - const currentUser = useCurrentUser(); const memoFilterStore = useMemoFilterStore(); const userStatsStore = useUserStatsStore(); const [treeMode, setTreeMode] = useLocalStorage("tag-view-as-tree", false); @@ -44,7 +42,7 @@ const TagsSection = (props: Props) => { parent: "memos/-", tag: tag, }); - await userStatsStore.listUserStats(currentUser.name); + userStatsStore.setStateId(); toast.success(t("message.deleted-successfully")); } }; diff --git a/web/src/components/MemoActionMenu.tsx b/web/src/components/MemoActionMenu.tsx index f8de39ff..90596b54 100644 --- a/web/src/components/MemoActionMenu.tsx +++ b/web/src/components/MemoActionMenu.tsx @@ -16,7 +16,7 @@ import toast from "react-hot-toast"; import { useLocation } from "react-router-dom"; import { markdownServiceClient } from "@/grpcweb"; import useNavigateTo from "@/hooks/useNavigateTo"; -import { useMemoStore } from "@/store/v1"; +import { useMemoStore, useUserStatsStore } from "@/store/v1"; import { State } from "@/types/proto/api/v1/common"; import { NodeType } from "@/types/proto/api/v1/markdown_service"; import { Memo } from "@/types/proto/api/v1/memo_service"; @@ -48,10 +48,16 @@ const MemoActionMenu = (props: Props) => { const location = useLocation(); const navigateTo = useNavigateTo(); const memoStore = useMemoStore(); + const userStatsStore = useUserStatsStore(); const isArchived = memo.state === State.ARCHIVED; const hasCompletedTaskList = checkHasCompletedTaskList(memo); const isInMemoDetailPage = location.pathname.startsWith(`/m/${memo.uid}`); + const memoUpdatedCallback = () => { + // Refresh user stats. + userStatsStore.setStateId(); + }; + const handleTogglePinMemoBtnClick = async () => { try { if (memo.pinned) { @@ -104,6 +110,7 @@ const MemoActionMenu = (props: Props) => { if (isInMemoDetailPage) { memo.state === State.ARCHIVED ? navigateTo("/") : navigateTo("/archived"); } + memoUpdatedCallback(); }; const handleCopyLink = () => { @@ -119,6 +126,7 @@ const MemoActionMenu = (props: Props) => { if (isInMemoDetailPage) { navigateTo("/"); } + memoUpdatedCallback(); } }; @@ -150,6 +158,7 @@ const MemoActionMenu = (props: Props) => { ["content"], ); toast.success(t("message.remove-completed-task-list-items-successfully")); + memoUpdatedCallback(); } }; @@ -180,7 +189,7 @@ const MemoActionMenu = (props: Props) => { {!readonly && ( <> {!isArchived && hasCompletedTaskList && ( - + {t("memo.remove-completed-task-list-items")} diff --git a/web/src/components/MemoEditor/index.tsx b/web/src/components/MemoEditor/index.tsx index 59ad1b57..6c3b51ca 100644 --- a/web/src/components/MemoEditor/index.tsx +++ b/web/src/components/MemoEditor/index.tsx @@ -334,6 +334,10 @@ const MemoEditor = (props: Props) => { updateMask.add("display_time"); memoPatch.displayTime = displayTime; } + if (updateMask.size === 0) { + toast.error("No changes detected"); + return; + } const memo = await memoStore.updateMemo(memoPatch, Array.from(updateMask)); if (onConfirm) { onConfirm(memo.name); diff --git a/web/src/components/MemoView.tsx b/web/src/components/MemoView.tsx index 3f51331a..389b2270 100644 --- a/web/src/components/MemoView.tsx +++ b/web/src/components/MemoView.tsx @@ -6,7 +6,7 @@ import { Link, useLocation } from "react-router-dom"; import useAsyncEffect from "@/hooks/useAsyncEffect"; import useCurrentUser from "@/hooks/useCurrentUser"; import useNavigateTo from "@/hooks/useNavigateTo"; -import { useUserStore, useWorkspaceSettingStore, useMemoStore } from "@/store/v1"; +import { useUserStore, useWorkspaceSettingStore, useMemoStore, useUserStatsStore } from "@/store/v1"; import { State } from "@/types/proto/api/v1/common"; import { MemoRelation_Type } from "@/types/proto/api/v1/memo_relation_service"; import { Memo, Visibility } from "@/types/proto/api/v1/memo_service"; @@ -47,6 +47,7 @@ const MemoView: React.FC = (props: Props) => { const user = useCurrentUser(); const memoStore = useMemoStore(); const workspaceSettingStore = useWorkspaceSettingStore(); + const userStatsStore = useUserStatsStore(); const [showEditor, setShowEditor] = useState(false); const [creator, setCreator] = useState(userStore.getUserByName(memo.creator)); const memoContainerRef = useRef(null); @@ -99,19 +100,20 @@ const MemoView: React.FC = (props: Props) => { } }, []); + const onEditorConfirm = () => { + setShowEditor(false); + userStatsStore.setStateId(); + }; + const onPinIconClick = async () => { - try { - if (memo.pinned) { - await memoStore.updateMemo( - { - name: memo.name, - pinned: false, - }, - ["pinned"], - ); - } - } catch (error) { - // do nth + if (memo.pinned) { + await memoStore.updateMemo( + { + name: memo.name, + pinned: false, + }, + ["pinned"], + ); } }; @@ -136,7 +138,7 @@ const MemoView: React.FC = (props: Props) => { className="border-none !p-0 -mb-2" cacheKey={`inline-memo-editor-${memo.name}`} memoName={memo.name} - onConfirm={() => setShowEditor(false)} + onConfirm={onEditorConfirm} onCancel={() => setShowEditor(false)} /> ) : ( diff --git a/web/src/components/RenameTagDialog.tsx b/web/src/components/RenameTagDialog.tsx index dbb1a89d..f5811531 100644 --- a/web/src/components/RenameTagDialog.tsx +++ b/web/src/components/RenameTagDialog.tsx @@ -4,7 +4,6 @@ import { XIcon } from "lucide-react"; import React, { useState } from "react"; import { toast } from "react-hot-toast"; import { memoServiceClient } from "@/grpcweb"; -import useCurrentUser from "@/hooks/useCurrentUser"; import useLoading from "@/hooks/useLoading"; import { useUserStatsStore } from "@/store/v1"; import { useTranslate } from "@/utils/i18n"; @@ -20,7 +19,6 @@ const RenameTagDialog: React.FC = (props: Props) => { const userStatsStore = useUserStatsStore(); const [newName, setNewName] = useState(tag); const requestState = useLoading(false); - const user = useCurrentUser(); const handleTagNameInputChange = (e: React.ChangeEvent) => { setNewName(e.target.value.trim()); @@ -43,7 +41,7 @@ const RenameTagDialog: React.FC = (props: Props) => { newTag: newName, }); toast.success("Rename tag successfully"); - userStatsStore.listUserStats(user.name); + userStatsStore.setStateId(); } catch (error: any) { console.error(error); toast.error(error.details); diff --git a/web/src/components/StatisticsView.tsx b/web/src/components/StatisticsView.tsx index 0ffff857..59f60fdd 100644 --- a/web/src/components/StatisticsView.tsx +++ b/web/src/components/StatisticsView.tsx @@ -34,7 +34,7 @@ const StatisticsView = () => { } setMemoTypeStats(memoTypeStats); setActivityStats(countBy(displayTimeList.map((date) => dayjs(date).format("YYYY-MM-DD")))); - }, [userStatsStore.stateId]); + }, [userStatsStore.userStatsByName, userStatsStore.stateId]); const onCalendarClick = (date: string) => { memoFilterStore.removeFilter((f) => f.factor === "displayTime"); diff --git a/web/src/store/v1/userStats.ts b/web/src/store/v1/userStats.ts index 8f48c1d8..486662d5 100644 --- a/web/src/store/v1/userStats.ts +++ b/web/src/store/v1/userStats.ts @@ -31,7 +31,10 @@ export const useUserStatsStore = create( const userStats = await userServiceClient.getUserStats({ name: user }); userStatsByName[user] = userStats; } - set({ stateId: uniqueId(), userStatsByName }); + set({ ...get(), userStatsByName }); + }, + setStateId: (id = uniqueId()) => { + set({ ...get(), stateId: id }); }, })), );