-
-
-
This view allows you to export your worked time to a google sheet. You need to allow for your Google Spreadsheets to be accessed by Super Productivity. You also need to create a spreadsheet with a headings in the first row nad specify it's ID in the input field (Ho to find the id of a spreadsheet?).
-
-
After successfully loading your spreadsheet a table will show up with 4 rows. The first row shows the heading you specified in the spreadsheet itself.
-
The second row is for informational purposes and shows the last row from the spreadsheet.
-
The forth row is a list of values you can directly enter save to the spreadsheet.
-
The third row is there to automatically define some values for the forth row. There are several special strings you can enter into the cells:
-
-
-
- - {{ '{' }}startTime}
- - The time when you first used this app today. It's possible to round this via the options.
-
- - {{ '{' }}currentTime}
- - The current time. Could be used for the for the end time of todays working day It's possible to round this via the options.
-
- - {{ '{' }}date}
- - Todays date in standard format (mm/dd/yyyy)
-
- - {{ '{' }}date:DD/MM/YYYY} (example)
- - Date with a custom date format string.
-
- - {{ '{' }}taskTitles}
- - Comma separated (parent) task titles
-
- - {{ '{' }}subTaskTitles}
- - Comma separated sub task titles
-
- - {{ '{' }}totalTime}
- - The total time you spend working on your todays tasks.
-
-
- In addition to this there are several options you can use to modify the calculation of those values.
-
-
-
- Auto-login and load data next time
-
- Always round work time up
-
-
-
-
- don't round
-
- {{roundOption.title}}
-
-
-
-
-
-
-
- don't round
-
- {{roundOption.title}}
-
-
-
-
-
-
-
- don't round
-
- {{roundOption.title}}
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
-
-
-
-
-
+
+
diff --git a/src/app/core/google/dialog-google-export-time/dialog-google-export-time.component.scss b/src/app/core/google/dialog-google-export-time/dialog-google-export-time.component.scss
index e69de29bb..2cf575ecd 100644
--- a/src/app/core/google/dialog-google-export-time/dialog-google-export-time.component.scss
+++ b/src/app/core/google/dialog-google-export-time/dialog-google-export-time.component.scss
@@ -0,0 +1,52 @@
+.help-collapsible {
+ ::ng-deep.collapsible-title {
+ font-size: 20px;
+ }
+}
+
+.options-collapsible {
+ ::ng-deep .collapsible-title {
+ font-size: 20px;
+ margin-top: 0;
+ }
+}
+
+.export-input-table {
+ th {
+ font-weight: bold;
+ }
+
+ input {
+ width: 100%;
+ min-width: 40px;
+ }
+}
+
+.possible-properties {
+ dt {
+ margin-bottom: 4px;
+ }
+ dd {
+ margin-bottom: 10px;
+ }
+}
+
+.loading-spinner {
+ position: absolute;
+ width: 100%;
+ height: 100%;
+ top: 0;
+ left: 0;
+ background: rgba(0, 0, 0, 0.3);
+
+ mat-progress-spinner {
+ position: absolute;
+ top: 50%;
+ left: 50%;
+ transform: translate(-50%, -50%, 0);
+ }
+}
+
+mat-input-container {
+ min-width: 150px;
+}
diff --git a/src/app/core/google/dialog-google-export-time/dialog-google-export-time.component.ts b/src/app/core/google/dialog-google-export-time/dialog-google-export-time.component.ts
index ad850e36a..5cc6e20f1 100644
--- a/src/app/core/google/dialog-google-export-time/dialog-google-export-time.component.ts
+++ b/src/app/core/google/dialog-google-export-time/dialog-google-export-time.component.ts
@@ -1,4 +1,8 @@
-import { ChangeDetectionStrategy, Component, OnInit } from '@angular/core';
+import { ChangeDetectionStrategy, ChangeDetectorRef, Component, OnInit } from '@angular/core';
+import * as moment from 'moment';
+import { Duration, Moment } from 'moment';
+import { GoogleApiService } from '../google-api.service';
+import { SnackService } from '../../snack/snack.service';
@Component({
selector: 'dialog-google-export-time',
@@ -7,11 +11,275 @@ import { ChangeDetectionStrategy, Component, OnInit } from '@angular/core';
changeDetection: ChangeDetectionStrategy.OnPush
})
export class DialogGoogleExportTimeComponent implements OnInit {
+ opts: any = {
+ spreadsheetId: undefined,
+ isAutoLogin: false,
+ isAutoFocusEmpty: false,
+ isRoundWorkTimeUp: undefined,
+ roundStartTimeTo: undefined,
+ roundEndTimeTo: undefined,
+ roundWorkTimeTo: undefined,
+ defaultValues: [
+ ''
+ ]
+ };
+ // $rootScope.r.uiHelper.timeSheetExportSettings;
+ actualValues = [];
+ isLoading = false;
+ isLoggedIn = false;
+ headings: string[] = [];
+ lastRow: string[] = [];
+ MISSING = {
+ startedTimeToday: 'MISSING startedTimeToday',
+ getTimeWorkedToday: 'MISSING getTimeWorkedToday',
+ getToday: []
+ };
- constructor() {
+ roundTimeOptions = [
+ {id: 'QUARTER', title: 'full quarters'},
+ {id: 'HALF', title: 'full half hours'},
+ {id: 'HOUR', title: 'full hours'},
+ ];
+
+
+ constructor(
+ public googleApiService: GoogleApiService,
+ private _snackService: SnackService,
+ private _cd: ChangeDetectorRef
+ ) {
}
ngOnInit() {
+ if (this.opts.isAutoLogin) {
+ this.login()
+ .then(() => {
+ if (this.opts.spreadsheetId) {
+ this.readSpreadsheet();
+ }
+ })
+ .then(() => {
+ this.updateDefaults();
+ });
+ }
+ }
+
+ cancel() {
+ // $mdDialog.hide();
+ }
+
+ login() {
+ this.isLoading = true;
+ return this.googleApiService.login()
+ .then(() => {
+ this.isLoading = false;
+ this.isLoggedIn = true;
+ this._cd.detectChanges();
+ });
+ }
+
+ readSpreadsheet() {
+ this.isLoading = true;
+ this.headings = undefined;
+ return this.googleApiService.getSpreadsheetHeadingsAndLastRow(this.opts.spreadsheetId)
+ .then((data: any) => {
+ this.headings = data.headings;
+ this.lastRow = data.lastRow;
+ this.updateDefaults();
+ this.isLoading = false;
+ this._cd.detectChanges();
+ });
+ }
+
+ logout() {
+ this.isLoading = false;
+ return this.googleApiService.logout()
+ .then(() => {
+ this.isLoading = false;
+ this.isLoggedIn = false;
+ this._cd.detectChanges();
+ });
+ }
+
+ save() {
+ this.isLoading = true;
+ const arraysEqual = (arr1, arr2) => {
+ if (arr1.length !== arr2.length) {
+ return false;
+ }
+ for (let i = arr1.length; i--;) {
+ if (arr1[i] !== arr2[i]) {
+ return false;
+ }
+ }
+ return true;
+ };
+
+ if (arraysEqual(this.actualValues, this.lastRow)) {
+ this._snackService.open('Current values and the last saved row have equal values, that is probably not what you want.');
+ } else {
+
+ this.googleApiService.appendRow(this.opts.spreadsheetId, this.actualValues)
+ .then(() => {
+ this._snackService.open({
+ message: 'Row successfully appended',
+ type: 'SUCCESS'
+ });
+
+ // $mdDialog.hide();
+ // $rootScope.r.currentSession.isTimeSheetExported = true;
+ this.isLoading = false;
+ });
+ }
+ }
+
+ updateDefaults() {
+ this.opts.defaultValues.forEach((val, index) => {
+ this.actualValues[index] = this.replaceVals(val);
+ });
+ }
+
+ replaceVals(defaultVal: string): string {
+ if (!defaultVal) {
+ return;
+ }
+
+ const dVal = defaultVal.trim();
+
+ if (dVal.match(/\{date:/)) {
+ return this.getCustomDate(dVal);
+ }
+
+ switch (dVal) {
+ case '{startTime}':
+ return this.getStartTime();
+ case '{currentTime}':
+ return this.getCurrentTime();
+ case '{date}':
+ return moment().format('MM/DD/YYYY');
+ case '{taskTitles}':
+ return this.getTaskTitles();
+ case '{subTaskTitles}':
+ return this.getSubTaskTitles();
+ case '{totalTime}':
+ return this.getTotalTimeWorked();
+ default:
+ return dVal;
+ }
+ }
+
+ private roundDuration(value: Duration, roundTo, isRoundUp): Duration {
+ let rounded;
+
+ switch (roundTo) {
+ case 'QUARTER':
+ rounded = Math.round(value.asMinutes() / 15) * 15;
+ if (isRoundUp) {
+ rounded = Math.ceil(value.asMinutes() / 15) * 15;
+ }
+ return moment.duration({minutes: rounded});
+
+ case 'HALF':
+ rounded = Math.round(value.asMinutes() / 30) * 30;
+ if (isRoundUp) {
+ rounded = Math.ceil(value.asMinutes() / 30) * 30;
+ }
+ return moment.duration({minutes: rounded});
+
+ case 'HOUR':
+ rounded = Math.round(value.asMinutes() / 60) * 60;
+ if (isRoundUp) {
+ rounded = Math.ceil(value.asMinutes() / 60) * 60;
+ }
+ return moment.duration({minutes: rounded});
+
+ default:
+ return value;
+ }
+ }
+
+ private roundTime(value: Moment, roundTo, isRoundUp = false): Moment {
+ let rounded;
+
+ switch (roundTo) {
+ case 'QUARTER':
+ rounded = Math.round(value.minute() / 15) * 15;
+ if (isRoundUp) {
+ rounded = Math.ceil(value.minute() / 15) * 15;
+ }
+ return value.minute(rounded).second(0);
+
+ case 'HALF':
+ rounded = Math.round(value.minute() / 30) * 30;
+ if (isRoundUp) {
+ rounded = Math.ceil(value.minute() / 30) * 30;
+ }
+ return value.minute(rounded).second(0);
+
+ case 'HOUR':
+ rounded = Math.round(value.minute() / 60) * 60;
+ if (isRoundUp) {
+ rounded = Math.ceil(value.minute() / 60) * 60;
+ }
+ return value.minute(rounded).second(0);
+
+ default:
+ return value;
+ }
+ }
+
+ private getCustomDate(dVal: string): string {
+ const dateFormatStr = dVal
+ .replace('{date:', '')
+ .replace('}', '')
+ .trim();
+ return moment().format(dateFormatStr);
+ }
+
+ private getStartTime() {
+ const val = moment(this.MISSING.startedTimeToday);
+ const roundTo = this.opts.roundStartTimeTo;
+ return this.roundTime(val, roundTo)
+ .format('HH:mm');
+ }
+
+ private getCurrentTime(): string {
+ const val = moment();
+ const roundTo = this.opts.roundEndTimeTo;
+
+ return this.roundTime(val, roundTo)
+ .format('HH:mm');
+ }
+
+ private getTotalTimeWorked(): string {
+ const val = moment.duration(this.MISSING.getTimeWorkedToday);
+
+ const roundTo = this.opts.roundWorkTimeTo;
+ const dur = this.roundDuration(val, roundTo, this.opts.isRoundWorkTimeUp) as any;
+ return dur.format('HH:mm');
+ }
+
+ private getTaskTitles(): string {
+ const tasks = this.MISSING.getToday;
+ let titleStr = '';
+ tasks.forEach((task) => {
+ titleStr += task.title + ', ';
+ });
+ return titleStr.substring(0, titleStr.length - 2);
+ }
+
+ private getSubTaskTitles(): string {
+ const tasks = this.MISSING.getToday;
+ let titleStr = '';
+ tasks.forEach((task) => {
+ if (task.subTasks) {
+ task.subTasks.forEach((subTask) => {
+ titleStr += subTask.title + ', ';
+ });
+ } else {
+ titleStr += task.title + ', ';
+ }
+ });
+ return titleStr.substring(0, titleStr.length - 2);
}
}
diff --git a/src/app/core/google/google-api.service.ts b/src/app/core/google/google-api.service.ts
index 6181ad29a..10d95f194 100644
--- a/src/app/core/google/google-api.service.ts
+++ b/src/app/core/google/google-api.service.ts
@@ -3,7 +3,7 @@ import { GOOGLE_DEFAULT_FIELDS_FOR_DRIVE, GOOGLE_DISCOVERY_DOCS, GOOGLE_SCOPES,
import * as moment from 'moment';
import { IS_ELECTRON } from '../../app.constants';
import { MultiPartBuilder } from './util/multi-part-builder';
-import { HttpClient } from '@angular/common/http';
+import { HttpClient, HttpRequest } from '@angular/common/http';
import { SnackService } from '../snack/snack.service';
import { SnackType } from '../snack/snack.model';
@@ -138,9 +138,9 @@ export class GoogleApiService {
return this._requestWrapper(new Promise((resolve, reject) => {
this.getSpreadsheetData(spreadsheetId, 'A1:Z99')
.then((response: any) => {
- const range = response.result || response.data;
+ const range = response.result || response.data || response;
- if (range.values && range.values[0]) {
+ if (range && range.values && range.values[0]) {
resolve({
headings: range.values[0],
lastRow: range.values[range.values.length - 1],
@@ -364,8 +364,8 @@ export class GoogleApiService {
});
}
- private _mapHttp(params: any): Promise