mirror of
https://github.com/DRYTRIX/TimeTracker.git
synced 2026-05-19 04:40:32 -05:00
feat(desktop): add state module, notifications UI, and esbuild bundle
- Add state.js and ui/notifications.js; update app.js and api client - Bundle with esbuild; update package.json dependencies - Improve connection status and accessible notifications (aria-live/role)
This commit is contained in:
Generated
+444
-3
@@ -1,12 +1,12 @@
|
||||
{
|
||||
"name": "timetracker-desktop",
|
||||
"version": "4.10.1",
|
||||
"version": "4.20.9",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "timetracker-desktop",
|
||||
"version": "4.10.1",
|
||||
"version": "4.20.9",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"axios": "^1.6.2",
|
||||
@@ -15,7 +15,8 @@
|
||||
},
|
||||
"devDependencies": {
|
||||
"electron": "^35.7.5",
|
||||
"electron-builder": "^25.1.8"
|
||||
"electron-builder": "^25.1.8",
|
||||
"esbuild": "^0.24.2"
|
||||
}
|
||||
},
|
||||
"node_modules/@develar/schema-utils": {
|
||||
@@ -355,6 +356,406 @@
|
||||
"node": ">= 10.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/aix-ppc64": {
|
||||
"version": "0.24.2",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.24.2.tgz",
|
||||
"integrity": "sha512-thpVCb/rhxE/BnMLQ7GReQLLN8q9qbHmI55F4489/ByVg2aQaQ6kbcLb6FHkocZzQhxc4gx0sCk0tJkKBFzDhA==",
|
||||
"cpu": [
|
||||
"ppc64"
|
||||
],
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"os": [
|
||||
"aix"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/android-arm": {
|
||||
"version": "0.24.2",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.24.2.tgz",
|
||||
"integrity": "sha512-tmwl4hJkCfNHwFB3nBa8z1Uy3ypZpxqxfTQOcHX+xRByyYgunVbZ9MzUUfb0RxaHIMnbHagwAxuTL+tnNM+1/Q==",
|
||||
"cpu": [
|
||||
"arm"
|
||||
],
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"os": [
|
||||
"android"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/android-arm64": {
|
||||
"version": "0.24.2",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.24.2.tgz",
|
||||
"integrity": "sha512-cNLgeqCqV8WxfcTIOeL4OAtSmL8JjcN6m09XIgro1Wi7cF4t/THaWEa7eL5CMoMBdjoHOTh/vwTO/o2TRXIyzg==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"os": [
|
||||
"android"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/android-x64": {
|
||||
"version": "0.24.2",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.24.2.tgz",
|
||||
"integrity": "sha512-B6Q0YQDqMx9D7rvIcsXfmJfvUYLoP722bgfBlO5cGvNVb5V/+Y7nhBE3mHV9OpxBf4eAS2S68KZztiPaWq4XYw==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"os": [
|
||||
"android"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/darwin-arm64": {
|
||||
"version": "0.24.2",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.24.2.tgz",
|
||||
"integrity": "sha512-kj3AnYWc+CekmZnS5IPu9D+HWtUI49hbnyqk0FLEJDbzCIQt7hg7ucF1SQAilhtYpIujfaHr6O0UHlzzSPdOeA==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"os": [
|
||||
"darwin"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/darwin-x64": {
|
||||
"version": "0.24.2",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.24.2.tgz",
|
||||
"integrity": "sha512-WeSrmwwHaPkNR5H3yYfowhZcbriGqooyu3zI/3GGpF8AyUdsrrP0X6KumITGA9WOyiJavnGZUwPGvxvwfWPHIA==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"os": [
|
||||
"darwin"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/freebsd-arm64": {
|
||||
"version": "0.24.2",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.24.2.tgz",
|
||||
"integrity": "sha512-UN8HXjtJ0k/Mj6a9+5u6+2eZ2ERD7Edt1Q9IZiB5UZAIdPnVKDoG7mdTVGhHJIeEml60JteamR3qhsr1r8gXvg==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"os": [
|
||||
"freebsd"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/freebsd-x64": {
|
||||
"version": "0.24.2",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.24.2.tgz",
|
||||
"integrity": "sha512-TvW7wE/89PYW+IevEJXZ5sF6gJRDY/14hyIGFXdIucxCsbRmLUcjseQu1SyTko+2idmCw94TgyaEZi9HUSOe3Q==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"os": [
|
||||
"freebsd"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/linux-arm": {
|
||||
"version": "0.24.2",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.24.2.tgz",
|
||||
"integrity": "sha512-n0WRM/gWIdU29J57hJyUdIsk0WarGd6To0s+Y+LwvlC55wt+GT/OgkwoXCXvIue1i1sSNWblHEig00GBWiJgfA==",
|
||||
"cpu": [
|
||||
"arm"
|
||||
],
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/linux-arm64": {
|
||||
"version": "0.24.2",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.24.2.tgz",
|
||||
"integrity": "sha512-7HnAD6074BW43YvvUmE/35Id9/NB7BeX5EoNkK9obndmZBUk8xmJJeU7DwmUeN7tkysslb2eSl6CTrYz6oEMQg==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/linux-ia32": {
|
||||
"version": "0.24.2",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.24.2.tgz",
|
||||
"integrity": "sha512-sfv0tGPQhcZOgTKO3oBE9xpHuUqguHvSo4jl+wjnKwFpapx+vUDcawbwPNuBIAYdRAvIDBfZVvXprIj3HA+Ugw==",
|
||||
"cpu": [
|
||||
"ia32"
|
||||
],
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/linux-loong64": {
|
||||
"version": "0.24.2",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.24.2.tgz",
|
||||
"integrity": "sha512-CN9AZr8kEndGooS35ntToZLTQLHEjtVB5n7dl8ZcTZMonJ7CCfStrYhrzF97eAecqVbVJ7APOEe18RPI4KLhwQ==",
|
||||
"cpu": [
|
||||
"loong64"
|
||||
],
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/linux-mips64el": {
|
||||
"version": "0.24.2",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.24.2.tgz",
|
||||
"integrity": "sha512-iMkk7qr/wl3exJATwkISxI7kTcmHKE+BlymIAbHO8xanq/TjHaaVThFF6ipWzPHryoFsesNQJPE/3wFJw4+huw==",
|
||||
"cpu": [
|
||||
"mips64el"
|
||||
],
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/linux-ppc64": {
|
||||
"version": "0.24.2",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.24.2.tgz",
|
||||
"integrity": "sha512-shsVrgCZ57Vr2L8mm39kO5PPIb+843FStGt7sGGoqiiWYconSxwTiuswC1VJZLCjNiMLAMh34jg4VSEQb+iEbw==",
|
||||
"cpu": [
|
||||
"ppc64"
|
||||
],
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/linux-riscv64": {
|
||||
"version": "0.24.2",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.24.2.tgz",
|
||||
"integrity": "sha512-4eSFWnU9Hhd68fW16GD0TINewo1L6dRrB+oLNNbYyMUAeOD2yCK5KXGK1GH4qD/kT+bTEXjsyTCiJGHPZ3eM9Q==",
|
||||
"cpu": [
|
||||
"riscv64"
|
||||
],
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/linux-s390x": {
|
||||
"version": "0.24.2",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.24.2.tgz",
|
||||
"integrity": "sha512-S0Bh0A53b0YHL2XEXC20bHLuGMOhFDO6GN4b3YjRLK//Ep3ql3erpNcPlEFed93hsQAjAQDNsvcK+hV90FubSw==",
|
||||
"cpu": [
|
||||
"s390x"
|
||||
],
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/linux-x64": {
|
||||
"version": "0.24.2",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.24.2.tgz",
|
||||
"integrity": "sha512-8Qi4nQcCTbLnK9WoMjdC9NiTG6/E38RNICU6sUNqK0QFxCYgoARqVqxdFmWkdonVsvGqWhmm7MO0jyTqLqwj0Q==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/netbsd-arm64": {
|
||||
"version": "0.24.2",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.24.2.tgz",
|
||||
"integrity": "sha512-wuLK/VztRRpMt9zyHSazyCVdCXlpHkKm34WUyinD2lzK07FAHTq0KQvZZlXikNWkDGoT6x3TD51jKQ7gMVpopw==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"os": [
|
||||
"netbsd"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/netbsd-x64": {
|
||||
"version": "0.24.2",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.24.2.tgz",
|
||||
"integrity": "sha512-VefFaQUc4FMmJuAxmIHgUmfNiLXY438XrL4GDNV1Y1H/RW3qow68xTwjZKfj/+Plp9NANmzbH5R40Meudu8mmw==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"os": [
|
||||
"netbsd"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/openbsd-arm64": {
|
||||
"version": "0.24.2",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.24.2.tgz",
|
||||
"integrity": "sha512-YQbi46SBct6iKnszhSvdluqDmxCJA+Pu280Av9WICNwQmMxV7nLRHZfjQzwbPs3jeWnuAhE9Jy0NrnJ12Oz+0A==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"os": [
|
||||
"openbsd"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/openbsd-x64": {
|
||||
"version": "0.24.2",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.24.2.tgz",
|
||||
"integrity": "sha512-+iDS6zpNM6EnJyWv0bMGLWSWeXGN/HTaF/LXHXHwejGsVi+ooqDfMCCTerNFxEkM3wYVcExkeGXNqshc9iMaOA==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"os": [
|
||||
"openbsd"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/sunos-x64": {
|
||||
"version": "0.24.2",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.24.2.tgz",
|
||||
"integrity": "sha512-hTdsW27jcktEvpwNHJU4ZwWFGkz2zRJUz8pvddmXPtXDzVKTTINmlmga3ZzwcuMpUvLw7JkLy9QLKyGpD2Yxig==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"os": [
|
||||
"sunos"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/win32-arm64": {
|
||||
"version": "0.24.2",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.24.2.tgz",
|
||||
"integrity": "sha512-LihEQ2BBKVFLOC9ZItT9iFprsE9tqjDjnbulhHoFxYQtQfai7qfluVODIYxt1PgdoyQkz23+01rzwNwYfutxUQ==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"os": [
|
||||
"win32"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/win32-ia32": {
|
||||
"version": "0.24.2",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.24.2.tgz",
|
||||
"integrity": "sha512-q+iGUwfs8tncmFC9pcnD5IvRHAzmbwQ3GPS5/ceCyHdjXubwQWI12MKWSNSMYLJMq23/IUCvJMS76PDqXe1fxA==",
|
||||
"cpu": [
|
||||
"ia32"
|
||||
],
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"os": [
|
||||
"win32"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/win32-x64": {
|
||||
"version": "0.24.2",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.24.2.tgz",
|
||||
"integrity": "sha512-7VTgWzgMGvup6aSqDPLiW5zHaxYJGTO4OokMjIlrCtf+VpEL+cXKtCvg723iguPYI5oaUNdS+/V7OU2gvXVWEg==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"os": [
|
||||
"win32"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@gar/promisify": {
|
||||
"version": "1.1.3",
|
||||
"resolved": "https://registry.npmjs.org/@gar/promisify/-/promisify-1.1.3.tgz",
|
||||
@@ -2431,6 +2832,46 @@
|
||||
"dev": true,
|
||||
"optional": true
|
||||
},
|
||||
"node_modules/esbuild": {
|
||||
"version": "0.24.2",
|
||||
"resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.24.2.tgz",
|
||||
"integrity": "sha512-+9egpBW8I3CD5XPe0n6BfT5fxLzxrlDzqydF3aviG+9ni1lDC/OvMHcxqEFV0+LANZG5R1bFMWfUrjVsdwxJvA==",
|
||||
"dev": true,
|
||||
"hasInstallScript": true,
|
||||
"bin": {
|
||||
"esbuild": "bin/esbuild"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"@esbuild/aix-ppc64": "0.24.2",
|
||||
"@esbuild/android-arm": "0.24.2",
|
||||
"@esbuild/android-arm64": "0.24.2",
|
||||
"@esbuild/android-x64": "0.24.2",
|
||||
"@esbuild/darwin-arm64": "0.24.2",
|
||||
"@esbuild/darwin-x64": "0.24.2",
|
||||
"@esbuild/freebsd-arm64": "0.24.2",
|
||||
"@esbuild/freebsd-x64": "0.24.2",
|
||||
"@esbuild/linux-arm": "0.24.2",
|
||||
"@esbuild/linux-arm64": "0.24.2",
|
||||
"@esbuild/linux-ia32": "0.24.2",
|
||||
"@esbuild/linux-loong64": "0.24.2",
|
||||
"@esbuild/linux-mips64el": "0.24.2",
|
||||
"@esbuild/linux-ppc64": "0.24.2",
|
||||
"@esbuild/linux-riscv64": "0.24.2",
|
||||
"@esbuild/linux-s390x": "0.24.2",
|
||||
"@esbuild/linux-x64": "0.24.2",
|
||||
"@esbuild/netbsd-arm64": "0.24.2",
|
||||
"@esbuild/netbsd-x64": "0.24.2",
|
||||
"@esbuild/openbsd-arm64": "0.24.2",
|
||||
"@esbuild/openbsd-x64": "0.24.2",
|
||||
"@esbuild/sunos-x64": "0.24.2",
|
||||
"@esbuild/win32-arm64": "0.24.2",
|
||||
"@esbuild/win32-ia32": "0.24.2",
|
||||
"@esbuild/win32-x64": "0.24.2"
|
||||
}
|
||||
},
|
||||
"node_modules/escalade": {
|
||||
"version": "3.2.0",
|
||||
"resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz",
|
||||
|
||||
@@ -1,12 +1,13 @@
|
||||
{
|
||||
"name": "timetracker-desktop",
|
||||
"version": "4.10.6",
|
||||
"version": "4.20.9",
|
||||
"description": "TimeTracker desktop app for Windows, Linux, and macOS",
|
||||
"main": "src/main/main.js",
|
||||
"scripts": {
|
||||
"start": "electron .",
|
||||
"start": "npm run build:renderer && electron .",
|
||||
"dev": "electron . --dev",
|
||||
"build": "electron-builder",
|
||||
"build:renderer": "esbuild src/renderer/js/app.js --bundle --outfile=src/renderer/js/bundle.js --platform=browser --format=iife",
|
||||
"build": "npm run build:renderer && electron-builder",
|
||||
"build:win": "electron-builder --win",
|
||||
"build:mac": "electron-builder --mac",
|
||||
"build:linux": "electron-builder --linux",
|
||||
@@ -29,12 +30,13 @@
|
||||
"license": "MIT",
|
||||
"devDependencies": {
|
||||
"electron": "^35.7.5",
|
||||
"electron-builder": "^25.1.8"
|
||||
"electron-builder": "^25.1.8",
|
||||
"esbuild": "^0.24.2"
|
||||
},
|
||||
"dependencies": {
|
||||
"axios": "^1.6.2",
|
||||
"electron-store": "^8.1.0",
|
||||
"dexie": "^3.2.4"
|
||||
"dexie": "^3.2.4",
|
||||
"electron-store": "^8.1.0"
|
||||
},
|
||||
"build": {
|
||||
"appId": "com.timetracker.desktop",
|
||||
|
||||
@@ -53,7 +53,7 @@
|
||||
<header class="app-header">
|
||||
<div class="header-left">
|
||||
<h1>TimeTracker</h1>
|
||||
<span id="connection-status" class="connection-status" title="Connection status"></span>
|
||||
<span id="connection-status" class="connection-status" role="status" aria-live="polite" aria-label="Connection status" title="Connection status"></span>
|
||||
</div>
|
||||
<div class="header-right">
|
||||
<button id="minimize-btn" class="header-btn" title="Minimize">−</button>
|
||||
@@ -67,6 +67,9 @@
|
||||
<button class="nav-btn active" data-view="dashboard">Dashboard</button>
|
||||
<button class="nav-btn" data-view="projects">Projects</button>
|
||||
<button class="nav-btn" data-view="entries">Time Entries</button>
|
||||
<button class="nav-btn" data-view="invoices">Invoices</button>
|
||||
<button class="nav-btn" data-view="expenses">Expenses</button>
|
||||
<button class="nav-btn" data-view="workforce">Workforce</button>
|
||||
<button class="nav-btn" data-view="settings">Settings</button>
|
||||
</nav>
|
||||
|
||||
@@ -142,6 +145,80 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Invoices View -->
|
||||
<div id="invoices-view" class="view">
|
||||
<div class="view-header">
|
||||
<h2>Invoices</h2>
|
||||
<div class="view-header-actions">
|
||||
<input type="search" id="invoice-search" placeholder="Search invoices...">
|
||||
<button id="invoice-prev-page-btn" class="btn btn-secondary">Prev</button>
|
||||
<span id="invoice-page-indicator">Page 1/1</span>
|
||||
<button id="invoice-next-page-btn" class="btn btn-secondary">Next</button>
|
||||
<button id="add-invoice-btn" class="btn btn-primary">Add Invoice</button>
|
||||
</div>
|
||||
</div>
|
||||
<div id="invoices-list" class="entries-list">
|
||||
<p class="empty-state">No invoices</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Expenses View -->
|
||||
<div id="expenses-view" class="view">
|
||||
<div class="view-header">
|
||||
<h2>Expenses</h2>
|
||||
<div class="view-header-actions">
|
||||
<input type="search" id="expense-search" placeholder="Search expenses...">
|
||||
<button id="expense-prev-page-btn" class="btn btn-secondary">Prev</button>
|
||||
<span id="expense-page-indicator">Page 1/1</span>
|
||||
<button id="expense-next-page-btn" class="btn btn-secondary">Next</button>
|
||||
<button id="add-expense-btn" class="btn btn-primary">Add Expense</button>
|
||||
</div>
|
||||
</div>
|
||||
<div id="expenses-list" class="entries-list">
|
||||
<p class="empty-state">No expenses</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Workforce View -->
|
||||
<div id="workforce-view" class="view">
|
||||
<div class="view-header">
|
||||
<h2>Workforce</h2>
|
||||
</div>
|
||||
<div id="workforce-summary" class="dashboard-grid">
|
||||
<div class="dashboard-card">
|
||||
<h3>Timesheet Periods</h3>
|
||||
<div class="view-header-actions" style="margin-bottom: 8px;">
|
||||
<button id="refresh-periods-btn" class="btn btn-secondary">Refresh</button>
|
||||
</div>
|
||||
<div id="periods-list" class="entries-list">
|
||||
<p class="empty-state">No periods</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="dashboard-card">
|
||||
<h3>Capacity</h3>
|
||||
<div id="capacity-list" class="entries-list">
|
||||
<p class="empty-state">No capacity rows</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="dashboard-card">
|
||||
<h3>Time-Off Requests</h3>
|
||||
<div class="view-header-actions" style="margin-bottom: 8px;">
|
||||
<input type="search" id="timeoff-search" placeholder="Search requests...">
|
||||
<button id="add-timeoff-btn" class="btn btn-primary">New Request</button>
|
||||
</div>
|
||||
<div id="timeoff-list" class="entries-list">
|
||||
<p class="empty-state">No time-off requests</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="dashboard-card">
|
||||
<h3>Leave Balances</h3>
|
||||
<div id="balances-list" class="entries-list">
|
||||
<p class="empty-state">No leave balances</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Settings View -->
|
||||
<div id="settings-view" class="view">
|
||||
<h2>Settings</h2>
|
||||
@@ -183,9 +260,7 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script src="js/api/client.js"></script>
|
||||
<script src="js/storage/storage.js"></script>
|
||||
<script src="js/utils/helpers.js"></script>
|
||||
<script src="js/app.js"></script>
|
||||
<script src="../shared/config.js"></script>
|
||||
<script src="js/bundle.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@@ -1,5 +1,8 @@
|
||||
const axios = require('axios');
|
||||
const { storeGet, storeSet } = require('../../../../shared/config');
|
||||
// In renderer, config is provided by shared/config.js (loaded before bundle). In Node (tests), use require.
|
||||
const cfg = (typeof window !== 'undefined' && window.config) ? window.config : (function() { try { return require('../../../shared/config'); } catch (_) { return {}; } })();
|
||||
const storeGet = cfg.storeGet || (async (k) => null);
|
||||
const storeSet = cfg.storeSet || (async (k, v) => {});
|
||||
|
||||
class ApiClient {
|
||||
constructor(baseUrl) {
|
||||
@@ -131,6 +134,14 @@ class ApiClient {
|
||||
async getProject(id) {
|
||||
return await this.client.get(`/api/v1/projects/${id}`);
|
||||
}
|
||||
|
||||
async getClients({ status, page, perPage }) {
|
||||
const params = {};
|
||||
if (status) params.status = status;
|
||||
if (page) params.page = page;
|
||||
if (perPage) params.per_page = perPage;
|
||||
return await this.client.get('/api/v1/clients', { params });
|
||||
}
|
||||
|
||||
// Tasks endpoints
|
||||
async getTasks({ projectId, status, page, perPage }) {
|
||||
@@ -151,6 +162,119 @@ class ApiClient {
|
||||
async getTimeEntry(id) {
|
||||
return await this.client.get(`/api/v1/time-entries/${id}`);
|
||||
}
|
||||
|
||||
// Invoices endpoints
|
||||
async getInvoices({ status, clientId, projectId, page, perPage }) {
|
||||
const params = {};
|
||||
if (status) params.status = status;
|
||||
if (clientId) params.client_id = clientId;
|
||||
if (projectId) params.project_id = projectId;
|
||||
if (page) params.page = page;
|
||||
if (perPage) params.per_page = perPage;
|
||||
|
||||
return await this.client.get('/api/v1/invoices', { params });
|
||||
}
|
||||
|
||||
async getInvoice(id) {
|
||||
return await this.client.get(`/api/v1/invoices/${id}`);
|
||||
}
|
||||
|
||||
async createInvoice(data) {
|
||||
return await this.client.post('/api/v1/invoices', data);
|
||||
}
|
||||
|
||||
async updateInvoice(id, data) {
|
||||
return await this.client.put(`/api/v1/invoices/${id}`, data);
|
||||
}
|
||||
|
||||
// Expenses endpoints
|
||||
async getExpenses({ projectId, category, startDate, endDate, page, perPage }) {
|
||||
const params = {};
|
||||
if (projectId) params.project_id = projectId;
|
||||
if (category) params.category = category;
|
||||
if (startDate) params.start_date = startDate;
|
||||
if (endDate) params.end_date = endDate;
|
||||
if (page) params.page = page;
|
||||
if (perPage) params.per_page = perPage;
|
||||
|
||||
return await this.client.get('/api/v1/expenses', { params });
|
||||
}
|
||||
|
||||
async createExpense(data) {
|
||||
return await this.client.post('/api/v1/expenses', data);
|
||||
}
|
||||
|
||||
async getCapacityReport({ startDate, endDate }) {
|
||||
return await this.client.get('/api/v1/reports/capacity', {
|
||||
params: { start_date: startDate, end_date: endDate },
|
||||
});
|
||||
}
|
||||
|
||||
async getTimesheetPeriods({ status, startDate, endDate }) {
|
||||
const params = {};
|
||||
if (status) params.status = status;
|
||||
if (startDate) params.start_date = startDate;
|
||||
if (endDate) params.end_date = endDate;
|
||||
return await this.client.get('/api/v1/timesheet-periods', { params });
|
||||
}
|
||||
|
||||
async submitTimesheetPeriod(periodId) {
|
||||
return await this.client.post(`/api/v1/timesheet-periods/${periodId}/submit`);
|
||||
}
|
||||
|
||||
async approveTimesheetPeriod(periodId, { comment } = {}) {
|
||||
const data = {};
|
||||
if (comment) data.comment = comment;
|
||||
return await this.client.post(`/api/v1/timesheet-periods/${periodId}/approve`, data);
|
||||
}
|
||||
|
||||
async rejectTimesheetPeriod(periodId, { reason } = {}) {
|
||||
const data = {};
|
||||
if (reason) data.reason = reason;
|
||||
return await this.client.post(`/api/v1/timesheet-periods/${periodId}/reject`, data);
|
||||
}
|
||||
|
||||
async getLeaveTypes() {
|
||||
return await this.client.get('/api/v1/time-off/leave-types');
|
||||
}
|
||||
|
||||
async getTimeOffRequests({ status, startDate, endDate }) {
|
||||
const params = {};
|
||||
if (status) params.status = status;
|
||||
if (startDate) params.start_date = startDate;
|
||||
if (endDate) params.end_date = endDate;
|
||||
return await this.client.get('/api/v1/time-off/requests', { params });
|
||||
}
|
||||
|
||||
async createTimeOffRequest({ leaveTypeId, startDate, endDate, requestedHours, comment, submit }) {
|
||||
const data = {
|
||||
leave_type_id: leaveTypeId,
|
||||
start_date: startDate,
|
||||
end_date: endDate,
|
||||
submit: submit !== undefined ? submit : true,
|
||||
};
|
||||
if (requestedHours !== undefined && requestedHours !== null) data.requested_hours = requestedHours;
|
||||
if (comment) data.comment = comment;
|
||||
return await this.client.post('/api/v1/time-off/requests', data);
|
||||
}
|
||||
|
||||
async getTimeOffBalances({ userId } = {}) {
|
||||
const params = {};
|
||||
if (userId) params.user_id = userId;
|
||||
return await this.client.get('/api/v1/time-off/balances', { params });
|
||||
}
|
||||
|
||||
async approveTimeOffRequest(requestId, { comment } = {}) {
|
||||
const data = {};
|
||||
if (comment) data.comment = comment;
|
||||
return await this.client.post(`/api/v1/time-off/requests/${requestId}/approve`, data);
|
||||
}
|
||||
|
||||
async rejectTimeOffRequest(requestId, { comment } = {}) {
|
||||
const data = {};
|
||||
if (comment) data.comment = comment;
|
||||
return await this.client.post(`/api/v1/time-off/requests/${requestId}/reject`, data);
|
||||
}
|
||||
}
|
||||
|
||||
// Export for use in other files
|
||||
|
||||
+773
-96
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,21 @@
|
||||
/**
|
||||
* Application state - single source of truth for view, timer, cache, and filters.
|
||||
* Used by app.js to avoid scattered globals.
|
||||
*/
|
||||
module.exports = {
|
||||
apiClient: null,
|
||||
currentView: 'dashboard',
|
||||
timerInterval: null,
|
||||
isTimerRunning: false,
|
||||
connectionCheckInterval: null,
|
||||
currentUserProfile: { is_admin: false, can_approve: false },
|
||||
cachedInvoices: [],
|
||||
cachedExpenses: [],
|
||||
cachedWorkforce: { periods: [], capacity: [], timeOffRequests: [], balances: [] },
|
||||
viewFilters: { invoiceQuery: '', expenseQuery: '', timeoffQuery: '' },
|
||||
viewLimits: { invoices: 20, expenses: 20, timeoff: 20 },
|
||||
pagination: {
|
||||
invoices: { page: 1, perPage: 20, totalPages: 1, total: 0 },
|
||||
expenses: { page: 1, perPage: 20, totalPages: 1, total: 0 },
|
||||
},
|
||||
};
|
||||
@@ -0,0 +1,41 @@
|
||||
/**
|
||||
* UI notifications: error and success toasts.
|
||||
* Used by app.js for non-login feedback.
|
||||
*/
|
||||
function showError(message) {
|
||||
let errorDiv = document.getElementById('error-notification');
|
||||
if (!errorDiv) {
|
||||
errorDiv = document.createElement('div');
|
||||
errorDiv.id = 'error-notification';
|
||||
errorDiv.className = 'notification notification-error';
|
||||
errorDiv.setAttribute('role', 'alert');
|
||||
errorDiv.setAttribute('aria-live', 'assertive');
|
||||
document.body.appendChild(errorDiv);
|
||||
}
|
||||
errorDiv.textContent = message;
|
||||
errorDiv.style.display = 'block';
|
||||
setTimeout(() => {
|
||||
errorDiv.style.display = 'none';
|
||||
}, 5000);
|
||||
}
|
||||
|
||||
function showSuccess(message) {
|
||||
let successDiv = document.getElementById('success-notification');
|
||||
if (!successDiv) {
|
||||
successDiv = document.createElement('div');
|
||||
successDiv.id = 'success-notification';
|
||||
successDiv.className = 'notification notification-success';
|
||||
successDiv.setAttribute('role', 'status');
|
||||
successDiv.setAttribute('aria-live', 'polite');
|
||||
document.body.appendChild(successDiv);
|
||||
}
|
||||
successDiv.textContent = message;
|
||||
successDiv.style.display = 'block';
|
||||
setTimeout(() => {
|
||||
successDiv.style.display = 'none';
|
||||
}, 3000);
|
||||
}
|
||||
|
||||
if (typeof module !== 'undefined' && module.exports) {
|
||||
module.exports = { showError, showSuccess };
|
||||
}
|
||||
Reference in New Issue
Block a user