From f20e9c6bdb4262f1ed2d3e3221905679a3ce302d Mon Sep 17 00:00:00 2001
From: Joel Jeremy Marquez <joeljeremy.marquez@gmail.com>
Date: Tue, 7 May 2024 09:48:27 -0700
Subject: [PATCH] [Mobile] Schedule name and date in mobile scheduled
 transaction modal (#2664)

* [Mobile] Add schedule name and date to scheduled transaction modal

* useRef to prevent rerenders

* Release notes

* Fix typecheck error

* Fix lint errors

* Update schedule font

* Heavier font weight
---
 .../src/components/NotesButton.tsx            |   2 +-
 .../src/components/common/Modal.tsx           | 110 ++++++++++++------
 .../src/components/common/Tooltip.tsx         |  33 +++---
 .../modals/ScheduledTransactionMenuModal.tsx  |  39 ++++++-
 upcoming-release-notes/2664.md                |   6 +
 5 files changed, 132 insertions(+), 58 deletions(-)
 create mode 100644 upcoming-release-notes/2664.md

diff --git a/packages/desktop-client/src/components/NotesButton.tsx b/packages/desktop-client/src/components/NotesButton.tsx
index bbd42b33c..901442b8c 100644
--- a/packages/desktop-client/src/components/NotesButton.tsx
+++ b/packages/desktop-client/src/components/NotesButton.tsx
@@ -66,7 +66,7 @@ export function NotesButton({
             setIsOpen(true);
           }}
         >
-          <SvgCustomNotesPaper style={{ width, height }} />
+          <SvgCustomNotesPaper style={{ width, height, flexShrink: 0 }} />
         </Button>
       </View>
 
diff --git a/packages/desktop-client/src/components/common/Modal.tsx b/packages/desktop-client/src/components/common/Modal.tsx
index ddd498717..64ec91e6d 100644
--- a/packages/desktop-client/src/components/common/Modal.tsx
+++ b/packages/desktop-client/src/components/common/Modal.tsx
@@ -12,15 +12,20 @@ import React, {
 import { useHotkeysContext } from 'react-hotkeys-hook';
 import ReactModal from 'react-modal';
 
+import { useHover } from 'usehooks-ts';
+
+import { useMergedRefs } from '../../hooks/useMergedRefs';
 import { AnimatedLoading } from '../../icons/AnimatedLoading';
 import { SvgLogo } from '../../icons/logo';
 import { SvgDelete } from '../../icons/v0';
+import { SvgClose } from '../../icons/v1';
 import { type CSSProperties, styles, theme } from '../../style';
 import { tokens } from '../../tokens';
 
 import { Button } from './Button';
 import { Input } from './Input';
 import { Text } from './Text';
+import { Tooltip } from './Tooltip';
 import { View } from './View';
 
 export type ModalProps = {
@@ -372,14 +377,42 @@ export function ModalTitle({
   shrinkOnOverflow = false,
 }: ModalTitleProps) {
   const [isEditing, setIsEditing] = useState(false);
-  const style = getStyle?.(isEditing);
+  const tooltipTriggerRef = useRef();
 
-  const _onEdit = () => {
-    if (!isEditable) {
-      return;
+  // Dynamic font size to avoid ellipsis.
+  const textRef = useRef<HTMLSpanElement>();
+  const [textOverflowed, setTextOverflowed] = useState(false);
+  const [textFontSize, setTextFontSize] = useState(25);
+
+  useEffect(() => {
+    const containerWidth = textRef.current.offsetWidth;
+    const textWidth = textRef.current.scrollWidth;
+
+    if (textWidth > containerWidth) {
+      setTextOverflowed(true);
+    } else {
+      setTextOverflowed(false);
     }
+  }, [title]);
 
-    setIsEditing(true);
+  useEffect(() => {
+    if (textOverflowed && shrinkOnOverflow) {
+      const containerWidth = textRef.current.offsetWidth;
+      const textWidth = textRef.current.scrollWidth;
+      const newFontSize = Math.floor(
+        (containerWidth / textWidth) * textFontSize,
+      );
+      setTextFontSize(newFontSize);
+    }
+  }, [title, textFontSize, shrinkOnOverflow, textOverflowed]);
+
+  const mergedTextRef = useMergedRefs(textRef, tooltipTriggerRef);
+  const isHovered = useHover(tooltipTriggerRef);
+
+  const _onEdit = () => {
+    if (isEditable) {
+      setIsEditing(true);
+    }
   };
 
   const _onTitleUpdate = newTitle => {
@@ -398,22 +431,7 @@ export function ModalTitle({
     }
   }, [isEditing]);
 
-  // Dynamic font size to avoid ellipsis.
-  const textRef = useRef<HTMLSpanElement>();
-  const [textFontSize, setTextFontSize] = useState(25);
-  useEffect(() => {
-    if (shrinkOnOverflow) {
-      const containerWidth = textRef.current.offsetWidth;
-      const textWidth = textRef.current.scrollWidth;
-
-      if (textWidth > containerWidth) {
-        const newFontSize = Math.floor(
-          (containerWidth / textWidth) * textFontSize,
-        );
-        setTextFontSize(newFontSize);
-      }
-    }
-  }, [textFontSize, shrinkOnOverflow]);
+  const style = getStyle?.(isEditing);
 
   return isEditing ? (
     <Input
@@ -435,22 +453,42 @@ export function ModalTitle({
       }}
     />
   ) : (
-    <Text
-      innerRef={textRef}
-      style={{
-        fontSize: textFontSize,
-        fontWeight: 700,
-        textAlign: 'center',
-        whiteSpace: 'nowrap',
-        overflow: 'hidden',
-        textOverflow: 'ellipsis',
-        ...(isEditable && styles.underlinedText),
-        ...style,
-      }}
-      onClick={_onEdit}
+    <Tooltip
+      content={
+        <View
+          style={{
+            flexDirection: 'row',
+            alignItems: 'center',
+            gap: 10,
+            maxWidth: '90vw',
+          }}
+        >
+          <SvgClose style={{ width: 10, height: 10, flexShrink: 0 }} />
+          <Text style={styles.mediumText}>{title}</Text>
+        </View>
+      }
+      placement="top"
+      triggerRef={tooltipTriggerRef}
+      isOpen={textOverflowed && isHovered}
+      offset={10}
     >
-      {title}
-    </Text>
+      <Text
+        innerRef={mergedTextRef}
+        style={{
+          fontSize: textFontSize,
+          fontWeight: 700,
+          textAlign: 'center',
+          whiteSpace: 'nowrap',
+          overflow: 'hidden',
+          textOverflow: 'ellipsis',
+          ...(isEditable && styles.underlinedText),
+          ...style,
+        }}
+        onClick={_onEdit}
+      >
+        {title}
+      </Text>
+    </Tooltip>
   );
 }
 
diff --git a/packages/desktop-client/src/components/common/Tooltip.tsx b/packages/desktop-client/src/components/common/Tooltip.tsx
index 050741284..4b9f68c47 100644
--- a/packages/desktop-client/src/components/common/Tooltip.tsx
+++ b/packages/desktop-client/src/components/common/Tooltip.tsx
@@ -25,44 +25,39 @@ export const Tooltip = ({
   ...props
 }: TooltipProps) => {
   const triggerRef = useRef(null);
-  const [hover, setHover] = useState(false);
+  const [isHovered, setIsHover] = useState(false);
 
-  const [delayHandler, setDelayHandler] = useState<ReturnType<
-    typeof setTimeout
-  > | null>(null);
+  const hoverTimeoutRef = useRef<ReturnType<typeof setTimeout>>();
 
-  const handleMouseEnter = useCallback(() => {
+  const handlePointerEnter = useCallback(() => {
     const timeout = setTimeout(() => {
-      setHover(true);
+      setIsHover(true);
     }, triggerProps.delay ?? 300);
 
-    setDelayHandler(timeout);
-    return () => {
-      clearTimeout(timeout);
-    };
+    hoverTimeoutRef.current = timeout;
   }, [triggerProps.delay]);
 
-  const handleMouseLeave = useCallback(() => {
-    if (delayHandler) {
-      clearTimeout(delayHandler);
+  const handlePointerLeave = useCallback(() => {
+    if (hoverTimeoutRef.current) {
+      clearTimeout(hoverTimeoutRef.current);
     }
 
-    setHover(false);
-  }, [delayHandler]);
+    setIsHover(false);
+  }, []);
 
   // Force closing the tooltip whenever the disablement state changes
   useEffect(() => {
-    setHover(false);
+    setIsHover(false);
   }, [triggerProps.isDisabled]);
 
   return (
     <View
       ref={triggerRef}
-      onMouseEnter={handleMouseEnter}
-      onMouseLeave={handleMouseLeave}
+      onPointerEnter={handlePointerEnter}
+      onPointerLeave={handlePointerLeave}
     >
       <TooltipTrigger
-        isOpen={hover && !triggerProps.isDisabled}
+        isOpen={isHovered && !triggerProps.isDisabled}
         {...triggerProps}
       >
         {children}
diff --git a/packages/desktop-client/src/components/modals/ScheduledTransactionMenuModal.tsx b/packages/desktop-client/src/components/modals/ScheduledTransactionMenuModal.tsx
index bac14eeb5..b0857d863 100644
--- a/packages/desktop-client/src/components/modals/ScheduledTransactionMenuModal.tsx
+++ b/packages/desktop-client/src/components/modals/ScheduledTransactionMenuModal.tsx
@@ -1,8 +1,14 @@
-import React, { type ComponentPropsWithoutRef } from 'react';
+import React, { useCallback, type ComponentPropsWithoutRef } from 'react';
+
+import { useSchedules } from 'loot-core/client/data-hooks/schedules';
+import { format } from 'loot-core/shared/months';
+import { type Query } from 'loot-core/shared/query';
 
 import { type CSSProperties, theme, styles } from '../../style';
 import { Menu } from '../common/Menu';
 import { Modal } from '../common/Modal';
+import { Text } from '../common/Text';
+import { View } from '../common/View';
 import { type CommonModalProps } from '../Modals';
 
 type ScheduledTransactionMenuModalProps = ScheduledTransactionMenuProps & {
@@ -21,9 +27,38 @@ export function ScheduledTransactionMenuModal({
     borderRadius: 0,
     borderTop: `1px solid ${theme.pillBorder}`,
   };
+  const scheduleId = transactionId?.split('/')?.[1];
+  const scheduleData = useSchedules({
+    transform: useCallback(
+      (q: Query) => q.filter({ id: scheduleId }),
+      [scheduleId],
+    ),
+  });
+  const schedule = scheduleData?.schedules?.[0];
+
+  if (!schedule) {
+    return;
+  }
 
   return (
-    <Modal showHeader focusAfterClose={false} {...modalProps}>
+    <Modal
+      title={schedule.name}
+      showHeader
+      focusAfterClose={false}
+      {...modalProps}
+    >
+      <View
+        style={{
+          justifyContent: 'center',
+          alignItems: 'center',
+          marginBottom: 20,
+        }}
+      >
+        <Text style={{ fontSize: 17, fontWeight: 400 }}>Scheduled date</Text>
+        <Text style={{ fontSize: 17, fontWeight: 700 }}>
+          {format(schedule.next_date, 'MMMM dd, yyyy')}
+        </Text>
+      </View>
       <ScheduledTransactionMenu
         transactionId={transactionId}
         onPost={onPost}
diff --git a/upcoming-release-notes/2664.md b/upcoming-release-notes/2664.md
new file mode 100644
index 000000000..623f2ba47
--- /dev/null
+++ b/upcoming-release-notes/2664.md
@@ -0,0 +1,6 @@
+---
+category: Enhancements
+authors: [joel-jeremy]
+---
+
+Add schedule name and date to mobile scheduled transaction modal.
\ No newline at end of file
-- 
GitLab