From 3c9679dff9740c9727b2cf4bb9ba5bef935d46b2 Mon Sep 17 00:00:00 2001 From: Aaryan Khandelwal <65252264+aaryan610@users.noreply.github.com> Date: Mon, 29 Jan 2024 15:42:57 +0530 Subject: [PATCH] chore: update time in real-time in dashboard and profile sidebar (#3489) * chore: update dashboard and profile time in realtime * chore: remove seconds * fix: cycle and module sidebar datepicker --- web/components/cycles/sidebar.tsx | 203 +++++++++++++----------- web/components/modules/sidebar.tsx | 205 ++++++++++++++----------- web/components/profile/index.ts | 4 +- web/components/profile/sidebar.tsx | 18 +-- web/components/profile/time.tsx | 27 ++++ web/components/user/user-greetings.tsx | 7 +- web/hooks/use-current-time.tsx | 17 ++ 7 files changed, 280 insertions(+), 201 deletions(-) create mode 100644 web/components/profile/time.tsx create mode 100644 web/hooks/use-current-time.tsx diff --git a/web/components/cycles/sidebar.tsx b/web/components/cycles/sidebar.tsx index 33b404e9d..33c2b3c82 100644 --- a/web/components/cycles/sidebar.tsx +++ b/web/components/cycles/sidebar.tsx @@ -1,4 +1,4 @@ -import React, { useEffect, useState } from "react"; +import React, { useEffect, useRef, useState } from "react"; import { useRouter } from "next/router"; import { observer } from "mobx-react-lite"; import { useForm } from "react-hook-form"; @@ -46,6 +46,11 @@ type Props = { handleClose: () => void; }; +const defaultValues: Partial = { + start_date: null, + end_date: null, +}; + // services const cycleService = new CycleService(); @@ -54,6 +59,9 @@ export const CycleDetailsSidebar: React.FC = observer((props) => { const { cycleId, handleClose } = props; // states const [cycleDeleteModal, setCycleDeleteModal] = useState(false); + // refs + const startDateButtonRef = useRef(null); + const endDateButtonRef = useRef(null); // router const router = useRouter(); const { workspaceSlug, projectId, peekCycle } = router.query; @@ -70,11 +78,6 @@ export const CycleDetailsSidebar: React.FC = observer((props) => { const { setToastAlert } = useToast(); - const defaultValues: Partial = { - start_date: new Date().toString(), - end_date: new Date().toString(), - }; - const { setValue, reset, watch } = useForm({ defaultValues, }); @@ -120,6 +123,9 @@ export const CycleDetailsSidebar: React.FC = observer((props) => { const handleStartDateChange = async (date: string) => { setValue("start_date", date); + + if (!watch("end_date") || watch("end_date") === "") endDateButtonRef.current?.click(); + if (watch("start_date") && watch("end_date") && watch("start_date") !== "" && watch("start_date") !== "") { if (!isDateGreaterThanToday(`${watch("end_date")}`)) { setToastAlert({ @@ -127,6 +133,7 @@ export const CycleDetailsSidebar: React.FC = observer((props) => { title: "Error!", message: "Unable to create cycle in past date. Please enter a valid date.", }); + reset({ ...cycleDetails }); return; } @@ -147,7 +154,6 @@ export const CycleDetailsSidebar: React.FC = observer((props) => { title: "Success!", message: "Cycle updated successfully.", }); - return; } else { setToastAlert({ type: "error", @@ -155,8 +161,10 @@ export const CycleDetailsSidebar: React.FC = observer((props) => { message: "You have a cycle already on the given dates, if you want to create your draft cycle you can do that by removing dates", }); - return; } + + reset({ ...cycleDetails }); + return; } const isDateValid = await dateChecker({ @@ -181,6 +189,7 @@ export const CycleDetailsSidebar: React.FC = observer((props) => { message: "You have a cycle already on the given dates, if you want to create your draft cycle you can do that by removing dates", }); + reset({ ...cycleDetails }); } } }; @@ -188,6 +197,8 @@ export const CycleDetailsSidebar: React.FC = observer((props) => { const handleEndDateChange = async (date: string) => { setValue("end_date", date); + if (!watch("start_date") || watch("start_date") === "") startDateButtonRef.current?.click(); + if (watch("start_date") && watch("end_date") && watch("start_date") !== "" && watch("start_date") !== "") { if (!isDateGreaterThanToday(`${watch("end_date")}`)) { setToastAlert({ @@ -195,6 +206,7 @@ export const CycleDetailsSidebar: React.FC = observer((props) => { title: "Error!", message: "Unable to create cycle in past date. Please enter a valid date.", }); + reset({ ...cycleDetails }); return; } @@ -215,7 +227,6 @@ export const CycleDetailsSidebar: React.FC = observer((props) => { title: "Success!", message: "Cycle updated successfully.", }); - return; } else { setToastAlert({ type: "error", @@ -223,8 +234,9 @@ export const CycleDetailsSidebar: React.FC = observer((props) => { message: "You have a cycle already on the given dates, if you want to create your draft cycle you can do that by removing dates", }); - return; } + reset({ ...cycleDetails }); + return; } const isDateValid = await dateChecker({ @@ -249,6 +261,7 @@ export const CycleDetailsSidebar: React.FC = observer((props) => { message: "You have a cycle already on the given dates, if you want to create your draft cycle you can do that by removing dates", }); + reset({ ...cycleDetails }); } } }; @@ -387,50 +400,56 @@ export const CycleDetailsSidebar: React.FC = observer((props) => {
- Start Date + Start date
- - - {renderFormattedDate(startDate) ?? "No date selected"} - - + {({ close }) => ( + <> + + + {renderFormattedDate(startDate) ?? "No date selected"} + + - - - { - if (val) { - setTrackElement("CYCLE_PAGE_SIDEBAR_START_DATE_BUTTON"); - handleStartDateChange(val); - } - }} - startDate={watch("start_date") ?? watch("end_date") ?? null} - endDate={watch("end_date") ?? watch("start_date") ?? null} - maxDate={new Date(`${watch("end_date")}`)} - selectsStart={watch("end_date") ? true : false} - /> - - + + + { + if (val) { + setTrackElement("CYCLE_PAGE_SIDEBAR_START_DATE_BUTTON"); + handleStartDateChange(val); + close(); + } + }} + startDate={watch("start_date") ?? watch("end_date") ?? null} + endDate={watch("end_date") ?? watch("start_date") ?? null} + maxDate={new Date(`${watch("end_date")}`)} + selectsStart={watch("end_date") ? true : false} + /> + + + + )}
@@ -438,52 +457,56 @@ export const CycleDetailsSidebar: React.FC = observer((props) => {
- Target Date + Target date
- <> - - ( + <> + - {renderFormattedDate(endDate) ?? "No date selected"} - - + + {renderFormattedDate(endDate) ?? "No date selected"} + + - - - { - if (val) { - setTrackElement("CYCLE_PAGE_SIDEBAR_END_DATE_BUTTON"); - handleEndDateChange(val); - } - }} - startDate={watch("start_date") ?? watch("end_date") ?? null} - endDate={watch("end_date") ?? watch("start_date") ?? null} - minDate={new Date(`${watch("start_date")}`)} - selectsEnd={watch("start_date") ? true : false} - /> - - - + + + { + if (val) { + setTrackElement("CYCLE_PAGE_SIDEBAR_END_DATE_BUTTON"); + handleEndDateChange(val); + close(); + } + }} + startDate={watch("start_date") ?? watch("end_date") ?? null} + endDate={watch("end_date") ?? watch("start_date") ?? null} + minDate={new Date(`${watch("start_date")}`)} + selectsEnd={watch("start_date") ? true : false} + /> + + + + )}
diff --git a/web/components/modules/sidebar.tsx b/web/components/modules/sidebar.tsx index 5b2c8f170..f43326b6b 100644 --- a/web/components/modules/sidebar.tsx +++ b/web/components/modules/sidebar.tsx @@ -1,20 +1,8 @@ -import React, { useEffect, useState } from "react"; +import React, { useEffect, useRef, useState } from "react"; import { useRouter } from "next/router"; import { observer } from "mobx-react-lite"; import { Controller, useForm } from "react-hook-form"; import { Disclosure, Popover, Transition } from "@headlessui/react"; -// hooks -import { useModule, useUser } from "hooks/store"; -// hooks -import useToast from "hooks/use-toast"; -// components -import { LinkModal, LinksList, SidebarProgressStats } from "components/core"; -import { DeleteModuleModal } from "components/modules"; -import ProgressChart from "components/core/sidebar/progress-chart"; -// ui -import { CustomRangeDatePicker } from "components/ui"; -import { CustomMenu, Loader, LayersIcon, CustomSelect, ModuleStatusIcon, UserGroupIcon } from "@plane/ui"; -// icon import { AlertCircle, CalendarCheck2, @@ -27,6 +15,17 @@ import { Trash2, UserCircle2, } from "lucide-react"; +// hooks +import { useModule, useUser } from "hooks/store"; +import useToast from "hooks/use-toast"; +// components +import { LinkModal, LinksList, SidebarProgressStats } from "components/core"; +import { DeleteModuleModal } from "components/modules"; +import ProgressChart from "components/core/sidebar/progress-chart"; +import { ProjectMemberDropdown } from "components/dropdowns"; +// ui +import { CustomRangeDatePicker } from "components/ui"; +import { CustomMenu, Loader, LayersIcon, CustomSelect, ModuleStatusIcon, UserGroupIcon } from "@plane/ui"; // helpers import { isDateGreaterThanToday, renderFormattedPayloadDate, renderFormattedDate } from "helpers/date-time.helper"; import { copyUrlToClipboard } from "helpers/string.helper"; @@ -35,7 +34,6 @@ import { ILinkDetails, IModule, ModuleLink } from "@plane/types"; // constant import { MODULE_STATUS } from "constants/module"; import { EUserProjectRoles } from "constants/project"; -import { ProjectMemberDropdown } from "components/dropdowns"; const defaultValues: Partial = { lead: "", @@ -57,6 +55,9 @@ export const ModuleDetailsSidebar: React.FC = observer((props) => { const [moduleDeleteModal, setModuleDeleteModal] = useState(false); const [moduleLinkModal, setModuleLinkModal] = useState(false); const [selectedLinkToUpdate, setSelectedLinkToUpdate] = useState(null); + // refs + const startDateButtonRef = useRef(null); + const endDateButtonRef = useRef(null); // router const router = useRouter(); const { workspaceSlug, projectId, peekModule } = router.query; @@ -164,6 +165,8 @@ export const ModuleDetailsSidebar: React.FC = observer((props) => { const handleStartDateChange = async (date: string) => { setValue("start_date", date); + if (!watch("target_date") || watch("target_date") === "") endDateButtonRef.current?.click(); + if (watch("start_date") && watch("target_date") && watch("start_date") !== "" && watch("start_date") !== "") { if (!isDateGreaterThanToday(`${watch("target_date")}`)) { setToastAlert({ @@ -171,6 +174,9 @@ export const ModuleDetailsSidebar: React.FC = observer((props) => { title: "Error!", message: "Unable to create module in past date. Please enter a valid date.", }); + reset({ + ...moduleDetails, + }); return; } @@ -189,6 +195,8 @@ export const ModuleDetailsSidebar: React.FC = observer((props) => { const handleEndDateChange = async (date: string) => { setValue("target_date", date); + if (!watch("start_date") || watch("start_date") === "") endDateButtonRef.current?.click(); + if (watch("start_date") && watch("target_date") && watch("start_date") !== "" && watch("start_date") !== "") { if (!isDateGreaterThanToday(`${watch("target_date")}`)) { setToastAlert({ @@ -196,6 +204,9 @@ export const ModuleDetailsSidebar: React.FC = observer((props) => { title: "Error!", message: "Unable to create module in past date. Please enter a valid date.", }); + reset({ + ...moduleDetails, + }); return; } @@ -355,49 +366,55 @@ export const ModuleDetailsSidebar: React.FC = observer((props) => {
- Start Date + Start date
- - - {renderFormattedDate(startDate) ?? "No date selected"} - - + {({ close }) => ( + <> + + + {renderFormattedDate(startDate) ?? "No date selected"} + + - - - { - if (val) { - handleStartDateChange(val); - } - }} - startDate={watch("start_date") ?? watch("target_date") ?? null} - endDate={watch("target_date") ?? watch("start_date") ?? null} - maxDate={new Date(`${watch("target_date")}`)} - selectsStart={watch("target_date") ? true : false} - /> - - + + + { + if (val) { + handleStartDateChange(val); + close(); + } + }} + startDate={watch("start_date") ?? watch("target_date") ?? null} + endDate={watch("target_date") ?? watch("start_date") ?? null} + maxDate={new Date(`${watch("target_date")}`)} + selectsStart={watch("target_date") ? true : false} + /> + + + + )}
@@ -405,51 +422,55 @@ export const ModuleDetailsSidebar: React.FC = observer((props) => {
- Target Date + Target date
- <> - - ( + <> + - {renderFormattedDate(endDate) ?? "No date selected"} - - + + {renderFormattedDate(endDate) ?? "No date selected"} + + - - - { - if (val) { - handleEndDateChange(val); - } - }} - startDate={watch("start_date") ?? watch("target_date") ?? null} - endDate={watch("target_date") ?? watch("start_date") ?? null} - minDate={new Date(`${watch("start_date")}`)} - selectsEnd={watch("start_date") ? true : false} - /> - - - + + + { + if (val) { + handleEndDateChange(val); + close(); + } + }} + startDate={watch("start_date") ?? watch("target_date") ?? null} + endDate={watch("target_date") ?? watch("start_date") ?? null} + minDate={new Date(`${watch("start_date")}`)} + selectsEnd={watch("start_date") ? true : false} + /> + + + + )}
diff --git a/web/components/profile/index.ts b/web/components/profile/index.ts index ffd05c5a5..f6d2a3775 100644 --- a/web/components/profile/index.ts +++ b/web/components/profile/index.ts @@ -1,5 +1,5 @@ export * from "./overview"; export * from "./navbar"; -export * from "./sidebar"; - export * from "./profile-issues-filter"; +export * from "./sidebar"; +export * from "./time"; diff --git a/web/components/profile/sidebar.tsx b/web/components/profile/sidebar.tsx index a701a058d..3ce7747c9 100644 --- a/web/components/profile/sidebar.tsx +++ b/web/components/profile/sidebar.tsx @@ -7,6 +7,8 @@ import { observer } from "mobx-react-lite"; import { useUser } from "hooks/store"; // services import { UserService } from "services/user.service"; +// components +import { ProfileSidebarTime } from "./time"; // ui import { Loader, Tooltip } from "@plane/ui"; // icons @@ -34,16 +36,6 @@ export const ProfileSidebar = observer(() => { : null ); - // Create a date object for the current time in the specified timezone - const currentTime = new Date(); - const formatter = new Intl.DateTimeFormat("en-US", { - timeZone: userProjectsData?.user_data.user_timezone, - hour12: false, // Use 24-hour format - hour: "2-digit", - minute: "2-digit", - }); - const timeString = formatter.format(currentTime); - const userDetails = [ { label: "Joined on", @@ -51,11 +43,7 @@ export const ProfileSidebar = observer(() => { }, { label: "Timezone", - value: ( - - {timeString} {userProjectsData?.user_data.user_timezone} - - ), + value: , }, ]; diff --git a/web/components/profile/time.tsx b/web/components/profile/time.tsx new file mode 100644 index 000000000..a1b740410 --- /dev/null +++ b/web/components/profile/time.tsx @@ -0,0 +1,27 @@ +// hooks +import { useCurrentTime } from "hooks/use-current-time"; + +type Props = { + timeZone: string | undefined; +}; + +export const ProfileSidebarTime: React.FC = (props) => { + const { timeZone } = props; + // current time hook + const { currentTime } = useCurrentTime(); + + // Create a date object for the current time in the specified timezone + const formatter = new Intl.DateTimeFormat("en-US", { + timeZone: timeZone, + hour12: false, // Use 24-hour format + hour: "2-digit", + minute: "2-digit", + }); + const timeString = formatter.format(currentTime); + + return ( + + {timeString} {timeZone} + + ); +}; diff --git a/web/components/user/user-greetings.tsx b/web/components/user/user-greetings.tsx index a9a449c93..3341ac77f 100644 --- a/web/components/user/user-greetings.tsx +++ b/web/components/user/user-greetings.tsx @@ -1,4 +1,7 @@ import { FC } from "react"; +// hooks +import { useCurrentTime } from "hooks/use-current-time"; +// types import { IUser } from "@plane/types"; export interface IUserGreetingsView { @@ -7,8 +10,8 @@ export interface IUserGreetingsView { export const UserGreetingsView: FC = (props) => { const { user } = props; - - const currentTime = new Date(); + // current time hook + const { currentTime } = useCurrentTime(); const hour = new Intl.DateTimeFormat("en-US", { hour12: false, diff --git a/web/hooks/use-current-time.tsx b/web/hooks/use-current-time.tsx new file mode 100644 index 000000000..fc37129c6 --- /dev/null +++ b/web/hooks/use-current-time.tsx @@ -0,0 +1,17 @@ +import { useEffect, useState } from "react"; + +export const useCurrentTime = () => { + const [currentTime, setCurrentTime] = useState(new Date()); + // update the current time every second + useEffect(() => { + const intervalId = setInterval(() => { + setCurrentTime(new Date()); + }, 1000); + + return () => clearInterval(intervalId); + }, []); + + return { + currentTime, + }; +};