diff --git a/cmds/addTimeEntry.mjs b/cmds/addTimeEntry.mjs new file mode 100644 index 0000000..01fe8bd --- /dev/null +++ b/cmds/addTimeEntry.mjs @@ -0,0 +1,68 @@ +/* eslint-disable no-unused-expressions */ +import Client from '../client.js' +import { defaultWorkspaceId, getProjectByName, getProjectById, appName, displayTimeEntry, parseTime } from '../utils.js' +import dayjs from 'dayjs' +import debugClient from 'debug' +import utc from 'dayjs/plugin/utc.js' +import timezone from 'dayjs/plugin/timezone.js' +dayjs.extend(utc) +dayjs.extend(timezone) + +const debug = debugClient('toggl-cli-add'); + +export const command = 'add [startTime] [endTime] [description]' +export const desc = 'Create a time entry. Time must be parsable by dayjs, e.g. 4:50PM or \'12:00 AM\'.' + +export const builder = { + d: { alias: ['description'], describe: 'Time entry name', type: 'string:', demandOption: true}, + p: { alias: ['projectId', 'project'], describe: 'The case insensitive project name or project id.', type: 'string', demandOption: false }, + s: { alias: ['start', 'startTime'], describe: 'The start time for the task, e.g. 13:00 12:45AM.', type: 'string', demandOption: false }, + e: { alias: ['end', 'endTime'], describe: 'The end time for the task, e.g. 13:00 12:45AM.', type: 'string', demandOption: false } +} + +export const handler = async function (argv) { + const client = await Client() + const params = {} + + params.workspace_id = +defaultWorkspaceId + let project + if (argv.projectId) { + if (isNaN(argv.projectId)) { + project = await getProjectByName(params.workspace_id, argv.projectId) + } else { + project = await getProjectById(params.workspace_id, argv.projectId) + } + } + + let startTime, endTime + if (dayjs(argv.startTime).isValid()) { + startTime = argv.startTime + } else { + // Parse the time and set it based upon the current time + startTime = parseTime(argv.startTime) + } + + if (dayjs(argv.endTime).isValid()) { + endTime = argv.endTime + } else { + // Parse the time and set it based upon the current time + endTime = parseTime(argv.endTime) + } + + params.created_with = appName + project ? params.project_id = +project.id : undefined + startTime ? params.start = startTime.toISOString() : undefined + endTime ? params.stop = endTime.toISOString() : undefined + if (startTime || endTime) { + const startTimeUnix = startTime.unix() + const endTimeUnix = endTime.unix() + let duration = endTimeUnix - startTimeUnix + duration = endTime ? duration : startTimeUnix * -1 + params.duration = duration + } + argv.description ? params.description = argv.description : undefined + debug(params) + const timeEntry = await client.timeEntries.create(params) + await displayTimeEntry(timeEntry) +} + diff --git a/cmds/edit.mjs b/cmds/edit.mjs index 6bf6958..180f012 100644 --- a/cmds/edit.mjs +++ b/cmds/edit.mjs @@ -1,6 +1,6 @@ /* eslint-disable no-unused-expressions */ import Client from '../client.js' -import { defaultWorkspaceId, getProjectByName, getProjectById, appName, displayTimeEntry } from '../utils.js' +import { defaultWorkspaceId, getProjectByName, getProjectById, appName, displayTimeEntry, parseTime } from '../utils.js' import dayjs from 'dayjs' import debugClient from 'debug' import utc from 'dayjs/plugin/utc.js' @@ -73,21 +73,3 @@ export const handler = async function (argv) { const timeEntry = await client.timeEntries.update(currentTimeEntry.id, params) await displayTimeEntry(timeEntry) } - -/** - * Parses a timelike string into a dayjs object of the current date and that time - * @param {string} timeString timelike string e.g. 4:50PM '12:00 AM' etc. - * @returns {object} dayjs object - */ -function parseTime (timeString) { - let h, m - // Assumes time in format 4:50 PM - const time = timeString.split(':', 2) - h = time[0] - m = time[1].match(/[0-9]+/)[0] - if (timeString.match(/PM/i) && h <= 12) { - // + in front of string converts to a number, cool! - h = +h + 12 - } - return dayjs().hour(h).minute(m).second(0) -} diff --git a/cmds/index.mjs b/cmds/index.mjs index 1634922..43f5fcc 100644 --- a/cmds/index.mjs +++ b/cmds/index.mjs @@ -8,6 +8,7 @@ import * as edit from './edit.mjs' import * as web from './web.mjs' import * as startTimeEntry from './startTimeEntry.mjs' import * as stopTimeEntry from './stopTimeEntry.mjs' +import * as addTimeEntry from './addTimeEntry.mjs' import * as removeTimeEntry from './removeTimeEntry.mjs' import * as today from './today.mjs' import * as weekly from './weekly.mjs' @@ -21,6 +22,7 @@ export const commands = [ projects, startTimeEntry, stopTimeEntry, + addTimeEntry, removeTimeEntry, today, web, diff --git a/cmds/startTimeEntry.mjs b/cmds/startTimeEntry.mjs index 2860d8d..e85dac4 100644 --- a/cmds/startTimeEntry.mjs +++ b/cmds/startTimeEntry.mjs @@ -1,4 +1,5 @@ -import { defaultWorkspaceId,getProjectByName, createTimeEntry, getProjectById, defaultProjectId } from '../utils.js' +import { defaultWorkspaceId, getProjectByName, createTimeEntry, getProjectById, defaultProjectId, parseTime } from '../utils.js' +import dayjs from 'dayjs' export const command = 'start' export const desc = 'Starts a time entry' @@ -10,7 +11,8 @@ export const builder = { }, p: { alias: ['projectId', 'project'], describe: 'The case insensitive project name or project id.', type: 'string', demandOption: false }, // TODO default to default workspace - w: { alias: ['workspaceId', 'workspace'], describe: 'The case insensitive workspace name or workspace id.', type: 'number', demandOption: false } + w: { alias: ['workspaceId', 'workspace'], describe: 'The case insensitive workspace name or workspace id.', type: 'number', demandOption: false }, + s: { alias: ['start', 'startTime'], describe: 'The start time for the task, e.g. 13:00 12:45AM.', type: 'string', demandOption: false }, } export const handler = async function (argv) { @@ -31,6 +33,19 @@ export const handler = async function (argv) { } } + if (argv.startTime) { + let startTime; + if (dayjs(argv.startTime).isValid()) { + startTime = argv.startTime + } else { + // Parse the time and set it based upon the current time + startTime = parseTime(argv.startTime) + } + + params.start = startTime.toISOString() + params.duration = -1 + } + params.projectId = project?.id || defaultProjectId || null // TODO check for invalid projectId or catch the error when creating fails const timeEntry = await createTimeEntry(params) diff --git a/utils.js b/utils.js index c4efbb0..42dcf01 100644 --- a/utils.js +++ b/utils.js @@ -53,7 +53,7 @@ export const createTimeEntry = async function (params) { description: params.description, workspace_id: +params.workspaceId, project_id: +params.projectId, - start: dayjs().toISOString(), + start: params.start ? params.start : dayjs().toISOString(), duration: -1 * dayjs().unix(), created_with: appName, at: dayjs().toISOString() @@ -113,11 +113,15 @@ export const displayTimeEntry = async function (timeEntry) { console.info(`${chalk.blueBright(timeEntry.description ? timeEntry.description : 'no description')} ${chalk.yellow('#'+timeEntry.id)}`) console.info(`Billable: ${chalk.gray(timeEntry.billable)}`) - // TODO this should be abstracted for reuse - const startTime = dayjs.unix(timeEntry.duration * -1) - const duration = dayjs().diff(startTime, 's') - const durationFormatted = dayjs.duration(duration * 1000).format('H[h] m[m]') + let stopTime; + if (timeEntry.stop == null) { + stopTime = dayjs() + } else { + stopTime = dayjs(timeEntry.stop) + } + const duration = stopTime.diff(dayjs(timeEntry.start)) + const durationFormatted = dayjs.duration(duration).format('H[h] m[m]') console.info(`Duration: ${chalk.green(durationFormatted)}`) const projects = await getProjects(timeEntry.wid) @@ -138,3 +142,23 @@ export const displayTimeEntry = async function (timeEntry) { console.info(`Workspace: ${workspace.name} (#${timeEntry.wid})`) } } + +/** + * Parses a timelike string into a dayjs object of the current date and that time + * @param {string} timeString timelike string e.g. 4:50PM '12:00 AM' etc. + * @returns {object} dayjs object + */ +export function parseTime (timeString) { + let h, m + // Assumes time in format 4:50 PM + const time = timeString.split(':', 2) + h = time[0] + m = time[1].match(/[0-9]+/)[0] + if (timeString.match(/PM/i) && h <= 12) { + // + in front of string converts to a number, cool! + h = +h + 12 + } else if (h == 12) { + h = 0 + } + return dayjs().hour(h).minute(m).second(0).millisecond(0) +}