Compare commits

...

5 Commits

Author SHA1 Message Date
e84a700a8b refactor: switch baseURL back to API endpoint for consistency 2025-10-29 19:56:56 +01:00
6a70a4a6ae Merge commit 'bcc56d6dd23746ff3cbd4f908865c100a08c21c0' 2025-10-29 19:42:13 +01:00
bcc56d6dd2 refactor: update nginx configuration for improved caching and clarity 2025-10-29 19:38:32 +01:00
e3656203ed fix: correct exit time handling in calculateDay function and update tests 2025-10-29 19:36:15 +01:00
54de913c16 feat: add vitest for testing and implement tests for calculateDay utility
- Added "test" script to package.json for running vitest.
- Updated api.js to use localhost for backend during development.
- Created utils.spec.js to test calculateDay function with various scenarios.
- Updated vite.config.js to include vitest configuration and conditionally load Vue DevTools.
2025-10-29 18:12:46 +01:00
7 changed files with 1113 additions and 43 deletions

View File

@@ -1,19 +1,33 @@
server { server {
listen 80; listen 80;
# Przekieruj /odoo (bez slash) na /odoo/ (z slash) # Przekierowanie bez / na /odoo/
location = /odoo { location = /odoo {
return 301 /odoo/; return 301 /odoo/;
} }
# Serwowanie plików Vue pod /odoo/ # Serwowanie Vue
location /odoo/ { location /odoo/ {
alias /usr/share/nginx/html/; alias /usr/share/nginx/html/;
index index.html; index index.html;
try_files $uri $uri/ /odoo/index.html; try_files $uri $uri/ /odoo/index.html;
} }
# Proxy do backendu (niewidoczny z zewnątrz) # no cache index.html
location = /odoo/index.html {
add_header Cache-Control "no-cache, no-store, must-revalidate";
expires 0;
}
# cache JS/CSS/OBRAZY na długo
location ~* \.(?:js|css|png|jpg|jpeg|gif|ico|svg|woff2?)$ {
add_header Cache-Control "public, max-age=31536000, immutable";
access_log off;
expires 1y;
alias /usr/share/nginx/html/;
}
# Proxy do backendu
location /odoo/api/ { location /odoo/api/ {
proxy_pass http://backend:8000/; proxy_pass http://backend:8000/;
proxy_set_header Host $host; proxy_set_header Host $host;
@@ -22,3 +36,4 @@ server {
proxy_set_header X-Forwarded-Proto $scheme; proxy_set_header X-Forwarded-Proto $scheme;
} }
} }

File diff suppressed because it is too large Load Diff

View File

@@ -11,7 +11,8 @@
"build": "vite build", "build": "vite build",
"preview": "vite preview", "preview": "vite preview",
"lint": "eslint . --fix", "lint": "eslint . --fix",
"format": "prettier --write src/" "format": "prettier --write src/",
"test": "vitest"
}, },
"dependencies": { "dependencies": {
"@jamescoyle/vue-icon": "^0.1.2", "@jamescoyle/vue-icon": "^0.1.2",
@@ -30,8 +31,10 @@
"eslint": "^9.31.0", "eslint": "^9.31.0",
"eslint-plugin-vue": "~10.3.0", "eslint-plugin-vue": "~10.3.0",
"globals": "^16.3.0", "globals": "^16.3.0",
"jsdom": "^27.0.1",
"prettier": "3.6.2", "prettier": "3.6.2",
"vite": "^7.0.6", "vite": "^7.0.6",
"vite-plugin-vue-devtools": "^8.0.0" "vite-plugin-vue-devtools": "^8.0.0",
"vitest": "^4.0.5"
} }
} }

View File

@@ -5,6 +5,7 @@ import { useAuthStore } from '@/stores/authStore'
const api = axios.create({ const api = axios.create({
baseURL: '/odoo/api', // Twój backend baseURL: '/odoo/api', // Twój backend
// baseURL: 'http://localhost:8000', // Twój backend
}) })
// Request interceptor dodawanie tokena // Request interceptor dodawanie tokena

View File

@@ -96,7 +96,13 @@ function isValidWorkDay(day) {
} }
function calculateWorkedTime(entryTimes, exitTimes) { function calculateWorkedTime(entryTimes, exitTimes) {
if (!entryTimes || !exitTimes || !Array.isArray(entryTimes) || !Array.isArray(exitTimes) || entryTimes.length === 0) { if (
!entryTimes ||
!exitTimes ||
!Array.isArray(entryTimes) ||
!Array.isArray(exitTimes) ||
entryTimes.length === 0
) {
return 0 return 0
} }
@@ -144,7 +150,7 @@ function floatHoursToHHMM(decimalHours) {
function calculateDay(day) { function calculateDay(day) {
// Create a mutable copy of exit times to potentially add the current time. // Create a mutable copy of exit times to potentially add the current time.
const exitTimes = [...day.exitTime] // const exitTimes = [...day.exitTime]
// If there's one more entry than exits, it means the user is currently clocked in. // If there's one more entry than exits, it means the user is currently clocked in.
if (day.entryTime.length > 0 && day.entryTime.length === day.exitTime.length + 1) { if (day.entryTime.length > 0 && day.entryTime.length === day.exitTime.length + 1) {
@@ -153,7 +159,7 @@ function calculateDay(day) {
const mm = String(now.getMinutes()).padStart(2, '0') const mm = String(now.getMinutes()).padStart(2, '0')
const ss = String(now.getSeconds()).padStart(2, '0') const ss = String(now.getSeconds()).padStart(2, '0')
// Add the current time as the "exit" for the last entry. // Add the current time as the "exit" for the last entry.
exitTimes.push(`${hh}:${mm}:${ss}`) day.exitTime.push(`${hh}:${mm}:${ss}`)
} }
if (day.entryTime.length === 0) { if (day.entryTime.length === 0) {
@@ -162,7 +168,7 @@ function calculateDay(day) {
// The -0.25 for a 15-minute break seems to be a business rule. // The -0.25 for a 15-minute break seems to be a business rule.
// It should probably only be subtracted once per day, not per interval. // It should probably only be subtracted once per day, not per interval.
// Let's subtract it from the total. // Let's subtract it from the total.
let totalHours = calculateWorkedTime(day.entryTime, exitTimes) let totalHours = calculateWorkedTime(day.entryTime, day.exitTime)
if (totalHours > 0) { if (totalHours > 0) {
totalHours -= 0.25 totalHours -= 0.25
} }

View File

@@ -0,0 +1,117 @@
import { describe, it, expect, beforeEach, vi } from 'vitest'
import utils from './utils.js'
const { calculateDay } = utils
describe('calculateDay', () => {
let day
beforeEach(() => {
day = {
entryTime: [],
exitTime: [],
isHolidayLeave: false,
isSickLeave: false,
dayOfWeek: 'Pn', // Monday
isPublicHoliday: false,
workedHours: 0,
overtime: 0,
}
})
it('should calculate worked hours and overtime for a standard workday', () => {
day.entryTime = ['08:00:00']
day.exitTime = ['16:30:00'] // 8.5 hours of work
const totalHours = calculateDay(day)
// 8.5 hours - 0.25 break = 8.25 workedHours
// 8.25 workedHours - 8 standard hours = 0.25 overtime
// totalHours should be workedHours, so 8.25
expect(day.workedHours).toBe(8.25)
expect(day.overtime).toBe(0.25)
expect(totalHours).toBe(8.25)
})
it('should handle multiple entries and exits', () => {
day.entryTime = ['08:00:00', '13:00:00']
day.exitTime = ['12:00:00', '16:30:00'] // 4h + 3.5h = 7.5h
const totalHours = calculateDay(day)
// 7.5 hours - 0.25 break = 7.25 workedHours
// 7.25 workedHours - 8 standard hours = -0.75 overtime
expect(day.workedHours).toBe(7.25)
expect(day.overtime).toBe(-0.75)
expect(totalHours).toBe(7.25)
})
it('should calculate hours correctly when currently clocked in', () => {
// Mock Date for consistent results
const now = new Date('2025-10-29T12:00:00')
vi.setSystemTime(now)
day.entryTime = ['09:00:00']
day.exitTime = [] // Clocked in
const totalHours = calculateDay(day)
// 9:00 to 12:00 is 3 hours
// 3 hours - 0.25 break = 2.75 workedHours
expect(day.workedHours).toBe(2.75)
expect(day.overtime).toBe(2.75 - 8) // -5.25
expect(totalHours).toBe(2.75)
expect(day.exitTime[0]).toBe('12:00:00')
vi.useRealTimers()
})
it('should return 0 for a day with no entries', () => {
const totalHours = calculateDay(day)
expect(day.workedHours).toBe(0)
expect(day.overtime).toBe(-8)
expect(totalHours).toBe(0)
})
it('should handle holiday leave', () => {
day.isHolidayLeave = true
const totalHours = calculateDay(day)
expect(day.workedHours).toBe(0)
expect(day.overtime).toBe(0) // Overtime is just workedHours on non-work days
expect(totalHours).toBe(8) // 0 worked + 8 holiday
})
it('should handle sick leave', () => {
day.isSickLeave = true
const totalHours = calculateDay(day)
expect(day.workedHours).toBe(0)
expect(day.overtime).toBe(0)
expect(totalHours).toBe(6.4) // 0 worked + 6.4 sick
})
it('should handle sick leave and holiday leave', () => {
day.isHolidayLeave = true
day.isSickLeave = true
const totalHours = calculateDay(day)
expect(day.workedHours).toBe(0)
expect(day.overtime).toBe(0)
expect(totalHours).toBe(14.4) // 0 worked + 8 holiday + 6.4 sick
})
it('should not subtract break time if worked hours are 0', () => {
day.entryTime = []
day.exitTime = []
const totalHours = calculateDay(day)
expect(day.workedHours).toBe(0)
expect(totalHours).toBe(0)
})
it('should calculate overtime as workedHours on a weekend', () => {
day.dayOfWeek = 'So' // Sunday
day.entryTime = ['10:00:00']
day.exitTime = ['12:00:00'] // 2 hours of work
const totalHours = calculateDay(day)
// 2 hours - 0.25 break = 1.75
expect(day.workedHours).toBe(1.75)
expect(day.overtime).toBe(1.75) // On non-work days, all worked time is overtime
expect(totalHours).toBe(1.75)
})
})

View File

@@ -1,16 +1,20 @@
/// <reference types="vitest" />
import { fileURLToPath, URL } from 'node:url' import { fileURLToPath, URL } from 'node:url'
import VueDevTools from 'vite-plugin-vue-devtools'
import { defineConfig } from 'vite' import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue' import vue from '@vitejs/plugin-vue'
import vueDevTools from 'vite-plugin-vue-devtools'
// https://vite.dev/config/ // https://vitejs.dev/config/
export default defineConfig({ export default defineConfig(({ command }) => ({
base: '/odoo/', base: '/odoo/',
plugins: [vue(), vueDevTools()], plugins: [vue(), ...(command === 'serve' ? [VueDevTools()] : [])],
resolve: { resolve: {
alias: { alias: {
'@': fileURLToPath(new URL('./src', import.meta.url)), '@': fileURLToPath(new URL('./src', import.meta.url)),
}, },
}, },
}) test: {
globals: true,
environment: 'jsdom',
},
}))