From 6833fd7a10f7b4b611db398a02407b9c628dd69a Mon Sep 17 00:00:00 2001 From: Vinay Badgujar Date: Thu, 10 Oct 2024 14:24:31 +0530 Subject: [PATCH 01/54] add timestamp field in goalItem --- src/models/GoalItem.ts | 1 + src/models/db.ts | 8 ++++++++ src/models/dexie.ts | 2 +- 3 files changed, 10 insertions(+), 1 deletion(-) diff --git a/src/models/GoalItem.ts b/src/models/GoalItem.ts index 9471c4b96..acbd185db 100644 --- a/src/models/GoalItem.ts +++ b/src/models/GoalItem.ts @@ -35,4 +35,5 @@ export interface GoalItem { typeOfGoal: "myGoal" | "shared"; category: TGoalCategory; newUpdates: boolean; + timestamp: number; } diff --git a/src/models/db.ts b/src/models/db.ts index 6b5865a6a..d4c506531 100644 --- a/src/models/db.ts +++ b/src/models/db.ts @@ -49,6 +49,14 @@ export class ZinZenDB extends Dexie { console.log("🚀 ~ file: db.ts:63 ~ ZinZenDB ~ .upgrade ~ this.verno:", currentVersion); syncVersion(db, currentVersion); }); + this.goalsCollection.hook("updating", (modfications: GoalItem) => { + modfications.timestamp = Date.now(); + return modfications; + }); + + this.goalsCollection.hook("creating", (primKey, obj) => { + obj.timestamp = Date.now(); + }); } } diff --git a/src/models/dexie.ts b/src/models/dexie.ts index 6abfe8948..389a35ef7 100644 --- a/src/models/dexie.ts +++ b/src/models/dexie.ts @@ -7,7 +7,7 @@ import { TaskItem } from "./TaskItem"; export const dbStoreSchema = { feelingsCollection: "++id, content, category, date, note", goalsCollection: - "id, category, title, duration, sublist, habit, on, start, due, afterTime, beforeTime, createdAt, parentGoalId, archived, participants, goalColor, language, link, rootGoalId, timeBudget, typeOfGoal", + "id, category, title, duration, sublist, habit, on, start, due, afterTime, beforeTime, createdAt, parentGoalId, archived, participants, goalColor, language, link, rootGoalId, timeBudget, typeOfGoal, timestamp", sharedWMCollection: "id, category, title, duration, sublist, repeat, start, due, afterTime, beforeTime, createdAt, parentGoalId, participants, archived, goalColor, language, link, rootGoalId, timeBudget, typeOfGoal", contactsCollection: "id, name, relId, accepted, goalsToBeShared, createdAt, type", From 219063592938d74314a5bca17b0dfac556e9337d Mon Sep 17 00:00:00 2001 From: Vinay Badgujar Date: Thu, 10 Oct 2024 14:26:01 +0530 Subject: [PATCH 02/54] feat: add and integrate LWW based conflict resolution --- src/helpers/InboxProcessor.ts | 65 +++++++++++++++++++++++------ src/helpers/mergeSharedGoalItems.ts | 16 +++++++ 2 files changed, 69 insertions(+), 12 deletions(-) create mode 100644 src/helpers/mergeSharedGoalItems.ts diff --git a/src/helpers/InboxProcessor.ts b/src/helpers/InboxProcessor.ts index 4536bd700..c6d56cbbb 100644 --- a/src/helpers/InboxProcessor.ts +++ b/src/helpers/InboxProcessor.ts @@ -21,9 +21,23 @@ import { import { ITagChangesSchemaVersion, ITagsChanges } from "@src/Interfaces/IDisplayChangesModal"; import { fixDateVlauesInGoalObject } from "@src/utils"; import { getDeletedGoal, restoreUserGoal } from "@src/api/TrashAPI"; +import { isIncomingGoalLatest } from "./mergeSharedGoalItems"; -export const handleIncomingChanges = async (payload, relId) => { +interface Payload { + relId: string; + lastProcessedTimestamp: string; + changeType: string; + rootGoalId: string; + changes: changesInGoal[]; + type: string; + timestamp: string; + TTL: number; +} + +export const handleIncomingChanges = async (payload: Payload, relId: string) => { + console.log("Incoming change", payload); if (payload.type === "sharer" && (await getSharedWMGoal(payload.rootGoalId))) { + console.log("Incoming change is a shared goal. Processing..."); const incGoal = await getSharedWMGoal(payload.rootGoalId); if (!incGoal || incGoal.participants.find((ele) => ele.relId === relId && ele.following) === undefined) { console.log("Changes ignored"); @@ -40,7 +54,8 @@ export const handleIncomingChanges = async (payload, relId) => { ]; await updateSharedWMGoal(changes[0].goal.id, changes[0].goal); } else if (payload.changeType === "deleted") { - const goalToBeDeleted = await getSharedWMGoal(payload.changes[0].id); + const goalToBeDeleted = await getSharedWMGoal(payload.changes[0].goal.id); + console.log("Deleting goal", goalToBeDeleted); await removeSharedWMChildrenGoals(goalToBeDeleted.id); await removeSharedWMGoal(goalToBeDeleted); if (goalToBeDeleted.parentGoalId !== "root") { @@ -67,17 +82,43 @@ export const handleIncomingChanges = async (payload, relId) => { const { rootGoalId, changes, changeType } = payload; const rootGoal = await getGoal(rootGoalId); if (rootGoal) { - let inbox: InboxItem = await getInboxItem(rootGoalId); - const defaultChanges = getDefaultValueOfGoalChanges(); - defaultChanges[changeType] = [...changes.map((ele) => ({ ...ele, intent: payload.type }))]; - if (!inbox) { - await createEmptyInboxItem(rootGoalId); - inbox = await getInboxItem(rootGoalId); + const allAreLatest = await Promise.all( + changes.map(async (ele) => { + const isLatest = await isIncomingGoalLatest(ele.goal.id, ele.goal); + console.log(`Goal ID: ${ele.goal.id} is latest: ${isLatest}`); + return isLatest ? ele : null; + }), + ); + + const filteredChanges = allAreLatest.filter((ele) => ele !== null); + + if (filteredChanges.length > 0) { + // Proceed if there are latest changes + console.log("Found latest changes. Proceeding with updates..."); + + let inbox: InboxItem = await getInboxItem(rootGoalId); + const defaultChanges = getDefaultValueOfGoalChanges(); + + // Add filtered changes to defaultChanges + defaultChanges[changeType] = filteredChanges.map((ele) => ({ + ...ele, + intent: payload.type, + })); + + if (!inbox) { + await createEmptyInboxItem(rootGoalId); + inbox = await getInboxItem(rootGoalId); + } + + await Promise.all([ + updateGoal(rootGoalId, { newUpdates: true }), + addGoalChangesInID(rootGoalId, relId, defaultChanges), + ]); + } else { + console.log("No latest changes. Skipping updates."); } - await Promise.all([ - updateGoal(rootGoalId, { newUpdates: true }), - await addGoalChangesInID(rootGoalId, relId, defaultChanges), - ]); + } else { + console.log(`Root goal with ID: ${rootGoalId} not found. Skipping update.`); } } }; diff --git a/src/helpers/mergeSharedGoalItems.ts b/src/helpers/mergeSharedGoalItems.ts new file mode 100644 index 000000000..01a503b52 --- /dev/null +++ b/src/helpers/mergeSharedGoalItems.ts @@ -0,0 +1,16 @@ +import { getGoalById } from "@src/api/GoalsAPI"; +import { GoalItem } from "@src/models/GoalItem"; + +export async function isIncomingGoalLatest(localGoalId: string, incomingGoal: GoalItem): Promise { + const localGoal = await getGoalById(localGoalId); + + if (!localGoal) { + return true; + } + + if (incomingGoal.timestamp > localGoal.timestamp) { + return true; + } + + return false; +} From 3756ad86f5e1082cefed852eeeebcfb9eb467a66 Mon Sep 17 00:00:00 2001 From: Vinay Badgujar Date: Fri, 11 Oct 2024 13:34:51 +0530 Subject: [PATCH 03/54] add: basic move goal functionality --- src/common/Icon.tsx | 17 +++++++ src/common/ZButton.tsx | 24 ++++++++++ .../GoalsComponents/MoveGoalButton.tsx | 41 ++++++++++++++++ .../GoalsComponents/MyGoal/MoveGoalGuide.tsx | 33 +++++++++++++ .../GoalsComponents/MyGoal/MyGoal.tsx | 11 ++++- .../GoalsComponents/MyGoal/index.scss | 20 ++++++++ .../MyGoalActions/RegularGoalActions.tsx | 12 ++++- src/helpers/GoalController.ts | 48 ++++++++++++++++++- src/hooks/useGoalStore.tsx | 10 +++- src/pages/GoalsPage/MyGoals.tsx | 8 +++- src/store/moveGoalState.ts | 7 +++ src/store/useNavigateToSubgoal.ts | 36 ++++++++++++++ 12 files changed, 260 insertions(+), 7 deletions(-) create mode 100644 src/common/ZButton.tsx create mode 100644 src/components/GoalsComponents/MoveGoalButton.tsx create mode 100644 src/components/GoalsComponents/MyGoal/MoveGoalGuide.tsx create mode 100644 src/components/GoalsComponents/MyGoal/index.scss create mode 100644 src/store/moveGoalState.ts create mode 100644 src/store/useNavigateToSubgoal.ts diff --git a/src/common/Icon.tsx b/src/common/Icon.tsx index 86c0ade97..25b45e7a4 100644 --- a/src/common/Icon.tsx +++ b/src/common/Icon.tsx @@ -656,6 +656,23 @@ const Icon: React.FC = ({ title, active }) => { ); + case "Move": + return ( + + + + + + ); + default: return
; } diff --git a/src/common/ZButton.tsx b/src/common/ZButton.tsx new file mode 100644 index 000000000..2a9663155 --- /dev/null +++ b/src/common/ZButton.tsx @@ -0,0 +1,24 @@ +import { darkModeState } from "@src/store"; +import React from "react"; +import { useRecoilValue } from "recoil"; + +interface ZButtonProps { + children: React.ReactNode; + onClick?: () => void; + className?: string; +} + +const ZButton: React.FC = ({ children, onClick, className }) => { + const darkModeStatus = useRecoilValue(darkModeState); + + const defaultClassName = `default-btn${darkModeStatus ? "-dark" : ""}`; + const combinedClassName = className ? `${defaultClassName} ${className}` : defaultClassName; + + return ( + + ); +}; + +export default ZButton; diff --git a/src/components/GoalsComponents/MoveGoalButton.tsx b/src/components/GoalsComponents/MoveGoalButton.tsx new file mode 100644 index 000000000..2859c08d4 --- /dev/null +++ b/src/components/GoalsComponents/MoveGoalButton.tsx @@ -0,0 +1,41 @@ +import React from "react"; +import { useRecoilState, useSetRecoilState } from "recoil"; +import { moveGoalHierarchy } from "@src/helpers/GoalController"; +import ZButton from "@src/common/ZButton"; +import { GoalItem } from "@src/models/GoalItem"; +import { lastAction } from "@src/store"; +import { moveGoalState } from "@src/store/moveGoalState"; +import useNavigateToSubgoal from "@src/store/useNavigateToSubgoal"; + +interface GoalMoveButtonProps { + targetGoal: GoalItem; +} + +const GoalMoveButton: React.FC = ({ targetGoal }) => { + const navigateToSubgoal = useNavigateToSubgoal(); + const [selectedGoal, setSelectedGoal] = useRecoilState(moveGoalState); + const setLastAction = useSetRecoilState(lastAction); + + const handleClick = () => { + if (selectedGoal && targetGoal?.id) { + moveGoalHierarchy(selectedGoal.id, targetGoal.id) + .then(() => { + setLastAction("goalMoved"); + }) + .then(() => { + navigateToSubgoal(targetGoal); + }) + .finally(() => { + setSelectedGoal(null); + }); + } + }; + + return ( + + Move Here + + ); +}; + +export default GoalMoveButton; diff --git a/src/components/GoalsComponents/MyGoal/MoveGoalGuide.tsx b/src/components/GoalsComponents/MyGoal/MoveGoalGuide.tsx new file mode 100644 index 000000000..299527f5d --- /dev/null +++ b/src/components/GoalsComponents/MyGoal/MoveGoalGuide.tsx @@ -0,0 +1,33 @@ +import React, { MutableRefObject } from "react"; +import { Tour } from "antd"; +import type { TourProps } from "antd"; +import { useRecoilState } from "recoil"; +import { moveGoalState } from "@src/store/moveGoalState"; + +interface MoveGoalGuideProps { + goalComponentRef: MutableRefObject; +} + +const MoveGoalGuide: React.FC = ({ goalComponentRef }) => { + const [goalToMove, setGoalToMove] = useRecoilState(moveGoalState); + + const steps: TourProps["steps"] = [ + { + title: "Navigate to the goal you want to move into.", + target: () => goalComponentRef.current as HTMLElement, + nextButtonProps: { + children: "Close", + onClick: () => setGoalToMove(null), + }, + placement: "bottom", + className: "move-goal-guide", + }, + ]; + return ( +
+ setGoalToMove(null)} steps={steps} /> +
+ ); +}; + +export default MoveGoalGuide; diff --git a/src/components/GoalsComponents/MyGoal/MyGoal.tsx b/src/components/GoalsComponents/MyGoal/MyGoal.tsx index f4add9bfd..183e47c41 100644 --- a/src/components/GoalsComponents/MyGoal/MyGoal.tsx +++ b/src/components/GoalsComponents/MyGoal/MyGoal.tsx @@ -8,9 +8,11 @@ import { ILocationState, ImpossibleGoal } from "@src/Interfaces"; import { useParentGoalContext } from "@src/contexts/parentGoal-context"; import { extractLinks, isGoalCode } from "@src/utils/patterns"; import useGoalActions from "@src/hooks/useGoalActions"; +import { moveGoalState } from "@src/store/moveGoalState"; import GoalAvatar from "../GoalAvatar"; import GoalTitle from "./components/GoalTitle"; import GoalDropdown from "./components/GoalDropdown"; +import GoalMoveButton from "../MoveGoalButton"; interface MyGoalProps { goal: ImpossibleGoal; @@ -22,6 +24,10 @@ const MyGoal: React.FC = ({ goal, dragAttributes, dragListeners }) const { partnerId } = useParams(); const isPartnerModeActive = !!partnerId; + const goalToMove = useRecoilValue(moveGoalState); + + const shouldRenderMoveButton = goalToMove && goal.id !== goalToMove.id && goal.id !== goalToMove.parentGoalId; + const [expandGoalId, setExpandGoalId] = useState("root"); const [isAnimating, setIsAnimating] = useState(true); const { copyCode } = useGoalActions(); @@ -91,7 +97,7 @@ const MyGoal: React.FC = ({ goal, dragAttributes, dragListeners }) key={String(`goal-${goal.id}`)} className={`user-goal${darkModeStatus ? "-dark" : ""} ${ expandGoalId === goal.id && isAnimating ? "goal-glow" : "" - }`} + } ${goalToMove && goalToMove.id === goal.id ? "goal-to-move-selected" : ""}`} >
= ({ goal, dragAttributes, dragListeners })
handleGoalClick(e)}>
+ {shouldRenderMoveButton && }
- {!isPartnerModeActive && goal.participants?.length > 0 && } + {!shouldRenderMoveButton && !isPartnerModeActive && goal.participants?.length > 0 && }
); }; diff --git a/src/components/GoalsComponents/MyGoal/index.scss b/src/components/GoalsComponents/MyGoal/index.scss new file mode 100644 index 000000000..074d74f66 --- /dev/null +++ b/src/components/GoalsComponents/MyGoal/index.scss @@ -0,0 +1,20 @@ +.move-goal-guide { + width: 250px; + .ant-tour-title { + font-size: 14px; + } +} + +.user-goal-main { + .move-goal-button { + margin-top: 0; + margin-right: 10px; + align-self: center; + min-width: fit-content; + } +} + +.goal-to-move-selected { + opacity: 0.5; + pointer-events: none; +} diff --git a/src/components/GoalsComponents/MyGoalActions/RegularGoalActions.tsx b/src/components/GoalsComponents/MyGoalActions/RegularGoalActions.tsx index fed2f1dec..1f0e88025 100644 --- a/src/components/GoalsComponents/MyGoalActions/RegularGoalActions.tsx +++ b/src/components/GoalsComponents/MyGoalActions/RegularGoalActions.tsx @@ -28,7 +28,7 @@ const RegularGoalActions = ({ goal }: { goal: GoalItem }) => { const navigate = useNavigate(); const { t } = useTranslation(); const { partnerId } = useParams(); - const { openEditMode } = useGoalStore(); + const { openEditMode, handleMove } = useGoalStore(); const { state, pathname }: { state: ILocationState; pathname: string } = useLocation(); const { deleteGoalAction } = useGoalActions(); const isPartnerModeActive = !!partnerId; @@ -148,6 +148,16 @@ const RegularGoalActions = ({ goal }: { goal: GoalItem }) => { > + {!isPartnerModeActive && ( +
{ + handleMove(goal); + }} + > + +
+ )} ); diff --git a/src/helpers/GoalController.ts b/src/helpers/GoalController.ts index 9b77a574c..4aa6c817a 100644 --- a/src/helpers/GoalController.ts +++ b/src/helpers/GoalController.ts @@ -1,5 +1,5 @@ import { GoalItem } from "@src/models/GoalItem"; -import { getSelectedLanguage, inheritParentProps } from "@src/utils"; +import { inheritParentProps } from "@src/utils"; import { sendUpdatesToSubscriber } from "@src/services/contact.service"; import { getSharedWMGoal, removeSharedWMChildrenGoals, updateSharedWMGoal } from "@src/api/SharedWMAPI"; import { @@ -10,6 +10,7 @@ import { removeGoalWithChildrens, getParticipantsOfGoals, getHintsFromAPI, + getChildrenGoals, } from "@src/api/GoalsAPI"; import { addHintItem, updateHintItem } from "@src/api/HintsAPI"; import { restoreUserGoal } from "@src/api/TrashAPI"; @@ -95,3 +96,48 @@ export const deleteSharedGoal = async (goal: GoalItem) => { }); } }; + +const updateRootGoal = async (goalId: string, newRootGoalId: string) => { + await updateGoal(goalId, { rootGoalId: newRootGoalId }); + + const childrenGoals = await getChildrenGoals(goalId); + if (childrenGoals) { + childrenGoals.forEach(async (goal: GoalItem) => { + await updateRootGoal(goal.id, newRootGoalId); + }); + } +}; + +const removeGoalFromParentSublist = async (goalId: string, parentGoalId: string) => { + const parentGoal = await getGoal(parentGoalId); + if (!parentGoal) return; + + const parentGoalSublist = parentGoal.sublist; + const childGoalIndex = parentGoalSublist.indexOf(goalId); + if (childGoalIndex !== -1) { + parentGoalSublist.splice(childGoalIndex, 1); + } + await updateGoal(parentGoal.id, { sublist: parentGoalSublist }); +}; + +const addGoalToNewParentSublist = async (goalId: string, newParentGoalId: string) => { + const newParentGoal = await getGoal(newParentGoalId); + if (!newParentGoal) return; + + const newParentGoalSublist = newParentGoal.sublist; + newParentGoalSublist.push(goalId); + await updateGoal(newParentGoal.id, { sublist: newParentGoalSublist }); +}; + +export const moveGoalHierarchy = async (goalId: string, newParentGoalId: string) => { + const goalToMove = await getGoal(goalId); + const newParentGoal = await getGoal(newParentGoalId); + if (!goalToMove) return; + + await Promise.all([ + updateGoal(goalToMove.id, { parentGoalId: newParentGoalId }), + removeGoalFromParentSublist(goalToMove.id, goalToMove.parentGoalId), + addGoalToNewParentSublist(goalToMove.id, newParentGoalId), + updateRootGoal(goalToMove.id, newParentGoal?.rootGoalId ?? "root"), + ]); +}; diff --git a/src/hooks/useGoalStore.tsx b/src/hooks/useGoalStore.tsx index bb89e762c..691d548ab 100644 --- a/src/hooks/useGoalStore.tsx +++ b/src/hooks/useGoalStore.tsx @@ -1,13 +1,15 @@ import { useLocation, useNavigate, useParams } from "react-router-dom"; -import { useRecoilValue } from "recoil"; +import { useRecoilValue, useSetRecoilState } from "recoil"; import { GoalItem, TGoalCategory } from "@src/models/GoalItem"; import { displayConfirmation } from "@src/store"; +import { moveGoalState } from "@src/store/moveGoalState"; const useGoalStore = () => { const { partnerId } = useParams(); const navigate = useNavigate(); const location = useLocation(); const showConfirmation = useRecoilValue(displayConfirmation); + const setGoalToMove = useSetRecoilState(moveGoalState); const openEditMode = (goal: GoalItem) => { const prefix = `${partnerId ? `/partners/${partnerId}/` : "/"}goals`; @@ -28,10 +30,16 @@ const useGoalStore = () => { navigate("/goals", { state: location.state }); }; + const handleMove = (goal: GoalItem) => { + setGoalToMove(goal); + navigate("/MyGoals", { state: { ...location.state, displayGoalActions: null } }); + }; + return { openEditMode, handleConfirmation, handleDisplayChanges, + handleMove, }; }; diff --git a/src/pages/GoalsPage/MyGoals.tsx b/src/pages/GoalsPage/MyGoals.tsx index 4fb738aeb..d0e9c8504 100644 --- a/src/pages/GoalsPage/MyGoals.tsx +++ b/src/pages/GoalsPage/MyGoals.tsx @@ -1,4 +1,4 @@ -import React, { useState, useEffect, ChangeEvent, act } from "react"; +import React, { useState, useEffect, ChangeEvent, useRef } from "react"; import { useRecoilState, useRecoilValue } from "recoil"; import ZinZenTextLight from "@assets/images/LogoTextLight.svg"; @@ -34,6 +34,7 @@ import DeletedGoals from "./components/DeletedGoals"; import ArchivedGoals from "./components/ArchivedGoals"; import "./GoalsPage.scss"; +import MoveGoalGuide from "@components/GoalsComponents/MyGoal/MoveGoalGuide"; export const MyGoals = () => { let debounceTimeout: ReturnType; @@ -63,6 +64,8 @@ export const MyGoals = () => { const [action, setLastAction] = useRecoilState(lastAction); + const goalWrapperRef = useRef(null); + const getAllGoals = async () => { const [goals, delGoals] = await Promise.all([getActiveGoals("true"), getDeletedGoals("root")]); return { goals, delGoals }; @@ -127,7 +130,8 @@ export const MyGoals = () => { /> )} -
+
+ {parentId === "root" ? (
diff --git a/src/store/moveGoalState.ts b/src/store/moveGoalState.ts new file mode 100644 index 000000000..772022d52 --- /dev/null +++ b/src/store/moveGoalState.ts @@ -0,0 +1,7 @@ +import { GoalItem } from "@src/models/GoalItem"; +import { atom } from "recoil"; + +export const moveGoalState = atom({ + key: "moveGoalState", + default: null as GoalItem | null, +}); diff --git a/src/store/useNavigateToSubgoal.ts b/src/store/useNavigateToSubgoal.ts new file mode 100644 index 000000000..6064efbf1 --- /dev/null +++ b/src/store/useNavigateToSubgoal.ts @@ -0,0 +1,36 @@ +import { useNavigate, useLocation, useParams } from "react-router-dom"; +import { ILocationState } from "@src/Interfaces"; +import { GoalItem } from "@src/models/GoalItem"; + +const useNavigateToSubgoal = () => { + const navigate = useNavigate(); + const location = useLocation(); + + const { partnerId } = useParams(); + const isPartnerModeActive = !!partnerId; + + const navigateToSubgoal = (goal: GoalItem | null) => { + if (!goal) { + return; + } + const newState: ILocationState = { + ...location.state, + activeGoalId: goal.id, + goalsHistory: [ + ...(location.state?.goalsHistory || []), + { + goalID: goal.id || "root", + goalColor: goal.goalColor || "#ffffff", + goalTitle: goal.title || "", + }, + ], + }; + const prefix = `${isPartnerModeActive ? `/partners/${partnerId}/` : "/"}goals`; + + navigate(`${prefix}/${goal.id}`, { state: newState }); + }; + + return navigateToSubgoal; +}; + +export default useNavigateToSubgoal; From 1df4ea4ce99275987cd3bfc31dc3c5e48b281a00 Mon Sep 17 00:00:00 2001 From: Vinay Badgujar Date: Tue, 15 Oct 2024 15:33:23 +0530 Subject: [PATCH 04/54] fix: location state problem while coming out of move state --- src/hooks/useGoalStore.tsx | 2 +- src/store/useNavigateToSubgoal.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/hooks/useGoalStore.tsx b/src/hooks/useGoalStore.tsx index 691d548ab..819a78bf7 100644 --- a/src/hooks/useGoalStore.tsx +++ b/src/hooks/useGoalStore.tsx @@ -32,7 +32,7 @@ const useGoalStore = () => { const handleMove = (goal: GoalItem) => { setGoalToMove(goal); - navigate("/MyGoals", { state: { ...location.state, displayGoalActions: null } }); + navigate("/goals", { replace: true }); }; return { diff --git a/src/store/useNavigateToSubgoal.ts b/src/store/useNavigateToSubgoal.ts index 6064efbf1..c0fb3e98f 100644 --- a/src/store/useNavigateToSubgoal.ts +++ b/src/store/useNavigateToSubgoal.ts @@ -27,7 +27,7 @@ const useNavigateToSubgoal = () => { }; const prefix = `${isPartnerModeActive ? `/partners/${partnerId}/` : "/"}goals`; - navigate(`${prefix}/${goal.id}`, { state: newState }); + navigate(`${prefix}/${goal.id}`, { state: newState, replace: true }); }; return navigateToSubgoal; From 46c1ba23c5c404d520963fe5487d342197fa2ff4 Mon Sep 17 00:00:00 2001 From: Vinay Badgujar Date: Thu, 24 Oct 2024 08:35:09 +0530 Subject: [PATCH 05/54] feat(collaboration): Implement goal creation suggestion in reciever side when a goal is moved into shared goal --- src/helpers/GoalController.ts | 85 +++++++++++++++++++++++++++++++++++ src/hooks/useGoalActions.tsx | 8 +++- 2 files changed, 91 insertions(+), 2 deletions(-) diff --git a/src/helpers/GoalController.ts b/src/helpers/GoalController.ts index 26a8eb3ae..56c49ecb4 100644 --- a/src/helpers/GoalController.ts +++ b/src/helpers/GoalController.ts @@ -1,3 +1,4 @@ +/* eslint-disable no-await-in-loop */ import { GoalItem } from "@src/models/GoalItem"; import { inheritParentProps } from "@src/utils"; import { sendUpdatesToSubscriber } from "@src/services/contact.service"; @@ -48,6 +49,86 @@ export const createGoal = async (newGoal: GoalItem, parentGoalId: string, ancest return { parentGoal: null }; }; +export const getGoalAncestors = async (goalId: string): Promise => { + const ancestors: string[] = []; + let currentGoalId = goalId; + + while (currentGoalId !== "root") { + const currentGoal = await getGoal(currentGoalId); + if (!currentGoal || currentGoal.parentGoalId === "root") break; + + ancestors.unshift(currentGoal.parentGoalId); + currentGoalId = currentGoal.parentGoalId; + } + + return ancestors; +}; + +const getAllDescendants = async (goalId: string): Promise => { + const descendants: GoalItem[] = []; + + const processGoalAndChildren = async (currentGoalId: string) => { + const childrenGoals = await getChildrenGoals(currentGoalId); + await Promise.all( + childrenGoals.map(async (childGoal) => { + descendants.push(childGoal); + await processGoalAndChildren(childGoal.id); + }), + ); + }; + + await processGoalAndChildren(goalId); + return descendants; +}; + +export const createSharedGoal = async (newGoal: GoalItem, parentGoalId: string, ancestors: string[]) => { + const level = ancestors.length; + + if (parentGoalId && parentGoalId !== "root") { + const parentGoal = await getGoal(parentGoalId); + if (!parentGoal) { + console.log("Parent goal not found"); + return { parentGoal: null }; + } + + const newGoalId = newGoal.id; + const subscribers = await getParticipantsOfGoals(ancestors); + + if (newGoalId) { + subscribers.map(async ({ sub, rootGoalId }) => { + await sendUpdatesToSubscriber(sub, rootGoalId, "subgoals", [ + { + level, + goal: { ...newGoal, id: newGoalId, parentGoalId }, + }, + ]); + }); + + const descendants = await getAllDescendants(newGoalId); + if (descendants.length > 0) { + subscribers.map(async ({ sub, rootGoalId }) => { + await sendUpdatesToSubscriber( + sub, + rootGoalId, + "subgoals", + descendants.map((descendant) => ({ + level: level + 1, + goal: { + ...descendant, + rootGoalId, + }, + })), + ); + }); + } + + console.log("Updates sent successfully"); + } + return { parentGoal }; + } + return { parentGoal: null }; +}; + export const modifyGoal = async ( goalId: string, goalTags: GoalItem, @@ -139,10 +220,14 @@ export const moveGoalHierarchy = async (goalId: string, newParentGoalId: string) const newParentGoal = await getGoal(newParentGoalId); if (!goalToMove) return; + const ancestors = await getGoalAncestors(newParentGoalId); + await Promise.all([ updateGoal(goalToMove.id, { parentGoalId: newParentGoalId }), removeGoalFromParentSublist(goalToMove.id, goalToMove.parentGoalId), addGoalToNewParentSublist(goalToMove.id, newParentGoalId), updateRootGoal(goalToMove.id, newParentGoal?.rootGoalId ?? "root"), ]); + + createSharedGoal(goalToMove, newParentGoal?.id ?? "root", [...ancestors, newParentGoalId]); }; diff --git a/src/hooks/useGoalActions.tsx b/src/hooks/useGoalActions.tsx index d46a7a965..c56d13e9c 100644 --- a/src/hooks/useGoalActions.tsx +++ b/src/hooks/useGoalActions.tsx @@ -1,7 +1,7 @@ import { getAllLevelGoalsOfId, unarchiveUserGoal, updateSharedStatusOfGoal } from "@src/api/GoalsAPI"; import { getSharedWMGoalById } from "@src/api/SharedWMAPI"; import { restoreUserGoal } from "@src/api/TrashAPI"; -import { createGoal, deleteGoal, deleteSharedGoal, modifyGoal } from "@src/helpers/GoalController"; +import { createGoal, createSharedGoal, deleteGoal, deleteSharedGoal, modifyGoal } from "@src/helpers/GoalController"; import { suggestChanges, suggestNewGoal } from "@src/helpers/PartnerController"; import { GoalItem } from "@src/models/GoalItem"; import { displayToast, lastAction, openDevMode } from "@src/store"; @@ -117,6 +117,10 @@ const useGoalActions = () => { } }; + const addSharedGoal = async (newGoal: GoalItem, parentGoalId: string) => { + await createSharedGoal(newGoal, parentGoalId, ancestors); + }; + const shareGoalWithRelId = async (relId: string, name: string, goal: GoalItem) => { const goalWithChildrens = await getAllLevelGoalsOfId(goal.id, true); await shareGoalWithContact(relId, [ @@ -155,7 +159,6 @@ const useGoalActions = () => { goalTitle = `${goalTitle} copied!`; showMessage("Code copied to clipboard", goalTitle); }; - return { addGoal, deleteGoalAction, @@ -165,6 +168,7 @@ const useGoalActions = () => { shareGoalWithRelId, addContact, copyCode, + addSharedGoal, }; }; From 21d4a0728b260ae553d9808aed0b223f0986989d Mon Sep 17 00:00:00 2001 From: Vinay Badgujar Date: Thu, 24 Oct 2024 12:40:28 +0530 Subject: [PATCH 06/54] feat(collaboration): Add handling of shared child goal which is moved out of shared parent --- src/helpers/GoalController.ts | 13 ++++++++++++- src/helpers/InboxProcessor.ts | 23 ++++++++++++++++------- src/helpers/PubSubController.ts | 27 +++++++++++++++++++-------- 3 files changed, 47 insertions(+), 16 deletions(-) diff --git a/src/helpers/GoalController.ts b/src/helpers/GoalController.ts index 56c49ecb4..728699d51 100644 --- a/src/helpers/GoalController.ts +++ b/src/helpers/GoalController.ts @@ -218,9 +218,11 @@ const addGoalToNewParentSublist = async (goalId: string, newParentGoalId: string export const moveGoalHierarchy = async (goalId: string, newParentGoalId: string) => { const goalToMove = await getGoal(goalId); const newParentGoal = await getGoal(newParentGoalId); + const oldParentGoal = goalToMove ? await getGoal(goalToMove.parentGoalId) : null; if (!goalToMove) return; const ancestors = await getGoalAncestors(newParentGoalId); + console.log("Ancestors:", ancestors); await Promise.all([ updateGoal(goalToMove.id, { parentGoalId: newParentGoalId }), @@ -229,5 +231,14 @@ export const moveGoalHierarchy = async (goalId: string, newParentGoalId: string) updateRootGoal(goalToMove.id, newParentGoal?.rootGoalId ?? "root"), ]); - createSharedGoal(goalToMove, newParentGoal?.id ?? "root", [...ancestors, newParentGoalId]); + if ( + oldParentGoal?.participants?.length > 0 && + (!newParentGoal?.participants || newParentGoal.participants.length === 0) + ) { + console.log("Sending delete update"); + console.log("Ancestors:", ancestors); + sendFinalUpdateOnGoal(goalToMove.id, "deleted", [...ancestors, goalToMove.parentGoalId, goalToMove.id], false); + } else { + createSharedGoal(goalToMove, newParentGoal?.id ?? "root", [...ancestors, newParentGoalId]); + } }; diff --git a/src/helpers/InboxProcessor.ts b/src/helpers/InboxProcessor.ts index c6d56cbbb..dffc19722 100644 --- a/src/helpers/InboxProcessor.ts +++ b/src/helpers/InboxProcessor.ts @@ -81,14 +81,23 @@ export const handleIncomingChanges = async (payload: Payload, relId: string) => } else if (["sharer", "suggestion"].includes(payload.type)) { const { rootGoalId, changes, changeType } = payload; const rootGoal = await getGoal(rootGoalId); + let allAreLatest: (changesInGoal | null)[] = []; if (rootGoal) { - const allAreLatest = await Promise.all( - changes.map(async (ele) => { - const isLatest = await isIncomingGoalLatest(ele.goal.id, ele.goal); - console.log(`Goal ID: ${ele.goal.id} is latest: ${isLatest}`); - return isLatest ? ele : null; - }), - ); + if (payload.changeType === "deleted") { + allAreLatest = await Promise.all( + changes.map(async (ele) => { + const isLatest = true; + return isLatest ? ele : null; + }), + ); + } else { + allAreLatest = await Promise.all( + changes.map(async (ele) => { + const isLatest = await isIncomingGoalLatest(ele.goal.id, ele.goal); + return isLatest ? ele : null; + }), + ); + } const filteredChanges = allAreLatest.filter((ele) => ele !== null); diff --git a/src/helpers/PubSubController.ts b/src/helpers/PubSubController.ts index a1c7ca09f..394df3052 100644 --- a/src/helpers/PubSubController.ts +++ b/src/helpers/PubSubController.ts @@ -35,18 +35,29 @@ export const sendFinalUpdateOnGoal = async ( redefineAncestors = true, excludeSubs: string[] = [], ) => { + console.log(`[sendFinalUpdateOnGoal] Starting for goalId: ${goalId}, action: ${action}`); + const ancestorGoalIds = redefineAncestors ? (await getHistoryUptoGoal(goalId)).map((ele) => ele.goalID) : ancestors; + console.log("[sendFinalUpdateOnGoal] Ancestor IDs:", ancestorGoalIds); + const subscribers = await getParticipantsOfGoals(ancestorGoalIds); + console.log("[sendFinalUpdateOnGoal] Initial subscribers:", subscribers.length); + if (action === "restored") { - (await getParticipantsOfDeletedGoal(goalId)).forEach((doc) => { + const deletedGoalParticipants = await getParticipantsOfDeletedGoal(goalId); + console.log("[sendFinalUpdateOnGoal] Additional restored participants:", deletedGoalParticipants.length); + deletedGoalParticipants.forEach((doc) => { subscribers.push(doc); }); } - subscribers - .filter((ele) => !excludeSubs.includes(ele.sub.relId)) - .forEach(async ({ sub, rootGoalId }) => { - sendUpdatesToSubscriber(sub, rootGoalId, action, [{ level: ancestorGoalIds.length, id: goalId }]).then(() => - console.log("update sent"), - ); - }); + + const filteredSubscribers = subscribers.filter((ele) => !excludeSubs.includes(ele.sub.relId)); + console.log("[sendFinalUpdateOnGoal] Filtered subscribers:", filteredSubscribers.length); + + filteredSubscribers.forEach(async ({ sub, rootGoalId }) => { + console.log(`[sendFinalUpdateOnGoal] Sending update to subscriber ${sub.relId} for root goal ${rootGoalId}`); + sendUpdatesToSubscriber(sub, rootGoalId, action, [{ level: ancestorGoalIds.length, id: goalId }]) + .then(() => console.log(`[sendFinalUpdateOnGoal] Update sent successfully to ${sub.relId}`)) + .catch((error) => console.error(`[sendFinalUpdateOnGoal] Error sending update to ${sub.relId}:`, error)); + }); }; From 7518be39911e088fb5f06385e8d31e8c3443949c Mon Sep 17 00:00:00 2001 From: Vinay Badgujar Date: Thu, 24 Oct 2024 14:00:04 +0530 Subject: [PATCH 07/54] fix: move goal out of shared goal issue --- src/helpers/GoalController.ts | 34 +++++++++++++++---- .../GoalsPage/components/ArchivedGoals.tsx | 1 - 2 files changed, 27 insertions(+), 8 deletions(-) diff --git a/src/helpers/GoalController.ts b/src/helpers/GoalController.ts index 728699d51..1eb56a968 100644 --- a/src/helpers/GoalController.ts +++ b/src/helpers/GoalController.ts @@ -215,13 +215,31 @@ const addGoalToNewParentSublist = async (goalId: string, newParentGoalId: string await updateGoal(newParentGoal.id, { sublist: newParentGoalSublist }); }; +export const getGoalHistoryToRoot = async (goalId: string): Promise<{ goalID: string; title: string }[]> => { + const history: { goalID: string; title: string }[] = []; + let currentGoalId = goalId; + + while (currentGoalId !== "root") { + const currentGoal = await getGoal(currentGoalId); + + if (!currentGoal) { + break; + } + + history.unshift({ goalID: currentGoal.id, title: currentGoal.title }); + currentGoalId = currentGoal.parentGoalId; + } + + return history; +}; + export const moveGoalHierarchy = async (goalId: string, newParentGoalId: string) => { const goalToMove = await getGoal(goalId); const newParentGoal = await getGoal(newParentGoalId); - const oldParentGoal = goalToMove ? await getGoal(goalToMove.parentGoalId) : null; if (!goalToMove) return; - const ancestors = await getGoalAncestors(newParentGoalId); + const ancestors = await getGoalHistoryToRoot(goalId); + const ancestorGoalIds = ancestors.map((ele) => ele.goalID); console.log("Ancestors:", ancestors); await Promise.all([ @@ -231,13 +249,15 @@ export const moveGoalHierarchy = async (goalId: string, newParentGoalId: string) updateRootGoal(goalToMove.id, newParentGoal?.rootGoalId ?? "root"), ]); - if ( - oldParentGoal?.participants?.length > 0 && - (!newParentGoal?.participants || newParentGoal.participants.length === 0) - ) { + if (true) { console.log("Sending delete update"); console.log("Ancestors:", ancestors); - sendFinalUpdateOnGoal(goalToMove.id, "deleted", [...ancestors, goalToMove.parentGoalId, goalToMove.id], false); + sendFinalUpdateOnGoal( + goalToMove.id, + "deleted", + [...ancestorGoalIds, goalToMove.parentGoalId, goalToMove.id], + false, + ); } else { createSharedGoal(goalToMove, newParentGoal?.id ?? "root", [...ancestors, newParentGoalId]); } diff --git a/src/pages/GoalsPage/components/ArchivedGoals.tsx b/src/pages/GoalsPage/components/ArchivedGoals.tsx index 610080cba..386834f8d 100644 --- a/src/pages/GoalsPage/components/ArchivedGoals.tsx +++ b/src/pages/GoalsPage/components/ArchivedGoals.tsx @@ -78,7 +78,6 @@ const ArchivedGoals = ({ goals }: { goals: GoalItem[] }) => { const [searchParams] = useSearchParams(); const { goal: activeGoal } = useActiveGoalContext(); const showOptions = !!searchParams.get("showOptions") && activeGoal?.archived === "true"; - console.log("🚀 ~ ArchivedGoals ~ showOptions:", showOptions); return ( <> From 86c5264bde3e71a03c6f067969bbc0da6511c6c3 Mon Sep 17 00:00:00 2001 From: Vinay Badgujar Date: Thu, 24 Oct 2024 15:00:17 +0530 Subject: [PATCH 08/54] add newGoalMoved action --- .../DisplayChangesModal/AcceptBtn.tsx | 1 + .../DisplayChangesModal.tsx | 11 +++++---- .../DisplayChangesModal/Header.tsx | 12 ++++++++++ src/helpers/GoalController.ts | 23 ++++++++----------- src/helpers/GoalProcessor.ts | 17 +++++++++----- src/helpers/InboxProcessor.ts | 2 +- src/helpers/PubSubController.ts | 2 +- src/models/InboxItem.ts | 10 +++++++- src/utils/defaultGenerators.ts | 1 + 9 files changed, 52 insertions(+), 27 deletions(-) diff --git a/src/components/GoalsComponents/DisplayChangesModal/AcceptBtn.tsx b/src/components/GoalsComponents/DisplayChangesModal/AcceptBtn.tsx index 0b2f87fa5..6fb75b5e8 100644 --- a/src/components/GoalsComponents/DisplayChangesModal/AcceptBtn.tsx +++ b/src/components/GoalsComponents/DisplayChangesModal/AcceptBtn.tsx @@ -35,6 +35,7 @@ const AcceptBtn = ({ typeAtPriority, acceptChanges }: AcceptBtnProps) => { {typeAtPriority === "deleted" && "Delete for me too"} {typeAtPriority === "subgoals" && "Add all checked"} {typeAtPriority === "modifiedGoals" && "Make all checked changes"} + {typeAtPriority === "newGoalMoved" && "Move for me too"} ); }; diff --git a/src/components/GoalsComponents/DisplayChangesModal/DisplayChangesModal.tsx b/src/components/GoalsComponents/DisplayChangesModal/DisplayChangesModal.tsx index 24df73de1..56a404780 100644 --- a/src/components/GoalsComponents/DisplayChangesModal/DisplayChangesModal.tsx +++ b/src/components/GoalsComponents/DisplayChangesModal/DisplayChangesModal.tsx @@ -125,7 +125,10 @@ const DisplayChangesModal = ({ currentMainGoal }: { currentMainGoal: GoalItem }) if (!goalUnderReview || !currentMainGoal) { return; } - const removeChanges = currentDisplay === "subgoals" ? newGoals.map(({ goal }) => goal.id) : [goalUnderReview.id]; + const removeChanges = + currentDisplay === "subgoals" || currentDisplay === "newGoalMoved" + ? newGoals.map(({ goal }) => goal.id) + : [goalUnderReview.id]; if (currentDisplay !== "none") { await deleteGoalChangesInID(currentMainGoal.id, participants[activePPT].relId, currentDisplay, removeChanges); } @@ -139,7 +142,7 @@ const DisplayChangesModal = ({ currentMainGoal }: { currentMainGoal: GoalItem }) if (currentDisplay !== "none") { await deleteChanges(); } - if (currentDisplay === "subgoals") { + if (currentDisplay === "subgoals" || currentDisplay === "newGoalMoved") { const goalsToBeSelected = newGoals .filter(({ goal }) => !unselectedChanges.includes(goal.id)) .map(({ goal }) => goal); @@ -205,7 +208,7 @@ const DisplayChangesModal = ({ currentMainGoal }: { currentMainGoal: GoalItem }) console.log("🚀 ~ getChanges ~ changedGoal:", changedGoal); if (changedGoal) { setGoalUnderReview({ ...changedGoal }); - if (typeAtPriority === "subgoals") { + if (typeAtPriority === "subgoals" || typeAtPriority === "newGoalMoved") { setNewGoals(goals || []); } else if (typeAtPriority === "modifiedGoals") { setUpdatesIntent(goals[0].intent); @@ -289,7 +292,7 @@ const DisplayChangesModal = ({ currentMainGoal }: { currentMainGoal: GoalItem }) )} {["deleted", "archived", "restored"].includes(currentDisplay) &&
} {currentDisplay === "modifiedGoals" && getEditChangesList()} - {currentDisplay === "subgoals" && getSubgoalsList()} + {(currentDisplay === "subgoals" || currentDisplay === "newGoalMoved") && getSubgoalsList()}
{goalUnderReview && ( diff --git a/src/components/GoalsComponents/DisplayChangesModal/Header.tsx b/src/components/GoalsComponents/DisplayChangesModal/Header.tsx index 16f5cc644..3288e71ec 100644 --- a/src/components/GoalsComponents/DisplayChangesModal/Header.tsx +++ b/src/components/GoalsComponents/DisplayChangesModal/Header.tsx @@ -42,6 +42,18 @@ const Header = ({ {contactName} restored {title}. ); + case "moved": + return ( + <> + {contactName} moved {title}. + + ); + case "newGoalMoved": + return ( + <> + {contactName} moved {title} to your goal. + + ); default: return <> ; } diff --git a/src/helpers/GoalController.ts b/src/helpers/GoalController.ts index 1eb56a968..70a9b1660 100644 --- a/src/helpers/GoalController.ts +++ b/src/helpers/GoalController.ts @@ -96,7 +96,7 @@ export const createSharedGoal = async (newGoal: GoalItem, parentGoalId: string, if (newGoalId) { subscribers.map(async ({ sub, rootGoalId }) => { - await sendUpdatesToSubscriber(sub, rootGoalId, "subgoals", [ + await sendUpdatesToSubscriber(sub, rootGoalId, "newGoalMoved", [ { level, goal: { ...newGoal, id: newGoalId, parentGoalId }, @@ -110,7 +110,7 @@ export const createSharedGoal = async (newGoal: GoalItem, parentGoalId: string, await sendUpdatesToSubscriber( sub, rootGoalId, - "subgoals", + "newGoalMoved", descendants.map((descendant) => ({ level: level + 1, goal: { @@ -249,16 +249,11 @@ export const moveGoalHierarchy = async (goalId: string, newParentGoalId: string) updateRootGoal(goalToMove.id, newParentGoal?.rootGoalId ?? "root"), ]); - if (true) { - console.log("Sending delete update"); - console.log("Ancestors:", ancestors); - sendFinalUpdateOnGoal( - goalToMove.id, - "deleted", - [...ancestorGoalIds, goalToMove.parentGoalId, goalToMove.id], - false, - ); - } else { - createSharedGoal(goalToMove, newParentGoal?.id ?? "root", [...ancestors, newParentGoalId]); - } + // if (true) { + // console.log("Sending delete update"); + // console.log("Ancestors:", ancestors); + // sendFinalUpdateOnGoal(goalToMove.id, "moved", [...ancestorGoalIds, goalToMove.parentGoalId, goalToMove.id], false); + // } else { + createSharedGoal(goalToMove, newParentGoal?.id ?? "root", [...ancestorGoalIds, newParentGoalId]); + // } }; diff --git a/src/helpers/GoalProcessor.ts b/src/helpers/GoalProcessor.ts index 228cb023f..89a98cb9e 100644 --- a/src/helpers/GoalProcessor.ts +++ b/src/helpers/GoalProcessor.ts @@ -112,6 +112,8 @@ export const getTypeAtPriority = (goalChanges: IChangesInGoal) => { typeAtPriority = "deleted"; } else if (goalChanges.restored.length > 0) { typeAtPriority = "restored"; + } else if (goalChanges.newGoalMoved.length > 0) { + typeAtPriority = "newGoalMoved"; } return { typeAtPriority }; }; @@ -129,15 +131,16 @@ export const jumpToLowestChanges = async (id: string, relId: string) => { const parentId = "id" in goalAtPriority ? goalAtPriority.id - : typeAtPriority === "subgoals" + : typeAtPriority === "subgoals" || typeAtPriority === "newGoalMoved" ? goalAtPriority.goal.parentGoalId : goalAtPriority.goal.id; if (typeAtPriority === "archived" || typeAtPriority === "deleted") { - return { typeAtPriority, parentId, goals: [await getGoal(parentId)] }; + const result = { typeAtPriority, parentId, goals: [await getGoal(parentId)] }; + return result; } - if (typeAtPriority === "subgoals") { - goalChanges.subgoals.forEach(({ intent, goal }) => { + if (typeAtPriority === "subgoals" || typeAtPriority === "newGoalMoved") { + goalChanges[typeAtPriority].forEach(({ intent, goal }) => { if (goal.parentGoalId === parentId) goals.push({ intent, goal }); }); } @@ -153,16 +156,18 @@ export const jumpToLowestChanges = async (id: string, relId: string) => { goals = [{ intent: goalIntent, goal: modifiedGoal }]; } - return { + const result = { typeAtPriority, parentId, goals, }; + return result; } } else { console.log("inbox item doesn't exist"); } - return { typeAtPriority, parentId: "", goals: [] }; + const defaultResult = { typeAtPriority, parentId: "", goals: [] }; + return defaultResult; }; export const findGoalTagChanges = (goal1: GoalItem, goal2: GoalItem) => { diff --git a/src/helpers/InboxProcessor.ts b/src/helpers/InboxProcessor.ts index dffc19722..5655287d8 100644 --- a/src/helpers/InboxProcessor.ts +++ b/src/helpers/InboxProcessor.ts @@ -43,7 +43,7 @@ export const handleIncomingChanges = async (payload: Payload, relId: string) => console.log("Changes ignored"); return; } - if (payload.changeType === "subgoals") { + if (payload.changeType === "subgoals" || payload.changeType === "newGoalMoved") { const changes = [ ...payload.changes.map((ele: changesInGoal) => ({ ...ele, goal: fixDateVlauesInGoalObject(ele.goal) })), ]; diff --git a/src/helpers/PubSubController.ts b/src/helpers/PubSubController.ts index 394df3052..9eb4b9e5f 100644 --- a/src/helpers/PubSubController.ts +++ b/src/helpers/PubSubController.ts @@ -30,7 +30,7 @@ export const sendUpdatedGoal = async ( export const sendFinalUpdateOnGoal = async ( goalId: string, - action: "archived" | "deleted" | "restored", + action: "archived" | "deleted" | "restored" | "moved", ancestors: string[] = [], redefineAncestors = true, excludeSubs: string[] = [], diff --git a/src/models/InboxItem.ts b/src/models/InboxItem.ts index d8e1358be..abfac1b5a 100644 --- a/src/models/InboxItem.ts +++ b/src/models/InboxItem.ts @@ -1,6 +1,13 @@ import { GoalItem } from "./GoalItem"; -export type typeOfChange = "subgoals" | "modifiedGoals" | "archived" | "deleted" | "restored"; +export type typeOfChange = + | "subgoals" + | "modifiedGoals" + | "archived" + | "deleted" + | "restored" + | "moved" + | "newGoalMoved"; export type typeOfIntent = "suggestion" | "shared"; export type changesInId = { level: number; id: string; intent: typeOfIntent }; @@ -12,6 +19,7 @@ export interface IChangesInGoal { archived: changesInId[]; deleted: changesInId[]; restored: changesInId[]; + newGoalMoved: changesInGoal[]; } export interface InboxItem { diff --git a/src/utils/defaultGenerators.ts b/src/utils/defaultGenerators.ts index 4d71305e7..5573bb3f0 100644 --- a/src/utils/defaultGenerators.ts +++ b/src/utils/defaultGenerators.ts @@ -28,5 +28,6 @@ export function getDefaultValueOfGoalChanges() { archived: [], deleted: [], restored: [], + newGoalMoved: [], }; } From 6209ebe599c650ee645887e68b3886a1b5cf1c27 Mon Sep 17 00:00:00 2001 From: Vinay Badgujar Date: Fri, 25 Oct 2024 09:51:27 +0530 Subject: [PATCH 09/54] feat(move): handle goal move in collaborated goal --- .../DisplayChangesModal.tsx | 80 +++++++++++++++++++ src/helpers/GoalController.ts | 48 ++++++++--- src/helpers/GoalProcessor.ts | 8 +- src/helpers/InboxProcessor.ts | 2 +- src/models/InboxItem.ts | 1 + src/utils/defaultGenerators.ts | 1 + 6 files changed, 124 insertions(+), 16 deletions(-) diff --git a/src/components/GoalsComponents/DisplayChangesModal/DisplayChangesModal.tsx b/src/components/GoalsComponents/DisplayChangesModal/DisplayChangesModal.tsx index 56a404780..dd6f910d5 100644 --- a/src/components/GoalsComponents/DisplayChangesModal/DisplayChangesModal.tsx +++ b/src/components/GoalsComponents/DisplayChangesModal/DisplayChangesModal.tsx @@ -19,6 +19,7 @@ import SubHeader from "@src/common/SubHeader"; import ContactItem from "@src/models/ContactItem"; import ZModal from "@src/common/ZModal"; +import { addGoalToNewParentSublist, removeGoalFromParentSublist } from "@src/helpers/GoalController"; import Header from "./Header"; import AcceptBtn from "./AcceptBtn"; import IgnoreBtn from "./IgnoreBtn"; @@ -121,6 +122,36 @@ const DisplayChangesModal = ({ currentMainGoal }: { currentMainGoal: GoalItem }) ); }; + const getMovedSubgoalsList = () => { + if (!goalUnderReview) return null; + + return ( +
+
+
+ Old Parent Goal: + {goalUnderReview.oldParentId === "root" ? "Root" : goalUnderReview.oldParentId} +
+
+ New Parent Goal: + {goalUnderReview.parentGoalId === "root" ? "Root" : goalUnderReview.parentGoalId} +
+
+ Goal Being Moved: + {goalUnderReview.title} +
+
+
+ ); + }; + const deleteChanges = async () => { if (!goalUnderReview || !currentMainGoal) { return; @@ -142,6 +173,50 @@ const DisplayChangesModal = ({ currentMainGoal }: { currentMainGoal: GoalItem }) if (currentDisplay !== "none") { await deleteChanges(); } + if (currentDisplay === "moved") { + console.log(`[Move Goal] Starting move process for goal: ${goalUnderReview.id} (${goalUnderReview.title})`); + console.log(`[Move Goal] Old parent: ${goalUnderReview.oldParentId}`); + console.log(`[Move Goal] New parent: ${goalUnderReview.parentGoalId}`); + + const parentGoal = await getGoal(goalUnderReview.parentGoalId); + console.log( + `[Move Goal] New parent goal found:`, + parentGoal ? `${parentGoal.id} (${parentGoal.title})` : "not found", + ); + + // If parent goal doesn't exist, it means the goal was moved out of shared hierarchy + if (!parentGoal) { + console.log(`[Move Goal] Parent goal is not shared or doesn't exist. Removing goal and its children.`); + // Remove the goal and its children from the database + await removeGoalWithChildrens(goalUnderReview); + console.log(`[Move Goal] Successfully removed goal and its children`); + } else { + console.log(`[Move Goal] Moving goal within shared hierarchy`); + // Goal was moved within shared hierarchy, update its position + await Promise.all([ + updateGoal(goalUnderReview.id, { parentGoalId: parentGoal.id }), + removeGoalFromParentSublist(goalUnderReview.id, goalUnderReview.oldParentId), + addGoalToNewParentSublist(goalUnderReview.id, parentGoal.id), + ]).then(() => { + console.log(`[Move Goal] Successfully updated goal position:`); + console.log(` - Updated parentGoalId to: ${parentGoal.id}`); + console.log(` - Removed from old parent: ${goalUnderReview.oldParentId}`); + console.log(` - Added to new parent: ${parentGoal.id}`); + }); + + // Send update to all participants + console.log( + `[Move Goal] Sending updates to participants. Excluding: ${updatesIntent === "suggestion" ? participants[activePPT].relId : "none"}`, + ); + await sendUpdatedGoal( + goalUnderReview.id, + [], + true, + updatesIntent === "suggestion" ? [] : [participants[activePPT].relId], + ).then(() => console.log(`[Move Goal] Successfully sent updates to participants`)); + } + console.log(`[Move Goal] Move process completed for goal: ${goalUnderReview.id} (${goalUnderReview.title})`); + } if (currentDisplay === "subgoals" || currentDisplay === "newGoalMoved") { const goalsToBeSelected = newGoals .filter(({ goal }) => !unselectedChanges.includes(goal.id)) @@ -214,6 +289,10 @@ const DisplayChangesModal = ({ currentMainGoal }: { currentMainGoal: GoalItem }) setUpdatesIntent(goals[0].intent); const incGoal: GoalItem = { ...goals[0].goal }; setUpdateList({ ...findGoalTagChanges(changedGoal, incGoal) }); + } else if (typeAtPriority === "moved") { + const movedGoal = goals[0].goal; + setUpdatesIntent(goals[0].intent); + setGoalUnderReview({ ...movedGoal }); } } } @@ -293,6 +372,7 @@ const DisplayChangesModal = ({ currentMainGoal }: { currentMainGoal: GoalItem }) {["deleted", "archived", "restored"].includes(currentDisplay) &&
} {currentDisplay === "modifiedGoals" && getEditChangesList()} {(currentDisplay === "subgoals" || currentDisplay === "newGoalMoved") && getSubgoalsList()} + {currentDisplay === "moved" && getMovedSubgoalsList()}
{goalUnderReview && ( diff --git a/src/helpers/GoalController.ts b/src/helpers/GoalController.ts index 70a9b1660..c7eb55a43 100644 --- a/src/helpers/GoalController.ts +++ b/src/helpers/GoalController.ts @@ -194,7 +194,7 @@ const updateRootGoal = async (goalId: string, newRootGoalId: string) => { } }; -const removeGoalFromParentSublist = async (goalId: string, parentGoalId: string) => { +export const removeGoalFromParentSublist = async (goalId: string, parentGoalId: string) => { const parentGoal = await getGoal(parentGoalId); if (!parentGoal) return; @@ -206,7 +206,7 @@ const removeGoalFromParentSublist = async (goalId: string, parentGoalId: string) await updateGoal(parentGoal.id, { sublist: parentGoalSublist }); }; -const addGoalToNewParentSublist = async (goalId: string, newParentGoalId: string) => { +export const addGoalToNewParentSublist = async (goalId: string, newParentGoalId: string) => { const newParentGoal = await getGoal(newParentGoalId); if (!newParentGoal) return; @@ -238,22 +238,48 @@ export const moveGoalHierarchy = async (goalId: string, newParentGoalId: string) const newParentGoal = await getGoal(newParentGoalId); if (!goalToMove) return; + const oldParentId = goalToMove.parentGoalId; const ancestors = await getGoalHistoryToRoot(goalId); const ancestorGoalIds = ancestors.map((ele) => ele.goalID); - console.log("Ancestors:", ancestors); await Promise.all([ updateGoal(goalToMove.id, { parentGoalId: newParentGoalId }), - removeGoalFromParentSublist(goalToMove.id, goalToMove.parentGoalId), + removeGoalFromParentSublist(goalToMove.id, oldParentId), addGoalToNewParentSublist(goalToMove.id, newParentGoalId), updateRootGoal(goalToMove.id, newParentGoal?.rootGoalId ?? "root"), ]); - // if (true) { - // console.log("Sending delete update"); - // console.log("Ancestors:", ancestors); - // sendFinalUpdateOnGoal(goalToMove.id, "moved", [...ancestorGoalIds, goalToMove.parentGoalId, goalToMove.id], false); - // } else { - createSharedGoal(goalToMove, newParentGoal?.id ?? "root", [...ancestorGoalIds, newParentGoalId]); - // } + const subscribers = await getParticipantsOfGoals(ancestorGoalIds); + subscribers.forEach(async ({ sub, rootGoalId }) => { + await sendUpdatesToSubscriber(sub, rootGoalId, "moved", [ + { + level: ancestorGoalIds.length, + goal: { + ...goalToMove, + parentGoalId: newParentGoalId, + rootGoalId, + oldParentId, + }, + }, + ]); + }); + + // Also send updates for all descendants + const descendants = await getAllDescendants(goalId); + if (descendants.length > 0) { + subscribers.forEach(async ({ sub, rootGoalId }) => { + await sendUpdatesToSubscriber( + sub, + rootGoalId, + "moved", + descendants.map((descendant) => ({ + level: ancestorGoalIds.length + 1, + goal: { + ...descendant, + rootGoalId, + }, + })), + ); + }); + } }; diff --git a/src/helpers/GoalProcessor.ts b/src/helpers/GoalProcessor.ts index 89a98cb9e..c7789fde9 100644 --- a/src/helpers/GoalProcessor.ts +++ b/src/helpers/GoalProcessor.ts @@ -114,6 +114,8 @@ export const getTypeAtPriority = (goalChanges: IChangesInGoal) => { typeAtPriority = "restored"; } else if (goalChanges.newGoalMoved.length > 0) { typeAtPriority = "newGoalMoved"; + } else if (goalChanges.moved.length > 0) { + typeAtPriority = "moved"; } return { typeAtPriority }; }; @@ -144,10 +146,10 @@ export const jumpToLowestChanges = async (id: string, relId: string) => { if (goal.parentGoalId === parentId) goals.push({ intent, goal }); }); } - if (typeAtPriority === "modifiedGoals") { + if (typeAtPriority === "modifiedGoals" || typeAtPriority === "moved") { let modifiedGoal = createGoalObjectFromTags({}); let goalIntent; - goalChanges.modifiedGoals.forEach(({ goal, intent }) => { + goalChanges[typeAtPriority].forEach(({ goal, intent }) => { if (goal.id === parentId) { modifiedGoal = { ...modifiedGoal, ...goal }; goalIntent = intent; @@ -163,8 +165,6 @@ export const jumpToLowestChanges = async (id: string, relId: string) => { }; return result; } - } else { - console.log("inbox item doesn't exist"); } const defaultResult = { typeAtPriority, parentId: "", goals: [] }; return defaultResult; diff --git a/src/helpers/InboxProcessor.ts b/src/helpers/InboxProcessor.ts index 5655287d8..2297d07ef 100644 --- a/src/helpers/InboxProcessor.ts +++ b/src/helpers/InboxProcessor.ts @@ -83,7 +83,7 @@ export const handleIncomingChanges = async (payload: Payload, relId: string) => const rootGoal = await getGoal(rootGoalId); let allAreLatest: (changesInGoal | null)[] = []; if (rootGoal) { - if (payload.changeType === "deleted") { + if (payload.changeType === "deleted" || payload.changeType === "moved") { allAreLatest = await Promise.all( changes.map(async (ele) => { const isLatest = true; diff --git a/src/models/InboxItem.ts b/src/models/InboxItem.ts index abfac1b5a..94993f2b3 100644 --- a/src/models/InboxItem.ts +++ b/src/models/InboxItem.ts @@ -19,6 +19,7 @@ export interface IChangesInGoal { archived: changesInId[]; deleted: changesInId[]; restored: changesInId[]; + moved: changesInGoal[]; newGoalMoved: changesInGoal[]; } diff --git a/src/utils/defaultGenerators.ts b/src/utils/defaultGenerators.ts index 5573bb3f0..0dcd1202c 100644 --- a/src/utils/defaultGenerators.ts +++ b/src/utils/defaultGenerators.ts @@ -29,5 +29,6 @@ export function getDefaultValueOfGoalChanges() { deleted: [], restored: [], newGoalMoved: [], + moved: [], }; } From 7aa85b44b26f7ac8c15462acdf35e7c7e2e79894 Mon Sep 17 00:00:00 2001 From: Vinay Badgujar Date: Fri, 25 Oct 2024 11:01:09 +0530 Subject: [PATCH 10/54] style: improve ui of move goal suggestio --- .../DisplayChangesModal/AcceptBtn.tsx | 2 +- .../DisplayChangesModal.tsx | 68 ++++++++-------- .../DisplayChangesModal/ShowChanges.scss | 79 +++++++++++++++++++ .../DisplayChangesModal/ShowChanges.tsx | 42 ++++++++++ 4 files changed, 154 insertions(+), 37 deletions(-) create mode 100644 src/components/GoalsComponents/DisplayChangesModal/ShowChanges.scss create mode 100644 src/components/GoalsComponents/DisplayChangesModal/ShowChanges.tsx diff --git a/src/components/GoalsComponents/DisplayChangesModal/AcceptBtn.tsx b/src/components/GoalsComponents/DisplayChangesModal/AcceptBtn.tsx index 6fb75b5e8..ba90f5af4 100644 --- a/src/components/GoalsComponents/DisplayChangesModal/AcceptBtn.tsx +++ b/src/components/GoalsComponents/DisplayChangesModal/AcceptBtn.tsx @@ -35,7 +35,7 @@ const AcceptBtn = ({ typeAtPriority, acceptChanges }: AcceptBtnProps) => { {typeAtPriority === "deleted" && "Delete for me too"} {typeAtPriority === "subgoals" && "Add all checked"} {typeAtPriority === "modifiedGoals" && "Make all checked changes"} - {typeAtPriority === "newGoalMoved" && "Move for me too"} + {typeAtPriority === "moved" && "Move for me too"} ); }; diff --git a/src/components/GoalsComponents/DisplayChangesModal/DisplayChangesModal.tsx b/src/components/GoalsComponents/DisplayChangesModal/DisplayChangesModal.tsx index dd6f910d5..0273ad2da 100644 --- a/src/components/GoalsComponents/DisplayChangesModal/DisplayChangesModal.tsx +++ b/src/components/GoalsComponents/DisplayChangesModal/DisplayChangesModal.tsx @@ -24,6 +24,7 @@ import Header from "./Header"; import AcceptBtn from "./AcceptBtn"; import IgnoreBtn from "./IgnoreBtn"; import "./DisplayChangesModal.scss"; +import { getMovedSubgoalsList } from "./ShowChanges"; const DisplayChangesModal = ({ currentMainGoal }: { currentMainGoal: GoalItem }) => { const darkModeStatus = useRecoilValue(darkModeState); @@ -35,6 +36,31 @@ const DisplayChangesModal = ({ currentMainGoal }: { currentMainGoal: GoalItem }) const [goalUnderReview, setGoalUnderReview] = useState(); const [participants, setParticipants] = useState([]); const [currentDisplay, setCurrentDisplay] = useState("none"); + const [oldParentTitle, setOldParentTitle] = useState(""); + const [newParentTitle, setNewParentTitle] = useState(""); + + useEffect(() => { + const fetchParentTitles = async () => { + if (!goalUnderReview) return; + + try { + const [oldParent, newParent] = await Promise.all([ + getGoal(goalUnderReview.oldParentId), + getGoal(goalUnderReview.parentGoalId), + ]); + + setOldParentTitle(oldParent?.title || goalUnderReview.oldParentId); + setNewParentTitle(newParent?.title || "Non-shared goal"); + } catch (error) { + console.error("Error fetching parent titles:", error); + // Handle error appropriately - maybe set default values + setOldParentTitle(goalUnderReview.oldParentId); + setNewParentTitle("Non-shared goal"); + } + }; + + fetchParentTitles(); + }, [goalUnderReview]); const [showSuggestions, setShowSuggestions] = useState(false); const [unselectedChanges, setUnselectedChanges] = useState([]); @@ -122,36 +148,6 @@ const DisplayChangesModal = ({ currentMainGoal }: { currentMainGoal: GoalItem }) ); }; - const getMovedSubgoalsList = () => { - if (!goalUnderReview) return null; - - return ( -
-
-
- Old Parent Goal: - {goalUnderReview.oldParentId === "root" ? "Root" : goalUnderReview.oldParentId} -
-
- New Parent Goal: - {goalUnderReview.parentGoalId === "root" ? "Root" : goalUnderReview.parentGoalId} -
-
- Goal Being Moved: - {goalUnderReview.title} -
-
-
- ); - }; - const deleteChanges = async () => { if (!goalUnderReview || !currentMainGoal) { return; @@ -180,18 +176,18 @@ const DisplayChangesModal = ({ currentMainGoal }: { currentMainGoal: GoalItem }) const parentGoal = await getGoal(goalUnderReview.parentGoalId); console.log( - `[Move Goal] New parent goal found:`, + "[Move Goal] New parent goal found:", parentGoal ? `${parentGoal.id} (${parentGoal.title})` : "not found", ); // If parent goal doesn't exist, it means the goal was moved out of shared hierarchy if (!parentGoal) { - console.log(`[Move Goal] Parent goal is not shared or doesn't exist. Removing goal and its children.`); + console.log("[Move Goal] Parent goal is not shared or doesn't exist. Removing goal and its children."); // Remove the goal and its children from the database - await removeGoalWithChildrens(goalUnderReview); - console.log(`[Move Goal] Successfully removed goal and its children`); + await deleteChanges(); + console.log("[Move Goal] Successfully removed goal and its children"); } else { - console.log(`[Move Goal] Moving goal within shared hierarchy`); + console.log("[Move Goal] Moving goal within shared hierarchy"); // Goal was moved within shared hierarchy, update its position await Promise.all([ updateGoal(goalUnderReview.id, { parentGoalId: parentGoal.id }), @@ -372,7 +368,7 @@ const DisplayChangesModal = ({ currentMainGoal }: { currentMainGoal: GoalItem }) {["deleted", "archived", "restored"].includes(currentDisplay) &&
} {currentDisplay === "modifiedGoals" && getEditChangesList()} {(currentDisplay === "subgoals" || currentDisplay === "newGoalMoved") && getSubgoalsList()} - {currentDisplay === "moved" && getMovedSubgoalsList()} + {currentDisplay === "moved" && getMovedSubgoalsList(goalUnderReview, oldParentTitle, newParentTitle)}
{goalUnderReview && ( diff --git a/src/components/GoalsComponents/DisplayChangesModal/ShowChanges.scss b/src/components/GoalsComponents/DisplayChangesModal/ShowChanges.scss new file mode 100644 index 000000000..fabe9ec86 --- /dev/null +++ b/src/components/GoalsComponents/DisplayChangesModal/ShowChanges.scss @@ -0,0 +1,79 @@ +.move-info-container { + display: flex; + flex-direction: column; + gap: 20px; + background: var(--secondary-background); + border-radius: 12px; + padding: 24px; + border: 1px solid var(--default-border-color); + box-shadow: 0 2px 8px rgba(0, 0, 0, 0.05); + + .move-info-item { + display: flex; + flex-direction: column; + gap: 8px; + + .move-info-label { + font-size: 12px; + color: var(--text-secondary); + font-weight: 600; + text-transform: uppercase; + letter-spacing: 0.5px; + } + + .move-info-value { + font-size: 14px; + color: var(--text-primary); + padding: 12px 16px; + background: var(--primary-background); + border-radius: 8px; + border: 1px solid var(--default-border-color); + line-height: 1.4; + + &.highlight-box { + background: var(--selection-color); + border: none; + font-weight: 500; + } + } + } + + .move-direction-container { + display: grid; + grid-template-columns: 1fr auto 1fr; + align-items: center; + gap: 12px; + padding: 16px; + background: var(--primary-background); + border-radius: 12px; + border: 1px solid var(--default-border-color); + + .arrow { + width: 32px; + height: 32px; + display: flex; + align-items: center; + justify-content: center; + background: var(--secondary-background); + border-radius: 50%; + color: var(--text-secondary); + font-size: 18px; + margin-top: 24px; + } + } + + .warning-message { + display: flex; + align-items: center; + gap: 8px; + padding: 12px 16px; + background: var(--bottom-nav-color); + border-radius: 8px; + margin-top: 4px; + font-size: 13px; + + .anticon { + font-size: 16px; + } + } +} diff --git a/src/components/GoalsComponents/DisplayChangesModal/ShowChanges.tsx b/src/components/GoalsComponents/DisplayChangesModal/ShowChanges.tsx new file mode 100644 index 000000000..e0df85049 --- /dev/null +++ b/src/components/GoalsComponents/DisplayChangesModal/ShowChanges.tsx @@ -0,0 +1,42 @@ +import React from "react"; +import { GoalItem } from "@src/models/GoalItem"; +import "./ShowChanges.scss"; +import { InfoCircleOutlined } from "@ant-design/icons"; + +export const getMovedSubgoalsList = ( + goalUnderReview: GoalItem | undefined, + oldParentTitle: string, + newParentTitle: string, +) => { + if (!goalUnderReview) return null; + + return ( +
+
+ Goal Being Moved +
{goalUnderReview.title}
+
+ +
+
+ From +
{oldParentTitle}
+
+ +
→
+ +
+ To +
{newParentTitle}
+
+
+ + {newParentTitle === "Non-shared goal" && ( +
+ + The new parent goal is not shared. Changes will be ignored if accepted. +
+ )} +
+ ); +}; From 3eba50510ba7146ae268b892663a89e4ee8fa5d1 Mon Sep 17 00:00:00 2001 From: Vinay Badgujar Date: Fri, 25 Oct 2024 11:05:24 +0530 Subject: [PATCH 11/54] chore: remove logs --- .../DisplayChangesModal.tsx | 28 ++----------------- 1 file changed, 2 insertions(+), 26 deletions(-) diff --git a/src/components/GoalsComponents/DisplayChangesModal/DisplayChangesModal.tsx b/src/components/GoalsComponents/DisplayChangesModal/DisplayChangesModal.tsx index 0273ad2da..52a270ad5 100644 --- a/src/components/GoalsComponents/DisplayChangesModal/DisplayChangesModal.tsx +++ b/src/components/GoalsComponents/DisplayChangesModal/DisplayChangesModal.tsx @@ -170,48 +170,24 @@ const DisplayChangesModal = ({ currentMainGoal }: { currentMainGoal: GoalItem }) await deleteChanges(); } if (currentDisplay === "moved") { - console.log(`[Move Goal] Starting move process for goal: ${goalUnderReview.id} (${goalUnderReview.title})`); - console.log(`[Move Goal] Old parent: ${goalUnderReview.oldParentId}`); - console.log(`[Move Goal] New parent: ${goalUnderReview.parentGoalId}`); - const parentGoal = await getGoal(goalUnderReview.parentGoalId); - console.log( - "[Move Goal] New parent goal found:", - parentGoal ? `${parentGoal.id} (${parentGoal.title})` : "not found", - ); - // If parent goal doesn't exist, it means the goal was moved out of shared hierarchy if (!parentGoal) { - console.log("[Move Goal] Parent goal is not shared or doesn't exist. Removing goal and its children."); - // Remove the goal and its children from the database await deleteChanges(); - console.log("[Move Goal] Successfully removed goal and its children"); } else { - console.log("[Move Goal] Moving goal within shared hierarchy"); - // Goal was moved within shared hierarchy, update its position await Promise.all([ updateGoal(goalUnderReview.id, { parentGoalId: parentGoal.id }), removeGoalFromParentSublist(goalUnderReview.id, goalUnderReview.oldParentId), addGoalToNewParentSublist(goalUnderReview.id, parentGoal.id), - ]).then(() => { - console.log(`[Move Goal] Successfully updated goal position:`); - console.log(` - Updated parentGoalId to: ${parentGoal.id}`); - console.log(` - Removed from old parent: ${goalUnderReview.oldParentId}`); - console.log(` - Added to new parent: ${parentGoal.id}`); - }); + ]); - // Send update to all participants - console.log( - `[Move Goal] Sending updates to participants. Excluding: ${updatesIntent === "suggestion" ? participants[activePPT].relId : "none"}`, - ); await sendUpdatedGoal( goalUnderReview.id, [], true, updatesIntent === "suggestion" ? [] : [participants[activePPT].relId], - ).then(() => console.log(`[Move Goal] Successfully sent updates to participants`)); + ); } - console.log(`[Move Goal] Move process completed for goal: ${goalUnderReview.id} (${goalUnderReview.title})`); } if (currentDisplay === "subgoals" || currentDisplay === "newGoalMoved") { const goalsToBeSelected = newGoals From 6005a8a4a8d26a4f30545ace559d800c9cb62f2a Mon Sep 17 00:00:00 2001 From: Vinay Badgujar Date: Fri, 25 Oct 2024 11:30:33 +0530 Subject: [PATCH 12/54] patch: dont pass old parent id in move change payload --- .../DisplayChangesModal/DisplayChangesModal.tsx | 16 ++++++++++------ src/helpers/GoalController.ts | 1 - 2 files changed, 10 insertions(+), 7 deletions(-) diff --git a/src/components/GoalsComponents/DisplayChangesModal/DisplayChangesModal.tsx b/src/components/GoalsComponents/DisplayChangesModal/DisplayChangesModal.tsx index 52a270ad5..0fdbb9cf0 100644 --- a/src/components/GoalsComponents/DisplayChangesModal/DisplayChangesModal.tsx +++ b/src/components/GoalsComponents/DisplayChangesModal/DisplayChangesModal.tsx @@ -44,17 +44,19 @@ const DisplayChangesModal = ({ currentMainGoal }: { currentMainGoal: GoalItem }) if (!goalUnderReview) return; try { + const currentGoalInDB = await getGoal(goalUnderReview.id); + const oldParentId = currentGoalInDB?.parentGoalId; + const [oldParent, newParent] = await Promise.all([ - getGoal(goalUnderReview.oldParentId), + oldParentId ? getGoal(oldParentId) : null, getGoal(goalUnderReview.parentGoalId), ]); - setOldParentTitle(oldParent?.title || goalUnderReview.oldParentId); + setOldParentTitle(oldParent?.title || ""); setNewParentTitle(newParent?.title || "Non-shared goal"); } catch (error) { console.error("Error fetching parent titles:", error); - // Handle error appropriately - maybe set default values - setOldParentTitle(goalUnderReview.oldParentId); + setOldParentTitle(""); setNewParentTitle("Non-shared goal"); } }; @@ -173,11 +175,13 @@ const DisplayChangesModal = ({ currentMainGoal }: { currentMainGoal: GoalItem }) const parentGoal = await getGoal(goalUnderReview.parentGoalId); if (!parentGoal) { - await deleteChanges(); + await deleteChanges().then(() => { + console.log("Goal moved to non-shared goal"); + }); } else { await Promise.all([ updateGoal(goalUnderReview.id, { parentGoalId: parentGoal.id }), - removeGoalFromParentSublist(goalUnderReview.id, goalUnderReview.oldParentId), + removeGoalFromParentSublist(goalUnderReview.id, oldParentTitle), addGoalToNewParentSublist(goalUnderReview.id, parentGoal.id), ]); diff --git a/src/helpers/GoalController.ts b/src/helpers/GoalController.ts index c7eb55a43..e1fa70cb2 100644 --- a/src/helpers/GoalController.ts +++ b/src/helpers/GoalController.ts @@ -258,7 +258,6 @@ export const moveGoalHierarchy = async (goalId: string, newParentGoalId: string) ...goalToMove, parentGoalId: newParentGoalId, rootGoalId, - oldParentId, }, }, ]); From 36b2660c81624256621e4d4baf873d56f7baa697 Mon Sep 17 00:00:00 2001 From: Vinay Badgujar Date: Sat, 26 Oct 2024 09:39:31 +0530 Subject: [PATCH 13/54] feat(move): handle share updates for move goal out of shared goal with descendants --- src/api/GoalsAPI/index.ts | 16 ++--- .../DisplayChangesModal.tsx | 61 ++++++++++++------- .../DisplayChangesModal/ShowChanges.tsx | 2 +- src/helpers/GoalController.ts | 2 +- 4 files changed, 49 insertions(+), 32 deletions(-) diff --git a/src/api/GoalsAPI/index.ts b/src/api/GoalsAPI/index.ts index 9290c9050..34a92233b 100644 --- a/src/api/GoalsAPI/index.ts +++ b/src/api/GoalsAPI/index.ts @@ -157,22 +157,22 @@ export const unarchiveUserGoal = async (goal: GoalItem) => { await unarchiveGoal(goal); }; -export const removeGoal = async (goal: GoalItem) => { +export const removeGoal = async (goal: GoalItem, permanently = false) => { await deleteHintItem(goal.id); await Promise.allSettled([ db.goalsCollection.delete(goal.id).catch((err) => console.log("failed to delete", err)), - addDeletedGoal(goal), + permanently ? null : addDeletedGoal(goal), ]); }; -export const removeChildrenGoals = async (parentGoalId: string) => { +export const removeChildrenGoals = async (parentGoalId: string, permanently = false) => { const childrenGoals = await getChildrenGoals(parentGoalId); if (childrenGoals.length === 0) { return; } childrenGoals.forEach((goal) => { - removeChildrenGoals(goal.id); - removeGoal(goal); + removeChildrenGoals(goal.id, permanently); + removeGoal(goal, permanently); }); }; @@ -312,9 +312,9 @@ export const notifyNewColabRequest = async (id: string, relId: string) => { // }); // }; -export const removeGoalWithChildrens = async (goal: GoalItem) => { - await removeChildrenGoals(goal.id); - await removeGoal(goal); +export const removeGoalWithChildrens = async (goal: GoalItem, permanently = false) => { + await removeChildrenGoals(goal.id, permanently); + await removeGoal(goal, permanently); if (goal.parentGoalId !== "root") { getGoal(goal.parentGoalId).then(async (parentGoal: GoalItem) => { const parentGoalSublist = parentGoal.sublist; diff --git a/src/components/GoalsComponents/DisplayChangesModal/DisplayChangesModal.tsx b/src/components/GoalsComponents/DisplayChangesModal/DisplayChangesModal.tsx index 0fdbb9cf0..1154293dc 100644 --- a/src/components/GoalsComponents/DisplayChangesModal/DisplayChangesModal.tsx +++ b/src/components/GoalsComponents/DisplayChangesModal/DisplayChangesModal.tsx @@ -19,7 +19,7 @@ import SubHeader from "@src/common/SubHeader"; import ContactItem from "@src/models/ContactItem"; import ZModal from "@src/common/ZModal"; -import { addGoalToNewParentSublist, removeGoalFromParentSublist } from "@src/helpers/GoalController"; +import { addGoalToNewParentSublist, getAllDescendants, removeGoalFromParentSublist } from "@src/helpers/GoalController"; import Header from "./Header"; import AcceptBtn from "./AcceptBtn"; import IgnoreBtn from "./IgnoreBtn"; @@ -157,13 +157,48 @@ const DisplayChangesModal = ({ currentMainGoal }: { currentMainGoal: GoalItem }) const removeChanges = currentDisplay === "subgoals" || currentDisplay === "newGoalMoved" ? newGoals.map(({ goal }) => goal.id) - : [goalUnderReview.id]; + : currentDisplay === "moved" + ? [goalUnderReview.id, ...(await getAllDescendants(goalUnderReview.id)).map((goal: GoalItem) => goal.id)] + : [goalUnderReview.id]; + if (currentDisplay !== "none") { await deleteGoalChangesInID(currentMainGoal.id, participants[activePPT].relId, currentDisplay, removeChanges); } setCurrentDisplay("none"); }; + const handleMoveChanges = async () => { + if (!goalUnderReview) { + console.log("No goal under review."); + return; + } + const parentGoal = await getGoal(goalUnderReview.parentGoalId); + + if (!parentGoal || parentGoal.id === goalUnderReview.parentGoalId) { + await removeGoalWithChildrens(goalUnderReview, true) + .then(() => { + deleteChanges(); + }) + .then(() => { + setCurrentDisplay("none"); + }); + } else { + await Promise.all([ + updateGoal(goalUnderReview.id, { parentGoalId: parentGoal.id }), + removeGoalFromParentSublist(goalUnderReview.id, oldParentTitle), + addGoalToNewParentSublist(goalUnderReview.id, parentGoal.id), + ]); + + // TODO: handle this later + // await sendUpdatedGoal( + // goalUnderReview.id, + // [], + // true, + // updatesIntent === "suggestion" ? [] : [participants[activePPT].relId], + // ); + } + }; + const acceptChanges = async () => { if (!goalUnderReview) { return; @@ -172,26 +207,7 @@ const DisplayChangesModal = ({ currentMainGoal }: { currentMainGoal: GoalItem }) await deleteChanges(); } if (currentDisplay === "moved") { - const parentGoal = await getGoal(goalUnderReview.parentGoalId); - - if (!parentGoal) { - await deleteChanges().then(() => { - console.log("Goal moved to non-shared goal"); - }); - } else { - await Promise.all([ - updateGoal(goalUnderReview.id, { parentGoalId: parentGoal.id }), - removeGoalFromParentSublist(goalUnderReview.id, oldParentTitle), - addGoalToNewParentSublist(goalUnderReview.id, parentGoal.id), - ]); - - await sendUpdatedGoal( - goalUnderReview.id, - [], - true, - updatesIntent === "suggestion" ? [] : [participants[activePPT].relId], - ); - } + await handleMoveChanges(); } if (currentDisplay === "subgoals" || currentDisplay === "newGoalMoved") { const goalsToBeSelected = newGoals @@ -259,6 +275,7 @@ const DisplayChangesModal = ({ currentMainGoal }: { currentMainGoal: GoalItem }) console.log("🚀 ~ getChanges ~ changedGoal:", changedGoal); if (changedGoal) { setGoalUnderReview({ ...changedGoal }); + // TODO: remove the newGoalsMoved and try handle in subgoal only if (typeAtPriority === "subgoals" || typeAtPriority === "newGoalMoved") { setNewGoals(goals || []); } else if (typeAtPriority === "modifiedGoals") { diff --git a/src/components/GoalsComponents/DisplayChangesModal/ShowChanges.tsx b/src/components/GoalsComponents/DisplayChangesModal/ShowChanges.tsx index e0df85049..8332d932f 100644 --- a/src/components/GoalsComponents/DisplayChangesModal/ShowChanges.tsx +++ b/src/components/GoalsComponents/DisplayChangesModal/ShowChanges.tsx @@ -34,7 +34,7 @@ export const getMovedSubgoalsList = ( {newParentTitle === "Non-shared goal" && (
- The new parent goal is not shared. Changes will be ignored if accepted. + The new parent goal is not shared. The goal will be deleted for you.
)}
diff --git a/src/helpers/GoalController.ts b/src/helpers/GoalController.ts index e1fa70cb2..8de35a153 100644 --- a/src/helpers/GoalController.ts +++ b/src/helpers/GoalController.ts @@ -64,7 +64,7 @@ export const getGoalAncestors = async (goalId: string): Promise => { return ancestors; }; -const getAllDescendants = async (goalId: string): Promise => { +export const getAllDescendants = async (goalId: string): Promise => { const descendants: GoalItem[] = []; const processGoalAndChildren = async (currentGoalId: string) => { From d94db6130124c35f569e91a94c03869d70e5c773 Mon Sep 17 00:00:00 2001 From: Vinay Badgujar Date: Sat, 26 Oct 2024 12:15:52 +0530 Subject: [PATCH 14/54] feat(move): skip processing move update if root goal is moved --- .../DisplayChangesModal/DisplayChangesModal.tsx | 9 +++++---- src/helpers/InboxProcessor.ts | 17 +++++++++++++++++ 2 files changed, 22 insertions(+), 4 deletions(-) diff --git a/src/components/GoalsComponents/DisplayChangesModal/DisplayChangesModal.tsx b/src/components/GoalsComponents/DisplayChangesModal/DisplayChangesModal.tsx index 1154293dc..761a6dde2 100644 --- a/src/components/GoalsComponents/DisplayChangesModal/DisplayChangesModal.tsx +++ b/src/components/GoalsComponents/DisplayChangesModal/DisplayChangesModal.tsx @@ -52,11 +52,11 @@ const DisplayChangesModal = ({ currentMainGoal }: { currentMainGoal: GoalItem }) getGoal(goalUnderReview.parentGoalId), ]); - setOldParentTitle(oldParent?.title || ""); + setOldParentTitle(oldParent?.title || "root"); setNewParentTitle(newParent?.title || "Non-shared goal"); } catch (error) { console.error("Error fetching parent titles:", error); - setOldParentTitle(""); + setOldParentTitle("root"); setNewParentTitle("Non-shared goal"); } }; @@ -174,7 +174,8 @@ const DisplayChangesModal = ({ currentMainGoal }: { currentMainGoal: GoalItem }) } const parentGoal = await getGoal(goalUnderReview.parentGoalId); - if (!parentGoal || parentGoal.id === goalUnderReview.parentGoalId) { + if (!parentGoal) { + console.log("Removing goal with childrens"); await removeGoalWithChildrens(goalUnderReview, true) .then(() => { deleteChanges(); @@ -185,7 +186,7 @@ const DisplayChangesModal = ({ currentMainGoal }: { currentMainGoal: GoalItem }) } else { await Promise.all([ updateGoal(goalUnderReview.id, { parentGoalId: parentGoal.id }), - removeGoalFromParentSublist(goalUnderReview.id, oldParentTitle), + removeGoalFromParentSublist(goalUnderReview.id, parentGoal.title), addGoalToNewParentSublist(goalUnderReview.id, parentGoal.id), ]); diff --git a/src/helpers/InboxProcessor.ts b/src/helpers/InboxProcessor.ts index 2297d07ef..0cf2ba254 100644 --- a/src/helpers/InboxProcessor.ts +++ b/src/helpers/InboxProcessor.ts @@ -36,6 +36,23 @@ interface Payload { export const handleIncomingChanges = async (payload: Payload, relId: string) => { console.log("Incoming change", payload); + + if (payload.changeType === "moved") { + const rootGoal = await getGoal(payload.rootGoalId); + const currentGoal = await getGoal(payload.changes[0].goal.id); + + if (rootGoal) { + const movedGoal = payload.changes[0].goal; + const newParentGoal = await getGoal(movedGoal.parentGoalId); + if ( + currentGoal?.participants.find((ele) => ele.relId === relId && ele.following) !== undefined && + !newParentGoal + ) { + return; + } + } + } + if (payload.type === "sharer" && (await getSharedWMGoal(payload.rootGoalId))) { console.log("Incoming change is a shared goal. Processing..."); const incGoal = await getSharedWMGoal(payload.rootGoalId); From b64433eb5e12bd903d53211d0aa6413dc1a7660a Mon Sep 17 00:00:00 2001 From: Vinay Badgujar Date: Sun, 27 Oct 2024 13:31:46 +0530 Subject: [PATCH 15/54] feat(sharing): push participant in child goals also --- src/hooks/useApp.tsx | 40 ++++++++++++++++++++++++++---------- src/hooks/useGoalActions.tsx | 15 +++++++++++--- src/models/GoalItem.ts | 1 + src/models/dexie.ts | 4 ++-- 4 files changed, 44 insertions(+), 16 deletions(-) diff --git a/src/hooks/useApp.tsx b/src/hooks/useApp.tsx index 0a4f4ecda..1ad49eeb9 100644 --- a/src/hooks/useApp.tsx +++ b/src/hooks/useApp.tsx @@ -81,20 +81,38 @@ function useApp() { console.log("🚀 ~ file: useApp.tsx:45 ~ resObject[relId].forEach ~ ele:", ele); if (ele.type === "shareMessage") { const { goalWithChildrens }: { goalWithChildrens: GoalItem[] } = ele; - const rootGoal = goalWithChildrens[0]; - rootGoal.participants.push({ + + const participant = { name: contactItem.name, relId, - type: "sharer", + type: "sharer" as const, following: true, - }); - addSharedWMGoal(rootGoal) - .then(() => { - goalWithChildrens.slice(1).forEach((goal) => { - addSharedWMGoal(goal).catch((err) => console.log(`Failed to add in inbox ${goal.title}`, err)); - }); - }) - .catch((err) => console.log(`Failed to add root goal ${rootGoal.title}`, err)); + }; + + const rootGoal = { + ...goalWithChildrens[0], + participants: [participant], + }; + + console.log("[useApp] Adding root goal with participant:", rootGoal.id); + + try { + await addSharedWMGoal(rootGoal); + + await Promise.all( + goalWithChildrens.slice(1).map(async (goal) => { + const goalWithParticipant = { + ...goal, + participants: [participant], + }; + await addSharedWMGoal(goalWithParticipant); + }), + ); + + console.log("[useApp] Successfully added all goals with participants"); + } catch (error) { + console.error("[useApp] Error adding shared goals:", error); + } } else if (["sharer", "suggestion"].includes(ele.type)) { handleIncomingChanges(ele, relId).then(() => setLastAction("goalNewUpdates")); } diff --git a/src/hooks/useGoalActions.tsx b/src/hooks/useGoalActions.tsx index c56d13e9c..3ac66a0a5 100644 --- a/src/hooks/useGoalActions.tsx +++ b/src/hooks/useGoalActions.tsx @@ -123,16 +123,25 @@ const useGoalActions = () => { const shareGoalWithRelId = async (relId: string, name: string, goal: GoalItem) => { const goalWithChildrens = await getAllLevelGoalsOfId(goal.id, true); + await shareGoalWithContact(relId, [ ...goalWithChildrens.map((ele) => ({ ...ele, participants: [], - parentGoalId: ele.id === goal.id ? "root" : ele.parentGoalId, + parentGoalId: ele.parentGoalId, rootGoalId: goal.id, })), ]); - updateSharedStatusOfGoal(goal.id, relId, name).then(() => console.log("status updated")); - showMessage(`Cheers!!, Your goal is shared with ${name}`); + + await Promise.all( + goalWithChildrens.map(async (goalItem) => { + await updateSharedStatusOfGoal(goalItem.id, relId, name); + }), + ).catch((error) => { + console.error("[shareGoalWithRelId] Error updating shared status:", error); + }); + + showMessage(`Cheers!! Your goal and its subgoals are shared with ${name}`); }; const addContact = async (relId: string, goalId: string) => { diff --git a/src/models/GoalItem.ts b/src/models/GoalItem.ts index acbd185db..38af50f8b 100644 --- a/src/models/GoalItem.ts +++ b/src/models/GoalItem.ts @@ -27,6 +27,7 @@ export interface GoalItem { language: string; link: string | null; participants: IParticipant[]; + isShared: boolean; rootGoalId: string; timeBudget?: { perDay: string; diff --git a/src/models/dexie.ts b/src/models/dexie.ts index 28ea29b20..c2e84d758 100644 --- a/src/models/dexie.ts +++ b/src/models/dexie.ts @@ -7,9 +7,9 @@ import { TaskItem } from "./TaskItem"; export const dbStoreSchema = { feelingsCollection: "++id, content, category, date, note", goalsCollection: - "id, category, title, duration, sublist, habit, on, start, due, afterTime, beforeTime, createdAt, parentGoalId, archived, participants, goalColor, language, link, rootGoalId, timeBudget, typeOfGoal, timestamp", + "id, category, title, duration, sublist, habit, on, start, due, afterTime, beforeTime, createdAt, parentGoalId, archived, participants, goalColor, language, link, rootGoalId, isShared, timeBudget, typeOfGoal, timestamp", sharedWMCollection: - "id, category, title, duration, sublist, repeat, start, due, afterTime, beforeTime, createdAt, parentGoalId, participants, archived, goalColor, language, link, rootGoalId, timeBudget, typeOfGoal", + "id, category, title, duration, sublist, repeat, start, due, afterTime, beforeTime, createdAt, parentGoalId, participants, archived, goalColor, language, link, rootGoalId, isShared, timeBudget, typeOfGoal", contactsCollection: "id, name, relId, accepted, goalsToBeShared, createdAt, type", outboxCollection: null, inboxCollection: "id, goalChanges", From d5c090b7dffea430acb05d9fdbb71dda67ae22e5 Mon Sep 17 00:00:00 2001 From: Vinay Badgujar Date: Sun, 27 Oct 2024 14:59:56 +0530 Subject: [PATCH 16/54] refactor: pluck the code for addition of new shared goal in function and add types for getContactSharedGoals response --- src/Interfaces/IContactMessages.ts | 15 +++++++ src/api/SharedWMAPI/index.ts | 4 +- src/helpers/InboxProcessor.ts | 2 +- src/hooks/useApp.tsx | 70 ++++++++++++++---------------- src/services/contact.service.ts | 5 ++- 5 files changed, 54 insertions(+), 42 deletions(-) create mode 100644 src/Interfaces/IContactMessages.ts diff --git a/src/Interfaces/IContactMessages.ts b/src/Interfaces/IContactMessages.ts new file mode 100644 index 000000000..0a1d1c04b --- /dev/null +++ b/src/Interfaces/IContactMessages.ts @@ -0,0 +1,15 @@ +import { GoalItem } from "@src/models/GoalItem"; + +export interface SharedGoalMessage { + relId: string; + goalWithChildrens: GoalItem[]; + lastProcessedTimestamp: string; + type: "shareMessage"; + installId: string; + TTL: number; +} + +export interface SharedGoalMessageResponse { + success: boolean; + response: SharedGoalMessage[]; +} diff --git a/src/api/SharedWMAPI/index.ts b/src/api/SharedWMAPI/index.ts index 00818799c..82820f957 100644 --- a/src/api/SharedWMAPI/index.ts +++ b/src/api/SharedWMAPI/index.ts @@ -2,7 +2,7 @@ import { db } from "@models"; import { GoalItem } from "@src/models/GoalItem"; import { createGoalObjectFromTags } from "@src/helpers/GoalProcessor"; -import { addDeletedGoal, addGoal } from "../GoalsAPI"; +import { addGoal } from "../GoalsAPI"; export const addSharedWMSublist = async (parentGoalId: string, goalIds: string[]) => { db.transaction("rw", db.sharedWMCollection, async () => { @@ -17,7 +17,7 @@ export const addSharedWMSublist = async (parentGoalId: string, goalIds: string[] }); }; -export const addSharedWMGoal = async (goalDetails: object) => { +export const addSharedWMGoal = async (goalDetails: GoalItem) => { const { participants } = goalDetails; const newGoal = createGoalObjectFromTags({ ...goalDetails, typeOfGoal: "shared" }); if (participants) newGoal.participants = participants; diff --git a/src/helpers/InboxProcessor.ts b/src/helpers/InboxProcessor.ts index 0cf2ba254..c26c5938f 100644 --- a/src/helpers/InboxProcessor.ts +++ b/src/helpers/InboxProcessor.ts @@ -23,7 +23,7 @@ import { fixDateVlauesInGoalObject } from "@src/utils"; import { getDeletedGoal, restoreUserGoal } from "@src/api/TrashAPI"; import { isIncomingGoalLatest } from "./mergeSharedGoalItems"; -interface Payload { +export interface Payload { relId: string; lastProcessedTimestamp: string; changeType: string; diff --git a/src/hooks/useApp.tsx b/src/hooks/useApp.tsx index 1ad49eeb9..34f46523e 100644 --- a/src/hooks/useApp.tsx +++ b/src/hooks/useApp.tsx @@ -3,18 +3,20 @@ import { useEffect } from "react"; import { lastAction, displayConfirmation, openDevMode, languageSelectionState, displayToast } from "@src/store"; import { getTheme } from "@src/store/ThemeState"; -import { GoalItem } from "@src/models/GoalItem"; +import { GoalItem, IParticipant } from "@src/models/GoalItem"; import { checkMagicGoal, getAllLevelGoalsOfId, getGoal, updateSharedStatusOfGoal } from "@src/api/GoalsAPI"; import { addSharedWMGoal } from "@src/api/SharedWMAPI"; import { createDefaultGoals } from "@src/helpers/NewUserController"; import { refreshTaskCollection } from "@src/api/TasksAPI"; -import { handleIncomingChanges } from "@src/helpers/InboxProcessor"; +import { handleIncomingChanges, Payload } from "@src/helpers/InboxProcessor"; import { getContactSharedGoals, shareGoalWithContact } from "@src/services/contact.service"; import { updateAllUnacceptedContacts, getContactByRelId, clearTheQueue } from "@src/api/ContactsAPI"; import { useSetRecoilState, useRecoilValue, useRecoilState } from "recoil"; import { scheduledHintCalls } from "@src/api/HintsAPI/ScheduledHintCall"; import { LocalStorageKeys } from "@src/constants/localStorageKeys"; import { checkAndCleanupTrash } from "@src/api/TrashAPI"; +import ContactItem from "@src/models/ContactItem"; +import { SharedGoalMessage } from "@src/Interfaces/IContactMessages"; const langFromStorage = localStorage.getItem(LocalStorageKeys.LANGUAGE)?.slice(1, -1); const exceptionRoutes = ["/", "/invest", "/feedback", "/donate"]; @@ -29,6 +31,29 @@ function useApp() { const confirmationState = useRecoilValue(displayConfirmation); + const handleNewIncomingGoal = async (ele: SharedGoalMessage, contactItem: ContactItem, relId: string) => { + const { goalWithChildrens }: { goalWithChildrens: GoalItem[] } = ele; + const participant: IParticipant = { + name: contactItem.name, + relId, + type: "sharer", + following: true, + }; + try { + await Promise.all( + goalWithChildrens.map(async (goal) => { + const goalWithParticipant = { + ...goal, + participants: [participant], + }; + await addSharedWMGoal(goalWithParticipant); + }), + ); + } catch (error) { + console.error("[useApp] Error adding shared goals:", error); + } + }; + useEffect(() => { const init = async () => { updateAllUnacceptedContacts().then(async (contacts) => { @@ -69,7 +94,10 @@ function useApp() { const res = await getContactSharedGoals(); // @ts-ignore const resObject = res.response.reduce( - (acc, curr) => ({ ...acc, [curr.relId]: [...(acc[curr.relId] || []), curr] }), + (acc: { [key: string]: SharedGoalMessage[] }, curr) => ({ + ...acc, + [curr.relId]: [...(acc[curr.relId] || []), curr], + }), {}, ); if (res.success) { @@ -80,41 +108,9 @@ function useApp() { resObject[relId].forEach(async (ele) => { console.log("🚀 ~ file: useApp.tsx:45 ~ resObject[relId].forEach ~ ele:", ele); if (ele.type === "shareMessage") { - const { goalWithChildrens }: { goalWithChildrens: GoalItem[] } = ele; - - const participant = { - name: contactItem.name, - relId, - type: "sharer" as const, - following: true, - }; - - const rootGoal = { - ...goalWithChildrens[0], - participants: [participant], - }; - - console.log("[useApp] Adding root goal with participant:", rootGoal.id); - - try { - await addSharedWMGoal(rootGoal); - - await Promise.all( - goalWithChildrens.slice(1).map(async (goal) => { - const goalWithParticipant = { - ...goal, - participants: [participant], - }; - await addSharedWMGoal(goalWithParticipant); - }), - ); - - console.log("[useApp] Successfully added all goals with participants"); - } catch (error) { - console.error("[useApp] Error adding shared goals:", error); - } + handleNewIncomingGoal(ele, contactItem, relId); } else if (["sharer", "suggestion"].includes(ele.type)) { - handleIncomingChanges(ele, relId).then(() => setLastAction("goalNewUpdates")); + handleIncomingChanges(ele as unknown as Payload, relId).then(() => setLastAction("goalNewUpdates")); } // else if (["suggestion", "shared", "collaboration", "collaborationInvite"].includes(ele.type)) { // let typeOfSub = ele.rootGoalId ? await findTypeOfSub(ele.rootGoalId) : "none"; diff --git a/src/services/contact.service.ts b/src/services/contact.service.ts index 93995f8a5..734d726f6 100644 --- a/src/services/contact.service.ts +++ b/src/services/contact.service.ts @@ -1,4 +1,5 @@ import { LocalStorageKeys } from "@src/constants/localStorageKeys"; +import { SharedGoalMessageResponse } from "@src/Interfaces/IContactMessages"; import { GoalItem, IParticipant } from "@src/models/GoalItem"; import { typeOfChange } from "@src/models/InboxItem"; import { createContactRequest, getInstallId } from "@src/utils"; @@ -43,7 +44,7 @@ export const collaborateWithContact = async (relId: string, goal: GoalItem) => { return res; }; -export const getContactSharedGoals = async () => { +export const getContactSharedGoals = async (): Promise => { const lastProcessedTimestamp = new Date( Number(localStorage.getItem(LocalStorageKeys.LAST_PROCESSED_TIMESTAMP)), ).toISOString(); @@ -54,7 +55,7 @@ export const getContactSharedGoals = async () => { ...(lastProcessedTimestamp ? { lastProcessedTimestamp } : {}), }); localStorage.setItem(LocalStorageKeys.LAST_PROCESSED_TIMESTAMP, `${Date.now()}`); - return res; + return res as SharedGoalMessageResponse; }; export const getRelationshipStatus = async (relationshipId: string) => { From c347441b1c1fe450141c59a3edf8a5d360b3705d Mon Sep 17 00:00:00 2001 From: Vinay Badgujar Date: Sun, 27 Oct 2024 20:13:42 +0530 Subject: [PATCH 17/54] chore: add type to changes --- src/helpers/InboxProcessor.ts | 100 ++++++++++++++++++-------------- src/helpers/PubSubController.ts | 2 +- 2 files changed, 59 insertions(+), 43 deletions(-) diff --git a/src/helpers/InboxProcessor.ts b/src/helpers/InboxProcessor.ts index c26c5938f..8dcfa571f 100644 --- a/src/helpers/InboxProcessor.ts +++ b/src/helpers/InboxProcessor.ts @@ -1,5 +1,5 @@ import { GoalItem } from "@src/models/GoalItem"; -import { changesInGoal, InboxItem } from "@src/models/InboxItem"; +import { changesInGoal, changesInId, InboxItem } from "@src/models/InboxItem"; import { getDefaultValueOfGoalChanges } from "@src/utils/defaultGenerators"; import { @@ -28,7 +28,7 @@ export interface Payload { lastProcessedTimestamp: string; changeType: string; rootGoalId: string; - changes: changesInGoal[]; + changes: (changesInGoal | changesInId)[]; type: string; timestamp: string; TTL: number; @@ -96,55 +96,71 @@ export const handleIncomingChanges = async (payload: Payload, relId: string) => } } } else if (["sharer", "suggestion"].includes(payload.type)) { - const { rootGoalId, changes, changeType } = payload; - const rootGoal = await getGoal(rootGoalId); + const { changes, changeType } = payload; + console.log("changes", changes); + + let currentGoal: GoalItem | null = null; + + if ("goal" in changes[0]) { + currentGoal = await getGoal(changes[0].goal.id); + } else { + currentGoal = await getGoal(changes[0].id); + } + + if (!currentGoal) { + console.log("Goal not found"); + return; + } + + if (currentGoal.participants.find((ele) => ele.relId === relId && ele.following) === undefined) { + console.log("Changes ignored - goal not shared with participant"); + return; + } + let allAreLatest: (changesInGoal | null)[] = []; - if (rootGoal) { - if (payload.changeType === "deleted" || payload.changeType === "moved") { - allAreLatest = await Promise.all( - changes.map(async (ele) => { - const isLatest = true; - return isLatest ? ele : null; - }), - ); - } else { - allAreLatest = await Promise.all( - changes.map(async (ele) => { - const isLatest = await isIncomingGoalLatest(ele.goal.id, ele.goal); - return isLatest ? ele : null; - }), - ); - } - const filteredChanges = allAreLatest.filter((ele) => ele !== null); + if (payload.changeType === "deleted" || payload.changeType === "moved") { + allAreLatest = await Promise.all( + changes.map(async (ele) => { + const isLatest = true; + return isLatest ? ele : null; + }), + ); + } else { + allAreLatest = await Promise.all( + changes.map(async (ele) => { + const isLatest = await isIncomingGoalLatest(ele.goal.id, ele.goal); + return isLatest ? ele : null; + }), + ); + } - if (filteredChanges.length > 0) { - // Proceed if there are latest changes - console.log("Found latest changes. Proceeding with updates..."); + const filteredChanges = allAreLatest.filter((ele) => ele !== null); - let inbox: InboxItem = await getInboxItem(rootGoalId); - const defaultChanges = getDefaultValueOfGoalChanges(); + if (filteredChanges.length > 0) { + // Proceed if there are latest changes + console.log("Found latest changes. Proceeding with updates..."); - // Add filtered changes to defaultChanges - defaultChanges[changeType] = filteredChanges.map((ele) => ({ - ...ele, - intent: payload.type, - })); + let inbox: InboxItem = await getInboxItem(currentGoal.id); + const defaultChanges = getDefaultValueOfGoalChanges(); - if (!inbox) { - await createEmptyInboxItem(rootGoalId); - inbox = await getInboxItem(rootGoalId); - } + // Add filtered changes to defaultChanges + defaultChanges[changeType] = filteredChanges.map((ele) => ({ + ...ele, + intent: payload.type, + })); - await Promise.all([ - updateGoal(rootGoalId, { newUpdates: true }), - addGoalChangesInID(rootGoalId, relId, defaultChanges), - ]); - } else { - console.log("No latest changes. Skipping updates."); + if (!inbox) { + await createEmptyInboxItem(currentGoal.id); + inbox = await getInboxItem(currentGoal.id); } + + await Promise.all([ + updateGoal(currentGoal.id, { newUpdates: true }), + addGoalChangesInID(currentGoal.id, relId, defaultChanges), + ]); } else { - console.log(`Root goal with ID: ${rootGoalId} not found. Skipping update.`); + console.log("No latest changes. Skipping updates."); } } }; diff --git a/src/helpers/PubSubController.ts b/src/helpers/PubSubController.ts index 9eb4b9e5f..c17714947 100644 --- a/src/helpers/PubSubController.ts +++ b/src/helpers/PubSubController.ts @@ -22,7 +22,7 @@ export const sendUpdatedGoal = async ( .filter((ele) => !excludeSubs.includes(ele.sub.relId)) .forEach(async ({ sub, rootGoalId }) => { sendUpdatesToSubscriber(sub, rootGoalId, "modifiedGoals", [ - { level: ancestorGoalIds.length, goal: { ...changes, rootGoalId } }, + { level: ancestorGoalIds.length, goal: { ...changes, rootGoalId, participants: [] } }, ]).then(() => console.log("update sent")); }); } From 8494239cc7572b0e2d7cab24b9997006723ed6e6 Mon Sep 17 00:00:00 2001 From: Vinay Badgujar Date: Mon, 28 Oct 2024 13:41:13 +0530 Subject: [PATCH 18/54] fix: new goal add broken --- src/helpers/InboxProcessor.ts | 34 +++++++++++++++------------------ src/helpers/PubSubController.ts | 2 +- 2 files changed, 16 insertions(+), 20 deletions(-) diff --git a/src/helpers/InboxProcessor.ts b/src/helpers/InboxProcessor.ts index 8dcfa571f..037a2aa86 100644 --- a/src/helpers/InboxProcessor.ts +++ b/src/helpers/InboxProcessor.ts @@ -99,33 +99,31 @@ export const handleIncomingChanges = async (payload: Payload, relId: string) => const { changes, changeType } = payload; console.log("changes", changes); - let currentGoal: GoalItem | null = null; + let goal: GoalItem | null = null; if ("goal" in changes[0]) { - currentGoal = await getGoal(changes[0].goal.id); + const goalId = + changeType === "subgoals" || changeType === "newGoalMoved" ? changes[0].goal.parentGoalId : changes[0].goal.id; + + goal = await getGoal(goalId); } else { - currentGoal = await getGoal(changes[0].id); + goal = await getGoal(changes[0].id); } - if (!currentGoal) { + if (!goal) { console.log("Goal not found"); return; } - if (currentGoal.participants.find((ele) => ele.relId === relId && ele.following) === undefined) { + if (!goal.participants.find((p) => p.relId === relId && p.following)) { console.log("Changes ignored - goal not shared with participant"); return; } - let allAreLatest: (changesInGoal | null)[] = []; + let allAreLatest: (changesInGoal | changesInId | null)[] = []; if (payload.changeType === "deleted" || payload.changeType === "moved") { - allAreLatest = await Promise.all( - changes.map(async (ele) => { - const isLatest = true; - return isLatest ? ele : null; - }), - ); + allAreLatest = changes.map((ele) => ele); } else { allAreLatest = await Promise.all( changes.map(async (ele) => { @@ -138,26 +136,24 @@ export const handleIncomingChanges = async (payload: Payload, relId: string) => const filteredChanges = allAreLatest.filter((ele) => ele !== null); if (filteredChanges.length > 0) { - // Proceed if there are latest changes console.log("Found latest changes. Proceeding with updates..."); - let inbox: InboxItem = await getInboxItem(currentGoal.id); + let inbox: InboxItem = await getInboxItem(goal.id); const defaultChanges = getDefaultValueOfGoalChanges(); - // Add filtered changes to defaultChanges defaultChanges[changeType] = filteredChanges.map((ele) => ({ ...ele, intent: payload.type, })); if (!inbox) { - await createEmptyInboxItem(currentGoal.id); - inbox = await getInboxItem(currentGoal.id); + await createEmptyInboxItem(goal.id); + inbox = await getInboxItem(goal.id); } await Promise.all([ - updateGoal(currentGoal.id, { newUpdates: true }), - addGoalChangesInID(currentGoal.id, relId, defaultChanges), + updateGoal(goal.id, { newUpdates: true }), + addGoalChangesInID(goal.id, relId, defaultChanges), ]); } else { console.log("No latest changes. Skipping updates."); diff --git a/src/helpers/PubSubController.ts b/src/helpers/PubSubController.ts index c17714947..ab8a2d070 100644 --- a/src/helpers/PubSubController.ts +++ b/src/helpers/PubSubController.ts @@ -30,7 +30,7 @@ export const sendUpdatedGoal = async ( export const sendFinalUpdateOnGoal = async ( goalId: string, - action: "archived" | "deleted" | "restored" | "moved", + action: "archived" | "deleted" | "restored", ancestors: string[] = [], redefineAncestors = true, excludeSubs: string[] = [], From 4e87593d88e1eb05c1ada3824724e6b87e046de3 Mon Sep 17 00:00:00 2001 From: Vinay Badgujar Date: Mon, 28 Oct 2024 16:21:01 +0530 Subject: [PATCH 19/54] feat(collaboration): add support of having participants when a subgoal added to collaborated goal --- src/helpers/GoalController.ts | 59 ++++++++++++++---- src/helpers/InboxProcessor.ts | 107 +++++++++++++++++++-------------- src/models/InboxItem.ts | 13 +--- src/utils/defaultGenerators.ts | 14 ++--- 4 files changed, 117 insertions(+), 76 deletions(-) diff --git a/src/helpers/GoalController.ts b/src/helpers/GoalController.ts index 8de35a153..9df94cb5d 100644 --- a/src/helpers/GoalController.ts +++ b/src/helpers/GoalController.ts @@ -1,5 +1,5 @@ /* eslint-disable no-await-in-loop */ -import { GoalItem } from "@src/models/GoalItem"; +import { GoalItem, IParticipant } from "@src/models/GoalItem"; import { inheritParentProps } from "@src/utils"; import { sendUpdatesToSubscriber } from "@src/services/contact.service"; import { getSharedWMGoal, removeSharedWMChildrenGoals, updateSharedWMGoal } from "@src/api/SharedWMAPI"; @@ -19,32 +19,65 @@ import { sendFinalUpdateOnGoal, sendUpdatedGoal } from "./PubSubController"; export const createGoal = async (newGoal: GoalItem, parentGoalId: string, ancestors: string[], hintOption: boolean) => { const level = ancestors.length; + if (hintOption) { getHintsFromAPI(newGoal) - .then((hints) => addHintItem(newGoal.id, hintOption, hints || [])) + .then((hints) => { + addHintItem(newGoal.id, hintOption, hints || []); + }) .catch((error) => console.error("Error fetching hints:", error)); } if (parentGoalId && parentGoalId !== "root") { const parentGoal = await getGoal(parentGoalId); - if (!parentGoal) return { parentGoal: null }; - const newGoalId = await addGoal(inheritParentProps(newGoal, parentGoal)); + if (!parentGoal) { + console.warn("Parent goal not found:", parentGoalId); + return { parentGoal: null }; + } - const subscribers = await getParticipantsOfGoals(ancestors); - if (newGoalId) { - subscribers.map(async ({ sub, rootGoalId }) => { - sendUpdatesToSubscriber(sub, rootGoalId, "subgoals", [ - { - level, - goal: { ...newGoal, id: newGoalId }, - }, - ]).then(() => console.log("update sent")); + const ancestorGoals = await Promise.all(ancestors.map((id) => getGoal(id))); + const allParticipants = new Map(); + + [...ancestorGoals, parentGoal].forEach((goal) => { + if (!goal?.participants) return; + goal.participants.forEach((participant) => { + if (participant.following) { + allParticipants.set(participant.relId, participant); + } }); + }); + const goalWithParentProps = inheritParentProps(newGoal, parentGoal); + const updatedGoal = { + ...goalWithParentProps, + participants: Array.from(allParticipants.values()), + }; + + const newGoalId = await addGoal(updatedGoal); + + if (newGoalId) { + const subscribers = await getParticipantsOfGoals(ancestors); + await Promise.all( + subscribers.map(async ({ sub, rootGoalId }) => { + await sendUpdatesToSubscriber(sub, rootGoalId, "subgoals", [ + { + level, + goal: { + ...updatedGoal, + id: newGoalId, + rootGoalId, + participants: [], + }, + }, + ]); + }), + ); } + const newSublist = parentGoal && parentGoal.sublist ? [...parentGoal.sublist, newGoalId] : [newGoalId]; await updateGoal(parentGoalId, { sublist: newSublist }); return { parentGoal }; } + await addGoal(newGoal); return { parentGoal: null }; }; diff --git a/src/helpers/InboxProcessor.ts b/src/helpers/InboxProcessor.ts index 037a2aa86..2e7fb934b 100644 --- a/src/helpers/InboxProcessor.ts +++ b/src/helpers/InboxProcessor.ts @@ -1,5 +1,5 @@ -import { GoalItem } from "@src/models/GoalItem"; -import { changesInGoal, changesInId, InboxItem } from "@src/models/InboxItem"; +import { GoalItem, typeOfSub } from "@src/models/GoalItem"; +import { changesInGoal, changesInId, typeOfChange, typeOfIntent } from "@src/models/InboxItem"; import { getDefaultValueOfGoalChanges } from "@src/utils/defaultGenerators"; import { @@ -21,12 +21,13 @@ import { import { ITagChangesSchemaVersion, ITagsChanges } from "@src/Interfaces/IDisplayChangesModal"; import { fixDateVlauesInGoalObject } from "@src/utils"; import { getDeletedGoal, restoreUserGoal } from "@src/api/TrashAPI"; +import { getContactByRelId } from "@src/api/ContactsAPI"; import { isIncomingGoalLatest } from "./mergeSharedGoalItems"; export interface Payload { relId: string; lastProcessedTimestamp: string; - changeType: string; + changeType: typeOfChange; rootGoalId: string; changes: (changesInGoal | changesInId)[]; type: string; @@ -96,68 +97,75 @@ export const handleIncomingChanges = async (payload: Payload, relId: string) => } } } else if (["sharer", "suggestion"].includes(payload.type)) { - const { changes, changeType } = payload; - console.log("changes", changes); + const { changes, changeType, rootGoalId } = payload; + if (changeType === "subgoals") { + const rootGoal = await getGoal(rootGoalId); + if (!rootGoal || !rootGoal.participants.find((p) => p.relId === relId && p.following)) { + return; + } - let goal: GoalItem | null = null; + const contact = await getContactByRelId(relId); - if ("goal" in changes[0]) { - const goalId = - changeType === "subgoals" || changeType === "newGoalMoved" ? changes[0].goal.parentGoalId : changes[0].goal.id; + const goalsWithParticipants = changes.map((ele: changesInGoal) => ({ + ...ele, + goal: { + ...fixDateVlauesInGoalObject(ele.goal), + participants: [{ relId, following: true, type: "sharer" as typeOfSub, name: contact?.name || "" }], + }, + })); - goal = await getGoal(goalId); - } else { - goal = await getGoal(changes[0].id); - } + const inbox = await getInboxItem(rootGoalId); + const defaultChanges = getDefaultValueOfGoalChanges(); + defaultChanges[changeType] = [ + ...goalsWithParticipants.map((ele) => ({ ...ele, intent: payload.type as typeOfIntent })), + ]; - if (!goal) { - console.log("Goal not found"); + if (!inbox) { + await createEmptyInboxItem(rootGoalId); + } + + await Promise.all([ + updateGoal(rootGoalId, { newUpdates: true }), + addGoalChangesInID(rootGoalId, relId, defaultChanges), + ]); return; } - if (!goal.participants.find((p) => p.relId === relId && p.following)) { - console.log("Changes ignored - goal not shared with participant"); + const goalId = "goal" in changes[0] ? changes[0].goal.id : changes[0].id; + const goal = await getGoal(goalId); + + if (!goal || !goal.participants.find((p) => p.relId === relId && p.following)) { + console.log("Goal not found or not shared with participant"); return; } - let allAreLatest: (changesInGoal | changesInId | null)[] = []; - - if (payload.changeType === "deleted" || payload.changeType === "moved") { - allAreLatest = changes.map((ele) => ele); - } else { - allAreLatest = await Promise.all( + let filteredChanges = changes; + if (changeType !== "deleted" && changeType !== "moved") { + const latestChanges = await Promise.all( changes.map(async (ele) => { const isLatest = await isIncomingGoalLatest(ele.goal.id, ele.goal); return isLatest ? ele : null; }), ); + filteredChanges = latestChanges.filter((ele): ele is changesInGoal => ele !== null); } - const filteredChanges = allAreLatest.filter((ele) => ele !== null); - - if (filteredChanges.length > 0) { - console.log("Found latest changes. Proceeding with updates..."); - - let inbox: InboxItem = await getInboxItem(goal.id); - const defaultChanges = getDefaultValueOfGoalChanges(); - - defaultChanges[changeType] = filteredChanges.map((ele) => ({ - ...ele, - intent: payload.type, - })); + if (filteredChanges.length === 0) { + return; + } - if (!inbox) { - await createEmptyInboxItem(goal.id); - inbox = await getInboxItem(goal.id); - } + const inbox = await getInboxItem(goal.id); + const defaultChanges = getDefaultValueOfGoalChanges(); + defaultChanges[changeType] = filteredChanges.map((ele) => ({ + ...ele, + intent: payload.type, + })); - await Promise.all([ - updateGoal(goal.id, { newUpdates: true }), - addGoalChangesInID(goal.id, relId, defaultChanges), - ]); - } else { - console.log("No latest changes. Skipping updates."); + if (!inbox) { + await createEmptyInboxItem(goal.id); } + + await Promise.all([updateGoal(goal.id, { newUpdates: true }), addGoalChangesInID(goal.id, relId, defaultChanges)]); } }; @@ -165,7 +173,14 @@ export const acceptSelectedSubgoals = async (selectedGoals: GoalItem[], parentGo try { const childrens: string[] = []; selectedGoals.forEach(async (goal: GoalItem) => { - addGoal(fixDateVlauesInGoalObject({ ...goal, participants: [] })).catch((err) => console.log(err)); + const { relId } = goal.participants[0]; + const contact = await getContactByRelId(relId); + addGoal( + fixDateVlauesInGoalObject({ + ...goal, + participants: [{ relId, following: true, type: "sharer", name: contact?.name || "" }], + }), + ).catch((err) => console.log(err)); childrens.push(goal.id); }); await addIntoSublist(parentGoal.id, childrens); diff --git a/src/models/InboxItem.ts b/src/models/InboxItem.ts index 94993f2b3..3b1399ad3 100644 --- a/src/models/InboxItem.ts +++ b/src/models/InboxItem.ts @@ -1,13 +1,6 @@ import { GoalItem } from "./GoalItem"; -export type typeOfChange = - | "subgoals" - | "modifiedGoals" - | "archived" - | "deleted" - | "restored" - | "moved" - | "newGoalMoved"; +export type typeOfChange = "subgoals" | "modifiedGoals" | "archived" | "deleted" | "restored" | "moved"; export type typeOfIntent = "suggestion" | "shared"; export type changesInId = { level: number; id: string; intent: typeOfIntent }; @@ -19,8 +12,8 @@ export interface IChangesInGoal { archived: changesInId[]; deleted: changesInId[]; restored: changesInId[]; - moved: changesInGoal[]; - newGoalMoved: changesInGoal[]; + moved: changesInId[]; + // newGoalMoved: changesInGoal[]; } export interface InboxItem { diff --git a/src/utils/defaultGenerators.ts b/src/utils/defaultGenerators.ts index 0dcd1202c..78bf3d0e6 100644 --- a/src/utils/defaultGenerators.ts +++ b/src/utils/defaultGenerators.ts @@ -1,6 +1,7 @@ import { v5 as uuidv5 } from "uuid"; import { GoalItem } from "@src/models/GoalItem"; +import { changesInGoal, changesInId } from "@src/models/InboxItem"; import { myNameSpace } from "."; export const createPollObject = (goal: GoalItem, params: object = {}) => ({ @@ -23,12 +24,11 @@ export const createPollObject = (goal: GoalItem, params: object = {}) => ({ export function getDefaultValueOfGoalChanges() { return { - subgoals: [], - modifiedGoals: [], - archived: [], - deleted: [], - restored: [], - newGoalMoved: [], - moved: [], + subgoals: [] as changesInGoal[], + modifiedGoals: [] as changesInGoal[], + archived: [] as changesInId[], + deleted: [] as changesInId[], + restored: [] as changesInId[], + moved: [] as changesInId[], }; } From 43efc3c7e57936bc029a532180e2d37a707db779 Mon Sep 17 00:00:00 2001 From: Vinay Badgujar Date: Wed, 30 Oct 2024 07:36:45 +0530 Subject: [PATCH 20/54] remove: newgoalmoved check --- src/helpers/GoalProcessor.ts | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/helpers/GoalProcessor.ts b/src/helpers/GoalProcessor.ts index c7789fde9..ee2d55666 100644 --- a/src/helpers/GoalProcessor.ts +++ b/src/helpers/GoalProcessor.ts @@ -112,8 +112,6 @@ export const getTypeAtPriority = (goalChanges: IChangesInGoal) => { typeAtPriority = "deleted"; } else if (goalChanges.restored.length > 0) { typeAtPriority = "restored"; - } else if (goalChanges.newGoalMoved.length > 0) { - typeAtPriority = "newGoalMoved"; } else if (goalChanges.moved.length > 0) { typeAtPriority = "moved"; } From 7ec0e15eb8f1448639b65f2c3eef48871afa6776 Mon Sep 17 00:00:00 2001 From: Vinay Badgujar Date: Mon, 4 Nov 2024 09:15:16 +0530 Subject: [PATCH 21/54] show new changes to root goal of shared goal --- .../DisplayChangesModal.tsx | 26 ++++-- .../DisplayChangesModal/Header.tsx | 6 -- src/helpers/GoalController.ts | 80 +++++++++++-------- src/helpers/GoalProcessor.ts | 4 +- src/helpers/InboxProcessor.ts | 22 +++-- 5 files changed, 87 insertions(+), 51 deletions(-) diff --git a/src/components/GoalsComponents/DisplayChangesModal/DisplayChangesModal.tsx b/src/components/GoalsComponents/DisplayChangesModal/DisplayChangesModal.tsx index 761a6dde2..d7cf74a00 100644 --- a/src/components/GoalsComponents/DisplayChangesModal/DisplayChangesModal.tsx +++ b/src/components/GoalsComponents/DisplayChangesModal/DisplayChangesModal.tsx @@ -155,7 +155,7 @@ const DisplayChangesModal = ({ currentMainGoal }: { currentMainGoal: GoalItem }) return; } const removeChanges = - currentDisplay === "subgoals" || currentDisplay === "newGoalMoved" + currentDisplay === "subgoals" ? newGoals.map(({ goal }) => goal.id) : currentDisplay === "moved" ? [goalUnderReview.id, ...(await getAllDescendants(goalUnderReview.id)).map((goal: GoalItem) => goal.id)] @@ -210,7 +210,7 @@ const DisplayChangesModal = ({ currentMainGoal }: { currentMainGoal: GoalItem }) if (currentDisplay === "moved") { await handleMoveChanges(); } - if (currentDisplay === "subgoals" || currentDisplay === "newGoalMoved") { + if (currentDisplay === "subgoals") { const goalsToBeSelected = newGoals .filter(({ goal }) => !unselectedChanges.includes(goal.id)) .map(({ goal }) => goal); @@ -277,14 +277,13 @@ const DisplayChangesModal = ({ currentMainGoal }: { currentMainGoal: GoalItem }) if (changedGoal) { setGoalUnderReview({ ...changedGoal }); // TODO: remove the newGoalsMoved and try handle in subgoal only - if (typeAtPriority === "subgoals" || typeAtPriority === "newGoalMoved") { + if (typeAtPriority === "subgoals") { setNewGoals(goals || []); } else if (typeAtPriority === "modifiedGoals") { setUpdatesIntent(goals[0].intent); const incGoal: GoalItem = { ...goals[0].goal }; setUpdateList({ ...findGoalTagChanges(changedGoal, incGoal) }); } else if (typeAtPriority === "moved") { - const movedGoal = goals[0].goal; setUpdatesIntent(goals[0].intent); setGoalUnderReview({ ...movedGoal }); } @@ -313,6 +312,23 @@ const DisplayChangesModal = ({ currentMainGoal }: { currentMainGoal: GoalItem }) init(); }, [currentMainGoal]); + const getChangedGoalFromRoot = async (rootGoal: GoalItem, relId: string) => { + const { typeAtPriority, goals, parentId } = await jumpToLowestChanges(rootGoal.id, relId); + + if (typeAtPriority === "none") return { typeAtPriority, goals, parentId }; + + const changedGoal = await getGoal(parentId); + if (!changedGoal) return { typeAtPriority, goals, parentId }; + + return { + typeAtPriority, + goals, + parentId, + changedGoal, + rootGoal, + }; + }; + return ( {currentMainGoal && ( @@ -365,7 +381,7 @@ const DisplayChangesModal = ({ currentMainGoal }: { currentMainGoal: GoalItem }) )} {["deleted", "archived", "restored"].includes(currentDisplay) &&
} {currentDisplay === "modifiedGoals" && getEditChangesList()} - {(currentDisplay === "subgoals" || currentDisplay === "newGoalMoved") && getSubgoalsList()} + {currentDisplay === "subgoals" && getSubgoalsList()} {currentDisplay === "moved" && getMovedSubgoalsList(goalUnderReview, oldParentTitle, newParentTitle)}
diff --git a/src/components/GoalsComponents/DisplayChangesModal/Header.tsx b/src/components/GoalsComponents/DisplayChangesModal/Header.tsx index 3288e71ec..d6fbfb723 100644 --- a/src/components/GoalsComponents/DisplayChangesModal/Header.tsx +++ b/src/components/GoalsComponents/DisplayChangesModal/Header.tsx @@ -48,12 +48,6 @@ const Header = ({ {contactName} moved {title}. ); - case "newGoalMoved": - return ( - <> - {contactName} moved {title} to your goal. - - ); default: return <> ; } diff --git a/src/helpers/GoalController.ts b/src/helpers/GoalController.ts index 9df94cb5d..a32b0db6e 100644 --- a/src/helpers/GoalController.ts +++ b/src/helpers/GoalController.ts @@ -227,6 +227,21 @@ const updateRootGoal = async (goalId: string, newRootGoalId: string) => { } }; +export const getRootGoalId = async (goalId: string): Promise => { + const goal = await getGoal(goalId); + if (!goal || goal.parentGoalId === "root") { + return goal?.id || "root"; + } + return getRootGoalId(goal.parentGoalId); +}; + +export const updateRootGoalNotification = async (goalId: string) => { + const rootGoalId = await getRootGoalId(goalId); + if (rootGoalId !== "root") { + await updateGoal(rootGoalId, { newUpdates: true }); + } +}; + export const removeGoalFromParentSublist = async (goalId: string, parentGoalId: string) => { const parentGoal = await getGoal(parentGoalId); if (!parentGoal) return; @@ -280,38 +295,39 @@ export const moveGoalHierarchy = async (goalId: string, newParentGoalId: string) removeGoalFromParentSublist(goalToMove.id, oldParentId), addGoalToNewParentSublist(goalToMove.id, newParentGoalId), updateRootGoal(goalToMove.id, newParentGoal?.rootGoalId ?? "root"), + sendUpdatedGoal(goalToMove.id, ancestorGoalIds, false), ]); - const subscribers = await getParticipantsOfGoals(ancestorGoalIds); - subscribers.forEach(async ({ sub, rootGoalId }) => { - await sendUpdatesToSubscriber(sub, rootGoalId, "moved", [ - { - level: ancestorGoalIds.length, - goal: { - ...goalToMove, - parentGoalId: newParentGoalId, - rootGoalId, - }, - }, - ]); - }); - - // Also send updates for all descendants - const descendants = await getAllDescendants(goalId); - if (descendants.length > 0) { - subscribers.forEach(async ({ sub, rootGoalId }) => { - await sendUpdatesToSubscriber( - sub, - rootGoalId, - "moved", - descendants.map((descendant) => ({ - level: ancestorGoalIds.length + 1, - goal: { - ...descendant, - rootGoalId, - }, - })), - ); - }); - } + // const subscribers = await getParticipantsOfGoals(ancestorGoalIds); + // subscribers.forEach(async ({ sub, rootGoalId }) => { + // await sendUpdatesToSubscriber(sub, rootGoalId, "moved", [ + // { + // level: ancestorGoalIds.length, + // goal: { + // ...goalToMove, + // parentGoalId: newParentGoalId, + // rootGoalId, + // }, + // }, + // ]); + // }); + + // // Also send updates for all descendants + // const descendants = await getAllDescendants(goalId); + // if (descendants.length > 0) { + // subscribers.forEach(async ({ sub, rootGoalId }) => { + // await sendUpdatesToSubscriber( + // sub, + // rootGoalId, + // "moved", + // descendants.map((descendant) => ({ + // level: ancestorGoalIds.length + 1, + // goal: { + // ...descendant, + // rootGoalId, + // }, + // })), + // ); + // }); + // } }; diff --git a/src/helpers/GoalProcessor.ts b/src/helpers/GoalProcessor.ts index ee2d55666..04e0b2001 100644 --- a/src/helpers/GoalProcessor.ts +++ b/src/helpers/GoalProcessor.ts @@ -131,7 +131,7 @@ export const jumpToLowestChanges = async (id: string, relId: string) => { const parentId = "id" in goalAtPriority ? goalAtPriority.id - : typeAtPriority === "subgoals" || typeAtPriority === "newGoalMoved" + : typeAtPriority === "subgoals" ? goalAtPriority.goal.parentGoalId : goalAtPriority.goal.id; @@ -139,7 +139,7 @@ export const jumpToLowestChanges = async (id: string, relId: string) => { const result = { typeAtPriority, parentId, goals: [await getGoal(parentId)] }; return result; } - if (typeAtPriority === "subgoals" || typeAtPriority === "newGoalMoved") { + if (typeAtPriority === "subgoals") { goalChanges[typeAtPriority].forEach(({ intent, goal }) => { if (goal.parentGoalId === parentId) goals.push({ intent, goal }); }); diff --git a/src/helpers/InboxProcessor.ts b/src/helpers/InboxProcessor.ts index 2e7fb934b..a05a21d9a 100644 --- a/src/helpers/InboxProcessor.ts +++ b/src/helpers/InboxProcessor.ts @@ -23,6 +23,7 @@ import { fixDateVlauesInGoalObject } from "@src/utils"; import { getDeletedGoal, restoreUserGoal } from "@src/api/TrashAPI"; import { getContactByRelId } from "@src/api/ContactsAPI"; import { isIncomingGoalLatest } from "./mergeSharedGoalItems"; +import { getRootGoalId, updateRootGoalNotification } from "./GoalController"; export interface Payload { relId: string; @@ -35,6 +36,18 @@ export interface Payload { TTL: number; } +const addChangesToRootGoal = async (goalId: string, relId: string, changes: any) => { + const rootGoalId = await getRootGoalId(goalId); + if (rootGoalId === "root") return; + + const inbox = await getInboxItem(rootGoalId); + if (!inbox) { + await createEmptyInboxItem(rootGoalId); + } + + await Promise.all([updateRootGoalNotification(rootGoalId), addGoalChangesInID(rootGoalId, relId, changes)]); +}; + export const handleIncomingChanges = async (payload: Payload, relId: string) => { console.log("Incoming change", payload); @@ -61,7 +74,7 @@ export const handleIncomingChanges = async (payload: Payload, relId: string) => console.log("Changes ignored"); return; } - if (payload.changeType === "subgoals" || payload.changeType === "newGoalMoved") { + if (payload.changeType === "subgoals") { const changes = [ ...payload.changes.map((ele: changesInGoal) => ({ ...ele, goal: fixDateVlauesInGoalObject(ele.goal) })), ]; @@ -124,10 +137,7 @@ export const handleIncomingChanges = async (payload: Payload, relId: string) => await createEmptyInboxItem(rootGoalId); } - await Promise.all([ - updateGoal(rootGoalId, { newUpdates: true }), - addGoalChangesInID(rootGoalId, relId, defaultChanges), - ]); + await addChangesToRootGoal(rootGoalId, relId, defaultChanges); return; } @@ -165,7 +175,7 @@ export const handleIncomingChanges = async (payload: Payload, relId: string) => await createEmptyInboxItem(goal.id); } - await Promise.all([updateGoal(goal.id, { newUpdates: true }), addGoalChangesInID(goal.id, relId, defaultChanges)]); + await addChangesToRootGoal(goal.id, relId, defaultChanges); } }; From cde09d6f013791315db1a983876232d5f5b0ac6a Mon Sep 17 00:00:00 2001 From: Vinay Badgujar Date: Mon, 4 Nov 2024 09:26:58 +0530 Subject: [PATCH 22/54] fix: archived action not processed correctly --- src/helpers/InboxProcessor.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/helpers/InboxProcessor.ts b/src/helpers/InboxProcessor.ts index a05a21d9a..a8a65ef7d 100644 --- a/src/helpers/InboxProcessor.ts +++ b/src/helpers/InboxProcessor.ts @@ -150,7 +150,7 @@ export const handleIncomingChanges = async (payload: Payload, relId: string) => } let filteredChanges = changes; - if (changeType !== "deleted" && changeType !== "moved") { + if (changeType !== "deleted" && changeType !== "moved" && changeType !== "restored" && changeType !== "archived") { const latestChanges = await Promise.all( changes.map(async (ele) => { const isLatest = await isIncomingGoalLatest(ele.goal.id, ele.goal); From 951b8bc661024dc073c1e69976f11a397565815c Mon Sep 17 00:00:00 2001 From: Vinay Badgujar Date: Mon, 4 Nov 2024 10:47:57 +0530 Subject: [PATCH 23/54] patch: if a descendant is moved from shared goal to non shared goal, it is moved to root in reciever side --- .../DisplayChangesModal.tsx | 37 ++++------- .../DisplayChangesModal/ShowChanges.tsx | 2 +- src/helpers/GoalController.ts | 64 +++++++++---------- src/helpers/InboxProcessor.ts | 16 ----- src/helpers/PubSubController.ts | 2 +- 5 files changed, 46 insertions(+), 75 deletions(-) diff --git a/src/components/GoalsComponents/DisplayChangesModal/DisplayChangesModal.tsx b/src/components/GoalsComponents/DisplayChangesModal/DisplayChangesModal.tsx index d7cf74a00..992243116 100644 --- a/src/components/GoalsComponents/DisplayChangesModal/DisplayChangesModal.tsx +++ b/src/components/GoalsComponents/DisplayChangesModal/DisplayChangesModal.tsx @@ -174,30 +174,19 @@ const DisplayChangesModal = ({ currentMainGoal }: { currentMainGoal: GoalItem }) } const parentGoal = await getGoal(goalUnderReview.parentGoalId); - if (!parentGoal) { - console.log("Removing goal with childrens"); - await removeGoalWithChildrens(goalUnderReview, true) - .then(() => { - deleteChanges(); - }) - .then(() => { - setCurrentDisplay("none"); - }); - } else { - await Promise.all([ - updateGoal(goalUnderReview.id, { parentGoalId: parentGoal.id }), - removeGoalFromParentSublist(goalUnderReview.id, parentGoal.title), - addGoalToNewParentSublist(goalUnderReview.id, parentGoal.id), - ]); + await Promise.all([ + updateGoal(goalUnderReview.id, { parentGoalId: parentGoal?.id ?? "root" }), + removeGoalFromParentSublist(goalUnderReview.id, parentGoal?.title ?? "root"), + addGoalToNewParentSublist(goalUnderReview.id, parentGoal?.id ?? "root"), + ]); - // TODO: handle this later - // await sendUpdatedGoal( - // goalUnderReview.id, - // [], - // true, - // updatesIntent === "suggestion" ? [] : [participants[activePPT].relId], - // ); - } + // TODO: handle this later + // await sendUpdatedGoal( + // goalUnderReview.id, + // [], + // true, + // updatesIntent === "suggestion" ? [] : [participants[activePPT].relId], + // ); }; const acceptChanges = async () => { @@ -285,7 +274,7 @@ const DisplayChangesModal = ({ currentMainGoal }: { currentMainGoal: GoalItem }) setUpdateList({ ...findGoalTagChanges(changedGoal, incGoal) }); } else if (typeAtPriority === "moved") { setUpdatesIntent(goals[0].intent); - setGoalUnderReview({ ...movedGoal }); + setGoalUnderReview({ ...goals[0].goal }); } } } diff --git a/src/components/GoalsComponents/DisplayChangesModal/ShowChanges.tsx b/src/components/GoalsComponents/DisplayChangesModal/ShowChanges.tsx index 8332d932f..2277bcf1a 100644 --- a/src/components/GoalsComponents/DisplayChangesModal/ShowChanges.tsx +++ b/src/components/GoalsComponents/DisplayChangesModal/ShowChanges.tsx @@ -34,7 +34,7 @@ export const getMovedSubgoalsList = ( {newParentTitle === "Non-shared goal" && (
- The new parent goal is not shared. The goal will be deleted for you. + The new parent goal is not shared. The goal will be moved to the root.
)}
diff --git a/src/helpers/GoalController.ts b/src/helpers/GoalController.ts index a32b0db6e..2181d6469 100644 --- a/src/helpers/GoalController.ts +++ b/src/helpers/GoalController.ts @@ -295,39 +295,37 @@ export const moveGoalHierarchy = async (goalId: string, newParentGoalId: string) removeGoalFromParentSublist(goalToMove.id, oldParentId), addGoalToNewParentSublist(goalToMove.id, newParentGoalId), updateRootGoal(goalToMove.id, newParentGoal?.rootGoalId ?? "root"), - sendUpdatedGoal(goalToMove.id, ancestorGoalIds, false), ]); - // const subscribers = await getParticipantsOfGoals(ancestorGoalIds); - // subscribers.forEach(async ({ sub, rootGoalId }) => { - // await sendUpdatesToSubscriber(sub, rootGoalId, "moved", [ - // { - // level: ancestorGoalIds.length, - // goal: { - // ...goalToMove, - // parentGoalId: newParentGoalId, - // rootGoalId, - // }, - // }, - // ]); - // }); - - // // Also send updates for all descendants - // const descendants = await getAllDescendants(goalId); - // if (descendants.length > 0) { - // subscribers.forEach(async ({ sub, rootGoalId }) => { - // await sendUpdatesToSubscriber( - // sub, - // rootGoalId, - // "moved", - // descendants.map((descendant) => ({ - // level: ancestorGoalIds.length + 1, - // goal: { - // ...descendant, - // rootGoalId, - // }, - // })), - // ); - // }); - // } + const subscribers = await getParticipantsOfGoals(ancestorGoalIds); + subscribers.forEach(async ({ sub, rootGoalId }) => { + await sendUpdatesToSubscriber(sub, rootGoalId, "moved", [ + { + level: ancestorGoalIds.length, + goal: { + ...goalToMove, + parentGoalId: newParentGoalId, + rootGoalId, + }, + }, + ]); + }); + + const descendants = await getAllDescendants(goalId); + if (descendants.length > 0) { + subscribers.forEach(async ({ sub, rootGoalId }) => { + await sendUpdatesToSubscriber( + sub, + rootGoalId, + "moved", + descendants.map((descendant) => ({ + level: ancestorGoalIds.length + 1, + goal: { + ...descendant, + rootGoalId, + }, + })), + ); + }); + } }; diff --git a/src/helpers/InboxProcessor.ts b/src/helpers/InboxProcessor.ts index a8a65ef7d..257d7134d 100644 --- a/src/helpers/InboxProcessor.ts +++ b/src/helpers/InboxProcessor.ts @@ -51,22 +51,6 @@ const addChangesToRootGoal = async (goalId: string, relId: string, changes: any) export const handleIncomingChanges = async (payload: Payload, relId: string) => { console.log("Incoming change", payload); - if (payload.changeType === "moved") { - const rootGoal = await getGoal(payload.rootGoalId); - const currentGoal = await getGoal(payload.changes[0].goal.id); - - if (rootGoal) { - const movedGoal = payload.changes[0].goal; - const newParentGoal = await getGoal(movedGoal.parentGoalId); - if ( - currentGoal?.participants.find((ele) => ele.relId === relId && ele.following) !== undefined && - !newParentGoal - ) { - return; - } - } - } - if (payload.type === "sharer" && (await getSharedWMGoal(payload.rootGoalId))) { console.log("Incoming change is a shared goal. Processing..."); const incGoal = await getSharedWMGoal(payload.rootGoalId); diff --git a/src/helpers/PubSubController.ts b/src/helpers/PubSubController.ts index ab8a2d070..c17714947 100644 --- a/src/helpers/PubSubController.ts +++ b/src/helpers/PubSubController.ts @@ -30,7 +30,7 @@ export const sendUpdatedGoal = async ( export const sendFinalUpdateOnGoal = async ( goalId: string, - action: "archived" | "deleted" | "restored", + action: "archived" | "deleted" | "restored" | "moved", ancestors: string[] = [], redefineAncestors = true, excludeSubs: string[] = [], From 4f34812a177bcf40001145b60d1bb0795f12072f Mon Sep 17 00:00:00 2001 From: Vinay Badgujar Date: Mon, 4 Nov 2024 11:59:45 +0530 Subject: [PATCH 24/54] feat(collaboration): integrate new shared goal creation function --- .../DisplayChangesModal/AcceptBtn.tsx | 1 + .../DisplayChangesModal/DisplayChangesModal.tsx | 8 ++++---- .../GoalsComponents/DisplayChangesModal/Header.tsx | 6 ++++++ src/helpers/GoalController.ts | 12 +++++++++++- src/helpers/GoalProcessor.ts | 6 ++++-- src/helpers/InboxProcessor.ts | 4 ++-- src/helpers/PartnerController.ts | 2 +- src/models/InboxItem.ts | 11 +++++++++-- src/utils/defaultGenerators.ts | 1 + 9 files changed, 39 insertions(+), 12 deletions(-) diff --git a/src/components/GoalsComponents/DisplayChangesModal/AcceptBtn.tsx b/src/components/GoalsComponents/DisplayChangesModal/AcceptBtn.tsx index ba90f5af4..5e5fe00e4 100644 --- a/src/components/GoalsComponents/DisplayChangesModal/AcceptBtn.tsx +++ b/src/components/GoalsComponents/DisplayChangesModal/AcceptBtn.tsx @@ -34,6 +34,7 @@ const AcceptBtn = ({ typeAtPriority, acceptChanges }: AcceptBtnProps) => { {typeAtPriority === "restored" && "Restore for me too"} {typeAtPriority === "deleted" && "Delete for me too"} {typeAtPriority === "subgoals" && "Add all checked"} + {typeAtPriority === "newGoalMoved" && "Move for me too"} {typeAtPriority === "modifiedGoals" && "Make all checked changes"} {typeAtPriority === "moved" && "Move for me too"} diff --git a/src/components/GoalsComponents/DisplayChangesModal/DisplayChangesModal.tsx b/src/components/GoalsComponents/DisplayChangesModal/DisplayChangesModal.tsx index 992243116..c214fdf7c 100644 --- a/src/components/GoalsComponents/DisplayChangesModal/DisplayChangesModal.tsx +++ b/src/components/GoalsComponents/DisplayChangesModal/DisplayChangesModal.tsx @@ -155,7 +155,7 @@ const DisplayChangesModal = ({ currentMainGoal }: { currentMainGoal: GoalItem }) return; } const removeChanges = - currentDisplay === "subgoals" + currentDisplay === "subgoals" || currentDisplay === "newGoalMoved" ? newGoals.map(({ goal }) => goal.id) : currentDisplay === "moved" ? [goalUnderReview.id, ...(await getAllDescendants(goalUnderReview.id)).map((goal: GoalItem) => goal.id)] @@ -199,7 +199,7 @@ const DisplayChangesModal = ({ currentMainGoal }: { currentMainGoal: GoalItem }) if (currentDisplay === "moved") { await handleMoveChanges(); } - if (currentDisplay === "subgoals") { + if (currentDisplay === "subgoals" || currentDisplay === "newGoalMoved") { const goalsToBeSelected = newGoals .filter(({ goal }) => !unselectedChanges.includes(goal.id)) .map(({ goal }) => goal); @@ -266,7 +266,7 @@ const DisplayChangesModal = ({ currentMainGoal }: { currentMainGoal: GoalItem }) if (changedGoal) { setGoalUnderReview({ ...changedGoal }); // TODO: remove the newGoalsMoved and try handle in subgoal only - if (typeAtPriority === "subgoals") { + if (typeAtPriority === "subgoals" || typeAtPriority === "newGoalMoved") { setNewGoals(goals || []); } else if (typeAtPriority === "modifiedGoals") { setUpdatesIntent(goals[0].intent); @@ -370,7 +370,7 @@ const DisplayChangesModal = ({ currentMainGoal }: { currentMainGoal: GoalItem }) )} {["deleted", "archived", "restored"].includes(currentDisplay) &&
} {currentDisplay === "modifiedGoals" && getEditChangesList()} - {currentDisplay === "subgoals" && getSubgoalsList()} + {(currentDisplay === "subgoals" || currentDisplay === "newGoalMoved") && getSubgoalsList()} {currentDisplay === "moved" && getMovedSubgoalsList(goalUnderReview, oldParentTitle, newParentTitle)}
diff --git a/src/components/GoalsComponents/DisplayChangesModal/Header.tsx b/src/components/GoalsComponents/DisplayChangesModal/Header.tsx index d6fbfb723..6133809cb 100644 --- a/src/components/GoalsComponents/DisplayChangesModal/Header.tsx +++ b/src/components/GoalsComponents/DisplayChangesModal/Header.tsx @@ -18,6 +18,12 @@ const Header = ({ {contactName} added to {title}.    Add as well ? ); + case "newGoalMoved": + return ( + <> + {contactName} moved {title} to {title}.    Move as well ? + + ); case "modifiedGoals": return ( <> diff --git a/src/helpers/GoalController.ts b/src/helpers/GoalController.ts index 2181d6469..811dd77b4 100644 --- a/src/helpers/GoalController.ts +++ b/src/helpers/GoalController.ts @@ -284,12 +284,20 @@ export const getGoalHistoryToRoot = async (goalId: string): Promise<{ goalID: st export const moveGoalHierarchy = async (goalId: string, newParentGoalId: string) => { const goalToMove = await getGoal(goalId); const newParentGoal = await getGoal(newParentGoalId); - if (!goalToMove) return; + + if (!goalToMove) { + return; + } const oldParentId = goalToMove.parentGoalId; const ancestors = await getGoalHistoryToRoot(goalId); const ancestorGoalIds = ancestors.map((ele) => ele.goalID); + const ancestorGoalsIdsOfNewParent = await getGoalHistoryToRoot(newParentGoalId); + const ancestorGoalIdsOfNewParent = ancestorGoalsIdsOfNewParent.map((ele) => ele.goalID); + + await createSharedGoal(goalToMove, newParentGoalId, ancestorGoalIdsOfNewParent); + await Promise.all([ updateGoal(goalToMove.id, { parentGoalId: newParentGoalId }), removeGoalFromParentSublist(goalToMove.id, oldParentId), @@ -298,6 +306,7 @@ export const moveGoalHierarchy = async (goalId: string, newParentGoalId: string) ]); const subscribers = await getParticipantsOfGoals(ancestorGoalIds); + subscribers.forEach(async ({ sub, rootGoalId }) => { await sendUpdatesToSubscriber(sub, rootGoalId, "moved", [ { @@ -312,6 +321,7 @@ export const moveGoalHierarchy = async (goalId: string, newParentGoalId: string) }); const descendants = await getAllDescendants(goalId); + if (descendants.length > 0) { subscribers.forEach(async ({ sub, rootGoalId }) => { await sendUpdatesToSubscriber( diff --git a/src/helpers/GoalProcessor.ts b/src/helpers/GoalProcessor.ts index 04e0b2001..85ef2e26a 100644 --- a/src/helpers/GoalProcessor.ts +++ b/src/helpers/GoalProcessor.ts @@ -114,6 +114,8 @@ export const getTypeAtPriority = (goalChanges: IChangesInGoal) => { typeAtPriority = "restored"; } else if (goalChanges.moved.length > 0) { typeAtPriority = "moved"; + } else if (goalChanges.newGoalMoved.length > 0) { + typeAtPriority = "newGoalMoved"; } return { typeAtPriority }; }; @@ -131,7 +133,7 @@ export const jumpToLowestChanges = async (id: string, relId: string) => { const parentId = "id" in goalAtPriority ? goalAtPriority.id - : typeAtPriority === "subgoals" + : typeAtPriority === "subgoals" || typeAtPriority === "newGoalMoved" ? goalAtPriority.goal.parentGoalId : goalAtPriority.goal.id; @@ -139,7 +141,7 @@ export const jumpToLowestChanges = async (id: string, relId: string) => { const result = { typeAtPriority, parentId, goals: [await getGoal(parentId)] }; return result; } - if (typeAtPriority === "subgoals") { + if (typeAtPriority === "subgoals" || typeAtPriority === "newGoalMoved") { goalChanges[typeAtPriority].forEach(({ intent, goal }) => { if (goal.parentGoalId === parentId) goals.push({ intent, goal }); }); diff --git a/src/helpers/InboxProcessor.ts b/src/helpers/InboxProcessor.ts index 257d7134d..f6dff31fb 100644 --- a/src/helpers/InboxProcessor.ts +++ b/src/helpers/InboxProcessor.ts @@ -58,7 +58,7 @@ export const handleIncomingChanges = async (payload: Payload, relId: string) => console.log("Changes ignored"); return; } - if (payload.changeType === "subgoals") { + if (payload.changeType === "subgoals" || payload.changeType === "newGoalMoved") { const changes = [ ...payload.changes.map((ele: changesInGoal) => ({ ...ele, goal: fixDateVlauesInGoalObject(ele.goal) })), ]; @@ -95,7 +95,7 @@ export const handleIncomingChanges = async (payload: Payload, relId: string) => } } else if (["sharer", "suggestion"].includes(payload.type)) { const { changes, changeType, rootGoalId } = payload; - if (changeType === "subgoals") { + if (changeType === "subgoals" || changeType === "newGoalMoved") { const rootGoal = await getGoal(rootGoalId); if (!rootGoal || !rootGoal.participants.find((p) => p.relId === relId && p.following)) { return; diff --git a/src/helpers/PartnerController.ts b/src/helpers/PartnerController.ts index 29ff5953b..5857d66ef 100644 --- a/src/helpers/PartnerController.ts +++ b/src/helpers/PartnerController.ts @@ -8,7 +8,7 @@ import { createGoalObjectFromTags } from "./GoalProcessor"; const sendUpdate = ( subscribers: IParticipant[], rootGoalId: string, - type: "subgoals" | "modifiedGoals", + type: "subgoals" | "modifiedGoals" | "newGoalMoved", obj: { level: number; goal: GoalItem; diff --git a/src/models/InboxItem.ts b/src/models/InboxItem.ts index 3b1399ad3..523153ad2 100644 --- a/src/models/InboxItem.ts +++ b/src/models/InboxItem.ts @@ -1,6 +1,13 @@ import { GoalItem } from "./GoalItem"; -export type typeOfChange = "subgoals" | "modifiedGoals" | "archived" | "deleted" | "restored" | "moved"; +export type typeOfChange = + | "subgoals" + | "modifiedGoals" + | "archived" + | "deleted" + | "restored" + | "moved" + | "newGoalMoved"; export type typeOfIntent = "suggestion" | "shared"; export type changesInId = { level: number; id: string; intent: typeOfIntent }; @@ -13,7 +20,7 @@ export interface IChangesInGoal { deleted: changesInId[]; restored: changesInId[]; moved: changesInId[]; - // newGoalMoved: changesInGoal[]; + newGoalMoved: changesInGoal[]; } export interface InboxItem { diff --git a/src/utils/defaultGenerators.ts b/src/utils/defaultGenerators.ts index 78bf3d0e6..db7436adb 100644 --- a/src/utils/defaultGenerators.ts +++ b/src/utils/defaultGenerators.ts @@ -30,5 +30,6 @@ export function getDefaultValueOfGoalChanges() { deleted: [] as changesInId[], restored: [] as changesInId[], moved: [] as changesInId[], + newGoalMoved: [] as changesInGoal[], }; } From 5e297d0b1dc01d0e3b07f3cee70977928e82aae9 Mon Sep 17 00:00:00 2001 From: Vinay Badgujar Date: Mon, 4 Nov 2024 16:29:59 +0530 Subject: [PATCH 25/54] fix: particpant not being correctly passed to partner goals while new goal moved to shared goal --- src/api/SharedWMAPI/index.ts | 37 +++++++++++++++++++---- src/helpers/GoalController.ts | 45 ++++++++++++++++++++++++---- src/helpers/InboxProcessor.ts | 13 ++++++-- src/pages/GoalsPage/PartnerGoals.tsx | 1 + 4 files changed, 82 insertions(+), 14 deletions(-) diff --git a/src/api/SharedWMAPI/index.ts b/src/api/SharedWMAPI/index.ts index 82820f957..c1d942b73 100644 --- a/src/api/SharedWMAPI/index.ts +++ b/src/api/SharedWMAPI/index.ts @@ -3,6 +3,7 @@ import { db } from "@models"; import { GoalItem } from "@src/models/GoalItem"; import { createGoalObjectFromTags } from "@src/helpers/GoalProcessor"; import { addGoal } from "../GoalsAPI"; +import { getContactByRelId } from "../ContactsAPI"; export const addSharedWMSublist = async (parentGoalId: string, goalIds: string[]) => { db.transaction("rw", db.sharedWMCollection, async () => { @@ -17,29 +18,53 @@ export const addSharedWMSublist = async (parentGoalId: string, goalIds: string[] }); }; -export const addSharedWMGoal = async (goalDetails: GoalItem) => { +export const addSharedWMGoal = async (goalDetails: GoalItem, relId = "") => { + console.log("[addSharedWMGoal] Input goal details:", goalDetails); + console.log("[addSharedWMGoal] Input relId:", relId); + const { participants } = goalDetails; - const newGoal = createGoalObjectFromTags({ ...goalDetails, typeOfGoal: "shared" }); - if (participants) newGoal.participants = participants; + let updatedParticipants = participants || []; + + if (relId) { + const contact = await getContactByRelId(relId); + if (contact) { + const contactExists = updatedParticipants.some((p) => p.relId === relId); + if (!contactExists) { + updatedParticipants = [...updatedParticipants, { ...contact, following: true, type: "sharer" }]; + } + } + } + + console.log("[addSharedWMGoal] Updated participants:", updatedParticipants); + const newGoal = createGoalObjectFromTags({ + ...goalDetails, + typeOfGoal: "shared", + participants: updatedParticipants, + }); + await db .transaction("rw", db.sharedWMCollection, async () => { await db.sharedWMCollection.add(newGoal); + console.log("[addSharedWMGoal] Goal added to sharedWMCollection"); }) .then(async () => { const { parentGoalId } = newGoal; if (parentGoalId !== "root") { + console.log("[addSharedWMGoal] Adding goal to parent sublist. ParentId:", parentGoalId); await addSharedWMSublist(parentGoalId, [newGoal.id]); } }) .catch((e) => { - console.log(e.stack || e); + console.error("[addSharedWMGoal] Error:", e.stack || e); }); + + console.log("[addSharedWMGoal] Successfully created goal with ID:", newGoal.id); return newGoal.id; }; -export const addGoalsInSharedWM = async (goals: GoalItem[]) => { +export const addGoalsInSharedWM = async (goals: GoalItem[], relId: string) => { goals.forEach((ele) => { - addSharedWMGoal(ele).then((res) => console.log(res, "added")); + addSharedWMGoal(ele, relId).then((res) => console.log(res, "added")); }); }; diff --git a/src/helpers/GoalController.ts b/src/helpers/GoalController.ts index 811dd77b4..7614c0e44 100644 --- a/src/helpers/GoalController.ts +++ b/src/helpers/GoalController.ts @@ -296,15 +296,51 @@ export const moveGoalHierarchy = async (goalId: string, newParentGoalId: string) const ancestorGoalsIdsOfNewParent = await getGoalHistoryToRoot(newParentGoalId); const ancestorGoalIdsOfNewParent = ancestorGoalsIdsOfNewParent.map((ele) => ele.goalID); - await createSharedGoal(goalToMove, newParentGoalId, ancestorGoalIdsOfNewParent); + const ancestorGoals = await Promise.all(ancestorGoalIdsOfNewParent.map((id) => getGoal(id))); + const allParticipants = new Map(); + + [...ancestorGoals, newParentGoal].forEach((goal) => { + if (!goal?.participants) return; + goal.participants.forEach((participant) => { + if (participant.following) { + allParticipants.set(participant.relId, participant); + } + }); + }); + + goalToMove.participants.forEach((participant) => { + if (participant.following) { + allParticipants.set(participant.relId, participant); + } + }); + + const updatedGoal = { + ...goalToMove, + participants: Array.from(allParticipants.values()), + }; + + await createSharedGoal(updatedGoal, newParentGoalId, ancestorGoalIdsOfNewParent); await Promise.all([ - updateGoal(goalToMove.id, { parentGoalId: newParentGoalId }), + updateGoal(goalToMove.id, { + parentGoalId: newParentGoalId, + participants: updatedGoal.participants, + }), removeGoalFromParentSublist(goalToMove.id, oldParentId), addGoalToNewParentSublist(goalToMove.id, newParentGoalId), updateRootGoal(goalToMove.id, newParentGoal?.rootGoalId ?? "root"), ]); + const descendants = await getAllDescendants(goalId); + await Promise.all( + descendants.map((descendant) => + updateGoal(descendant.id, { + participants: updatedGoal.participants, + rootGoalId: newParentGoal?.rootGoalId ?? "root", + }), + ), + ); + const subscribers = await getParticipantsOfGoals(ancestorGoalIds); subscribers.forEach(async ({ sub, rootGoalId }) => { @@ -312,7 +348,7 @@ export const moveGoalHierarchy = async (goalId: string, newParentGoalId: string) { level: ancestorGoalIds.length, goal: { - ...goalToMove, + ...updatedGoal, parentGoalId: newParentGoalId, rootGoalId, }, @@ -320,8 +356,6 @@ export const moveGoalHierarchy = async (goalId: string, newParentGoalId: string) ]); }); - const descendants = await getAllDescendants(goalId); - if (descendants.length > 0) { subscribers.forEach(async ({ sub, rootGoalId }) => { await sendUpdatesToSubscriber( @@ -332,6 +366,7 @@ export const moveGoalHierarchy = async (goalId: string, newParentGoalId: string) level: ancestorGoalIds.length + 1, goal: { ...descendant, + participants: updatedGoal.participants, rootGoalId, }, })), diff --git a/src/helpers/InboxProcessor.ts b/src/helpers/InboxProcessor.ts index f6dff31fb..38b4c5662 100644 --- a/src/helpers/InboxProcessor.ts +++ b/src/helpers/InboxProcessor.ts @@ -58,18 +58,25 @@ export const handleIncomingChanges = async (payload: Payload, relId: string) => console.log("Changes ignored"); return; } - if (payload.changeType === "subgoals" || payload.changeType === "newGoalMoved") { + if (payload.changeType === "subgoals") { const changes = [ ...payload.changes.map((ele: changesInGoal) => ({ ...ele, goal: fixDateVlauesInGoalObject(ele.goal) })), ]; - await addGoalsInSharedWM([changes[0].goal]); + await addGoalsInSharedWM([changes[0].goal], relId); + } else if (payload.changeType === "newGoalMoved") { + const changes = [ + ...payload.changes.map((ele: changesInGoal) => ({ ...ele, goal: fixDateVlauesInGoalObject(ele.goal) })), + ]; + changes.map(async (ele) => { + await addGoalsInSharedWM([ele.goal], relId); + }); } else if (payload.changeType === "modifiedGoals") { const changes = [ ...payload.changes.map((ele: changesInGoal) => ({ ...ele, goal: fixDateVlauesInGoalObject(ele.goal) })), ]; await updateSharedWMGoal(changes[0].goal.id, changes[0].goal); } else if (payload.changeType === "deleted") { - const goalToBeDeleted = await getSharedWMGoal(payload.changes[0].goal.id); + const goalToBeDeleted = await getSharedWMGoal(payload.changes[0].id); console.log("Deleting goal", goalToBeDeleted); await removeSharedWMChildrenGoals(goalToBeDeleted.id); await removeSharedWMGoal(goalToBeDeleted); diff --git a/src/pages/GoalsPage/PartnerGoals.tsx b/src/pages/GoalsPage/PartnerGoals.tsx index 80f467f56..e62a9ab5b 100644 --- a/src/pages/GoalsPage/PartnerGoals.tsx +++ b/src/pages/GoalsPage/PartnerGoals.tsx @@ -56,6 +56,7 @@ const PartnerGoals = () => { const refreshActiveGoals = async () => { const rootGoals = await getRootGoalsOfPartner(relId); + console.log("rootGoals", rootGoals); handleUserGoals(rootGoals); }; From 406a18315b399cd5d190bb01328e747991efd7eb Mon Sep 17 00:00:00 2001 From: Vinay Badgujar Date: Tue, 5 Nov 2024 06:59:48 +0530 Subject: [PATCH 26/54] remove: dead code and ts-ignore comment --- src/hooks/useApp.tsx | 15 --------------- src/hooks/useGoalActions.tsx | 2 +- 2 files changed, 1 insertion(+), 16 deletions(-) diff --git a/src/hooks/useApp.tsx b/src/hooks/useApp.tsx index 34f46523e..a5cd8a775 100644 --- a/src/hooks/useApp.tsx +++ b/src/hooks/useApp.tsx @@ -92,7 +92,6 @@ function useApp() { ); }); const res = await getContactSharedGoals(); - // @ts-ignore const resObject = res.response.reduce( (acc: { [key: string]: SharedGoalMessage[] }, curr) => ({ ...acc, @@ -104,7 +103,6 @@ function useApp() { Object.keys(resObject).forEach(async (relId: string) => { const contactItem = await getContactByRelId(relId); if (contactItem) { - // @ts-ignore resObject[relId].forEach(async (ele) => { console.log("🚀 ~ file: useApp.tsx:45 ~ resObject[relId].forEach ~ ele:", ele); if (ele.type === "shareMessage") { @@ -112,19 +110,6 @@ function useApp() { } else if (["sharer", "suggestion"].includes(ele.type)) { handleIncomingChanges(ele as unknown as Payload, relId).then(() => setLastAction("goalNewUpdates")); } - // else if (["suggestion", "shared", "collaboration", "collaborationInvite"].includes(ele.type)) { - // let typeOfSub = ele.rootGoalId ? await findTypeOfSub(ele.rootGoalId) : "none"; - // if (ele.type === "collaborationInvite") { - // typeOfSub = "collaborationInvite"; - // } else if (["collaboration", "suggestion"].includes(ele.type)) { - // typeOfSub = ele.type; - // } else if (ele.type === "shared") { - // typeOfSub = typeOfSub === "collaboration" ? "collaboration" : "shared"; - // } - // if (typeOfSub !== "none") { - // handleIncomingChanges({ ...ele, type: typeOfSub }).then(() => setLastAction("goalNewUpdates")); - // } - // } }); } }); diff --git a/src/hooks/useGoalActions.tsx b/src/hooks/useGoalActions.tsx index a30ec6970..af1c0bd65 100644 --- a/src/hooks/useGoalActions.tsx +++ b/src/hooks/useGoalActions.tsx @@ -130,7 +130,7 @@ const useGoalActions = () => { ...goalWithChildrens.map((ele) => ({ ...ele, participants: [], - parentGoalId: ele.parentGoalId, + parentGoalId: ele.id === goal.id ? "root" : ele.parentGoalId, rootGoalId: goal.id, })), ]); From c419332d78dd30e5413f80029a27be98ed275b19 Mon Sep 17 00:00:00 2001 From: Vinay Badgujar Date: Tue, 5 Nov 2024 13:08:22 +0530 Subject: [PATCH 27/54] fix: tslint issues --- src/helpers/InboxProcessor.ts | 93 +++++++++++++++++++--------------- src/models/InboxItem.ts | 28 ++++------ src/utils/defaultGenerators.ts | 18 +++---- 3 files changed, 72 insertions(+), 67 deletions(-) diff --git a/src/helpers/InboxProcessor.ts b/src/helpers/InboxProcessor.ts index 38b4c5662..68e313b15 100644 --- a/src/helpers/InboxProcessor.ts +++ b/src/helpers/InboxProcessor.ts @@ -1,5 +1,5 @@ import { GoalItem, typeOfSub } from "@src/models/GoalItem"; -import { changesInGoal, changesInId, typeOfChange, typeOfIntent } from "@src/models/InboxItem"; +import { changesInGoal, changesInId, IdChangeTypes, typeOfChange, typeOfIntent } from "@src/models/InboxItem"; import { getDefaultValueOfGoalChanges } from "@src/utils/defaultGenerators"; import { @@ -22,7 +22,6 @@ import { ITagChangesSchemaVersion, ITagsChanges } from "@src/Interfaces/IDisplay import { fixDateVlauesInGoalObject } from "@src/utils"; import { getDeletedGoal, restoreUserGoal } from "@src/api/TrashAPI"; import { getContactByRelId } from "@src/api/ContactsAPI"; -import { isIncomingGoalLatest } from "./mergeSharedGoalItems"; import { getRootGoalId, updateRootGoalNotification } from "./GoalController"; export interface Payload { @@ -36,6 +35,10 @@ export interface Payload { TTL: number; } +const isIdChangeType = (type: typeOfChange): type is IdChangeTypes => { + return ["deleted", "moved", "restored", "archived"].includes(type); +}; + const addChangesToRootGoal = async (goalId: string, relId: string, changes: any) => { const rootGoalId = await getRootGoalId(goalId); if (rootGoalId === "root") return; @@ -48,6 +51,42 @@ const addChangesToRootGoal = async (goalId: string, relId: string, changes: any) await Promise.all([updateRootGoalNotification(rootGoalId), addGoalChangesInID(rootGoalId, relId, changes)]); }; +const handleSubgoalsChanges = async ( + changes: changesInGoal[], + changeType: typeOfChange, + rootGoalId: string, + relId: string, + type: string, +) => { + const rootGoal = await getGoal(rootGoalId); + if (!rootGoal || !rootGoal.participants.find((p) => p.relId === relId && p.following)) { + return; + } + + const contact = await getContactByRelId(relId); + + const goalsWithParticipants = changes.map((ele: changesInGoal) => ({ + ...ele, + goal: { + ...fixDateVlauesInGoalObject(ele.goal), + participants: [{ relId, following: true, type: "sharer" as typeOfSub, name: contact?.name || "" }], + }, + })); + + const inbox = await getInboxItem(rootGoalId); + const defaultChanges = getDefaultValueOfGoalChanges(); + (defaultChanges[changeType] as changesInGoal[]) = goalsWithParticipants.map((ele) => ({ + ...ele, + intent: type as typeOfIntent, + })); + + if (!inbox) { + await createEmptyInboxItem(rootGoalId); + } + + await addChangesToRootGoal(rootGoalId, relId, defaultChanges); +}; + export const handleIncomingChanges = async (payload: Payload, relId: string) => { console.log("Incoming change", payload); @@ -76,7 +115,8 @@ export const handleIncomingChanges = async (payload: Payload, relId: string) => ]; await updateSharedWMGoal(changes[0].goal.id, changes[0].goal); } else if (payload.changeType === "deleted") { - const goalToBeDeleted = await getSharedWMGoal(payload.changes[0].id); + const change = payload.changes[0] as changesInId; + const goalToBeDeleted = await getSharedWMGoal(change.id); console.log("Deleting goal", goalToBeDeleted); await removeSharedWMChildrenGoals(goalToBeDeleted.id); await removeSharedWMGoal(goalToBeDeleted); @@ -91,11 +131,13 @@ export const handleIncomingChanges = async (payload: Payload, relId: string) => }); } } else if (payload.changeType === "archived") { - getSharedWMGoal(payload.changes[0].id).then(async (goal: GoalItem) => + const change = payload.changes[0] as changesInId; + getSharedWMGoal(change.id).then(async (goal: GoalItem) => archiveSharedWMGoal(goal).catch((err) => console.log(err, "failed to archive")), ); } else if (payload.changeType === "restored") { - const goalToBeRestored = await getDeletedGoal(payload.changes[0].id); + const change = payload.changes[0] as changesInId; + const goalToBeRestored = await getDeletedGoal(change.id); if (goalToBeRestored) { await restoreUserGoal(goalToBeRestored, true); } @@ -103,32 +145,7 @@ export const handleIncomingChanges = async (payload: Payload, relId: string) => } else if (["sharer", "suggestion"].includes(payload.type)) { const { changes, changeType, rootGoalId } = payload; if (changeType === "subgoals" || changeType === "newGoalMoved") { - const rootGoal = await getGoal(rootGoalId); - if (!rootGoal || !rootGoal.participants.find((p) => p.relId === relId && p.following)) { - return; - } - - const contact = await getContactByRelId(relId); - - const goalsWithParticipants = changes.map((ele: changesInGoal) => ({ - ...ele, - goal: { - ...fixDateVlauesInGoalObject(ele.goal), - participants: [{ relId, following: true, type: "sharer" as typeOfSub, name: contact?.name || "" }], - }, - })); - - const inbox = await getInboxItem(rootGoalId); - const defaultChanges = getDefaultValueOfGoalChanges(); - defaultChanges[changeType] = [ - ...goalsWithParticipants.map((ele) => ({ ...ele, intent: payload.type as typeOfIntent })), - ]; - - if (!inbox) { - await createEmptyInboxItem(rootGoalId); - } - - await addChangesToRootGoal(rootGoalId, relId, defaultChanges); + await handleSubgoalsChanges(changes as changesInGoal[], changeType, rootGoalId, relId, payload.type); return; } @@ -142,13 +159,9 @@ export const handleIncomingChanges = async (payload: Payload, relId: string) => let filteredChanges = changes; if (changeType !== "deleted" && changeType !== "moved" && changeType !== "restored" && changeType !== "archived") { - const latestChanges = await Promise.all( - changes.map(async (ele) => { - const isLatest = await isIncomingGoalLatest(ele.goal.id, ele.goal); - return isLatest ? ele : null; - }), - ); - filteredChanges = latestChanges.filter((ele): ele is changesInGoal => ele !== null); + // TODO: Handle version conflict resolution later + // For now, accepting all incoming changes without checking if they are the latest + filteredChanges = changes as changesInGoal[]; } if (filteredChanges.length === 0) { @@ -158,8 +171,8 @@ export const handleIncomingChanges = async (payload: Payload, relId: string) => const inbox = await getInboxItem(goal.id); const defaultChanges = getDefaultValueOfGoalChanges(); defaultChanges[changeType] = filteredChanges.map((ele) => ({ - ...ele, - intent: payload.type, + ...(isIdChangeType(changeType) ? (ele as changesInId) : (ele as changesInGoal)), + intent: payload.type as typeOfIntent, })); if (!inbox) { diff --git a/src/models/InboxItem.ts b/src/models/InboxItem.ts index 523153ad2..0f6137101 100644 --- a/src/models/InboxItem.ts +++ b/src/models/InboxItem.ts @@ -1,31 +1,23 @@ import { GoalItem } from "./GoalItem"; -export type typeOfChange = - | "subgoals" - | "modifiedGoals" - | "archived" - | "deleted" - | "restored" - | "moved" - | "newGoalMoved"; +export type IdChangeTypes = "deleted" | "moved" | "restored" | "archived"; +type GoalChangeTypes = "subgoals" | "modifiedGoals" | "newGoalMoved"; + +export type typeOfChange = IdChangeTypes | GoalChangeTypes; export type typeOfIntent = "suggestion" | "shared"; export type changesInId = { level: number; id: string; intent: typeOfIntent }; export type changesInGoal = { level: number; goal: GoalItem; intent: typeOfIntent }; -export interface IChangesInGoal { - subgoals: changesInGoal[]; - modifiedGoals: changesInGoal[]; - archived: changesInId[]; - deleted: changesInId[]; - restored: changesInId[]; - moved: changesInId[]; - newGoalMoved: changesInGoal[]; -} +export type ChangesByType = { + [K in IdChangeTypes]: changesInId[]; +} & { + [K in GoalChangeTypes]: changesInGoal[]; +}; export interface InboxItem { id: string; changes: { - [relId: string]: IChangesInGoal; + [relId: string]: ChangesByType; }; } diff --git a/src/utils/defaultGenerators.ts b/src/utils/defaultGenerators.ts index db7436adb..7377bd40b 100644 --- a/src/utils/defaultGenerators.ts +++ b/src/utils/defaultGenerators.ts @@ -1,7 +1,7 @@ import { v5 as uuidv5 } from "uuid"; import { GoalItem } from "@src/models/GoalItem"; -import { changesInGoal, changesInId } from "@src/models/InboxItem"; +import { ChangesByType } from "@src/models/InboxItem"; import { myNameSpace } from "."; export const createPollObject = (goal: GoalItem, params: object = {}) => ({ @@ -22,14 +22,14 @@ export const createPollObject = (goal: GoalItem, params: object = {}) => ({ ...params, }); -export function getDefaultValueOfGoalChanges() { +export function getDefaultValueOfGoalChanges(): ChangesByType { return { - subgoals: [] as changesInGoal[], - modifiedGoals: [] as changesInGoal[], - archived: [] as changesInId[], - deleted: [] as changesInId[], - restored: [] as changesInId[], - moved: [] as changesInId[], - newGoalMoved: [] as changesInGoal[], + subgoals: [], + modifiedGoals: [], + archived: [], + deleted: [], + restored: [], + moved: [], + newGoalMoved: [], }; } From 156ec4d981738f2d808a418ff0d60e3bb4f3bdb5 Mon Sep 17 00:00:00 2001 From: Vinay Badgujar Date: Tue, 5 Nov 2024 13:09:53 +0530 Subject: [PATCH 28/54] remove: dead code --- src/utils/defaultGenerators.ts | 22 ---------------------- 1 file changed, 22 deletions(-) diff --git a/src/utils/defaultGenerators.ts b/src/utils/defaultGenerators.ts index 7377bd40b..4b0737811 100644 --- a/src/utils/defaultGenerators.ts +++ b/src/utils/defaultGenerators.ts @@ -1,26 +1,4 @@ -import { v5 as uuidv5 } from "uuid"; - -import { GoalItem } from "@src/models/GoalItem"; import { ChangesByType } from "@src/models/InboxItem"; -import { myNameSpace } from "."; - -export const createPollObject = (goal: GoalItem, params: object = {}) => ({ - id: uuidv5(goal.title, myNameSpace), - goal, - metrics: { - upVotes: 0, - downVotes: 0, - inMyGoals: 0, - completed: 0, - }, - myMetrics: { - voteScore: 0, - inMyGoals: false, - completed: false, - }, - createdAt: new Date().toISOString(), - ...params, -}); export function getDefaultValueOfGoalChanges(): ChangesByType { return { From 4400271fa83c186452f46b99912c2ad078067a5e Mon Sep 17 00:00:00 2001 From: Vinay Badgujar Date: Tue, 5 Nov 2024 13:10:17 +0530 Subject: [PATCH 29/54] remove: dead code --- src/utils/index.ts | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/utils/index.ts b/src/utils/index.ts index fe4c547e7..14769e8ea 100644 --- a/src/utils/index.ts +++ b/src/utils/index.ts @@ -43,8 +43,6 @@ export async function createGroupRequest(url: string, body: object | null = null } } -export const myNameSpace = "c95256dc-aa03-11ed-afa1-0242ac120002"; - export const getJustDate = (fullDate: Date) => new Date(fullDate.toDateString()); export const truncateContent = (content: string, maxLength = 20) => { From 280269ae342e3de5b6df6e8b0369aa6f3e893197 Mon Sep 17 00:00:00 2001 From: Vinay Badgujar Date: Tue, 5 Nov 2024 13:32:15 +0530 Subject: [PATCH 30/54] handle move in partner goals --- src/api/SharedWMAPI/index.ts | 16 +++++++++++ src/helpers/InboxProcessor.ts | 50 ++++++++++++++++++++++++++++++++--- 2 files changed, 63 insertions(+), 3 deletions(-) diff --git a/src/api/SharedWMAPI/index.ts b/src/api/SharedWMAPI/index.ts index c1d942b73..8dc3c6ac8 100644 --- a/src/api/SharedWMAPI/index.ts +++ b/src/api/SharedWMAPI/index.ts @@ -182,3 +182,19 @@ export const convertSharedWMGoalToColab = async (goal: GoalItem) => { }) .catch((err) => console.log(err)); }; + +export const updateSharedWMParentSublist = async (oldParentId: string, newParentId: string, goalId: string) => { + // Remove from old parent + const oldParentGoal = await getSharedWMGoal(oldParentId); + if (oldParentGoal?.sublist) { + const updatedOldSublist = oldParentGoal.sublist.filter((id) => id !== goalId); + await updateSharedWMGoal(oldParentId, { sublist: updatedOldSublist }); + } + + // Add to new parent + const newParentGoal = await getSharedWMGoal(newParentId); + if (newParentGoal) { + const updatedNewSublist = [...(newParentGoal.sublist || []), goalId]; + await updateSharedWMGoal(newParentId, { sublist: updatedNewSublist }); + } +}; diff --git a/src/helpers/InboxProcessor.ts b/src/helpers/InboxProcessor.ts index 68e313b15..d87b0fd73 100644 --- a/src/helpers/InboxProcessor.ts +++ b/src/helpers/InboxProcessor.ts @@ -1,5 +1,12 @@ import { GoalItem, typeOfSub } from "@src/models/GoalItem"; -import { changesInGoal, changesInId, IdChangeTypes, typeOfChange, typeOfIntent } from "@src/models/InboxItem"; +import { + ChangesByType, + changesInGoal, + changesInId, + IdChangeTypes, + typeOfChange, + typeOfIntent, +} from "@src/models/InboxItem"; import { getDefaultValueOfGoalChanges } from "@src/utils/defaultGenerators"; import { @@ -17,12 +24,13 @@ import { removeSharedWMChildrenGoals, removeSharedWMGoal, updateSharedWMGoal, + updateSharedWMParentSublist, } from "@src/api/SharedWMAPI"; import { ITagChangesSchemaVersion, ITagsChanges } from "@src/Interfaces/IDisplayChangesModal"; import { fixDateVlauesInGoalObject } from "@src/utils"; import { getDeletedGoal, restoreUserGoal } from "@src/api/TrashAPI"; import { getContactByRelId } from "@src/api/ContactsAPI"; -import { getRootGoalId, updateRootGoalNotification } from "./GoalController"; +import { getAllDescendants, getRootGoalId, updateRootGoalNotification } from "./GoalController"; export interface Payload { relId: string; @@ -39,7 +47,30 @@ const isIdChangeType = (type: typeOfChange): type is IdChangeTypes => { return ["deleted", "moved", "restored", "archived"].includes(type); }; -const addChangesToRootGoal = async (goalId: string, relId: string, changes: any) => { +const updateSharedWMGoalAndDescendants = async (movedGoal: GoalItem) => { + await updateSharedWMGoal(movedGoal.id, { + parentGoalId: movedGoal.parentGoalId, + rootGoalId: movedGoal.rootGoalId, + }); + + const descendants = await getAllDescendants(movedGoal.id); + if (descendants.length > 0) { + await Promise.all( + descendants.map((descendant) => + updateSharedWMGoal(descendant.id, { + rootGoalId: movedGoal.rootGoalId, + }), + ), + ); + } +}; + +const handleMoveOperation = async (movedGoal: GoalItem, goalToMove: GoalItem) => { + await updateSharedWMParentSublist(goalToMove.parentGoalId, movedGoal.parentGoalId, goalToMove.id); + await updateSharedWMGoalAndDescendants(movedGoal); +}; + +const addChangesToRootGoal = async (goalId: string, relId: string, changes: ChangesByType) => { const rootGoalId = await getRootGoalId(goalId); if (rootGoalId === "root") return; @@ -141,6 +172,19 @@ export const handleIncomingChanges = async (payload: Payload, relId: string) => if (goalToBeRestored) { await restoreUserGoal(goalToBeRestored, true); } + } else if (payload.changeType === "moved") { + const changes = [ + ...payload.changes.map((ele: changesInGoal) => ({ ...ele, goal: fixDateVlauesInGoalObject(ele.goal) })), + ]; + const movedGoal = changes[0].goal; + const goalToMove = await getSharedWMGoal(movedGoal.id); + + if (!goalToMove) { + console.log("Goal to move not found"); + return; + } + + await handleMoveOperation(movedGoal, goalToMove); } } else if (["sharer", "suggestion"].includes(payload.type)) { const { changes, changeType, rootGoalId } = payload; From d45934fcdfdc9ba361f93593560b234af95ec5bf Mon Sep 17 00:00:00 2001 From: Vinay Badgujar Date: Wed, 6 Nov 2024 14:54:20 +0530 Subject: [PATCH 31/54] move the move hear button in global add button --- src/components/GlobalAddBtn.tsx | 23 ++++++++++++++++++- .../GoalSublist/GoalSublist.tsx | 4 +++- .../GoalsComponents/MyGoal/MyGoal.tsx | 6 +---- src/pages/GoalsPage/MyGoals.tsx | 12 ++++++---- 4 files changed, 33 insertions(+), 12 deletions(-) diff --git a/src/components/GlobalAddBtn.tsx b/src/components/GlobalAddBtn.tsx index b6761f0d0..421c3924c 100644 --- a/src/components/GlobalAddBtn.tsx +++ b/src/components/GlobalAddBtn.tsx @@ -1,5 +1,5 @@ import React, { ReactNode, useEffect } from "react"; -import { useRecoilValue } from "recoil"; +import { useRecoilValue, useSetRecoilState } from "recoil"; import { useTranslation } from "react-i18next"; import { useLocation, useNavigate, useParams, useSearchParams } from "react-router-dom"; @@ -17,6 +17,9 @@ import { TGoalCategory } from "@src/models/GoalItem"; import { allowAddingBudgetGoal } from "@src/store/GoalsState"; import useLongPress from "@src/hooks/useLongPress"; import { useKeyPress } from "@src/hooks/useKeyPress"; +import { moveGoalState } from "@src/store/moveGoalState"; +import { moveGoalHierarchy } from "@src/helpers/GoalController"; +import { lastAction } from "@src/store"; interface AddGoalOptionProps { children: ReactNode; @@ -52,10 +55,14 @@ const GlobalAddBtn = ({ add }: { add: string }) => { const { handleAddFeeling } = useFeelingStore(); const isPartnerModeActive = !!partnerId; + const setLastAction = useSetRecoilState(lastAction); + const navigate = useNavigate(); const [searchParams] = useSearchParams(); const themeSelection = useRecoilValue(themeSelectionMode); const isAddingBudgetGoalAllowed = useRecoilValue(allowAddingBudgetGoal); + const goalToMove = useRecoilValue(moveGoalState); + const setGoalToMove = useSetRecoilState(moveGoalState); const enterPressed = useKeyPress("Enter"); const plusPressed = useKeyPress("+"); @@ -102,6 +109,15 @@ const GlobalAddBtn = ({ add }: { add: string }) => { const { onClick, onMouseDown, onMouseUp, onTouchStart, onTouchEnd } = handlers; + const handleMoveGoalHere = async () => { + if (!goalToMove) return; + + await moveGoalHierarchy(goalToMove.id, parentId); + setLastAction("goalMoved"); + setGoalToMove(null); + window.history.back(); + }; + useEffect(() => { if ((plusPressed || enterPressed) && !state.goalType) { // @ts-ignore @@ -119,6 +135,11 @@ const GlobalAddBtn = ({ add }: { add: string }) => { window.history.back(); }} /> + {goalToMove && ( + + {t("Move Here")} + + )} { handleAddGoal("Budget"); diff --git a/src/components/GoalsComponents/GoalSublist/GoalSublist.tsx b/src/components/GoalsComponents/GoalSublist/GoalSublist.tsx index 2d53a725b..7ff599d64 100644 --- a/src/components/GoalsComponents/GoalSublist/GoalSublist.tsx +++ b/src/components/GoalsComponents/GoalSublist/GoalSublist.tsx @@ -19,6 +19,7 @@ import AvailableGoalHints from "@pages/GoalsPage/components/AvailableGoalHints"; import GoalsList from "../GoalsList"; import GoalHistory from "./components/GoalHistory"; import "./GoalSublist.scss"; +import { moveGoalState } from "@src/store/moveGoalState"; export const GoalSublist = () => { const { @@ -34,6 +35,7 @@ export const GoalSublist = () => { const goalID = useRecoilValue(displayGoalId); const showChangesModal = useRecoilValue(displayChangesModal); const showSuggestionModal = useRecoilValue(displaySuggestionsModal); + const goalToMove = useRecoilValue(moveGoalState); useEffect(() => { if (parentGoal === undefined) return; @@ -59,7 +61,7 @@ export const GoalSublist = () => { setActiveGoals([...sortedGoals.filter((goal) => goal.archived === "false")]); } init(); - }, [action, parentGoal, showSuggestionModal, showChangesModal, subgoals, goalID]); + }, [action, parentGoal, showSuggestionModal, showChangesModal, subgoals, goalID, goalToMove]); const setGoals = useCallback( (setGoalsStateUpdateWrapper: (prevGoals: GoalItem[]) => GoalItem[]) => { diff --git a/src/components/GoalsComponents/MyGoal/MyGoal.tsx b/src/components/GoalsComponents/MyGoal/MyGoal.tsx index 183e47c41..a6b120bd8 100644 --- a/src/components/GoalsComponents/MyGoal/MyGoal.tsx +++ b/src/components/GoalsComponents/MyGoal/MyGoal.tsx @@ -12,7 +12,6 @@ import { moveGoalState } from "@src/store/moveGoalState"; import GoalAvatar from "../GoalAvatar"; import GoalTitle from "./components/GoalTitle"; import GoalDropdown from "./components/GoalDropdown"; -import GoalMoveButton from "../MoveGoalButton"; interface MyGoalProps { goal: ImpossibleGoal; @@ -26,8 +25,6 @@ const MyGoal: React.FC = ({ goal, dragAttributes, dragListeners }) const goalToMove = useRecoilValue(moveGoalState); - const shouldRenderMoveButton = goalToMove && goal.id !== goalToMove.id && goal.id !== goalToMove.parentGoalId; - const [expandGoalId, setExpandGoalId] = useState("root"); const [isAnimating, setIsAnimating] = useState(true); const { copyCode } = useGoalActions(); @@ -119,9 +116,8 @@ const MyGoal: React.FC = ({ goal, dragAttributes, dragListeners })
handleGoalClick(e)}>
- {shouldRenderMoveButton && }
- {!shouldRenderMoveButton && !isPartnerModeActive && goal.participants?.length > 0 && } + {!isPartnerModeActive && goal.participants?.length > 0 && }
); }; diff --git a/src/pages/GoalsPage/MyGoals.tsx b/src/pages/GoalsPage/MyGoals.tsx index d0e9c8504..35a4d807a 100644 --- a/src/pages/GoalsPage/MyGoals.tsx +++ b/src/pages/GoalsPage/MyGoals.tsx @@ -30,11 +30,12 @@ import { TGoalConfigMode } from "@src/types"; import { DeletedGoalProvider } from "@src/contexts/deletedGoal-context"; import { goalCategories } from "@src/constants/goals"; import { suggestedGoalState } from "@src/store/SuggestedGoalState"; +import { moveGoalState } from "@src/store/moveGoalState"; +import MoveGoalGuide from "@components/GoalsComponents/MyGoal/MoveGoalGuide"; import DeletedGoals from "./components/DeletedGoals"; import ArchivedGoals from "./components/ArchivedGoals"; import "./GoalsPage.scss"; -import MoveGoalGuide from "@components/GoalsComponents/MyGoal/MoveGoalGuide"; export const MyGoals = () => { let debounceTimeout: ReturnType; @@ -59,6 +60,7 @@ export const MyGoals = () => { const suggestedGoal = useRecoilValue(suggestedGoalState); const displaySearch = useRecoilValue(searchActive); const darkModeStatus = useRecoilValue(darkModeState); + const goalToMove = useRecoilValue(moveGoalState); const showChangesModal = useRecoilValue(displayChangesModal); @@ -102,17 +104,17 @@ export const MyGoals = () => { useEffect(() => { if (action === "goalArchived") return; - if (action !== "none") { + if (action !== "none" || goalToMove === null) { setLastAction("none"); refreshActiveGoals(); } - }, [action]); + }, [action, goalToMove]); useEffect(() => { if (parentId === "root") { refreshActiveGoals(); } - }, [parentId, displaySearch, suggestedGoal]); + }, [parentId, displaySearch, suggestedGoal, goalToMove]); return ( @@ -131,7 +133,7 @@ export const MyGoals = () => { )}
- + {/* */} {parentId === "root" ? (
From 3425d1324cda015b1043fba34b7ed1206715394c Mon Sep 17 00:00:00 2001 From: Vinay Badgujar Date: Wed, 6 Nov 2024 15:26:23 +0530 Subject: [PATCH 32/54] uncomment code --- .../DisplayChangesModal/DisplayChangesModal.tsx | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/src/components/GoalsComponents/DisplayChangesModal/DisplayChangesModal.tsx b/src/components/GoalsComponents/DisplayChangesModal/DisplayChangesModal.tsx index c214fdf7c..1d75f32b3 100644 --- a/src/components/GoalsComponents/DisplayChangesModal/DisplayChangesModal.tsx +++ b/src/components/GoalsComponents/DisplayChangesModal/DisplayChangesModal.tsx @@ -180,13 +180,12 @@ const DisplayChangesModal = ({ currentMainGoal }: { currentMainGoal: GoalItem }) addGoalToNewParentSublist(goalUnderReview.id, parentGoal?.id ?? "root"), ]); - // TODO: handle this later - // await sendUpdatedGoal( - // goalUnderReview.id, - // [], - // true, - // updatesIntent === "suggestion" ? [] : [participants[activePPT].relId], - // ); + await sendUpdatedGoal( + goalUnderReview.id, + [], + true, + updatesIntent === "suggestion" ? [] : [participants[activePPT].relId], + ); }; const acceptChanges = async () => { @@ -265,7 +264,6 @@ const DisplayChangesModal = ({ currentMainGoal }: { currentMainGoal: GoalItem }) console.log("🚀 ~ getChanges ~ changedGoal:", changedGoal); if (changedGoal) { setGoalUnderReview({ ...changedGoal }); - // TODO: remove the newGoalsMoved and try handle in subgoal only if (typeAtPriority === "subgoals" || typeAtPriority === "newGoalMoved") { setNewGoals(goals || []); } else if (typeAtPriority === "modifiedGoals") { From b2cbdc8c4cd7482cda48e99dfd04f3e6e2a3f724 Mon Sep 17 00:00:00 2001 From: Vinay Badgujar Date: Wed, 6 Nov 2024 15:59:23 +0530 Subject: [PATCH 33/54] edit: bottom navbar to support move goal mode --- src/components/BottomNavbar/BottomNavbar.tsx | 64 ++++++++++++++------ 1 file changed, 45 insertions(+), 19 deletions(-) diff --git a/src/components/BottomNavbar/BottomNavbar.tsx b/src/components/BottomNavbar/BottomNavbar.tsx index ba408ba34..09e1933cf 100644 --- a/src/components/BottomNavbar/BottomNavbar.tsx +++ b/src/components/BottomNavbar/BottomNavbar.tsx @@ -1,9 +1,9 @@ import React from "react"; import { useTranslation } from "react-i18next"; -import { useRecoilState, useRecoilValue } from "recoil"; +import { useRecoilState, useRecoilValue, useSetRecoilState } from "recoil"; import { useLocation, useNavigate, useParams } from "react-router-dom"; -import { darkModeState } from "@src/store"; +import { darkModeState, lastAction } from "@src/store"; import { themeSelectionMode, themeState } from "@src/store/ThemeState"; import BottomNavLayout from "@src/layouts/BottomNavLayout"; @@ -13,12 +13,17 @@ import GlobalAddBtn from "@components/GlobalAddBtn"; import "./BottomNavbar.scss"; import Icon from "@src/common/Icon"; import { LocalStorageKeys } from "@src/constants/localStorageKeys"; +import { moveGoalState } from "@src/store/moveGoalState"; +import { moveGoalHierarchy } from "@src/helpers/GoalController"; const BottomNavbar = ({ title }: { title: string }) => { const { t } = useTranslation(); const navigate = useNavigate(); const location = useLocation(); + const [goalToMove, setGoalToMove] = useRecoilState(moveGoalState); + const setLastAction = useSetRecoilState(lastAction); + const { partnerId } = useParams(); const isPartnerModeActive = !!partnerId; @@ -66,6 +71,20 @@ const BottomNavbar = ({ title }: { title: string }) => { }; const { activeGoalId } = location.state || {}; const isAddBtnVisible = title !== "Focus" && (isPartnerModeActive ? !!activeGoalId : true); + + const handleMoveHere = () => { + if (goalToMove) { + moveGoalHierarchy(goalToMove.id, activeGoalId ?? "root") + .then(() => { + setGoalToMove(null); + setLastAction("goalMoved"); + }) + .catch((error) => { + console.error("Error moving goal:", error); + }); + } + }; + return ( <> {themeSelection && ( @@ -80,23 +99,21 @@ const BottomNavbar = ({ title }: { title: string }) => { + + From 30ba34ceb01117950a6c083f3a06ea8dfc31c364 Mon Sep 17 00:00:00 2001 From: Vinay Badgujar Date: Thu, 7 Nov 2024 15:26:47 +0530 Subject: [PATCH 34/54] add participants to child when goal shared to new contacts when sharing req accepted --- src/hooks/useApp.tsx | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/src/hooks/useApp.tsx b/src/hooks/useApp.tsx index a5cd8a775..ea927eaba 100644 --- a/src/hooks/useApp.tsx +++ b/src/hooks/useApp.tsx @@ -83,7 +83,14 @@ function useApp() { rootGoalId: goalId, })), ]).then(async () => { - updateSharedStatusOfGoal(goalId, relId, name).then(() => console.log("status updated")); + await Promise.all( + goalWithChildrens.map(async (goalItem) => { + console.log(goalItem.id, relId, name); + await updateSharedStatusOfGoal(goalItem.id, relId, name); + }), + ).catch((error) => { + console.error("[shareGoalWithRelId] Error updating shared status:", error); + }); }); }), clearTheQueue(relId), From 6baa127ad135cce0e23db7f2452c26bb9407b6e3 Mon Sep 17 00:00:00 2001 From: Vinay Badgujar Date: Sat, 9 Nov 2024 12:38:07 +0530 Subject: [PATCH 35/54] show the move guide and move the move here button in global add button onlu --- src/components/BottomNavbar/BottomNavbar.tsx | 65 ++++++------------- .../GoalsComponents/MyGoal/MoveGoalAlert.tsx | 26 ++++++++ .../GoalsComponents/MyGoal/MoveGoalGuide.tsx | 33 ---------- .../GoalsComponents/MyGoal/index.scss | 25 ++++++- src/pages/GoalsPage/MyGoals.tsx | 7 +- 5 files changed, 72 insertions(+), 84 deletions(-) create mode 100644 src/components/GoalsComponents/MyGoal/MoveGoalAlert.tsx delete mode 100644 src/components/GoalsComponents/MyGoal/MoveGoalGuide.tsx diff --git a/src/components/BottomNavbar/BottomNavbar.tsx b/src/components/BottomNavbar/BottomNavbar.tsx index 09e1933cf..12eb605a8 100644 --- a/src/components/BottomNavbar/BottomNavbar.tsx +++ b/src/components/BottomNavbar/BottomNavbar.tsx @@ -1,9 +1,9 @@ import React from "react"; import { useTranslation } from "react-i18next"; -import { useRecoilState, useRecoilValue, useSetRecoilState } from "recoil"; +import { useRecoilState, useRecoilValue } from "recoil"; import { useLocation, useNavigate, useParams } from "react-router-dom"; -import { darkModeState, lastAction } from "@src/store"; +import { darkModeState } from "@src/store"; import { themeSelectionMode, themeState } from "@src/store/ThemeState"; import BottomNavLayout from "@src/layouts/BottomNavLayout"; @@ -13,17 +13,12 @@ import GlobalAddBtn from "@components/GlobalAddBtn"; import "./BottomNavbar.scss"; import Icon from "@src/common/Icon"; import { LocalStorageKeys } from "@src/constants/localStorageKeys"; -import { moveGoalState } from "@src/store/moveGoalState"; -import { moveGoalHierarchy } from "@src/helpers/GoalController"; const BottomNavbar = ({ title }: { title: string }) => { const { t } = useTranslation(); const navigate = useNavigate(); const location = useLocation(); - const [goalToMove, setGoalToMove] = useRecoilState(moveGoalState); - const setLastAction = useSetRecoilState(lastAction); - const { partnerId } = useParams(); const isPartnerModeActive = !!partnerId; @@ -69,22 +64,9 @@ const BottomNavbar = ({ title }: { title: string }) => { } } }; + const { activeGoalId } = location.state || {}; const isAddBtnVisible = title !== "Focus" && (isPartnerModeActive ? !!activeGoalId : true); - - const handleMoveHere = () => { - if (goalToMove) { - moveGoalHierarchy(goalToMove.id, activeGoalId ?? "root") - .then(() => { - setGoalToMove(null); - setLastAction("goalMoved"); - }) - .catch((error) => { - console.error("Error moving goal:", error); - }); - } - }; - return ( <> {themeSelection && ( @@ -99,21 +81,23 @@ const BottomNavbar = ({ title }: { title: string }) => { - - diff --git a/src/components/GoalsComponents/MyGoal/MoveGoalAlert.tsx b/src/components/GoalsComponents/MyGoal/MoveGoalAlert.tsx new file mode 100644 index 000000000..ea8f5fc4a --- /dev/null +++ b/src/components/GoalsComponents/MyGoal/MoveGoalAlert.tsx @@ -0,0 +1,26 @@ +import React from "react"; +import { Alert } from "antd"; +import { useRecoilState } from "recoil"; +import { moveGoalState } from "@src/store/moveGoalState"; +import "./index.scss"; + +const MoveGoalAlert: React.FC = () => { + const [goalToMove, setGoalToMove] = useRecoilState(moveGoalState); + + if (!goalToMove) return null; + + return ( +
+ setGoalToMove(null)} + /> +
+ ); +}; + +export default MoveGoalAlert; diff --git a/src/components/GoalsComponents/MyGoal/MoveGoalGuide.tsx b/src/components/GoalsComponents/MyGoal/MoveGoalGuide.tsx deleted file mode 100644 index 299527f5d..000000000 --- a/src/components/GoalsComponents/MyGoal/MoveGoalGuide.tsx +++ /dev/null @@ -1,33 +0,0 @@ -import React, { MutableRefObject } from "react"; -import { Tour } from "antd"; -import type { TourProps } from "antd"; -import { useRecoilState } from "recoil"; -import { moveGoalState } from "@src/store/moveGoalState"; - -interface MoveGoalGuideProps { - goalComponentRef: MutableRefObject; -} - -const MoveGoalGuide: React.FC = ({ goalComponentRef }) => { - const [goalToMove, setGoalToMove] = useRecoilState(moveGoalState); - - const steps: TourProps["steps"] = [ - { - title: "Navigate to the goal you want to move into.", - target: () => goalComponentRef.current as HTMLElement, - nextButtonProps: { - children: "Close", - onClick: () => setGoalToMove(null), - }, - placement: "bottom", - className: "move-goal-guide", - }, - ]; - return ( -
- setGoalToMove(null)} steps={steps} /> -
- ); -}; - -export default MoveGoalGuide; diff --git a/src/components/GoalsComponents/MyGoal/index.scss b/src/components/GoalsComponents/MyGoal/index.scss index 074d74f66..9110dbacc 100644 --- a/src/components/GoalsComponents/MyGoal/index.scss +++ b/src/components/GoalsComponents/MyGoal/index.scss @@ -15,6 +15,29 @@ } .goal-to-move-selected { - opacity: 0.5; pointer-events: none; } + +.move-goal-toast { + padding: 10px; + top: 20px; + z-index: 1000; + width: inherit; + + .ant-alert { + background: var(--primary-background); + border: 1px solid var(--default-border-color); + box-shadow: 0 2px 8px rgba(0, 0, 0, 0.15); + + .ant-alert-message { + color: inherit; + font-weight: 600; + font-size: 16px; + } + + .ant-alert-description { + color: inherit; + font-size: 14px; + } + } +} diff --git a/src/pages/GoalsPage/MyGoals.tsx b/src/pages/GoalsPage/MyGoals.tsx index 35a4d807a..f2b8b775d 100644 --- a/src/pages/GoalsPage/MyGoals.tsx +++ b/src/pages/GoalsPage/MyGoals.tsx @@ -4,7 +4,6 @@ import { useRecoilState, useRecoilValue } from "recoil"; import ZinZenTextLight from "@assets/images/LogoTextLight.svg"; import ZinZenTextDark from "@assets/images/LogoTextDark.svg"; -import { displayChangesModal } from "@src/store/GoalsState"; import { GoalItem, TGoalCategory } from "@src/models/GoalItem"; import { GoalSublist } from "@components/GoalsComponents/GoalSublist/GoalSublist"; import { getActiveGoals } from "@api/GoalsAPI"; @@ -31,11 +30,11 @@ import { DeletedGoalProvider } from "@src/contexts/deletedGoal-context"; import { goalCategories } from "@src/constants/goals"; import { suggestedGoalState } from "@src/store/SuggestedGoalState"; import { moveGoalState } from "@src/store/moveGoalState"; -import MoveGoalGuide from "@components/GoalsComponents/MyGoal/MoveGoalGuide"; import DeletedGoals from "./components/DeletedGoals"; import ArchivedGoals from "./components/ArchivedGoals"; import "./GoalsPage.scss"; +import MoveGoalAlert from "@components/GoalsComponents/MyGoal/MoveGoalAlert"; export const MyGoals = () => { let debounceTimeout: ReturnType; @@ -62,8 +61,6 @@ export const MyGoals = () => { const darkModeStatus = useRecoilValue(darkModeState); const goalToMove = useRecoilValue(moveGoalState); - const showChangesModal = useRecoilValue(displayChangesModal); - const [action, setLastAction] = useRecoilState(lastAction); const goalWrapperRef = useRef(null); @@ -133,7 +130,7 @@ export const MyGoals = () => { )}
- {/* */} + {parentId === "root" ? (
From 06465a595485c2ceecf9212d0c91331997a5b480 Mon Sep 17 00:00:00 2001 From: Vinay Badgujar Date: Sat, 9 Nov 2024 13:07:00 +0530 Subject: [PATCH 36/54] restrict render of move here button --- src/components/GlobalAddBtn.tsx | 10 +++++++++- src/pages/GoalsPage/MyGoals.tsx | 8 ++++---- 2 files changed, 13 insertions(+), 5 deletions(-) diff --git a/src/components/GlobalAddBtn.tsx b/src/components/GlobalAddBtn.tsx index 421c3924c..cf9f51508 100644 --- a/src/components/GlobalAddBtn.tsx +++ b/src/components/GlobalAddBtn.tsx @@ -20,6 +20,7 @@ import { useKeyPress } from "@src/hooks/useKeyPress"; import { moveGoalState } from "@src/store/moveGoalState"; import { moveGoalHierarchy } from "@src/helpers/GoalController"; import { lastAction } from "@src/store"; +import { useParentGoalContext } from "@src/contexts/parentGoal-context"; interface AddGoalOptionProps { children: ReactNode; @@ -57,6 +58,10 @@ const GlobalAddBtn = ({ add }: { add: string }) => { const setLastAction = useSetRecoilState(lastAction); + const { + parentData: { parentGoal = { id: "root" } }, + } = useParentGoalContext(); + const navigate = useNavigate(); const [searchParams] = useSearchParams(); const themeSelection = useRecoilValue(themeSelectionMode); @@ -125,6 +130,9 @@ const GlobalAddBtn = ({ add }: { add: string }) => { } }, [plusPressed, enterPressed]); + const shouldRenderMoveButton = + goalToMove && goalToMove.id !== parentGoal?.id && goalToMove.parentGoalId !== parentGoal?.id; + if (searchParams?.get("addOptions")) { return ( <> @@ -135,7 +143,7 @@ const GlobalAddBtn = ({ add }: { add: string }) => { window.history.back(); }} /> - {goalToMove && ( + {shouldRenderMoveButton && ( {t("Move Here")} diff --git a/src/pages/GoalsPage/MyGoals.tsx b/src/pages/GoalsPage/MyGoals.tsx index f2b8b775d..e3afa8f0e 100644 --- a/src/pages/GoalsPage/MyGoals.tsx +++ b/src/pages/GoalsPage/MyGoals.tsx @@ -114,8 +114,8 @@ export const MyGoals = () => { }, [parentId, displaySearch, suggestedGoal, goalToMove]); return ( - - + + {showOptions && } {showShareModal && activeGoal && } {showParticipants && } @@ -151,7 +151,7 @@ export const MyGoals = () => { alt="Zinzen" />
- - + + ); }; From 669bd4671b21d9c835f4f75a3104f7cad532f70d Mon Sep 17 00:00:00 2001 From: Vinay Badgujar Date: Sat, 9 Nov 2024 13:13:58 +0530 Subject: [PATCH 37/54] increase width of add pill and show toast when goal is moved --- src/components/GlobalAddBtn.tsx | 9 ++++++--- src/components/index.scss | 2 +- 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/src/components/GlobalAddBtn.tsx b/src/components/GlobalAddBtn.tsx index cf9f51508..f1552731c 100644 --- a/src/components/GlobalAddBtn.tsx +++ b/src/components/GlobalAddBtn.tsx @@ -19,7 +19,7 @@ import useLongPress from "@src/hooks/useLongPress"; import { useKeyPress } from "@src/hooks/useKeyPress"; import { moveGoalState } from "@src/store/moveGoalState"; import { moveGoalHierarchy } from "@src/helpers/GoalController"; -import { lastAction } from "@src/store"; +import { displayToast, lastAction } from "@src/store"; import { useParentGoalContext } from "@src/contexts/parentGoal-context"; interface AddGoalOptionProps { @@ -56,6 +56,8 @@ const GlobalAddBtn = ({ add }: { add: string }) => { const { handleAddFeeling } = useFeelingStore(); const isPartnerModeActive = !!partnerId; + const setToastMessage = useSetRecoilState(displayToast); + const setLastAction = useSetRecoilState(lastAction); const { @@ -116,8 +118,9 @@ const GlobalAddBtn = ({ add }: { add: string }) => { const handleMoveGoalHere = async () => { if (!goalToMove) return; - - await moveGoalHierarchy(goalToMove.id, parentId); + await moveGoalHierarchy(goalToMove.id, parentId).then(() => { + setToastMessage({ open: true, message: "Goal moved successfully", extra: "" }); + }); setLastAction("goalMoved"); setGoalToMove(null); window.history.back(); diff --git a/src/components/index.scss b/src/components/index.scss index 6fefe4aea..d5ae8e788 100644 --- a/src/components/index.scss +++ b/src/components/index.scss @@ -38,7 +38,7 @@ align-items: center; background-color: var(--secondary-background); padding: 5px; - width: 150px; + width: 180px; right: 35px; &.disabled { From 9f3fd12bbdd2b76e3714aa05e267601b5e86776d Mon Sep 17 00:00:00 2001 From: Vinay Badgujar Date: Sat, 9 Nov 2024 13:50:25 +0530 Subject: [PATCH 38/54] fix: sharedwm goal disappear when moved to non shared goal --- src/helpers/InboxProcessor.ts | 24 ++++++++++++++++++------ 1 file changed, 18 insertions(+), 6 deletions(-) diff --git a/src/helpers/InboxProcessor.ts b/src/helpers/InboxProcessor.ts index d87b0fd73..cc8e5af14 100644 --- a/src/helpers/InboxProcessor.ts +++ b/src/helpers/InboxProcessor.ts @@ -14,6 +14,7 @@ import { addIntoSublist, // changeNewUpdatesStatus, getGoal, + getGoalById, updateGoal, } from "@src/api/GoalsAPI"; import { addGoalChangesInID, createEmptyInboxItem, getInboxItem } from "@src/api/InboxAPI"; @@ -65,9 +66,20 @@ const updateSharedWMGoalAndDescendants = async (movedGoal: GoalItem) => { } }; -const handleMoveOperation = async (movedGoal: GoalItem, goalToMove: GoalItem) => { - await updateSharedWMParentSublist(goalToMove.parentGoalId, movedGoal.parentGoalId, goalToMove.id); - await updateSharedWMGoalAndDescendants(movedGoal); +const handleMoveOperation = async (movedGoal: GoalItem, correspondingSharedWMGoal: GoalItem) => { + const isNewParentAvailable = await getSharedWMGoal(movedGoal.parentGoalId); + const updatedGoal = { + ...movedGoal, + parentGoalId: !isNewParentAvailable ? "root" : movedGoal.parentGoalId, + rootGoalId: !isNewParentAvailable ? "root" : movedGoal.rootGoalId, + }; + + await updateSharedWMParentSublist( + correspondingSharedWMGoal.parentGoalId, + updatedGoal.parentGoalId, + correspondingSharedWMGoal.id, + ); + await updateSharedWMGoalAndDescendants(updatedGoal); }; const addChangesToRootGoal = async (goalId: string, relId: string, changes: ChangesByType) => { @@ -177,14 +189,14 @@ export const handleIncomingChanges = async (payload: Payload, relId: string) => ...payload.changes.map((ele: changesInGoal) => ({ ...ele, goal: fixDateVlauesInGoalObject(ele.goal) })), ]; const movedGoal = changes[0].goal; - const goalToMove = await getSharedWMGoal(movedGoal.id); + const correspondingSharedWMGoal = await getSharedWMGoal(movedGoal.id); - if (!goalToMove) { + if (!correspondingSharedWMGoal) { console.log("Goal to move not found"); return; } - await handleMoveOperation(movedGoal, goalToMove); + await handleMoveOperation(movedGoal, correspondingSharedWMGoal); } } else if (["sharer", "suggestion"].includes(payload.type)) { const { changes, changeType, rootGoalId } = payload; From 46e2dc022044e9ad3967ea2f2ad97593a2b6d849 Mon Sep 17 00:00:00 2001 From: Vinay Badgujar Date: Sun, 10 Nov 2024 08:04:49 +0530 Subject: [PATCH 39/54] add move goal action confirmation --- src/Interfaces/IPopupModals.ts | 3 ++- .../GoalsComponents/MyGoalActions/RegularGoalActions.tsx | 5 +++-- src/constants/goals.ts | 3 +++ src/store/index.ts | 1 + src/translations/en/translation.json | 5 +++++ 5 files changed, 14 insertions(+), 3 deletions(-) diff --git a/src/Interfaces/IPopupModals.ts b/src/Interfaces/IPopupModals.ts index d5a368b8d..b8382b095 100644 --- a/src/Interfaces/IPopupModals.ts +++ b/src/Interfaces/IPopupModals.ts @@ -9,6 +9,7 @@ export interface TConfirmActionState { addHint: boolean; deleteHint: boolean; reportHint: boolean; + move: boolean; }; collaboration: { colabRequest: boolean; @@ -20,7 +21,7 @@ export interface TConfirmActionState { export interface TConfirmGoalAction { actionCategory: "goal"; - actionName: "archive" | "delete" | "shareAnonymously" | "shareWithOne" | "restore"; + actionName: "archive" | "delete" | "shareAnonymously" | "shareWithOne" | "restore" | "move"; } export interface TConfirmColabGoalAction { diff --git a/src/components/GoalsComponents/MyGoalActions/RegularGoalActions.tsx b/src/components/GoalsComponents/MyGoalActions/RegularGoalActions.tsx index 06b9d8245..246b1a642 100644 --- a/src/components/GoalsComponents/MyGoalActions/RegularGoalActions.tsx +++ b/src/components/GoalsComponents/MyGoalActions/RegularGoalActions.tsx @@ -162,8 +162,9 @@ const RegularGoalActions = ({ goal }: { goal: GoalItem }) => { {!isPartnerModeActive && (
{ - handleMove(goal); + onClickCapture={async (e) => { + e.stopPropagation(); + await openConfirmationPopUp({ actionCategory: "goal", actionName: "move" }); }} > diff --git a/src/constants/goals.ts b/src/constants/goals.ts index a610ee1af..f2299716a 100644 --- a/src/constants/goals.ts +++ b/src/constants/goals.ts @@ -62,6 +62,9 @@ export const getConfirmButtonText = (actionName: string) => { case "reportHint": confirmButtonText = "reportHint"; break; + case "move": + confirmButtonText = "moveGoal"; + break; default: } return confirmButtonText; diff --git a/src/store/index.ts b/src/store/index.ts index 000d619c8..caa5b8859 100644 --- a/src/store/index.ts +++ b/src/store/index.ts @@ -17,6 +17,7 @@ const defaultConfirmationObj: TConfirmActionState = { addHint: true, deleteHint: true, reportHint: true, + move: true, }, collaboration: { colabRequest: true, diff --git a/src/translations/en/translation.json b/src/translations/en/translation.json index fd5cdb7d8..0ecfb633c 100644 --- a/src/translations/en/translation.json +++ b/src/translations/en/translation.json @@ -118,6 +118,7 @@ "addHint": "Add hint", "deleteHint": "Delete hint", "reportHint": "Report hint", + "moveGoal": "Move goal", "confirm": "Confirm", "cancel": "Cancel", "duration": "Duration", @@ -157,6 +158,10 @@ "reportHint": { "header": "Do you want to report this hint?", "note": "You will be notified when it's reported." + }, + "move": { + "header": "Do you want to move this goal?", + "note": "Navigate to the new location and press the + button to move." } }, "collaboration": { From db860a4998b09ffdab03c665aed24f718f7e1ac0 Mon Sep 17 00:00:00 2001 From: Vinay Badgujar Date: Sun, 10 Nov 2024 13:29:31 +0530 Subject: [PATCH 40/54] when in move state single tap on global add btn show options --- src/components/GlobalAddBtn.tsx | 68 ++++++++++++------- .../GoalsComponents/MyGoal/MoveGoalAlert.tsx | 26 ------- .../MyGoalActions/RegularGoalActions.tsx | 2 + src/pages/GoalsPage/MyGoals.tsx | 2 - 4 files changed, 46 insertions(+), 52 deletions(-) delete mode 100644 src/components/GoalsComponents/MyGoal/MoveGoalAlert.tsx diff --git a/src/components/GlobalAddBtn.tsx b/src/components/GlobalAddBtn.tsx index f1552731c..df8ef4bc1 100644 --- a/src/components/GlobalAddBtn.tsx +++ b/src/components/GlobalAddBtn.tsx @@ -1,5 +1,5 @@ import React, { ReactNode, useEffect } from "react"; -import { useRecoilValue, useSetRecoilState } from "recoil"; +import { useRecoilState, useRecoilValue, useSetRecoilState } from "recoil"; import { useTranslation } from "react-i18next"; import { useLocation, useNavigate, useParams, useSearchParams } from "react-router-dom"; @@ -68,8 +68,7 @@ const GlobalAddBtn = ({ add }: { add: string }) => { const [searchParams] = useSearchParams(); const themeSelection = useRecoilValue(themeSelectionMode); const isAddingBudgetGoalAllowed = useRecoilValue(allowAddingBudgetGoal); - const goalToMove = useRecoilValue(moveGoalState); - const setGoalToMove = useSetRecoilState(moveGoalState); + const [goalToMove, setGoalToMove] = useRecoilState(moveGoalState); const enterPressed = useKeyPress("Enter"); const plusPressed = useKeyPress("+"); @@ -91,6 +90,13 @@ const GlobalAddBtn = ({ add }: { add: string }) => { }; const handleGlobalAddClick = async (e: React.MouseEvent) => { e.stopPropagation(); + if (goalToMove) { + if (add === "myGoals" || isPartnerModeActive) { + navigate(`/goals/${parentId}?addOptions=true`, { state }); + } + return; + } + if (themeSelection) { window.history.back(); } else if (add === "myTime" || add === "myGoals" || isPartnerModeActive) { @@ -146,28 +152,42 @@ const GlobalAddBtn = ({ add }: { add: string }) => { window.history.back(); }} /> - {shouldRenderMoveButton && ( - - {t("Move Here")} - + {goalToMove ? ( + <> + + {t("Move Here")} + + { + setGoalToMove(null); + window.history.back(); + }} + bottom={74} + > + {t("Cancel")} + + + ) : ( + <> + { + handleAddGoal("Budget"); + }} + disabled={!isAddingBudgetGoalAllowed} + bottom={144} + > + {t("addBtnBudget")} + + { + handleAddGoal("Standard"); + }} + bottom={74} + > + {t("addBtnGoal")} + + )} - { - handleAddGoal("Budget"); - }} - disabled={!isAddingBudgetGoalAllowed} - bottom={144} - > - {t("addBtnBudget")} - - { - handleAddGoal("Standard"); - }} - bottom={74} - > - {t("addBtnGoal")} - ); } diff --git a/src/components/GoalsComponents/MyGoal/MoveGoalAlert.tsx b/src/components/GoalsComponents/MyGoal/MoveGoalAlert.tsx deleted file mode 100644 index ea8f5fc4a..000000000 --- a/src/components/GoalsComponents/MyGoal/MoveGoalAlert.tsx +++ /dev/null @@ -1,26 +0,0 @@ -import React from "react"; -import { Alert } from "antd"; -import { useRecoilState } from "recoil"; -import { moveGoalState } from "@src/store/moveGoalState"; -import "./index.scss"; - -const MoveGoalAlert: React.FC = () => { - const [goalToMove, setGoalToMove] = useRecoilState(moveGoalState); - - if (!goalToMove) return null; - - return ( -
- setGoalToMove(null)} - /> -
- ); -}; - -export default MoveGoalAlert; diff --git a/src/components/GoalsComponents/MyGoalActions/RegularGoalActions.tsx b/src/components/GoalsComponents/MyGoalActions/RegularGoalActions.tsx index 246b1a642..f7b0ad52a 100644 --- a/src/components/GoalsComponents/MyGoalActions/RegularGoalActions.tsx +++ b/src/components/GoalsComponents/MyGoalActions/RegularGoalActions.tsx @@ -74,6 +74,8 @@ const RegularGoalActions = ({ goal }: { goal: GoalItem }) => { } else if (action === "colabRequest") { await convertSharedWMGoalToColab(goal); setLastAction("goalColabRequest"); + } else if (action === "move") { + await handleMove(goal); } window.history.back(); }; diff --git a/src/pages/GoalsPage/MyGoals.tsx b/src/pages/GoalsPage/MyGoals.tsx index e3afa8f0e..ecbc7cde0 100644 --- a/src/pages/GoalsPage/MyGoals.tsx +++ b/src/pages/GoalsPage/MyGoals.tsx @@ -34,7 +34,6 @@ import DeletedGoals from "./components/DeletedGoals"; import ArchivedGoals from "./components/ArchivedGoals"; import "./GoalsPage.scss"; -import MoveGoalAlert from "@components/GoalsComponents/MyGoal/MoveGoalAlert"; export const MyGoals = () => { let debounceTimeout: ReturnType; @@ -130,7 +129,6 @@ export const MyGoals = () => { )}
- {parentId === "root" ? (
From 983c6efa119508f986876dbe57832954a8f5cdea Mon Sep 17 00:00:00 2001 From: Vinay Badgujar Date: Mon, 11 Nov 2024 13:14:52 +0530 Subject: [PATCH 41/54] edit move goal confirmation modal wording --- src/common/ConfirmationModal.tsx | 4 ++-- src/components/GlobalAddBtn.tsx | 2 +- src/translations/en/translation.json | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/common/ConfirmationModal.tsx b/src/common/ConfirmationModal.tsx index 4520ede48..a7120438d 100644 --- a/src/common/ConfirmationModal.tsx +++ b/src/common/ConfirmationModal.tsx @@ -1,7 +1,7 @@ import { Checkbox } from "antd"; import React, { useState } from "react"; import { useRecoilState, useRecoilValue } from "recoil"; -import { useTranslation } from "react-i18next"; +import { Trans, useTranslation } from "react-i18next"; import { darkModeState, displayConfirmation } from "@src/store"; import { ConfirmationModalProps } from "@src/Interfaces/IPopupModals"; @@ -51,7 +51,7 @@ const ConfirmationModal: React.FC = ({ action, handleCli {t(headerKey)}

- {t("note")}: {t(noteKey)} + {t("note")}: , ul:

    , li:
  • }} />

    { {goalToMove ? ( <> - {t("Move Here")} + {t("Move here")} { diff --git a/src/translations/en/translation.json b/src/translations/en/translation.json index 0ecfb633c..bd1cbe441 100644 --- a/src/translations/en/translation.json +++ b/src/translations/en/translation.json @@ -161,7 +161,7 @@ }, "move": { "header": "Do you want to move this goal?", - "note": "Navigate to the new location and press the + button to move." + "note": "
    • Navigate to the new location.
    • Press the + button to move.
    " } }, "collaboration": { From 96c1b8d3cf3353ce9428bbd51989fcbaf2e9ce03 Mon Sep 17 00:00:00 2001 From: Vinay Badgujar Date: Mon, 11 Nov 2024 13:35:28 +0530 Subject: [PATCH 42/54] fix eslint issues --- .../DisplayChangesModal/DisplayChangesModal.tsx | 17 ----------------- .../GoalsComponents/GoalSublist/GoalSublist.tsx | 2 +- 2 files changed, 1 insertion(+), 18 deletions(-) diff --git a/src/components/GoalsComponents/DisplayChangesModal/DisplayChangesModal.tsx b/src/components/GoalsComponents/DisplayChangesModal/DisplayChangesModal.tsx index 1d75f32b3..cdc7844af 100644 --- a/src/components/GoalsComponents/DisplayChangesModal/DisplayChangesModal.tsx +++ b/src/components/GoalsComponents/DisplayChangesModal/DisplayChangesModal.tsx @@ -299,23 +299,6 @@ const DisplayChangesModal = ({ currentMainGoal }: { currentMainGoal: GoalItem }) init(); }, [currentMainGoal]); - const getChangedGoalFromRoot = async (rootGoal: GoalItem, relId: string) => { - const { typeAtPriority, goals, parentId } = await jumpToLowestChanges(rootGoal.id, relId); - - if (typeAtPriority === "none") return { typeAtPriority, goals, parentId }; - - const changedGoal = await getGoal(parentId); - if (!changedGoal) return { typeAtPriority, goals, parentId }; - - return { - typeAtPriority, - goals, - parentId, - changedGoal, - rootGoal, - }; - }; - return ( {currentMainGoal && ( diff --git a/src/components/GoalsComponents/GoalSublist/GoalSublist.tsx b/src/components/GoalsComponents/GoalSublist/GoalSublist.tsx index 7ff599d64..3d5248be3 100644 --- a/src/components/GoalsComponents/GoalSublist/GoalSublist.tsx +++ b/src/components/GoalsComponents/GoalSublist/GoalSublist.tsx @@ -16,10 +16,10 @@ import ArchivedGoals from "@pages/GoalsPage/components/ArchivedGoals"; import { TrashItem } from "@src/models/TrashItem"; import GoalItemSummary from "@src/common/GoalItemSummary/GoalItemSummary"; import AvailableGoalHints from "@pages/GoalsPage/components/AvailableGoalHints"; +import { moveGoalState } from "@src/store/moveGoalState"; import GoalsList from "../GoalsList"; import GoalHistory from "./components/GoalHistory"; import "./GoalSublist.scss"; -import { moveGoalState } from "@src/store/moveGoalState"; export const GoalSublist = () => { const { From 0558d2ad6d8bbad78aa360fe30e91390ef56d18e Mon Sep 17 00:00:00 2001 From: Vinay Badgujar Date: Tue, 12 Nov 2024 18:46:51 +0530 Subject: [PATCH 43/54] fix: partner goal disappears when use modified by sharer --- src/api/SharedWMAPI/index.ts | 2 +- src/helpers/GoalController.ts | 1 - src/helpers/PubSubController.ts | 2 +- src/services/contact.service.ts | 2 +- 4 files changed, 3 insertions(+), 4 deletions(-) diff --git a/src/api/SharedWMAPI/index.ts b/src/api/SharedWMAPI/index.ts index 8dc3c6ac8..62e59a69a 100644 --- a/src/api/SharedWMAPI/index.ts +++ b/src/api/SharedWMAPI/index.ts @@ -108,7 +108,7 @@ export const getRootGoalsOfPartner = async (relId: string) => { ).reverse(); }; -export const updateSharedWMGoal = async (id: string, changes: object) => { +export const updateSharedWMGoal = async (id: string, changes: Partial) => { db.transaction("rw", db.sharedWMCollection, async () => { await db.sharedWMCollection.update(id, changes).then((updated) => updated); }).catch((e) => { diff --git a/src/helpers/GoalController.ts b/src/helpers/GoalController.ts index 7614c0e44..cdf73ed40 100644 --- a/src/helpers/GoalController.ts +++ b/src/helpers/GoalController.ts @@ -65,7 +65,6 @@ export const createGoal = async (newGoal: GoalItem, parentGoalId: string, ancest ...updatedGoal, id: newGoalId, rootGoalId, - participants: [], }, }, ]); diff --git a/src/helpers/PubSubController.ts b/src/helpers/PubSubController.ts index c17714947..9eb4b9e5f 100644 --- a/src/helpers/PubSubController.ts +++ b/src/helpers/PubSubController.ts @@ -22,7 +22,7 @@ export const sendUpdatedGoal = async ( .filter((ele) => !excludeSubs.includes(ele.sub.relId)) .forEach(async ({ sub, rootGoalId }) => { sendUpdatesToSubscriber(sub, rootGoalId, "modifiedGoals", [ - { level: ancestorGoalIds.length, goal: { ...changes, rootGoalId, participants: [] } }, + { level: ancestorGoalIds.length, goal: { ...changes, rootGoalId } }, ]).then(() => console.log("update sent")); }); } diff --git a/src/services/contact.service.ts b/src/services/contact.service.ts index 734d726f6..6689c372d 100644 --- a/src/services/contact.service.ts +++ b/src/services/contact.service.ts @@ -72,7 +72,7 @@ export const sendUpdatesToSubscriber = async ( sub: IParticipant, rootGoalId: string, changeType: typeOfChange, - changes: { level: number; goal: GoalItem }[] | { level: number; id: string }[], + changes: { level: number; goal: Omit }[] | { level: number; id: string }[], customEventType = "", ) => { const url = "https://x7phxjeuwd4aqpgbde6f74s4ey0yobfi.lambda-url.eu-west-1.on.aws/"; From d8d9ced67787cf4fcc834c5a85de050790719bf4 Mon Sep 17 00:00:00 2001 From: Vinay Badgujar Date: Wed, 13 Nov 2024 15:16:27 +0530 Subject: [PATCH 44/54] add types --- src/helpers/InboxProcessor.ts | 16 +++++++++++----- src/models/InboxItem.ts | 10 +++++++++- src/utils/defaultGenerators.ts | 16 ++++++++-------- 3 files changed, 28 insertions(+), 14 deletions(-) diff --git a/src/helpers/InboxProcessor.ts b/src/helpers/InboxProcessor.ts index cc8e5af14..b6c6055d2 100644 --- a/src/helpers/InboxProcessor.ts +++ b/src/helpers/InboxProcessor.ts @@ -14,7 +14,6 @@ import { addIntoSublist, // changeNewUpdatesStatus, getGoal, - getGoalById, updateGoal, } from "@src/api/GoalsAPI"; import { addGoalChangesInID, createEmptyInboxItem, getInboxItem } from "@src/api/InboxAPI"; @@ -226,10 +225,17 @@ export const handleIncomingChanges = async (payload: Payload, relId: string) => const inbox = await getInboxItem(goal.id); const defaultChanges = getDefaultValueOfGoalChanges(); - defaultChanges[changeType] = filteredChanges.map((ele) => ({ - ...(isIdChangeType(changeType) ? (ele as changesInId) : (ele as changesInGoal)), - intent: payload.type as typeOfIntent, - })); + if (isIdChangeType(changeType)) { + defaultChanges[changeType] = filteredChanges.map((ele) => ({ + ...(ele as changesInId), + intent: payload.type as typeOfIntent, + })); + } else { + defaultChanges[changeType] = filteredChanges.map((ele) => ({ + ...(ele as changesInGoal), + intent: payload.type as typeOfIntent, + })); + } if (!inbox) { await createEmptyInboxItem(goal.id); diff --git a/src/models/InboxItem.ts b/src/models/InboxItem.ts index 0f6137101..bb2e87190 100644 --- a/src/models/InboxItem.ts +++ b/src/models/InboxItem.ts @@ -6,7 +6,7 @@ type GoalChangeTypes = "subgoals" | "modifiedGoals" | "newGoalMoved"; export type typeOfChange = IdChangeTypes | GoalChangeTypes; export type typeOfIntent = "suggestion" | "shared"; -export type changesInId = { level: number; id: string; intent: typeOfIntent }; +export type changesInId = { level: number; id: string; intent: typeOfIntent; timestamp: string }; export type changesInGoal = { level: number; goal: GoalItem; intent: typeOfIntent }; export type ChangesByType = { @@ -15,6 +15,14 @@ export type ChangesByType = { [K in GoalChangeTypes]: changesInGoal[]; }; +export interface IChangesInGoal { + subgoals: changesInGoal[]; + modifiedGoals: changesInGoal[]; + archived: changesInId[]; + deleted: changesInId[]; + restored: changesInId[]; +} + export interface InboxItem { id: string; changes: { diff --git a/src/utils/defaultGenerators.ts b/src/utils/defaultGenerators.ts index 4b0737811..ca1bfe621 100644 --- a/src/utils/defaultGenerators.ts +++ b/src/utils/defaultGenerators.ts @@ -1,13 +1,13 @@ -import { ChangesByType } from "@src/models/InboxItem"; +import { ChangesByType, changesInGoal, changesInId } from "@src/models/InboxItem"; export function getDefaultValueOfGoalChanges(): ChangesByType { return { - subgoals: [], - modifiedGoals: [], - archived: [], - deleted: [], - restored: [], - moved: [], - newGoalMoved: [], + subgoals: [] as changesInGoal[], + modifiedGoals: [] as changesInGoal[], + archived: [] as changesInId[], + deleted: [] as changesInId[], + restored: [] as changesInId[], + moved: [] as changesInId[], + newGoalMoved: [] as changesInGoal[], }; } From b8108bd240cc1cf0dbea2dcf88f8c8d4f959ae64 Mon Sep 17 00:00:00 2001 From: Vinay Badgujar Date: Wed, 13 Nov 2024 17:07:12 +0530 Subject: [PATCH 45/54] integrate lww merge conflict resolution for the collaborated goals (except newgoalmoved and subgoals change) --- src/helpers/InboxProcessor.ts | 31 +++++++++++++++++++---------- src/helpers/PubSubController.ts | 5 +++-- src/helpers/mergeSharedGoalItems.ts | 11 ++++++++++ src/models/InboxItem.ts | 2 +- src/services/contact.service.ts | 4 +++- 5 files changed, 38 insertions(+), 15 deletions(-) diff --git a/src/helpers/InboxProcessor.ts b/src/helpers/InboxProcessor.ts index b6c6055d2..c90ce306c 100644 --- a/src/helpers/InboxProcessor.ts +++ b/src/helpers/InboxProcessor.ts @@ -31,6 +31,7 @@ import { fixDateVlauesInGoalObject } from "@src/utils"; import { getDeletedGoal, restoreUserGoal } from "@src/api/TrashAPI"; import { getContactByRelId } from "@src/api/ContactsAPI"; import { getAllDescendants, getRootGoalId, updateRootGoalNotification } from "./GoalController"; +import { isIncomingGoalLatest, isIncomingIdChangeLatest } from "./mergeSharedGoalItems"; export interface Payload { relId: string; @@ -205,25 +206,33 @@ export const handleIncomingChanges = async (payload: Payload, relId: string) => } const goalId = "goal" in changes[0] ? changes[0].goal.id : changes[0].id; - const goal = await getGoal(goalId); + const localGoal = await getGoal(goalId); - if (!goal || !goal.participants.find((p) => p.relId === relId && p.following)) { + if (!localGoal || !localGoal.participants.find((p) => p.relId === relId && p.following)) { console.log("Goal not found or not shared with participant"); return; } - let filteredChanges = changes; - if (changeType !== "deleted" && changeType !== "moved" && changeType !== "restored" && changeType !== "archived") { - // TODO: Handle version conflict resolution later - // For now, accepting all incoming changes without checking if they are the latest - filteredChanges = changes as changesInGoal[]; - } + const allAreLatest = await Promise.all( + changes.map(async (ele) => { + let isLatest = true; + if ("goal" in ele) { + isLatest = await isIncomingGoalLatest(localGoal.id, ele.goal); + } else { + isLatest = await isIncomingIdChangeLatest(localGoal.id, ele.timestamp); + } + return isLatest ? ele : null; + }), + ); + + const filteredChanges = allAreLatest.filter((ele) => ele !== null); if (filteredChanges.length === 0) { + console.log("All changes are not latest"); return; } - const inbox = await getInboxItem(goal.id); + const inbox = await getInboxItem(localGoal.id); const defaultChanges = getDefaultValueOfGoalChanges(); if (isIdChangeType(changeType)) { defaultChanges[changeType] = filteredChanges.map((ele) => ({ @@ -238,10 +247,10 @@ export const handleIncomingChanges = async (payload: Payload, relId: string) => } if (!inbox) { - await createEmptyInboxItem(goal.id); + await createEmptyInboxItem(localGoal.id); } - await addChangesToRootGoal(goal.id, relId, defaultChanges); + await addChangesToRootGoal(localGoal.id, relId, defaultChanges); } }; diff --git a/src/helpers/PubSubController.ts b/src/helpers/PubSubController.ts index 9eb4b9e5f..2b955ed80 100644 --- a/src/helpers/PubSubController.ts +++ b/src/helpers/PubSubController.ts @@ -22,7 +22,7 @@ export const sendUpdatedGoal = async ( .filter((ele) => !excludeSubs.includes(ele.sub.relId)) .forEach(async ({ sub, rootGoalId }) => { sendUpdatesToSubscriber(sub, rootGoalId, "modifiedGoals", [ - { level: ancestorGoalIds.length, goal: { ...changes, rootGoalId } }, + { level: ancestorGoalIds.length, goal: { ...changes, rootGoalId, timestamp: Date.now() } }, ]).then(() => console.log("update sent")); }); } @@ -34,6 +34,7 @@ export const sendFinalUpdateOnGoal = async ( ancestors: string[] = [], redefineAncestors = true, excludeSubs: string[] = [], + timestamp: number = Date.now(), ) => { console.log(`[sendFinalUpdateOnGoal] Starting for goalId: ${goalId}, action: ${action}`); @@ -56,7 +57,7 @@ export const sendFinalUpdateOnGoal = async ( filteredSubscribers.forEach(async ({ sub, rootGoalId }) => { console.log(`[sendFinalUpdateOnGoal] Sending update to subscriber ${sub.relId} for root goal ${rootGoalId}`); - sendUpdatesToSubscriber(sub, rootGoalId, action, [{ level: ancestorGoalIds.length, id: goalId }]) + sendUpdatesToSubscriber(sub, rootGoalId, action, [{ level: ancestorGoalIds.length, id: goalId, timestamp }]) .then(() => console.log(`[sendFinalUpdateOnGoal] Update sent successfully to ${sub.relId}`)) .catch((error) => console.error(`[sendFinalUpdateOnGoal] Error sending update to ${sub.relId}:`, error)); }); diff --git a/src/helpers/mergeSharedGoalItems.ts b/src/helpers/mergeSharedGoalItems.ts index 01a503b52..7f55824f8 100644 --- a/src/helpers/mergeSharedGoalItems.ts +++ b/src/helpers/mergeSharedGoalItems.ts @@ -1,5 +1,6 @@ import { getGoalById } from "@src/api/GoalsAPI"; import { GoalItem } from "@src/models/GoalItem"; +import { changesInId } from "@src/models/InboxItem"; export async function isIncomingGoalLatest(localGoalId: string, incomingGoal: GoalItem): Promise { const localGoal = await getGoalById(localGoalId); @@ -14,3 +15,13 @@ export async function isIncomingGoalLatest(localGoalId: string, incomingGoal: Go return false; } + +export async function isIncomingIdChangeLatest(localGoalId: string, incomingChangeTimestamp: number): Promise { + const localGoal = await getGoalById(localGoalId); + + if (!localGoal) { + return true; + } + + return incomingChangeTimestamp > localGoal.timestamp; +} diff --git a/src/models/InboxItem.ts b/src/models/InboxItem.ts index bb2e87190..43ba10e93 100644 --- a/src/models/InboxItem.ts +++ b/src/models/InboxItem.ts @@ -6,7 +6,7 @@ type GoalChangeTypes = "subgoals" | "modifiedGoals" | "newGoalMoved"; export type typeOfChange = IdChangeTypes | GoalChangeTypes; export type typeOfIntent = "suggestion" | "shared"; -export type changesInId = { level: number; id: string; intent: typeOfIntent; timestamp: string }; +export type changesInId = { level: number; id: string; intent: typeOfIntent; timestamp: number }; export type changesInGoal = { level: number; goal: GoalItem; intent: typeOfIntent }; export type ChangesByType = { diff --git a/src/services/contact.service.ts b/src/services/contact.service.ts index 6689c372d..e88be0368 100644 --- a/src/services/contact.service.ts +++ b/src/services/contact.service.ts @@ -72,7 +72,9 @@ export const sendUpdatesToSubscriber = async ( sub: IParticipant, rootGoalId: string, changeType: typeOfChange, - changes: { level: number; goal: Omit }[] | { level: number; id: string }[], + changes: + | { level: number; goal: Omit }[] + | { level: number; id: string; timestamp: number }[], customEventType = "", ) => { const url = "https://x7phxjeuwd4aqpgbde6f74s4ey0yobfi.lambda-url.eu-west-1.on.aws/"; From 2baf26c8ab3b467dd0a99324b1d6505eb77f273b Mon Sep 17 00:00:00 2001 From: Vinay Badgujar Date: Sat, 16 Nov 2024 20:36:51 +0530 Subject: [PATCH 46/54] refactor: use styles from short css file --- .../DisplayChangesModal/ShowChanges.scss | 44 ------------------- .../DisplayChangesModal/ShowChanges.tsx | 18 ++++---- src/short.scss | 8 ++++ 3 files changed, 17 insertions(+), 53 deletions(-) diff --git a/src/components/GoalsComponents/DisplayChangesModal/ShowChanges.scss b/src/components/GoalsComponents/DisplayChangesModal/ShowChanges.scss index fabe9ec86..f2c7e202b 100644 --- a/src/components/GoalsComponents/DisplayChangesModal/ShowChanges.scss +++ b/src/components/GoalsComponents/DisplayChangesModal/ShowChanges.scss @@ -1,40 +1,14 @@ .move-info-container { - display: flex; - flex-direction: column; - gap: 20px; - background: var(--secondary-background); - border-radius: 12px; padding: 24px; - border: 1px solid var(--default-border-color); - box-shadow: 0 2px 8px rgba(0, 0, 0, 0.05); .move-info-item { - display: flex; - flex-direction: column; - gap: 8px; - .move-info-label { - font-size: 12px; color: var(--text-secondary); - font-weight: 600; - text-transform: uppercase; - letter-spacing: 0.5px; } .move-info-value { - font-size: 14px; color: var(--text-primary); padding: 12px 16px; - background: var(--primary-background); - border-radius: 8px; - border: 1px solid var(--default-border-color); - line-height: 1.4; - - &.highlight-box { - background: var(--selection-color); - border: none; - font-weight: 500; - } } } @@ -42,38 +16,20 @@ display: grid; grid-template-columns: 1fr auto 1fr; align-items: center; - gap: 12px; padding: 16px; background: var(--primary-background); - border-radius: 12px; - border: 1px solid var(--default-border-color); .arrow { width: 32px; height: 32px; - display: flex; - align-items: center; - justify-content: center; - background: var(--secondary-background); border-radius: 50%; - color: var(--text-secondary); - font-size: 18px; margin-top: 24px; } } .warning-message { - display: flex; - align-items: center; - gap: 8px; padding: 12px 16px; background: var(--bottom-nav-color); - border-radius: 8px; margin-top: 4px; - font-size: 13px; - - .anticon { - font-size: 16px; - } } } diff --git a/src/components/GoalsComponents/DisplayChangesModal/ShowChanges.tsx b/src/components/GoalsComponents/DisplayChangesModal/ShowChanges.tsx index 2277bcf1a..512660a0c 100644 --- a/src/components/GoalsComponents/DisplayChangesModal/ShowChanges.tsx +++ b/src/components/GoalsComponents/DisplayChangesModal/ShowChanges.tsx @@ -11,28 +11,28 @@ export const getMovedSubgoalsList = ( if (!goalUnderReview) return null; return ( -
    -
    - Goal Being Moved -
    {goalUnderReview.title}
    +
    +
    + Goal Being Moved +
    {goalUnderReview.title}
    -
    +
    From -
    {oldParentTitle}
    +
    {oldParentTitle}
    -
    →
    +
    →
    To -
    {newParentTitle}
    +
    {newParentTitle}
    {newParentTitle === "Non-shared goal" && ( -
    +
    The new parent goal is not shared. The goal will be moved to the root.
    diff --git a/src/short.scss b/src/short.scss index fc49e8dc6..cca36cc97 100644 --- a/src/short.scss +++ b/src/short.scss @@ -96,6 +96,14 @@ font-size: 1.25rem; } +.text-sm { + font-size: 0.875rem; +} + +.text-xs { + font-size: 0.75rem; +} + .m-0 { margin: 0; } From c78b4a18e98575d473b24d3a98f520d853ccc7f1 Mon Sep 17 00:00:00 2001 From: Vinay Badgujar Date: Sat, 23 Nov 2024 10:24:32 +0530 Subject: [PATCH 47/54] fix: duplicate move request sent --- src/helpers/GoalController.ts | 154 ++++++++++++++++++---------------- 1 file changed, 80 insertions(+), 74 deletions(-) diff --git a/src/helpers/GoalController.ts b/src/helpers/GoalController.ts index cdf73ed40..c68d9f2b8 100644 --- a/src/helpers/GoalController.ts +++ b/src/helpers/GoalController.ts @@ -119,7 +119,6 @@ export const createSharedGoal = async (newGoal: GoalItem, parentGoalId: string, if (parentGoalId && parentGoalId !== "root") { const parentGoal = await getGoal(parentGoalId); if (!parentGoal) { - console.log("Parent goal not found"); return { parentGoal: null }; } @@ -127,34 +126,41 @@ export const createSharedGoal = async (newGoal: GoalItem, parentGoalId: string, const subscribers = await getParticipantsOfGoals(ancestors); if (newGoalId) { - subscribers.map(async ({ sub, rootGoalId }) => { - await sendUpdatesToSubscriber(sub, rootGoalId, "newGoalMoved", [ - { - level, - goal: { ...newGoal, id: newGoalId, parentGoalId }, - }, - ]); - }); + try { + await Promise.all( + subscribers.map(async ({ sub, rootGoalId }) => { + await sendUpdatesToSubscriber(sub, rootGoalId, "newGoalMoved", [ + { + level, + goal: { ...newGoal, id: newGoalId, parentGoalId }, + }, + ]); + }), + ); - const descendants = await getAllDescendants(newGoalId); - if (descendants.length > 0) { - subscribers.map(async ({ sub, rootGoalId }) => { - await sendUpdatesToSubscriber( - sub, - rootGoalId, - "newGoalMoved", - descendants.map((descendant) => ({ - level: level + 1, - goal: { - ...descendant, + const descendants = await getAllDescendants(newGoalId); + + if (descendants.length > 0) { + await Promise.all( + subscribers.map(async ({ sub, rootGoalId }) => { + await sendUpdatesToSubscriber( + sub, rootGoalId, - }, - })), + "newGoalMoved", + descendants.map((descendant) => ({ + level: level + 1, + goal: { + ...descendant, + rootGoalId, + }, + })), + ); + }), ); - }); + } + } catch (error) { + console.error("[createSharedGoal] Error sending updates:", error); } - - console.log("Updates sent successfully"); } return { parentGoal }; } @@ -292,8 +298,8 @@ export const moveGoalHierarchy = async (goalId: string, newParentGoalId: string) const ancestors = await getGoalHistoryToRoot(goalId); const ancestorGoalIds = ancestors.map((ele) => ele.goalID); - const ancestorGoalsIdsOfNewParent = await getGoalHistoryToRoot(newParentGoalId); - const ancestorGoalIdsOfNewParent = ancestorGoalsIdsOfNewParent.map((ele) => ele.goalID); + const ancestorGoalsOfNewParent = await getGoalHistoryToRoot(newParentGoalId); + const ancestorGoalIdsOfNewParent = ancestorGoalsOfNewParent.map((ele) => ele.goalID); const ancestorGoals = await Promise.all(ancestorGoalIdsOfNewParent.map((id) => getGoal(id))); const allParticipants = new Map(); @@ -318,58 +324,58 @@ export const moveGoalHierarchy = async (goalId: string, newParentGoalId: string) participants: Array.from(allParticipants.values()), }; - await createSharedGoal(updatedGoal, newParentGoalId, ancestorGoalIdsOfNewParent); - - await Promise.all([ - updateGoal(goalToMove.id, { - parentGoalId: newParentGoalId, - participants: updatedGoal.participants, - }), - removeGoalFromParentSublist(goalToMove.id, oldParentId), - addGoalToNewParentSublist(goalToMove.id, newParentGoalId), - updateRootGoal(goalToMove.id, newParentGoal?.rootGoalId ?? "root"), - ]); - - const descendants = await getAllDescendants(goalId); - await Promise.all( - descendants.map((descendant) => - updateGoal(descendant.id, { + const isMovingToSharedParent = newParentGoal?.participants?.some((p) => p.following); + + if (isMovingToSharedParent) { + await createSharedGoal(updatedGoal, newParentGoalId, ancestorGoalIdsOfNewParent); + } else { + const subscribers = await getParticipantsOfGoals(ancestorGoalIds); + + try { + await Promise.all( + subscribers.map(({ sub, rootGoalId }) => + sendUpdatesToSubscriber(sub, rootGoalId, "moved", [ + { + level: ancestorGoalIds.length, + goal: { + ...updatedGoal, + parentGoalId: newParentGoalId, + rootGoalId, + }, + }, + ]), + ), + ); + } catch (error) { + console.error("[moveGoalHierarchy] Error sending move updates:", error); + } + } + + try { + await Promise.all([ + updateGoal(goalToMove.id, { + parentGoalId: newParentGoalId, participants: updatedGoal.participants, - rootGoalId: newParentGoal?.rootGoalId ?? "root", }), - ), - ); - - const subscribers = await getParticipantsOfGoals(ancestorGoalIds); - - subscribers.forEach(async ({ sub, rootGoalId }) => { - await sendUpdatesToSubscriber(sub, rootGoalId, "moved", [ - { - level: ancestorGoalIds.length, - goal: { - ...updatedGoal, - parentGoalId: newParentGoalId, - rootGoalId, - }, - }, + removeGoalFromParentSublist(goalToMove.id, oldParentId), + addGoalToNewParentSublist(goalToMove.id, newParentGoalId), + updateRootGoal(goalToMove.id, newParentGoal?.rootGoalId ?? "root"), ]); - }); - if (descendants.length > 0) { - subscribers.forEach(async ({ sub, rootGoalId }) => { - await sendUpdatesToSubscriber( - sub, - rootGoalId, - "moved", - descendants.map((descendant) => ({ - level: ancestorGoalIds.length + 1, - goal: { - ...descendant, + const descendants = await getAllDescendants(goalId); + if (descendants.length > 0) { + await Promise.all( + descendants.map((descendant) => + updateGoal(descendant.id, { participants: updatedGoal.participants, - rootGoalId, - }, - })), + rootGoalId: newParentGoal?.rootGoalId ?? "root", + }), + ), ); - }); + } + } catch (error) { + console.error("[moveGoalHierarchy] Error updating goal relationships:", error); } + + console.log("[moveGoalHierarchy] Successfully completed goal hierarchy move"); }; From fb22e87c390fbea22279208e8a4d7c8807339e79 Mon Sep 17 00:00:00 2001 From: Vinay Badgujar Date: Sat, 23 Nov 2024 12:00:57 +0530 Subject: [PATCH 48/54] fix: backticks not being removed for code snippet --- src/helpers/GoalController.ts | 52 ++++++++++++++++++++++------------- 1 file changed, 33 insertions(+), 19 deletions(-) diff --git a/src/helpers/GoalController.ts b/src/helpers/GoalController.ts index c68d9f2b8..d6b633d03 100644 --- a/src/helpers/GoalController.ts +++ b/src/helpers/GoalController.ts @@ -124,30 +124,38 @@ export const createSharedGoal = async (newGoal: GoalItem, parentGoalId: string, const newGoalId = newGoal.id; const subscribers = await getParticipantsOfGoals(ancestors); + const descendants = await getAllDescendants(newGoalId); if (newGoalId) { try { - await Promise.all( - subscribers.map(async ({ sub, rootGoalId }) => { - await sendUpdatesToSubscriber(sub, rootGoalId, "newGoalMoved", [ + const subscriberUpdates = new Map< + string, + { + sub: IParticipant; + rootGoalId: string; + updates: Array<{ level: number; goal: Omit }>; + } + >(); + + subscribers.forEach(({ sub, rootGoalId }) => { + subscriberUpdates.set(sub.relId, { + sub, + rootGoalId, + updates: [ { level, goal: { ...newGoal, id: newGoalId, parentGoalId }, }, - ]); - }), - ); - - const descendants = await getAllDescendants(newGoalId); + ], + }); + }); if (descendants.length > 0) { - await Promise.all( - subscribers.map(async ({ sub, rootGoalId }) => { - await sendUpdatesToSubscriber( - sub, - rootGoalId, - "newGoalMoved", - descendants.map((descendant) => ({ + subscribers.forEach(({ sub, rootGoalId }) => { + const subscriberUpdate = subscriberUpdates.get(sub.relId); + if (subscriberUpdate) { + subscriberUpdate.updates.push( + ...descendants.map((descendant) => ({ level: level + 1, goal: { ...descendant, @@ -155,9 +163,15 @@ export const createSharedGoal = async (newGoal: GoalItem, parentGoalId: string, }, })), ); - }), - ); + } + }); } + + await Promise.all( + Array.from(subscriberUpdates.values()).map(({ sub, rootGoalId, updates }) => + sendUpdatesToSubscriber(sub, rootGoalId, "newGoalMoved", updates), + ), + ); } catch (error) { console.error("[createSharedGoal] Error sending updates:", error); } @@ -324,9 +338,9 @@ export const moveGoalHierarchy = async (goalId: string, newParentGoalId: string) participants: Array.from(allParticipants.values()), }; - const isMovingToSharedParent = newParentGoal?.participants?.some((p) => p.following); + const isNonSharedGoal = !goalToMove?.participants?.some((p) => p.following); - if (isMovingToSharedParent) { + if (isNonSharedGoal) { await createSharedGoal(updatedGoal, newParentGoalId, ancestorGoalIdsOfNewParent); } else { const subscribers = await getParticipantsOfGoals(ancestorGoalIds); From 3b311279c8220d948c5a1b6867ea513ae6c0d1ba Mon Sep 17 00:00:00 2001 From: Vinay Badgujar Date: Sun, 24 Nov 2024 12:14:31 +0530 Subject: [PATCH 49/54] fix: sublist array not updating correctly while moving goals --- .../DisplayChangesModal.tsx | 9 +-- src/helpers/GoalController.ts | 3 + src/helpers/InboxProcessor.ts | 58 +++++++++++++------ 3 files changed, 49 insertions(+), 21 deletions(-) diff --git a/src/components/GoalsComponents/DisplayChangesModal/DisplayChangesModal.tsx b/src/components/GoalsComponents/DisplayChangesModal/DisplayChangesModal.tsx index cdc7844af..36a7a125a 100644 --- a/src/components/GoalsComponents/DisplayChangesModal/DisplayChangesModal.tsx +++ b/src/components/GoalsComponents/DisplayChangesModal/DisplayChangesModal.tsx @@ -172,12 +172,13 @@ const DisplayChangesModal = ({ currentMainGoal }: { currentMainGoal: GoalItem }) console.log("No goal under review."); return; } - const parentGoal = await getGoal(goalUnderReview.parentGoalId); + const localGoal = await getGoal(goalUnderReview.id); + const localParentGoalId = localGoal?.parentGoalId ?? "root"; await Promise.all([ - updateGoal(goalUnderReview.id, { parentGoalId: parentGoal?.id ?? "root" }), - removeGoalFromParentSublist(goalUnderReview.id, parentGoal?.title ?? "root"), - addGoalToNewParentSublist(goalUnderReview.id, parentGoal?.id ?? "root"), + updateGoal(goalUnderReview.id, { parentGoalId: goalUnderReview.parentGoalId }), + removeGoalFromParentSublist(goalUnderReview.id, localParentGoalId), + addGoalToNewParentSublist(goalUnderReview.id, goalUnderReview.parentGoalId), ]); await sendUpdatedGoal( diff --git a/src/helpers/GoalController.ts b/src/helpers/GoalController.ts index d6b633d03..a5f49afb2 100644 --- a/src/helpers/GoalController.ts +++ b/src/helpers/GoalController.ts @@ -277,6 +277,9 @@ export const addGoalToNewParentSublist = async (goalId: string, newParentGoalId: const newParentGoal = await getGoal(newParentGoalId); if (!newParentGoal) return; + const isGoalAlreadyInSublist = newParentGoal.sublist.includes(goalId); + if (isGoalAlreadyInSublist) return; + const newParentGoalSublist = newParentGoal.sublist; newParentGoalSublist.push(goalId); await updateGoal(newParentGoal.id, { sublist: newParentGoalSublist }); diff --git a/src/helpers/InboxProcessor.ts b/src/helpers/InboxProcessor.ts index c90ce306c..b2df83c69 100644 --- a/src/helpers/InboxProcessor.ts +++ b/src/helpers/InboxProcessor.ts @@ -102,7 +102,13 @@ const handleSubgoalsChanges = async ( type: string, ) => { const rootGoal = await getGoal(rootGoalId); - if (!rootGoal || !rootGoal.participants.find((p) => p.relId === relId && p.following)) { + if (!rootGoal) { + console.warn("[handleSubgoalsChanges] Root goal not found:", rootGoalId); + return; + } + + if (!rootGoal.participants.find((p) => p.relId === relId && p.following)) { + console.warn("[handleSubgoalsChanges] Participant not found or not following:", relId); return; } @@ -117,16 +123,16 @@ const handleSubgoalsChanges = async ( })); const inbox = await getInboxItem(rootGoalId); + if (!inbox) { + await createEmptyInboxItem(rootGoalId); + } + const defaultChanges = getDefaultValueOfGoalChanges(); (defaultChanges[changeType] as changesInGoal[]) = goalsWithParticipants.map((ele) => ({ ...ele, intent: type as typeOfIntent, })); - if (!inbox) { - await createEmptyInboxItem(rootGoalId); - } - await addChangesToRootGoal(rootGoalId, relId, defaultChanges); }; @@ -256,21 +262,39 @@ export const handleIncomingChanges = async (payload: Payload, relId: string) => export const acceptSelectedSubgoals = async (selectedGoals: GoalItem[], parentGoal: GoalItem) => { try { + const currentSublist = parentGoal.sublist; const childrens: string[] = []; - selectedGoals.forEach(async (goal: GoalItem) => { - const { relId } = goal.participants[0]; - const contact = await getContactByRelId(relId); - addGoal( - fixDateVlauesInGoalObject({ - ...goal, - participants: [{ relId, following: true, type: "sharer", name: contact?.name || "" }], - }), - ).catch((err) => console.log(err)); - childrens.push(goal.id); - }); - await addIntoSublist(parentGoal.id, childrens); + + await Promise.all( + selectedGoals.map(async (goal) => { + if (currentSublist.includes(goal.id)) { + console.log(`[acceptSelectedSubgoals] Goal ID ${goal.id} is already in the sublist, skipping.`); + return; + } + + const { relId } = goal.participants[0]; + const contact = await getContactByRelId(relId); + try { + await addGoal( + fixDateVlauesInGoalObject({ + ...goal, + participants: [{ relId, following: true, type: "sharer", name: contact?.name || "" }], + }), + ); + childrens.push(goal.id); + } catch (err) { + console.log(`[acceptSelectedSubgoals] Error adding goal ID ${goal.id}:`, err); + } + }), + ); + + if (childrens.length > 0) { + await addIntoSublist(parentGoal.id, childrens); + } + return "Done!!"; } catch (err) { + console.error("[acceptSelectedSubgoals] Failed to add changes:", err); return "Failed To add Changes"; } }; From 5614dc23eea14a36bf178a6d0776f6b7094e3432 Mon Sep 17 00:00:00 2001 From: Vinay Badgujar Date: Sun, 24 Nov 2024 12:20:17 +0530 Subject: [PATCH 50/54] fix: typo --- src/models/db.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/models/db.ts b/src/models/db.ts index d4c506531..96bf8833e 100644 --- a/src/models/db.ts +++ b/src/models/db.ts @@ -49,9 +49,9 @@ export class ZinZenDB extends Dexie { console.log("🚀 ~ file: db.ts:63 ~ ZinZenDB ~ .upgrade ~ this.verno:", currentVersion); syncVersion(db, currentVersion); }); - this.goalsCollection.hook("updating", (modfications: GoalItem) => { - modfications.timestamp = Date.now(); - return modfications; + this.goalsCollection.hook("updating", (modifications: GoalItem) => { + modifications.timestamp = Date.now(); + return modifications; }); this.goalsCollection.hook("creating", (primKey, obj) => { From f942c2ce6badac7b14b6aa154a2bd566e357fbc9 Mon Sep 17 00:00:00 2001 From: Vinay Badgujar Date: Sun, 24 Nov 2024 12:31:36 +0530 Subject: [PATCH 51/54] improve changes modal heading title for new goal moved action --- .../DisplayChangesModal/DisplayChangesModal.tsx | 1 + .../GoalsComponents/DisplayChangesModal/Header.tsx | 2 +- src/helpers/GoalController.ts | 8 ++++++-- src/hooks/useGoalActions.tsx | 7 +------ 4 files changed, 9 insertions(+), 9 deletions(-) diff --git a/src/components/GoalsComponents/DisplayChangesModal/DisplayChangesModal.tsx b/src/components/GoalsComponents/DisplayChangesModal/DisplayChangesModal.tsx index 36a7a125a..30faf243a 100644 --- a/src/components/GoalsComponents/DisplayChangesModal/DisplayChangesModal.tsx +++ b/src/components/GoalsComponents/DisplayChangesModal/DisplayChangesModal.tsx @@ -347,6 +347,7 @@ const DisplayChangesModal = ({ currentMainGoal }: { currentMainGoal: GoalItem }) contactName={participants[activePPT].name} title={goalUnderReview.title} currentDisplay={currentDisplay} + newParentTitle={newParentTitle} />

    )} diff --git a/src/components/GoalsComponents/DisplayChangesModal/Header.tsx b/src/components/GoalsComponents/DisplayChangesModal/Header.tsx index 6133809cb..b71a6f446 100644 --- a/src/components/GoalsComponents/DisplayChangesModal/Header.tsx +++ b/src/components/GoalsComponents/DisplayChangesModal/Header.tsx @@ -21,7 +21,7 @@ const Header = ({ case "newGoalMoved": return ( <> - {contactName} moved {title} to {title}.    Move as well ? + {contactName} moved new goals to {title}.  Move as well ? ); case "modifiedGoals": diff --git a/src/helpers/GoalController.ts b/src/helpers/GoalController.ts index a5f49afb2..c5185fd63 100644 --- a/src/helpers/GoalController.ts +++ b/src/helpers/GoalController.ts @@ -113,7 +113,11 @@ export const getAllDescendants = async (goalId: string): Promise => return descendants; }; -export const createSharedGoal = async (newGoal: GoalItem, parentGoalId: string, ancestors: string[]) => { +export const createSharedGoalObjectForSending = async ( + newGoal: GoalItem, + parentGoalId: string, + ancestors: string[], +) => { const level = ancestors.length; if (parentGoalId && parentGoalId !== "root") { @@ -344,7 +348,7 @@ export const moveGoalHierarchy = async (goalId: string, newParentGoalId: string) const isNonSharedGoal = !goalToMove?.participants?.some((p) => p.following); if (isNonSharedGoal) { - await createSharedGoal(updatedGoal, newParentGoalId, ancestorGoalIdsOfNewParent); + await createSharedGoalObjectForSending(updatedGoal, newParentGoalId, ancestorGoalIdsOfNewParent); } else { const subscribers = await getParticipantsOfGoals(ancestorGoalIds); diff --git a/src/hooks/useGoalActions.tsx b/src/hooks/useGoalActions.tsx index af1c0bd65..fe42956a2 100644 --- a/src/hooks/useGoalActions.tsx +++ b/src/hooks/useGoalActions.tsx @@ -1,7 +1,7 @@ import { getAllLevelGoalsOfId, unarchiveUserGoal, updateSharedStatusOfGoal } from "@src/api/GoalsAPI"; import { getSharedWMGoalById } from "@src/api/SharedWMAPI"; import { restoreUserGoal } from "@src/api/TrashAPI"; -import { createGoal, createSharedGoal, deleteGoal, deleteSharedGoal, modifyGoal } from "@src/helpers/GoalController"; +import { createGoal, deleteGoal, deleteSharedGoal, modifyGoal } from "@src/helpers/GoalController"; import { suggestChanges, suggestNewGoal } from "@src/helpers/PartnerController"; import { GoalItem } from "@src/models/GoalItem"; import { displayToast, lastAction, openDevMode } from "@src/store"; @@ -119,10 +119,6 @@ const useGoalActions = () => { } }; - const addSharedGoal = async (newGoal: GoalItem, parentGoalId: string) => { - await createSharedGoal(newGoal, parentGoalId, ancestors); - }; - const shareGoalWithRelId = async (relId: string, name: string, goal: GoalItem) => { const goalWithChildrens = await getAllLevelGoalsOfId(goal.id, true); @@ -179,7 +175,6 @@ const useGoalActions = () => { shareGoalWithRelId, addContact, copyCode, - addSharedGoal, }; }; From 80a6fea379e54eeabda4bc75a62f7adc2d52df09 Mon Sep 17 00:00:00 2001 From: Vinay Badgujar Date: Sun, 24 Nov 2024 13:42:28 +0530 Subject: [PATCH 52/54] revert: bug causing code --- src/helpers/InboxProcessor.ts | 39 +++++++++++------------------------ 1 file changed, 12 insertions(+), 27 deletions(-) diff --git a/src/helpers/InboxProcessor.ts b/src/helpers/InboxProcessor.ts index b2df83c69..33968160a 100644 --- a/src/helpers/InboxProcessor.ts +++ b/src/helpers/InboxProcessor.ts @@ -262,35 +262,20 @@ export const handleIncomingChanges = async (payload: Payload, relId: string) => export const acceptSelectedSubgoals = async (selectedGoals: GoalItem[], parentGoal: GoalItem) => { try { - const currentSublist = parentGoal.sublist; const childrens: string[] = []; - await Promise.all( - selectedGoals.map(async (goal) => { - if (currentSublist.includes(goal.id)) { - console.log(`[acceptSelectedSubgoals] Goal ID ${goal.id} is already in the sublist, skipping.`); - return; - } - - const { relId } = goal.participants[0]; - const contact = await getContactByRelId(relId); - try { - await addGoal( - fixDateVlauesInGoalObject({ - ...goal, - participants: [{ relId, following: true, type: "sharer", name: contact?.name || "" }], - }), - ); - childrens.push(goal.id); - } catch (err) { - console.log(`[acceptSelectedSubgoals] Error adding goal ID ${goal.id}:`, err); - } - }), - ); - - if (childrens.length > 0) { - await addIntoSublist(parentGoal.id, childrens); - } + selectedGoals.forEach(async (goal: GoalItem) => { + const { relId } = goal.participants[0]; + const contact = await getContactByRelId(relId); + addGoal( + fixDateVlauesInGoalObject({ + ...goal, + participants: [{ relId, following: true, type: "sharer", name: contact?.name || "" }], + }), + ).catch((err) => console.log(err)); + childrens.push(goal.id); + }); + await addIntoSublist(parentGoal.id, childrens); return "Done!!"; } catch (err) { From 88b9cce842c4231036fc8f9eb3fa64f82dc19c55 Mon Sep 17 00:00:00 2001 From: Vinay Badgujar Date: Sun, 24 Nov 2024 13:52:52 +0530 Subject: [PATCH 53/54] disable move button when processing move operation --- src/components/GlobalAddBtn.tsx | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/src/components/GlobalAddBtn.tsx b/src/components/GlobalAddBtn.tsx index b3083d16a..da759266d 100644 --- a/src/components/GlobalAddBtn.tsx +++ b/src/components/GlobalAddBtn.tsx @@ -1,4 +1,4 @@ -import React, { ReactNode, useEffect } from "react"; +import React, { ReactNode, useEffect, useState } from "react"; import { useRecoilState, useRecoilValue, useSetRecoilState } from "recoil"; import { useTranslation } from "react-i18next"; import { useLocation, useNavigate, useParams, useSearchParams } from "react-router-dom"; @@ -69,6 +69,7 @@ const GlobalAddBtn = ({ add }: { add: string }) => { const themeSelection = useRecoilValue(themeSelectionMode); const isAddingBudgetGoalAllowed = useRecoilValue(allowAddingBudgetGoal); const [goalToMove, setGoalToMove] = useRecoilState(moveGoalState); + const [isDisabled, setIsDisabled] = useState(false); const enterPressed = useKeyPress("Enter"); const plusPressed = useKeyPress("+"); @@ -124,11 +125,13 @@ const GlobalAddBtn = ({ add }: { add: string }) => { const handleMoveGoalHere = async () => { if (!goalToMove) return; + setIsDisabled(true); await moveGoalHierarchy(goalToMove.id, parentId).then(() => { setToastMessage({ open: true, message: "Goal moved successfully", extra: "" }); }); setLastAction("goalMoved"); setGoalToMove(null); + setIsDisabled(false); window.history.back(); }; @@ -154,7 +157,11 @@ const GlobalAddBtn = ({ add }: { add: string }) => { /> {goalToMove ? ( <> - + {t("Move here")} Date: Sun, 24 Nov 2024 14:18:17 +0530 Subject: [PATCH 54/54] fix: duplicate new add subgoal request sent to collaborator --- src/helpers/GoalController.ts | 48 ++++++++++++++++++++++++++--------- 1 file changed, 36 insertions(+), 12 deletions(-) diff --git a/src/helpers/GoalController.ts b/src/helpers/GoalController.ts index c5185fd63..f625c8e0a 100644 --- a/src/helpers/GoalController.ts +++ b/src/helpers/GoalController.ts @@ -55,20 +55,44 @@ export const createGoal = async (newGoal: GoalItem, parentGoalId: string, ancest const newGoalId = await addGoal(updatedGoal); if (newGoalId) { + const subscriberUpdates = new Map< + string, + { + sub: IParticipant; + rootGoalId: string; + updates: Array<{ level: number; goal: Omit }>; + } + >(); + const subscribers = await getParticipantsOfGoals(ancestors); + + const uniqueSubscribers = new Map(); + + subscribers.forEach(({ sub, rootGoalId }) => { + if (!uniqueSubscribers.has(sub.relId)) { + uniqueSubscribers.set(sub.relId, { sub, rootGoalId }); + } + }); + + uniqueSubscribers.forEach(({ sub, rootGoalId }) => { + if (!subscriberUpdates.has(sub.relId)) { + subscriberUpdates.set(sub.relId, { sub, rootGoalId, updates: [] }); + } + const subscriberUpdate = subscriberUpdates.get(sub.relId); + subscriberUpdate?.updates.push({ + level, + goal: { + ...updatedGoal, + id: newGoalId, + rootGoalId, + }, + }); + }); + await Promise.all( - subscribers.map(async ({ sub, rootGoalId }) => { - await sendUpdatesToSubscriber(sub, rootGoalId, "subgoals", [ - { - level, - goal: { - ...updatedGoal, - id: newGoalId, - rootGoalId, - }, - }, - ]); - }), + Array.from(subscriberUpdates.values()).map(({ sub, rootGoalId, updates }) => + sendUpdatesToSubscriber(sub, rootGoalId, "subgoals", updates), + ), ); }