Skip to content

Commit d73f181

Browse files
authored
feat: Add time entry, include start with create (#165)
* Add time entry, incl start with create * Fixed bug displaying time of current time entry * works with both positional and option specified values
1 parent a5ae7a2 commit d73f181

5 files changed

Lines changed: 117 additions & 26 deletions

File tree

cmds/addTimeEntry.mjs

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
/* eslint-disable no-unused-expressions */
2+
import Client from '../client.js'
3+
import { defaultWorkspaceId, getProjectByName, getProjectById, appName, displayTimeEntry, parseTime } from '../utils.js'
4+
import dayjs from 'dayjs'
5+
import debugClient from 'debug'
6+
import utc from 'dayjs/plugin/utc.js'
7+
import timezone from 'dayjs/plugin/timezone.js'
8+
dayjs.extend(utc)
9+
dayjs.extend(timezone)
10+
11+
const debug = debugClient('toggl-cli-add');
12+
13+
export const command = 'add [startTime] [endTime] [description]'
14+
export const desc = 'Create a time entry. Time must be parsable by dayjs, e.g. 4:50PM or \'12:00 AM\'.'
15+
16+
export const builder = {
17+
d: { alias: ['description'], describe: 'Time entry name', type: 'string:', demandOption: true},
18+
p: { alias: ['projectId', 'project'], describe: 'The case insensitive project name or project id.', type: 'string', demandOption: false },
19+
s: { alias: ['start', 'startTime'], describe: 'The start time for the task, e.g. 13:00 12:45AM.', type: 'string', demandOption: false },
20+
e: { alias: ['end', 'endTime'], describe: 'The end time for the task, e.g. 13:00 12:45AM.', type: 'string', demandOption: false }
21+
}
22+
23+
export const handler = async function (argv) {
24+
const client = await Client()
25+
const params = {}
26+
27+
params.workspace_id = +defaultWorkspaceId
28+
let project
29+
if (argv.projectId) {
30+
if (isNaN(argv.projectId)) {
31+
project = await getProjectByName(params.workspace_id, argv.projectId)
32+
} else {
33+
project = await getProjectById(params.workspace_id, argv.projectId)
34+
}
35+
}
36+
37+
let startTime, endTime
38+
if (dayjs(argv.startTime).isValid()) {
39+
startTime = argv.startTime
40+
} else {
41+
// Parse the time and set it based upon the current time
42+
startTime = parseTime(argv.startTime)
43+
}
44+
45+
if (dayjs(argv.endTime).isValid()) {
46+
endTime = argv.endTime
47+
} else {
48+
// Parse the time and set it based upon the current time
49+
endTime = parseTime(argv.endTime)
50+
}
51+
52+
params.created_with = appName
53+
project ? params.project_id = +project.id : undefined
54+
startTime ? params.start = startTime.toISOString() : undefined
55+
endTime ? params.stop = endTime.toISOString() : undefined
56+
if (startTime || endTime) {
57+
const startTimeUnix = startTime.unix()
58+
const endTimeUnix = endTime.unix()
59+
let duration = endTimeUnix - startTimeUnix
60+
duration = endTime ? duration : startTimeUnix * -1
61+
params.duration = duration
62+
}
63+
argv.description ? params.description = argv.description : undefined
64+
debug(params)
65+
const timeEntry = await client.timeEntries.create(params)
66+
await displayTimeEntry(timeEntry)
67+
}
68+

cmds/edit.mjs

Lines changed: 1 addition & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
/* eslint-disable no-unused-expressions */
22
import Client from '../client.js'
3-
import { defaultWorkspaceId, getProjectByName, getProjectById, appName, displayTimeEntry } from '../utils.js'
3+
import { defaultWorkspaceId, getProjectByName, getProjectById, appName, displayTimeEntry, parseTime } from '../utils.js'
44
import dayjs from 'dayjs'
55
import debugClient from 'debug'
66
import utc from 'dayjs/plugin/utc.js'
@@ -73,21 +73,3 @@ export const handler = async function (argv) {
7373
const timeEntry = await client.timeEntries.update(currentTimeEntry.id, params)
7474
await displayTimeEntry(timeEntry)
7575
}
76-
77-
/**
78-
* Parses a timelike string into a dayjs object of the current date and that time
79-
* @param {string} timeString timelike string e.g. 4:50PM '12:00 AM' etc.
80-
* @returns {object} dayjs object
81-
*/
82-
function parseTime (timeString) {
83-
let h, m
84-
// Assumes time in format 4:50 PM
85-
const time = timeString.split(':', 2)
86-
h = time[0]
87-
m = time[1].match(/[0-9]+/)[0]
88-
if (timeString.match(/PM/i) && h <= 12) {
89-
// + in front of string converts to a number, cool!
90-
h = +h + 12
91-
}
92-
return dayjs().hour(h).minute(m).second(0)
93-
}

cmds/index.mjs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import * as edit from './edit.mjs'
88
import * as web from './web.mjs'
99
import * as startTimeEntry from './startTimeEntry.mjs'
1010
import * as stopTimeEntry from './stopTimeEntry.mjs'
11+
import * as addTimeEntry from './addTimeEntry.mjs'
1112
import * as removeTimeEntry from './removeTimeEntry.mjs'
1213
import * as today from './today.mjs'
1314
import * as weekly from './weekly.mjs'
@@ -21,6 +22,7 @@ export const commands = [
2122
projects,
2223
startTimeEntry,
2324
stopTimeEntry,
25+
addTimeEntry,
2426
removeTimeEntry,
2527
today,
2628
web,

cmds/startTimeEntry.mjs

Lines changed: 17 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
1-
import { defaultWorkspaceId,getProjectByName, createTimeEntry, getProjectById, defaultProjectId } from '../utils.js'
1+
import { defaultWorkspaceId, getProjectByName, createTimeEntry, getProjectById, defaultProjectId, parseTime } from '../utils.js'
2+
import dayjs from 'dayjs'
23

34
export const command = 'start'
45
export const desc = 'Starts a time entry'
@@ -10,7 +11,8 @@ export const builder = {
1011
},
1112
p: { alias: ['projectId', 'project'], describe: 'The case insensitive project name or project id.', type: 'string', demandOption: false },
1213
// TODO default to default workspace
13-
w: { alias: ['workspaceId', 'workspace'], describe: 'The case insensitive workspace name or workspace id.', type: 'number', demandOption: false }
14+
w: { alias: ['workspaceId', 'workspace'], describe: 'The case insensitive workspace name or workspace id.', type: 'number', demandOption: false },
15+
s: { alias: ['start', 'startTime'], describe: 'The start time for the task, e.g. 13:00 12:45AM.', type: 'string', demandOption: false },
1416
}
1517

1618
export const handler = async function (argv) {
@@ -31,6 +33,19 @@ export const handler = async function (argv) {
3133
}
3234
}
3335

36+
if (argv.startTime) {
37+
let startTime;
38+
if (dayjs(argv.startTime).isValid()) {
39+
startTime = argv.startTime
40+
} else {
41+
// Parse the time and set it based upon the current time
42+
startTime = parseTime(argv.startTime)
43+
}
44+
45+
params.start = startTime.toISOString()
46+
params.duration = -1
47+
}
48+
3449
params.projectId = project?.id || defaultProjectId || null
3550
// TODO check for invalid projectId or catch the error when creating fails
3651
const timeEntry = await createTimeEntry(params)

utils.js

Lines changed: 29 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,7 @@ export const createTimeEntry = async function (params) {
5353
description: params.description,
5454
workspace_id: +params.workspaceId,
5555
project_id: +params.projectId,
56-
start: dayjs().toISOString(),
56+
start: params.start ? params.start : dayjs().toISOString(),
5757
duration: -1 * dayjs().unix(),
5858
created_with: appName,
5959
at: dayjs().toISOString()
@@ -113,11 +113,15 @@ export const displayTimeEntry = async function (timeEntry) {
113113
console.info(`${chalk.blueBright(timeEntry.description ? timeEntry.description : 'no description')} ${chalk.yellow('#'+timeEntry.id)}`)
114114
console.info(`Billable: ${chalk.gray(timeEntry.billable)}`)
115115

116-
// TODO this should be abstracted for reuse
117-
const startTime = dayjs.unix(timeEntry.duration * -1)
118-
const duration = dayjs().diff(startTime, 's')
119-
const durationFormatted = dayjs.duration(duration * 1000).format('H[h] m[m]')
116+
let stopTime;
117+
if (timeEntry.stop == null) {
118+
stopTime = dayjs()
119+
} else {
120+
stopTime = dayjs(timeEntry.stop)
121+
}
120122

123+
const duration = stopTime.diff(dayjs(timeEntry.start))
124+
const durationFormatted = dayjs.duration(duration).format('H[h] m[m]')
121125
console.info(`Duration: ${chalk.green(durationFormatted)}`)
122126

123127
const projects = await getProjects(timeEntry.wid)
@@ -138,3 +142,23 @@ export const displayTimeEntry = async function (timeEntry) {
138142
console.info(`Workspace: ${workspace.name} (#${timeEntry.wid})`)
139143
}
140144
}
145+
146+
/**
147+
* Parses a timelike string into a dayjs object of the current date and that time
148+
* @param {string} timeString timelike string e.g. 4:50PM '12:00 AM' etc.
149+
* @returns {object} dayjs object
150+
*/
151+
export function parseTime (timeString) {
152+
let h, m
153+
// Assumes time in format 4:50 PM
154+
const time = timeString.split(':', 2)
155+
h = time[0]
156+
m = time[1].match(/[0-9]+/)[0]
157+
if (timeString.match(/PM/i) && h <= 12) {
158+
// + in front of string converts to a number, cool!
159+
h = +h + 12
160+
} else if (h == 12) {
161+
h = 0
162+
}
163+
return dayjs().hour(h).minute(m).second(0).millisecond(0)
164+
}

0 commit comments

Comments
 (0)