diff options
-rw-r--r-- | src/client/documents/Documents.ts | 1 | ||||
-rw-r--r-- | src/client/views/nodes/TaskBox.tsx | 97 | ||||
-rw-r--r-- | src/server/ApiManagers/GeneralGoogleManager.ts | 65 | ||||
-rw-r--r-- | src/server/RouteManager.ts | 8 |
4 files changed, 160 insertions, 11 deletions
diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts index 4ad9c9bd8..120ad0688 100644 --- a/src/client/documents/Documents.ts +++ b/src/client/documents/Documents.ts @@ -528,6 +528,7 @@ export class DocumentOptions { $task_endTime?: DateInfo | DateField = new DateInfo('end date and time', /*filterable*/ false); $task_allDay?: BoolInfo | boolean = new BoolInfo('whether task is all-day or not', /*filterable*/ false); $task_completed?: BoolInfo | boolean = new BoolInfo('whether the task is completed', /*filterable*/ false); + $googleTaskId?: STRt = new StrInfo('Google Task ID for syncing'); _calendar_date?: DateInfo | DateField = new DateInfo('current selected date of a calendar', /*filterable*/ false); _calendar_dateRange?: STRt = new StrInfo('date range shown on a calendar', false); diff --git a/src/client/views/nodes/TaskBox.tsx b/src/client/views/nodes/TaskBox.tsx index b59599052..7df3876c3 100644 --- a/src/client/views/nodes/TaskBox.tsx +++ b/src/client/views/nodes/TaskBox.tsx @@ -159,13 +159,62 @@ export class TaskBox extends React.Component<TaskBoxProps> { makeObservable(this); } + _googleTaskCreateDisposer?: IReactionDisposer; _heightDisposer?: IReactionDisposer; _widthDisposer?: IReactionDisposer; componentDidMount() { this.setTaskDateRange(); - const doc = this.props.Document; + + // adding task on creation to google + + (async () => { + if (!doc.$googleTaskId && doc.title) { + try { + const token = await GoogleAuthenticationManager.Instance.fetchOrGenerateAccessToken(); + if (!token) return; + + const body: any = { + title: doc.title || 'Untitled Task', + notes: doc.text || '', + status: doc.$task_completed ? 'completed' : 'needsAction', + completed: doc.$task_completed ? new Date().toISOString() : undefined, + }; + + if (doc.$task_allDay && typeof doc.$task_dateRange === 'string') { + const datePart = doc.$task_dateRange.split('|')[0]; + if (datePart && !isNaN(new Date(datePart).getTime())) { + const baseDate = datePart.includes('T') ? datePart : datePart + 'T00:00:00Z'; + body.due = new Date(baseDate).toISOString(); + } + } else if (doc.$task_endTime instanceof DateField) { + body.due = doc.$task_endTime.date.toISOString(); + } + + const res = await fetch('/googleTasks/create', { + method: 'POST', + credentials: 'include', + headers: { + 'Content-Type': 'application/json', + Authorization: `Bearer ${token}`, + }, + body: JSON.stringify(body), + }); + + const result = await res.json(); + if (result?.id) { + doc.$googleTaskId = result.id; + console.log('✅ Google Task created on mount:', result); + } else { + console.warn('❌ Google Task creation failed:', result); + } + } catch (err) { + console.error('❌ Error creating Google Task:', err); + } + } + })(); + this._heightDisposer = reaction( () => Number(doc._height), height => { @@ -188,8 +237,32 @@ export class TaskBox extends React.Component<TaskBoxProps> { } componentWillUnmount() { + const doc = this.props.Document; + this._googleTaskCreateDisposer?.(); this._heightDisposer?.(); this._widthDisposer?.(); + + // task deletion + if (doc.$googleTaskId) { + (async () => { + try { + const token = await GoogleAuthenticationManager.Instance.fetchOrGenerateAccessToken(); + if (!token) return; + + await fetch(`/googleTasks/${doc.$googleTaskId}`, { + method: 'DELETE', + credentials: 'include', + headers: { + Authorization: `Bearer ${token}`, + }, + }); + + console.log(`✅ Deleted Google Task ${doc.$googleTaskId}`); + } catch (err) { + console.warn('❌ Failed to delete Google Task:', err); + } + })(); + } } /** @@ -213,7 +286,7 @@ export class TaskBox extends React.Component<TaskBoxProps> { const startTime = doc.$task_startTime instanceof DateField && doc.$task_startTime.date instanceof Date ? toLocalDateTimeString(doc.$task_startTime.date) : ''; const endTime = doc.$task_endTime instanceof DateField && doc.$task_endTime.date instanceof Date ? toLocalDateTimeString(doc.$task_endTime.date) : ''; - const handleGoogleTaskClick = async () => { + const handleGoogleTaskSync = async () => { console.log('GT button clicked'); try { @@ -221,9 +294,9 @@ export class TaskBox extends React.Component<TaskBoxProps> { if (!token) { const listener = () => { window.removeEventListener('focusin', listener); - if (confirm('✅ Authorization complete. Try adding the task again?')) { + if (confirm('✅ Authorization complete. Try syncing the task again?')) { // you could refactor the click handler here - handleGoogleTaskClick(); + handleGoogleTaskSync(); } window.removeEventListener('focusin', listener); }; @@ -252,8 +325,12 @@ export class TaskBox extends React.Component<TaskBoxProps> { due = undefined; } - const response = await fetch('/googleTasks/create', { - method: 'POST', + const isUpdate = !!doc.$googleTaskId; + const endpoint = isUpdate ? `/googleTasks/${doc.$googleTaskId}` : '/googleTasks/create'; + const method = isUpdate ? 'PATCH' : 'POST'; + + const response = await fetch(endpoint, { + method, credentials: 'include', headers: { 'Content-Type': 'application/json', @@ -272,13 +349,13 @@ export class TaskBox extends React.Component<TaskBoxProps> { console.log('Google Task result:', result); if (result?.id) { - alert('✅ Task sent to Google Tasks!'); + alert('✅ Task synced with Google Tasks!'); } else { alert(`❌ Failed: ${result?.error?.message || 'Unknown error'}`); } } catch (err) { console.error('Fetch error:', err); - alert('❌ Task creation failed.'); + alert('❌ Task syncing failed.'); } }; @@ -327,9 +404,9 @@ export class TaskBox extends React.Component<TaskBoxProps> { className="task-manager-google" onClick={event => { event.preventDefault(); - handleGoogleTaskClick(); + handleGoogleTaskSync(); }}> - Add to Google Tasks + Sync to Google </button> </div> diff --git a/src/server/ApiManagers/GeneralGoogleManager.ts b/src/server/ApiManagers/GeneralGoogleManager.ts index 4f0b8c02b..79ca55444 100644 --- a/src/server/ApiManagers/GeneralGoogleManager.ts +++ b/src/server/ApiManagers/GeneralGoogleManager.ts @@ -63,8 +63,9 @@ export default class GeneralGoogleManager extends ApiManager { }, }); - // AARAV ADD (creating a task) + // AARAV ADD + // Task Creation register({ method: Method.POST, subscription: new RouteSubscriber('googleTasks').add('create'), @@ -92,6 +93,68 @@ export default class GeneralGoogleManager extends ApiManager { }, }); + // Task Update + register({ + method: Method.PATCH, + subscription: new RouteSubscriber('googleTasks').add('taskId'), + // any way to add static params? like /update (this is not very descriptive) + secureHandler: async ({ req, res, user }) => { + try { + const auth = await GoogleApiServerUtils.retrieveOAuthClient(user.id); + + if (!auth) { + return res.status(401).send('Google credentials missing or invalid.'); + } + + const tasks = google.tasks({ version: 'v1', auth }); + + const { taskId } = req.params; + const { title, notes, due, status, completed } = req.body; + + const result = await tasks.tasks.patch({ + tasklist: '@default', + task: taskId, + requestBody: { title, notes, due, status, completed }, + }); + + res.status(200).send(result.data); + } catch (err) { + console.error('Google Tasks update error:', err); + res.status(500).send('Failed to update task.'); + } + }, + }); + + // Task Deletion + register({ + method: Method.DELETE, + subscription: new RouteSubscriber('googleTasks').add('taskId'), + secureHandler: async ({ req, res, user }) => { + try { + const auth = await GoogleApiServerUtils.retrieveOAuthClient(user.id); + + if (!auth) { + return res.status(401).send('Google credentials missing or invalid.'); + } + + const tasks = google.tasks({ version: 'v1', auth }); + const { taskId } = req.params; + + await tasks.tasks.delete({ + tasklist: '@default', + task: taskId, + }); + + res.status(200).send({ success: true }); + } catch (err) { + console.error('Google Tasks delete error:', err); + res.status(500).send('Failed to delete task.'); + } + }, + }); + + + register({ method: Method.GET, subscription: '/refreshGoogle', diff --git a/src/server/RouteManager.ts b/src/server/RouteManager.ts index 2f6cf80b5..c5d70da3d 100644 --- a/src/server/RouteManager.ts +++ b/src/server/RouteManager.ts @@ -8,6 +8,8 @@ import { DashUserModel } from './authentication/DashUserModel'; export enum Method { GET, POST, + PATCH, + DELETE, } export interface CoreArguments { @@ -207,6 +209,12 @@ export default class RouteManager { case Method.POST: this.server.post(route, supervised); break; + case Method.PATCH: + this.server.patch(route, supervised); + break; + case Method.DELETE: + this.server.delete(route, supervised); + break; default: } } |