diff --git a/angular-client/angular.json b/angular-client/angular.json index a61606a..d595dc8 100644 --- a/angular-client/angular.json +++ b/angular-client/angular.json @@ -91,6 +91,7 @@ } ], "styles": [ + "@angular/material/prebuilt-themes/cyan-orange.css", "src/styles.scss" ], "scripts": [] diff --git a/angular-client/build.gradle.kts b/angular-client/build.gradle.kts index 97bda96..8e49ea9 100644 --- a/angular-client/build.gradle.kts +++ b/angular-client/build.gradle.kts @@ -27,5 +27,5 @@ tasks.register("start") { dependsOn("npmInstall") dependsOn("getClient") command = "ng" - args.add("start") + args.add("serve") } diff --git a/angular-client/package-lock.json b/angular-client/package-lock.json index 281ae88..b43a529 100644 --- a/angular-client/package-lock.json +++ b/angular-client/package-lock.json @@ -9,10 +9,12 @@ "version": "0.0.0", "dependencies": { "@angular/animations": "^19.0.0", + "@angular/cdk": "^19.0.0", "@angular/common": "^19.0.0", "@angular/compiler": "^19.0.0", "@angular/core": "^19.0.0", "@angular/forms": "^19.0.0", + "@angular/material": "^19.0.0", "@angular/platform-browser": "^19.0.0", "@angular/platform-browser-dynamic": "^19.0.0", "@angular/router": "^19.0.0", @@ -34,6 +36,21 @@ "typescript": "~5.6.2" } }, + "node_modules/@alloc/quick-lru": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/@alloc/quick-lru/-/quick-lru-5.2.0.tgz", + "integrity": "sha512-UrcABB+4bUrFABwbluTIBErXwvbsU/V7TZWfmbgJfbkwiBuziS9gxdODUyuiecfdGQ85jglMW6juS3+z5TsKLw==", + "dev": true, + "license": "MIT", + "optional": true, + "peer": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/@ampproject/remapping": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.3.0.tgz", @@ -349,6 +366,23 @@ } } }, + "node_modules/@angular/cdk": { + "version": "19.0.0", + "resolved": "https://registry.npmjs.org/@angular/cdk/-/cdk-19.0.0.tgz", + "integrity": "sha512-KcOYhCwN4Bw3L4+W4ymTfPGqRjrkwD8M5jX8GM7YsZ5DsX9OEd/gNrwRvjn+8JItzimXLXdGrcqXrMTxkq7QPA==", + "license": "MIT", + "dependencies": { + "tslib": "^2.3.0" + }, + "optionalDependencies": { + "parse5": "^7.1.2" + }, + "peerDependencies": { + "@angular/common": "^19.0.0 || ^20.0.0", + "@angular/core": "^19.0.0 || ^20.0.0", + "rxjs": "^6.5.3 || ^7.4.0" + } + }, "node_modules/@angular/cli": { "version": "19.0.1", "resolved": "https://registry.npmjs.org/@angular/cli/-/cli-19.0.1.tgz", @@ -482,6 +516,24 @@ "rxjs": "^6.5.3 || ^7.4.0" } }, + "node_modules/@angular/material": { + "version": "19.0.0", + "resolved": "https://registry.npmjs.org/@angular/material/-/material-19.0.0.tgz", + "integrity": "sha512-j7dDFUh8dqiysuWu32biukDTHScajUYHFR9Srhn98kBwnXMob5y1paMoOx5RQO5DU4KCxKaKx8HcHJBJeTKHjw==", + "license": "MIT", + "dependencies": { + "tslib": "^2.3.0" + }, + "peerDependencies": { + "@angular/animations": "^19.0.0 || ^20.0.0", + "@angular/cdk": "19.0.0", + "@angular/common": "^19.0.0 || ^20.0.0", + "@angular/core": "^19.0.0 || ^20.0.0", + "@angular/forms": "^19.0.0 || ^20.0.0", + "@angular/platform-browser": "^19.0.0 || ^20.0.0", + "rxjs": "^6.5.3 || ^7.4.0" + } + }, "node_modules/@angular/platform-browser": { "version": "19.0.0", "resolved": "https://registry.npmjs.org/@angular/platform-browser/-/platform-browser-19.0.0.tgz", @@ -5529,6 +5581,15 @@ "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, + "node_modules/any-promise": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/any-promise/-/any-promise-1.3.0.tgz", + "integrity": "sha512-7UvmKalWRt1wgjL1RrGxoSJW/0QZFIegpeGvZG9kjp8vrRu55XTHbwnqq2GpXm9uLbcuhxm3IqX9OB4MZR1b2A==", + "dev": true, + "license": "MIT", + "optional": true, + "peer": true + }, "node_modules/anymatch": { "version": "3.1.3", "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", @@ -5556,6 +5617,15 @@ "url": "https://github.com/sponsors/jonschlinkert" } }, + "node_modules/arg": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/arg/-/arg-5.0.2.tgz", + "integrity": "sha512-PYjyFOLKQ9y57JvQ6QLo8dAgNqswh8M1RMJYdQduT6xbWSgK36P/Z/v+p888pM69jMMfS8Xd8F6I1kQ/I9HUGg==", + "dev": true, + "license": "MIT", + "optional": true, + "peer": true + }, "node_modules/argparse": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", @@ -6142,6 +6212,18 @@ "node": ">=6" } }, + "node_modules/camelcase-css": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/camelcase-css/-/camelcase-css-2.0.1.tgz", + "integrity": "sha512-QOSvevhslijgYwRx6Rv7zKdMF8lbRmx+uQGx2+vDc+KI/eBnsy9kit5aj23AgGu3pa4t9AgwbnXWqS+iOY+2aA==", + "dev": true, + "license": "MIT", + "optional": true, + "peer": true, + "engines": { + "node": ">= 6" + } + }, "node_modules/caniuse-lite": { "version": "1.0.30001684", "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001684.tgz", @@ -6944,6 +7026,24 @@ "dev": true, "license": "MIT" }, + "node_modules/didyoumean": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/didyoumean/-/didyoumean-1.2.2.tgz", + "integrity": "sha512-gxtyfqMg7GKyhQmb056K7M3xszy/myH8w+B4RT+QXBQsvAOdc3XymqDDPHx1BgPgsdAA5SIifona89YtRATDzw==", + "dev": true, + "license": "Apache-2.0", + "optional": true, + "peer": true + }, + "node_modules/dlv": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/dlv/-/dlv-1.1.3.tgz", + "integrity": "sha512-+HlytyjlPKnIG8XuRG8WvmBP8xs8P71y+SKKS6ZXWoEgLuePxtDoUEiH7WkdePWrQ5JBpE6aoVqfZfJUQkjXwA==", + "dev": true, + "license": "MIT", + "optional": true, + "peer": true + }, "node_modules/dns-packet": { "version": "5.6.1", "resolved": "https://registry.npmjs.org/dns-packet/-/dns-packet-5.6.1.tgz", @@ -7165,7 +7265,7 @@ "version": "4.5.0", "resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz", "integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==", - "dev": true, + "devOptional": true, "license": "BSD-2-Clause", "engines": { "node": ">=0.12" @@ -9463,6 +9563,18 @@ } } }, + "node_modules/lilconfig": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-2.1.0.tgz", + "integrity": "sha512-utWOt/GHzuUxnLKxB6dk81RoOeoNeHgbrXiuGk4yyF5qlRz+iIVWu56E2fqGHFrXz0QNUhLB/8nKqvRH66JKGQ==", + "dev": true, + "license": "MIT", + "optional": true, + "peer": true, + "engines": { + "node": ">=10" + } + }, "node_modules/lines-and-columns": { "version": "1.2.4", "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz", @@ -10477,6 +10589,20 @@ "node": "^18.17.0 || >=20.5.0" } }, + "node_modules/mz": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/mz/-/mz-2.7.0.tgz", + "integrity": "sha512-z81GNO7nnYMEhrGh9LeymoE4+Yr0Wn5McHIZMK5cfQCl+NDX08sCZgUc9/6MHni9IWuFLm1Z3HTCXu2z9fN62Q==", + "dev": true, + "license": "MIT", + "optional": true, + "peer": true, + "dependencies": { + "any-promise": "^1.0.0", + "object-assign": "^4.0.1", + "thenify-all": "^1.0.0" + } + }, "node_modules/nanoid": { "version": "3.3.7", "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.7.tgz", @@ -11021,6 +11147,18 @@ "node": ">=0.10.0" } }, + "node_modules/object-hash": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/object-hash/-/object-hash-3.0.0.tgz", + "integrity": "sha512-RSn9F68PjH9HqtltsSnqYC1XXoWe9Bju5+213R98cNGttag9q9yAOTzdbsqvIa7aNm5WffBZFpWYr2aWrklWAw==", + "dev": true, + "license": "MIT", + "optional": true, + "peer": true, + "engines": { + "node": ">= 6" + } + }, "node_modules/object-inspect": { "version": "1.13.3", "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.3.tgz", @@ -11366,7 +11504,7 @@ "version": "7.2.1", "resolved": "https://registry.npmjs.org/parse5/-/parse5-7.2.1.tgz", "integrity": "sha512-BuBYQYlv1ckiPdQi/ohiivi9Sagc9JG+Ozs0r7b/0iK3sKmrb0b9FdWdBbOdx6hBCM/F9Ir82ofnBhtZOjCRPQ==", - "dev": true, + "devOptional": true, "license": "MIT", "dependencies": { "entities": "^4.5.0" @@ -11525,6 +11663,18 @@ "node": ">=6" } }, + "node_modules/pirates": { + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/pirates/-/pirates-4.0.6.tgz", + "integrity": "sha512-saLsH7WeYYPiD25LDuLRRY/i+6HaPYr6G1OUlN39otzkSTxKnubR9RTxS3/Kk50s1g2JTgFwWQDQyplC5/SHZg==", + "dev": true, + "license": "MIT", + "optional": true, + "peer": true, + "engines": { + "node": ">= 6" + } + }, "node_modules/piscina": { "version": "4.7.0", "resolved": "https://registry.npmjs.org/piscina/-/piscina-4.7.0.tgz", @@ -11580,6 +11730,101 @@ "node": "^10 || ^12 || >=14" } }, + "node_modules/postcss-import": { + "version": "15.1.0", + "resolved": "https://registry.npmjs.org/postcss-import/-/postcss-import-15.1.0.tgz", + "integrity": "sha512-hpr+J05B2FVYUAXHeK1YyI267J/dDDhMU6B6civm8hSY1jYJnBXxzKDKDswzJmtLHryrjhnDjqqp/49t8FALew==", + "dev": true, + "license": "MIT", + "optional": true, + "peer": true, + "dependencies": { + "postcss-value-parser": "^4.0.0", + "read-cache": "^1.0.0", + "resolve": "^1.1.7" + }, + "engines": { + "node": ">=14.0.0" + }, + "peerDependencies": { + "postcss": "^8.0.0" + } + }, + "node_modules/postcss-js": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/postcss-js/-/postcss-js-4.0.1.tgz", + "integrity": "sha512-dDLF8pEO191hJMtlHFPRa8xsizHaM82MLfNkUHdUtVEV3tgTp5oj+8qbEqYM57SLfc74KSbw//4SeJma2LRVIw==", + "dev": true, + "license": "MIT", + "optional": true, + "peer": true, + "dependencies": { + "camelcase-css": "^2.0.1" + }, + "engines": { + "node": "^12 || ^14 || >= 16" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + "peerDependencies": { + "postcss": "^8.4.21" + } + }, + "node_modules/postcss-load-config": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/postcss-load-config/-/postcss-load-config-4.0.2.tgz", + "integrity": "sha512-bSVhyJGL00wMVoPUzAVAnbEoWyqRxkjv64tUl427SKnPrENtq6hJwUojroMz2VB+Q1edmi4IfrAPpami5VVgMQ==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "optional": true, + "peer": true, + "dependencies": { + "lilconfig": "^3.0.0", + "yaml": "^2.3.4" + }, + "engines": { + "node": ">= 14" + }, + "peerDependencies": { + "postcss": ">=8.0.9", + "ts-node": ">=9.0.0" + }, + "peerDependenciesMeta": { + "postcss": { + "optional": true + }, + "ts-node": { + "optional": true + } + } + }, + "node_modules/postcss-load-config/node_modules/lilconfig": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-3.1.2.tgz", + "integrity": "sha512-eop+wDAvpItUys0FWkHIKeC9ybYrTGbU41U5K7+bttZZeohvnY7M9dZ5kB21GNWiFT2q1OoPTvncPCgSOVO5ow==", + "dev": true, + "license": "MIT", + "optional": true, + "peer": true, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/antonk52" + } + }, "node_modules/postcss-loader": { "version": "8.1.1", "resolved": "https://registry.npmjs.org/postcss-loader/-/postcss-loader-8.1.1.tgz", @@ -11682,6 +11927,50 @@ "postcss": "^8.1.0" } }, + "node_modules/postcss-nested": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/postcss-nested/-/postcss-nested-6.2.0.tgz", + "integrity": "sha512-HQbt28KulC5AJzG+cZtj9kvKB93CFCdLvog1WFLf1D+xmMvPGlBstkpTEZfK5+AN9hfJocyBFCNiqyS48bpgzQ==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "optional": true, + "peer": true, + "dependencies": { + "postcss-selector-parser": "^6.1.1" + }, + "engines": { + "node": ">=12.0" + }, + "peerDependencies": { + "postcss": "^8.2.14" + } + }, + "node_modules/postcss-nested/node_modules/postcss-selector-parser": { + "version": "6.1.2", + "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.1.2.tgz", + "integrity": "sha512-Q8qQfPiZ+THO/3ZrOrO0cJJKfpYCagtMUkXbnEfmgUjwXg6z/WBeOyS9APBBPCTSiDV+s4SwQGu8yFsiMRIudg==", + "dev": true, + "license": "MIT", + "optional": true, + "peer": true, + "dependencies": { + "cssesc": "^3.0.0", + "util-deprecate": "^1.0.2" + }, + "engines": { + "node": ">=4" + } + }, "node_modules/postcss-selector-parser": { "version": "7.0.0", "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-7.0.0.tgz", @@ -11863,6 +12152,30 @@ "node": ">= 0.8" } }, + "node_modules/read-cache": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/read-cache/-/read-cache-1.0.0.tgz", + "integrity": "sha512-Owdv/Ft7IjOgm/i0xvNDZ1LrRANRfew4b2prF3OWMQLxLfu3bS8FVhCsrSCMK4lR56Y9ya+AThoTpDCTxCmpRA==", + "dev": true, + "license": "MIT", + "optional": true, + "peer": true, + "dependencies": { + "pify": "^2.3.0" + } + }, + "node_modules/read-cache/node_modules/pify": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", + "integrity": "sha512-udgsAY+fTnvv7kI7aaxbqwWNb0AHiB0qBO89PZKPkoTmGOgdbrHDKD+0B2X4uTfJ/FT1R09r9gTsjUjNJotuog==", + "dev": true, + "license": "MIT", + "optional": true, + "peer": true, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/readable-stream": { "version": "3.6.2", "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", @@ -13137,6 +13450,96 @@ "node": ">=8" } }, + "node_modules/sucrase": { + "version": "3.35.0", + "resolved": "https://registry.npmjs.org/sucrase/-/sucrase-3.35.0.tgz", + "integrity": "sha512-8EbVDiu9iN/nESwxeSxDKe0dunta1GOlHufmSSXxMD2z2/tMZpDMpvXQGsc+ajGo8y2uYUmixaSRUc/QPoQ0GA==", + "dev": true, + "license": "MIT", + "optional": true, + "peer": true, + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.2", + "commander": "^4.0.0", + "glob": "^10.3.10", + "lines-and-columns": "^1.1.6", + "mz": "^2.7.0", + "pirates": "^4.0.1", + "ts-interface-checker": "^0.1.9" + }, + "bin": { + "sucrase": "bin/sucrase", + "sucrase-node": "bin/sucrase-node" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + } + }, + "node_modules/sucrase/node_modules/brace-expansion": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", + "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "dev": true, + "license": "MIT", + "optional": true, + "peer": true, + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/sucrase/node_modules/commander": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/commander/-/commander-4.1.1.tgz", + "integrity": "sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA==", + "dev": true, + "license": "MIT", + "optional": true, + "peer": true, + "engines": { + "node": ">= 6" + } + }, + "node_modules/sucrase/node_modules/glob": { + "version": "10.4.5", + "resolved": "https://registry.npmjs.org/glob/-/glob-10.4.5.tgz", + "integrity": "sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg==", + "dev": true, + "license": "ISC", + "optional": true, + "peer": true, + "dependencies": { + "foreground-child": "^3.1.0", + "jackspeak": "^3.1.2", + "minimatch": "^9.0.4", + "minipass": "^7.1.2", + "package-json-from-dist": "^1.0.0", + "path-scurry": "^1.11.1" + }, + "bin": { + "glob": "dist/esm/bin.mjs" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/sucrase/node_modules/minimatch": { + "version": "9.0.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", + "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", + "dev": true, + "license": "ISC", + "optional": true, + "peer": true, + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, "node_modules/supports-color": { "version": "7.2.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", @@ -13173,6 +13576,134 @@ "node": ">=0.10" } }, + "node_modules/tailwindcss": { + "version": "3.4.15", + "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-3.4.15.tgz", + "integrity": "sha512-r4MeXnfBmSOuKUWmXe6h2CcyfzJCEk4F0pptO5jlnYSIViUkVmsawj80N5h2lO3gwcmSb4n3PuN+e+GC1Guylw==", + "dev": true, + "license": "MIT", + "optional": true, + "peer": true, + "dependencies": { + "@alloc/quick-lru": "^5.2.0", + "arg": "^5.0.2", + "chokidar": "^3.6.0", + "didyoumean": "^1.2.2", + "dlv": "^1.1.3", + "fast-glob": "^3.3.2", + "glob-parent": "^6.0.2", + "is-glob": "^4.0.3", + "jiti": "^1.21.6", + "lilconfig": "^2.1.0", + "micromatch": "^4.0.8", + "normalize-path": "^3.0.0", + "object-hash": "^3.0.0", + "picocolors": "^1.1.1", + "postcss": "^8.4.47", + "postcss-import": "^15.1.0", + "postcss-js": "^4.0.1", + "postcss-load-config": "^4.0.2", + "postcss-nested": "^6.2.0", + "postcss-selector-parser": "^6.1.2", + "resolve": "^1.22.8", + "sucrase": "^3.35.0" + }, + "bin": { + "tailwind": "lib/cli.js", + "tailwindcss": "lib/cli.js" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/tailwindcss/node_modules/chokidar": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz", + "integrity": "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==", + "dev": true, + "license": "MIT", + "optional": true, + "peer": true, + "dependencies": { + "anymatch": "~3.1.2", + "braces": "~3.0.2", + "glob-parent": "~5.1.2", + "is-binary-path": "~2.1.0", + "is-glob": "~4.0.1", + "normalize-path": "~3.0.0", + "readdirp": "~3.6.0" + }, + "engines": { + "node": ">= 8.10.0" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + }, + "optionalDependencies": { + "fsevents": "~2.3.2" + } + }, + "node_modules/tailwindcss/node_modules/chokidar/node_modules/glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "dev": true, + "license": "ISC", + "optional": true, + "peer": true, + "dependencies": { + "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/tailwindcss/node_modules/picomatch": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "dev": true, + "license": "MIT", + "optional": true, + "peer": true, + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/tailwindcss/node_modules/postcss-selector-parser": { + "version": "6.1.2", + "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.1.2.tgz", + "integrity": "sha512-Q8qQfPiZ+THO/3ZrOrO0cJJKfpYCagtMUkXbnEfmgUjwXg6z/WBeOyS9APBBPCTSiDV+s4SwQGu8yFsiMRIudg==", + "dev": true, + "license": "MIT", + "optional": true, + "peer": true, + "dependencies": { + "cssesc": "^3.0.0", + "util-deprecate": "^1.0.2" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/tailwindcss/node_modules/readdirp": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", + "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", + "dev": true, + "license": "MIT", + "optional": true, + "peer": true, + "dependencies": { + "picomatch": "^2.2.1" + }, + "engines": { + "node": ">=8.10.0" + } + }, "node_modules/tapable": { "version": "2.2.1", "resolved": "https://registry.npmjs.org/tapable/-/tapable-2.2.1.tgz", @@ -13364,6 +13895,33 @@ "url": "https://opencollective.com/webpack" } }, + "node_modules/thenify": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/thenify/-/thenify-3.3.1.tgz", + "integrity": "sha512-RVZSIV5IG10Hk3enotrhvz0T9em6cyHBLkH/YAZuKqd8hRkKhSfCGIcP2KUY0EPxndzANBmNllzWPwak+bheSw==", + "dev": true, + "license": "MIT", + "optional": true, + "peer": true, + "dependencies": { + "any-promise": "^1.0.0" + } + }, + "node_modules/thenify-all": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/thenify-all/-/thenify-all-1.6.0.tgz", + "integrity": "sha512-RNxQH/qI8/t3thXJDwcstUO4zeqo64+Uy/+sNVRBx4Xn2OX+OZ9oP+iJnNFqplFra2ZUVeKCSa2oVWi3T4uVmA==", + "dev": true, + "license": "MIT", + "optional": true, + "peer": true, + "dependencies": { + "thenify": ">= 3.1.0 < 4" + }, + "engines": { + "node": ">=0.8" + } + }, "node_modules/thingies": { "version": "1.21.0", "resolved": "https://registry.npmjs.org/thingies/-/thingies-1.21.0.tgz", @@ -13447,6 +14005,15 @@ "tree-kill": "cli.js" } }, + "node_modules/ts-interface-checker": { + "version": "0.1.13", + "resolved": "https://registry.npmjs.org/ts-interface-checker/-/ts-interface-checker-0.1.13.tgz", + "integrity": "sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA==", + "dev": true, + "license": "Apache-2.0", + "optional": true, + "peer": true + }, "node_modules/tslib": { "version": "2.8.1", "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", @@ -14998,6 +15565,21 @@ "dev": true, "license": "ISC" }, + "node_modules/yaml": { + "version": "2.6.1", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.6.1.tgz", + "integrity": "sha512-7r0XPzioN/Q9kXBro/XPnA6kznR73DHq+GXh5ON7ZozRO6aMjbmiBuKste2wslTFkC5d1dw0GooOCepZXJ2SAg==", + "dev": true, + "license": "ISC", + "optional": true, + "peer": true, + "bin": { + "yaml": "bin.mjs" + }, + "engines": { + "node": ">= 14" + } + }, "node_modules/yargs": { "version": "17.7.2", "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz", diff --git a/angular-client/package.json b/angular-client/package.json index b8633b3..da022c8 100644 --- a/angular-client/package.json +++ b/angular-client/package.json @@ -11,10 +11,12 @@ "private": true, "dependencies": { "@angular/animations": "^19.0.0", + "@angular/cdk": "^19.0.0", "@angular/common": "^19.0.0", "@angular/compiler": "^19.0.0", "@angular/core": "^19.0.0", "@angular/forms": "^19.0.0", + "@angular/material": "^19.0.0", "@angular/platform-browser": "^19.0.0", "@angular/platform-browser-dynamic": "^19.0.0", "@angular/router": "^19.0.0", diff --git a/angular-client/public/assets/images/pets.png b/angular-client/public/assets/images/pets.png new file mode 100644 index 0000000..bb5cf3a Binary files /dev/null and b/angular-client/public/assets/images/pets.png differ diff --git a/angular-client/src/app/app.component.html b/angular-client/src/app/app.component.html index 36093e1..0b1532e 100644 --- a/angular-client/src/app/app.component.html +++ b/angular-client/src/app/app.component.html @@ -1,336 +1,45 @@ - - - - - - - - - - - -
-
-
- -

Hello, {{ title }}

-

Congratulations! Your app is running. 🎉

-
- -
-
- @for (item of [ - { title: 'Explore the Docs', link: 'https://angular.dev' }, - { title: 'Learn with Tutorials', link: 'https://angular.dev/tutorials' }, - { title: 'CLI Docs', link: 'https://angular.dev/tools/cli' }, - { title: 'Angular Language Service', link: 'https://angular.dev/tools/language-service' }, - { title: 'Angular DevTools', link: 'https://angular.dev/tools/devtools' }, - ]; track item.title) { - - {{ item.title }} - - - - - } -
- -
-
-
- - - - - - - - - - - + + + + Home + + + + + + Search + + + + Add New + + + + + + + All + + + + Add New + + + + + Pet Types + + + + Specialties + + +
+ +
\ No newline at end of file diff --git a/angular-client/src/app/app.component.ts b/angular-client/src/app/app.component.ts index 4492543..bf50937 100644 --- a/angular-client/src/app/app.component.ts +++ b/angular-client/src/app/app.component.ts @@ -1,12 +1,15 @@ import { Component } from '@angular/core'; -import { RouterOutlet } from '@angular/router'; +import { RouterLink, RouterOutlet } from '@angular/router'; +import { MatToolbarModule } from '@angular/material/toolbar'; +import { MatButtonModule } from '@angular/material/button'; +import { MatIconModule } from '@angular/material/icon'; +import { MatMenuModule } from '@angular/material/menu'; @Component({ selector: 'app-root', - imports: [RouterOutlet], + imports: [RouterLink, RouterOutlet, MatToolbarModule, MatButtonModule, MatIconModule, MatMenuModule], templateUrl: './app.component.html', styleUrl: './app.component.scss' }) export class AppComponent { - title = 'angular-client'; } diff --git a/angular-client/src/app/app.config.ts b/angular-client/src/app/app.config.ts index 61548f6..aab26ad 100644 --- a/angular-client/src/app/app.config.ts +++ b/angular-client/src/app/app.config.ts @@ -4,8 +4,9 @@ import { provideRouter } from '@angular/router'; import { routes } from './app.routes'; import { provideHttpClient } from '@angular/common/http'; import { Configuration } from '../../build/client'; +import { provideAnimationsAsync } from '@angular/platform-browser/animations/async'; export const appConfig: ApplicationConfig = { providers: [provideZoneChangeDetection({ eventCoalescing: true }), provideRouter(routes), provideHttpClient(), {provide: Configuration, useValue: new Configuration({ - })}] + })}, provideAnimationsAsync()] }; diff --git a/angular-client/src/app/app.routes.ts b/angular-client/src/app/app.routes.ts index dc39edb..92bb2da 100644 --- a/angular-client/src/app/app.routes.ts +++ b/angular-client/src/app/app.routes.ts @@ -1,3 +1,13 @@ import { Routes } from '@angular/router'; +import { PageNotFoundComponent } from './page-not-found/page-not-found.component'; +import { WelcomeComponent } from './welcome/welcome.component'; +import { OwnerListComponent } from './owner-list/owner-list.component'; +import { OwnerAddComponent } from './owner-add/owner-add.component'; -export const routes: Routes = []; +export const routes: Routes = [ + { path: 'welcome', component: WelcomeComponent }, + { path: 'owners', component: OwnerListComponent }, + { path: 'owners/add', component: OwnerAddComponent }, + { path: '', component: WelcomeComponent }, + { path: '**', component: PageNotFoundComponent } +]; diff --git a/angular-client/src/app/owner-add/owner-add.component.html b/angular-client/src/app/owner-add/owner-add.component.html new file mode 100644 index 0000000..1ece570 --- /dev/null +++ b/angular-client/src/app/owner-add/owner-add.component.html @@ -0,0 +1,55 @@ +

New Owner

+
+ + First Name + + @if (form.controls["firstName"].hasError('required')) {First name is required} + @if (form.controls["firstName"].hasError('minlength')) {First name must be at least 1 character + long} + @if (form.controls["firstName"].hasError('maxlength')) {First name may be at most 30 characters + long} + @if (form.controls["firstName"].hasError('pattern')) {First name must consist of letters + only} + +
+ + Last Name + + @if (form.controls["lastName"].hasError('required')) {Last name is required} + @if (form.controls["lastName"].hasError('minlength')) {Last name must be at least 1 character + long} + @if (form.controls["lastName"].hasError('maxlength')) {Last name may be at most 30 characters + long} + @if (form.controls["lastName"].hasError('pattern')) {Last name must consist of letters + only} + +
+ + Address + + @if (form.controls["address"].hasError('required')) {Address is required} + @if (form.controls["address"].hasError('maxlength')) {Address may be at most 255 characters + long} + +
+ + City + + @if (form.controls["city"].hasError('required')) {City is required} + @if (form.controls["city"].hasError('maxlength')) {City may be at most 80 characters + long} + +
+ + Telephone + + @if (form.controls["telephone"].hasError('required')) {Telephone is required} + @if (form.controls["telephone"].hasError('maxlength')) {Telephone may be at most 20 numbers + long} + @if (form.controls["telephone"].hasError('pattern')) {Telephone must consist of numbers + only} + +
+ Back + +
\ No newline at end of file diff --git a/angular-client/src/app/owner-add/owner-add.component.scss b/angular-client/src/app/owner-add/owner-add.component.scss new file mode 100644 index 0000000..e69de29 diff --git a/angular-client/src/app/owner-add/owner-add.component.spec.ts b/angular-client/src/app/owner-add/owner-add.component.spec.ts new file mode 100644 index 0000000..4108f3d --- /dev/null +++ b/angular-client/src/app/owner-add/owner-add.component.spec.ts @@ -0,0 +1,23 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; + +import { OwnerAddComponent } from './owner-add.component'; + +describe('OwnerAddComponent', () => { + let component: OwnerAddComponent; + let fixture: ComponentFixture; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + imports: [OwnerAddComponent] + }) + .compileComponents(); + + fixture = TestBed.createComponent(OwnerAddComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/angular-client/src/app/owner-add/owner-add.component.ts b/angular-client/src/app/owner-add/owner-add.component.ts new file mode 100644 index 0000000..53d2b91 --- /dev/null +++ b/angular-client/src/app/owner-add/owner-add.component.ts @@ -0,0 +1,39 @@ +import { Component, inject } from '@angular/core'; +import { FormBuilder, FormGroup, ReactiveFormsModule, Validators } from '@angular/forms'; +import { MatFormFieldModule } from '@angular/material/form-field'; +import { MatInputModule } from '@angular/material/input'; +import { OwnerService } from '../../../build/client'; +import { Router, RouterLink } from '@angular/router'; +import { MatButtonModule } from '@angular/material/button'; +import { MatSnackBar } from '@angular/material/snack-bar'; + +@Component({ + selector: 'app-owner-add', + imports: [ReactiveFormsModule, RouterLink, MatButtonModule, MatFormFieldModule, MatInputModule], + templateUrl: './owner-add.component.html', + styleUrl: './owner-add.component.scss' +}) +export class OwnerAddComponent { + form: FormGroup; + private readonly ownerService = inject(OwnerService); + private readonly router = inject(Router); + private readonly snackBar = inject(MatSnackBar); + constructor(builder: FormBuilder) { + this.form = builder.group({ + firstName: [null, [Validators.minLength(1), Validators.maxLength(30), Validators.pattern(/^[a-zA-Z]*$/), Validators.required]], + lastName: [null, [Validators.minLength(1), Validators.maxLength(30), Validators.pattern(/^[a-zA-Z]*$/), Validators.required]], + address: [null, [Validators.required, Validators.maxLength(255)]], + city: [null, [Validators.required, Validators.maxLength(80)]], + telephone: [null, [Validators.minLength(1), Validators.maxLength(20), Validators.pattern(/^[0-9]*$/), Validators.required]], + }); + } + + onSubmit() { + this.ownerService.addOwner(this.form.value).subscribe({ + next: _newOwner => { + this.router.navigate(['/owners']); + }, + error: error => this.snackBar.open(error) + }); + } +} diff --git a/angular-client/src/app/owner-list/owner-list.component.html b/angular-client/src/app/owner-list/owner-list.component.html new file mode 100644 index 0000000..ff77203 --- /dev/null +++ b/angular-client/src/app/owner-list/owner-list.component.html @@ -0,0 +1,29 @@ +

Owners

+
+
+ + Last Name + + +
+ +
+ + + + + + + + + + + + + + +
Name{{owner.firstName}} + {{owner.lastName}}Pets +
    @for (pet of owner.pets; track pet.id) {
  • {{pet.name}}
  • }
+
+Add Owner \ No newline at end of file diff --git a/angular-client/src/app/owner-list/owner-list.component.scss b/angular-client/src/app/owner-list/owner-list.component.scss new file mode 100644 index 0000000..e69de29 diff --git a/angular-client/src/app/owner-list/owner-list.component.spec.ts b/angular-client/src/app/owner-list/owner-list.component.spec.ts new file mode 100644 index 0000000..f7a1f19 --- /dev/null +++ b/angular-client/src/app/owner-list/owner-list.component.spec.ts @@ -0,0 +1,23 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; + +import { OwnerListComponent } from './owner-list.component'; + +describe('OwnerListComponent', () => { + let component: OwnerListComponent; + let fixture: ComponentFixture; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + imports: [OwnerListComponent] + }) + .compileComponents(); + + fixture = TestBed.createComponent(OwnerListComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/angular-client/src/app/owner-list/owner-list.component.ts b/angular-client/src/app/owner-list/owner-list.component.ts new file mode 100644 index 0000000..dc45859 --- /dev/null +++ b/angular-client/src/app/owner-list/owner-list.component.ts @@ -0,0 +1,34 @@ +import { Component, inject, OnInit } from '@angular/core'; +import { FormControl, ReactiveFormsModule } from '@angular/forms'; +import { MatButtonModule } from '@angular/material/button'; +import { MatCardModule } from '@angular/material/card'; +import { MatFormFieldModule } from '@angular/material/form-field'; +import { MatInputModule } from '@angular/material/input'; +import { MatTableModule } from '@angular/material/table'; +import { Owner, OwnerService } from '../../../build/client'; +import { RouterLink } from '@angular/router'; +import { Observable, startWith, Subject, switchMap } from 'rxjs'; +import { CollectionViewer, DataSource } from '@angular/cdk/collections'; + +@Component({ + selector: 'app-owner-list', + imports: [RouterLink, MatCardModule, MatTableModule, MatButtonModule, MatFormFieldModule, MatInputModule, ReactiveFormsModule], + templateUrl: './owner-list.component.html', + styleUrl: './owner-list.component.scss' +}) +export class OwnerListComponent implements DataSource { + lastNameInput = new FormControl(); + lastName$ = new Subject(); + columnsToDisplay = ["name", "address", "city", "telephone", "pets"]; + private readonly ownerService = inject(OwnerService); + + connect(_collectionViewer: CollectionViewer): Observable { + return this.lastName$.pipe(startWith(this.lastNameInput.value), switchMap(lastName => this.ownerService.listOwners(lastName))) + } + disconnect(_collectionViewer: CollectionViewer): void { + } + + onSubmit() { + this.lastName$.next(this.lastNameInput.value || undefined); + } +} \ No newline at end of file diff --git a/angular-client/src/app/page-not-found/page-not-found.component.html b/angular-client/src/app/page-not-found/page-not-found.component.html new file mode 100644 index 0000000..c683218 --- /dev/null +++ b/angular-client/src/app/page-not-found/page-not-found.component.html @@ -0,0 +1,5 @@ + + Oops! Page not found ! + Not Found - 404 error + pets logo + \ No newline at end of file diff --git a/angular-client/src/app/page-not-found/page-not-found.component.scss b/angular-client/src/app/page-not-found/page-not-found.component.scss new file mode 100644 index 0000000..e69de29 diff --git a/angular-client/src/app/page-not-found/page-not-found.component.spec.ts b/angular-client/src/app/page-not-found/page-not-found.component.spec.ts new file mode 100644 index 0000000..878de5b --- /dev/null +++ b/angular-client/src/app/page-not-found/page-not-found.component.spec.ts @@ -0,0 +1,23 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; + +import { PageNotFoundComponent } from './page-not-found.component'; + +describe('PageNotFoundComponent', () => { + let component: PageNotFoundComponent; + let fixture: ComponentFixture; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + imports: [PageNotFoundComponent] + }) + .compileComponents(); + + fixture = TestBed.createComponent(PageNotFoundComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/angular-client/src/app/page-not-found/page-not-found.component.ts b/angular-client/src/app/page-not-found/page-not-found.component.ts new file mode 100644 index 0000000..4783a40 --- /dev/null +++ b/angular-client/src/app/page-not-found/page-not-found.component.ts @@ -0,0 +1,12 @@ +import { Component } from '@angular/core'; +import { MatCardModule } from '@angular/material/card'; + +@Component({ + selector: 'app-page-not-found', + imports: [MatCardModule], + templateUrl: './page-not-found.component.html', + styleUrl: './page-not-found.component.scss' +}) +export class PageNotFoundComponent { + +} diff --git a/angular-client/src/app/welcome/welcome.component.html b/angular-client/src/app/welcome/welcome.component.html new file mode 100644 index 0000000..a234aee --- /dev/null +++ b/angular-client/src/app/welcome/welcome.component.html @@ -0,0 +1,5 @@ + + Welcome to Petclinic + Welcome + pets logo + \ No newline at end of file diff --git a/angular-client/src/app/welcome/welcome.component.scss b/angular-client/src/app/welcome/welcome.component.scss new file mode 100644 index 0000000..e69de29 diff --git a/angular-client/src/app/welcome/welcome.component.spec.ts b/angular-client/src/app/welcome/welcome.component.spec.ts new file mode 100644 index 0000000..92182b5 --- /dev/null +++ b/angular-client/src/app/welcome/welcome.component.spec.ts @@ -0,0 +1,23 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; + +import { WelcomeComponent } from './welcome.component'; + +describe('WelcomeComponent', () => { + let component: WelcomeComponent; + let fixture: ComponentFixture; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + imports: [WelcomeComponent] + }) + .compileComponents(); + + fixture = TestBed.createComponent(WelcomeComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/angular-client/src/app/welcome/welcome.component.ts b/angular-client/src/app/welcome/welcome.component.ts new file mode 100644 index 0000000..304b2c0 --- /dev/null +++ b/angular-client/src/app/welcome/welcome.component.ts @@ -0,0 +1,12 @@ +import { Component } from '@angular/core'; +import { MatCardModule } from '@angular/material/card'; + +@Component({ + selector: 'app-welcome', + imports: [MatCardModule], + templateUrl: './welcome.component.html', + styleUrl: './welcome.component.scss' +}) +export class WelcomeComponent { + +} diff --git a/angular-client/src/index.html b/angular-client/src/index.html index ed43911..5347070 100644 --- a/angular-client/src/index.html +++ b/angular-client/src/index.html @@ -6,8 +6,10 @@ + + - + diff --git a/angular-client/src/styles.scss b/angular-client/src/styles.scss index 90d4ee0..4bd30ea 100644 --- a/angular-client/src/styles.scss +++ b/angular-client/src/styles.scss @@ -1 +1,12 @@ /* You can add global styles to this file, and also import other style files */ +@use '@angular/material' as mat; +html { + color-scheme: light; + @include mat.theme(( + color: mat.$green-palette, + typography: Roboto, + density: 0 + )); +} +html, body { height: 100%; } +body { margin: 0; font-family: Roboto, "Helvetica Neue", sans-serif; } \ No newline at end of file