diff options
-rw-r--r-- | src/client/documents/Documents.ts | 4 | ||||
-rw-r--r-- | src/client/views/nodes/TaskManagerTask.tsx | 138 | ||||
-rw-r--r-- | src/client/views/nodes/calendarBox/CalendarBox.scss | 19 | ||||
-rw-r--r-- | src/client/views/nodes/calendarBox/CalendarBox.tsx | 42 |
4 files changed, 183 insertions, 20 deletions
diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts index 9b1f64a36..112a73e46 100644 --- a/src/client/documents/Documents.ts +++ b/src/client/documents/Documents.ts @@ -530,12 +530,12 @@ export class DocumentOptions { /** 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); } export const DocOptions = new DocumentOptions(); diff --git a/src/client/views/nodes/TaskManagerTask.tsx b/src/client/views/nodes/TaskManagerTask.tsx index 1a9205ada..2d2444275 100644 --- a/src/client/views/nodes/TaskManagerTask.tsx +++ b/src/client/views/nodes/TaskManagerTask.tsx @@ -37,24 +37,87 @@ export class TaskManagerTask extends React.Component<TaskManagerProps> { delete this.props.Document.startTime; delete this.props.Document.endTime; } + + this.setTaskDateRange(); }; @action updateStart = (e: React.ChangeEvent<HTMLInputElement>) => { - this.props.Document.startTime = new DateField(new Date(e.target.value)); + 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<HTMLInputElement>) => { - this.props.Document.endTime = new DateField(new Date(e.target.value)); + 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(); }; - @observable - completed = false; + + + + @action + setTaskDateRange() { + const doc = this.props.Document; + + if (doc.allDay) { + // All-day task → use date only + if (!doc.title) return; + + const parsedDate = new Date(doc.title as string); + if (!isNaN(parsedDate.getTime())) { + const localStart = new Date(parsedDate.getFullYear(), parsedDate.getMonth(), parsedDate.getDate()); + const localEnd = new Date(localStart); + doc.date_range = `${localStart.toISOString()}|${localEnd.toISOString()}`; + doc.allDay = true; + } + } else { + // Timed task → use full startTime and endTime + 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; + } else { + console.warn('startTime or endTime is invalid'); + } + } + } @action toggleComplete = (e: React.ChangeEvent<HTMLInputElement>) => { - this.completed = e.target.checked; + this.props.Document.completed = e.target.checked; }; constructor(props: TaskManagerProps) { @@ -62,15 +125,44 @@ export class TaskManagerTask extends React.Component<TaskManagerProps> { makeObservable(this); } + componentDidMount() { + this.setTaskDateRange(); + } + + 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) + : ''; + - const startTime = doc.startTime instanceof DateField ? doc.startTime.date.toISOString().slice(0, 16) : ''; - const endTime = doc.endTime instanceof DateField ? doc.endTime.date.toISOString().slice(0, 16) : ''; return ( <div className="task-manager-container"> @@ -80,8 +172,8 @@ export class TaskManagerTask extends React.Component<TaskManagerProps> { placeholder="Task Title" value={taskTitle} onChange={this.updateTitle} - disabled={this.completed} - style={{opacity: this.completed ? 0.7 : 1,}} + disabled={isCompleted} + style={{opacity: isCompleted ? 0.7 : 1,}} /> <textarea @@ -89,42 +181,54 @@ export class TaskManagerTask extends React.Component<TaskManagerProps> { placeholder="What’s your task?" value={taskDesc} onChange={this.updateText} - disabled={this.completed} - style={{opacity: this.completed ? 0.7 : 1,}} + disabled={isCompleted} + style={{opacity: isCompleted ? 0.7 : 1,}} /> <div className="task-manager-checkboxes"> - <label className="task-manager-allday" style={{opacity: this.completed ? 0.7 : 1,}}> + <label className="task-manager-allday" style={{opacity: isCompleted ? 0.7 : 1,}}> <input type="checkbox" checked={allDay} onChange={this.updateAllDay} - disabled={this.completed} + disabled={isCompleted} /> All day </label> <label className="task-manager-complete"> - <input type="checkbox" checked={this.completed} onChange={this.toggleComplete} /> + <input type="checkbox" checked={isCompleted} onChange={this.toggleComplete} /> Complete </label> </div> + {!allDay && ( <div className="task-manager-times" - style={{ visibility: allDay ? 'hidden' : 'visible', opacity: this.completed ? 0.7 : 1}} + style={{ opacity: isCompleted ? 0.7 : 1 }} > <label> Start: - <input type="datetime-local" value={startTime} onChange={this.updateStart} disabled={this.completed}/> + <input + type="datetime-local" + value={startTime} + onChange={this.updateStart} + disabled={isCompleted} + /> </label> <label> End: - <input type="datetime-local" value={endTime} onChange={this.updateEnd} disabled={this.completed} /> + <input + type="datetime-local" + value={endTime} + onChange={this.updateEnd} + disabled={isCompleted} + /> </label> </div> + )} </div> ); } 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(); }); |