From 11cdfbcf26714351b5fd43ac2f95d30a7ae6de4b Mon Sep 17 00:00:00 2001
From: bobzel <zzzman@gmail.com>
Date: Thu, 5 Jun 2025 14:58:31 -0400
Subject: updated taskBox to use Cast methods and mobx instead of react state.
 Made TaskBox extend ViewBoxBaseComponent  to access
 this.Document/this.fieldKey, etc

---
 src/client/views/nodes/TaskBox.tsx | 164 +++++++++++++++++--------------------
 1 file changed, 75 insertions(+), 89 deletions(-)

(limited to 'src/client/views/nodes/TaskBox.tsx')

diff --git a/src/client/views/nodes/TaskBox.tsx b/src/client/views/nodes/TaskBox.tsx
index 82c22057e..168e1455e 100644
--- a/src/client/views/nodes/TaskBox.tsx
+++ b/src/client/views/nodes/TaskBox.tsx
@@ -1,30 +1,30 @@
-import { action, makeObservable, IReactionDisposer, reaction } from 'mobx';
+import { action, IReactionDisposer, makeObservable, observable, reaction, runInAction } from 'mobx';
 import { observer } from 'mobx-react';
 import * as React from 'react';
+import { DateField } from '../../../fields/DateField';
+import { BoolCast, DateCast, NumCast, StrCast } from '../../../fields/Types';
+import { GoogleAuthenticationManager } from '../../apis/GoogleAuthenticationManager';
 import { Docs } from '../../documents/Documents';
 import { DocumentType } from '../../documents/DocumentTypes';
-import { FieldView } from './FieldView';
-import { DateField } from '../../../fields/DateField';
-import { Doc } from '../../../fields/Doc';
-
+import { ViewBoxBaseComponent } from '../DocComponent';
+import { FieldView, FieldViewProps } from './FieldView';
 import './TaskBox.scss';
-import { GoogleAuthenticationManager } from '../../apis/GoogleAuthenticationManager';
-
-/**
- * Props (reference to document) for Task Box
- */
-
-interface TaskBoxProps {
-    Document: Doc;
-}
 
 /**
  * TaskBox class for adding task information + completing tasks
  */
 @observer
-export class TaskBox extends React.Component<TaskBoxProps> {
+export class TaskBox extends ViewBoxBaseComponent<FieldViewProps>() {
+    /**
+     * Return the JSX string that will create this component
+     * @param fieldStr the Doc field that contains the primary data for this component
+     * @returns
+     */
+    public static LayoutString(fieldStr: string) {
+        return FieldView.LayoutString(TaskBox, fieldStr);
+    }
     // contains the last synced task information
-    lastSyncedTask: {
+    private _lastSyncedTask: {
         title: string;
         text: string;
         due?: string;
@@ -36,18 +36,7 @@ export class TaskBox extends React.Component<TaskBoxProps> {
         completed: false,
     };
 
-    state = {
-        needsSync: false,
-    };
-
-    /**
-     * Method to reuturn the
-     * @param fieldStr
-     * @returns
-     */
-    public static LayoutString(fieldStr: string) {
-        return FieldView.LayoutString(TaskBox, fieldStr);
-    }
+    @observable _needsSync = false;
 
     /**
      * Method to update the task description
@@ -56,7 +45,7 @@ export class TaskBox extends React.Component<TaskBoxProps> {
 
     @action
     updateText = (e: React.ChangeEvent<HTMLInputElement | HTMLTextAreaElement>) => {
-        this.props.Document.text = e.target.value;
+        this.Document[this.fieldKey] = e.target.value;
     };
 
     /**
@@ -65,7 +54,7 @@ export class TaskBox extends React.Component<TaskBoxProps> {
      */
     @action
     updateTitle = (e: React.ChangeEvent<HTMLInputElement>) => {
-        this.props.Document.title = e.target.value;
+        this.Document.title = e.target.value;
     };
 
     /**
@@ -74,11 +63,11 @@ export class TaskBox extends React.Component<TaskBoxProps> {
      */
     @action
     updateAllDay = (e: React.ChangeEvent<HTMLInputElement>) => {
-        this.props.Document.$task_allDay = e.target.checked;
+        this.Document.$task_allDay = e.target.checked;
 
         if (e.target.checked) {
-            delete this.props.Document.$task_startTime;
-            delete this.props.Document.$task_endTime;
+            delete this.Document.$task_startTime;
+            delete this.Document.$task_endTime;
         }
 
         this.setTaskDateRange();
@@ -92,16 +81,16 @@ export class TaskBox extends React.Component<TaskBoxProps> {
     updateStart = (e: React.ChangeEvent<HTMLInputElement>) => {
         const newStart = new Date(e.target.value);
 
-        this.props.Document.$task_startTime = new DateField(newStart);
+        this.Document.$task_startTime = new DateField(newStart);
 
-        const endDate = this.props.Document.$task_endTime instanceof DateField ? this.props.Document.$task_endTime.date : undefined;
+        const endDate = this.Document.$task_endTime instanceof DateField ? this.Document.$task_endTime.date : undefined;
         if (endDate && newStart > endDate) {
             // Alert user
             alert('Start time cannot be after end time. End time has been adjusted.');
 
             // Fix end time
             const adjustedEnd = new Date(newStart.getTime() + 60 * 60 * 1000);
-            this.props.Document.$task_endTime = new DateField(adjustedEnd);
+            this.Document.$task_endTime = new DateField(adjustedEnd);
         }
 
         this.setTaskDateRange();
@@ -115,16 +104,16 @@ export class TaskBox extends React.Component<TaskBoxProps> {
     updateEnd = (e: React.ChangeEvent<HTMLInputElement>) => {
         const newEnd = new Date(e.target.value);
 
-        this.props.Document.$task_endTime = new DateField(newEnd);
+        this.Document.$task_endTime = new DateField(newEnd);
 
-        const startDate = this.props.Document.$task_startTime instanceof DateField ? this.props.Document.$task_startTime.date : undefined;
+        const startDate = this.Document.$task_startTime instanceof DateField ? this.Document.$task_startTime.date : undefined;
         if (startDate && newEnd < startDate) {
             // Alert user
             alert('End time cannot be before start time. Start time has been adjusted.');
 
             // Fix start time
             const adjustedStart = new Date(newEnd.getTime() - 60 * 60 * 1000);
-            this.props.Document.$task_startTime = new DateField(adjustedStart);
+            this.Document.$task_startTime = new DateField(adjustedStart);
         }
 
         this.setTaskDateRange();
@@ -135,10 +124,10 @@ export class TaskBox extends React.Component<TaskBoxProps> {
      */
     @action
     setTaskDateRange() {
-        const doc = this.props.Document;
+        const doc = this.Document;
 
         if (doc.$task_allDay) {
-            const range = typeof doc.$task_dateRange === 'string' ? doc.$task_dateRange.split('|') : [];
+            const range = StrCast(doc.$task_dateRange).split('|');
             const dateStr = range[0] ?? new Date().toISOString().split('T')[0]; // default to today
 
             doc.$task_dateRange = `${dateStr}|${dateStr}`;
@@ -163,7 +152,7 @@ export class TaskBox extends React.Component<TaskBoxProps> {
 
     @action
     toggleComplete = (e: React.ChangeEvent<HTMLInputElement>) => {
-        this.props.Document.$task_completed = e.target.checked;
+        this.Document.$task_completed = e.target.checked;
     };
 
     /**
@@ -182,7 +171,7 @@ export class TaskBox extends React.Component<TaskBoxProps> {
 
     componentDidMount() {
         this.setTaskDateRange();
-        const doc = this.props.Document;
+        const doc = this.Document;
 
         // adding task on creation to google
 
@@ -192,15 +181,15 @@ export class TaskBox extends React.Component<TaskBoxProps> {
                     const token = await GoogleAuthenticationManager.Instance.fetchOrGenerateAccessToken();
                     if (!token) return;
 
-                    const body: any = {
-                        title: doc.title || 'Untitled Task',
-                        notes: doc.text || '',
+                    const body: { title: string; notes: string; status: string; completed?: string; due?: string } = {
+                        title: StrCast(doc.title, 'Untitled Task'),
+                        notes: StrCast(doc[this.fieldKey]),
                         status: doc.$task_completed ? 'completed' : 'needsAction',
                         completed: doc.$task_completed ? new Date().toISOString() : undefined,
                     };
 
-                    if (doc.$task_allDay && typeof doc.$task_dateRange === 'string') {
-                        const datePart = doc.$task_dateRange.split('|')[0];
+                    const datePart = StrCast(doc.$task_dateRange).split('|')[0];
+                    if (doc.$task_allDay && datePart) {
                         if (datePart && !isNaN(new Date(datePart).getTime())) {
                             const baseDate = datePart.includes('T') ? datePart : datePart + 'T00:00:00Z';
                             body.due = new Date(baseDate).toISOString();
@@ -233,20 +222,20 @@ export class TaskBox extends React.Component<TaskBoxProps> {
         })();
 
         this._heightDisposer = reaction(
-            () => Number(doc._height),
+            () => NumCast(doc._height),
             height => {
-                const minHeight = Number(doc.height_min ?? 0);
-                if (!isNaN(height) && height < minHeight) {
+                const minHeight = NumCast(doc.height_min);
+                if (height < minHeight) {
                     doc._height = minHeight;
                 }
             }
         );
 
         this._widthDisposer = reaction(
-            () => Number(doc._width),
+            () => NumCast(doc._width),
             width => {
-                const minWidth = Number(doc.width_min ?? 0);
-                if (!isNaN(width) && width < minWidth) {
+                const minWidth = NumCast(doc.width_min);
+                if (width < minWidth) {
                     doc._width = minWidth;
                 }
             }
@@ -254,35 +243,34 @@ export class TaskBox extends React.Component<TaskBoxProps> {
 
         this._googleTaskCreateDisposer = reaction(
             () => {
-                const { title, text, $task_completed, $task_dateRange, $task_startTime, $task_endTime, $task_allDay } = doc;
-
-                const completed = !!$task_completed;
-                let due: string | undefined;
-
-                if ($task_allDay && typeof $task_dateRange === 'string') {
-                    const datePart = $task_dateRange.split('|')[0];
-                    if (datePart && !isNaN(new Date(datePart).getTime())) {
-                        due = new Date(datePart + 'T00:00:00Z').toISOString();
-                    }
-                } else if ($task_endTime && $task_endTime instanceof DateField && $task_endTime.date) {
-                    due = $task_endTime.date.toISOString();
-                } else if ($task_startTime && $task_startTime instanceof DateField && $task_startTime.date) {
-                    due = $task_startTime.date.toISOString();
-                }
-
-                return { title, text, completed, due };
+                const completed = BoolCast(doc.$task_completed);
+                const $task_allDay = BoolCast(doc.$task_allDay);
+                const endTime = DateCast(doc.$task_endTime);
+                const startTime = DateCast(doc.$task_startTime);
+                const datePart = StrCast(doc.$task_dateRange)?.split('|')[0];
+
+                const due = (() => {
+                    if ($task_allDay && datePart) {
+                        if (!isNaN(new Date(datePart).getTime())) return new Date(datePart).toISOString();
+                    } else if (endTime && !isNaN(+endTime.date)) return endTime.date.toISOString();
+                    else if (startTime && !isNaN(+startTime.date)) return startTime.date.toISOString();
+                })();
+
+                return { title: StrCast(doc.title), text: StrCast(doc[this.fieldKey]), completed, due };
             },
-            current => {
-                const hasChanged = current.title !== this.lastSyncedTask.title || current.text !== this.lastSyncedTask.text || current.due !== this.lastSyncedTask.due || current.completed !== this.lastSyncedTask.completed;
-
-                this.setState({ needsSync: hasChanged });
+            ({ title, text, completed, due }) => {
+                this._needsSync =
+                    title !== this._lastSyncedTask.title || //
+                    text !== this._lastSyncedTask.text ||
+                    due !== this._lastSyncedTask.due ||
+                    completed !== this._lastSyncedTask.completed;
             },
             { fireImmediately: true }
         );
     }
 
     componentWillUnmount() {
-        const doc = this.props.Document;
+        const doc = this.Document;
         this._googleTaskCreateDisposer?.();
         this._heightDisposer?.();
         this._widthDisposer?.();
@@ -321,15 +309,15 @@ export class TaskBox extends React.Component<TaskBoxProps> {
             return date.getFullYear() + '-' + pad(date.getMonth() + 1) + '-' + pad(date.getDate()) + 'T' + pad(date.getHours()) + ':' + pad(date.getMinutes());
         }
 
-        const doc = this.props.Document;
+        const doc = this.Document;
 
-        const taskDesc = typeof doc.text === 'string' ? doc.text : '';
-        const taskTitle = typeof doc.title === 'string' ? doc.title : '';
+        const taskDesc = StrCast(doc[this.fieldKey]);
+        const taskTitle = StrCast(doc.title);
         const allDay = !!doc.$task_allDay;
-        const isCompleted = !!this.props.Document.$task_completed;
+        const isCompleted = !!this.Document.$task_completed;
 
-        const startTime = doc.$task_startTime instanceof DateField && doc.$task_startTime.date instanceof Date ? toLocalDateTimeString(doc.$task_startTime.date) : '';
-        const endTime = doc.$task_endTime instanceof DateField && doc.$task_endTime.date instanceof Date ? toLocalDateTimeString(doc.$task_endTime.date) : '';
+        const startTime = DateCast(doc.$task_startTime) ? toLocalDateTimeString(DateCast(doc.$task_startTime)!.date) : '';
+        const endTime = DateCast(doc.$task_endTime) ? toLocalDateTimeString(DateCast(doc.$task_endTime)!.date) : '';
 
         const handleGoogleTaskSync = async () => {
             console.log('GT button clicked');
@@ -352,7 +340,7 @@ export class TaskBox extends React.Component<TaskBoxProps> {
                 let due: string | undefined;
 
                 if (allDay) {
-                    const rawRange = typeof doc.$task_dateRange === 'string' ? doc.$task_dateRange : '';
+                    const rawRange = StrCast(doc.$task_dateRange);
                     const datePart = rawRange.split('|')[0];
 
                     if (datePart && !isNaN(new Date(datePart).getTime())) {
@@ -396,13 +384,13 @@ export class TaskBox extends React.Component<TaskBoxProps> {
                 if (result?.id) {
                     alert('✅ Task synced with Google Tasks!');
                     if (result?.id) {
-                        this.lastSyncedTask = {
+                        this._lastSyncedTask = {
                             title: taskTitle,
                             text: taskDesc,
                             due,
                             completed: isCompleted,
                         };
-                        this.setState({ needsSync: false });
+                        runInAction(() => (this._needsSync = false));
                     }
                 } else {
                     alert(`❌ Failed: ${result?.error?.message || 'Unknown error'}`);
@@ -427,9 +415,7 @@ export class TaskBox extends React.Component<TaskBoxProps> {
                             <input
                                 type="date"
                                 value={(() => {
-                                    const rawRange = doc.$task_dateRange;
-                                    if (typeof rawRange !== 'string') return '';
-                                    const datePart = rawRange.split('|')[0];
+                                    const datePart = StrCast(doc.$task_dateRange).split('|')[0];
                                     if (!datePart) return '';
                                     const d = new Date(datePart);
                                     return !isNaN(d.getTime()) ? d.toISOString().split('T')[0] : '';
@@ -456,7 +442,7 @@ export class TaskBox extends React.Component<TaskBoxProps> {
 
                     <button
                         className="task-manager-google"
-                        disabled={!this.state.needsSync}
+                        disabled={!this._needsSync}
                         onClick={event => {
                             event.preventDefault();
                             handleGoogleTaskSync();
-- 
cgit v1.2.3-70-g09d2

