@beauraines/toggl-cli 0.10.22 → 2.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +12 -0
- package/README.md +38 -51
- package/client.js +23 -19
- package/cmds/continue.mjs +1 -1
- package/cmds/create-config.mjs +28 -0
- package/cmds/currentTimeEntry.mjs +1 -1
- package/cmds/edit.mjs +1 -1
- package/cmds/index.mjs +3 -1
- package/cmds/ls.mjs +1 -1
- package/cmds/me.mjs +1 -1
- package/cmds/projects/list.mjs +1 -1
- package/cmds/stopTimeEntry.mjs +1 -1
- package/cmds/today.mjs +1 -1
- package/cmds/weekly.mjs +1 -1
- package/cmds/workspaces/list.mjs +1 -1
- package/config.js +82 -0
- package/package.json +2 -2
- package/standalone/currentTimeEntry.js +1 -1
- package/standalone/startTimeEntry.js +1 -1
- package/utils.js +17 -11
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,18 @@
|
|
|
2
2
|
|
|
3
3
|
All notable changes to this project will be documented in this file. See [standard-version](https://github.com/conventional-changelog/standard-version) for commit guidelines.
|
|
4
4
|
|
|
5
|
+
## [2.0.0](https://github.com/beauraines/toggl-cli/compare/v0.10.22...v2.0.0) (2023-07-08)
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
### ⚠ BREAKING CHANGES
|
|
9
|
+
|
|
10
|
+
* ready for version 1.0.0
|
|
11
|
+
Reads configuration (API token, timezone, default workspace and projects) from `.toggl-cli.json` from the users home. Adds a command to create the configuration file.
|
|
12
|
+
|
|
13
|
+
### Features
|
|
14
|
+
|
|
15
|
+
* Adds configuration file ([#91](https://github.com/beauraines/toggl-cli/issues/91)) ([b7d70bf](https://github.com/beauraines/toggl-cli/commit/b7d70bfdb60aa9353e4da18f85e8b279ddf8bf34))
|
|
16
|
+
|
|
5
17
|
### [0.10.22](https://github.com/beauraines/toggl-cli/compare/v0.10.21...v0.10.22) (2023-07-08)
|
|
6
18
|
|
|
7
19
|
|
package/README.md
CHANGED
|
@@ -8,65 +8,52 @@ This was made possible because [saintedlama](https://github.com/saintedlama) had
|
|
|
8
8
|
|
|
9
9
|
## Configuration
|
|
10
10
|
|
|
11
|
-
|
|
11
|
+
### Configuration File
|
|
12
|
+
|
|
13
|
+
You can create the `.toggl-cli.json` in your home directory manually using the template below or with the `toggl create-config` command. The values can be found in your Toggl [profile](https://track.toggl.com/profile) and from the URL for your account settings. The current workspace id is `https://track.toggl.com/${workspace_id}}/settings/general`. Don't forget to change the permissions `chmod 600` so that only you can read/write the configuration file as it has your API token.
|
|
14
|
+
|
|
15
|
+
```json
|
|
16
|
+
{
|
|
17
|
+
"api_token": "",
|
|
18
|
+
"default_workspace_id": "",
|
|
19
|
+
"timezone": "",
|
|
20
|
+
"default_project_id": ""
|
|
21
|
+
}
|
|
22
|
+
```
|
|
23
|
+
### Environment Variables
|
|
24
|
+
|
|
25
|
+
Configure your environment, with environment variables or a `.env` file in the project root. Environment variables will take precedence over a config file.
|
|
26
|
+
|
|
12
27
|
1. `TOGGL_API_TOKEN` (required)
|
|
13
28
|
2. `TOGGL_DEFAULT_WORKSPACE_ID` (required)
|
|
14
29
|
3. `TOGGL_DEFAULT_PROJECT_ID` (optional)
|
|
15
30
|
4. `TOGGL_TIMEZONE=America/Los_Angeles` (defaults to `America/New_York`)
|
|
16
|
-
## Dependencies
|
|
17
|
-
|
|
18
|
-
1. toggl-client - Used from [saintedlama/toggl-client](https://github.com/saintedlama/toggl-client) repository as the latest code hasn't been released to npm
|
|
19
|
-
3. dotenv
|
|
20
|
-
4. yargs
|
|
21
|
-
|
|
22
31
|
|
|
23
32
|
|
|
24
33
|
## Features
|
|
25
34
|
|
|
26
|
-
| Feature
|
|
27
|
-
|
|
|
28
|
-
| Start time entry
|
|
29
|
-
| Start time entry with description
|
|
30
|
-
| Start time entry with project
|
|
31
|
-
| stop time entry
|
|
32
|
-
| Continue named time entry
|
|
33
|
-
| Report today by project
|
|
34
|
-
| Report this week by project by day
|
|
35
|
-
| Edit time entry
|
|
36
|
-
| Use config from file
|
|
37
|
-
| Save config to file
|
|
38
|
-
| Refactor: Display and format modules
|
|
39
|
-
| Client: reset PAT
|
|
40
|
-
| Client:
|
|
41
|
-
|
|
|
42
|
-
|
|
|
43
|
-
|
|
|
44
|
-
|
|
|
45
|
-
|
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
Priority order... I think.
|
|
50
|
-
|
|
51
|
-
1. ~today - improve the output~
|
|
52
|
-
2. ~weekly - improve the output format~
|
|
53
|
-
3. ~now - format the display time entry output~
|
|
54
|
-
4. ~toggl continue~
|
|
55
|
-
5. ~duration to display helper function?~
|
|
56
|
-
6. read configuration (token, default workspace) from files (client and utils)
|
|
57
|
-
7. now - update running description, project, start time
|
|
58
|
-
8. now - update start time with overlap detection and adjust prior time entry
|
|
59
|
-
9. project list - format the output, group by client
|
|
60
|
-
10. Add ability to lookup project by name not just id
|
|
61
|
-
11. start - add project name to output, add time started
|
|
62
|
-
12. toggl workspace add - this command is not yet supported.
|
|
63
|
-
13. toggl workspace list - improve output
|
|
64
|
-
14. colorized output
|
|
65
|
-
15. config - some way to save /me information to a file
|
|
66
|
-
1. default workspace?
|
|
67
|
-
2. API key?
|
|
68
|
-
3. display formats?
|
|
69
|
-
16. toggl project add - this command is not yet supported.
|
|
35
|
+
| Feature | Available | Comments |
|
|
36
|
+
| ---------------------------------------- | --------- | ----------------------------------------------------------- |
|
|
37
|
+
| Start time entry | ✅ | |
|
|
38
|
+
| Start time entry with description | ✅ | |
|
|
39
|
+
| Start time entry with project | ✅ | |
|
|
40
|
+
| stop time entry | ✅ | |
|
|
41
|
+
| Continue named time entry | ✅ | |
|
|
42
|
+
| Report today by project | ✅ | |
|
|
43
|
+
| Report this week by project by day | ✅ | |
|
|
44
|
+
| Edit current time entry | ✅ | |
|
|
45
|
+
| Use config from file | ✅ | |
|
|
46
|
+
| Save config to file | ✅ | |
|
|
47
|
+
| Refactor: Display and format modules | | |
|
|
48
|
+
| Client: reset PAT | | Is this really necessary? |
|
|
49
|
+
| Client: specify client name | ✅ | |
|
|
50
|
+
| Colorize output | ✅ | |
|
|
51
|
+
| Better table output | ✅ | |
|
|
52
|
+
| List recent time entries | ✅ | |
|
|
53
|
+
| Command line completion | ✅ | [#6](https://github.com/beauraines/toggl-cli-node/issues/6) |
|
|
54
|
+
| Delete time entry by id | | |
|
|
55
|
+
| Edit other time entries than the current | | |
|
|
56
|
+
| Display earlier time entries | | |
|
|
70
57
|
|
|
71
58
|
|
|
72
59
|
|
package/client.js
CHANGED
|
@@ -1,29 +1,33 @@
|
|
|
1
1
|
import dotenv from 'dotenv'
|
|
2
2
|
import togglClient from 'toggl-client'
|
|
3
|
+
import { readConfig } from './config.js'
|
|
3
4
|
dotenv.config()
|
|
5
|
+
import debugClient from 'debug'
|
|
6
|
+
const debug = debugClient('toggl-cli-client')
|
|
4
7
|
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
8
|
+
|
|
9
|
+
export default async function () {
|
|
10
|
+
let conf
|
|
11
|
+
try {
|
|
12
|
+
conf = await readConfig('.toggl-cli.json')
|
|
13
|
+
debug(conf)
|
|
14
|
+
} catch (error) {
|
|
15
|
+
console.error('Using config from environment variables or create one with the create-config command')
|
|
9
16
|
}
|
|
10
17
|
|
|
11
|
-
|
|
18
|
+
const apiToken = process.env.TOGGL_API_TOKEN || conf?.api_token
|
|
19
|
+
debug(apiToken)
|
|
12
20
|
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
// console.error(error);
|
|
22
|
-
// }
|
|
23
|
-
if (!client) {
|
|
24
|
-
console.error('There was a problem')
|
|
25
|
-
process.exit(1)
|
|
26
|
-
}
|
|
21
|
+
let client
|
|
22
|
+
try {
|
|
23
|
+
client = togglClient({ apiToken });
|
|
24
|
+
} catch (error) {
|
|
25
|
+
console.error(error.message);
|
|
26
|
+
console.error('There was a problem')
|
|
27
|
+
process.exit(1)
|
|
28
|
+
}
|
|
27
29
|
|
|
28
30
|
return client
|
|
29
31
|
}
|
|
32
|
+
|
|
33
|
+
|
package/cmds/continue.mjs
CHANGED
|
@@ -16,7 +16,7 @@ export const builder = {
|
|
|
16
16
|
}
|
|
17
17
|
|
|
18
18
|
export const handler = async function (argv) {
|
|
19
|
-
const client = Client()
|
|
19
|
+
const client = await Client()
|
|
20
20
|
const timeEntries = await client.timeEntries.list(
|
|
21
21
|
{
|
|
22
22
|
start_date: dayjs().subtract(14, 'days').toISOString(),
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { createConfig } from '../config.js'
|
|
2
|
+
import debugClient from 'debug';
|
|
3
|
+
|
|
4
|
+
const debug = debugClient('toggl-cli-create-config');
|
|
5
|
+
|
|
6
|
+
export const command = 'create-config'
|
|
7
|
+
export const desc = 'Creates a boilerplate .toggl-cli.json configuration file in your home directory.'
|
|
8
|
+
export const builder = {}
|
|
9
|
+
|
|
10
|
+
export const handler = async function (argv) {
|
|
11
|
+
const configProps = [
|
|
12
|
+
"api_token",
|
|
13
|
+
"default_workspace_id",
|
|
14
|
+
"timezone",
|
|
15
|
+
"default_project_id"
|
|
16
|
+
]
|
|
17
|
+
|
|
18
|
+
let configFile
|
|
19
|
+
try {
|
|
20
|
+
configFile = await createConfig('.toggl-cli.json',configProps)
|
|
21
|
+
console.log(`Configuration file written to ${configFile} in your home directory`)
|
|
22
|
+
console.log(`Edit with your user information from Toggl.`)
|
|
23
|
+
} catch (error) {
|
|
24
|
+
console.error(error.message)
|
|
25
|
+
process.exit(1)
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
}
|
|
@@ -9,7 +9,7 @@ export const desc = 'Displays the current running time entry'
|
|
|
9
9
|
export const builder = {}
|
|
10
10
|
|
|
11
11
|
export const handler = async function (argv) {
|
|
12
|
-
const client =
|
|
12
|
+
const client = await Client()
|
|
13
13
|
const currentTimeEntry = await client.timeEntries.current()
|
|
14
14
|
if (currentTimeEntry) {
|
|
15
15
|
debug(currentTimeEntry)
|
package/cmds/edit.mjs
CHANGED
|
@@ -27,7 +27,7 @@ export const handler = async function (argv) {
|
|
|
27
27
|
yargs().help()
|
|
28
28
|
yargs().exit(1, new Error('At least one option must be provided, description, project, start or end'))
|
|
29
29
|
}
|
|
30
|
-
const client =
|
|
30
|
+
const client = await Client()
|
|
31
31
|
const currentTimeEntry = await client.timeEntries.current()
|
|
32
32
|
debug(currentTimeEntry)
|
|
33
33
|
const params = {}
|
package/cmds/index.mjs
CHANGED
|
@@ -10,6 +10,7 @@ import * as startTimeEntry from './startTimeEntry.mjs'
|
|
|
10
10
|
import * as stopTimeEntry from './stopTimeEntry.mjs'
|
|
11
11
|
import * as today from './today.mjs'
|
|
12
12
|
import * as weekly from './weekly.mjs'
|
|
13
|
+
import * as createConfig from './create-config.mjs'
|
|
13
14
|
export const commands = [
|
|
14
15
|
continueEntry,
|
|
15
16
|
current,
|
|
@@ -22,5 +23,6 @@ export const commands = [
|
|
|
22
23
|
today,
|
|
23
24
|
web,
|
|
24
25
|
weekly,
|
|
25
|
-
workspace
|
|
26
|
+
workspace,
|
|
27
|
+
createConfig
|
|
26
28
|
]
|
package/cmds/ls.mjs
CHANGED
|
@@ -16,7 +16,7 @@ export const builder = {
|
|
|
16
16
|
|
|
17
17
|
export const handler = async function (argv) {
|
|
18
18
|
debug(argv)
|
|
19
|
-
const client = Client()
|
|
19
|
+
const client = await Client()
|
|
20
20
|
const days = argv.today ? 0 : argv.days
|
|
21
21
|
let timeEntries = await client.timeEntries.list({
|
|
22
22
|
start_date: dayjs().subtract(days, 'days').startOf('day').toISOString(),
|
package/cmds/me.mjs
CHANGED
|
@@ -6,7 +6,7 @@ export const desc = 'Displays the current user'
|
|
|
6
6
|
export const builder = {}
|
|
7
7
|
|
|
8
8
|
export const handler = async function (argv) {
|
|
9
|
-
const client = Client()
|
|
9
|
+
const client = await Client()
|
|
10
10
|
const currentUser = await client.user.current()
|
|
11
11
|
// console.log(currentUser);
|
|
12
12
|
|
package/cmds/projects/list.mjs
CHANGED
package/cmds/stopTimeEntry.mjs
CHANGED
|
@@ -6,7 +6,7 @@ export const desc = 'Stops the current running time entry'
|
|
|
6
6
|
export const builder = {}
|
|
7
7
|
|
|
8
8
|
export const handler = async function (argv) {
|
|
9
|
-
const client = Client()
|
|
9
|
+
const client = await Client()
|
|
10
10
|
const currentTimeEntry = await client.timeEntries.current()
|
|
11
11
|
if (currentTimeEntry) {
|
|
12
12
|
const stopped = await client.timeEntries.stop(currentTimeEntry)
|
package/cmds/today.mjs
CHANGED
|
@@ -13,7 +13,7 @@ export const desc = 'Reports today\'s activities by project'
|
|
|
13
13
|
export const builder = {}
|
|
14
14
|
|
|
15
15
|
export const handler = async function (argv) {
|
|
16
|
-
const client = Client()
|
|
16
|
+
const client = await Client()
|
|
17
17
|
const workspace = await getWorkspace()
|
|
18
18
|
const projects = await getProjects(workspace.id)
|
|
19
19
|
const params = {
|
package/cmds/weekly.mjs
CHANGED
package/cmds/workspaces/list.mjs
CHANGED
package/config.js
ADDED
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
import fs from 'fs';
|
|
2
|
+
import {homedir} from 'os';
|
|
3
|
+
import path from 'path';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Reads the specified config file from the users home directory
|
|
7
|
+
*
|
|
8
|
+
* @param {string} configFile file name for the JSON config file, from the users home directory
|
|
9
|
+
*
|
|
10
|
+
* @returns {Promise} the configuration object
|
|
11
|
+
*
|
|
12
|
+
* @throws {Error} if configuration file not found.
|
|
13
|
+
*/
|
|
14
|
+
export const readConfig = async (configFile) => {
|
|
15
|
+
configFile = path.join(homedir(),configFile)
|
|
16
|
+
let config
|
|
17
|
+
if ( await fs.existsSync(configFile) ) {
|
|
18
|
+
config = fs.readFileSync(configFile,
|
|
19
|
+
{ encoding: 'utf8', flag: 'r' });
|
|
20
|
+
config = JSON.parse(config);
|
|
21
|
+
} else {
|
|
22
|
+
throw new Error(`Config file not found. You must create one using the config command`)
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
return config
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Validates the config file to ensure that it has all of the required properties specified. This
|
|
30
|
+
* is only schema validation, it does not check if the properties are valid values or data types.
|
|
31
|
+
*
|
|
32
|
+
* @param {string} configFile file name for the JSON config file, from the users home directory
|
|
33
|
+
* @param {Array} configProps an array of properties that defines a valid config file
|
|
34
|
+
*
|
|
35
|
+
* @throws {Configuration file not found}
|
|
36
|
+
* @throws {Invalid configuration file}
|
|
37
|
+
* @returns {boolean}
|
|
38
|
+
*/
|
|
39
|
+
export const validateConfig = async (configFile, configProps) => {
|
|
40
|
+
configFile = path.join(homedir(),configFile)
|
|
41
|
+
if (! fs.existsSync(configFile) ) {
|
|
42
|
+
throw new Error('Configuration file not found')
|
|
43
|
+
}
|
|
44
|
+
let config
|
|
45
|
+
config = fs.readFileSync(configFile,
|
|
46
|
+
{ encoding: 'utf8', flag: 'r' });
|
|
47
|
+
config = JSON.parse(config);
|
|
48
|
+
// Check for properties
|
|
49
|
+
let validConfig = true
|
|
50
|
+
for (const key of configProps) {
|
|
51
|
+
validConfig = config[key] ? true : false
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
if (!validConfig) {
|
|
55
|
+
throw Error('Invalid configuration file')
|
|
56
|
+
}
|
|
57
|
+
return validConfig
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* Creates a boilerplate config file in the user's home directory
|
|
62
|
+
*
|
|
63
|
+
* @param {string} configFile file name for the JSON config file, from the users home directory
|
|
64
|
+
* @param {Array} configProps an array of properties that defines a valid config file
|
|
65
|
+
*
|
|
66
|
+
* @returns {string} Fully qualified path to configuration file
|
|
67
|
+
*
|
|
68
|
+
* @throws {error} if the file already exists
|
|
69
|
+
*
|
|
70
|
+
*/
|
|
71
|
+
export const createConfig = async (configFile,configProps) => {
|
|
72
|
+
configFile = path.join(homedir(),configFile)
|
|
73
|
+
if ( await fs.existsSync(configFile) ) {
|
|
74
|
+
throw Error('File already exists. Will not overwrite')
|
|
75
|
+
}
|
|
76
|
+
let config = {}
|
|
77
|
+
for (const key of configProps) {
|
|
78
|
+
config[key] = ''
|
|
79
|
+
}
|
|
80
|
+
fs.writeFileSync(configFile,JSON.stringify(config, null, 2))
|
|
81
|
+
return configFile
|
|
82
|
+
}
|
package/package.json
CHANGED
package/utils.js
CHANGED
|
@@ -4,43 +4,49 @@ import dayjs from 'dayjs'
|
|
|
4
4
|
import utc from "dayjs/plugin/utc.js";
|
|
5
5
|
import timezone from 'dayjs/plugin/timezone.js';
|
|
6
6
|
import duration from 'dayjs/plugin/duration.js';
|
|
7
|
+
import { readConfig } from './config.js'
|
|
7
8
|
dayjs.extend(utc);
|
|
8
9
|
dayjs.extend(timezone);
|
|
9
10
|
dayjs.extend(duration);
|
|
10
11
|
|
|
11
|
-
|
|
12
|
-
|
|
12
|
+
let conf
|
|
13
|
+
try {
|
|
14
|
+
conf = await readConfig('.toggl-cli.json')
|
|
15
|
+
} catch (error) {
|
|
16
|
+
console.error('Using config from environment variables or create one with the create-config command')
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export const defaultWorkspaceId = process.env.TOGGL_DEFAULT_WORKSPACE_ID || conf.default_workspace_id
|
|
13
20
|
|
|
14
|
-
|
|
15
|
-
export const defaultProjectId = process.env.TOGGL_DEFAULT_PROJECT_ID
|
|
21
|
+
export const defaultProjectId = process.env.TOGGL_DEFAULT_PROJECT_ID || conf.default_project_id
|
|
16
22
|
|
|
17
23
|
export const getProjects = async function (workspaceId) {
|
|
18
|
-
const client = Client()
|
|
24
|
+
const client = await Client()
|
|
19
25
|
const projects = await client.workspaces.projects(workspaceId)
|
|
20
26
|
const activeProjects = projects.filter(x => x.active)
|
|
21
27
|
return activeProjects
|
|
22
28
|
}
|
|
23
29
|
|
|
24
30
|
export const getWorkspace = async function () {
|
|
25
|
-
const client = Client()
|
|
31
|
+
const client = await Client()
|
|
26
32
|
const workspaces = await client.workspaces.list()
|
|
27
33
|
return workspaces[0]
|
|
28
34
|
}
|
|
29
35
|
|
|
30
36
|
export const getProjectByName = async function (workspaceId, string) {
|
|
31
|
-
const client = Client()
|
|
37
|
+
const client = await Client()
|
|
32
38
|
const projects = await client.workspaces.projects(workspaceId)
|
|
33
39
|
return projects.find(x => x.name.toLowerCase().includes(string.toLowerCase()))
|
|
34
40
|
}
|
|
35
41
|
|
|
36
42
|
export const getProjectById = async function (workspaceId, projectId) {
|
|
37
|
-
const client = Client()
|
|
43
|
+
const client = await Client()
|
|
38
44
|
const projects = await client.workspaces.projects(workspaceId)
|
|
39
45
|
return projects.find(x => x.id == projectId)
|
|
40
46
|
}
|
|
41
47
|
|
|
42
48
|
export const createTimeEntry = async function (params) {
|
|
43
|
-
const client = Client()
|
|
49
|
+
const client = await Client()
|
|
44
50
|
|
|
45
51
|
const timeEntry = await client.timeEntries.create(
|
|
46
52
|
{
|
|
@@ -90,7 +96,7 @@ export const formatDurationAsTime = function (milliseconds) {
|
|
|
90
96
|
* @returns {String}
|
|
91
97
|
*/
|
|
92
98
|
export const convertUtcTime = function (dateTime) {
|
|
93
|
-
const tz = process.env.TOGGL_TIMEZONE || 'America/New_York'
|
|
99
|
+
const tz = process.env.TOGGL_TIMEZONE || conf?.timezone || 'America/New_York'
|
|
94
100
|
return dayjs(dateTime).tz(tz).format('YYYY-MM-DD HH:mm')
|
|
95
101
|
}
|
|
96
102
|
|
|
@@ -119,7 +125,7 @@ export const displayTimeEntry = async function (timeEntry) {
|
|
|
119
125
|
|
|
120
126
|
console.info(`Project: ${project?.name} (#${timeEntry.pid})`);
|
|
121
127
|
|
|
122
|
-
const tz = process.env.TOGGL_TIMEZONE || 'America/New_York'
|
|
128
|
+
const tz = process.env.TOGGL_TIMEZONE || conf?.timezone || 'America/New_York'
|
|
123
129
|
const startTimeFormatted = dayjs(timeEntry.start).tz(tz).format('YYYY-MM-DD HH:mm')
|
|
124
130
|
const stopTimeFormatted = timeEntry.stop ? dayjs(timeEntry.stop).tz(tz).format('YYYY-MM-DD HH:mm') : 'Currently Running'
|
|
125
131
|
|