feat: update memo editor

This commit is contained in:
Steven
2024-07-22 09:57:40 +08:00
parent d2727e6825
commit c313596144
9 changed files with 227 additions and 327 deletions

View File

@ -18,11 +18,11 @@ const getCellAdditionalStyles = (count: number, maxCount: number) => {
const ratio = count / maxCount; const ratio = count / maxCount;
if (ratio > 0.7) { if (ratio > 0.7) {
return "bg-teal-600 text-gray-100 dark:opacity-80"; return "bg-teal-700 text-gray-100 dark:opacity-80";
} else if (ratio > 0.4) { } else if (ratio > 0.4) {
return "bg-teal-400 text-gray-100 dark:opacity-80"; return "bg-teal-600 text-gray-100 dark:opacity-80";
} else { } else {
return "bg-teal-300 text-gray-100 dark:opacity-80"; return "bg-teal-500 text-gray-100 dark:opacity-70";
} }
}; };
@ -58,15 +58,15 @@ const ActivityCalendar = (props: Props) => {
const tooltipText = count ? t("memo.count-memos-in-date", { count: count, date: date }) : date; const tooltipText = count ? t("memo.count-memos-in-date", { count: count, date: date }) : date;
const isSelected = new Date(props.selectedDate).toDateString() === new Date(date).toDateString(); const isSelected = new Date(props.selectedDate).toDateString() === new Date(date).toDateString();
return day ? ( return day ? (
count > 0 ? (
<Tooltip className="shrink-0" key={`${date}-${index}`} title={tooltipText} placement="top" arrow> <Tooltip className="shrink-0" key={`${date}-${index}`} title={tooltipText} placement="top" arrow>
<div <div
className={clsx( className={clsx(
"w-6 h-6 text-xs rounded-xl flex justify-center items-center border", "w-6 h-6 text-xs rounded-xl flex justify-center items-center border cursor-default",
getCellAdditionalStyles(count, maxCount), getCellAdditionalStyles(count, maxCount),
isToday && "border-gray-600 dark:border-zinc-300", isToday && "border-zinc-400 dark:border-zinc-300",
isSelected && "font-bold border-gray-600 dark:border-zinc-300", isSelected && "font-bold border-zinc-400 dark:border-zinc-300",
!isToday && !isSelected && "border-transparent", !isToday && !isSelected && "border-transparent",
count > 0 ? "cursor-pointer" : "cursor-default",
)} )}
onClick={() => count && onClick && onClick(new Date(date).toDateString())} onClick={() => count && onClick && onClick(new Date(date).toDateString())}
> >
@ -77,10 +77,17 @@ const ActivityCalendar = (props: Props) => {
<div <div
key={`${date}-${index}`} key={`${date}-${index}`}
className={clsx( className={clsx(
"shrink-0 opacity-30 w-6 h-6 rounded-xl flex justify-center items-center border border-transparent", "w-6 h-6 text-xs rounded-xl flex justify-center items-center border cursor-default",
getCellAdditionalStyles(count, maxCount), "bg-gray-100 text-gray-400 dark:bg-zinc-800 dark:text-gray-500",
isToday && "border-zinc-400 dark:border-zinc-500",
!isToday && !isSelected && "border-transparent",
)} )}
></div> >
{day}
</div>
)
) : (
<div key={`${date}-${index}`} className={clsx("shrink-0 w-6 h-6 opacity-0", getCellAdditionalStyles(count, maxCount))}></div>
); );
})} })}
</div> </div>

View File

@ -9,7 +9,6 @@ import { useMemoStore } from "@/store/v1";
import { RowStatus } from "@/types/proto/api/v1/common"; import { RowStatus } from "@/types/proto/api/v1/common";
import { Memo } from "@/types/proto/api/v1/memo_service"; import { Memo } from "@/types/proto/api/v1/memo_service";
import { useTranslate } from "@/utils/i18n"; import { useTranslate } from "@/utils/i18n";
import showMemoEditorDialog from "./MemoEditor/MemoEditorDialog";
interface Props { interface Props {
memo: Memo; memo: Memo;
@ -55,12 +54,6 @@ const MemoActionMenu = (props: Props) => {
props.onEdit(); props.onEdit();
return; return;
} }
// TODO: remove me later.
showMemoEditorDialog({
memoName: memo.name,
cacheKey: `${memo.name}-${memo.updateTime}`,
});
}; };
const handleToggleMemoStatusClick = async () => { const handleToggleMemoStatusClick = async () => {
@ -125,7 +118,7 @@ const MemoActionMenu = (props: Props) => {
{memo.pinned ? t("common.unpin") : t("common.pin")} {memo.pinned ? t("common.unpin") : t("common.pin")}
</MenuItem> </MenuItem>
)} )}
{!hiddenActions?.includes("edit") && ( {!hiddenActions?.includes("edit") && props.onEdit && (
<MenuItem onClick={handleEditMemoClick}> <MenuItem onClick={handleEditMemoClick}>
<Icon.Edit3 className="w-4 h-auto" /> <Icon.Edit3 className="w-4 h-auto" />
{t("common.edit")} {t("common.edit")}

View File

@ -1,103 +0,0 @@
import { IconButton } from "@mui/joy";
import clsx from "clsx";
import { useEffect, useRef } from "react";
import useCurrentUser from "@/hooks/useCurrentUser";
import useDateTime from "@/hooks/useDateTime";
import { useMemoStore, useTagStore } from "@/store/v1";
import { Memo } from "@/types/proto/api/v1/memo_service";
import MemoEditor, { Props as MemoEditorProps } from ".";
import { generateDialog } from "../Dialog";
import Icon from "../Icon";
interface Props extends DialogProps, MemoEditorProps {}
const MemoEditorDialog: React.FC<Props> = ({
memoName,
parentMemoName,
placeholder,
cacheKey,
relationList,
onConfirm,
destroy,
}: Props) => {
const tagStore = useTagStore();
const memoStore = useMemoStore();
const { setDateTime, displayDateTime, datePickerDateTime } = useDateTime(memoStore.getMemoByName(memoName || "")?.displayTime);
const memoPatchRef = useRef<Partial<Memo>>({
displayTime: memoStore.getMemoByName(memoName || "")?.displayTime,
});
const user = useCurrentUser();
useEffect(() => {
tagStore.fetchTags({ user }, { skipCache: false });
}, []);
const updateDisplayTime = (displayTime: string) => {
setDateTime(displayTime);
memoPatchRef.current.displayTime = new Date(displayTime);
};
const handleCloseBtnClick = () => {
destroy();
};
const handleConfirm = (memoName: string) => {
handleCloseBtnClick();
if (onConfirm) {
onConfirm(memoName);
}
};
return (
<>
<div className="w-full flex flex-row justify-between items-center">
<div className={clsx("flex flex-row justify-start items-center", !displayDateTime && "mb-2")}>
{displayDateTime ? (
<div className="relative">
<span className="cursor-pointer text-gray-500 dark:text-gray-400">{displayDateTime}</span>
<input
className="inset-0 absolute z-1 opacity-0"
type="datetime-local"
value={datePickerDateTime}
onFocus={(e: any) => e.target.showPicker()}
onChange={(e) => updateDisplayTime(e.target.value)}
/>
</div>
) : (
<>
<img className="w-6 h-auto rounded-full shadow" src={"/full-logo.webp"} alt="" />
<p className="ml-1 text-lg opacity-80 dark:text-gray-300">Memos</p>
</>
)}
</div>
<IconButton size="sm" onClick={handleCloseBtnClick}>
<Icon.X className="w-5 h-auto" />
</IconButton>
</div>
<div className="flex flex-col justify-start items-start max-w-full w-[40rem]">
<MemoEditor
className="border-none !p-0 -mb-2"
cacheKey={`memo-editor-${cacheKey || memoName}`}
memoName={memoName}
parentMemoName={parentMemoName}
placeholder={placeholder}
relationList={relationList}
memoPatchRef={memoPatchRef}
onConfirm={handleConfirm}
autoFocus
/>
</div>
</>
);
};
export default function showMemoEditorDialog(props: Partial<Props> = {}): void {
generateDialog(
{
className: "memo-editor-dialog",
dialogName: "memo-editor-dialog",
},
MemoEditorDialog,
props,
);
}

View File

@ -1,4 +1,5 @@
import { Select, Option, Button, Divider } from "@mui/joy"; import { Select, Option, Button, Divider } from "@mui/joy";
import { isEqual } from "lodash-es";
import React, { useEffect, useMemo, useRef, useState } from "react"; import React, { useEffect, useMemo, useRef, useState } from "react";
import { toast } from "react-hot-toast"; import { toast } from "react-hot-toast";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
@ -6,6 +7,7 @@ import useLocalStorage from "react-use/lib/useLocalStorage";
import { memoServiceClient } from "@/grpcweb"; import { memoServiceClient } from "@/grpcweb";
import { TAB_SPACE_WIDTH } from "@/helpers/consts"; import { TAB_SPACE_WIDTH } from "@/helpers/consts";
import { isValidUrl } from "@/helpers/utils"; import { isValidUrl } from "@/helpers/utils";
import useAsyncEffect from "@/hooks/useAsyncEffect";
import useCurrentUser from "@/hooks/useCurrentUser"; import useCurrentUser from "@/hooks/useCurrentUser";
import { useMemoStore, useResourceStore, useUserStore, useWorkspaceSettingStore } from "@/store/v1"; import { useMemoStore, useResourceStore, useUserStore, useWorkspaceSettingStore } from "@/store/v1";
import { MemoRelation, MemoRelation_Type } from "@/types/proto/api/v1/memo_relation_service"; import { MemoRelation, MemoRelation_Type } from "@/types/proto/api/v1/memo_relation_service";
@ -32,11 +34,11 @@ export interface Props {
className?: string; className?: string;
cacheKey?: string; cacheKey?: string;
placeholder?: string; placeholder?: string;
// The name of the memo to be edited.
memoName?: string; memoName?: string;
// The name of the parent memo if the memo is a comment.
parentMemoName?: string; parentMemoName?: string;
relationList?: MemoRelation[];
autoFocus?: boolean; autoFocus?: boolean;
memoPatchRef?: React.MutableRefObject<Partial<Memo>>;
onConfirm?: (memoName: string) => void; onConfirm?: (memoName: string) => void;
onCancel?: () => void; onCancel?: () => void;
} }
@ -62,11 +64,12 @@ const MemoEditor = (props: Props) => {
const [state, setState] = useState<State>({ const [state, setState] = useState<State>({
memoVisibility: Visibility.PRIVATE, memoVisibility: Visibility.PRIVATE,
resourceList: [], resourceList: [],
relationList: props.relationList ?? [], relationList: [],
isUploadingResource: false, isUploadingResource: false,
isRequesting: false, isRequesting: false,
isComposing: false, isComposing: false,
}); });
const [displayTime, setDisplayTime] = useState<Date | undefined>();
const [hasContent, setHasContent] = useState<boolean>(false); const [hasContent, setHasContent] = useState<boolean>(false);
const editorRef = useRef<EditorRefActions>(null); const editorRef = useRef<EditorRefActions>(null);
const userSetting = userStore.userSetting as UserSetting; const userSetting = userStore.userSetting as UserSetting;
@ -102,9 +105,12 @@ const MemoEditor = (props: Props) => {
})); }));
}, [userSetting.memoVisibility, workspaceMemoRelatedSetting.disallowPublicVisibility]); }, [userSetting.memoVisibility, workspaceMemoRelatedSetting.disallowPublicVisibility]);
useEffect(() => { useAsyncEffect(async () => {
if (memoName) { if (!memoName) {
memoStore.getOrFetchMemoByName(memoName).then((memo) => { return;
}
const memo = await memoStore.getOrFetchMemoByName(memoName);
if (memo) { if (memo) {
handleEditorFocus(); handleEditorFocus();
setState((prevState) => ({ setState((prevState) => ({
@ -113,12 +119,11 @@ const MemoEditor = (props: Props) => {
resourceList: memo.resources, resourceList: memo.resources,
relationList: memo.relations, relationList: memo.relations,
})); }));
setDisplayTime(memo.displayTime);
if (!contentCache) { if (!contentCache) {
editorRef.current?.setContent(memo.content ?? ""); editorRef.current?.setContent(memo.content ?? "");
} }
} }
});
}
}, [memoName]); }, [memoName]);
const handleCompositionStart = () => { const handleCompositionStart = () => {
@ -289,18 +294,16 @@ const MemoEditor = (props: Props) => {
const prevMemo = await memoStore.getOrFetchMemoByName(memoName); const prevMemo = await memoStore.getOrFetchMemoByName(memoName);
if (prevMemo) { if (prevMemo) {
const updateMask = ["content", "visibility"]; const updateMask = ["content", "visibility"];
if (props.memoPatchRef?.current?.displayTime) { const memoPatch: Partial<Memo> = {
updateMask.push("display_time");
}
const memo = await memoStore.updateMemo(
{
name: prevMemo.name, name: prevMemo.name,
content, content,
visibility: state.memoVisibility, visibility: state.memoVisibility,
...props.memoPatchRef?.current, };
}, if (!isEqual(displayTime, prevMemo.displayTime)) {
updateMask, updateMask.push("display_time");
); memoPatch.displayTime = displayTime;
}
const memo = await memoStore.updateMemo(memoPatch, updateMask);
await memoServiceClient.setMemoResources({ await memoServiceClient.setMemoResources({
name: memo.name, name: memo.name,
resources: state.resourceList, resources: state.resourceList,
@ -409,6 +412,18 @@ const MemoEditor = (props: Props) => {
onCompositionStart={handleCompositionStart} onCompositionStart={handleCompositionStart}
onCompositionEnd={handleCompositionEnd} onCompositionEnd={handleCompositionEnd}
> >
{memoName && displayTime && (
<div className="relative text-sm">
<span className="cursor-pointer text-gray-400 dark:text-gray-500">{displayTime.toLocaleString()}</span>
<input
className="inset-0 absolute z-1 opacity-0"
type="datetime-local"
value={displayTime.toLocaleString()}
onFocus={(e: any) => e.target.showPicker()}
onChange={(e) => setDisplayTime(new Date(e.target.value))}
/>
</div>
)}
<Editor ref={editorRef} {...editorConfig} /> <Editor ref={editorRef} {...editorConfig} />
<ResourceListView resourceList={state.resourceList} setResourceList={handleSetResourceList} /> <ResourceListView resourceList={state.resourceList} setResourceList={handleSetResourceList} />
<RelationListView relationList={referenceRelations} setRelationList={handleSetRelationList} /> <RelationListView relationList={referenceRelations} setRelationList={handleSetRelationList} />

View File

@ -44,7 +44,7 @@ const MemoRelationListView = (props: Props) => {
} }
return ( return (
<div className="relative flex flex-col justify-start items-start w-full px-2 pt-2 pb-1 bg-zinc-50 dark:bg-zinc-900 rounded-lg border border-gray-200 dark:border-zinc-700"> <div className="relative flex flex-col justify-start items-start w-full px-2 pt-2 pb-1.5 bg-zinc-50 dark:bg-zinc-900 rounded-lg border border-gray-200 dark:border-zinc-700">
<div className="w-full flex flex-row justify-start items-center mb-1 gap-3 opacity-60"> <div className="w-full flex flex-row justify-start items-center mb-1 gap-3 opacity-60">
{referencingMemoList.length > 0 && ( {referencingMemoList.length > 0 && (
<button <button
@ -56,6 +56,7 @@ const MemoRelationListView = (props: Props) => {
> >
<Icon.Link className="w-3 h-auto shrink-0 opacity-70" /> <Icon.Link className="w-3 h-auto shrink-0 opacity-70" />
<span>Referencing</span> <span>Referencing</span>
<span className="opacity-80">({referencingMemoList.length})</span>
</button> </button>
)} )}
{referencedMemoList.length > 0 && ( {referencedMemoList.length > 0 && (
@ -68,6 +69,7 @@ const MemoRelationListView = (props: Props) => {
> >
<Icon.Milestone className="w-3 h-auto shrink-0 opacity-70" /> <Icon.Milestone className="w-3 h-auto shrink-0 opacity-70" />
<span>Referenced by</span> <span>Referenced by</span>
<span className="opacity-80">({referencedMemoList.length})</span>
</button> </button>
)} )}
</div> </div>
@ -81,7 +83,7 @@ const MemoRelationListView = (props: Props) => {
to={`/m/${memo.uid}`} to={`/m/${memo.uid}`}
unstable_viewTransition unstable_viewTransition
> >
<Icon.Dot className="shrink-0 -ml-1 opacity-60" /> <Icon.Dot className="shrink-0 w-4 h-auto opacity-40" />
<span className="truncate">{memo.snippet}</span> <span className="truncate">{memo.snippet}</span>
</Link> </Link>
); );
@ -98,7 +100,7 @@ const MemoRelationListView = (props: Props) => {
to={`/m/${memo.uid}`} to={`/m/${memo.uid}`}
unstable_viewTransition unstable_viewTransition
> >
<Icon.Dot className="shrink-0 -ml-1 opacity-60" /> <Icon.Dot className="shrink-0 w-4 h-auto opacity-40" />
<span className="truncate">{memo.snippet}</span> <span className="truncate">{memo.snippet}</span>
</Link> </Link>
); );

View File

@ -106,6 +106,17 @@ const MemoView: React.FC<Props> = (props: Props) => {
)} )}
ref={memoContainerRef} ref={memoContainerRef}
> >
{showEditor ? (
<MemoEditor
autoFocus
className="border-none !p-0 -mb-2"
cacheKey={`inline-memo-editor-${memo.name}`}
memoName={memo.name}
onConfirm={() => setShowEditor(false)}
onCancel={() => setShowEditor(false)}
/>
) : (
<>
<div className="w-full flex flex-row justify-between items-center gap-2"> <div className="w-full flex flex-row justify-between items-center gap-2">
<div className="w-auto max-w-[calc(100%-8rem)] grow flex flex-row justify-start items-center"> <div className="w-auto max-w-[calc(100%-8rem)] grow flex flex-row justify-start items-center">
{props.showCreator && creator ? ( {props.showCreator && creator ? (
@ -130,12 +141,14 @@ const MemoView: React.FC<Props> = (props: Props) => {
</div> </div>
</div> </div>
) : ( ) : (
<div className="w-full text-sm leading-tight text-gray-400 dark:text-gray-500 select-none" onClick={handleGotoMemoDetailPage}> <div
className="w-full text-sm leading-tight text-gray-400 dark:text-gray-500 select-none"
onClick={handleGotoMemoDetailPage}
>
{displayTime} {displayTime}
</div> </div>
)} )}
</div> </div>
{!showEditor && (
<div className="flex flex-row justify-end items-center select-none shrink-0 gap-2"> <div className="flex flex-row justify-end items-center select-none shrink-0 gap-2">
<div className="w-auto invisible group-hover:visible flex flex-row justify-between items-center gap-2"> <div className="w-auto invisible group-hover:visible flex flex-row justify-between items-center gap-2">
{props.showVisibility && memo.visibility !== Visibility.PRIVATE && ( {props.showVisibility && memo.visibility !== Visibility.PRIVATE && (
@ -174,20 +187,7 @@ const MemoView: React.FC<Props> = (props: Props) => {
/> />
)} )}
</div> </div>
)}
</div> </div>
{showEditor ? (
<MemoEditor
autoFocus
className="border-none !p-0 -mb-2"
cacheKey={`inline-memo-editor-${memo.name}`}
memoName={memo.name}
onConfirm={() => setShowEditor(false)}
onCancel={() => setShowEditor(false)}
/>
) : (
<>
<MemoContent <MemoContent
key={`${memo.name}-${memo.updateTime}`} key={`${memo.name}-${memo.updateTime}`}
memoName={memo.name} memoName={memo.name}

View File

@ -12,6 +12,7 @@ import { useMemoStore } from "@/store/v1";
import { useTranslate } from "@/utils/i18n"; import { useTranslate } from "@/utils/i18n";
import ActivityCalendar from "./ActivityCalendar"; import ActivityCalendar from "./ActivityCalendar";
import Icon from "./Icon"; import Icon from "./Icon";
import { Popover, PopoverContent, PopoverTrigger } from "./ui/Popover";
interface UserMemoStats { interface UserMemoStats {
link: number; link: number;
@ -26,15 +27,14 @@ const UserStatisticsView = () => {
const memoStore = useMemoStore(); const memoStore = useMemoStore();
const filterStore = useFilterStore(); const filterStore = useFilterStore();
const [memoAmount, setMemoAmount] = useState(0); const [memoAmount, setMemoAmount] = useState(0);
const [isRequesting, setIsRequesting] = useState(false);
const [memoStats, setMemoStats] = useState<UserMemoStats>({ link: 0, taskList: 0, code: 0, incompleteTasks: 0 }); const [memoStats, setMemoStats] = useState<UserMemoStats>({ link: 0, taskList: 0, code: 0, incompleteTasks: 0 });
const [activityStats, setActivityStats] = useState<Record<string, number>>({}); const [activityStats, setActivityStats] = useState<Record<string, number>>({});
const monthString = dayjs(new Date().toDateString()).format("YYYY-MM"); 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 days = Math.ceil((Date.now() - currentUser.createTime!.getTime()) / 86400000);
const filter = filterStore.state; const filter = filterStore.state;
useAsyncEffect(async () => { useAsyncEffect(async () => {
setIsRequesting(true);
const { properties } = await memoServiceClient.listMemoProperties({ const { properties } = await memoServiceClient.listMemoProperties({
name: `memos/-`, name: `memos/-`,
}); });
@ -62,7 +62,6 @@ const UserStatisticsView = () => {
timezone: Intl.DateTimeFormat().resolvedOptions().timeZone, timezone: Intl.DateTimeFormat().resolvedOptions().timeZone,
filter: filters.join(" && "), filter: filters.join(" && "),
}); });
setActivityStats( setActivityStats(
Object.fromEntries( Object.fromEntries(
Object.entries(stats).filter(([date]) => { Object.entries(stats).filter(([date]) => {
@ -70,7 +69,6 @@ const UserStatisticsView = () => {
}), }),
), ),
); );
setIsRequesting(false);
}, [memoStore.stateId]); }, [memoStore.stateId]);
const handleRebuildMemoTags = async () => { const handleRebuildMemoTags = async () => {
@ -83,39 +81,42 @@ const UserStatisticsView = () => {
return ( return (
<div className="group w-full border mt-2 py-2 px-3 rounded-lg space-y-0.5 text-gray-500 dark:text-gray-400 bg-zinc-50 dark:bg-zinc-900 dark:border-zinc-800"> <div className="group w-full border mt-2 py-2 px-3 rounded-lg space-y-0.5 text-gray-500 dark:text-gray-400 bg-zinc-50 dark:bg-zinc-900 dark:border-zinc-800">
<div className="w-full mb-1 flex flex-row justify-between items-center"> <div className="w-full mb-2 flex flex-row justify-between items-center">
<p className="text-sm font-medium leading-6 dark:text-gray-400"> <div className="relative text-base font-medium leading-6 flex flex-row items-center dark:text-gray-400">
{new Date().toLocaleDateString(i18n.language, { month: "long", day: "numeric" })} <Icon.CalendarDays className="w-5 h-auto mr-1 opacity-60" strokeWidth={1.5} />
</p> <span>{new Date(monthString).toLocaleString(i18n.language, { year: "numeric", month: "long" })}</span>
<div className="group-hover:block hidden"> <input
<Tooltip title={"Refresh"} placement="top"> className="inset-0 absolute z-1 opacity-0"
<Icon.RefreshCcw type="month"
className="text-gray-400 w-4 h-auto cursor-pointer opacity-60 hover:opacity-100" value={monthString}
onClick={handleRebuildMemoTags} onFocus={(e: any) => e.target.showPicker()}
onChange={(e) => setMonthString(e.target.value)}
/> />
</Tooltip> </div>
<div className="invisible group-hover:visible flex justify-end items-center">
<Popover>
<PopoverTrigger>
<Icon.MoreVertical className="w-4 h-auto shrink-0 opacity-60" />
</PopoverTrigger>
<PopoverContent>
<button className="w-auto flex flex-row justify-between items-center gap-2 hover:opacity-80" onClick={handleRebuildMemoTags}>
<Icon.RefreshCcw className="text-gray-400 w-4 h-auto cursor-pointer opacity-60" />
<span className="text-sm shrink-0 text-gray-500 dark:text-gray-400">Refresh</span>
</button>
</PopoverContent>
</Popover>
</div> </div>
</div> </div>
<div className="w-full pb-2"> <div className="w-full">
<ActivityCalendar month={monthString} selectedDate={new Date().toDateString()} data={activityStats} /> <ActivityCalendar month={monthString} selectedDate={selectedDate.toDateString()} data={activityStats} />
{memoAmount > 0 && (
<p className="mt-1 w-full text-xs italic opacity-80">
<span>{memoAmount}</span> memos in <span>{days}</span> days
</p>
)}
</div> </div>
<div className="w-full grid grid-cols-1 gap-x-4"> <Divider className="!my-2 opacity-50" />
<div className="w-full flex justify-between items-center"> <div className="w-full flex flex-row justify-start items-center gap-x-2 gap-y-1 flex-wrap">
<div className="w-auto flex justify-start items-center">
<Icon.CalendarDays className="w-4 h-auto mr-1" />
<span className="block text-base sm:text-sm">Days</span>
</div>
<span>{days}</span>
</div>
<div className="w-full flex justify-between items-center">
<div className="w-auto flex justify-start items-center">
<Icon.Library className="w-4 h-auto mr-1" />
<span className="block text-base sm:text-sm">Memos</span>
</div>
{isRequesting ? <Icon.Loader className="animate-spin w-4 h-auto text-gray-400" /> : <span className="">{memoAmount}</span>}
</div>
<Divider className="!my-1 opacity-50" />
<div className="w-full mt-1 flex flex-row justify-start items-center gap-x-2 gap-y-1 flex-wrap">
<div <div
className={clsx( 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", "w-auto border dark:border-zinc-800 pl-1 pr-1.5 rounded-md flex justify-between items-center cursor-pointer hover:shadow",
@ -171,7 +172,6 @@ const UserStatisticsView = () => {
</div> </div>
</div> </div>
</div> </div>
</div>
); );
}; };

View File

@ -4,4 +4,3 @@ export * from "./useNavigateTo";
export * from "./useAsyncEffect"; export * from "./useAsyncEffect";
export * from "./useFilterWithUrlParams"; export * from "./useFilterWithUrlParams";
export * from "./useResponsiveWidth"; export * from "./useResponsiveWidth";
export * from "./useDateTime";

View File

@ -1,13 +0,0 @@
import { useState } from "react";
const useDateTime = (initalState?: Date) => {
const [dateTime, setDateTimeInternal] = useState<Date | undefined>(initalState && new Date(initalState));
return {
setDateTime: (dateTimeString: string) => setDateTimeInternal(new Date(dateTimeString)),
displayDateTime: dateTime && dateTime.toLocaleString(),
datePickerDateTime: dateTime && new Date(dateTime.getTime() - dateTime.getTimezoneOffset() * 60000).toISOString().split(".")[0],
};
};
export default useDateTime;