pz5 wip
This commit is contained in:
@@ -9,6 +9,13 @@ spring:
|
||||
locator:
|
||||
enabled: true
|
||||
lower-case-service-id: true
|
||||
globalcors:
|
||||
cors-configurations:
|
||||
'[/**]':
|
||||
allowedOrigins: "*"
|
||||
allowedMethods:
|
||||
- '*'
|
||||
allowedHeaders: "*"
|
||||
application:
|
||||
name: api-gateway
|
||||
eureka:
|
||||
|
||||
@@ -9,7 +9,11 @@
|
||||
},
|
||||
"devDependencies": {
|
||||
"@sveltejs/vite-plugin-svelte": "^6.2.1",
|
||||
"@tailwindcss/vite": "^4.1.17",
|
||||
"flowbite": "^4.0.1",
|
||||
"svelte": "^5.43.8",
|
||||
"svelte5-router": "^3.0.2",
|
||||
"tailwindcss": "^4.1.17",
|
||||
"vite": "npm:rolldown-vite@7.2.5",
|
||||
},
|
||||
},
|
||||
@@ -18,6 +22,8 @@
|
||||
"vite": "npm:rolldown-vite@7.2.5",
|
||||
},
|
||||
"packages": {
|
||||
"@alloc/quick-lru": ["@alloc/quick-lru@5.2.0", "", {}, "sha512-UrcABB+4bUrFABwbluTIBErXwvbsU/V7TZWfmbgJfbkwiBuziS9gxdODUyuiecfdGQ85jglMW6juS3+z5TsKLw=="],
|
||||
|
||||
"@emnapi/core": ["@emnapi/core@1.7.1", "", { "dependencies": { "@emnapi/wasi-threads": "1.1.0", "tslib": "^2.4.0" } }, "sha512-o1uhUASyo921r2XtHYOHy7gdkGLge8ghBEQHMWmyJFoXlpU58kIrhhN3w26lpQb6dspetweapMn2CSNwQ8I4wg=="],
|
||||
|
||||
"@emnapi/runtime": ["@emnapi/runtime@1.7.1", "", { "dependencies": { "tslib": "^2.4.0" } }, "sha512-PVtJr5CmLwYAU9PZDMITZoR5iAOShYREoR45EyyLrbntV50mdePTgUn4AmOw90Ifcj+x2kRjdzr1HP3RrNiHGA=="],
|
||||
@@ -98,6 +104,38 @@
|
||||
|
||||
"@svgdotjs/svg.select.js": ["@svgdotjs/svg.select.js@4.0.3", "", { "peerDependencies": { "@svgdotjs/svg.js": "^3.2.4" } }, "sha512-qkMgso1sd2hXKd1FZ1weO7ANq12sNmQJeGDjs46QwDVsxSRcHmvWKL2NDF7Yimpwf3sl5esOLkPqtV2bQ3v/Jg=="],
|
||||
|
||||
"@tailwindcss/node": ["@tailwindcss/node@4.1.18", "", { "dependencies": { "@jridgewell/remapping": "^2.3.4", "enhanced-resolve": "^5.18.3", "jiti": "^2.6.1", "lightningcss": "1.30.2", "magic-string": "^0.30.21", "source-map-js": "^1.2.1", "tailwindcss": "4.1.18" } }, "sha512-DoR7U1P7iYhw16qJ49fgXUlry1t4CpXeErJHnQ44JgTSKMaZUdf17cfn5mHchfJ4KRBZRFA/Coo+MUF5+gOaCQ=="],
|
||||
|
||||
"@tailwindcss/oxide": ["@tailwindcss/oxide@4.1.18", "", { "optionalDependencies": { "@tailwindcss/oxide-android-arm64": "4.1.18", "@tailwindcss/oxide-darwin-arm64": "4.1.18", "@tailwindcss/oxide-darwin-x64": "4.1.18", "@tailwindcss/oxide-freebsd-x64": "4.1.18", "@tailwindcss/oxide-linux-arm-gnueabihf": "4.1.18", "@tailwindcss/oxide-linux-arm64-gnu": "4.1.18", "@tailwindcss/oxide-linux-arm64-musl": "4.1.18", "@tailwindcss/oxide-linux-x64-gnu": "4.1.18", "@tailwindcss/oxide-linux-x64-musl": "4.1.18", "@tailwindcss/oxide-wasm32-wasi": "4.1.18", "@tailwindcss/oxide-win32-arm64-msvc": "4.1.18", "@tailwindcss/oxide-win32-x64-msvc": "4.1.18" } }, "sha512-EgCR5tTS5bUSKQgzeMClT6iCY3ToqE1y+ZB0AKldj809QXk1Y+3jB0upOYZrn9aGIzPtUsP7sX4QQ4XtjBB95A=="],
|
||||
|
||||
"@tailwindcss/oxide-android-arm64": ["@tailwindcss/oxide-android-arm64@4.1.18", "", { "os": "android", "cpu": "arm64" }, "sha512-dJHz7+Ugr9U/diKJA0W6N/6/cjI+ZTAoxPf9Iz9BFRF2GzEX8IvXxFIi/dZBloVJX/MZGvRuFA9rqwdiIEZQ0Q=="],
|
||||
|
||||
"@tailwindcss/oxide-darwin-arm64": ["@tailwindcss/oxide-darwin-arm64@4.1.18", "", { "os": "darwin", "cpu": "arm64" }, "sha512-Gc2q4Qhs660bhjyBSKgq6BYvwDz4G+BuyJ5H1xfhmDR3D8HnHCmT/BSkvSL0vQLy/nkMLY20PQ2OoYMO15Jd0A=="],
|
||||
|
||||
"@tailwindcss/oxide-darwin-x64": ["@tailwindcss/oxide-darwin-x64@4.1.18", "", { "os": "darwin", "cpu": "x64" }, "sha512-FL5oxr2xQsFrc3X9o1fjHKBYBMD1QZNyc1Xzw/h5Qu4XnEBi3dZn96HcHm41c/euGV+GRiXFfh2hUCyKi/e+yw=="],
|
||||
|
||||
"@tailwindcss/oxide-freebsd-x64": ["@tailwindcss/oxide-freebsd-x64@4.1.18", "", { "os": "freebsd", "cpu": "x64" }, "sha512-Fj+RHgu5bDodmV1dM9yAxlfJwkkWvLiRjbhuO2LEtwtlYlBgiAT4x/j5wQr1tC3SANAgD+0YcmWVrj8R9trVMA=="],
|
||||
|
||||
"@tailwindcss/oxide-linux-arm-gnueabihf": ["@tailwindcss/oxide-linux-arm-gnueabihf@4.1.18", "", { "os": "linux", "cpu": "arm" }, "sha512-Fp+Wzk/Ws4dZn+LV2Nqx3IilnhH51YZoRaYHQsVq3RQvEl+71VGKFpkfHrLM/Li+kt5c0DJe/bHXK1eHgDmdiA=="],
|
||||
|
||||
"@tailwindcss/oxide-linux-arm64-gnu": ["@tailwindcss/oxide-linux-arm64-gnu@4.1.18", "", { "os": "linux", "cpu": "arm64" }, "sha512-S0n3jboLysNbh55Vrt7pk9wgpyTTPD0fdQeh7wQfMqLPM/Hrxi+dVsLsPrycQjGKEQk85Kgbx+6+QnYNiHalnw=="],
|
||||
|
||||
"@tailwindcss/oxide-linux-arm64-musl": ["@tailwindcss/oxide-linux-arm64-musl@4.1.18", "", { "os": "linux", "cpu": "arm64" }, "sha512-1px92582HkPQlaaCkdRcio71p8bc8i/ap5807tPRDK/uw953cauQBT8c5tVGkOwrHMfc2Yh6UuxaH4vtTjGvHg=="],
|
||||
|
||||
"@tailwindcss/oxide-linux-x64-gnu": ["@tailwindcss/oxide-linux-x64-gnu@4.1.18", "", { "os": "linux", "cpu": "x64" }, "sha512-v3gyT0ivkfBLoZGF9LyHmts0Isc8jHZyVcbzio6Wpzifg/+5ZJpDiRiUhDLkcr7f/r38SWNe7ucxmGW3j3Kb/g=="],
|
||||
|
||||
"@tailwindcss/oxide-linux-x64-musl": ["@tailwindcss/oxide-linux-x64-musl@4.1.18", "", { "os": "linux", "cpu": "x64" }, "sha512-bhJ2y2OQNlcRwwgOAGMY0xTFStt4/wyU6pvI6LSuZpRgKQwxTec0/3Scu91O8ir7qCR3AuepQKLU/kX99FouqQ=="],
|
||||
|
||||
"@tailwindcss/oxide-wasm32-wasi": ["@tailwindcss/oxide-wasm32-wasi@4.1.18", "", { "dependencies": { "@emnapi/core": "^1.7.1", "@emnapi/runtime": "^1.7.1", "@emnapi/wasi-threads": "^1.1.0", "@napi-rs/wasm-runtime": "^1.1.0", "@tybys/wasm-util": "^0.10.1", "tslib": "^2.4.0" }, "cpu": "none" }, "sha512-LffYTvPjODiP6PT16oNeUQJzNVyJl1cjIebq/rWWBF+3eDst5JGEFSc5cWxyRCJ0Mxl+KyIkqRxk1XPEs9x8TA=="],
|
||||
|
||||
"@tailwindcss/oxide-win32-arm64-msvc": ["@tailwindcss/oxide-win32-arm64-msvc@4.1.18", "", { "os": "win32", "cpu": "arm64" }, "sha512-HjSA7mr9HmC8fu6bdsZvZ+dhjyGCLdotjVOgLA2vEqxEBZaQo9YTX4kwgEvPCpRh8o4uWc4J/wEoFzhEmjvPbA=="],
|
||||
|
||||
"@tailwindcss/oxide-win32-x64-msvc": ["@tailwindcss/oxide-win32-x64-msvc@4.1.18", "", { "os": "win32", "cpu": "x64" }, "sha512-bJWbyYpUlqamC8dpR7pfjA0I7vdF6t5VpUGMWRkXVE3AXgIZjYUYAK7II1GNaxR8J1SSrSrppRar8G++JekE3Q=="],
|
||||
|
||||
"@tailwindcss/postcss": ["@tailwindcss/postcss@4.1.18", "", { "dependencies": { "@alloc/quick-lru": "^5.2.0", "@tailwindcss/node": "4.1.18", "@tailwindcss/oxide": "4.1.18", "postcss": "^8.4.41", "tailwindcss": "4.1.18" } }, "sha512-Ce0GFnzAOuPyfV5SxjXGn0CubwGcuDB0zcdaPuCSzAa/2vII24JTkH+I6jcbXLb1ctjZMZZI6OjDaLPJQL1S0g=="],
|
||||
|
||||
"@tailwindcss/vite": ["@tailwindcss/vite@4.1.18", "", { "dependencies": { "@tailwindcss/node": "4.1.18", "@tailwindcss/oxide": "4.1.18", "tailwindcss": "4.1.18" }, "peerDependencies": { "vite": "^5.2.0 || ^6 || ^7" } }, "sha512-jVA+/UpKL1vRLg6Hkao5jldawNmRo7mQYrZtNHMIVpLfLhDml5nMRUo/8MwoX2vNXvnaXNNMedrMfMugAVX1nA=="],
|
||||
|
||||
"@tybys/wasm-util": ["@tybys/wasm-util@0.10.1", "", { "dependencies": { "tslib": "^2.4.0" } }, "sha512-9tTaPJLSiejZKx+Bmog4uSubteqTvFrVrURwkmHixBo0G4seD0zUxp98E1DzUBJxLQ3NPwXrGKDiVjwx/DpPsg=="],
|
||||
|
||||
"@types/estree": ["@types/estree@1.0.8", "", {}, "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w=="],
|
||||
@@ -126,6 +164,8 @@
|
||||
|
||||
"devalue": ["devalue@5.6.1", "", {}, "sha512-jDwizj+IlEZBunHcOuuFVBnIMPAEHvTsJj0BcIp94xYguLRVBcXO853px/MyIJvbVzWdsGvrRweIUWJw8hBP7A=="],
|
||||
|
||||
"enhanced-resolve": ["enhanced-resolve@5.18.4", "", { "dependencies": { "graceful-fs": "^4.2.4", "tapable": "^2.2.0" } }, "sha512-LgQMM4WXU3QI+SYgEc2liRgznaD5ojbmY3sb8LxyguVkIg5FxdpTkvk72te2R38/TGKxH634oLxXRGY6d7AP+Q=="],
|
||||
|
||||
"esm-env": ["esm-env@1.2.2", "", {}, "sha512-Epxrv+Nr/CaL4ZcFGPJIYLWFom+YeV1DqMLHJoEd9SYRxNbaFruBwfEX/kkHUJf55j2+TUbmDcmuilbP1TmXHA=="],
|
||||
|
||||
"esrap": ["esrap@2.2.1", "", { "dependencies": { "@jridgewell/sourcemap-codec": "^1.4.15" } }, "sha512-GiYWG34AN/4CUyaWAgunGt0Rxvr1PTMlGC0vvEov/uOQYWne2bpN03Um+k8jT+q3op33mKouP2zeJ6OlM+qeUg=="],
|
||||
@@ -134,9 +174,9 @@
|
||||
|
||||
"fdir": ["fdir@6.5.0", "", { "peerDependencies": { "picomatch": "^3 || ^4" }, "optionalPeers": ["picomatch"] }, "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg=="],
|
||||
|
||||
"flowbite": ["flowbite@3.1.2", "", { "dependencies": { "@popperjs/core": "^2.9.3", "flowbite-datepicker": "^1.3.1", "mini-svg-data-uri": "^1.4.3", "postcss": "^8.5.1" } }, "sha512-MkwSgbbybCYgMC+go6Da5idEKUFfMqc/AmSjm/2ZbdmvoKf5frLPq/eIhXc9P+rC8t9boZtUXzHDgt5whZ6A/Q=="],
|
||||
"flowbite": ["flowbite@4.0.1", "", { "dependencies": { "@popperjs/core": "^2.9.3", "flowbite-datepicker": "^2.0.0", "mini-svg-data-uri": "^1.4.3", "postcss": "^8.5.1", "tailwindcss": "^4.1.12" } }, "sha512-UwUjvnqrQTiFm3uMJ0WWnzKXKoDyNyfyEzoNnxmZo6KyDzCedjqZw1UW0Oqdn+E0iYVdPu0fizydJN6e4pP9Rw=="],
|
||||
|
||||
"flowbite-datepicker": ["flowbite-datepicker@1.3.2", "", { "dependencies": { "@rollup/plugin-node-resolve": "^15.2.3", "flowbite": "^2.0.0" } }, "sha512-6Nfm0MCVX3mpaR7YSCjmEO2GO8CDt6CX8ZpQnGdeu03WUCWtEPQ/uy0PUiNtIJjJZWnX0Cm3H55MOhbD1g+E/g=="],
|
||||
"flowbite-datepicker": ["flowbite-datepicker@2.0.0", "", { "dependencies": { "@rollup/plugin-node-resolve": "^15.2.3", "@tailwindcss/postcss": "^4.1.17" } }, "sha512-m81hl0Bimq45MUg4maJLOnXrX+C9lZ0AkjMb9uotuVUSr729k/YiymWDfVAm63AYDH7g7y3rI3ke3XaBzWWqLw=="],
|
||||
|
||||
"flowbite-svelte": ["flowbite-svelte@1.31.0", "", { "dependencies": { "@floating-ui/dom": "^1.7.4", "@floating-ui/utils": "^0.2.10", "apexcharts": "^5.3.6", "clsx": "^2.1.1", "date-fns": "^4.1.0", "esm-env": "^1.2.2", "flowbite": "^3.1.2", "tailwind-merge": "^3.4.0", "tailwind-variants": "^3.2.2" }, "peerDependencies": { "svelte": "^5.40.0", "tailwindcss": "^4.1.4" } }, "sha512-A7Ts/R5GsL8DbgRf+8+1wdrIOOK0nq4ggEkv4RuY0oGuzH1PLBAH+bvC1L8AgQ5li9mj3o8eE9tHW7Md8yjPsw=="],
|
||||
|
||||
@@ -144,6 +184,8 @@
|
||||
|
||||
"function-bind": ["function-bind@1.1.2", "", {}, "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA=="],
|
||||
|
||||
"graceful-fs": ["graceful-fs@4.2.11", "", {}, "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ=="],
|
||||
|
||||
"hasown": ["hasown@2.0.2", "", { "dependencies": { "function-bind": "^1.1.2" } }, "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ=="],
|
||||
|
||||
"is-core-module": ["is-core-module@2.16.1", "", { "dependencies": { "hasown": "^2.0.2" } }, "sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w=="],
|
||||
@@ -152,6 +194,8 @@
|
||||
|
||||
"is-reference": ["is-reference@3.0.3", "", { "dependencies": { "@types/estree": "^1.0.6" } }, "sha512-ixkJoqQvAP88E6wLydLGGqCJsrFUnqoH6HnaczB8XmDH1oaWU+xxdptvikTgaEhtZ53Ky6YXiBuUI2WXLMCwjw=="],
|
||||
|
||||
"jiti": ["jiti@2.6.1", "", { "bin": { "jiti": "lib/jiti-cli.mjs" } }, "sha512-ekilCSN1jwRvIbgeg/57YFh8qQDNbwDb9xT/qu2DAHbFFZUicIl4ygVaAvzveMhMVr3LnpSKTNnwt8PoOfmKhQ=="],
|
||||
|
||||
"lightningcss": ["lightningcss@1.30.2", "", { "dependencies": { "detect-libc": "^2.0.3" }, "optionalDependencies": { "lightningcss-android-arm64": "1.30.2", "lightningcss-darwin-arm64": "1.30.2", "lightningcss-darwin-x64": "1.30.2", "lightningcss-freebsd-x64": "1.30.2", "lightningcss-linux-arm-gnueabihf": "1.30.2", "lightningcss-linux-arm64-gnu": "1.30.2", "lightningcss-linux-arm64-musl": "1.30.2", "lightningcss-linux-x64-gnu": "1.30.2", "lightningcss-linux-x64-musl": "1.30.2", "lightningcss-win32-arm64-msvc": "1.30.2", "lightningcss-win32-x64-msvc": "1.30.2" } }, "sha512-utfs7Pr5uJyyvDETitgsaqSyjCb2qNRAtuqUeWIAKztsOYdcACf2KtARYXg2pSvhkt+9NfoaNY7fxjl6nuMjIQ=="],
|
||||
|
||||
"lightningcss-android-arm64": ["lightningcss-android-arm64@1.30.2", "", { "os": "android", "cpu": "arm64" }, "sha512-BH9sEdOCahSgmkVhBLeU7Hc9DWeZ1Eb6wNS6Da8igvUwAe0sqROHddIlvU06q3WyXVEOYDZ6ykBZQnjTbmo4+A=="],
|
||||
@@ -208,12 +252,16 @@
|
||||
|
||||
"svelte": ["svelte@5.46.0", "", { "dependencies": { "@jridgewell/remapping": "^2.3.4", "@jridgewell/sourcemap-codec": "^1.5.0", "@sveltejs/acorn-typescript": "^1.0.5", "@types/estree": "^1.0.5", "acorn": "^8.12.1", "aria-query": "^5.3.1", "axobject-query": "^4.1.0", "clsx": "^2.1.1", "devalue": "^5.5.0", "esm-env": "^1.2.1", "esrap": "^2.2.1", "is-reference": "^3.0.3", "locate-character": "^3.0.0", "magic-string": "^0.30.11", "zimmerframe": "^1.1.2" } }, "sha512-ZhLtvroYxUxr+HQJfMZEDRsGsmU46x12RvAv/zi9584f5KOX7bUrEbhPJ7cKFmUvZTJXi/CFZUYwDC6M1FigPw=="],
|
||||
|
||||
"svelte5-router": ["svelte5-router@3.0.2", "", { "peerDependencies": { "svelte": "^5.0.0" } }, "sha512-3XE97eEVrV2OY/7iuhrBIvD48KWmD9M8Cf2mZqjoxnI9bR5ZiwtAg9ObGP3kP7Z/kyVrn0n8riNBJdHeddctlQ=="],
|
||||
|
||||
"tailwind-merge": ["tailwind-merge@3.4.0", "", {}, "sha512-uSaO4gnW+b3Y2aWoWfFpX62vn2sR3skfhbjsEnaBI81WD1wBLlHZe5sWf0AqjksNdYTbGBEd0UasQMT3SNV15g=="],
|
||||
|
||||
"tailwind-variants": ["tailwind-variants@3.2.2", "", { "peerDependencies": { "tailwind-merge": ">=3.0.0", "tailwindcss": "*" }, "optionalPeers": ["tailwind-merge"] }, "sha512-Mi4kHeMTLvKlM98XPnK+7HoBPmf4gygdFmqQPaDivc3DpYS6aIY6KiG/PgThrGvii5YZJqRsPz0aPyhoFzmZgg=="],
|
||||
|
||||
"tailwindcss": ["tailwindcss@4.1.18", "", {}, "sha512-4+Z+0yiYyEtUVCScyfHCxOYP06L5Ne+JiHhY2IjR2KWMIWhJOYZKLSGZaP5HkZ8+bY0cxfzwDE5uOmzFXyIwxw=="],
|
||||
|
||||
"tapable": ["tapable@2.3.0", "", {}, "sha512-g9ljZiwki/LfxmQADO3dEY1CbpmXT5Hm2fJ+QaGKwSXUylMybePR7/67YW7jOrrvjEgL1Fmz5kzyAjWVWLlucg=="],
|
||||
|
||||
"tinyglobby": ["tinyglobby@0.2.15", "", { "dependencies": { "fdir": "^6.5.0", "picomatch": "^4.0.3" } }, "sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ=="],
|
||||
|
||||
"tslib": ["tslib@2.8.1", "", {}, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="],
|
||||
@@ -224,6 +272,22 @@
|
||||
|
||||
"zimmerframe": ["zimmerframe@1.1.4", "", {}, "sha512-B58NGBEoc8Y9MWWCQGl/gq9xBCe4IiKM0a2x7GZdQKOW5Exr8S1W24J6OgM1njK8xCRGvAJIL/MxXHf6SkmQKQ=="],
|
||||
|
||||
"flowbite-datepicker/flowbite": ["flowbite@2.5.2", "", { "dependencies": { "@popperjs/core": "^2.9.3", "flowbite-datepicker": "^1.3.0", "mini-svg-data-uri": "^1.4.3" } }, "sha512-kwFD3n8/YW4EG8GlY3Od9IoKND97kitO+/ejISHSqpn3vw2i5K/+ZI8Jm2V+KC4fGdnfi0XZ+TzYqQb4Q1LshA=="],
|
||||
"@tailwindcss/oxide-wasm32-wasi/@emnapi/core": ["@emnapi/core@1.7.1", "", { "dependencies": { "@emnapi/wasi-threads": "1.1.0", "tslib": "^2.4.0" }, "bundled": true }, "sha512-o1uhUASyo921r2XtHYOHy7gdkGLge8ghBEQHMWmyJFoXlpU58kIrhhN3w26lpQb6dspetweapMn2CSNwQ8I4wg=="],
|
||||
|
||||
"@tailwindcss/oxide-wasm32-wasi/@emnapi/runtime": ["@emnapi/runtime@1.7.1", "", { "dependencies": { "tslib": "^2.4.0" }, "bundled": true }, "sha512-PVtJr5CmLwYAU9PZDMITZoR5iAOShYREoR45EyyLrbntV50mdePTgUn4AmOw90Ifcj+x2kRjdzr1HP3RrNiHGA=="],
|
||||
|
||||
"@tailwindcss/oxide-wasm32-wasi/@emnapi/wasi-threads": ["@emnapi/wasi-threads@1.1.0", "", { "dependencies": { "tslib": "^2.4.0" }, "bundled": true }, "sha512-WI0DdZ8xFSbgMjR1sFsKABJ/C5OnRrjT06JXbZKexJGrDuPTzZdDYfFlsgcCXCyf+suG5QU2e/y1Wo2V/OapLQ=="],
|
||||
|
||||
"@tailwindcss/oxide-wasm32-wasi/@napi-rs/wasm-runtime": ["@napi-rs/wasm-runtime@1.1.0", "", { "dependencies": { "@emnapi/core": "^1.7.1", "@emnapi/runtime": "^1.7.1", "@tybys/wasm-util": "^0.10.1" }, "bundled": true }, "sha512-Fq6DJW+Bb5jaWE69/qOE0D1TUN9+6uWhCeZpdnSBk14pjLcCWR7Q8n49PTSPHazM37JqrsdpEthXy2xn6jWWiA=="],
|
||||
|
||||
"@tailwindcss/oxide-wasm32-wasi/@tybys/wasm-util": ["@tybys/wasm-util@0.10.1", "", { "dependencies": { "tslib": "^2.4.0" }, "bundled": true }, "sha512-9tTaPJLSiejZKx+Bmog4uSubteqTvFrVrURwkmHixBo0G4seD0zUxp98E1DzUBJxLQ3NPwXrGKDiVjwx/DpPsg=="],
|
||||
|
||||
"@tailwindcss/oxide-wasm32-wasi/tslib": ["tslib@2.8.1", "", { "bundled": true }, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="],
|
||||
|
||||
"flowbite-svelte/flowbite": ["flowbite@3.1.2", "", { "dependencies": { "@popperjs/core": "^2.9.3", "flowbite-datepicker": "^1.3.1", "mini-svg-data-uri": "^1.4.3", "postcss": "^8.5.1" } }, "sha512-MkwSgbbybCYgMC+go6Da5idEKUFfMqc/AmSjm/2ZbdmvoKf5frLPq/eIhXc9P+rC8t9boZtUXzHDgt5whZ6A/Q=="],
|
||||
|
||||
"flowbite-svelte/flowbite/flowbite-datepicker": ["flowbite-datepicker@1.3.2", "", { "dependencies": { "@rollup/plugin-node-resolve": "^15.2.3", "flowbite": "^2.0.0" } }, "sha512-6Nfm0MCVX3mpaR7YSCjmEO2GO8CDt6CX8ZpQnGdeu03WUCWtEPQ/uy0PUiNtIJjJZWnX0Cm3H55MOhbD1g+E/g=="],
|
||||
|
||||
"flowbite-svelte/flowbite/flowbite-datepicker/flowbite": ["flowbite@2.5.2", "", { "dependencies": { "@popperjs/core": "^2.9.3", "flowbite-datepicker": "^1.3.0", "mini-svg-data-uri": "^1.4.3" } }, "sha512-kwFD3n8/YW4EG8GlY3Od9IoKND97kitO+/ejISHSqpn3vw2i5K/+ZI8Jm2V+KC4fGdnfi0XZ+TzYqQb4Q1LshA=="],
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,10 +1,16 @@
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<html lang="en" class="dark">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>ITROI client application</title>
|
||||
<style>
|
||||
html.dark {
|
||||
background-color: #111827;
|
||||
color: white;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div id="app"></div>
|
||||
|
||||
@@ -10,7 +10,11 @@
|
||||
},
|
||||
"devDependencies": {
|
||||
"@sveltejs/vite-plugin-svelte": "^6.2.1",
|
||||
"@tailwindcss/vite": "^4.1.17",
|
||||
"flowbite": "^4.0.1",
|
||||
"svelte": "^5.43.8",
|
||||
"svelte5-router": "^3.0.2",
|
||||
"tailwindcss": "^4.1.17",
|
||||
"vite": "npm:rolldown-vite@7.2.5"
|
||||
},
|
||||
"overrides": {
|
||||
|
||||
@@ -1,7 +1,29 @@
|
||||
<script>
|
||||
import "./app.css";
|
||||
import Header from "./Header.svelte";
|
||||
import Vehicles from "./Vehicles.svelte";
|
||||
import Freights from "./Freights.svelte";
|
||||
import Routes from "./Routes.svelte";
|
||||
import { Route, Router } from "svelte5-router";
|
||||
import { fly } from "svelte/transition";
|
||||
</script>
|
||||
|
||||
<main>
|
||||
<Router>
|
||||
<Header />
|
||||
</main>
|
||||
|
||||
<Router
|
||||
viewtransition={() => {
|
||||
return {
|
||||
fn: fly,
|
||||
duration: 1000,
|
||||
y: 200,
|
||||
};
|
||||
}}
|
||||
>
|
||||
<main class="pt-20">
|
||||
<Route path="/vehicles"><Vehicles /></Route>
|
||||
<Route path="/freights"><Freights /></Route>
|
||||
<Route path="/routes"><Routes /></Route>
|
||||
</main>
|
||||
</Router>
|
||||
</Router>
|
||||
|
||||
196
client/src/Freights.svelte
Normal file
196
client/src/Freights.svelte
Normal file
@@ -0,0 +1,196 @@
|
||||
<script>
|
||||
import { onMount } from "svelte";
|
||||
import {
|
||||
Button,
|
||||
Table,
|
||||
TableBody,
|
||||
TableBodyCell,
|
||||
TableBodyRow,
|
||||
TableHead,
|
||||
TableHeadCell,
|
||||
Modal,
|
||||
Label,
|
||||
Input,
|
||||
Select,
|
||||
Badge,
|
||||
Heading,
|
||||
Helper,
|
||||
} from "flowbite-svelte";
|
||||
|
||||
let freights = $state([]);
|
||||
let isModalOpen = $state(false);
|
||||
let editingId = $state(null);
|
||||
|
||||
let formData = $state({
|
||||
name: "",
|
||||
description: "",
|
||||
weight_kg: 0,
|
||||
dimensions: { length_cm: 0, width_cm: 0, height_cm: 0 },
|
||||
status: "PENDING",
|
||||
});
|
||||
|
||||
const BASE_URL = "http://localhost:8079/freight-service/freights";
|
||||
|
||||
async function fetchFreights() {
|
||||
try {
|
||||
const res = await fetch(BASE_URL);
|
||||
freights = await res.json();
|
||||
} catch (err) {
|
||||
console.error("Fetch error:", err);
|
||||
}
|
||||
}
|
||||
|
||||
async function saveFreight() {
|
||||
const method = editingId ? "PUT" : "POST";
|
||||
const url = editingId ? `${BASE_URL}/${editingId}` : BASE_URL;
|
||||
|
||||
try {
|
||||
const res = await fetch(url, {
|
||||
method,
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify(formData),
|
||||
});
|
||||
if (res.ok) {
|
||||
await fetchFreights();
|
||||
closeModal();
|
||||
}
|
||||
} catch (err) {
|
||||
console.error("Save error:", err);
|
||||
}
|
||||
}
|
||||
|
||||
async function deleteFreight(id) {
|
||||
await fetch(`${BASE_URL}/${id}`, { method: "DELETE" });
|
||||
fetchFreights();
|
||||
}
|
||||
|
||||
function openModal(freight = null) {
|
||||
if (freight) {
|
||||
editingId = freight.id;
|
||||
formData = JSON.parse(JSON.stringify(freight)); // deep clone
|
||||
} else {
|
||||
editingId = null;
|
||||
formData = {
|
||||
name: "",
|
||||
description: "",
|
||||
weight_kg: 0,
|
||||
dimensions: { length_cm: 0, width_cm: 0, height_cm: 0 },
|
||||
status: "PENDING",
|
||||
};
|
||||
}
|
||||
isModalOpen = true;
|
||||
}
|
||||
|
||||
const closeModal = () => (isModalOpen = false);
|
||||
|
||||
const statusMap = {
|
||||
PENDING: { color: "yellow", label: "Pending" },
|
||||
IN_TRANSIT: { color: "indigo", label: "In Transit" },
|
||||
DELIVERED: { color: "green", label: "Delivered" },
|
||||
};
|
||||
|
||||
onMount(fetchFreights);
|
||||
</script>
|
||||
|
||||
<div class="p-8 space-y-4">
|
||||
<div class="flex justify-between items-center">
|
||||
<Heading tag="h2" class="text-2xl font-bold">Freights</Heading>
|
||||
<Button color="blue" onclick={() => openModal()}>+ Add Freight</Button>
|
||||
</div>
|
||||
|
||||
<Table shadow hoverable={true}>
|
||||
<TableHead>
|
||||
<TableHeadCell>ID</TableHeadCell>
|
||||
<TableHeadCell>Cargo Name</TableHeadCell>
|
||||
<TableHeadCell>Weight (kg)</TableHeadCell>
|
||||
<TableHeadCell>Dimensions (L/W/H)</TableHeadCell>
|
||||
<TableHeadCell>Status</TableHeadCell>
|
||||
<TableHeadCell>Actions</TableHeadCell>
|
||||
</TableHead>
|
||||
<TableBody>
|
||||
{#each freights as item (item.id)}
|
||||
<TableBodyRow>
|
||||
<TableBodyCell>{item.id}</TableBodyCell>
|
||||
<TableBodyCell>
|
||||
<div class="font-medium">{item.name}</div>
|
||||
<div class="text-xs text-gray-500">{item.description}</div>
|
||||
</TableBodyCell>
|
||||
<TableBodyCell>{item.weight_kg} kg</TableBodyCell>
|
||||
<TableBodyCell>
|
||||
{item.dimensions.length_cm}×{item.dimensions.width_cm}×{item
|
||||
.dimensions.height_cm} cm
|
||||
</TableBodyCell>
|
||||
<TableBodyCell>
|
||||
<Badge color={statusMap[item.status].color}
|
||||
>{statusMap[item.status].label}</Badge
|
||||
>
|
||||
</TableBodyCell>
|
||||
<TableBodyCell class="space-x-2">
|
||||
<Button
|
||||
size="xs"
|
||||
color="alternative"
|
||||
onclick={() => openModal(item)}>Edit</Button
|
||||
>
|
||||
<Button size="xs" color="red" onclick={() => deleteFreight(item.id)}
|
||||
>Delete</Button
|
||||
>
|
||||
</TableBodyCell>
|
||||
</TableBodyRow>
|
||||
{/each}
|
||||
</TableBody>
|
||||
</Table>
|
||||
</div>
|
||||
|
||||
<Modal
|
||||
title={editingId ? "Update Freight" : "Add Freight"}
|
||||
bind:open={isModalOpen}
|
||||
size="md"
|
||||
>
|
||||
<div class="grid grid-cols-2 gap-4">
|
||||
<div class="col-span-2">
|
||||
<Label class="mb-2">Item Name</Label>
|
||||
<Input bind:value={formData.name} placeholder="e.g. Electronics" />
|
||||
</div>
|
||||
<div class="col-span-2">
|
||||
<Label class="mb-2">Description</Label>
|
||||
<Input bind:value={formData.description} placeholder="Cargo details..." />
|
||||
</div>
|
||||
<div>
|
||||
<Label class="mb-2">Weight (kg)</Label>
|
||||
<Input type="number" bind:value={formData.weight_kg} />
|
||||
</div>
|
||||
<div>
|
||||
<Label class="mb-2">Status</Label>
|
||||
<Select
|
||||
items={Object.keys(statusMap).map((k) => ({
|
||||
value: k,
|
||||
name: statusMap[k].label,
|
||||
}))}
|
||||
bind:value={formData.status}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="col-span-2 border-t pt-4 mt-2">
|
||||
<Label class="mb-2 font-bold">Dimensions (cm)</Label>
|
||||
<div class="grid grid-cols-3 gap-2">
|
||||
<div>
|
||||
<Helper>Length (cm.)</Helper>
|
||||
<Input type="number" bind:value={formData.dimensions.length_cm} />
|
||||
</div>
|
||||
<div>
|
||||
<Helper>Width (cm.)</Helper>
|
||||
<Input type="number" bind:value={formData.dimensions.width_cm} />
|
||||
</div>
|
||||
<div>
|
||||
<Helper>Height (cm.)</Helper>
|
||||
<Input type="number" bind:value={formData.dimensions.height_cm} />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="flex justify-end space-x-3 mt-6">
|
||||
<Button color="alternative" onclick={closeModal}>Cancel</Button>
|
||||
<Button color="blue" onclick={saveFreight}>Confirm</Button>
|
||||
</div>
|
||||
</Modal>
|
||||
@@ -1,8 +1,9 @@
|
||||
<script>
|
||||
import { Navbar, NavBrand, NavUl, NavLi } from "flowbite-svelte";
|
||||
import { Link } from "svelte5-router";
|
||||
</script>
|
||||
|
||||
<header>
|
||||
<header class="fixed top-0 left-0 right-0 z-50">
|
||||
<Navbar>
|
||||
<NavBrand href="/">
|
||||
<span
|
||||
@@ -11,9 +12,9 @@
|
||||
>
|
||||
</NavBrand>
|
||||
<NavUl>
|
||||
<NavLi href="/freights">Freights</NavLi>
|
||||
<NavLi href="/vehicles">Vehicles</NavLi>
|
||||
<NavLi href="/routes">Routes</NavLi>
|
||||
<NavLi><Link to="/freights">Freights</Link></NavLi>
|
||||
<NavLi><Link to="/vehicles">Vehicles</Link></NavLi>
|
||||
<NavLi><Link to="/routes">Routes</Link></NavLi>
|
||||
</NavUl>
|
||||
</Navbar>
|
||||
</header>
|
||||
|
||||
292
client/src/Routes.svelte
Normal file
292
client/src/Routes.svelte
Normal file
@@ -0,0 +1,292 @@
|
||||
<script>
|
||||
import { onMount } from "svelte";
|
||||
import {
|
||||
Button,
|
||||
Table,
|
||||
TableBody,
|
||||
TableBodyCell,
|
||||
TableBodyRow,
|
||||
TableHead,
|
||||
TableHeadCell,
|
||||
Modal,
|
||||
Label,
|
||||
Input,
|
||||
Select,
|
||||
Badge,
|
||||
Heading,
|
||||
MultiSelect,
|
||||
Helper,
|
||||
} from "flowbite-svelte";
|
||||
|
||||
let routes = $state([]);
|
||||
let vehicles = $state([]); // For dropdown
|
||||
let freights = $state([]); // For dropdown
|
||||
let isModalOpen = $state(false);
|
||||
let editingId = $state(null);
|
||||
|
||||
let formData = $state({
|
||||
vehicle_id: "",
|
||||
freight_id: [],
|
||||
start_location: "",
|
||||
end_location: "",
|
||||
distance_km: 0,
|
||||
estimated_duration_hours: 0,
|
||||
status: "PLANNED",
|
||||
});
|
||||
|
||||
const BASE_URL = "http://localhost:8079/route-service/routes";
|
||||
|
||||
async function fetchData() {
|
||||
try {
|
||||
const [routeRes, vehRes, frRes] = await Promise.all([
|
||||
fetch(BASE_URL),
|
||||
fetch("http://localhost:8079/vehicle-service/vehicles"),
|
||||
fetch("http://localhost:8079/freight-service/freights"),
|
||||
]);
|
||||
|
||||
routes = await routeRes.json();
|
||||
|
||||
vehicles = (await vehRes.json()).map((v) => ({
|
||||
value: v.id,
|
||||
name: `${v.brand} ${v.model} (${v.license_plate})`,
|
||||
}));
|
||||
freights = (await frRes.json()).map((f) => ({
|
||||
value: f.id,
|
||||
name: f.name,
|
||||
}));
|
||||
} catch (err) {
|
||||
console.error("Load error:", err);
|
||||
}
|
||||
}
|
||||
|
||||
let errors = $derived.by(() => {
|
||||
const e = {};
|
||||
if (!formData.vehicle_id) e.vehicle_id = "Vehicle is required";
|
||||
if (formData.freight_id.length === 0)
|
||||
e.freight_id = "Select at least one freight";
|
||||
if (!formData.start_location.trim()) e.start_location = "Origin required";
|
||||
if (!formData.end_location.trim()) e.end_location = "Destination required";
|
||||
if (formData.distance_km <= 0) e.distance_km = "Distance must be > 0";
|
||||
if (formData.estimated_duration_hours <= 0)
|
||||
e.duration = "Duration must be > 0";
|
||||
return e;
|
||||
});
|
||||
|
||||
async function saveRoute() {
|
||||
const method = editingId ? "PUT" : "POST";
|
||||
const url = editingId ? `${BASE_URL}/${editingId}` : BASE_URL;
|
||||
|
||||
const res = await fetch(url, {
|
||||
method,
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify(formData),
|
||||
});
|
||||
|
||||
if (res.ok) {
|
||||
await fetchData();
|
||||
closeModal();
|
||||
}
|
||||
}
|
||||
|
||||
async function deleteRoute(id) {
|
||||
await fetch(`${BASE_URL}/${id}`, { method: "DELETE" });
|
||||
fetchData();
|
||||
}
|
||||
|
||||
function openModal(route = null) {
|
||||
if (route) {
|
||||
editingId = route.id;
|
||||
formData = { ...route };
|
||||
} else {
|
||||
editingId = null;
|
||||
formData = {
|
||||
vehicle_id: "",
|
||||
freight_id: [],
|
||||
start_location: "",
|
||||
end_location: "",
|
||||
distance_km: 0,
|
||||
estimated_duration_hours: 0,
|
||||
status: "PLANNED",
|
||||
};
|
||||
}
|
||||
isModalOpen = true;
|
||||
}
|
||||
|
||||
const closeModal = () => (isModalOpen = false);
|
||||
|
||||
const statusMap = {
|
||||
PLANNED: { color: "blue", label: "Planned" },
|
||||
IN_PROGRESS: { color: "yellow", label: "In Progress" },
|
||||
COMPLETED: { color: "green", label: "Completed" },
|
||||
};
|
||||
|
||||
onMount(fetchData);
|
||||
</script>
|
||||
|
||||
<div class="p-8 space-y-4">
|
||||
<div class="flex justify-between items-center">
|
||||
<Heading tag="h2" class="text-2xl font-bold text-gray-800">Routes</Heading>
|
||||
<Button color="blue" onclick={() => openModal()}>+ Add Route</Button>
|
||||
</div>
|
||||
|
||||
<Table shadow hoverable>
|
||||
<TableHead>
|
||||
<TableHeadCell>ID</TableHeadCell>
|
||||
<TableHeadCell>Path</TableHeadCell>
|
||||
<TableHeadCell>Vehicle ID</TableHeadCell>
|
||||
<TableHeadCell>Metrics</TableHeadCell>
|
||||
<TableHeadCell>Status</TableHeadCell>
|
||||
<TableHeadCell>Actions</TableHeadCell>
|
||||
</TableHead>
|
||||
<TableBody>
|
||||
{#each routes as route (route.id)}
|
||||
<TableBodyRow>
|
||||
<TableBodyCell>{route.id}</TableBodyCell>
|
||||
<TableBodyCell>
|
||||
<span class="font-bold text-blue-600">{route.start_location}</span>
|
||||
→
|
||||
<span class="font-bold text-red-600">{route.end_location}</span>
|
||||
</TableBodyCell>
|
||||
<TableBodyCell>Vehicle #{route.vehicle_id}</TableBodyCell>
|
||||
<TableBodyCell>
|
||||
<div class="text-xs">{route.distance_km} km</div>
|
||||
<div class="text-xs text-gray-500">
|
||||
{route.estimated_duration_hours} hrs
|
||||
</div>
|
||||
</TableBodyCell>
|
||||
<TableBodyCell>
|
||||
<Badge color={statusMap[route.status].color}
|
||||
>{statusMap[route.status].label}</Badge
|
||||
>
|
||||
</TableBodyCell>
|
||||
<TableBodyCell class="space-x-2">
|
||||
<Button
|
||||
size="xs"
|
||||
color="alternative"
|
||||
onclick={() => openModal(route)}>Edit</Button
|
||||
>
|
||||
<Button size="xs" color="red" onclick={() => deleteRoute(route.id)}
|
||||
>Delete</Button
|
||||
>
|
||||
</TableBodyCell>
|
||||
</TableBodyRow>
|
||||
{/each}
|
||||
</TableBody>
|
||||
</Table>
|
||||
</div>
|
||||
|
||||
<Modal
|
||||
title={editingId ? "Update Route" : "Add Route"}
|
||||
bind:open={isModalOpen}
|
||||
size="md"
|
||||
>
|
||||
<div class="grid grid-cols-2 gap-4">
|
||||
<div>
|
||||
<Label class="mb-2">Assign Vehicle</Label>
|
||||
<Select
|
||||
items={vehicles}
|
||||
bind:value={formData.vehicleId}
|
||||
placeholder="Select vehicle..."
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<Label class="mb-2">Status</Label>
|
||||
<Select
|
||||
items={Object.keys(statusMap).map((k) => ({
|
||||
value: k,
|
||||
name: statusMap[k].label,
|
||||
}))}
|
||||
bind:value={formData.status}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="col-span-2">
|
||||
<Label class="mb-2">Freights (Multi-select)</Label>
|
||||
<MultiSelect items={freights} bind:value={formData.freight_id} />
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<Label class="mb-2">Start Location</Label>
|
||||
<Input bind:value={formData.start_location} placeholder="City A" />
|
||||
</div>
|
||||
<div>
|
||||
<Label class="mb-2">End Location</Label>
|
||||
<Input bind:value={formData.end_location} placeholder="City B" />
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<Label class="mb-2">Distance (km)</Label>
|
||||
<Input type="number" bind:value={formData.distance_km} />
|
||||
</div>
|
||||
<div>
|
||||
<Label class="mb-2">Est. Time (hours)</Label>
|
||||
<Input
|
||||
type="number"
|
||||
step="0.5"
|
||||
bind:value={formData.estimated_duration_hours}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="grid grid-cols-2 gap-4">
|
||||
<div>
|
||||
<Label color={errors.vehicle_id ? "red" : "base"} class="mb-2"
|
||||
>Assign Vehicle</Label
|
||||
>
|
||||
<Select
|
||||
items={vehicles}
|
||||
bind:value={formData.vehicle_id}
|
||||
color={errors.vehicle_id ? "red" : "base"}
|
||||
/>
|
||||
{#if errors.vehicle_id}<Helper class="mt-2" color="red"
|
||||
>{errors.vehicle_id}</Helper
|
||||
>{/if}
|
||||
</div>
|
||||
|
||||
<div class="col-span-2">
|
||||
<Label color={errors.freight_id ? "red" : "base"} class="mb-2"
|
||||
>Freights</Label
|
||||
>
|
||||
<MultiSelect items={freights} bind:value={formData.freight_id} />
|
||||
{#if errors.freight_id}<Helper class="mt-2" color="red"
|
||||
>{errors.freight_id}</Helper
|
||||
>{/if}
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<Label color={errors.start_location ? "red" : "base"} class="mb-2"
|
||||
>Start Location</Label
|
||||
>
|
||||
<Input
|
||||
bind:value={formData.start_location}
|
||||
color={errors.start_location ? "red" : "base"}
|
||||
/>
|
||||
{#if errors.start_location}<Helper class="mt-2" color="red"
|
||||
>{errors.start_location}</Helper
|
||||
>{/if}
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<Label color={errors.distance_km ? "red" : "base"} class="mb-2"
|
||||
>Distance (km)</Label
|
||||
>
|
||||
<Input
|
||||
type="number"
|
||||
bind:value={formData.distance_km}
|
||||
color={errors.distance_km ? "red" : "base"}
|
||||
/>
|
||||
{#if errors.distance_km}<Helper class="mt-2" color="red"
|
||||
>{errors.distance_km}</Helper
|
||||
>{/if}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="flex justify-end space-x-3 mt-6">
|
||||
<Button color="alternative" onclick={closeModal}>Cancel</Button>
|
||||
<Button
|
||||
color="blue"
|
||||
disabled={!(Object.keys(errors).length === 0)}
|
||||
onclick={saveRoute}>Save Route</Button
|
||||
>
|
||||
</div>
|
||||
</Modal>
|
||||
224
client/src/Vehicles.svelte
Normal file
224
client/src/Vehicles.svelte
Normal file
@@ -0,0 +1,224 @@
|
||||
<script>
|
||||
import { onMount } from "svelte";
|
||||
import {
|
||||
Button,
|
||||
Table,
|
||||
TableBody,
|
||||
TableBodyCell,
|
||||
TableBodyRow,
|
||||
TableHead,
|
||||
TableHeadCell,
|
||||
Modal,
|
||||
Label,
|
||||
Input,
|
||||
Select,
|
||||
Badge,
|
||||
Heading,
|
||||
} from "flowbite-svelte";
|
||||
|
||||
let vehicles = $state([]);
|
||||
let isModalOpen = $state(false);
|
||||
let editingId = $state(null);
|
||||
|
||||
let formData = $state({
|
||||
brand: "",
|
||||
model: "",
|
||||
license_plate: "",
|
||||
year: 2024,
|
||||
capacity_kg: 0,
|
||||
status: "AVAILABLE",
|
||||
});
|
||||
|
||||
const BASE_URL = "http://localhost:8079/vehicle-service/vehicles";
|
||||
|
||||
const statusOptions = [
|
||||
{ value: "AVAILABLE", name: "Available" },
|
||||
{ value: "MAINTENANCE", name: "Maintenance" },
|
||||
{ value: "IN_TRANSIT", name: "In Transit" },
|
||||
];
|
||||
|
||||
async function fetchVehicles() {
|
||||
try {
|
||||
const res = await fetch(BASE_URL);
|
||||
vehicles = await res.json();
|
||||
} catch (err) {
|
||||
console.error("Error fetching vehicles:", err);
|
||||
}
|
||||
}
|
||||
|
||||
async function saveVehicle() {
|
||||
const method = editingId ? "PUT" : "POST";
|
||||
const url = editingId ? `${BASE_URL}/${editingId}` : BASE_URL;
|
||||
|
||||
try {
|
||||
const res = await fetch(url, {
|
||||
method,
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify(formData),
|
||||
});
|
||||
|
||||
if (res.ok) {
|
||||
await fetchVehicles();
|
||||
closeModal();
|
||||
}
|
||||
} catch (err) {
|
||||
console.error("Error saving vehicle:", err);
|
||||
}
|
||||
}
|
||||
|
||||
async function deleteVehicle(id) {
|
||||
try {
|
||||
const res = await fetch(`${BASE_URL}/${id}`, { method: "DELETE" });
|
||||
if (res.ok) fetchVehicles();
|
||||
} catch (err) {
|
||||
console.error("Error deleting vehicle:", err);
|
||||
}
|
||||
}
|
||||
|
||||
function openModal(vehicle = null) {
|
||||
if (vehicle) {
|
||||
editingId = vehicle.id;
|
||||
formData = { ...vehicle };
|
||||
} else {
|
||||
editingId = null;
|
||||
formData = {
|
||||
brand: "",
|
||||
model: "",
|
||||
license_plate: "",
|
||||
year: 2024,
|
||||
capacity_kg: 0,
|
||||
status: "AVAILABLE",
|
||||
};
|
||||
}
|
||||
isModalOpen = true;
|
||||
}
|
||||
|
||||
function closeModal() {
|
||||
isModalOpen = false;
|
||||
editingId = null;
|
||||
}
|
||||
|
||||
function getStatusColor(status) {
|
||||
switch (status) {
|
||||
case "AVAILABLE":
|
||||
return "green";
|
||||
case "MAINTENANCE":
|
||||
return "yellow";
|
||||
case "IN_TRANSIT":
|
||||
return "indigo";
|
||||
default:
|
||||
return "none";
|
||||
}
|
||||
}
|
||||
|
||||
onMount(fetchVehicles);
|
||||
</script>
|
||||
|
||||
<div class="p-8 space-y-4">
|
||||
<div class="flex justify-between items-center">
|
||||
<Heading tag="h2" class="text-2xl font-bold">Vehicles</Heading>
|
||||
<Button color="blue" onclick={() => openModal()}>+ Add Vehicle</Button>
|
||||
</div>
|
||||
|
||||
<Table shadow hoverable={true}>
|
||||
<TableHead>
|
||||
<TableHeadCell>ID</TableHeadCell>
|
||||
<TableHeadCell>Manufacturer/Model</TableHeadCell>
|
||||
<TableHeadCell>License Plate</TableHeadCell>
|
||||
<TableHeadCell>Year</TableHeadCell>
|
||||
<TableHeadCell>Capacity (kg.)</TableHeadCell>
|
||||
<TableHeadCell>Status</TableHeadCell>
|
||||
<TableHeadCell>Actions</TableHeadCell>
|
||||
</TableHead>
|
||||
<TableBody>
|
||||
{#each vehicles as vehicle (vehicle.id)}
|
||||
<TableBodyRow>
|
||||
<TableBodyCell>{vehicle.id}</TableBodyCell>
|
||||
<TableBodyCell class="font-medium"
|
||||
>{vehicle.brand} {vehicle.model}</TableBodyCell
|
||||
>
|
||||
<TableBodyCell>{vehicle.license_plate}</TableBodyCell>
|
||||
<TableBodyCell>{vehicle.year}</TableBodyCell>
|
||||
<TableBodyCell>{vehicle.capacity_kg}</TableBodyCell>
|
||||
<TableBodyCell>
|
||||
<Badge color={getStatusColor(vehicle.status)}
|
||||
>{statusOptions.filter((s) => s.value === vehicle.status).at(0)
|
||||
.name}</Badge
|
||||
>
|
||||
</TableBodyCell>
|
||||
<TableBodyCell class="space-x-2">
|
||||
<Button
|
||||
size="xs"
|
||||
color="alternative"
|
||||
onclick={() => openModal(vehicle)}>Edit</Button
|
||||
>
|
||||
<Button
|
||||
size="xs"
|
||||
color="red"
|
||||
onclick={() => deleteVehicle(vehicle.id)}>Delete</Button
|
||||
>
|
||||
</TableBodyCell>
|
||||
</TableBodyRow>
|
||||
{/each}
|
||||
</TableBody>
|
||||
</Table>
|
||||
</div>
|
||||
|
||||
<Modal
|
||||
title={editingId ? "Update Vehicle" : "Add Vehicle"}
|
||||
bind:open={isModalOpen}
|
||||
size="sm"
|
||||
autoclose={false}
|
||||
>
|
||||
<div class="space-y-4">
|
||||
<div>
|
||||
<Label for="brand" class="mb-2">Make</Label>
|
||||
<Input
|
||||
type="text"
|
||||
id="brand"
|
||||
bind:value={formData.brand}
|
||||
placeholder="e.g. Mercedes"
|
||||
required
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<Label for="model" class="mb-2">Model</Label>
|
||||
<Input
|
||||
type="text"
|
||||
id="model"
|
||||
bind:value={formData.model}
|
||||
placeholder="e.g. Actros"
|
||||
required
|
||||
/>
|
||||
</div>
|
||||
<div class="grid grid-cols-3 gap-4">
|
||||
<div>
|
||||
<Label for="plate" class="mb-2">License Plate</Label>
|
||||
<Input
|
||||
type="text"
|
||||
id="plate"
|
||||
bind:value={formData.license_plate}
|
||||
placeholder="AA1234BB"
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<Label for="year" class="mb-2">Year</Label>
|
||||
<Input type="number" id="year" bind:value={formData.year} />
|
||||
</div>
|
||||
<div>
|
||||
<Label for="capacity" class="mb-2">Capacity (kg.)</Label>
|
||||
<Input type="number" id="capacity" bind:value={formData.capacity_kg} />
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<Label for="status" class="mb-2">Status</Label>
|
||||
<Select items={statusOptions} bind:value={formData.status} />
|
||||
</div>
|
||||
<div class="flex justify-end space-x-3 pt-4">
|
||||
<Button color="alternative" onclick={closeModal}>Cancel</Button>
|
||||
<Button color="blue" onclick={saveVehicle}
|
||||
>{editingId ? "Update" : "Create"}</Button
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
</Modal>
|
||||
46
client/src/app.css
Normal file
46
client/src/app.css
Normal file
@@ -0,0 +1,46 @@
|
||||
@import 'tailwindcss';
|
||||
@plugin 'flowbite/plugin';
|
||||
|
||||
@custom-variant dark (&:where(.dark, .dark *));
|
||||
|
||||
@theme {
|
||||
--color-primary-50: #fff5f2;
|
||||
--color-primary-100: #fff1ee;
|
||||
--color-primary-200: #ffe4de;
|
||||
--color-primary-300: #ffd5cc;
|
||||
--color-primary-400: #ffbcad;
|
||||
--color-primary-500: #fe795d;
|
||||
--color-primary-600: #ef562f;
|
||||
--color-primary-700: #eb4f27;
|
||||
--color-primary-800: #cc4522;
|
||||
--color-primary-900: #a5371b;
|
||||
|
||||
--color-secondary-50: #f0f9ff;
|
||||
--color-secondary-100: #e0f2fe;
|
||||
--color-secondary-200: #bae6fd;
|
||||
--color-secondary-300: #7dd3fc;
|
||||
--color-secondary-400: #38bdf8;
|
||||
--color-secondary-500: #0ea5e9;
|
||||
--color-secondary-600: #0284c7;
|
||||
--color-secondary-700: #0369a1;
|
||||
--color-secondary-800: #075985;
|
||||
--color-secondary-900: #0c4a6e;
|
||||
}
|
||||
|
||||
@source "../node_modules/flowbite-svelte/dist";
|
||||
@source "../node_modules/flowbite-svelte-icons/dist";
|
||||
|
||||
@layer base {
|
||||
/* disable chrome cancel button */
|
||||
input[type="search"]::-webkit-search-cancel-button {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
:root {
|
||||
color-scheme: dark;
|
||||
}
|
||||
|
||||
body {
|
||||
@apply bg-gray-900 text-gray-100;
|
||||
}
|
||||
8
client/tailwind.config.js
Normal file
8
client/tailwind.config.js
Normal file
@@ -0,0 +1,8 @@
|
||||
/** @type {import('tailwindcss').Config} */
|
||||
export default {
|
||||
content: [
|
||||
"./src/**/*.{html,js,svelte,ts}",
|
||||
"./node_modules/flowbite-svelte/**/*.{html,js,svelte,ts}",
|
||||
],
|
||||
darkMode: "class", // This is the crucial line
|
||||
};
|
||||
@@ -1,7 +1,6 @@
|
||||
import tailwindcss from "@tailwindcss/vite";
|
||||
import { defineConfig } from "vite";
|
||||
import { svelte } from "@sveltejs/vite-plugin-svelte";
|
||||
|
||||
// https://vite.dev/config/
|
||||
export default defineConfig({
|
||||
plugins: [svelte()],
|
||||
});
|
||||
export default defineConfig({ plugins: [tailwindcss(), svelte()] });
|
||||
|
||||
@@ -7,6 +7,7 @@ import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
import org.jspecify.annotations.Nullable;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
import ua.com.dxrkness.dto.FreightRequest;
|
||||
import ua.com.dxrkness.model.Freight;
|
||||
import ua.com.dxrkness.service.FreightService;
|
||||
|
||||
@@ -74,8 +75,8 @@ public class FreightController {
|
||||
description = "Invalid freight data (e.g., weight exceeds vehicle capacity)"
|
||||
)
|
||||
@PostMapping
|
||||
public Freight add(@RequestBody Freight newFreight) {
|
||||
return service.add(newFreight);
|
||||
public Freight add(@RequestBody FreightRequest newFreight) {
|
||||
return service.add(newFreight.toEntity());
|
||||
}
|
||||
|
||||
@Operation(
|
||||
@@ -91,8 +92,8 @@ public class FreightController {
|
||||
description = "Invalid input"
|
||||
)
|
||||
@PutMapping("/{id}")
|
||||
public Freight update(@PathVariable("id") long id, @RequestBody Freight newFreight) {
|
||||
return service.update(id, newFreight);
|
||||
public Freight update(@PathVariable("id") long id, @RequestBody FreightRequest newFreight) {
|
||||
return service.update(id, newFreight.toEntity());
|
||||
}
|
||||
|
||||
@Operation(
|
||||
@@ -108,8 +109,8 @@ public class FreightController {
|
||||
description = "Invalid input"
|
||||
)
|
||||
@PatchMapping("/{id}")
|
||||
public Freight updatePatch(@PathVariable("id") long id, @RequestBody Freight newFreight) {
|
||||
return service.update(id, newFreight);
|
||||
public Freight updatePatch(@PathVariable("id") long id, @RequestBody FreightRequest newFreight) {
|
||||
return service.update(id, newFreight.toEntity());
|
||||
}
|
||||
|
||||
@Operation(
|
||||
|
||||
19
models/src/main/java/ua/com/dxrkness/dto/FreightRequest.java
Normal file
19
models/src/main/java/ua/com/dxrkness/dto/FreightRequest.java
Normal file
@@ -0,0 +1,19 @@
|
||||
package ua.com.dxrkness.dto;
|
||||
|
||||
import com.fasterxml.jackson.databind.PropertyNamingStrategies;
|
||||
import com.fasterxml.jackson.databind.annotation.JsonNaming;
|
||||
import ua.com.dxrkness.model.Freight;
|
||||
|
||||
@JsonNaming(PropertyNamingStrategies.SnakeCaseStrategy.class)
|
||||
@tools.jackson.databind.annotation.JsonNaming(tools.jackson.databind.PropertyNamingStrategies.SnakeCaseStrategy.class)
|
||||
public record FreightRequest(
|
||||
String name,
|
||||
String description,
|
||||
int weightKg,
|
||||
Freight.Dimensions dimensions,
|
||||
Freight.Status status
|
||||
) {
|
||||
public Freight toEntity() {
|
||||
return new Freight(0, name, description, weightKg, dimensions, status);
|
||||
}
|
||||
}
|
||||
21
models/src/main/java/ua/com/dxrkness/dto/RouteRequest.java
Normal file
21
models/src/main/java/ua/com/dxrkness/dto/RouteRequest.java
Normal file
@@ -0,0 +1,21 @@
|
||||
package ua.com.dxrkness.dto;
|
||||
|
||||
import com.fasterxml.jackson.databind.PropertyNamingStrategies;
|
||||
import com.fasterxml.jackson.databind.annotation.JsonNaming;
|
||||
import ua.com.dxrkness.model.Route;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@JsonNaming(PropertyNamingStrategies.SnakeCaseStrategy.class)
|
||||
@tools.jackson.databind.annotation.JsonNaming(tools.jackson.databind.PropertyNamingStrategies.SnakeCaseStrategy.class)
|
||||
public record RouteRequest(long vehicleId,
|
||||
List<Long> freightId,
|
||||
String startLocation,
|
||||
String endLocation,
|
||||
Double distanceKm,
|
||||
Double estimatedDurationHours,
|
||||
Route.Status status) {
|
||||
public Route toEntity() {
|
||||
return new Route(0, vehicleId, freightId, startLocation, endLocation, distanceKm, estimatedDurationHours, status);
|
||||
}
|
||||
}
|
||||
20
models/src/main/java/ua/com/dxrkness/dto/VehicleRequest.java
Normal file
20
models/src/main/java/ua/com/dxrkness/dto/VehicleRequest.java
Normal file
@@ -0,0 +1,20 @@
|
||||
package ua.com.dxrkness.dto;
|
||||
|
||||
import com.fasterxml.jackson.databind.PropertyNamingStrategies;
|
||||
import com.fasterxml.jackson.databind.annotation.JsonNaming;
|
||||
import ua.com.dxrkness.model.Vehicle;
|
||||
|
||||
@JsonNaming(PropertyNamingStrategies.SnakeCaseStrategy.class)
|
||||
@tools.jackson.databind.annotation.JsonNaming(tools.jackson.databind.PropertyNamingStrategies.SnakeCaseStrategy.class)
|
||||
public record VehicleRequest(
|
||||
String brand,
|
||||
String model,
|
||||
String licensePlate,
|
||||
int year,
|
||||
int capacityKg,
|
||||
Vehicle.Status status
|
||||
) {
|
||||
public Vehicle toEntity() {
|
||||
return new Vehicle(0, brand, model, licensePlate, year, capacityKg, status);
|
||||
}
|
||||
}
|
||||
@@ -10,6 +10,7 @@ import org.springframework.http.MediaType;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
import ua.com.dxrkness.client.FreightClient;
|
||||
import ua.com.dxrkness.client.VehicleClient;
|
||||
import ua.com.dxrkness.dto.RouteRequest;
|
||||
import ua.com.dxrkness.model.Freight;
|
||||
import ua.com.dxrkness.model.Route;
|
||||
import ua.com.dxrkness.model.Vehicle;
|
||||
@@ -153,8 +154,8 @@ public class RouteController {
|
||||
description = "Route object to be created. Must include valid vehicleId and freightId list.",
|
||||
required = true
|
||||
)
|
||||
@RequestBody Route newRoute) {
|
||||
return routeService.add(newRoute);
|
||||
@RequestBody RouteRequest newRoute) {
|
||||
return routeService.add(newRoute.toEntity());
|
||||
}
|
||||
|
||||
@Operation(
|
||||
@@ -177,8 +178,8 @@ public class RouteController {
|
||||
description = "Updated route object",
|
||||
required = true
|
||||
)
|
||||
@RequestBody Route newRoute) {
|
||||
return routeService.update(id, newRoute);
|
||||
@RequestBody RouteRequest newRoute) {
|
||||
return routeService.update(id, newRoute.toEntity());
|
||||
}
|
||||
|
||||
@Operation(
|
||||
@@ -201,8 +202,8 @@ public class RouteController {
|
||||
description = "Route object with fields to update",
|
||||
required = true
|
||||
)
|
||||
@RequestBody Route newRoute) {
|
||||
return routeService.update(id, newRoute);
|
||||
@RequestBody RouteRequest newRoute) {
|
||||
return routeService.update(id, newRoute.toEntity());
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -8,6 +8,7 @@ import org.jspecify.annotations.NullMarked;
|
||||
import org.jspecify.annotations.Nullable;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
import ua.com.dxrkness.dto.VehicleRequest;
|
||||
import ua.com.dxrkness.model.Vehicle;
|
||||
import ua.com.dxrkness.service.VehicleService;
|
||||
|
||||
@@ -68,8 +69,8 @@ public class VehicleController {
|
||||
description = "Vehicle object to be created",
|
||||
required = true
|
||||
)
|
||||
@RequestBody Vehicle newVehicle) {
|
||||
return service.add(newVehicle);
|
||||
@RequestBody VehicleRequest newVehicle) {
|
||||
return service.add(newVehicle.toEntity());
|
||||
}
|
||||
|
||||
@Operation(
|
||||
@@ -86,8 +87,8 @@ public class VehicleController {
|
||||
description = "Updated vehicle object",
|
||||
required = true
|
||||
)
|
||||
@RequestBody Vehicle newVehicle) {
|
||||
return service.update(id, newVehicle);
|
||||
@RequestBody VehicleRequest newVehicle) {
|
||||
return service.update(id, newVehicle.toEntity());
|
||||
}
|
||||
|
||||
@Operation(
|
||||
@@ -104,8 +105,8 @@ public class VehicleController {
|
||||
description = "Vehicle object with fields to update",
|
||||
required = true
|
||||
)
|
||||
@RequestBody Vehicle newVehicle) {
|
||||
return service.update(id, newVehicle);
|
||||
@RequestBody VehicleRequest newVehicle) {
|
||||
return service.update(id, newVehicle.toEntity());
|
||||
}
|
||||
|
||||
|
||||
|
||||
1
ІТРОІ_ПЗ_5_Критерії оцінки проєкту JSON.txt
Normal file
1
ІТРОІ_ПЗ_5_Критерії оцінки проєкту JSON.txt
Normal file
@@ -0,0 +1 @@
|
||||
["Клієнт успішно запускається та спрямовує всі запити + + + на API Gateway","Клієнт демонструє успішний обмін даними (GET/POST) + + + з мінімум двома публічними сервісами","Коректна обробка вхідних даних: Клієнт відображає дані, отримані з мікросервісів, у зрозумілому для + + + користувача вигляді.","Реалізовано функціональність \"Перегляд (Read): Відображення повного списку замовлень та списку + + + ресторанів","Реалізовано функціональність \"Створення (Create)\": Наявність форми для створення нової сутності, яка + + успішно відправляє POST-запит.","Реалізовано функціональність \"Редагування/Видалення (Update/Delete)\" для однієї + + ключової сутності.","Складна агрегація: Клієнт демонструє агрегацію + даних з кількох сервісів.","Користувацька взаємодія: Реалізовано елементи динамічної взаємодії (наприклад, перегляд деталей + + замовлення).","Обробка каскадних операцій: Клієнт демонструє виклик операції, яка запускає каскад внутрішніх + міжсервісних викликів на бекенді.","Обробка помилок (UX): Реалізовано механізм дружнього сповіщення користувача при отриманні + + + помилки від Gateway (HTTP 4xx/5xx).","Валідація вводу (Frontend): Реалізовано базову перевірку вхідних даних на стороні клієнта перед + + відправкою запиту.","Захист від дублювання (Ідемпотентність): Реалізовано механізм запобігання дублюванню для + неідемпотентних запитів.","Дизайн та Юзабіліті: Інтерфейс є інтуїтивно зрозумілим, має логічну навігацію та адекватний + візуальний дизайн."]
|
||||
Reference in New Issue
Block a user