From 54b8c7e9ec258fd43ec523aaf3d967a646022cee Mon Sep 17 00:00:00 2001 From: aaravkumar Date: Wed, 30 Apr 2025 22:54:16 -0400 Subject: made document options as data doc options ($), and fixed namign conventions --- src/client/documents/Documents.ts | 16 +- src/client/views/Main.tsx | 4 +- src/client/views/nodes/TaskBox.scss | 72 +++++ src/client/views/nodes/TaskBox.tsx | 298 ++++++++++++++++++++ src/client/views/nodes/TaskManagerTask.scss | 72 ----- src/client/views/nodes/TaskManagerTask.tsx | 301 --------------------- src/client/views/nodes/calendarBox/CalendarBox.tsx | 22 +- 7 files changed, 389 insertions(+), 396 deletions(-) create mode 100644 src/client/views/nodes/TaskBox.scss create mode 100644 src/client/views/nodes/TaskBox.tsx delete mode 100644 src/client/views/nodes/TaskManagerTask.scss delete mode 100644 src/client/views/nodes/TaskManagerTask.tsx (limited to 'src') diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts index 217967c52..a11f56143 100644 --- a/src/client/documents/Documents.ts +++ b/src/client/documents/Documents.ts @@ -526,16 +526,12 @@ export class DocumentOptions { ai_firefly_seed?: number; ai_firefly_prompt?: string; - // AARAV ADD DOC OPTIONS -- TASK MANAGER - - /** Task start date/time picker (metadata and default) */ - startTime?: DateInfo | DateField = new DateInfo('start date and time', /*filterable*/ false); - /** Task end date/time picker (metadata and default) */ - endTime?: DateInfo | DateField = new DateInfo('end date and time', /*filterable*/ false); - /** Treat this as an all-day task (metadata and default) */ - allDay?: BoolInfo | boolean = new BoolInfo('all-day task', /*filterable*/ false); - /** Whether the task is completed */ - completed?: BoolInfo | boolean = new BoolInfo('whether the task is completed', /*filterable*/ false); + // TASK MANAGER + $startTime?: DateInfo | DateField = new DateInfo('start date and time', /*filterable*/ false); + $endTime?: DateInfo | DateField = new DateInfo('end date and time', /*filterable*/ false); + $allDay?: BoolInfo | boolean = new BoolInfo('whether task is all-day or not', /*filterable*/ false); + $completed?: BoolInfo | boolean = new BoolInfo('whether the task is completed', /*filterable*/ false); + /** * JSON‐stringified slot configuration for ScrapbookBox */ diff --git a/src/client/views/Main.tsx b/src/client/views/Main.tsx index ecbef3497..17ed63b05 100644 --- a/src/client/views/Main.tsx +++ b/src/client/views/Main.tsx @@ -65,7 +65,7 @@ import { PresBox, PresSlideBox } from './nodes/trails'; import { FaceRecognitionHandler } from './search/FaceRecognitionHandler'; import { SearchBox } from './search/SearchBox'; import { StickerPalette } from './smartdraw/StickerPalette'; -import { TaskManagerTask } from './nodes/TaskManagerTask'; +import { TaskBox } from './nodes/TaskBox'; import { ScrapbookBox } from './nodes/scrapbook/ScrapbookBox'; dotenv.config(); @@ -121,7 +121,7 @@ FieldLoader.ServerLoadStatus = { requested: 0, retrieved: 0, message: 'cache' }; StickerPalette: StickerPalette, FormattedTextBox, DailyJournal, // AARAV - TaskManagerTask, // AARAV + TaskBox, // AARAV ImageBox, FontIconBox, LabelBox, diff --git a/src/client/views/nodes/TaskBox.scss b/src/client/views/nodes/TaskBox.scss new file mode 100644 index 000000000..0fcc2f955 --- /dev/null +++ b/src/client/views/nodes/TaskBox.scss @@ -0,0 +1,72 @@ +.task-manager-container { + display: flex; + flex-direction: column; + padding: 8px; + gap: 10px; + width: 100%; + height: 100%; + box-sizing: border-box; +} + +.task-manager-title { + width: 100%; + font-size: 1.25rem; + font-weight: 600; + padding: 6px 10px; + border: 1px solid #ccc; + border-radius: 6px; + box-sizing: border-box; +} + +.task-manager-description { + width: 100%; + font-size: 1rem; + padding: 8px 10px; + border: 1px solid #ccc; + border-radius: 6px; + min-height: 40px; + box-sizing: border-box; + vertical-align: top; + text-align: start; + resize: none; + line-height: 1.4; + resize: none; + flex-grow: 1 +} + +.task-manager-checkboxes { + display: flex; + align-items: center; + gap: 16px; +} + +.task-manager-allday, .task-manager-complete { + display: flex; + align-items: center; + gap: 6px; + font-size: 0.95rem; +} + +.task-manager-times { + display: flex; + flex-direction: column; + gap: 6px; + width: 100%; +} + +.task-manager-times label { + display: flex; + flex-direction: column; + font-size: 0.9rem; + font-weight: 500; + gap: 4px; +} + +input[type="datetime-local"] { + width: 100%; + font-size: 0.9rem; + padding: 6px 8px; + border: 1px solid #ccc; + border-radius: 6px; + box-sizing: border-box; +} diff --git a/src/client/views/nodes/TaskBox.tsx b/src/client/views/nodes/TaskBox.tsx new file mode 100644 index 000000000..dca01817f --- /dev/null +++ b/src/client/views/nodes/TaskBox.tsx @@ -0,0 +1,298 @@ +import { action, observable, makeObservable, IReactionDisposer, reaction } from 'mobx'; +import { observer } from 'mobx-react'; +import * as React from 'react'; +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 './TaskBox.scss'; + +interface TaskBoxProps { + Document: Doc; +} + +@observer +export class TaskBox extends React.Component { + public static LayoutString(fieldStr: string) { + return FieldView.LayoutString(TaskBox, fieldStr); + } + + @action + updateText = (e: React.ChangeEvent) => { + this.props.Document.text = e.target.value; + }; + + @action + updateTitle = (e: React.ChangeEvent) => { + this.props.Document.title = e.target.value; + }; + + @action + updateAllDay = (e: React.ChangeEvent) => { + this.props.Document.$allDay = e.target.checked; + + if (e.target.checked) { + delete this.props.Document.$startTime; + delete this.props.Document.$endTime; + } + + this.setTaskDateRange(); + }; + + @action + updateStart = (e: React.ChangeEvent) => { + const newStart = new Date(e.target.value); + + this.props.Document.$startTime = new DateField(newStart); + + const endDate = this.props.Document.$endTime instanceof DateField ? this.props.Document.$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.$endTime = new DateField(adjustedEnd); + } + + this.setTaskDateRange(); + }; + + + + @action + updateEnd = (e: React.ChangeEvent) => { + const newEnd = new Date(e.target.value); + + this.props.Document.$endTime = new DateField(newEnd); + + const startDate = this.props.Document.$startTime instanceof DateField ? this.props.Document.$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.$startTime = new DateField(adjustedStart); + } + + this.setTaskDateRange(); + }; + + + + + @action + setTaskDateRange() { + const doc = this.props.Document; + + if (doc.$allDay) { + const range = typeof doc.date_range === 'string' ? doc.date_range.split('|') : []; + const dateStr = range[0] ?? new Date().toISOString().split('T')[0]; // default to today + + doc.date_range = `${dateStr}|${dateStr}`; + doc.$allDay = true; + } else { + const startField = doc.$startTime; + const endField = doc.$endTime; + const startDate = startField instanceof DateField ? startField.date : null; + const endDate = endField instanceof DateField ? endField.date : null; + + if (startDate && endDate && !isNaN(startDate.getTime()) && !isNaN(endDate.getTime())) { + doc.date_range = `${startDate.toISOString()}|${endDate.toISOString()}`; + doc.$allDay = false; + } + } + } + + @action + toggleComplete = (e: React.ChangeEvent) => { + this.props.Document.$completed = e.target.checked; + }; + + constructor(props: TaskBoxProps) { + super(props); + makeObservable(this); + } + + _heightDisposer?: IReactionDisposer; + _widthDisposer?: IReactionDisposer; + + componentDidMount() { + this.setTaskDateRange(); + + const doc = this.props.Document; + this._heightDisposer = reaction( + () => Number(doc._height), + height => { + const minHeight = Number(doc.height_min ?? 0); + if (!isNaN(height) && height < minHeight) { + doc._height = minHeight; + } + } + ); + + this._widthDisposer = reaction( + () => Number(doc._width), + width => { + const minWidth = Number(doc.width_min ?? 0); + if (!isNaN(width) && width < minWidth) { + doc._width = minWidth; + } + } + ); + } + + componentWillUnmount() { + this._heightDisposer?.(); + this._widthDisposer?.(); + } + + render() { + + function toLocalDateTimeString(date: Date): string { + const pad = (n: number) => n.toString().padStart(2, '0'); + return ( + date.getFullYear() + + '-' + + pad(date.getMonth() + 1) + + '-' + + pad(date.getDate()) + + 'T' + + pad(date.getHours()) + + ':' + + pad(date.getMinutes()) + ); + } + + const doc = this.props.Document; + + const taskDesc = typeof doc.text === 'string' ? doc.text : ''; + const taskTitle = typeof doc.title === 'string' ? doc.title : ''; + const allDay = !!doc.$allDay; + const isCompleted = !!this.props.Document.$completed; + + const startTime = doc.$startTime instanceof DateField && doc.$startTime.date instanceof Date + ? toLocalDateTimeString(doc.$startTime.date) + : ''; + + const endTime = doc.$endTime instanceof DateField && doc.$endTime.date instanceof Date + ? toLocalDateTimeString(doc.$endTime.date) + : ''; + + + + return ( +
+ + +