mirror of
https://github.com/usememos/memos.git
synced 2025-06-05 22:09:59 +02:00
chore: tweak user stats in frontend
This commit is contained in:
@@ -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")
|
return nil, errors.Wrap(err, "failed to get workspace memo related setting")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
normalStatus := store.Normal
|
||||||
memoFind := &store.FindMemo{
|
memoFind := &store.FindMemo{
|
||||||
// Exclude comments by default.
|
// Exclude comments by default.
|
||||||
ExcludeComments: true,
|
ExcludeComments: true,
|
||||||
ExcludeContent: true,
|
ExcludeContent: true,
|
||||||
VisibilityList: visibilities,
|
VisibilityList: visibilities,
|
||||||
|
RowStatus: &normalStatus,
|
||||||
}
|
}
|
||||||
memos, err := s.Store.ListMemos(ctx, memoFind)
|
memos, err := s.Store.ListMemos(ctx, memoFind)
|
||||||
if err != nil {
|
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)
|
return nil, status.Errorf(codes.Internal, "failed to get user: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
workspaceMemoRelatedSetting, err := s.Store.GetWorkspaceMemoRelatedSetting(ctx)
|
normalStatus := store.Normal
|
||||||
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{},
|
|
||||||
}
|
|
||||||
memoFind := &store.FindMemo{
|
memoFind := &store.FindMemo{
|
||||||
// Exclude comments by default.
|
// Exclude comments by default.
|
||||||
ExcludeComments: true,
|
ExcludeComments: true,
|
||||||
ExcludeContent: true,
|
ExcludeContent: true,
|
||||||
CreatorID: &userID,
|
CreatorID: &userID,
|
||||||
|
RowStatus: &normalStatus,
|
||||||
}
|
}
|
||||||
|
|
||||||
currentUser, err := s.GetCurrentUser(ctx)
|
currentUser, err := s.GetCurrentUser(ctx)
|
||||||
@@ -125,6 +119,17 @@ func (s *APIV1Service) GetUserStats(ctx context.Context, request *v1pb.GetUserSt
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, status.Errorf(codes.Internal, "failed to list memos: %v", err)
|
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 {
|
for _, memo := range memos {
|
||||||
displayTs := memo.CreatedTs
|
displayTs := memo.CreatedTs
|
||||||
if workspaceMemoRelatedSetting.DisplayWithUpdateTime {
|
if workspaceMemoRelatedSetting.DisplayWithUpdateTime {
|
||||||
|
@@ -14,10 +14,10 @@ const ExploreSidebar = (props: Props) => {
|
|||||||
|
|
||||||
useDebounce(
|
useDebounce(
|
||||||
async () => {
|
async () => {
|
||||||
userStatsStore.listUserStats();
|
await userStatsStore.listUserStats();
|
||||||
},
|
},
|
||||||
300,
|
300,
|
||||||
[],
|
[userStatsStore.stateId],
|
||||||
);
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
@@ -20,7 +20,7 @@ const HomeSidebar = (props: Props) => {
|
|||||||
await userStatsStore.listUserStats(currentUser.name);
|
await userStatsStore.listUserStats(currentUser.name);
|
||||||
},
|
},
|
||||||
300,
|
300,
|
||||||
[memoList.size(), currentUser],
|
[memoList.size(), userStatsStore.stateId, currentUser],
|
||||||
);
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
@@ -4,7 +4,6 @@ import { Edit3Icon, HashIcon, MoreVerticalIcon, TagsIcon, TrashIcon } from "luci
|
|||||||
import toast from "react-hot-toast";
|
import toast from "react-hot-toast";
|
||||||
import useLocalStorage from "react-use/lib/useLocalStorage";
|
import useLocalStorage from "react-use/lib/useLocalStorage";
|
||||||
import { memoServiceClient } from "@/grpcweb";
|
import { memoServiceClient } from "@/grpcweb";
|
||||||
import useCurrentUser from "@/hooks/useCurrentUser";
|
|
||||||
import { useMemoFilterStore, useUserStatsStore, useUserStatsTags } from "@/store/v1";
|
import { useMemoFilterStore, useUserStatsStore, useUserStatsTags } from "@/store/v1";
|
||||||
import { useTranslate } from "@/utils/i18n";
|
import { useTranslate } from "@/utils/i18n";
|
||||||
import showRenameTagDialog from "../RenameTagDialog";
|
import showRenameTagDialog from "../RenameTagDialog";
|
||||||
@@ -17,7 +16,6 @@ interface Props {
|
|||||||
|
|
||||||
const TagsSection = (props: Props) => {
|
const TagsSection = (props: Props) => {
|
||||||
const t = useTranslate();
|
const t = useTranslate();
|
||||||
const currentUser = useCurrentUser();
|
|
||||||
const memoFilterStore = useMemoFilterStore();
|
const memoFilterStore = useMemoFilterStore();
|
||||||
const userStatsStore = useUserStatsStore();
|
const userStatsStore = useUserStatsStore();
|
||||||
const [treeMode, setTreeMode] = useLocalStorage<boolean>("tag-view-as-tree", false);
|
const [treeMode, setTreeMode] = useLocalStorage<boolean>("tag-view-as-tree", false);
|
||||||
@@ -44,7 +42,7 @@ const TagsSection = (props: Props) => {
|
|||||||
parent: "memos/-",
|
parent: "memos/-",
|
||||||
tag: tag,
|
tag: tag,
|
||||||
});
|
});
|
||||||
await userStatsStore.listUserStats(currentUser.name);
|
userStatsStore.setStateId();
|
||||||
toast.success(t("message.deleted-successfully"));
|
toast.success(t("message.deleted-successfully"));
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
@@ -16,7 +16,7 @@ import toast from "react-hot-toast";
|
|||||||
import { useLocation } from "react-router-dom";
|
import { useLocation } from "react-router-dom";
|
||||||
import { markdownServiceClient } from "@/grpcweb";
|
import { markdownServiceClient } from "@/grpcweb";
|
||||||
import useNavigateTo from "@/hooks/useNavigateTo";
|
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 { State } from "@/types/proto/api/v1/common";
|
||||||
import { NodeType } from "@/types/proto/api/v1/markdown_service";
|
import { NodeType } from "@/types/proto/api/v1/markdown_service";
|
||||||
import { Memo } from "@/types/proto/api/v1/memo_service";
|
import { Memo } from "@/types/proto/api/v1/memo_service";
|
||||||
@@ -48,10 +48,16 @@ const MemoActionMenu = (props: Props) => {
|
|||||||
const location = useLocation();
|
const location = useLocation();
|
||||||
const navigateTo = useNavigateTo();
|
const navigateTo = useNavigateTo();
|
||||||
const memoStore = useMemoStore();
|
const memoStore = useMemoStore();
|
||||||
|
const userStatsStore = useUserStatsStore();
|
||||||
const isArchived = memo.state === State.ARCHIVED;
|
const isArchived = memo.state === State.ARCHIVED;
|
||||||
const hasCompletedTaskList = checkHasCompletedTaskList(memo);
|
const hasCompletedTaskList = checkHasCompletedTaskList(memo);
|
||||||
const isInMemoDetailPage = location.pathname.startsWith(`/m/${memo.uid}`);
|
const isInMemoDetailPage = location.pathname.startsWith(`/m/${memo.uid}`);
|
||||||
|
|
||||||
|
const memoUpdatedCallback = () => {
|
||||||
|
// Refresh user stats.
|
||||||
|
userStatsStore.setStateId();
|
||||||
|
};
|
||||||
|
|
||||||
const handleTogglePinMemoBtnClick = async () => {
|
const handleTogglePinMemoBtnClick = async () => {
|
||||||
try {
|
try {
|
||||||
if (memo.pinned) {
|
if (memo.pinned) {
|
||||||
@@ -104,6 +110,7 @@ const MemoActionMenu = (props: Props) => {
|
|||||||
if (isInMemoDetailPage) {
|
if (isInMemoDetailPage) {
|
||||||
memo.state === State.ARCHIVED ? navigateTo("/") : navigateTo("/archived");
|
memo.state === State.ARCHIVED ? navigateTo("/") : navigateTo("/archived");
|
||||||
}
|
}
|
||||||
|
memoUpdatedCallback();
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleCopyLink = () => {
|
const handleCopyLink = () => {
|
||||||
@@ -119,6 +126,7 @@ const MemoActionMenu = (props: Props) => {
|
|||||||
if (isInMemoDetailPage) {
|
if (isInMemoDetailPage) {
|
||||||
navigateTo("/");
|
navigateTo("/");
|
||||||
}
|
}
|
||||||
|
memoUpdatedCallback();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -150,6 +158,7 @@ const MemoActionMenu = (props: Props) => {
|
|||||||
["content"],
|
["content"],
|
||||||
);
|
);
|
||||||
toast.success(t("message.remove-completed-task-list-items-successfully"));
|
toast.success(t("message.remove-completed-task-list-items-successfully"));
|
||||||
|
memoUpdatedCallback();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -180,7 +189,7 @@ const MemoActionMenu = (props: Props) => {
|
|||||||
{!readonly && (
|
{!readonly && (
|
||||||
<>
|
<>
|
||||||
{!isArchived && hasCompletedTaskList && (
|
{!isArchived && hasCompletedTaskList && (
|
||||||
<MenuItem color="danger" onClick={handleRemoveCompletedTaskListItemsClick}>
|
<MenuItem color="warning" onClick={handleRemoveCompletedTaskListItemsClick}>
|
||||||
<SquareCheckIcon className="w-4 h-auto" />
|
<SquareCheckIcon className="w-4 h-auto" />
|
||||||
{t("memo.remove-completed-task-list-items")}
|
{t("memo.remove-completed-task-list-items")}
|
||||||
</MenuItem>
|
</MenuItem>
|
||||||
|
@@ -334,6 +334,10 @@ const MemoEditor = (props: Props) => {
|
|||||||
updateMask.add("display_time");
|
updateMask.add("display_time");
|
||||||
memoPatch.displayTime = displayTime;
|
memoPatch.displayTime = displayTime;
|
||||||
}
|
}
|
||||||
|
if (updateMask.size === 0) {
|
||||||
|
toast.error("No changes detected");
|
||||||
|
return;
|
||||||
|
}
|
||||||
const memo = await memoStore.updateMemo(memoPatch, Array.from(updateMask));
|
const memo = await memoStore.updateMemo(memoPatch, Array.from(updateMask));
|
||||||
if (onConfirm) {
|
if (onConfirm) {
|
||||||
onConfirm(memo.name);
|
onConfirm(memo.name);
|
||||||
|
@@ -6,7 +6,7 @@ import { Link, useLocation } from "react-router-dom";
|
|||||||
import useAsyncEffect from "@/hooks/useAsyncEffect";
|
import useAsyncEffect from "@/hooks/useAsyncEffect";
|
||||||
import useCurrentUser from "@/hooks/useCurrentUser";
|
import useCurrentUser from "@/hooks/useCurrentUser";
|
||||||
import useNavigateTo from "@/hooks/useNavigateTo";
|
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 { State } from "@/types/proto/api/v1/common";
|
||||||
import { MemoRelation_Type } from "@/types/proto/api/v1/memo_relation_service";
|
import { MemoRelation_Type } from "@/types/proto/api/v1/memo_relation_service";
|
||||||
import { Memo, Visibility } from "@/types/proto/api/v1/memo_service";
|
import { Memo, Visibility } from "@/types/proto/api/v1/memo_service";
|
||||||
@@ -47,6 +47,7 @@ const MemoView: React.FC<Props> = (props: Props) => {
|
|||||||
const user = useCurrentUser();
|
const user = useCurrentUser();
|
||||||
const memoStore = useMemoStore();
|
const memoStore = useMemoStore();
|
||||||
const workspaceSettingStore = useWorkspaceSettingStore();
|
const workspaceSettingStore = useWorkspaceSettingStore();
|
||||||
|
const userStatsStore = useUserStatsStore();
|
||||||
const [showEditor, setShowEditor] = useState<boolean>(false);
|
const [showEditor, setShowEditor] = useState<boolean>(false);
|
||||||
const [creator, setCreator] = useState(userStore.getUserByName(memo.creator));
|
const [creator, setCreator] = useState(userStore.getUserByName(memo.creator));
|
||||||
const memoContainerRef = useRef<HTMLDivElement>(null);
|
const memoContainerRef = useRef<HTMLDivElement>(null);
|
||||||
@@ -99,19 +100,20 @@ const MemoView: React.FC<Props> = (props: Props) => {
|
|||||||
}
|
}
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
|
const onEditorConfirm = () => {
|
||||||
|
setShowEditor(false);
|
||||||
|
userStatsStore.setStateId();
|
||||||
|
};
|
||||||
|
|
||||||
const onPinIconClick = async () => {
|
const onPinIconClick = async () => {
|
||||||
try {
|
if (memo.pinned) {
|
||||||
if (memo.pinned) {
|
await memoStore.updateMemo(
|
||||||
await memoStore.updateMemo(
|
{
|
||||||
{
|
name: memo.name,
|
||||||
name: memo.name,
|
pinned: false,
|
||||||
pinned: false,
|
},
|
||||||
},
|
["pinned"],
|
||||||
["pinned"],
|
);
|
||||||
);
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
// do nth
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -136,7 +138,7 @@ const MemoView: React.FC<Props> = (props: Props) => {
|
|||||||
className="border-none !p-0 -mb-2"
|
className="border-none !p-0 -mb-2"
|
||||||
cacheKey={`inline-memo-editor-${memo.name}`}
|
cacheKey={`inline-memo-editor-${memo.name}`}
|
||||||
memoName={memo.name}
|
memoName={memo.name}
|
||||||
onConfirm={() => setShowEditor(false)}
|
onConfirm={onEditorConfirm}
|
||||||
onCancel={() => setShowEditor(false)}
|
onCancel={() => setShowEditor(false)}
|
||||||
/>
|
/>
|
||||||
) : (
|
) : (
|
||||||
|
@@ -4,7 +4,6 @@ import { XIcon } from "lucide-react";
|
|||||||
import React, { useState } from "react";
|
import React, { useState } from "react";
|
||||||
import { toast } from "react-hot-toast";
|
import { toast } from "react-hot-toast";
|
||||||
import { memoServiceClient } from "@/grpcweb";
|
import { memoServiceClient } from "@/grpcweb";
|
||||||
import useCurrentUser from "@/hooks/useCurrentUser";
|
|
||||||
import useLoading from "@/hooks/useLoading";
|
import useLoading from "@/hooks/useLoading";
|
||||||
import { useUserStatsStore } from "@/store/v1";
|
import { useUserStatsStore } from "@/store/v1";
|
||||||
import { useTranslate } from "@/utils/i18n";
|
import { useTranslate } from "@/utils/i18n";
|
||||||
@@ -20,7 +19,6 @@ const RenameTagDialog: React.FC<Props> = (props: Props) => {
|
|||||||
const userStatsStore = useUserStatsStore();
|
const userStatsStore = useUserStatsStore();
|
||||||
const [newName, setNewName] = useState(tag);
|
const [newName, setNewName] = useState(tag);
|
||||||
const requestState = useLoading(false);
|
const requestState = useLoading(false);
|
||||||
const user = useCurrentUser();
|
|
||||||
|
|
||||||
const handleTagNameInputChange = (e: React.ChangeEvent<HTMLInputElement>) => {
|
const handleTagNameInputChange = (e: React.ChangeEvent<HTMLInputElement>) => {
|
||||||
setNewName(e.target.value.trim());
|
setNewName(e.target.value.trim());
|
||||||
@@ -43,7 +41,7 @@ const RenameTagDialog: React.FC<Props> = (props: Props) => {
|
|||||||
newTag: newName,
|
newTag: newName,
|
||||||
});
|
});
|
||||||
toast.success("Rename tag successfully");
|
toast.success("Rename tag successfully");
|
||||||
userStatsStore.listUserStats(user.name);
|
userStatsStore.setStateId();
|
||||||
} catch (error: any) {
|
} catch (error: any) {
|
||||||
console.error(error);
|
console.error(error);
|
||||||
toast.error(error.details);
|
toast.error(error.details);
|
||||||
|
@@ -34,7 +34,7 @@ const StatisticsView = () => {
|
|||||||
}
|
}
|
||||||
setMemoTypeStats(memoTypeStats);
|
setMemoTypeStats(memoTypeStats);
|
||||||
setActivityStats(countBy(displayTimeList.map((date) => dayjs(date).format("YYYY-MM-DD"))));
|
setActivityStats(countBy(displayTimeList.map((date) => dayjs(date).format("YYYY-MM-DD"))));
|
||||||
}, [userStatsStore.stateId]);
|
}, [userStatsStore.userStatsByName, userStatsStore.stateId]);
|
||||||
|
|
||||||
const onCalendarClick = (date: string) => {
|
const onCalendarClick = (date: string) => {
|
||||||
memoFilterStore.removeFilter((f) => f.factor === "displayTime");
|
memoFilterStore.removeFilter((f) => f.factor === "displayTime");
|
||||||
|
@@ -31,7 +31,10 @@ export const useUserStatsStore = create(
|
|||||||
const userStats = await userServiceClient.getUserStats({ name: user });
|
const userStats = await userServiceClient.getUserStats({ name: user });
|
||||||
userStatsByName[user] = userStats;
|
userStatsByName[user] = userStats;
|
||||||
}
|
}
|
||||||
set({ stateId: uniqueId(), userStatsByName });
|
set({ ...get(), userStatsByName });
|
||||||
|
},
|
||||||
|
setStateId: (id = uniqueId()) => {
|
||||||
|
set({ ...get(), stateId: id });
|
||||||
},
|
},
|
||||||
})),
|
})),
|
||||||
);
|
);
|
||||||
|
Reference in New Issue
Block a user