From ef6c44985daca92a6eaec70119e59ec3db2a0557 Mon Sep 17 00:00:00 2001 From: altaskur <105789412+altaskur@users.noreply.github.com> Date: Tue, 17 Feb 2026 20:23:58 +0100 Subject: [PATCH 1/6] refactor(build): migrate from Angular CLI + manual Electron to electron-vite - Replace Angular CLI build with electron-vite unified build system - Add @analogjs/vite-plugin-angular for Angular compilation in Vite - Move index.html to project root (Vite convention) - Update paths.ts for electron-vite dev server and production paths - Update window.ts to load from Vite dev server in development - Remove old electron/build/ tsconfig files (replaced by electron.vite.config.ts) - Add cross-platform asset serving plugin (dev middleware + build copy) - Update package.json scripts for electron-vite commands - Add Zone.js import in src/main.ts for Vite compatibility - Update electron/tsconfig.spec.json for standalone Vitest usage --- electron.vite.config.ts | 102 ++++ electron/build/tsconfig.json | 24 - electron/build/tsconfig.preload.json | 20 - electron/src/main/window.ts | 27 +- electron/src/utils/paths.ts | 23 +- electron/tsconfig.spec.json | 22 +- src/index.html => index.html | 1 + package-lock.json | 681 ++++++++++++++++++++++++++- package.json | 22 +- src/main.ts | 9 +- 10 files changed, 840 insertions(+), 91 deletions(-) create mode 100644 electron.vite.config.ts delete mode 100644 electron/build/tsconfig.json delete mode 100644 electron/build/tsconfig.preload.json rename src/index.html => index.html (86%) diff --git a/electron.vite.config.ts b/electron.vite.config.ts new file mode 100644 index 0000000..3c42607 --- /dev/null +++ b/electron.vite.config.ts @@ -0,0 +1,102 @@ +import { defineConfig, externalizeDepsPlugin } from 'electron-vite'; +import { resolve } from 'path'; +import { existsSync, cpSync, createReadStream } from 'fs'; +import angular from '@analogjs/vite-plugin-angular'; + +export default defineConfig({ + main: { + plugins: [externalizeDepsPlugin()], + build: { + outDir: 'dist/main', + rollupOptions: { + input: { + main: resolve(__dirname, 'electron/src/main/main.ts'), + }, + output: { + entryFileNames: '[name].js', + }, + }, + }, + }, + preload: { + plugins: [externalizeDepsPlugin()], + build: { + outDir: 'dist/preload', + rollupOptions: { + input: { + preload: resolve(__dirname, 'electron/src/preload/preload.ts'), + }, + output: { + format: 'cjs', + entryFileNames: '[name].js', + }, + }, + }, + }, + renderer: { + root: '.', + publicDir: false, + plugins: [ + angular({ + jit: false, + workspaceRoot: process.cwd(), + }), + // Serve src/assets at /assets/ in dev, copy on build + { + name: 'serve-and-copy-assets', + configureServer(server) { + server.middlewares.use('/assets', (req, res, next) => { + const filePath = resolve( + __dirname, + 'src/assets', + req.url!.replace(/^\//, ''), + ); + if (existsSync(filePath)) { + const ext = filePath.split('.').pop(); + const mimeTypes: Record = { + json: 'application/json', + png: 'image/png', + svg: 'image/svg+xml', + ico: 'image/x-icon', + }; + res.setHeader( + 'Content-Type', + mimeTypes[ext || ''] || 'application/octet-stream', + ); + createReadStream(filePath).pipe(res); + } else { + next(); + } + }); + }, + closeBundle() { + const src = resolve(__dirname, 'src/assets'); + const dest = resolve(__dirname, 'dist/renderer/assets'); + if (existsSync(src)) { + cpSync(src, dest, { recursive: true }); + } + }, + }, + ], + base: './', + build: { + outDir: 'dist/renderer', + rollupOptions: { + input: { + index: resolve(__dirname, 'index.html'), + }, + }, + }, + server: { + fs: { + allow: [resolve(__dirname, '.'), resolve(__dirname, 'src')], + }, + }, + resolve: { + alias: { + src: resolve(__dirname, 'src'), + '/assets': resolve(__dirname, 'src/assets'), + }, + }, + }, +}); diff --git a/electron/build/tsconfig.json b/electron/build/tsconfig.json deleted file mode 100644 index 262e79c..0000000 --- a/electron/build/tsconfig.json +++ /dev/null @@ -1,24 +0,0 @@ -{ - "compilerOptions": { - "target": "ES2022", - "module": "NodeNext", - "lib": ["ES2022"], - "types": ["node"], - "outDir": "../../dist/electron", - "rootDir": "../src", - "strict": true, - "esModuleInterop": true, - "skipLibCheck": true, - "forceConsistentCasingInFileNames": true, - "moduleResolution": "NodeNext", - "resolveJsonModule": true, - "declaration": true, - "sourceMap": true, - "baseUrl": "../..", - "paths": { - "@prisma/generated/*": ["prisma/generated/client/*"] - } - }, - "include": ["../src/**/*"], - "exclude": ["node_modules", "dist", "../src/preload/**/*"] -} diff --git a/electron/build/tsconfig.preload.json b/electron/build/tsconfig.preload.json deleted file mode 100644 index bbb9171..0000000 --- a/electron/build/tsconfig.preload.json +++ /dev/null @@ -1,20 +0,0 @@ -{ - "compilerOptions": { - "target": "ES2022", - "module": "CommonJS", - "lib": ["ES2022"], - "types": ["node"], - "outDir": "../../dist/electron/preload", - "rootDir": "../src/preload", - "strict": true, - "esModuleInterop": true, - "skipLibCheck": true, - "forceConsistentCasingInFileNames": true, - "moduleResolution": "Node", - "resolveJsonModule": true, - "declaration": false, - "sourceMap": true - }, - "include": ["../src/preload/**/*"], - "exclude": ["node_modules", "dist", "**/*.spec.ts"] -} diff --git a/electron/src/main/window.ts b/electron/src/main/window.ts index f26ae30..d0b4f0e 100644 --- a/electron/src/main/window.ts +++ b/electron/src/main/window.ts @@ -38,18 +38,25 @@ export class WindowManager { * Loads the Angular application */ private async loadApplication(): Promise { - const indexPath = getIndexPath(); - console.log('Index path:', indexPath); + const rendererUrl = process.env['ELECTRON_RENDERER_URL']; - if (fs.existsSync(indexPath)) { - this.navigationHandler = new NavigationHandler(this.mainWindow!); - this.navigationHandler.setupNavigationHandlers(); - this.navigationHandler.loadIndex(); + if (rendererUrl) { + console.log('Loading renderer from:', rendererUrl); + await this.mainWindow?.loadURL(rendererUrl); } else { - console.error('index.html not found at:', indexPath); - this.mainWindow?.loadURL( - `data:text/html,

Error: index.html not found

Path: ${indexPath}

`, - ); + const indexPath = getIndexPath(); + console.log('Index path:', indexPath); + + if (fs.existsSync(indexPath)) { + this.navigationHandler = new NavigationHandler(this.mainWindow!); + this.navigationHandler.setupNavigationHandlers(); + this.navigationHandler.loadIndex(); + } else { + console.error('index.html not found at:', indexPath); + this.mainWindow?.loadURL( + `data:text/html,

Error: index.html not found

Path: ${indexPath}

`, + ); + } } } diff --git a/electron/src/utils/paths.ts b/electron/src/utils/paths.ts index d1c1062..eed2d0f 100644 --- a/electron/src/utils/paths.ts +++ b/electron/src/utils/paths.ts @@ -53,9 +53,8 @@ export const getBackupPath = (): string => { /** * Gets the path to the Angular index.html file. - * In development: dist/OpenTimeTracker/browser/index.html - * In production: resources/app.asar/dist/OpenTimeTracker/browser/index.html - * Note: Uses process.resourcesPath for cross-platform compatibility (Windows/macOS/Linux) + * In development: dist/renderer/index.html (relative to main.js via __dirname) + * In production: resources/app.asar/dist/renderer/index.html */ export const getIndexPath = (): string => { if (isPackaged()) { @@ -63,24 +62,17 @@ export const getIndexPath = (): string => { process.resourcesPath, 'app.asar', 'dist', - 'OpenTimeTracker', - 'browser', + 'renderer', 'index.html', ); } - return path.join( - __dirname, - '..', - '..', - 'OpenTimeTracker', - 'browser', - 'index.html', - ); + return path.join(__dirname, '../renderer/index.html'); }; /** * Gets the path to the preload script. - * Note: Uses process.resourcesPath for cross-platform compatibility (Windows/macOS/Linux) + * In development: dist/preload/preload.js (relative to main.js via __dirname) + * In production: resources/app.asar/dist/preload/preload.js */ export const getPreloadPath = (): string => { if (isPackaged()) { @@ -88,12 +80,11 @@ export const getPreloadPath = (): string => { process.resourcesPath, 'app.asar', 'dist', - 'electron', 'preload', 'preload.js', ); } - return path.join(__dirname, '..', 'preload', 'preload.js'); + return path.join(__dirname, '../preload/preload.js'); }; /** diff --git a/electron/tsconfig.spec.json b/electron/tsconfig.spec.json index 322d283..769cdee 100644 --- a/electron/tsconfig.spec.json +++ b/electron/tsconfig.spec.json @@ -1,14 +1,24 @@ { - "extends": "./build/tsconfig.json", "compilerOptions": { + "target": "ES2022", + "module": "NodeNext", + "moduleResolution": "NodeNext", + "lib": ["ES2022"], "types": ["node"], + "baseUrl": "..", + "outDir": "./dist", + "strict": true, "esModuleInterop": true, - "composite": true, + "skipLibCheck": true, + "forceConsistentCasingInFileNames": true, + "resolveJsonModule": true, "declaration": true, - "outDir": "./dist", - "module": "NodeNext", - "moduleResolution": "NodeNext", - "isolatedModules": true + "composite": true, + "sourceMap": true, + "isolatedModules": true, + "paths": { + "@prisma/generated/*": ["prisma/generated/client/*"] + } }, "include": ["src/**/*.spec.ts", "src/**/*.ts"] } diff --git a/src/index.html b/index.html similarity index 86% rename from src/index.html rename to index.html index 6331635..65245f6 100644 --- a/src/index.html +++ b/index.html @@ -9,5 +9,6 @@ + diff --git a/package-lock.json b/package-lock.json index 2d52d44..6e36756 100644 --- a/package-lock.json +++ b/package-lock.json @@ -35,6 +35,7 @@ "zone.js": "~0.15.0" }, "devDependencies": { + "@analogjs/vite-plugin-angular": "^2.2.3", "@angular-devkit/architect": "^0.2100.1", "@angular-devkit/build-angular": "^21.0.1", "@angular-devkit/core": "^21.0.1", @@ -61,6 +62,7 @@ "dotenv": "^17.2.3", "electron": "^37.1.0", "electron-builder": "^26.0.12", + "electron-vite": "^5.0.0", "eslint": "^9.39.1", "husky": "^9.1.7", "jasmine-core": "~5.7.0", @@ -77,6 +79,7 @@ "storybook": "^10.2.8", "typescript": "~5.9.3", "typescript-eslint": "8.46.4", + "vite": "^7.3.1", "vitest": "^4.0.15" } }, @@ -317,6 +320,79 @@ "node": ">=6.0.0" } }, + "node_modules/@analogjs/vite-plugin-angular": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/@analogjs/vite-plugin-angular/-/vite-plugin-angular-2.2.3.tgz", + "integrity": "sha512-OqVfiJsaHdHMxzvK0heVvp8MenSXh+xib6/p+v3d44kJ3J7ooD4gRx/jKC350zkgRKwcZc3a0ybGUnG6LEF7mg==", + "dev": true, + "license": "MIT", + "dependencies": { + "ts-morph": "^21.0.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/brandonroberts" + }, + "peerDependencies": { + "@angular-devkit/build-angular": "^15.0.0 || ^16.0.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^20.0.0 || ^21.0.0", + "@angular/build": "^18.0.0 || ^19.0.0 || ^20.0.0 || ^21.0.0" + }, + "peerDependenciesMeta": { + "@angular-devkit/build-angular": { + "optional": true + }, + "@angular/build": { + "optional": true + } + } + }, + "node_modules/@analogjs/vite-plugin-angular/node_modules/@ts-morph/common": { + "version": "0.22.0", + "resolved": "https://registry.npmjs.org/@ts-morph/common/-/common-0.22.0.tgz", + "integrity": "sha512-HqNBuV/oIlMKdkLshXd1zKBqNQCsuPEsgQOkfFQ/eUKjRlwndXW1AjN9LVkBEIukm00gGXSRmfkl0Wv5VXLnlw==", + "dev": true, + "license": "MIT", + "dependencies": { + "fast-glob": "^3.3.2", + "minimatch": "^9.0.3", + "mkdirp": "^3.0.1", + "path-browserify": "^1.0.1" + } + }, + "node_modules/@analogjs/vite-plugin-angular/node_modules/code-block-writer": { + "version": "12.0.0", + "resolved": "https://registry.npmjs.org/code-block-writer/-/code-block-writer-12.0.0.tgz", + "integrity": "sha512-q4dMFMlXtKR3XNBHyMHt/3pwYNA69EDk00lloMOaaUMKPUXBw6lpXtbu3MMVG6/uOihGnRDOlkyqsONEUj60+w==", + "dev": true, + "license": "MIT" + }, + "node_modules/@analogjs/vite-plugin-angular/node_modules/mkdirp": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-3.0.1.tgz", + "integrity": "sha512-+NsyUUAZDmo6YVHzL/stxSu3t9YS1iljliy3BSDrXJ/dkn1KYdmtZODGGjLcc9XLgVVpH4KshHB8XmZgMhaBXg==", + "dev": true, + "license": "MIT", + "bin": { + "mkdirp": "dist/cjs/src/bin.js" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/@analogjs/vite-plugin-angular/node_modules/ts-morph": { + "version": "21.0.1", + "resolved": "https://registry.npmjs.org/ts-morph/-/ts-morph-21.0.1.tgz", + "integrity": "sha512-dbDtVdEAncKctzrVZ+Nr7kHpHkv+0JDJb2MjjpBaj8bFeCkePU9rHfMklmhuLFnpeq/EJZk2IhStY6NzqgjOkg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@ts-morph/common": "~0.22.0", + "code-block-writer": "^12.0.0" + } + }, "node_modules/@angular-devkit/architect": { "version": "0.2100.6", "resolved": "https://registry.npmjs.org/@angular-devkit/architect/-/architect-0.2100.6.tgz", @@ -939,6 +1015,81 @@ } } }, + "node_modules/@angular/build/node_modules/vite": { + "version": "7.3.0", + "resolved": "https://registry.npmjs.org/vite/-/vite-7.3.0.tgz", + "integrity": "sha512-dZwN5L1VlUBewiP6H9s2+B3e3Jg96D0vzN+Ry73sOefebhYr9f94wwkMNN/9ouoU8pV1BqA1d1zGk8928cx0rg==", + "dev": true, + "license": "MIT", + "dependencies": { + "esbuild": "^0.27.0", + "fdir": "^6.5.0", + "picomatch": "^4.0.3", + "postcss": "^8.5.6", + "rollup": "^4.43.0", + "tinyglobby": "^0.2.15" + }, + "bin": { + "vite": "bin/vite.js" + }, + "engines": { + "node": "^20.19.0 || >=22.12.0" + }, + "funding": { + "url": "https://github.com/vitejs/vite?sponsor=1" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + }, + "peerDependencies": { + "@types/node": "^20.19.0 || >=22.12.0", + "jiti": ">=1.21.0", + "less": "^4.0.0", + "lightningcss": "^1.21.0", + "sass": "^1.70.0", + "sass-embedded": "^1.70.0", + "stylus": ">=0.54.8", + "sugarss": "^5.0.0", + "terser": "^5.16.0", + "tsx": "^4.8.1", + "yaml": "^2.4.2" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + }, + "jiti": { + "optional": true + }, + "less": { + "optional": true + }, + "lightningcss": { + "optional": true + }, + "sass": { + "optional": true + }, + "sass-embedded": { + "optional": true + }, + "stylus": { + "optional": true + }, + "sugarss": { + "optional": true + }, + "terser": { + "optional": true + }, + "tsx": { + "optional": true + }, + "yaml": { + "optional": true + } + } + }, "node_modules/@angular/cdk": { "version": "21.1.3", "resolved": "https://registry.npmjs.org/@angular/cdk/-/cdk-21.1.3.tgz", @@ -12813,6 +12964,16 @@ "url": "https://paulmillr.com/funding/" } }, + "node_modules/cac": { + "version": "6.7.14", + "resolved": "https://registry.npmjs.org/cac/-/cac-6.7.14.tgz", + "integrity": "sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, "node_modules/cacache": { "version": "19.0.1", "resolved": "https://registry.npmjs.org/cacache/-/cacache-19.0.1.tgz", @@ -15017,6 +15178,520 @@ "dev": true, "license": "ISC" }, + "node_modules/electron-vite": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/electron-vite/-/electron-vite-5.0.0.tgz", + "integrity": "sha512-OHp/vjdlubNlhNkPkL/+3JD34ii5ov7M0GpuXEVdQeqdQ3ulvVR7Dg/rNBLfS5XPIFwgoBLDf9sjjrL+CuDyRQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/core": "^7.28.4", + "@babel/plugin-transform-arrow-functions": "^7.27.1", + "cac": "^6.7.14", + "esbuild": "^0.25.11", + "magic-string": "^0.30.19", + "picocolors": "^1.1.1" + }, + "bin": { + "electron-vite": "bin/electron-vite.js" + }, + "engines": { + "node": "^20.19.0 || >=22.12.0" + }, + "peerDependencies": { + "@swc/core": "^1.0.0", + "vite": "^5.0.0 || ^6.0.0 || ^7.0.0" + }, + "peerDependenciesMeta": { + "@swc/core": { + "optional": true + } + } + }, + "node_modules/electron-vite/node_modules/@esbuild/aix-ppc64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.12.tgz", + "integrity": "sha512-Hhmwd6CInZ3dwpuGTF8fJG6yoWmsToE+vYgD4nytZVxcu1ulHpUQRAB1UJ8+N1Am3Mz4+xOByoQoSZf4D+CpkA==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/electron-vite/node_modules/@esbuild/android-arm": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.25.12.tgz", + "integrity": "sha512-VJ+sKvNA/GE7Ccacc9Cha7bpS8nyzVv0jdVgwNDaR4gDMC/2TTRc33Ip8qrNYUcpkOHUT5OZ0bUcNNVZQ9RLlg==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/electron-vite/node_modules/@esbuild/android-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.25.12.tgz", + "integrity": "sha512-6AAmLG7zwD1Z159jCKPvAxZd4y/VTO0VkprYy+3N2FtJ8+BQWFXU+OxARIwA46c5tdD9SsKGZ/1ocqBS/gAKHg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/electron-vite/node_modules/@esbuild/android-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.25.12.tgz", + "integrity": "sha512-5jbb+2hhDHx5phYR2By8GTWEzn6I9UqR11Kwf22iKbNpYrsmRB18aX/9ivc5cabcUiAT/wM+YIZ6SG9QO6a8kg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/electron-vite/node_modules/@esbuild/darwin-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.25.12.tgz", + "integrity": "sha512-N3zl+lxHCifgIlcMUP5016ESkeQjLj/959RxxNYIthIg+CQHInujFuXeWbWMgnTo4cp5XVHqFPmpyu9J65C1Yg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/electron-vite/node_modules/@esbuild/darwin-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.25.12.tgz", + "integrity": "sha512-HQ9ka4Kx21qHXwtlTUVbKJOAnmG1ipXhdWTmNXiPzPfWKpXqASVcWdnf2bnL73wgjNrFXAa3yYvBSd9pzfEIpA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/electron-vite/node_modules/@esbuild/freebsd-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.25.12.tgz", + "integrity": "sha512-gA0Bx759+7Jve03K1S0vkOu5Lg/85dou3EseOGUes8flVOGxbhDDh/iZaoek11Y8mtyKPGF3vP8XhnkDEAmzeg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/electron-vite/node_modules/@esbuild/freebsd-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.25.12.tgz", + "integrity": "sha512-TGbO26Yw2xsHzxtbVFGEXBFH0FRAP7gtcPE7P5yP7wGy7cXK2oO7RyOhL5NLiqTlBh47XhmIUXuGciXEqYFfBQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/electron-vite/node_modules/@esbuild/linux-arm": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.25.12.tgz", + "integrity": "sha512-lPDGyC1JPDou8kGcywY0YILzWlhhnRjdof3UlcoqYmS9El818LLfJJc3PXXgZHrHCAKs/Z2SeZtDJr5MrkxtOw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/electron-vite/node_modules/@esbuild/linux-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.25.12.tgz", + "integrity": "sha512-8bwX7a8FghIgrupcxb4aUmYDLp8pX06rGh5HqDT7bB+8Rdells6mHvrFHHW2JAOPZUbnjUpKTLg6ECyzvas2AQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/electron-vite/node_modules/@esbuild/linux-ia32": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.25.12.tgz", + "integrity": "sha512-0y9KrdVnbMM2/vG8KfU0byhUN+EFCny9+8g202gYqSSVMonbsCfLjUO+rCci7pM0WBEtz+oK/PIwHkzxkyharA==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/electron-vite/node_modules/@esbuild/linux-loong64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.25.12.tgz", + "integrity": "sha512-h///Lr5a9rib/v1GGqXVGzjL4TMvVTv+s1DPoxQdz7l/AYv6LDSxdIwzxkrPW438oUXiDtwM10o9PmwS/6Z0Ng==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/electron-vite/node_modules/@esbuild/linux-mips64el": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.25.12.tgz", + "integrity": "sha512-iyRrM1Pzy9GFMDLsXn1iHUm18nhKnNMWscjmp4+hpafcZjrr2WbT//d20xaGljXDBYHqRcl8HnxbX6uaA/eGVw==", + "cpu": [ + "mips64el" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/electron-vite/node_modules/@esbuild/linux-ppc64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.25.12.tgz", + "integrity": "sha512-9meM/lRXxMi5PSUqEXRCtVjEZBGwB7P/D4yT8UG/mwIdze2aV4Vo6U5gD3+RsoHXKkHCfSxZKzmDssVlRj1QQA==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/electron-vite/node_modules/@esbuild/linux-riscv64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.25.12.tgz", + "integrity": "sha512-Zr7KR4hgKUpWAwb1f3o5ygT04MzqVrGEGXGLnj15YQDJErYu/BGg+wmFlIDOdJp0PmB0lLvxFIOXZgFRrdjR0w==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/electron-vite/node_modules/@esbuild/linux-s390x": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.25.12.tgz", + "integrity": "sha512-MsKncOcgTNvdtiISc/jZs/Zf8d0cl/t3gYWX8J9ubBnVOwlk65UIEEvgBORTiljloIWnBzLs4qhzPkJcitIzIg==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/electron-vite/node_modules/@esbuild/linux-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.25.12.tgz", + "integrity": "sha512-uqZMTLr/zR/ed4jIGnwSLkaHmPjOjJvnm6TVVitAa08SLS9Z0VM8wIRx7gWbJB5/J54YuIMInDquWyYvQLZkgw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/electron-vite/node_modules/@esbuild/netbsd-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.25.12.tgz", + "integrity": "sha512-xXwcTq4GhRM7J9A8Gv5boanHhRa/Q9KLVmcyXHCTaM4wKfIpWkdXiMog/KsnxzJ0A1+nD+zoecuzqPmCRyBGjg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/electron-vite/node_modules/@esbuild/netbsd-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.25.12.tgz", + "integrity": "sha512-Ld5pTlzPy3YwGec4OuHh1aCVCRvOXdH8DgRjfDy/oumVovmuSzWfnSJg+VtakB9Cm0gxNO9BzWkj6mtO1FMXkQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/electron-vite/node_modules/@esbuild/openbsd-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.25.12.tgz", + "integrity": "sha512-fF96T6KsBo/pkQI950FARU9apGNTSlZGsv1jZBAlcLL1MLjLNIWPBkj5NlSz8aAzYKg+eNqknrUJ24QBybeR5A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/electron-vite/node_modules/@esbuild/openbsd-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.25.12.tgz", + "integrity": "sha512-MZyXUkZHjQxUvzK7rN8DJ3SRmrVrke8ZyRusHlP+kuwqTcfWLyqMOE3sScPPyeIXN/mDJIfGXvcMqCgYKekoQw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/electron-vite/node_modules/@esbuild/openharmony-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.25.12.tgz", + "integrity": "sha512-rm0YWsqUSRrjncSXGA7Zv78Nbnw4XL6/dzr20cyrQf7ZmRcsovpcRBdhD43Nuk3y7XIoW2OxMVvwuRvk9XdASg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/electron-vite/node_modules/@esbuild/sunos-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.25.12.tgz", + "integrity": "sha512-3wGSCDyuTHQUzt0nV7bocDy72r2lI33QL3gkDNGkod22EsYl04sMf0qLb8luNKTOmgF/eDEDP5BFNwoBKH441w==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/electron-vite/node_modules/@esbuild/win32-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.25.12.tgz", + "integrity": "sha512-rMmLrur64A7+DKlnSuwqUdRKyd3UE7oPJZmnljqEptesKM8wx9J8gx5u0+9Pq0fQQW8vqeKebwNXdfOyP+8Bsg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/electron-vite/node_modules/@esbuild/win32-ia32": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.25.12.tgz", + "integrity": "sha512-HkqnmmBoCbCwxUKKNPBixiWDGCpQGVsrQfJoVGYLPT41XWF8lHuE5N6WhVia2n4o5QK5M4tYr21827fNhi4byQ==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/electron-vite/node_modules/@esbuild/win32-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.25.12.tgz", + "integrity": "sha512-alJC0uCZpTFrSL0CCDjcgleBXPnCrEAhTBILpeAp7M/OFgoqtAetfBzX0xM00MUsVVPpVjlPuMbREqnZCXaTnA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/electron-vite/node_modules/esbuild": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.12.tgz", + "integrity": "sha512-bbPBYYrtZbkt6Os6FiTLCTFxvq4tt3JKall1vRwshA3fdVztsLAatFaZobhkBC8/BrPetoa0oksYoKXoG4ryJg==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=18" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.25.12", + "@esbuild/android-arm": "0.25.12", + "@esbuild/android-arm64": "0.25.12", + "@esbuild/android-x64": "0.25.12", + "@esbuild/darwin-arm64": "0.25.12", + "@esbuild/darwin-x64": "0.25.12", + "@esbuild/freebsd-arm64": "0.25.12", + "@esbuild/freebsd-x64": "0.25.12", + "@esbuild/linux-arm": "0.25.12", + "@esbuild/linux-arm64": "0.25.12", + "@esbuild/linux-ia32": "0.25.12", + "@esbuild/linux-loong64": "0.25.12", + "@esbuild/linux-mips64el": "0.25.12", + "@esbuild/linux-ppc64": "0.25.12", + "@esbuild/linux-riscv64": "0.25.12", + "@esbuild/linux-s390x": "0.25.12", + "@esbuild/linux-x64": "0.25.12", + "@esbuild/netbsd-arm64": "0.25.12", + "@esbuild/netbsd-x64": "0.25.12", + "@esbuild/openbsd-arm64": "0.25.12", + "@esbuild/openbsd-x64": "0.25.12", + "@esbuild/openharmony-arm64": "0.25.12", + "@esbuild/sunos-x64": "0.25.12", + "@esbuild/win32-arm64": "0.25.12", + "@esbuild/win32-ia32": "0.25.12", + "@esbuild/win32-x64": "0.25.12" + } + }, "node_modules/electron/node_modules/@types/node": { "version": "22.19.10", "resolved": "https://registry.npmjs.org/@types/node/-/node-22.19.10.tgz", @@ -26427,9 +27102,9 @@ } }, "node_modules/vite": { - "version": "7.3.0", - "resolved": "https://registry.npmjs.org/vite/-/vite-7.3.0.tgz", - "integrity": "sha512-dZwN5L1VlUBewiP6H9s2+B3e3Jg96D0vzN+Ry73sOefebhYr9f94wwkMNN/9ouoU8pV1BqA1d1zGk8928cx0rg==", + "version": "7.3.1", + "resolved": "https://registry.npmjs.org/vite/-/vite-7.3.1.tgz", + "integrity": "sha512-w+N7Hifpc3gRjZ63vYBXA56dvvRlNWRczTdmCBBa+CotUzAPf5b7YMdMR/8CQoeYE5LX3W4wj6RYTgonm1b9DA==", "dev": true, "license": "MIT", "dependencies": { diff --git a/package.json b/package.json index 4433307..1c4f2f9 100644 --- a/package.json +++ b/package.json @@ -5,7 +5,7 @@ "license": "GPL-3.0", "type": "module", "description": "Time tracking application for managing projects and tasks", - "main": "dist/electron/main/main.js", + "main": "dist/main/main.js", "overrides": { "node-forge": "^1.3.2", "hono": "4.11.8", @@ -13,31 +13,30 @@ "axios": "1.13.5" }, "scripts": { - "start": "ng serve", - "build": "npm run prisma:generate && ng build && tsc --project electron/build/tsconfig.json && tsc --project electron/build/tsconfig.preload.json", - "dev": "npm run prisma:generate && ng build --configuration=development && tsc --project electron/build/tsconfig.json && tsc --project electron/build/tsconfig.preload.json && electron .", + "start": "electron-vite preview", + "dev": "npm run prisma:generate && electron-vite dev", + "build": "npm run prisma:generate && electron-vite build", "dist": "npm run build && electron-builder", "dist:win": "npm run build && electron-builder --win", "dist:linux": "npm run build && electron-builder --linux", "dist:mac": "npm run build && electron-builder --mac", - "pack": "electron-builder --dir", + "postinstall": "npx @electron/rebuild", "prisma:generate": "cross-env DATABASE_URL=file:./dist/data/timetracker.db prisma generate", "prisma:push": "cross-env DATABASE_URL=file:./dist/data/timetracker.db prisma db push", "prisma:studio": "cross-env DATABASE_URL=file:./dist/data/timetracker.db prisma studio", "prisma:migrate": "cross-env DATABASE_URL=file:./dist/data/timetracker.db prisma migrate dev", "prisma:template": "node scripts/update-db-template.mjs", - "postinstall": "npx @electron/rebuild", "test": "ng test", "test:coverage": "ng test --code-coverage --watch=false --browsers=ChromeHeadless", "test:electron": "vitest run", "test:electron:watch": "vitest", "test:electron:coverage": "vitest run --coverage", - "electron": "electron .", + "lint": "ng lint", + "lint:fix": "ng lint --fix", + "format": "prettier --write \"src/**/*.{ts,html,scss}\"", "sonar": "node scripts/run-sonar.mjs", "sonar:check": "npm run test:coverage && npm run test:electron:coverage && npm run sonar && node scripts/check-sonar-quality-gate.mjs", "prepare": "husky", - "lint": "ng lint", - "lint:fix": "ng lint --fix", "storybook": "ng run OpenTimeTracker:storybook", "build-storybook": "ng run OpenTimeTracker:build-storybook" }, @@ -64,7 +63,7 @@ "node_modules/@prisma/client/**/*", "node_modules/@prisma/adapter-better-sqlite3/**/*", "node_modules/better-sqlite3/**/*", - "dist/electron/generated/**/*", + "dist/main/generated/**/*", "prisma/template.db" ], "asar": true, @@ -181,6 +180,7 @@ "zone.js": "~0.15.0" }, "devDependencies": { + "@analogjs/vite-plugin-angular": "^2.2.3", "@angular-devkit/architect": "^0.2100.1", "@angular-devkit/build-angular": "^21.0.1", "@angular-devkit/core": "^21.0.1", @@ -207,6 +207,7 @@ "dotenv": "^17.2.3", "electron": "^37.1.0", "electron-builder": "^26.0.12", + "electron-vite": "^5.0.0", "eslint": "^9.39.1", "husky": "^9.1.7", "jasmine-core": "~5.7.0", @@ -223,6 +224,7 @@ "storybook": "^10.2.8", "typescript": "~5.9.3", "typescript-eslint": "8.46.4", + "vite": "^7.3.1", "vitest": "^4.0.15" }, "lint-staged": { diff --git a/src/main.ts b/src/main.ts index 5df75f9..00ff9b7 100644 --- a/src/main.ts +++ b/src/main.ts @@ -1,6 +1,11 @@ +import 'zone.js'; + +import './styles.scss'; +import 'primeflex/primeflex.css'; +import 'primeicons/primeicons.css'; + import { bootstrapApplication } from '@angular/platform-browser'; import { appConfig } from './app/app.config'; import { App } from './app/app'; -bootstrapApplication(App, appConfig) - .catch((err) => console.error(err)); +bootstrapApplication(App, appConfig).catch((err) => console.error(err)); From 993313adb904a4ce89e840d8acb677ae8231022e Mon Sep 17 00:00:00 2001 From: altaskur <105789412+altaskur@users.noreply.github.com> Date: Tue, 17 Feb 2026 20:24:58 +0100 Subject: [PATCH 2/6] fix(home): use daySchedule for daily target calculation (#66) - Fix workDays parsing: use split(',') instead of JSON.parse() to match the comma-separated format stored in the database - Use daySchedule per-day minutes instead of dividing weeklyMinutes evenly, matching the calendar component's behavior - Handle dayTypeId in overrides (holidays/special days return 0) - Fix Sunday dayOfWeek conversion (0 -> 7) to match ISO format Closes #66 --- src/app/pages/open-home/open-home.ts | 42 ++++++++++++++++------------ 1 file changed, 24 insertions(+), 18 deletions(-) diff --git a/src/app/pages/open-home/open-home.ts b/src/app/pages/open-home/open-home.ts index 9b3c9f6..0fb2999 100644 --- a/src/app/pages/open-home/open-home.ts +++ b/src/app/pages/open-home/open-home.ts @@ -206,28 +206,26 @@ export class OpenHome implements OnInit { * Counts work days in the week from config */ private getWorkDaysCount(monthConfig: MonthConfig): number { - try { - const workDays = JSON.parse(monthConfig.workDays); - return Array.isArray(workDays) ? workDays.length : 5; - } catch { - return 5; - } + const workDays = monthConfig.workDays + ?.split(',') + .map((d) => parseInt(d, 10)) + .filter((d) => !isNaN(d)); + return workDays?.length ?? 5; } /** * Checks if a day of week is a work day */ private isWorkDay(dayOfWeek: number, monthConfig: MonthConfig): boolean { - try { - const workDays = JSON.parse(monthConfig.workDays); - return Array.isArray(workDays) && workDays.includes(dayOfWeek); - } catch { - return dayOfWeek >= 1 && dayOfWeek <= 5; - } + const workDays = monthConfig.workDays + ?.split(',') + .map((d) => parseInt(d, 10)) + .filter((d) => !isNaN(d)) || [1, 2, 3, 4, 5]; + return workDays.includes(dayOfWeek); } /** - * Gets target minutes for a specific day + * Gets target minutes for a specific day using daySchedule (same logic as calendar) */ private getDayTarget( date: Date, @@ -238,18 +236,26 @@ export class OpenHome implements OnInit { const override = dayOverrides.find((o) => o.date === dateStr); if (override) { + if (override.dayTypeId) { + return 0; + } return override.minutes ?? 0; } - const dayOfWeek = date.getDay(); + const dayOfWeek = date.getDay() === 0 ? 7 : date.getDay(); if (!this.isWorkDay(dayOfWeek, monthConfig)) { return 0; } - const workDaysCount = this.getWorkDaysCount(monthConfig); - return workDaysCount > 0 - ? Math.round(monthConfig.weeklyMinutes / workDaysCount) - : 0; + // Use daySchedule for per-day minutes (consistent with calendar) + try { + const daySchedule: Record = monthConfig.daySchedule + ? JSON.parse(monthConfig.daySchedule) + : { '1': 480, '2': 480, '3': 480, '4': 480, '5': 480 }; + return daySchedule[String(dayOfWeek)] ?? 480; + } catch { + return 480; + } } /** From 2a24e04513725925d00ef3a2affd355daa7c3ffb Mon Sep 17 00:00:00 2001 From: altaskur <105789412+altaskur@users.noreply.github.com> Date: Tue, 17 Feb 2026 20:31:43 +0100 Subject: [PATCH 3/6] style(home): constrain card dimensions and add text overflow - Set card height to 134px for all variants - Set max-width to 356px for all cards - Add text-overflow: ellipsis on card names and task titles - Change grid to auto-fill with minmax(200px, 356px) - Add overflow: hidden to p-card for clean truncation - Reduce card body padding for compact layout --- src/app/components/open-card/open-card.scss | 18 +++- src/app/pages/open-home/open-home.html | 96 +++++++++++---------- src/app/pages/open-home/open-home.scss | 48 ++++------- 3 files changed, 82 insertions(+), 80 deletions(-) diff --git a/src/app/components/open-card/open-card.scss b/src/app/components/open-card/open-card.scss index 954d4f5..f3d8a7e 100644 --- a/src/app/components/open-card/open-card.scss +++ b/src/app/components/open-card/open-card.scss @@ -1,7 +1,8 @@ /* Base card styles - common to all variants */ .open-card { - height: 100%; + height: 134px; width: 100%; + max-width: 356px; ::ng-deep .p-card { height: 100%; @@ -11,6 +12,7 @@ transition: box-shadow var(--transition-base), transform var(--transition-base); + overflow: hidden; &:hover { box-shadow: var(--elevation-md); @@ -20,6 +22,11 @@ ::ng-deep .p-card-body { height: 100%; + padding: 0.75rem 1rem; + } + + ::ng-deep .p-card-content { + padding: 0; } /* Common header styles */ @@ -31,6 +38,7 @@ i { font-size: 1.5rem; color: var(--p-primary-color); + flex-shrink: 0; } } @@ -39,6 +47,9 @@ font-size: var(--font-size-lg); font-weight: var(--font-weight-semibold); color: var(--p-text-color); + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; } } @@ -156,6 +167,9 @@ font-size: var(--font-size-base); font-weight: var(--font-weight-semibold); color: var(--p-text-color); + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; } &__description { @@ -188,4 +202,4 @@ border-radius: var(--radius-md); } } -} +} \ No newline at end of file diff --git a/src/app/pages/open-home/open-home.html b/src/app/pages/open-home/open-home.html index 9331402..ee00233 100644 --- a/src/app/pages/open-home/open-home.html +++ b/src/app/pages/open-home/open-home.html @@ -35,56 +35,58 @@

{{ "home.title" | translate }}

/> -

{{ "home.pendingTasks" | translate }}

+
+

{{ "home.pendingTasks" | translate }}

- @if (loading()) { -

{{ "home.loading" | translate }}

- } @else if (pendingTasks().length === 0) { - -
- -

{{ "home.empty" | translate }}

-
-
- } @else { -
- @for (task of pendingTasks(); track task.id) { -
- + @if (loading()) { +

{{ "home.loading" | translate }}

+ } @else if (pendingTasks().length === 0) { + +
+ +

{{ "home.empty" | translate }}

- } -
- } - - -

{{ "home.openProjects" | translate }}

- - @if (openProjects().length === 0) { - -
- -

{{ "home.emptyProjects" | translate }}

+ + } @else { +
+ @for (task of pendingTasks(); track task.id) { +
+ +
+ }
- - } @else { -
- @for (project of openProjects(); track project.id) { -
- + } +
+
+

{{ "home.openProjects" | translate }}

+ + @if (openProjects().length === 0) { + +
+ +

{{ "home.emptyProjects" | translate }}

- } -
- } + + } @else { +
+ @for (project of openProjects(); track project.id) { +
+ +
+ } +
+ } +
diff --git a/src/app/pages/open-home/open-home.scss b/src/app/pages/open-home/open-home.scss index ea5339c..f5243a9 100644 --- a/src/app/pages/open-home/open-home.scss +++ b/src/app/pages/open-home/open-home.scss @@ -44,6 +44,22 @@ grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); gap: 1rem; margin-bottom: 1.5rem; + + >* { + max-width: 356px; + } +} + +.task-panel, +.projects-panel { + margin-bottom: 1.5rem; +} + +.tasks-grid, +.projects-grid { + display: grid; + grid-template-columns: repeat(auto-fill, minmax(200px, 356px)); + gap: 1rem; } .stats-card { @@ -156,36 +172,6 @@ } } -.tasks-grid { - display: grid; - grid-template-columns: repeat(auto-fill, minmax(280px, 1fr)); - grid-auto-rows: 1fr; - gap: 1rem; - list-style: none; - padding: 0; - margin: 0 0 1.5rem; - - > [role="listitem"] { - display: flex; - min-height: 150px; - } -} - -/* ==================== PROJECTS GRID ==================== */ -.projects-grid { - display: grid; - grid-template-columns: repeat(auto-fill, minmax(200px, 1fr)); - grid-auto-rows: 1fr; - gap: 1rem; - list-style: none; - padding: 0; - margin: 0; - - > [role="listitem"] { - display: flex; - } -} - .project-card { display: flex; flex-direction: column; @@ -292,4 +278,4 @@ background: var(--p-surface-100); border-radius: var(--p-border-radius-sm); } -} +} \ No newline at end of file From bd449ba9f87a68bb3b3586cb28ccbd2e07888a15 Mon Sep 17 00:00:00 2001 From: altaskur <105789412+altaskur@users.noreply.github.com> Date: Tue, 17 Feb 2026 20:32:11 +0100 Subject: [PATCH 4/6] docs: update documentation for electron-vite migration --- COLLABORATION.md | 4 ++++ CONTRIBUTING.md | 14 ++++++++++++++ README.md | 5 +++++ 3 files changed, 23 insertions(+) diff --git a/COLLABORATION.md b/COLLABORATION.md index 57b71a6..c244de2 100644 --- a/COLLABORATION.md +++ b/COLLABORATION.md @@ -23,6 +23,7 @@ Brief guide to contribute consistently. English first, Spanish version below. ```bash npm start # Angular dev server on 4200 npm run dev # build + Electron dev mode + npm run storybook # UI component explorer ``` ## Workflow @@ -68,6 +69,7 @@ Brief guide to contribute consistently. English first, Spanish version below. ## UI and i18n - Uses PrimeNG/PrimeFlex, dark theme by default. +- Use Storybook to develop components in isolation. - Add strings in src/assets/i18n/en.json and src/assets/i18n/es.json. - Mind accessibility: labels, visible focus, contrast. @@ -113,6 +115,7 @@ Guía breve para contribuir de forma consistente. ```bash npm start # servidor Angular en 4200 npm run dev # build + Electron en modo desarrollo + npm run storybook # explorador de componentes UI ``` ### Flujo de trabajo @@ -158,6 +161,7 @@ Guía breve para contribuir de forma consistente. ### UI e i18n - Componentes con PrimeNG/PrimeFlex, tema oscuro por defecto. +- Usa Storybook para desarrollar componentes de forma aislada. - Añade cadenas en src/assets/i18n/en.json y src/assets/i18n/es.json. - Cuida accesibilidad: labels, focus visible, contrastes. diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 7642270..817cb1a 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -254,6 +254,13 @@ When modifying the Prisma schema: - Dark theme (Aura Black) is the default - Follow existing component patterns +* +### Storybook +* +We use [Storybook](https://storybook.js.org/) for UI component development and documentation. +* +- **Run Storybook**: `npm run storybook` (accessible at http://localhost:6006) + +- **Build Storybook**: `npm run build-storybook` + +- **Creating Stories**: Add `*.stories.ts` files alongside your components. + +- **Documentation**: Storybook documentation is auto-generated using Compodoc. Ensure `documentation.json` is up-to-date if you encounter issues. + ### Adding Translations Add new strings to both language files: @@ -540,6 +547,13 @@ Al modificar el esquema de Prisma: - El tema oscuro (Aura Black) es el predeterminado - Sigue los patrones de componentes existentes +* +### Storybook +* +Usamos [Storybook](https://storybook.js.org/) para el desarrollo y documentación de componentes de UI. +* +- **Ejecutar Storybook**: `npm run storybook` (accesible en http://localhost:6006) + +- **Construir Storybook**: `npm run build-storybook` + +- **Crear Historias**: Añade archivos `*.stories.ts` junto a tus componentes. + +- **Documentación**: La documentación de Storybook se genera automáticamente usando Compodoc. Asegúrate de que `documentation.json` esté actualizado si encuentras problemas. + ### Añadir Traducciones Añade nuevas cadenas a ambos archivos de idioma: diff --git a/README.md b/README.md index 37c0acc..dd2a573 100644 --- a/README.md +++ b/README.md @@ -82,6 +82,9 @@ cd OpenTimeTracker npm install npm run prisma:generate npm run dev + +# Run Storybook (Component Explorer) +npm run storybook ``` npm run dev is the recommended and supported development command. @@ -105,6 +108,8 @@ All contribution-related documentation lives outside this README to keep things 🧭 COLLABORATION.md — technical and architectural details +🤖 AI_GUIDELINES.md — context and rules for AI assistants + 🛡️ SECURITY.md — how to report vulnerabilities 📜 CODE_OF_CONDUCT.md — community guidelines From dbe7ea1eac3a158e45c34dec4a7952da43588bbf Mon Sep 17 00:00:00 2001 From: altaskur <105789412+altaskur@users.noreply.github.com> Date: Tue, 17 Feb 2026 20:37:28 +0100 Subject: [PATCH 5/6] docs: add AI guidelines for project contributors - Add ia-rules.md with coding standards, architecture context, and essential commands for AI agents working on the project --- .agent/rules/ia-rules.md | 109 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 109 insertions(+) create mode 100644 .agent/rules/ia-rules.md diff --git a/.agent/rules/ia-rules.md b/.agent/rules/ia-rules.md new file mode 100644 index 0000000..3ea7ee0 --- /dev/null +++ b/.agent/rules/ia-rules.md @@ -0,0 +1,109 @@ +--- +trigger: always_on +--- + +# AI Guidelines for OpenTimeTracker + +This document provides context and rules for AI agents contributing to OpenTimeTracker. Follow these guidelines to ensure consistency, quality, and adherence to project standards. + +## Project Overview + +- **Name**: OpenTimeTracker +- **Purpose**: Free, open-source, local-first time tracking application. +- **Key Features**: Offline-first (SQLite), no subscriptions, cross-platform (Windows, macOS, Linux). + +## Tech Stack + +- **Frontend**: Angular 21 (Signals, Standalone Components, strict mode). +- **Desktop Wrapper**: Electron 37 (IPC for Node.js access). +- **Database**: SQLite via Prisma (local file `timetracker.db`). +- **UI Component Library**: PrimeNG 21 + PrimeFlex. +- **State Management**: Angular Signals + Services (avoid NgRx unless strictly necessary). +- **Internationalization**: `@ngx-translate/core`. + +## Coding Standards + +### TypeScript & Angular + +- **Strict Typing**: No `any`. Define interfaces for all data structures. +- **Signals**: Use Angular Signals for state reactivity. Avoid `BehaviorSubject` where Signals suffice. +- **Standalone Components**: All new components must be `standalone: true`. +- **Control Flow**: Use modern Angular control flow (`@if`, `@for`, `@switch`) instead of `*ngIf`, `*ngFor`. +- **Change Detection**: Use `OnPush` strategy where possible. + +### Electron & IPC + +- **Security**: Enable context isolation and sandbox. +- **IPC**: Use `ipcMain.handle` and `ipcRenderer.invoke` for bidirectional communication. +- **Preload Scripts**: Expose typed APIs via `contextBridge`. + +### CSS & Styling + +- **PrimeFlex**: Use PrimeFlex utility classes for layout and spacing (e.g., `flex`, `gap-2`, `p-3`). +- **SCSS**: Use component-specific SCSS for custom styles not covered by PrimeFlex. +- **Variables**: Use CSS variables for theming (e.g., `var(--primary-color)`). + +### Git & Commits + +- **Conventional Commits**: `type(scope): description`. + - Types: `feat`, `fix`, `docs`, `style`, `refactor`, `test`, `chore`. + - Example: `feat(calendar): add weekly view support`. + +## Architecture + +### Directory Structure + +- `src/app`: Angular application code. +- `src/assets`: Static assets and i18n files. +- `electron/src`: Electron main process and preload scripts. +- `electron/src/services`: IPC handlers and Node.js logic. +- `prisma`: Database schema and migrations. + +### Internationalization (i18n) + +- All user-facing text must be translatable. +- Keys: `SECTION.FEATURE.KEY` (e.g., `SETTINGS.UPDATES.CHECK_NOW`). +- Files: `src/assets/i18n/en.json`, `src/assets/i18n/es.json`. +- Tools: `TranslateService`, `TranslatePipe`. + +### Testing + +- **Unit Tests**: Jasmine + Karma (Angular), Vitest (Electron). +- **Coverage**: Maintain high coverage (>80%). +- **SonarQube**: Respect quality gates. Run `npm run sonar:check` before pushing. + +### UI Development (Storybook) + +- Create stories for new dumb/presentational components. +- Path: `src/app/components/[name]/[name].stories.ts`. +- Run: `npm run storybook`. + +## Workflows + +### Database Changes + +1. Modify `prisma/schema.prisma`. +2. Run `npx prisma migrate dev --name `. +3. Run `npm run prisma:template` to update production template. + +### Adding a New Feature + +1. Design component/service hierarchy. +2. Implement core logic and state. +3. Add UI with PrimeNG/PrimeFlex. +4. Add i18n keys to English and Spanish. +5. Add Unit Tests. +6. (Optional) Add Storybook story. +7. Verify with `npm run sonar:check`. + +## Context for AI + +- **Do not** suggest cloud-based solutions unless explicitly asked (Local-first philosophy). +- **Do not** introduce new heavy dependencies without approval. +- **Always** check `package.json` scripts (`dev`, `build`, `test`, `sonar:check`) for running tasks. +- **Always** favor modern Angular syntax (Signals, Control Flow). + +## Essential Commands + +- **Run Project**: `npm run dev` (Runs Angular + Electron in development mode). +- **Run Tests & Quality Checks**: `npm run sonar:check` (Runs Unit Tests, Coverage, and SonarQube analysis). Use this to verify your changes. From 06cec672a36b7c53e45a1ce98adb66f86e7f44ad Mon Sep 17 00:00:00 2001 From: altaskur <105789412+altaskur@users.noreply.github.com> Date: Tue, 17 Feb 2026 20:39:07 +0100 Subject: [PATCH 6/6] test(paths): update getIndexPath assertion for electron-vite renderer path --- electron/src/utils/paths.spec.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/electron/src/utils/paths.spec.ts b/electron/src/utils/paths.spec.ts index 6c954d1..b78dc8d 100644 --- a/electron/src/utils/paths.spec.ts +++ b/electron/src/utils/paths.spec.ts @@ -166,7 +166,7 @@ describe('Paths Utility', () => { const result = getIndexPath(); expect(result).toContain('OpenTimeTracker'); - expect(result).toContain('browser'); + expect(result).toContain('renderer'); expect(result).toContain('index.html'); });