aboutsummaryrefslogtreecommitdiff
path: root/src/client/views/nodes/TaskManagerTask.tsx
diff options
context:
space:
mode:
Diffstat (limited to 'src/client/views/nodes/TaskManagerTask.tsx')
-rw-r--r--src/client/views/nodes/TaskManagerTask.tsx251
1 files changed, 251 insertions, 0 deletions
diff --git a/src/client/views/nodes/TaskManagerTask.tsx b/src/client/views/nodes/TaskManagerTask.tsx
new file mode 100644
index 000000000..2d2444275
--- /dev/null
+++ b/src/client/views/nodes/TaskManagerTask.tsx
@@ -0,0 +1,251 @@
+import { action, observable, makeObservable } 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 './TaskManagerTask.scss';
+
+interface TaskManagerProps {
+ Document: Doc;
+}
+
+@observer
+export class TaskManagerTask extends React.Component<TaskManagerProps> {
+ public static LayoutString(fieldStr: string) {
+ return FieldView.LayoutString(TaskManagerTask, fieldStr);
+ }
+
+ @action
+ updateText = (e: React.ChangeEvent<HTMLInputElement | HTMLTextAreaElement>) => {
+ this.props.Document.text = e.target.value;
+ };
+
+ @action
+ updateTitle = (e: React.ChangeEvent<HTMLInputElement>) => {
+ this.props.Document.title = e.target.value;
+ };
+
+ @action
+ updateAllDay = (e: React.ChangeEvent<HTMLInputElement>) => {
+ 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<HTMLInputElement>) => {
+ 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>) => {
+ 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) {
+ // 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.props.Document.completed = e.target.checked;
+ };
+
+ constructor(props: TaskManagerProps) {
+ super(props);
+ 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)
+ : '';
+
+
+
+ return (
+ <div className="task-manager-container">
+ <input
+ className="task-manager-title"
+ type="text"
+ placeholder="Task Title"
+ value={taskTitle}
+ onChange={this.updateTitle}
+ disabled={isCompleted}
+ style={{opacity: isCompleted ? 0.7 : 1,}}
+ />
+
+ <textarea
+ className="task-manager-description"
+ placeholder="What’s your task?"
+ value={taskDesc}
+ onChange={this.updateText}
+ disabled={isCompleted}
+ style={{opacity: isCompleted ? 0.7 : 1,}}
+ />
+
+ <div className="task-manager-checkboxes">
+
+ <label className="task-manager-allday" style={{opacity: isCompleted ? 0.7 : 1,}}>
+ <input
+ type="checkbox"
+ checked={allDay}
+ onChange={this.updateAllDay}
+ disabled={isCompleted}
+ />
+ All day
+ </label>
+
+ <label className="task-manager-complete">
+ <input type="checkbox" checked={isCompleted} onChange={this.toggleComplete} />
+ Complete
+ </label>
+
+ </div>
+
+ {!allDay && (
+ <div
+ className="task-manager-times"
+ style={{ opacity: isCompleted ? 0.7 : 1 }}
+ >
+ <label>
+ Start:
+ <input
+ type="datetime-local"
+ value={startTime}
+ onChange={this.updateStart}
+ disabled={isCompleted}
+ />
+ </label>
+ <label>
+ End:
+ <input
+ type="datetime-local"
+ value={endTime}
+ onChange={this.updateEnd}
+ disabled={isCompleted}
+ />
+ </label>
+ </div>
+ )}
+ </div>
+ );
+ }
+}
+
+Docs.Prototypes.TemplateMap.set(DocumentType.TASK, {
+ layout: { view: TaskManagerTask, dataField: 'text' },
+ options: {
+ acl: '',
+ _height: 35,
+ _xMargin: 10,
+ _yMargin: 10,
+ _layout_autoHeight: true,
+ _layout_nativeDimEditable: true,
+ _layout_reflowVertical: true,
+ _layout_reflowHorizontal: true,
+ defaultDoubleClick: 'ignore',
+ systemIcon: 'BsCheckSquare', // or whatever icon you like
+ },
+});