From c53ab98d37e68653057f12ff02e00fbe131ae930 Mon Sep 17 00:00:00 2001 From: aaravkumar Date: Mon, 28 Apr 2025 23:48:41 -0400 Subject: added task nodes calendar intergation, and ability to complete tasks directly via calendar view --- .../views/nodes/calendarBox/CalendarBox.scss | 19 ++++++++++ src/client/views/nodes/calendarBox/CalendarBox.tsx | 42 +++++++++++++++++++++- 2 files changed, 60 insertions(+), 1 deletion(-) (limited to 'src/client/views/nodes/calendarBox') diff --git a/src/client/views/nodes/calendarBox/CalendarBox.scss b/src/client/views/nodes/calendarBox/CalendarBox.scss index f8ac4b2d1..f5d3613e3 100644 --- a/src/client/views/nodes/calendarBox/CalendarBox.scss +++ b/src/client/views/nodes/calendarBox/CalendarBox.scss @@ -23,3 +23,22 @@ } } } + +// AARAV ADD + +/* Existing styles */ +.fc-event.mother { + font-weight: 500; + border-radius: 4px; + padding: 2px 4px; + border-width: 2px; + } + + /* New styles for completed tasks */ + .fc-event.completed-task { + opacity: 1; + filter: grayscale(70%) brightness(90%); + text-decoration: line-through; + color: #ffffff; + } + \ No newline at end of file diff --git a/src/client/views/nodes/calendarBox/CalendarBox.tsx b/src/client/views/nodes/calendarBox/CalendarBox.tsx index 6f1f58a4c..cc8614f66 100644 --- a/src/client/views/nodes/calendarBox/CalendarBox.tsx +++ b/src/client/views/nodes/calendarBox/CalendarBox.tsx @@ -66,6 +66,7 @@ export class CalendarBox extends CollectionSubView() { @computed get calendarEvents(): EventSourceInput | undefined { return this.childDocs.map(doc => { const { start, end } = dateRangeStrToDates(StrCast(doc.date_range)); + const isCompleted = BoolCast(doc.completed); // AARAV ADD return { title: StrCast(doc.title), start, @@ -74,7 +75,7 @@ export class CalendarBox extends CollectionSubView() { startEditable: true, endEditable: true, allDay: BoolCast(doc.allDay), - classNames: ['mother'], // will determine the style + classNames: ['mother', isCompleted ? 'completed-task' : ''], // will determine the style editable: true, // subject to change in the future backgroundColor: this.eventToColor(doc), borderColor: this.eventToColor(doc), @@ -105,6 +106,10 @@ export class CalendarBox extends CollectionSubView() { // TODO: Return a different color based on the event type eventToColor = (event: Doc): string => { + const docType = StrCast(event.type); + if (docType === 'task') { + return '#20B2AA'; // teal for tasks + } return 'red'; }; @@ -171,6 +176,41 @@ export class CalendarBox extends CollectionSubView() { eventClick: this.handleEventClick, eventDrop: this.handleEventDrop, eventDidMount: arg => { + + const doc = DocServer.GetCachedRefField(arg.event._def.groupId ?? ''); + if (!doc) return; + + if (doc.type === 'task') { + const checkButton = document.createElement('button'); + checkButton.innerText = doc.completed ? '✅' : '⬜'; + checkButton.style.position = 'absolute'; + checkButton.style.right = '5px'; + checkButton.style.top = '50%'; + checkButton.style.transform = 'translateY(-50%)'; + checkButton.style.background = 'transparent'; + checkButton.style.border = 'none'; + checkButton.style.cursor = 'pointer'; + checkButton.style.fontSize = '18px'; + checkButton.style.zIndex = '1000'; + checkButton.style.padding = '0'; + checkButton.style.margin = '0'; + + checkButton.onclick = ev => { + ev.stopPropagation(); + doc.completed = !doc.completed; + this._calendar?.refetchEvents(); + }; + + // Make sure the parent box is positioned relative + arg.el.style.position = 'relative'; + arg.el.appendChild(checkButton); + } + + // (keep your other pointerup/contextmenu handlers here) + + + + arg.el.addEventListener('pointerdown', ev => { ev.button && ev.stopPropagation(); }); -- cgit v1.2.3-70-g09d2 From 9ec7df6af508eccdfda3a195a69c8bb1f86c41ea Mon Sep 17 00:00:00 2001 From: aaravkumar Date: Wed, 30 Apr 2025 12:51:51 -0400 Subject: two way integration -- changes in calendar reflect on task node -- and changes to node reflect on calendar --- src/client/views/nodes/calendarBox/CalendarBox.tsx | 29 ++++++++++++++++++++-- 1 file changed, 27 insertions(+), 2 deletions(-) (limited to 'src/client/views/nodes/calendarBox') diff --git a/src/client/views/nodes/calendarBox/CalendarBox.tsx b/src/client/views/nodes/calendarBox/CalendarBox.tsx index cc8614f66..4ee2413be 100644 --- a/src/client/views/nodes/calendarBox/CalendarBox.tsx +++ b/src/client/views/nodes/calendarBox/CalendarBox.tsx @@ -1,4 +1,5 @@ import { Calendar, EventClickArg, EventDropArg, EventSourceInput } from '@fullcalendar/core'; +import { EventResizeDoneArg } from '@fullcalendar/interaction'; import dayGridPlugin from '@fullcalendar/daygrid'; import interactionPlugin from '@fullcalendar/interaction'; import multiMonthPlugin from '@fullcalendar/multimonth'; @@ -17,6 +18,7 @@ import { ContextMenu } from '../../ContextMenu'; import { DocumentView } from '../DocumentView'; import { OpenWhere } from '../OpenWhere'; import './CalendarBox.scss'; +import { DateField } from '../../../../fields/DateField'; type CalendarView = 'multiMonth' | 'dayGridMonth' | 'timeGridWeek' | 'timeGridDay'; @@ -127,9 +129,31 @@ export class CalendarBox extends CollectionSubView() { return false; }; - handleEventDrop = (arg: EventDropArg) => { + handleEventDrop = (arg: EventDropArg | EventResizeDoneArg) => { const doc = DocServer.GetCachedRefField(arg.event._def.groupId ?? ''); - doc && arg.event.start && (doc.date_range = arg.event.start?.toString() + '|' + (arg.event.end ?? arg.event.start).toString()); + // doc && arg.event.start && (doc.date_range = arg.event.start?.toString() + '|' + (arg.event.end ?? arg.event.start).toString()); + if (!doc || !arg.event.start) return; + + // get the new start and end dates + const startDate = new Date(arg.event.start); + const endDate = new Date(arg.event.end ?? arg.event.start); + + // update date range, time range, and all day status + doc.date_range = `${startDate.toISOString()}|${endDate.toISOString()}`; + + const allDayStatus = arg.event.allDay ?? false; + if (doc.allDay !== allDayStatus) { + doc.allDay = allDayStatus; + } + + if (doc.allDay) { + delete doc.startTime; + delete doc.endTime; + } else { + doc.startTime = new DateField(startDate); + doc.endTime = new DateField(endDate); + } + }; handleEventClick = (arg: EventClickArg) => { @@ -175,6 +199,7 @@ export class CalendarBox extends CollectionSubView() { events: this.calendarEvents, eventClick: this.handleEventClick, eventDrop: this.handleEventDrop, + eventResize: this.handleEventDrop, eventDidMount: arg => { const doc = DocServer.GetCachedRefField(arg.event._def.groupId ?? ''); -- cgit v1.2.3-70-g09d2 From c1ebafcbe29140da7c1e260bab88921964dd82b0 Mon Sep 17 00:00:00 2001 From: aaravkumar Date: Wed, 30 Apr 2025 20:45:16 -0400 Subject: fixed height / width restrictions and added date selector for all day tasks --- src/client/util/CurrentUserUtils.ts | 2 +- src/client/views/nodes/TaskManagerTask.tsx | 61 ++++++++++++++++++++-- src/client/views/nodes/calendarBox/CalendarBox.tsx | 3 -- 3 files changed, 58 insertions(+), 8 deletions(-) (limited to 'src/client/views/nodes/calendarBox') diff --git a/src/client/util/CurrentUserUtils.ts b/src/client/util/CurrentUserUtils.ts index 1288abd3e..d378ca02f 100644 --- a/src/client/util/CurrentUserUtils.ts +++ b/src/client/util/CurrentUserUtils.ts @@ -407,7 +407,7 @@ pie title Minerals in my tap water {key: "DataViz", creator: opts => Docs.Create.DataVizDocument("", opts), opts: { _width: 300, _height: 300, }}, // AARAV ADD // {key: "DailyJournal",creator:opts => Docs.Create.DailyJournalDocument("", opts),opts: { _width: 300, _height: 300, }}, - {key: "Task", creator: opts => Docs.Create.TaskDocument("", opts), opts: { _width: 250, _height: 100, _layout_autoHeight: true, title: "Task", }}, + {key: "Task", creator: opts => Docs.Create.TaskDocument("", opts), opts: { _width: 300, _height: 300, _layout_autoHeight: true, title: "Task", }}, {key: "Scrapbook", creator: opts => Docs.Create.ScrapbookDocument([], opts), opts: { _width: 300, _height: 300}}, //{key: "Scrapbook",creator:opts => Docs.Create.ScrapbookDocument([], opts),opts:{ _width: 300, _height: 300}}, {key: "Chat", creator: Docs.Create.ChatDocument, opts: { _width: 500, _height: 500, _layout_fitWidth: true, }}, diff --git a/src/client/views/nodes/TaskManagerTask.tsx b/src/client/views/nodes/TaskManagerTask.tsx index 2d2444275..d8c1430d4 100644 --- a/src/client/views/nodes/TaskManagerTask.tsx +++ b/src/client/views/nodes/TaskManagerTask.tsx @@ -1,4 +1,4 @@ -import { action, observable, makeObservable } from 'mobx'; +import { action, observable, makeObservable, IReactionDisposer, reaction } from 'mobx'; import { observer } from 'mobx-react'; import * as React from 'react'; import { Docs } from '../../documents/Documents'; @@ -14,7 +14,7 @@ interface TaskManagerProps { } @observer -export class TaskManagerTask extends React.Component { +export class TaskManagerTask extends React.Component { public static LayoutString(fieldStr: string) { return FieldView.LayoutString(TaskManagerTask, fieldStr); } @@ -125,10 +125,38 @@ export class TaskManagerTask extends React.Component { 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() { @@ -195,6 +223,29 @@ export class TaskManagerTask extends React.Component { disabled={isCompleted} /> All day + + {allDay && ( + { + const rawRange = doc.date_range; + if (typeof rawRange !== 'string') return ''; + const datePart = rawRange.split('|')[0]; + if (!datePart) return ''; + const d = new Date(datePart); + return !isNaN(d.getTime()) ? d.toISOString().split('T')[0] : ''; + })()} + onChange={e => { + const newDate = new Date(e.target.value); + if (!isNaN(newDate.getTime())) { + const iso = newDate.toISOString().split('T')[0]; + doc.date_range = `${iso}T00:00:00.000Z|${iso}T00:00:00.000Z`; + } + }} + disabled={isCompleted} + style={{ marginLeft: '8px' }} + /> + )}