perf: complete performance audit optimizations
This commit is contained in:
@ -8,6 +8,7 @@
|
|||||||
android:roundIcon="@mipmap/ic_launcher_round"
|
android:roundIcon="@mipmap/ic_launcher_round"
|
||||||
android:supportsRtl="true"
|
android:supportsRtl="true"
|
||||||
android:theme="@style/AppTheme"
|
android:theme="@style/AppTheme"
|
||||||
|
android:hardwareAccelerated="true"
|
||||||
android:networkSecurityConfig="@xml/network_security_config">
|
android:networkSecurityConfig="@xml/network_security_config">
|
||||||
|
|
||||||
<activity
|
<activity
|
||||||
|
|||||||
@ -8,6 +8,9 @@ const config: CapacitorConfig = {
|
|||||||
androidScheme: 'http',
|
androidScheme: 'http',
|
||||||
cleartext: true
|
cleartext: true
|
||||||
},
|
},
|
||||||
|
android: {
|
||||||
|
allowMixedContent: false,
|
||||||
|
},
|
||||||
plugins: {
|
plugins: {
|
||||||
SplashScreen: {
|
SplashScreen: {
|
||||||
launchShowDuration: 2000,
|
launchShowDuration: 2000,
|
||||||
@ -16,6 +19,9 @@ const config: CapacitorConfig = {
|
|||||||
androidSplashResourceName: "splash",
|
androidSplashResourceName: "splash",
|
||||||
androidScaleType: "CENTER_CROP",
|
androidScaleType: "CENTER_CROP",
|
||||||
showSpinner: false,
|
showSpinner: false,
|
||||||
|
},
|
||||||
|
Geolocation: {
|
||||||
|
androidUsePreciseLocation: true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
351
frontend/package-lock.json
generated
351
frontend/package-lock.json
generated
@ -31,7 +31,9 @@
|
|||||||
"@vue/tsconfig": "^0.8.1",
|
"@vue/tsconfig": "^0.8.1",
|
||||||
"autoprefixer": "^10.4.24",
|
"autoprefixer": "^10.4.24",
|
||||||
"postcss": "^8.5.6",
|
"postcss": "^8.5.6",
|
||||||
|
"rollup-plugin-visualizer": "^7.0.0",
|
||||||
"tailwindcss": "^4.2.0",
|
"tailwindcss": "^4.2.0",
|
||||||
|
"terser": "^5.46.0",
|
||||||
"typescript": "~5.9.3",
|
"typescript": "~5.9.3",
|
||||||
"vite": "^7.2.4",
|
"vite": "^7.2.4",
|
||||||
"vite-plugin-pwa": "^1.2.0",
|
"vite-plugin-pwa": "^1.2.0",
|
||||||
@ -3773,6 +3775,19 @@
|
|||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
|
"node_modules/ansi-regex": {
|
||||||
|
"version": "6.2.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.2.2.tgz",
|
||||||
|
"integrity": "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=12"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/chalk/ansi-regex?sponsor=1"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/ansi-styles": {
|
"node_modules/ansi-styles": {
|
||||||
"version": "4.3.0",
|
"version": "4.3.0",
|
||||||
"resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz",
|
"resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz",
|
||||||
@ -4220,6 +4235,93 @@
|
|||||||
"node": ">=18"
|
"node": ">=18"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/cliui": {
|
||||||
|
"version": "9.0.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/cliui/-/cliui-9.0.1.tgz",
|
||||||
|
"integrity": "sha512-k7ndgKhwoQveBL+/1tqGJYNz097I7WOvwbmmU2AR5+magtbjPWQTS1C5vzGkBC8Ym8UWRzfKUzUUqFLypY4Q+w==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "ISC",
|
||||||
|
"dependencies": {
|
||||||
|
"string-width": "^7.2.0",
|
||||||
|
"strip-ansi": "^7.1.0",
|
||||||
|
"wrap-ansi": "^9.0.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=20"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/cliui/node_modules/ansi-styles": {
|
||||||
|
"version": "6.2.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.3.tgz",
|
||||||
|
"integrity": "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=12"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/chalk/ansi-styles?sponsor=1"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/cliui/node_modules/emoji-regex": {
|
||||||
|
"version": "10.6.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-10.6.0.tgz",
|
||||||
|
"integrity": "sha512-toUI84YS5YmxW219erniWD0CIVOo46xGKColeNQRgOzDorgBi1v4D71/OFzgD9GO2UGKIv1C3Sp8DAn0+j5w7A==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
|
"node_modules/cliui/node_modules/string-width": {
|
||||||
|
"version": "7.2.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/string-width/-/string-width-7.2.0.tgz",
|
||||||
|
"integrity": "sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"emoji-regex": "^10.3.0",
|
||||||
|
"get-east-asian-width": "^1.0.0",
|
||||||
|
"strip-ansi": "^7.1.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=18"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/sponsors/sindresorhus"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/cliui/node_modules/strip-ansi": {
|
||||||
|
"version": "7.2.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.2.0.tgz",
|
||||||
|
"integrity": "sha512-yDPMNjp4WyfYBkHnjIRLfca1i6KMyGCtsVgoKe/z1+6vukgaENdgGBZt+ZmKPc4gavvEZ5OgHfHdrazhgNyG7w==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"ansi-regex": "^6.2.2"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=12"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/chalk/strip-ansi?sponsor=1"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/cliui/node_modules/wrap-ansi": {
|
||||||
|
"version": "9.0.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-9.0.2.tgz",
|
||||||
|
"integrity": "sha512-42AtmgqjV+X1VpdOfyTGOYRi0/zsoLqtXQckTmqTeybT+BDIbM/Guxo7x3pE2vtpr1ok6xRqM9OpBe+Jyoqyww==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"ansi-styles": "^6.2.1",
|
||||||
|
"string-width": "^7.0.0",
|
||||||
|
"strip-ansi": "^7.1.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=18"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/chalk/wrap-ansi?sponsor=1"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/color-convert": {
|
"node_modules/color-convert": {
|
||||||
"version": "2.0.1",
|
"version": "2.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
|
||||||
@ -5080,6 +5182,29 @@
|
|||||||
"node": ">=6.9.0"
|
"node": ">=6.9.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/get-caller-file": {
|
||||||
|
"version": "2.0.5",
|
||||||
|
"resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz",
|
||||||
|
"integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "ISC",
|
||||||
|
"engines": {
|
||||||
|
"node": "6.* || 8.* || >= 10.*"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/get-east-asian-width": {
|
||||||
|
"version": "1.5.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/get-east-asian-width/-/get-east-asian-width-1.5.0.tgz",
|
||||||
|
"integrity": "sha512-CQ+bEO+Tva/qlmw24dCejulK5pMzVnUOFOijVogd3KQs07HnRIgp8TGipvCCRT06xeYEbpbgwaCxglFyiuIcmA==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=18"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/sponsors/sindresorhus"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/get-intrinsic": {
|
"node_modules/get-intrinsic": {
|
||||||
"version": "1.3.0",
|
"version": "1.3.0",
|
||||||
"resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz",
|
"resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz",
|
||||||
@ -5530,6 +5655,19 @@
|
|||||||
"url": "https://github.com/sponsors/ljharb"
|
"url": "https://github.com/sponsors/ljharb"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/is-in-ssh": {
|
||||||
|
"version": "1.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/is-in-ssh/-/is-in-ssh-1.0.0.tgz",
|
||||||
|
"integrity": "sha512-jYa6Q9rH90kR1vKB6NM7qqd1mge3Fx4Dhw5TVlK1MUBqhEOuCagrEHMevNuCcbECmXZ0ThXkRm+Ymr51HwEPAw==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=20"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/sponsors/sindresorhus"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/is-inside-container": {
|
"node_modules/is-inside-container": {
|
||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/is-inside-container/-/is-inside-container-1.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/is-inside-container/-/is-inside-container-1.0.0.tgz",
|
||||||
@ -6618,6 +6756,19 @@
|
|||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
|
"node_modules/powershell-utils": {
|
||||||
|
"version": "0.1.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/powershell-utils/-/powershell-utils-0.1.0.tgz",
|
||||||
|
"integrity": "sha512-dM0jVuXJPsDN6DvRpea484tCUaMiXWjuCn++HGTqUWzGDjv5tZkEZldAJ/UMlqRYGFrD/etByo4/xOuC/snX2A==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=20"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/sponsors/sindresorhus"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/pretty-bytes": {
|
"node_modules/pretty-bytes": {
|
||||||
"version": "6.1.1",
|
"version": "6.1.1",
|
||||||
"resolved": "https://registry.npmjs.org/pretty-bytes/-/pretty-bytes-6.1.1.tgz",
|
"resolved": "https://registry.npmjs.org/pretty-bytes/-/pretty-bytes-6.1.1.tgz",
|
||||||
@ -6981,6 +7132,127 @@
|
|||||||
"fsevents": "~2.3.2"
|
"fsevents": "~2.3.2"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/rollup-plugin-visualizer": {
|
||||||
|
"version": "7.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/rollup-plugin-visualizer/-/rollup-plugin-visualizer-7.0.0.tgz",
|
||||||
|
"integrity": "sha512-loo4kmhTg7GMO0hqaUv/azvLPUT2B4jXU3gNMG35gm1mWKpOzhV6rspb/Mqmsfg7oOTdkzdmOckCIwGB5Ca1CA==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"open": "^11.0.0",
|
||||||
|
"picomatch": "^4.0.2",
|
||||||
|
"source-map": "^0.7.4",
|
||||||
|
"yargs": "^18.0.0"
|
||||||
|
},
|
||||||
|
"bin": {
|
||||||
|
"rollup-plugin-visualizer": "dist/bin/cli.js"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=22"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"rolldown": "1.x || ^1.0.0-beta || ^1.0.0-rc",
|
||||||
|
"rollup": "2.x || 3.x || 4.x"
|
||||||
|
},
|
||||||
|
"peerDependenciesMeta": {
|
||||||
|
"rolldown": {
|
||||||
|
"optional": true
|
||||||
|
},
|
||||||
|
"rollup": {
|
||||||
|
"optional": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/rollup-plugin-visualizer/node_modules/define-lazy-prop": {
|
||||||
|
"version": "3.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/define-lazy-prop/-/define-lazy-prop-3.0.0.tgz",
|
||||||
|
"integrity": "sha512-N+MeXYoqr3pOgn8xfyRPREN7gHakLYjhsHhWGT3fWAiL4IkAt0iDw14QiiEm2bE30c5XX5q0FtAA3CK5f9/BUg==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=12"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/sponsors/sindresorhus"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/rollup-plugin-visualizer/node_modules/is-wsl": {
|
||||||
|
"version": "3.1.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-3.1.1.tgz",
|
||||||
|
"integrity": "sha512-e6rvdUCiQCAuumZslxRJWR/Doq4VpPR82kqclvcS0efgt430SlGIk05vdCN58+VrzgtIcfNODjozVielycD4Sw==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"is-inside-container": "^1.0.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=16"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/sponsors/sindresorhus"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/rollup-plugin-visualizer/node_modules/open": {
|
||||||
|
"version": "11.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/open/-/open-11.0.0.tgz",
|
||||||
|
"integrity": "sha512-smsWv2LzFjP03xmvFoJ331ss6h+jixfA4UUV/Bsiyuu4YJPfN+FIQGOIiv4w9/+MoHkfkJ22UIaQWRVFRfH6Vw==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"default-browser": "^5.4.0",
|
||||||
|
"define-lazy-prop": "^3.0.0",
|
||||||
|
"is-in-ssh": "^1.0.0",
|
||||||
|
"is-inside-container": "^1.0.0",
|
||||||
|
"powershell-utils": "^0.1.0",
|
||||||
|
"wsl-utils": "^0.3.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=20"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/sponsors/sindresorhus"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/rollup-plugin-visualizer/node_modules/picomatch": {
|
||||||
|
"version": "4.0.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz",
|
||||||
|
"integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=12"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/sponsors/jonschlinkert"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/rollup-plugin-visualizer/node_modules/source-map": {
|
||||||
|
"version": "0.7.6",
|
||||||
|
"resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.6.tgz",
|
||||||
|
"integrity": "sha512-i5uvt8C3ikiWeNZSVZNWcfZPItFQOsYTUAOkcUPGd8DqDy1uOUikjt5dG+uRlwyvR108Fb9DOd4GvXfT0N2/uQ==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "BSD-3-Clause",
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 12"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/rollup-plugin-visualizer/node_modules/wsl-utils": {
|
||||||
|
"version": "0.3.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/wsl-utils/-/wsl-utils-0.3.1.tgz",
|
||||||
|
"integrity": "sha512-g/eziiSUNBSsdDJtCLB8bdYEUMj4jR7AGeUo96p/3dTafgjHhpF4RiCFPiRILwjQoDXx5MqkBr4fwWtR3Ky4Wg==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"is-wsl": "^3.1.0",
|
||||||
|
"powershell-utils": "^0.1.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=20"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/sponsors/sindresorhus"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/run-applescript": {
|
"node_modules/run-applescript": {
|
||||||
"version": "7.1.0",
|
"version": "7.1.0",
|
||||||
"resolved": "https://registry.npmjs.org/run-applescript/-/run-applescript-7.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/run-applescript/-/run-applescript-7.1.0.tgz",
|
||||||
@ -9134,6 +9406,16 @@
|
|||||||
"node": ">=8.0"
|
"node": ">=8.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/y18n": {
|
||||||
|
"version": "5.0.8",
|
||||||
|
"resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz",
|
||||||
|
"integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "ISC",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=10"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/yallist": {
|
"node_modules/yallist": {
|
||||||
"version": "5.0.0",
|
"version": "5.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/yallist/-/yallist-5.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/yallist/-/yallist-5.0.0.tgz",
|
||||||
@ -9143,6 +9425,75 @@
|
|||||||
"node": ">=18"
|
"node": ">=18"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/yargs": {
|
||||||
|
"version": "18.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/yargs/-/yargs-18.0.0.tgz",
|
||||||
|
"integrity": "sha512-4UEqdc2RYGHZc7Doyqkrqiln3p9X2DZVxaGbwhn2pi7MrRagKaOcIKe8L3OxYcbhXLgLFUS3zAYuQjKBQgmuNg==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"cliui": "^9.0.1",
|
||||||
|
"escalade": "^3.1.1",
|
||||||
|
"get-caller-file": "^2.0.5",
|
||||||
|
"string-width": "^7.2.0",
|
||||||
|
"y18n": "^5.0.5",
|
||||||
|
"yargs-parser": "^22.0.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": "^20.19.0 || ^22.12.0 || >=23"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/yargs-parser": {
|
||||||
|
"version": "22.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-22.0.0.tgz",
|
||||||
|
"integrity": "sha512-rwu/ClNdSMpkSrUb+d6BRsSkLUq1fmfsY6TOpYzTwvwkg1/NRG85KBy3kq++A8LKQwX6lsu+aWad+2khvuXrqw==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "ISC",
|
||||||
|
"engines": {
|
||||||
|
"node": "^20.19.0 || ^22.12.0 || >=23"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/yargs/node_modules/emoji-regex": {
|
||||||
|
"version": "10.6.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-10.6.0.tgz",
|
||||||
|
"integrity": "sha512-toUI84YS5YmxW219erniWD0CIVOo46xGKColeNQRgOzDorgBi1v4D71/OFzgD9GO2UGKIv1C3Sp8DAn0+j5w7A==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
|
"node_modules/yargs/node_modules/string-width": {
|
||||||
|
"version": "7.2.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/string-width/-/string-width-7.2.0.tgz",
|
||||||
|
"integrity": "sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"emoji-regex": "^10.3.0",
|
||||||
|
"get-east-asian-width": "^1.0.0",
|
||||||
|
"strip-ansi": "^7.1.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=18"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/sponsors/sindresorhus"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/yargs/node_modules/strip-ansi": {
|
||||||
|
"version": "7.2.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.2.0.tgz",
|
||||||
|
"integrity": "sha512-yDPMNjp4WyfYBkHnjIRLfca1i6KMyGCtsVgoKe/z1+6vukgaENdgGBZt+ZmKPc4gavvEZ5OgHfHdrazhgNyG7w==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"ansi-regex": "^6.2.2"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=12"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/chalk/strip-ansi?sponsor=1"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/yauzl": {
|
"node_modules/yauzl": {
|
||||||
"version": "2.10.0",
|
"version": "2.10.0",
|
||||||
"resolved": "https://registry.npmjs.org/yauzl/-/yauzl-2.10.0.tgz",
|
"resolved": "https://registry.npmjs.org/yauzl/-/yauzl-2.10.0.tgz",
|
||||||
|
|||||||
@ -32,7 +32,9 @@
|
|||||||
"@vue/tsconfig": "^0.8.1",
|
"@vue/tsconfig": "^0.8.1",
|
||||||
"autoprefixer": "^10.4.24",
|
"autoprefixer": "^10.4.24",
|
||||||
"postcss": "^8.5.6",
|
"postcss": "^8.5.6",
|
||||||
|
"rollup-plugin-visualizer": "^7.0.0",
|
||||||
"tailwindcss": "^4.2.0",
|
"tailwindcss": "^4.2.0",
|
||||||
|
"terser": "^5.46.0",
|
||||||
"typescript": "~5.9.3",
|
"typescript": "~5.9.3",
|
||||||
"vite": "^7.2.4",
|
"vite": "^7.2.4",
|
||||||
"vite-plugin-pwa": "^1.2.0",
|
"vite-plugin-pwa": "^1.2.0",
|
||||||
|
|||||||
@ -5,14 +5,14 @@ import type { BusStop, Route } from '@/types'
|
|||||||
export const busStopsService = {
|
export const busStopsService = {
|
||||||
/** Get all bus stops */
|
/** Get all bus stops */
|
||||||
async getAllBusStops(): Promise<BusStop[]> {
|
async getAllBusStops(): Promise<BusStop[]> {
|
||||||
const { data, error } = await supabase.from('bus_stops').select('*')
|
const { data, error } = await supabase.from('bus_stops').select('id, name, latitude, longitude, city, address, parent_id, side, stop_type, has_shelter, has_seating, is_accessible, created_at, updated_at')
|
||||||
if (error) throw new Error(error.message)
|
if (error) throw new Error(error.message)
|
||||||
return data as BusStop[]
|
return data as BusStop[]
|
||||||
},
|
},
|
||||||
|
|
||||||
/** Get a single bus stop by ID */
|
/** Get a single bus stop by ID */
|
||||||
async getBusStopById(id: string): Promise<BusStop> {
|
async getBusStopById(id: string): Promise<BusStop> {
|
||||||
const { data, error } = await supabase.from('bus_stops').select('*').eq('id', id).single()
|
const { data, error } = await supabase.from('bus_stops').select('id, name, latitude, longitude, city, address, parent_id, side, stop_type, has_shelter, has_seating, is_accessible, created_at, updated_at').eq('id', id).single()
|
||||||
if (error) throw new Error(error.message)
|
if (error) throw new Error(error.message)
|
||||||
return data as BusStop
|
return data as BusStop
|
||||||
},
|
},
|
||||||
|
|||||||
@ -18,14 +18,14 @@ export const businessService = {
|
|||||||
|
|
||||||
/** Get all businesses */
|
/** Get all businesses */
|
||||||
async getAllBusinesses(): Promise<Business[]> {
|
async getAllBusinesses(): Promise<Business[]> {
|
||||||
const { data, error } = await supabase.from('businesses').select('*')
|
const { data, error } = await supabase.from('businesses').select('id, name, address, phone, image_url, social_media, category, latitude, longitude, area, updated_at')
|
||||||
if (error) throw new Error(error.message)
|
if (error) throw new Error(error.message)
|
||||||
return data as Business[]
|
return data as Business[]
|
||||||
},
|
},
|
||||||
|
|
||||||
/** Get a single business by ID */
|
/** Get a single business by ID */
|
||||||
async getBusiness(id: string): Promise<Business> {
|
async getBusiness(id: string): Promise<Business> {
|
||||||
const { data, error } = await supabase.from('businesses').select('*').eq('id', id).single()
|
const { data, error } = await supabase.from('businesses').select('id, name, address, phone, image_url, social_media, category, latitude, longitude, area, updated_at').eq('id', id).single()
|
||||||
if (error) throw new Error(error.message)
|
if (error) throw new Error(error.message)
|
||||||
return data as Business
|
return data as Business
|
||||||
},
|
},
|
||||||
|
|||||||
@ -5,7 +5,7 @@ import type { Route, BusStop } from '@/types'
|
|||||||
export const routesService = {
|
export const routesService = {
|
||||||
/** Get all routes with optional filtering */
|
/** Get all routes with optional filtering */
|
||||||
async getAllRoutes(filters?: { originCity?: string, destinationCity?: string }): Promise<Route[]> {
|
async getAllRoutes(filters?: { originCity?: string, destinationCity?: string }): Promise<Route[]> {
|
||||||
let query = supabase.from('routes').select('*')
|
let query = supabase.from('routes').select('id, name, description, color, direction, origin_city, destination_city, distance_km, estimated_duration_minutes, average_speed_kmh, status, created_at, updated_at')
|
||||||
|
|
||||||
if (filters?.originCity) {
|
if (filters?.originCity) {
|
||||||
query = query.eq('origin_city', filters.originCity)
|
query = query.eq('origin_city', filters.originCity)
|
||||||
@ -21,7 +21,7 @@ export const routesService = {
|
|||||||
|
|
||||||
/** Get a single route by ID */
|
/** Get a single route by ID */
|
||||||
async getRouteById(id: string): Promise<Route> {
|
async getRouteById(id: string): Promise<Route> {
|
||||||
const { data, error } = await supabase.from('routes').select('*').eq('id', id).single()
|
const { data, error } = await supabase.from('routes').select('id, name, description, color, direction, origin_city, destination_city, distance_km, estimated_duration_minutes, average_speed_kmh, status, created_at, updated_at').eq('id', id).single()
|
||||||
if (error) throw new Error(error.message)
|
if (error) throw new Error(error.message)
|
||||||
return data as Route
|
return data as Route
|
||||||
},
|
},
|
||||||
|
|||||||
@ -32,7 +32,16 @@ export const useBusStopStore = defineStore('busStop', () => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async function loadBusStopById(id: string) {
|
async function loadBusStopById(id: string, force = false) {
|
||||||
|
// Buscar en cache primero
|
||||||
|
if (!force && busStops.value.length > 0) {
|
||||||
|
const cachedStop = busStops.value.find(s => s.id === id);
|
||||||
|
if (cachedStop) {
|
||||||
|
selectedStop.value = cachedStop;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
isLoading.value = true
|
isLoading.value = true
|
||||||
error.value = null
|
error.value = null
|
||||||
try {
|
try {
|
||||||
|
|||||||
@ -12,7 +12,8 @@ export const useRouteStore = defineStore('route', () => {
|
|||||||
const isLoadingRoutes = ref(false)
|
const isLoadingRoutes = ref(false)
|
||||||
const isLoadingStops = ref(false)
|
const isLoadingStops = ref(false)
|
||||||
const error = ref<string | null>(null)
|
const error = ref<string | null>(null)
|
||||||
const lastFetched = ref<number>(0) // ⚡ CACHÉ ESTÁTICO
|
const lastFetched = ref<number>(0) // ⚡ CACHÉ ESTÁTICO RUTAS
|
||||||
|
const stopsCache = ref<Map<string, { fetchedAt: number, stops: BusStop[] }>>(new Map()) // ⚡ CACHÉ ESTÁTICO PARADAS
|
||||||
|
|
||||||
const hasSelectedRoute = computed(() => selectedRouteId.value !== null && selectedRouteName.value !== null)
|
const hasSelectedRoute = computed(() => selectedRouteId.value !== null && selectedRouteName.value !== null)
|
||||||
|
|
||||||
@ -38,11 +39,24 @@ export const useRouteStore = defineStore('route', () => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async function loadRouteStops(routeId: string) {
|
async function loadRouteStops(routeId: string, force = false) {
|
||||||
|
const CACHE_TIME = 1000 * 60 * 15; // 15 minutos
|
||||||
|
const now = Date.now();
|
||||||
|
|
||||||
|
if (!force && stopsCache.value.has(routeId)) {
|
||||||
|
const cacheEntry = stopsCache.value.get(routeId)!;
|
||||||
|
if (now - cacheEntry.fetchedAt < CACHE_TIME) {
|
||||||
|
selectedRouteStops.value = cacheEntry.stops;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
isLoadingStops.value = true
|
isLoadingStops.value = true
|
||||||
error.value = null
|
error.value = null
|
||||||
try {
|
try {
|
||||||
selectedRouteStops.value = await routesService.getRouteStops(routeId)
|
const stops = await routesService.getRouteStops(routeId)
|
||||||
|
selectedRouteStops.value = stops
|
||||||
|
stopsCache.value.set(routeId, { fetchedAt: now, stops })
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
error.value = e instanceof Error ? e.message : 'Failed to load route stops'
|
error.value = e instanceof Error ? e.message : 'Failed to load route stops'
|
||||||
console.error('Error loading route stops:', e)
|
console.error('Error loading route stops:', e)
|
||||||
|
|||||||
@ -51,7 +51,8 @@ import { ref, onMounted } from 'vue'
|
|||||||
import { useRouter } from 'vue-router'
|
import { useRouter } from 'vue-router'
|
||||||
import { busStopsService } from '@/services/busStopsService'
|
import { busStopsService } from '@/services/busStopsService'
|
||||||
import type { BusStop } from '@/types'
|
import type { BusStop } from '@/types'
|
||||||
import BusStopEditor from '@/components/BusStopEditor.vue'
|
import { defineAsyncComponent } from 'vue'
|
||||||
|
const BusStopEditor = defineAsyncComponent(() => import('@/components/BusStopEditor.vue'))
|
||||||
|
|
||||||
const router = useRouter()
|
const router = useRouter()
|
||||||
const stops = ref<BusStop[]>([])
|
const stops = ref<BusStop[]>([])
|
||||||
|
|||||||
@ -1,8 +1,9 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { ref, onMounted } from 'vue'
|
import { ref, onMounted } from 'vue'
|
||||||
import { useRouter, useRoute } from 'vue-router'
|
import { useRouter, useRoute } from 'vue-router'
|
||||||
import LoginForm from '@/components/auth/LoginForm.vue'
|
import { defineAsyncComponent } from 'vue'
|
||||||
import RegisterForm from '@/components/auth/RegisterForm.vue'
|
const LoginForm = defineAsyncComponent(() => import('@/components/auth/LoginForm.vue'))
|
||||||
|
const RegisterForm = defineAsyncComponent(() => import('@/components/auth/RegisterForm.vue'))
|
||||||
|
|
||||||
const isLogin = ref(true)
|
const isLogin = ref(true)
|
||||||
const toggleAuth = () => { isLogin.value = !isLogin.value }
|
const toggleAuth = () => { isLogin.value = !isLogin.value }
|
||||||
|
|||||||
@ -122,11 +122,12 @@ function getCategoryIcon(category?: string | null) {
|
|||||||
<div
|
<div
|
||||||
v-for="coupon in filteredCoupons"
|
v-for="coupon in filteredCoupons"
|
||||||
:key="coupon.id"
|
:key="coupon.id"
|
||||||
|
v-memo="[coupon.id]"
|
||||||
class="offer-card-new"
|
class="offer-card-new"
|
||||||
@click="openCoupon(coupon)"
|
@click="openCoupon(coupon)"
|
||||||
>
|
>
|
||||||
<div class="offer-image-wrapper">
|
<div class="offer-image-wrapper">
|
||||||
<img :src="getImageUrl(coupon.image_url)" :alt="coupon.title" class="offer-img">
|
<img :src="getImageUrl(coupon.image_url)" :alt="coupon.title" loading="lazy" decoding="async" class="offer-img">
|
||||||
<div class="status-badge" :class="{ 'tmr': coupon.title.toLowerCase().includes('mañana') || (coupon.description?.toLowerCase().includes('mañana') ?? false) }">
|
<div class="status-badge" :class="{ 'tmr': coupon.title.toLowerCase().includes('mañana') || (coupon.description?.toLowerCase().includes('mañana') ?? false) }">
|
||||||
<span class="material-icons">schedule</span>
|
<span class="material-icons">schedule</span>
|
||||||
{{ coupon.title.toLowerCase().includes('mañana') ? t('coupons.tomorrow') : t('coupons.active') }}
|
{{ coupon.title.toLowerCase().includes('mañana') ? t('coupons.tomorrow') : t('coupons.active') }}
|
||||||
|
|||||||
@ -5,7 +5,7 @@ import { useI18n } from 'vue-i18n'
|
|||||||
import { useTaxiStore } from '@/stores/taxi'
|
import { useTaxiStore } from '@/stores/taxi'
|
||||||
import { useShuttleStore } from '@/stores/shuttle'
|
import { useShuttleStore } from '@/stores/shuttle'
|
||||||
import { analyticsService } from '@/services/analyticsService'
|
import { analyticsService } from '@/services/analyticsService'
|
||||||
import type { Taxi, Shuttle } from '@/types'
|
import type { Taxi } from '@/types'
|
||||||
import FavoriteButton from '@/components/FavoriteButton.vue'
|
import FavoriteButton from '@/components/FavoriteButton.vue'
|
||||||
import { getImageUrl } from '@/utils/imageUrl'
|
import { getImageUrl } from '@/utils/imageUrl'
|
||||||
|
|
||||||
|
|||||||
@ -3,6 +3,7 @@ import vue from '@vitejs/plugin-vue'
|
|||||||
import { VitePWA } from 'vite-plugin-pwa'
|
import { VitePWA } from 'vite-plugin-pwa'
|
||||||
import tailwindcss from '@tailwindcss/vite'
|
import tailwindcss from '@tailwindcss/vite'
|
||||||
import path from 'path'
|
import path from 'path'
|
||||||
|
import { visualizer } from 'rollup-plugin-visualizer'
|
||||||
|
|
||||||
// https://vite.dev/config/
|
// https://vite.dev/config/
|
||||||
export default defineConfig(() => {
|
export default defineConfig(() => {
|
||||||
@ -10,6 +11,12 @@ export default defineConfig(() => {
|
|||||||
plugins: [
|
plugins: [
|
||||||
vue(),
|
vue(),
|
||||||
tailwindcss(),
|
tailwindcss(),
|
||||||
|
visualizer({
|
||||||
|
open: false,
|
||||||
|
filename: 'dist/stats.html',
|
||||||
|
gzipSize: true,
|
||||||
|
brotliSize: true
|
||||||
|
}),
|
||||||
VitePWA({
|
VitePWA({
|
||||||
selfDestroying: true,
|
selfDestroying: true,
|
||||||
registerType: 'autoUpdate',
|
registerType: 'autoUpdate',
|
||||||
@ -48,36 +55,39 @@ export default defineConfig(() => {
|
|||||||
navigateFallbackDenylist: [/^\/api/, /^\/rest\/v1/],
|
navigateFallbackDenylist: [/^\/api/, /^\/rest\/v1/],
|
||||||
runtimeCaching: [
|
runtimeCaching: [
|
||||||
{
|
{
|
||||||
// ASSETS EXTERNOS E IMÁGENES SUPERBASE
|
urlPattern: /^https:\/\/maps\.googleapis\.com\/.*/i,
|
||||||
urlPattern: /^https:\/\/(.*\.(png|jpg|jpeg|svg|webp|woff2|css))/,
|
|
||||||
handler: 'CacheFirst',
|
handler: 'CacheFirst',
|
||||||
options: {
|
options: {
|
||||||
cacheName: 'assets-estaticos-sibu',
|
cacheName: 'google-maps-cache',
|
||||||
expiration: { maxEntries: 100, maxAgeSeconds: 60 * 60 * 24 * 30 }, // 30 días
|
expiration: { maxEntries: 50, maxAgeSeconds: 604800 }
|
||||||
cacheableResponse: { statuses: [0, 200] },
|
}
|
||||||
},
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
// LLAMADAS API SEMI-ESTÁTICAS (Supabase listas que no mutan tan rápido)
|
urlPattern: /^https:\/\/.*\.supabase\.co\/rest\/v1\/(routes|bus_stops|businesses).*/i,
|
||||||
urlPattern: /^https:\/\/.*\.supabase\.co\/rest\/v1\/(routes|bus_stops)/,
|
|
||||||
handler: 'StaleWhileRevalidate',
|
handler: 'StaleWhileRevalidate',
|
||||||
options: {
|
options: {
|
||||||
cacheName: 'api-estatica-sibu',
|
cacheName: 'supabase-static-cache',
|
||||||
cacheableResponse: { statuses: [0, 200] },
|
expiration: { maxEntries: 30, maxAgeSeconds: 300 }
|
||||||
},
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
// LLAMADAS API REALTIME / DELUXE
|
urlPattern: /^https:\/\/.*\.supabase\.co\/rest\/v1\/.*/i,
|
||||||
urlPattern: /^https:\/\/.*\.supabase\.co\/rest\/v1\/(shuttles|locations|users)/,
|
|
||||||
handler: 'NetworkFirst',
|
handler: 'NetworkFirst',
|
||||||
options: {
|
options: {
|
||||||
cacheName: 'api-dinamica',
|
cacheName: 'supabase-dynamic-cache',
|
||||||
networkTimeoutSeconds: 5, // Vital en zonas rurales: si el 3G no responde en 5s, muestra la caché
|
networkTimeoutSeconds: 5,
|
||||||
cacheableResponse: { statuses: [0, 200] },
|
expiration: { maxEntries: 50, maxAgeSeconds: 60 }
|
||||||
},
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
urlPattern: /^https:\/\/.*\.supabase\.co\/storage\/.*/i,
|
||||||
|
handler: 'CacheFirst',
|
||||||
|
options: {
|
||||||
|
cacheName: 'supabase-images-cache',
|
||||||
|
expiration: { maxEntries: 100, maxAgeSeconds: 2592000 }
|
||||||
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
// FONT CACHE (Google)
|
|
||||||
urlPattern: /^https:\/\/fonts\.(?:googleapis|gstatic)\.com\/.*/i,
|
urlPattern: /^https:\/\/fonts\.(?:googleapis|gstatic)\.com\/.*/i,
|
||||||
handler: 'CacheFirst',
|
handler: 'CacheFirst',
|
||||||
options: {
|
options: {
|
||||||
@ -126,9 +136,19 @@ export default defineConfig(() => {
|
|||||||
if (id.includes('node_modules/@googlemaps')) {
|
if (id.includes('node_modules/@googlemaps')) {
|
||||||
return 'vendor-maps'
|
return 'vendor-maps'
|
||||||
}
|
}
|
||||||
|
if (id.includes('node_modules/@supabase/supabase-js')) {
|
||||||
|
return 'vendor-supabase'
|
||||||
|
}
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
minify: 'terser',
|
||||||
|
terserOptions: {
|
||||||
|
compress: {
|
||||||
|
drop_console: true,
|
||||||
|
drop_debugger: true
|
||||||
|
}
|
||||||
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|||||||
Reference in New Issue
Block a user