From f9dc40a8572e4b224f9c36165ceb780084853958 Mon Sep 17 00:00:00 2001 From: Zachary Date: Fri, 22 Nov 2024 19:39:56 +0100 Subject: [PATCH] Allow customisable Weekend days --- package-lock.json | 156 ++++++++------------------- package.json | 2 - src/lib/CalendarMonth.svelte | 10 +- src/lib/holidayUtils.ts | 191 ++++++++++++++++++++++++++------- src/routes/+page.svelte | 199 +++++++++++++++++++++++++++-------- 5 files changed, 360 insertions(+), 198 deletions(-) diff --git a/package-lock.json b/package-lock.json index b804a45..4fedc79 100644 --- a/package-lock.json +++ b/package-lock.json @@ -8,8 +8,6 @@ "name": "stretch-my-time-off", "version": "0.0.1", "dependencies": { - "@vercel/analytics": "^1.3.2", - "@vercel/speed-insights": "^1.1.0", "date-holidays": "^3.23.12", "i18n-iso-countries": "^7.13.0" }, @@ -27,7 +25,7 @@ "version": "2.3.0", "resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.3.0.tgz", "integrity": "sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw==", - "devOptional": true, + "dev": true, "license": "Apache-2.0", "dependencies": { "@jridgewell/gen-mapping": "^0.3.5", @@ -432,7 +430,7 @@ "version": "0.3.5", "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.5.tgz", "integrity": "sha512-IzL8ZoEDIBRWEzlCcRhOaCupYyN5gdIK+Q6fbFdPDg6HqX6jpkItn7DFIpW9LQzXG6Df9sA7+OKnq0qlz/GaQg==", - "devOptional": true, + "dev": true, "license": "MIT", "dependencies": { "@jridgewell/set-array": "^1.2.1", @@ -447,7 +445,7 @@ "version": "3.1.2", "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", - "devOptional": true, + "dev": true, "license": "MIT", "engines": { "node": ">=6.0.0" @@ -457,7 +455,7 @@ "version": "1.2.1", "resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.2.1.tgz", "integrity": "sha512-R8gLRTZeyp03ymzP/6Lil/28tGeGEzhx1q2k703KGWRAI1VdvPIXdG70VJc2pAMw3NA6JKL5hhFu1sJX0Mnn/A==", - "devOptional": true, + "dev": true, "license": "MIT", "engines": { "node": ">=6.0.0" @@ -467,14 +465,14 @@ "version": "1.5.0", "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.0.tgz", "integrity": "sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ==", - "devOptional": true, + "dev": true, "license": "MIT" }, "node_modules/@jridgewell/trace-mapping": { "version": "0.3.25", "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.25.tgz", "integrity": "sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==", - "devOptional": true, + "dev": true, "license": "MIT", "dependencies": { "@jridgewell/resolve-uri": "^3.1.0", @@ -485,7 +483,7 @@ "version": "1.0.0-next.28", "resolved": "https://registry.npmjs.org/@polka/url/-/url-1.0.0-next.28.tgz", "integrity": "sha512-8LduaNlMZGwdZ6qWrKlfa+2M4gahzFkprZiAt2TF8uS0qQgBizKXpXURqvTJ4WtmupWxaLqjRb2UCTe72mu+Aw==", - "devOptional": true, + "dev": true, "license": "MIT" }, "node_modules/@rollup/rollup-android-arm-eabi": { @@ -757,7 +755,7 @@ "version": "2.8.0", "resolved": "https://registry.npmjs.org/@sveltejs/kit/-/kit-2.8.0.tgz", "integrity": "sha512-HCiWupCuKJQ3aPaC4Xc6lpPdjOOnoGzEiYjOqMqppdtfGtY2ABrx932Vw66ZwS2RGXc0BmZvFvNq5SzqlmDVLg==", - "devOptional": true, + "dev": true, "hasInstallScript": true, "license": "MIT", "dependencies": { @@ -790,7 +788,7 @@ "version": "4.0.0", "resolved": "https://registry.npmjs.org/@sveltejs/vite-plugin-svelte/-/vite-plugin-svelte-4.0.0.tgz", "integrity": "sha512-kpVJwF+gNiMEsoHaw+FJL76IYiwBikkxYU83+BpqQLdVMff19KeRKLd2wisS8niNBMJ2omv5gG+iGDDwd8jzag==", - "devOptional": true, + "dev": true, "license": "MIT", "dependencies": { "@sveltejs/vite-plugin-svelte-inspector": "^3.0.0-next.0||^3.0.0", @@ -812,7 +810,7 @@ "version": "3.0.1", "resolved": "https://registry.npmjs.org/@sveltejs/vite-plugin-svelte-inspector/-/vite-plugin-svelte-inspector-3.0.1.tgz", "integrity": "sha512-2CKypmj1sM4GE7HjllT7UKmo4Q6L5xFRd7VMGEWhYnZ+wc6AUVU01IBd7yUi6WnFndEwWoMNOd6e8UjoN0nbvQ==", - "devOptional": true, + "dev": true, "license": "MIT", "dependencies": { "debug": "^4.3.7" @@ -830,77 +828,21 @@ "version": "0.6.0", "resolved": "https://registry.npmjs.org/@types/cookie/-/cookie-0.6.0.tgz", "integrity": "sha512-4Kh9a6B2bQciAhf7FSuMRRkUWecJgJu9nPnx3yzpsfXX/c50REIqpHY4C82bXP90qrLtXtkDxTZosYO3UpOwlA==", - "devOptional": true, + "dev": true, "license": "MIT" }, "node_modules/@types/estree": { "version": "1.0.6", "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.6.tgz", "integrity": "sha512-AYnb1nQyY49te+VRAVgmzfcgjYS91mY5P0TKUDCLEM+gNnA+3T6rWITXRLYCpahpqSQbN5cE+gHpnPyXjHWxcw==", - "devOptional": true, + "dev": true, "license": "MIT" }, - "node_modules/@vercel/analytics": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/@vercel/analytics/-/analytics-1.3.2.tgz", - "integrity": "sha512-n/Ws7skBbW+fUBMeg+jrT30+GP00jTHvCcL4fuVrShuML0uveEV/4vVUdvqEVnDgXIGfLm0GXW5EID2mCcRXhg==", - "license": "MPL-2.0", - "dependencies": { - "server-only": "^0.0.1" - }, - "peerDependencies": { - "next": ">= 13", - "react": "^18.0 || ^19.0 || ^19.0.0-rc" - }, - "peerDependenciesMeta": { - "next": { - "optional": true - }, - "react": { - "optional": true - } - } - }, - "node_modules/@vercel/speed-insights": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@vercel/speed-insights/-/speed-insights-1.1.0.tgz", - "integrity": "sha512-rAXxuhhO4mlRGC9noa5F7HLMtGg8YF1zAN6Pjd1Ny4pII4cerhtwSG4vympbCl+pWkH7nBS9kVXRD4FAn54dlg==", - "hasInstallScript": true, - "license": "Apache-2.0", - "peerDependencies": { - "@sveltejs/kit": "^1 || ^2", - "next": ">= 13", - "react": "^18 || ^19 || ^19.0.0-rc", - "svelte": ">= 4", - "vue": "^3", - "vue-router": "^4" - }, - "peerDependenciesMeta": { - "@sveltejs/kit": { - "optional": true - }, - "next": { - "optional": true - }, - "react": { - "optional": true - }, - "svelte": { - "optional": true - }, - "vue": { - "optional": true - }, - "vue-router": { - "optional": true - } - } - }, "node_modules/acorn": { "version": "8.14.0", "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.14.0.tgz", "integrity": "sha512-cl669nCJTZBsL97OF4kUQm5g5hC2uihk0NxY3WENAC0TYdILVkAyHymAntgxGkl7K+t0cXIrH5siy5S4XkFycA==", - "devOptional": true, + "dev": true, "license": "MIT", "bin": { "acorn": "bin/acorn" @@ -913,7 +855,7 @@ "version": "1.4.13", "resolved": "https://registry.npmjs.org/acorn-typescript/-/acorn-typescript-1.4.13.tgz", "integrity": "sha512-xsc9Xv0xlVfwp2o7sQ+GCQ1PgbkdcpWdTzrwXxO3xDMTAywVS3oXVOcOHuRjAPkS4P9b+yc/qNF15460v+jp4Q==", - "devOptional": true, + "dev": true, "license": "MIT", "peerDependencies": { "acorn": ">=8.9.0" @@ -929,7 +871,7 @@ "version": "5.3.2", "resolved": "https://registry.npmjs.org/aria-query/-/aria-query-5.3.2.tgz", "integrity": "sha512-COROpnaoap1E2F000S62r6A60uHZnmlvomhfyT2DlTcrY1OrBKn2UhH7qn5wTC9zMvD0AY7csdPSNwKP+7WiQw==", - "devOptional": true, + "dev": true, "license": "Apache-2.0", "engines": { "node": ">= 0.4" @@ -948,7 +890,7 @@ "version": "4.1.0", "resolved": "https://registry.npmjs.org/axobject-query/-/axobject-query-4.1.0.tgz", "integrity": "sha512-qIj0G9wZbMGNLjLmg1PT6v2mE9AH2zlnADJD/2tC6E00hgmhUOfEB6greHPAfLRSufHqROIUTkw6E+M3lH0PTQ==", - "devOptional": true, + "dev": true, "license": "Apache-2.0", "engines": { "node": ">= 0.4" @@ -986,7 +928,7 @@ "version": "0.6.0", "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.6.0.tgz", "integrity": "sha512-U71cyTamuh1CRNCfpGY6to28lxvNwPG4Guz/EVjgf3Jmzv0vlDp1atT9eS5dDjMYHucpHbWns6Lwf3BKz6svdw==", - "devOptional": true, + "dev": true, "license": "MIT", "engines": { "node": ">= 0.6" @@ -1063,7 +1005,7 @@ "version": "4.3.7", "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.7.tgz", "integrity": "sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==", - "devOptional": true, + "dev": true, "license": "MIT", "dependencies": { "ms": "^2.1.3" @@ -1090,7 +1032,7 @@ "version": "5.1.1", "resolved": "https://registry.npmjs.org/devalue/-/devalue-5.1.1.tgz", "integrity": "sha512-maua5KUiapvEwiEAe+XnlZ3Rh0GD+qI1J/nb9vrJc3muPXvcF/8gXYTWF76+5DAqHyDUtOIImEuo0YKE9mshVw==", - "devOptional": true, + "dev": true, "license": "MIT" }, "node_modules/diacritics": { @@ -1103,7 +1045,7 @@ "version": "0.21.5", "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.21.5.tgz", "integrity": "sha512-mg3OPMV4hXywwpoDxu3Qda5xCKQi+vCTZq8S9J/EpkhB2HzKXq4SNFZE3+NK93JYxc8VMSep+lOUSC/RVKaBqw==", - "devOptional": true, + "dev": true, "hasInstallScript": true, "license": "MIT", "bin": { @@ -1142,14 +1084,14 @@ "version": "1.1.4", "resolved": "https://registry.npmjs.org/esm-env/-/esm-env-1.1.4.tgz", "integrity": "sha512-oO82nKPHKkzIj/hbtuDYy/JHqBHFlMIW36SDiPCVsj87ntDLcWN+sJ1erdVryd4NxODacFTsdrIE3b7IamqbOg==", - "devOptional": true, + "dev": true, "license": "MIT" }, "node_modules/esrap": { "version": "1.2.2", "resolved": "https://registry.npmjs.org/esrap/-/esrap-1.2.2.tgz", "integrity": "sha512-F2pSJklxx1BlQIQgooczXCPHmcWpn6EsP5oo73LQfonG9fIlIENQ8vMmfGXeojP9MrkzUNAfyU5vdFlR9shHAw==", - "devOptional": true, + "dev": true, "license": "MIT", "dependencies": { "@jridgewell/sourcemap-codec": "^1.4.15", @@ -1190,14 +1132,14 @@ "version": "0.1.0", "resolved": "https://registry.npmjs.org/globalyzer/-/globalyzer-0.1.0.tgz", "integrity": "sha512-40oNTM9UfG6aBmuKxk/giHn5nQ8RVz/SS4Ir6zgzOv9/qC3kKZ9v4etGTcJbEl/NyVQH7FGU7d+X1egr57Md2Q==", - "devOptional": true, + "dev": true, "license": "MIT" }, "node_modules/globrex": { "version": "0.1.2", "resolved": "https://registry.npmjs.org/globrex/-/globrex-0.1.2.tgz", "integrity": "sha512-uHJgbwAMwNFf5mLst7IWLNg14x1CkeqglJb/K3doi4dw6q2IvAAmM/Y81kevy83wP+Sst+nutFTYOGg3d1lsxg==", - "devOptional": true, + "dev": true, "license": "MIT" }, "node_modules/i18n-iso-countries": { @@ -1216,7 +1158,7 @@ "version": "4.1.0", "resolved": "https://registry.npmjs.org/import-meta-resolve/-/import-meta-resolve-4.1.0.tgz", "integrity": "sha512-I6fiaX09Xivtk+THaMfAwnA3MVA5Big1WHF1Dfx9hFuvNIWpXnorlkzhcQf6ehrqQiiZECRt1poOAkPmer3ruw==", - "devOptional": true, + "dev": true, "license": "MIT", "funding": { "type": "github", @@ -1227,7 +1169,7 @@ "version": "3.0.2", "resolved": "https://registry.npmjs.org/is-reference/-/is-reference-3.0.2.tgz", "integrity": "sha512-v3rht/LgVcsdZa3O2Nqs+NMowLOxeOm7Ay9+/ARQ2F+qEoANRcqrjAZKGN0v8ymUetZGgkp26LTnGT7H0Qo9Pg==", - "devOptional": true, + "dev": true, "license": "MIT", "dependencies": { "@types/estree": "*" @@ -1255,7 +1197,7 @@ "version": "4.1.5", "resolved": "https://registry.npmjs.org/kleur/-/kleur-4.1.5.tgz", "integrity": "sha512-o+NO+8WrRiQEE4/7nwRJhN1HWpVmJm511pBHUxPLtp0BUISzlBplORYSmTclCnJvQq2tKu/sgl3xVpkc7ZWuQQ==", - "devOptional": true, + "dev": true, "license": "MIT", "engines": { "node": ">=6" @@ -1265,7 +1207,7 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/locate-character/-/locate-character-3.0.0.tgz", "integrity": "sha512-SW13ws7BjaeJ6p7Q6CO2nchbYEc3X3J6WrmTTDto7yMPqVSZTUyY5Tjbid+Ab8gLnATtygYtiDIJGQRRn2ZOiA==", - "devOptional": true, + "dev": true, "license": "MIT" }, "node_modules/lodash": { @@ -1278,7 +1220,7 @@ "version": "0.30.12", "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.12.tgz", "integrity": "sha512-Ea8I3sQMVXr8JhN4z+H/d8zwo+tYDgHE9+5G4Wnrwhs0gaK9fXTKx0Tw5Xwsd/bCPTTZNRAdpyzvoeORe9LYpw==", - "devOptional": true, + "dev": true, "license": "MIT", "dependencies": { "@jridgewell/sourcemap-codec": "^1.5.0" @@ -1309,7 +1251,7 @@ "version": "1.2.0", "resolved": "https://registry.npmjs.org/mri/-/mri-1.2.0.tgz", "integrity": "sha512-tzzskb3bG8LvYGFF/mDTpq3jpI6Q9wc3LEmBaghu+DdCssd1FakN7Bc0hVNmEyGq1bq3RgfkCb3cmQLpNPOroA==", - "devOptional": true, + "dev": true, "license": "MIT", "engines": { "node": ">=4" @@ -1319,7 +1261,7 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/mrmime/-/mrmime-2.0.0.tgz", "integrity": "sha512-eu38+hdgojoyq63s+yTpN4XMBdt5l8HhMhc4VKLO9KM5caLIBvUm4thi7fFaxyTmCKeNnXZ5pAlBwCUnhA09uw==", - "devOptional": true, + "dev": true, "license": "MIT", "engines": { "node": ">=10" @@ -1329,14 +1271,14 @@ "version": "2.1.3", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", - "devOptional": true, + "dev": true, "license": "MIT" }, "node_modules/nanoid": { "version": "3.3.7", "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.7.tgz", "integrity": "sha512-eSRppjcPIatRIMC1U6UngP8XFcz8MQWGQdt1MTBQ7NaAmvXDfvNxbvWV3x2y6CdEUciCSsDHDQZbhYaB8QEo2g==", - "devOptional": true, + "dev": true, "funding": [ { "type": "github", @@ -1355,14 +1297,14 @@ "version": "1.1.1", "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", - "devOptional": true, + "dev": true, "license": "ISC" }, "node_modules/postcss": { "version": "8.4.47", "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.47.tgz", "integrity": "sha512-56rxCq7G/XfB4EkXq9Egn5GCqugWvDFjafDOThIdMBsI15iqPqR5r15TfSr1YPYeEI19YeaXMCbY6u88Y76GLQ==", - "devOptional": true, + "dev": true, "funding": [ { "type": "opencollective", @@ -1414,7 +1356,7 @@ "version": "4.25.0", "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.25.0.tgz", "integrity": "sha512-uVbClXmR6wvx5R1M3Od4utyLUxrmOcEm3pAtMphn73Apq19PDtHpgZoEvqH2YnnaNUuvKmg2DgRd2Sqv+odyqg==", - "devOptional": true, + "dev": true, "license": "MIT", "dependencies": { "@types/estree": "1.0.6" @@ -1452,7 +1394,7 @@ "version": "1.8.1", "resolved": "https://registry.npmjs.org/sade/-/sade-1.8.1.tgz", "integrity": "sha512-xal3CZX1Xlo/k4ApwCFrHVACi9fBqJ7V+mwhBsuf/1IOKbBy098Fex+Wa/5QMubw09pSZ/u8EY8PWgevJsXp1A==", - "devOptional": true, + "dev": true, "license": "MIT", "dependencies": { "mri": "^1.1.0" @@ -1461,24 +1403,18 @@ "node": ">=6" } }, - "node_modules/server-only": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/server-only/-/server-only-0.0.1.tgz", - "integrity": "sha512-qepMx2JxAa5jjfzxG79yPPq+8BuFToHd1hm7kI+Z4zAq1ftQiP7HcxMhDDItrbtwVeLg/cY2JnKnrcFkmiswNA==", - "license": "MIT" - }, "node_modules/set-cookie-parser": { "version": "2.7.1", "resolved": "https://registry.npmjs.org/set-cookie-parser/-/set-cookie-parser-2.7.1.tgz", "integrity": "sha512-IOc8uWeOZgnb3ptbCURJWNjWUPcO3ZnTTdzsurqERrP6nPyv+paC55vJM0LpOlT2ne+Ix+9+CRG1MNLlyZ4GjQ==", - "devOptional": true, + "dev": true, "license": "MIT" }, "node_modules/sirv": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/sirv/-/sirv-3.0.0.tgz", "integrity": "sha512-BPwJGUeDaDCHihkORDchNyyTvWFhcusy1XMmhEVTQTwGeybFbp8YEmB+njbPnth1FibULBSBVwCQni25XlCUDg==", - "devOptional": true, + "dev": true, "license": "MIT", "dependencies": { "@polka/url": "^1.0.0-next.24", @@ -1493,7 +1429,7 @@ "version": "1.2.1", "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", - "devOptional": true, + "dev": true, "license": "BSD-3-Clause", "engines": { "node": ">=0.10.0" @@ -1503,7 +1439,7 @@ "version": "5.1.13", "resolved": "https://registry.npmjs.org/svelte/-/svelte-5.1.13.tgz", "integrity": "sha512-xVNk8yLsZNfkyqWzVg8+nfU9ewiSjVW0S4qyTxfKa6Y7P5ZBhA+LDsh2cHWIXJQMltikQAk6W3sqGdQZSH58PA==", - "devOptional": true, + "dev": true, "license": "MIT", "dependencies": { "@ampproject/remapping": "^2.3.0", @@ -1552,7 +1488,7 @@ "version": "0.2.9", "resolved": "https://registry.npmjs.org/tiny-glob/-/tiny-glob-0.2.9.tgz", "integrity": "sha512-g/55ssRPUjShh+xkfx9UPDXqhckHEsHr4Vd9zX55oSdGZc/MD0m3sferOkwWtp98bv+kcVfEHtRJgBVJzelrzg==", - "devOptional": true, + "dev": true, "license": "MIT", "dependencies": { "globalyzer": "0.1.0", @@ -1563,7 +1499,7 @@ "version": "3.0.1", "resolved": "https://registry.npmjs.org/totalist/-/totalist-3.0.1.tgz", "integrity": "sha512-sf4i37nQ2LBx4m3wB74y+ubopq6W/dIzXg0FDGjsYnZHVa1Da8FH853wlL2gtUhg+xJXjfk3kUZS3BRoQeoQBQ==", - "devOptional": true, + "dev": true, "license": "MIT", "engines": { "node": ">=6" @@ -1587,7 +1523,7 @@ "version": "5.4.10", "resolved": "https://registry.npmjs.org/vite/-/vite-5.4.10.tgz", "integrity": "sha512-1hvaPshuPUtxeQ0hsVH3Mud0ZanOLwVTneA1EgbAM5LhaZEqyPWGRQ7BtaMvUrTDeEaC8pxtj6a6jku3x4z6SQ==", - "devOptional": true, + "dev": true, "license": "MIT", "dependencies": { "esbuild": "^0.21.3", @@ -1647,7 +1583,7 @@ "version": "1.0.3", "resolved": "https://registry.npmjs.org/vitefu/-/vitefu-1.0.3.tgz", "integrity": "sha512-iKKfOMBHob2WxEJbqbJjHAkmYgvFDPhuqrO82om83S8RLk+17FtyMBfcyeH8GqD0ihShtkMW/zzJgiA51hCNCQ==", - "devOptional": true, + "dev": true, "license": "MIT", "workspaces": [ "tests/deps/*", @@ -1666,7 +1602,7 @@ "version": "1.1.2", "resolved": "https://registry.npmjs.org/zimmerframe/-/zimmerframe-1.1.2.tgz", "integrity": "sha512-rAbqEGa8ovJy4pyBxZM70hg4pE6gDgaQ0Sl9M3enG3I0d6H4XSAM3GeNGLKnsBpuijUow064sf7ww1nutC5/3w==", - "devOptional": true, + "dev": true, "license": "MIT" } } diff --git a/package.json b/package.json index 550928a..532b413 100644 --- a/package.json +++ b/package.json @@ -19,8 +19,6 @@ "vite": "^5.0.3" }, "dependencies": { - "@vercel/analytics": "^1.3.2", - "@vercel/speed-insights": "^1.1.0", "date-holidays": "^3.23.12", "i18n-iso-countries": "^7.13.0" } diff --git a/src/lib/CalendarMonth.svelte b/src/lib/CalendarMonth.svelte index bc2ad8e..2fd2b85 100644 --- a/src/lib/CalendarMonth.svelte +++ b/src/lib/CalendarMonth.svelte @@ -7,6 +7,7 @@ export let optimizedDaysOff: Date[]; export let consecutiveDaysOff: Array<{ startDate: Date; endDate: Date; totalDays: number }>; export let selectedCountryCode: string; + export let weekendDays: number[] = [6, 0]; // Function to determine the first day of the week based on locale function getFirstDayOfWeek(locale: string): number { @@ -80,11 +81,8 @@ }); } - function isWeekend(day: number): boolean { - const dayOfWeek = (adjustedFirstDay + day - 1) % 7; - const saturdayIndex = (6 - firstDayOfWeek + 7) % 7; - const sundayIndex = (7 - firstDayOfWeek + 7) % 7; - return dayOfWeek === saturdayIndex || dayOfWeek === sundayIndex; + function isWeekend(date: Date): boolean { + return weekendDays.includes(date.getDay()); } const dayInitials = ['S', 'M', 'T', 'W', 'T', 'F', 'S']; @@ -103,7 +101,7 @@
{/each} {#each Array.from({ length: daysInMonth }, (_, i) => i + 1) as day} -
+
{day} {#if getHoliday(day)} diff --git a/src/lib/holidayUtils.ts b/src/lib/holidayUtils.ts index 105749a..4c7ea3f 100644 --- a/src/lib/holidayUtils.ts +++ b/src/lib/holidayUtils.ts @@ -5,7 +5,7 @@ const MS_IN_A_DAY = 86400000; const MAX_GAP_LENGTH = 5; // Helper function to check if a date is a weekend -const isWeekend = (date: Date): boolean => date.getDay() === 0 || date.getDay() === 6; +const isWeekend = (date: Date, weekendDays: number[]): boolean => weekendDays.includes(date.getDay()); // Helper function to check if two dates are the same day const isSameDay = (date1: Date, date2: Date): boolean => @@ -17,7 +17,7 @@ const isSameDay = (date1: Date, date2: Date): boolean => const dateKey = (date: Date): string => `${date.getFullYear()}-${date.getMonth()}-${date.getDate()}`; // Helper function to check if a date is a holiday -const isHoliday = (date: Date, holidays: { date: Date }[]): boolean => holidays.some(h => isSameDay(h.date, date)); +const isHoliday = (date: Date, holidays: { date: Date }[]): boolean => holidays.some(h => dateKey(h.date) === dateKey(date)); // Helper function to check if a date is a day off const isDayOff = (date: Date, allDaysOffSet: Set): boolean => allDaysOffSet.has(dateKey(date)); @@ -50,22 +50,49 @@ export function getHolidaysForYear(countryCode: string, year: number, stateCode? } // Optimize days off to create the longest possible chains -export function optimizeDaysOff(holidays: { date: Date }[], year: number, daysOff: number): Date[] { +export function optimizeDaysOff( + holidays: { date: Date }[], + year: number, + daysOff: number, + weekendDays: number[] = [0, 6] +): Date[] { const currentYearHolidays = holidays.filter(h => h.date.getFullYear() === year); - const weekends = getWeekends(year); + const weekends = getWeekends(year, weekendDays); const allDaysOffSet = new Set([ ...currentYearHolidays.map(h => dateKey(h.date)), ...weekends.map(d => dateKey(d)) ]); - let rankedGaps = rankGapsByEfficiency(findGaps(allDaysOffSet, year), allDaysOffSet); + let rankedGaps = rankGapsByEfficiency( + findGaps(allDaysOffSet, year, weekendDays), + allDaysOffSet, + weekendDays + ); - return selectDaysOff(rankedGaps, daysOff, allDaysOffSet, year); + return selectDaysOff(rankedGaps, daysOff, allDaysOffSet, year, weekendDays); } // Calculate consecutive days off -export function calculateConsecutiveDaysOff(holidays: { date: Date }[], optimizedDaysOff: Date[], year: number): { startDate: Date; endDate: Date; usedDaysOff: number; totalDays: number }[] { - const allDaysOffSet = new Set([...holidays.map(h => dateKey(h.date)), ...optimizedDaysOff.map(d => dateKey(d))]); +export function calculateConsecutiveDaysOff( + holidays: Array<{ date: Date }>, + optimizedDaysOff: Date[], + year: number, + weekendDays: number[] = [0, 6] +) { + const allDaysOff = new Set([ + ...holidays.map(h => h.date.toISOString()), + ...optimizedDaysOff.map(d => d.toISOString()) + ]); + + // Add all weekend days for the year + const startDate = new Date(year, 0, 1); + const endDate = new Date(year, 11, 31); + for (let d = new Date(startDate); d <= endDate; d.setDate(d.getDate() + 1)) { + if (weekendDays.includes(d.getDay())) { + allDaysOff.add(new Date(d).toISOString()); + } + } + const consecutiveDaysOff: { startDate: Date; endDate: Date; usedDaysOff: number; totalDays: number }[] = []; let currentGroup: Date[] = []; @@ -74,39 +101,102 @@ export function calculateConsecutiveDaysOff(holidays: { date: Date }[], optimize const date = new Date(year, month, day); if (date.getMonth() !== month) break; - if (isWeekend(date) || isHoliday(date, holidays) || isDayOff(date, allDaysOffSet)) { + if (isWeekend(date, weekendDays) || isHoliday(date, holidays) || allDaysOff.has(date.toISOString())) { currentGroup.push(date); } else { - if (currentGroup.length > 2) { - addConsecutiveDaysOff(consecutiveDaysOff, currentGroup, optimizedDaysOff); + if (isValidPeriod(currentGroup, weekendDays, holidays, optimizedDaysOff)) { + const startDate = currentGroup[0]; + const endDate = currentGroup[currentGroup.length - 1]; + const totalDays = daysBetween(startDate, endDate) + 1; + const usedDaysOff = currentGroup.filter(d => + optimizedDaysOff.some(od => + od.getFullYear() === d.getFullYear() && + od.getMonth() === d.getMonth() && + od.getDate() === d.getDate() + ) + ).length; + + consecutiveDaysOff.push({ + startDate, + endDate, + usedDaysOff, + totalDays + }); } currentGroup = []; } } } - if (currentGroup.length > 2) { - addConsecutiveDaysOff(consecutiveDaysOff, currentGroup, optimizedDaysOff); + if (isValidPeriod(currentGroup, weekendDays, holidays, optimizedDaysOff)) { + const startDate = currentGroup[0]; + const endDate = currentGroup[currentGroup.length - 1]; + const totalDays = daysBetween(startDate, endDate) + 1; + const usedDaysOff = currentGroup.filter(d => + optimizedDaysOff.some(od => + od.getFullYear() === d.getFullYear() && + od.getMonth() === d.getMonth() && + od.getDate() === d.getDate() + ) + ).length; + + consecutiveDaysOff.push({ + startDate, + endDate, + usedDaysOff, + totalDays + }); } return consecutiveDaysOff; } +// Update the isValidPeriod function +function isValidPeriod( + group: Date[], + weekendDays: number[], + holidays: Array<{ date: Date }>, + optimizedDaysOff: Date[] +): boolean { + if (group.length < 2) return false; + + // Count weekend days + const weekendDates = group.filter(date => weekendDays.includes(date.getDay())); + if (weekendDates.length === 0) return false; // Must have at least one weekend day + + // Count non-weekend days that are either holidays or PTO days + const nonWeekendHolidayOrPTO = group.some(date => { + // Skip if it's a weekend day + if (weekendDays.includes(date.getDay())) return false; + + // Check if it's either a holiday or PTO day + return isHoliday(date, holidays) || + optimizedDaysOff.some(od => + od.getFullYear() === date.getFullYear() && + od.getMonth() === date.getMonth() && + od.getDate() === date.getDate() + ); + }); + + // Must have at least one weekend day AND one non-weekend holiday/PTO day + return nonWeekendHolidayOrPTO; +} + // Get all weekends for a specific year -function getWeekends(year: number): Date[] { +function getWeekends(year: number, weekendDays: number[]): Date[] { const weekends: Date[] = []; for (let month = 0; month < 12; month++) { for (let day = 1; day <= 31; day++) { const date = new Date(year, month, day); if (date.getMonth() !== month) break; - if (isWeekend(date)) weekends.push(date); + if (isWeekend(date, weekendDays)) weekends.push(date); } } return weekends; } // Find gaps between days off -function findGaps(allDaysOffSet: Set, year: number): { start: Date; end: Date; gapLength: number }[] { +function findGaps(allDaysOffSet: Set, year: number, weekendDays: number[]): { start: Date; end: Date; gapLength: number }[] { const gaps: { start: Date; end: Date; gapLength: number }[] = []; let currentGapStart: Date | null = null; @@ -117,7 +207,7 @@ function findGaps(allDaysOffSet: Set, year: number): { start: Date; end: const isDayOff = allDaysOffSet.has(dateKey(date)); - if (!isDayOff && !isWeekend(date)) { + if (!isDayOff && !isWeekend(date, weekendDays)) { if (!currentGapStart) currentGapStart = date; } else if (currentGapStart) { const gapLength = daysBetween(currentGapStart, date); @@ -141,19 +231,34 @@ function findGaps(allDaysOffSet: Set, year: number): { start: Date; end: } // Rank gaps by efficiency -function rankGapsByEfficiency(gaps: { start: Date; end: Date; gapLength: number }[], allDaysOffSet: Set): any[] { +function rankGapsByEfficiency( + gaps: { start: Date; end: Date; gapLength: number }[], + allDaysOffSet: Set, + weekendDays: number[] = [0, 6] +): any[] { return gaps.map(gap => { - const backward = calculateChain(gap.start, gap.gapLength, allDaysOffSet, 'backward'); - const forward = calculateChain(gap.end, gap.gapLength, allDaysOffSet, 'forward'); + const backward = calculateChain(gap.start, gap.gapLength, allDaysOffSet, 'backward', weekendDays); + const forward = calculateChain(gap.end, gap.gapLength, allDaysOffSet, 'forward', weekendDays); - return forward.chainLength > backward.chainLength || (forward.chainLength === backward.chainLength && forward.usedDaysOff <= backward.usedDaysOff) + return forward.chainLength > backward.chainLength || + (forward.chainLength === backward.chainLength && forward.usedDaysOff <= backward.usedDaysOff) ? { ...gap, ...forward, fillFrom: 'end' } : { ...gap, ...backward, fillFrom: 'start' }; - }).sort((a, b) => a.gapLength - b.gapLength || b.chainLength - a.chainLength || a.usedDaysOff - b.usedDaysOff); + }).sort((a, b) => + a.gapLength - b.gapLength || + b.chainLength - a.chainLength || + a.usedDaysOff - b.usedDaysOff + ); } // Calculate potential chain length and days off used -function calculateChain(startDate: Date, gapLength: number, allDaysOffSet: Set, direction: 'backward' | 'forward'): { chainLength: number; usedDaysOff: number } { +function calculateChain( + startDate: Date, + gapLength: number, + allDaysOffSet: Set, + direction: 'backward' | 'forward', + weekendDays: number[] = [0, 6] +): { chainLength: number; usedDaysOff: number } { let chainLength = gapLength; let usedDaysOff = 0; let currentDate = new Date(startDate); @@ -161,7 +266,8 @@ function calculateChain(startDate: Date, gapLength: number, allDaysOffSet: Set, year: number): Date[] { +function selectDaysOff(rankedGaps: any[], daysOff: number, allDaysOffSet: Set, year: number, weekendDays: number[]): Date[] { const selectedDays: Date[] = []; while (daysOff > 0 && rankedGaps.length > 0) { @@ -191,33 +297,40 @@ function selectDaysOff(rankedGaps: any[], daysOff: number, allDaysOffSet: Set isDayOff(d, new Set(optimizedDaysOff.map(d => dateKey(d))))).length; + + // Create a Set of optimized days off for faster lookup + const optimizedDaysOffSet = new Set(optimizedDaysOff.map(d => dateKey(d))); + + // Count only the days that were actually used from our PTO days + const usedDaysOff = currentGroup.filter(d => optimizedDaysOffSet.has(dateKey(d))).length; - if (totalDays > 2) { - consecutiveDaysOff.push({ - startDate, - endDate, - usedDaysOff, - totalDays - }); - } + consecutiveDaysOff.push({ + startDate, + endDate, + usedDaysOff, + totalDays + }); } \ No newline at end of file diff --git a/src/routes/+page.svelte b/src/routes/+page.svelte index 60415fd..9f45761 100644 --- a/src/routes/+page.svelte +++ b/src/routes/+page.svelte @@ -1,7 +1,5 @@
@@ -632,37 +711,74 @@
+
+
+ + Day Off +
+
+
+
+ + Public Holiday +
+ {#if holidays.length > 0} + showHolidaysList = !showHolidaysList} class="edit-link"> + (edit) + + {/if} +
- {#if showHolidaysList} + {#if showHolidaysList || showWeekendSettings}
-

Public Holidays

-
    - {#each holidays as holiday} -
  • - - - {formatDate(holiday.date)}: {holiday.name} - - -
  • - {/each} -
+ {#if showHolidaysList} +
+

Public Holidays

+
    + {#each holidays as holiday} +
  • +
    + + + {formatDate(holiday.date)}: {holiday.name} + +
    + +
  • + {/each} +
+
+ {/if} + + {#if showWeekendSettings} +
+

Weekend Days

+
    + {#each getOrderedDays() as {name, index}} +
  • +
    + + {name} +
    + +
  • + {/each} +
+
+ {/if}
{/if} @@ -676,6 +792,7 @@ optimizedDaysOff={optimizedDaysOff} consecutiveDaysOff={consecutiveDaysOff} selectedCountryCode={selectedCountryCode} + weekendDays={weekendDays} />
{/each}