aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/client/documents/Documents.ts4
-rw-r--r--src/client/views/nodes/TaskManagerTask.tsx138
-rw-r--r--src/client/views/nodes/calendarBox/CalendarBox.scss19
-rw-r--r--src/client/views/nodes/calendarBox/CalendarBox.tsx42
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();
});