aboutsummaryrefslogtreecommitdiff
path: root/src/client/util/CalendarManager.tsx
diff options
context:
space:
mode:
Diffstat (limited to 'src/client/util/CalendarManager.tsx')
-rw-r--r--src/client/util/CalendarManager.tsx312
1 files changed, 312 insertions, 0 deletions
diff --git a/src/client/util/CalendarManager.tsx b/src/client/util/CalendarManager.tsx
new file mode 100644
index 000000000..50a5437a0
--- /dev/null
+++ b/src/client/util/CalendarManager.tsx
@@ -0,0 +1,312 @@
+import * as React from 'react';
+import './CalendarManager.scss';
+import { observer } from 'mobx-react';
+import { action, computed, observable, runInAction, makeObservable } from 'mobx';
+import { Doc, DocListCast } from '../../fields/Doc';
+import { DocumentView } from '../views/nodes/DocumentView';
+import { DictationOverlay } from '../views/DictationOverlay';
+import { TaskCompletionBox } from '../views/nodes/TaskCompletedBox';
+import { MainViewModal } from '../views/MainViewModal';
+import { TextField } from '@mui/material';
+import Select from 'react-select';
+import { SettingsManager } from './SettingsManager';
+import { DocCast, StrCast } from '../../fields/Types';
+import { SelectionManager } from './SelectionManager';
+import { DocumentManager } from './DocumentManager';
+import { DocData } from '../../fields/DocSymbols';
+// import { DateRange, Range, RangeKeyDict } from 'react-date-range';
+import { Button } from 'browndash-components';
+import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
+import { DateRangePicker } from '@adobe/react-spectrum';
+import { IconLookup, faPlus } from '@fortawesome/free-solid-svg-icons';
+import { Docs } from '../documents/Documents';
+import { ObservableReactComponent } from '../views/ObservableReactComponent';
+// import 'react-date-range/dist/styles.css';
+// import 'react-date-range/dist/theme/default.css';
+
+type CreationType = 'new-calendar' | 'existing-calendar' | 'manage-calendars';
+
+interface CalendarSelectOptions {
+ label: string;
+ value: string;
+}
+
+const formatDateToString = (date: Date) => {
+ const year = date.getFullYear();
+ const month = String(date.getMonth() + 1).padStart(2, '0');
+ const day = String(date.getDate()).padStart(2, '0');
+
+ return `${year}-${month}-${day}`;
+};
+
+// TODO: If doc is already part of a calendar, display that
+// TODO: For a doc already in a calendar: give option to edit date range, delete from calendar
+
+@observer
+export class CalendarManager extends ObservableReactComponent<{}> {
+ public static Instance: CalendarManager;
+ @observable private isOpen = false;
+ @observable private targetDoc: Doc | undefined; // the target document
+ @observable private targetDocView: DocumentView | undefined; // the DocumentView of the target doc
+ @observable private dialogueBoxOpacity = 1; // for the modal
+ @observable private overlayOpacity = 0.4; // for the modal
+
+ @observable private layoutDocAcls: boolean = false; // whether the layout doc or data doc's acls are to be used
+
+ @observable private creationType: CreationType = 'new-calendar';
+
+ @observable private existingCalendars: Doc[] = DocListCast(Doc.MyCalendars?.data);
+
+ @computed get selectOptions() {
+ return this.existingCalendars.map(calendar => ({ label: StrCast(calendar.title), value: StrCast(calendar.title) }));
+ }
+
+ @observable
+ selectedExistingCalendarOption: CalendarSelectOptions | null = null;
+
+ @observable
+ calendarName: string = '';
+
+ @observable
+ calendarDescription: string = '';
+
+ @observable
+ errorMessage: string = '';
+
+ @action
+ setInterationType = (type: CreationType) => {
+ this.errorMessage = '';
+ this.calendarName = '';
+ this.creationType = type;
+ };
+
+ public open = (target?: DocumentView, target_doc?: Doc) => {
+ console.log('hi');
+ runInAction(() => {
+ this.targetDoc = target_doc || target?.Document;
+ this.targetDocView = target;
+ DictationOverlay.Instance.hasActiveModal = true;
+ this.isOpen = this.targetDoc !== undefined;
+ });
+ };
+
+ public close = action(() => {
+ this.isOpen = false;
+ TaskCompletionBox.taskCompleted = false;
+ setTimeout(
+ action(() => {
+ DictationOverlay.Instance.hasActiveModal = false;
+ this.targetDoc = undefined;
+ }),
+ 500
+ );
+ this.layoutDocAcls = false;
+ });
+
+ constructor(props: {}) {
+ super(props);
+ CalendarManager.Instance = this;
+ makeObservable(this);
+ }
+
+ componentDidMount(): void {}
+
+ @action
+ handleSelectChange = (option: any) => {
+ let selectOpt = option as CalendarSelectOptions;
+ this.selectedExistingCalendarOption = selectOpt;
+ this.calendarName = selectOpt.value; // or label
+ };
+
+ @action
+ handleTextFieldChange = (event: React.ChangeEvent<HTMLTextAreaElement | HTMLInputElement>) => {
+ this.calendarName = event.target.value;
+ };
+
+ // TODO: Make undoable
+ private addToCalendar = () => {
+ let docs = SelectionManager.Views.length < 2 ? [this.targetDoc] : SelectionManager.Views.map(docView => docView.Document);
+ const targetDoc = this.layoutDocAcls ? docs[0] : docs[0]?.[DocData]; // doc to add to calendar
+
+ if (targetDoc) {
+ let calendar: Doc;
+ if (this.creationType === 'new-calendar') {
+ if (!this.existingCalendars.find(doc => StrCast(doc.title) === this.calendarName)) {
+ calendar = Docs.Create.CalendarDocument(
+ {
+ title: this.calendarName,
+ description: this.calendarDescription,
+ },
+ []
+ );
+ } else {
+ this.errorMessage = 'Calendar with this name already exists';
+ return;
+ }
+ } else {
+ // find existing calendar based on selected name (should technically always find one)
+ const existingCalendar = this.existingCalendars.find(calendar => StrCast(calendar.title) === this.calendarName);
+ if (existingCalendar) calendar = existingCalendar;
+ else {
+ this.errorMessage = 'Must select an existing calendar';
+ return;
+ }
+ }
+ // Get start and end date strings
+ const startDateStr = formatDateToString(this.selectedDateRange.start);
+ const endDateStr = formatDateToString(this.selectedDateRange.end);
+
+ const subDocEmbedding = Doc.MakeEmbedding(targetDoc); // embedding
+ subDocEmbedding.embedContainer = calendar; // set embed container
+ subDocEmbedding.date_range = `${startDateStr}-${endDateStr}`; // set subDoc date range
+
+ Doc.AddDocToList(calendar, 'data', subDocEmbedding); // add embedded subDoc to calendar
+
+ if (this.creationType === 'new-calendar') {
+ Doc.AddDocToList(Doc.MyCalendars, 'data', calendar); // add to new calendar to dashboard calendars
+ }
+ }
+ };
+
+ private focusOn = (contents: string) => {
+ const title = this.targetDoc ? StrCast(this.targetDoc.title) : '';
+ const docs = SelectionManager.Views.length > 1 ? SelectionManager.Views.map(docView => docView.Document) : [this.targetDoc];
+ return (
+ <span
+ className="focus-span"
+ title={title}
+ onClick={() => {
+ if (this.targetDoc && this.targetDocView && docs.length === 1) {
+ DocumentManager.Instance.showDocument(this.targetDoc, { willZoomCentered: true });
+ }
+ }}
+ onPointerEnter={action(() => {
+ if (docs.length) {
+ docs.forEach(doc => doc && Doc.BrushDoc(doc));
+ this.dialogueBoxOpacity = 0.1;
+ this.overlayOpacity = 0.1;
+ }
+ })}
+ onPointerLeave={action(() => {
+ if (docs.length) {
+ docs.forEach(doc => doc && Doc.UnBrushDoc(doc));
+ this.dialogueBoxOpacity = 1;
+ this.overlayOpacity = 0.4;
+ }
+ })}>
+ {contents}
+ </span>
+ );
+ };
+
+ @observable
+ selectedDateRange: any = [
+ {
+ start: new Date(),
+ end: new Date(),
+ },
+ ];
+
+ @action
+ setSelectedDateRange = (range: any) => {
+ this.selectedDateRange = range;
+ };
+
+ @computed
+ get createButtonActive() {
+ if (this.calendarName.length === 0 || this.errorMessage.length > 0) return false; // disabled if no calendar name
+ let startDate: Date | undefined;
+ let endDate: Date | undefined;
+ try {
+ startDate = this.selectedDateRange[0].startDate;
+ endDate = this.selectedDateRange[0].endDate;
+ } catch (e: any) {
+ return false; // disabled
+ }
+ if (!startDate || !endDate) return false; // disabled if any is undefined
+ return true;
+ }
+
+ @computed
+ get calendarInterface() {
+ let docs = SelectionManager.Views.length < 2 ? [this.targetDoc] : SelectionManager.Views.map(docView => docView.Document);
+ const targetDoc = this.layoutDocAcls ? docs[0] : docs[0]?.[DocData];
+
+ const currentDate = new Date();
+
+ return (
+ <div
+ className="calendar-interface"
+ style={{
+ background: SettingsManager.userBackgroundColor,
+ color: StrCast(Doc.UserDoc().userColor),
+ }}>
+ <p className="selected-doc-title" style={{ color: SettingsManager.userColor }}>
+ <b>{this.focusOn(docs.length < 2 ? StrCast(targetDoc?.title, 'this document') : '-multiple-')}</b>
+ </p>
+ <div className="creation-type-container">
+ <div className={`calendar-creation ${this.creationType === 'new-calendar' ? 'calendar-creation-selected' : ''}`} onClick={e => this.setInterationType('new-calendar')}>
+ Add to New Calendar
+ </div>
+ <div className={`calendar-creation ${this.creationType === 'existing-calendar' ? 'calendar-creation-selected' : ''}`} onClick={e => this.setInterationType('existing-calendar')}>
+ Add to Existing calendar
+ </div>
+ </div>
+ <div className="choose-calendar-container">
+ {this.creationType === 'new-calendar' ? (
+ <TextField
+ fullWidth
+ onChange={this.handleTextFieldChange}
+ placeholder="Enter calendar name..."
+ variant="filled"
+ style={{
+ backgroundColor: 'white',
+ color: 'black',
+ borderRadius: '5px',
+ }}
+ />
+ ) : (
+ <Select
+ className="existing-calendar-search"
+ placeholder="Search for existing calendar..."
+ isClearable
+ isSearchable
+ options={this.selectOptions}
+ value={this.selectedExistingCalendarOption}
+ onChange={this.handleSelectChange}
+ styles={{
+ control: () => ({
+ display: 'inline-flex',
+ width: '100%',
+ }),
+ indicatorSeparator: () => ({
+ display: 'inline-flex',
+ visibility: 'hidden',
+ }),
+ indicatorsContainer: () => ({
+ display: 'inline-flex',
+ textDecorationColor: 'black',
+ }),
+ valueContainer: () => ({
+ display: 'inline-flex',
+ fontStyle: StrCast(Doc.UserDoc().userColor),
+ color: StrCast(Doc.UserDoc().userColor),
+ width: '100%',
+ }),
+ }}></Select>
+ )}
+ </div>
+ <div className="date-range-picker-container">
+ <DateRangePicker value={this.selectedDateRange} onChange={v => this.setSelectedDateRange(v)} label="Date range" />
+ </div>
+ <div className="create-button-container">
+ <Button active={this.createButtonActive} onClick={() => {}} text="Add to Calendar" iconPlacement="right" icon={<FontAwesomeIcon icon={faPlus as IconLookup} />} />
+ </div>
+ </div>
+ );
+ }
+
+ render() {
+ return <MainViewModal contents={this.calendarInterface} isDisplayed={this.isOpen} interactive={true} dialogueBoxDisplayedOpacity={this.dialogueBoxOpacity} overlayDisplayedOpacity={this.overlayOpacity} closeOnExternalClick={this.close} />;
+ }
+}