diff --git a/.gitignore b/.gitignore index f080928eb..c253b6dea 100644 --- a/.gitignore +++ b/.gitignore @@ -50,4 +50,7 @@ config.yaml # OS .DS_Store -Thumbs.db \ No newline at end of file +Thumbs.db + +# Copilot instructions (local only) +.github/copilot-instructions.md diff --git a/backend/.render-build-trigger b/backend/.render-build-trigger index 782d52a5c..3bb7a5909 100644 --- a/backend/.render-build-trigger +++ b/backend/.render-build-trigger @@ -2,9 +2,9 @@ # This file is used to force Docker service rebuilds in PR previews # Modify LAST_UPDATE to trigger rebuilds -LAST_UPDATE=2025-10-22T15:22:55Z +LAST_UPDATE=2026-02-10T12:10:22Z # Instructions: # 1. To force rebuild of Docker services in a PR, update LAST_UPDATE -# 2. Run: perl -i -pe "s/LAST_UPDATE=2025-10-22T15:22:55Z +# 2. Run: perl -i -pe "s/LAST_UPDATE=2026-02-10T12:10:22Z # 2. Commit and push changes to trigger Docker rebuilds \ No newline at end of file diff --git a/collect/.render-build-trigger b/collect/.render-build-trigger index 782d52a5c..3bb7a5909 100644 --- a/collect/.render-build-trigger +++ b/collect/.render-build-trigger @@ -2,9 +2,9 @@ # This file is used to force Docker service rebuilds in PR previews # Modify LAST_UPDATE to trigger rebuilds -LAST_UPDATE=2025-10-22T15:22:55Z +LAST_UPDATE=2026-02-10T12:10:22Z # Instructions: # 1. To force rebuild of Docker services in a PR, update LAST_UPDATE -# 2. Run: perl -i -pe "s/LAST_UPDATE=2025-10-22T15:22:55Z +# 2. Run: perl -i -pe "s/LAST_UPDATE=2026-02-10T12:10:22Z # 2. Commit and push changes to trigger Docker rebuilds \ No newline at end of file diff --git a/docs/en/05-system-registration.md b/docs/en/05-system-registration.md index 8b6e2e034..aad909bc9 100644 --- a/docs/en/05-system-registration.md +++ b/docs/en/05-system-registration.md @@ -323,7 +323,7 @@ MY_SYSTEM_SECRET=my_a1b2c3d4e5f6g7h8i9j0.k1l2m3n4o5p6q7r8s9t0u1v2w3x4y5z6a7b8c9d Administrators can view registration status: 1. Navigate to **Systems** -2. Find the system and click **View details** +2. Find the system and click **View** 3. Check fields: - **System_key**: Now visible (was hidden before) - **Subscription**: Shows timestamp diff --git a/frontend/.render-build-trigger b/frontend/.render-build-trigger index 782d52a5c..3bb7a5909 100644 --- a/frontend/.render-build-trigger +++ b/frontend/.render-build-trigger @@ -2,9 +2,9 @@ # This file is used to force Docker service rebuilds in PR previews # Modify LAST_UPDATE to trigger rebuilds -LAST_UPDATE=2025-10-22T15:22:55Z +LAST_UPDATE=2026-02-10T12:10:22Z # Instructions: # 1. To force rebuild of Docker services in a PR, update LAST_UPDATE -# 2. Run: perl -i -pe "s/LAST_UPDATE=2025-10-22T15:22:55Z +# 2. Run: perl -i -pe "s/LAST_UPDATE=2026-02-10T12:10:22Z # 2. Commit and push changes to trigger Docker rebuilds \ No newline at end of file diff --git a/frontend/package-lock.json b/frontend/package-lock.json index a3cf78f88..fb81c14df 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -15,8 +15,9 @@ "@fortawesome/vue-fontawesome": "^3.0.8", "@logto/vue": "^3.0.8", "@nethesis/nethesis-light-svg-icons": "github:nethesis/Font-Awesome#ns-light", - "@nethesis/vue-components": "^3.4.0", - "@pinia/colada": "^0.17.6", + "@nethesis/nethesis-solid-svg-icons": "github:nethesis/Font-Awesome#ns-solid", + "@nethesis/vue-components": "^3.6.0", + "@pinia/colada": "^0.21.5", "@tailwindcss/vite": "^4.1.10", "@vueuse/core": "^13.4.0", "axios": "^1.11.0", @@ -30,7 +31,7 @@ "vue-router": "^4.5.0" }, "devDependencies": { - "@pinia/colada-devtools": "^0.1.5", + "@pinia/colada-devtools": "^0.4.3", "@tsconfig/node22": "^22.0.1", "@types/lodash": "^4.17.18", "@types/node": "^22.14.0", @@ -122,7 +123,6 @@ "integrity": "sha512-2BCOP7TN8M+gVDj7/ht3hsaO/B/n5oDbiAyyvnRlNOs+u1o+JWNYTQrmpuNp1/Wq2gcFrI01JAW+paEKDMx/CA==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@babel/code-frame": "^7.27.1", "@babel/generator": "^7.28.3", @@ -701,7 +701,6 @@ } ], "license": "MIT", - "peer": true, "engines": { "node": ">=18" }, @@ -725,7 +724,6 @@ } ], "license": "MIT", - "peer": true, "engines": { "node": ">=18" } @@ -1345,7 +1343,6 @@ "resolved": "https://registry.npmjs.org/@fortawesome/fontawesome-svg-core/-/fontawesome-svg-core-6.7.2.tgz", "integrity": "sha512-yxtOBWDrdi5DD5o1pmVdq3WMCvnobT0LU6R8RyyVXPvFRd2o79/0NCuQoCjNTeZz9EzA9xS3JxNWfv54RIHFEA==", "license": "MIT", - "peer": true, "dependencies": { "@fortawesome/fontawesome-common-types": "6.7.2" }, @@ -1650,10 +1647,22 @@ "node": ">=6" } }, + "node_modules/@nethesis/nethesis-solid-svg-icons": { + "version": "6.2.1", + "resolved": "git+ssh://git@github.com/nethesis/Font-Awesome.git#16419000ca62bc35db676d033ef00b8ef9771024", + "hasInstallScript": true, + "license": "UNLICENSED", + "dependencies": { + "@fortawesome/fontawesome-common-types": "^6.7.2" + }, + "engines": { + "node": ">=6" + } + }, "node_modules/@nethesis/vue-components": { - "version": "3.4.0", - "resolved": "https://registry.npmjs.org/@nethesis/vue-components/-/vue-components-3.4.0.tgz", - "integrity": "sha512-+ST793nRmJS59l0jq7BpJCXLQDGPn3W9BGumnW4LO37jvBo9V19igjXoq9Z/pza8qNtHc/hWghtS/jW22W7M2w==", + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/@nethesis/vue-components/-/vue-components-3.6.0.tgz", + "integrity": "sha512-b1nEkqKFHWppEIS30cTnjmIJh4ZPEgTvvCmL/FD0vTef/HxE9OrqOCmNGQIMiZWGOeSbFunD01t0zNrXgiCf7A==", "dependencies": { "@fontsource/poppins": "^5.2.6", "@fortawesome/fontawesome-svg-core": "^6.5.1", @@ -1804,13 +1813,10 @@ } }, "node_modules/@pinia/colada": { - "version": "0.17.6", - "resolved": "https://registry.npmjs.org/@pinia/colada/-/colada-0.17.6.tgz", - "integrity": "sha512-odayx9xVMUgC8ZMU/hwqODoboHnSWigp7VsbKGHKrNl9yHnljmxgJMwm1vtF/KIBSd2vUBigmN3maFqcC48/Rg==", + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@pinia/colada/-/colada-0.21.5.tgz", + "integrity": "sha512-oWKFUXCE/mibRqeJtWmI37hGnlXk6te7j1XHML/pi2RkUCNl4GWxHYc3Q8cmEuWtuoynvWByiAZhnvthLp+3UA==", "license": "MIT", - "dependencies": { - "@vue/devtools-api": "^8.0.2" - }, "funding": { "url": "https://github.com/sponsors/posva" }, @@ -1820,9 +1826,9 @@ } }, "node_modules/@pinia/colada-devtools": { - "version": "0.1.6", - "resolved": "https://registry.npmjs.org/@pinia/colada-devtools/-/colada-devtools-0.1.6.tgz", - "integrity": "sha512-wRW/GxP8SiahC5TRVulQe+5NuIQ7DGtgsO4Xsf9tP2HSTTRD8ac+7pn9vbKxovPdXrgAyAo9PWzk1b+y5MYEUQ==", + "version": "0.4.3", + "resolved": "https://registry.npmjs.org/@pinia/colada-devtools/-/colada-devtools-0.4.3.tgz", + "integrity": "sha512-BMP7WCPsub2JY3wQwkUGxeqMEulx6SgyJ7kTZRXviohmHfsW0K0w5oXpXdkm8luXpjTxyPmkeSayX0sUAHekmQ==", "dev": true, "license": "MIT", "funding": { @@ -2613,7 +2619,6 @@ "integrity": "sha512-UJdblFqXymSBhmZf96BnbisoFIr8ooiiBRMolQgg77Ea+VM37jXw76C2LQr9n8wm9+i/OvlUlW6xSvqwzwqznw==", "devOptional": true, "license": "MIT", - "peer": true, "dependencies": { "undici-types": "~6.21.0" } @@ -2670,7 +2675,6 @@ "integrity": "sha512-VGMpFQGUQWYT9LfnPcX8ouFojyrZ/2w3K5BucvxL/spdNehccKhB4jUyB1yBCXpr2XFm0jkECxgrpXBW2ipoAw==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@typescript-eslint/scope-manager": "8.44.0", "@typescript-eslint/types": "8.44.0", @@ -3198,15 +3202,6 @@ "he": "^1.2.0" } }, - "node_modules/@vue/devtools-api": { - "version": "8.0.2", - "resolved": "https://registry.npmjs.org/@vue/devtools-api/-/devtools-api-8.0.2.tgz", - "integrity": "sha512-RdwsaYoSTumwZ7XOt5yIPP1/T4O0bTs+c5XaEjmUB6f9x+FvDSL9AekxW1vuhK1lmA9TfewpXVt2r5LIax3LHw==", - "license": "MIT", - "dependencies": { - "@vue/devtools-kit": "^8.0.2" - } - }, "node_modules/@vue/devtools-core": { "version": "7.7.7", "resolved": "https://registry.npmjs.org/@vue/devtools-core/-/devtools-core-7.7.7.tgz", @@ -3277,30 +3272,6 @@ "dev": true, "license": "MIT" }, - "node_modules/@vue/devtools-kit": { - "version": "8.0.2", - "resolved": "https://registry.npmjs.org/@vue/devtools-kit/-/devtools-kit-8.0.2.tgz", - "integrity": "sha512-yjZKdEmhJzQqbOh4KFBfTOQjDPMrjjBNCnHBvnTGJX+YLAqoUtY2J+cg7BE+EA8KUv8LprECq04ts75wCoIGWA==", - "license": "MIT", - "dependencies": { - "@vue/devtools-shared": "^8.0.2", - "birpc": "^2.5.0", - "hookable": "^5.5.3", - "mitt": "^3.0.1", - "perfect-debounce": "^2.0.0", - "speakingurl": "^14.0.1", - "superjson": "^2.2.2" - } - }, - "node_modules/@vue/devtools-shared": { - "version": "8.0.2", - "resolved": "https://registry.npmjs.org/@vue/devtools-shared/-/devtools-shared-8.0.2.tgz", - "integrity": "sha512-mLU0QVdy5Lp40PMGSixDw/Kbd6v5dkQXltd2r+mdVQV7iUog2NlZuLxFZApFZ/mObUBDhoCpf0T3zF2FWWdeHw==", - "license": "MIT", - "dependencies": { - "rfdc": "^1.4.1" - } - }, "node_modules/@vue/eslint-config-prettier": { "version": "10.2.0", "resolved": "https://registry.npmjs.org/@vue/eslint-config-prettier/-/eslint-config-prettier-10.2.0.tgz", @@ -3480,7 +3451,6 @@ "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==", "dev": true, "license": "MIT", - "peer": true, "bin": { "acorn": "bin/acorn" }, @@ -3642,9 +3612,9 @@ } }, "node_modules/birpc": { - "version": "2.5.0", - "resolved": "https://registry.npmjs.org/birpc/-/birpc-2.5.0.tgz", - "integrity": "sha512-VSWO/W6nNQdyP520F1mhf+Lc2f8pjGQOtoHHm7Ze8Go1kX7akpVIrtTa0fn+HB0QJEDVacl6aO08YE0PgXfdnQ==", + "version": "2.9.0", + "resolved": "https://registry.npmjs.org/birpc/-/birpc-2.9.0.tgz", + "integrity": "sha512-KrayHS5pBi69Xi9JmvoqrIgYGDkD6mcSe/i6YKi3w5kekCLzrX4+nawcXqrj2tIp50Kw/mT/s3p+GVK0A0sKxw==", "license": "MIT", "funding": { "url": "https://github.com/sponsors/antfu" @@ -3700,7 +3670,6 @@ } ], "license": "MIT", - "peer": true, "dependencies": { "baseline-browser-mapping": "^2.8.2", "caniuse-lite": "^1.0.30001741", @@ -3996,7 +3965,6 @@ "resolved": "https://registry.npmjs.org/date-fns/-/date-fns-2.30.0.tgz", "integrity": "sha512-fnULvOpxnC5/Vg3NCiWelDsLiUc9bRwAPs/+LfTLNvetFCtCTN+yQz15C/fs4AwX1R9K5GLtLfn8QW+dWisaAw==", "license": "MIT", - "peer": true, "dependencies": { "@babel/runtime": "^7.21.0" }, @@ -4320,7 +4288,6 @@ "integrity": "sha512-QePbBFMJFjgmlE+cXAlbHZbHpdFVS2E/6vzCy7aKlebddvl1vadiC4JFV5u/wqTkNUwEV8WrQi257jf5f06hrg==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@eslint-community/eslint-utils": "^4.8.0", "@eslint-community/regexpp": "^4.12.1", @@ -4382,7 +4349,6 @@ "integrity": "sha512-82GZUjRS0p/jganf6q1rEO25VSoHH0hKPCTrgillPjdI/3bgBhAE1QzHrHTizjpRvy6pGAvKjDJtk2pF9NDq8w==", "dev": true, "license": "MIT", - "peer": true, "bin": { "eslint-config-prettier": "bin/cli.js" }, @@ -4430,7 +4396,6 @@ "integrity": "sha512-A5dRYc3eQ5i2rJFBW8J6F69ur/H7YfYg+5SCg6v829FU0BhM4fUTrRVR2d4MdZgzw0ioJEk6otYHEAnoGFqO4A==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@eslint-community/eslint-utils": "^4.4.0", "natural-compare": "^1.4.0", @@ -6420,12 +6385,6 @@ "node": ">= 14.16" } }, - "node_modules/perfect-debounce": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/perfect-debounce/-/perfect-debounce-2.0.0.tgz", - "integrity": "sha512-fkEH/OBiKrqqI/yIgjR92lMfs2K8105zt/VT6+7eTjNwisrsh47CeIED9z58zI7DfKdH3uHAn25ziRZn3kgAow==", - "license": "MIT" - }, "node_modules/picocolors": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", @@ -6463,7 +6422,6 @@ "resolved": "https://registry.npmjs.org/pinia/-/pinia-3.0.3.tgz", "integrity": "sha512-ttXO/InUULUXkMHpTdp9Fj4hLpD/2AoJdmAbAeW2yu1iy1k+pkFekQXw5VpC0/5p51IOR/jDaDRfRWRnMMsGOA==", "license": "MIT", - "peer": true, "dependencies": { "@vue/devtools-api": "^7.7.2" }, @@ -6577,7 +6535,6 @@ "integrity": "sha512-QQtaxnoDJeAkDvDKWCLiwIXkTgRhwYDEQCghU9Z6q03iyek/rxRh/2lC3HB7P8sWT2xC/y5JDctPLBIGzHKbhw==", "dev": true, "license": "MIT", - "peer": true, "bin": { "prettier": "bin/prettier.cjs" }, @@ -6799,7 +6756,6 @@ "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.50.2.tgz", "integrity": "sha512-BgLRGy7tNS9H66aIMASq1qSYbAAJV6Z6WR4QYTvj5FgF15rZ/ympT1uixHXwzbZUBDbkvqUI1KR0fH1FhMaQ9w==", "license": "MIT", - "peer": true, "dependencies": { "@types/estree": "1.0.8" }, @@ -7217,8 +7173,7 @@ "version": "4.1.13", "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-4.1.13.tgz", "integrity": "sha512-i+zidfmTqtwquj4hMEwdjshYYgMbOrPzb9a0M3ZgNa0JMoZeFC6bxZvO8yr8ozS6ix2SDz0+mvryPeBs2TFE+w==", - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/tapable": { "version": "2.2.3", @@ -7317,7 +7272,6 @@ "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", "license": "MIT", - "peer": true, "engines": { "node": ">=12" }, @@ -7477,7 +7431,6 @@ "integrity": "sha512-p1diW6TqL9L07nNxvRMM7hMMw4c5XOo/1ibL4aAIGmSAt9slTE1Xgw5KWuof2uTOvCg9BY7ZRi+GaF+7sfgPeQ==", "devOptional": true, "license": "Apache-2.0", - "peer": true, "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" @@ -7632,7 +7585,6 @@ "resolved": "https://registry.npmjs.org/vite/-/vite-6.4.1.tgz", "integrity": "sha512-+Oxm7q9hDoLMyJOYfUYBuHQo+dkAloi33apOPP56pzj+vsdJDzr+j1NISE5pyaAuKL4A3UD34qd0lx5+kfKp2g==", "license": "MIT", - "peer": true, "dependencies": { "esbuild": "^0.25.0", "fdir": "^6.4.4", @@ -7875,7 +7827,6 @@ "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", "license": "MIT", - "peer": true, "engines": { "node": ">=12" }, @@ -7889,7 +7840,6 @@ "integrity": "sha512-LUCP5ev3GURDysTWiP47wRRUpLKMOfPh+yKTx3kVIEiu5KOMeqzpnYNsKyOoVrULivR8tLcks4+lga33Whn90A==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@types/chai": "^5.2.2", "@vitest/expect": "3.2.4", @@ -7982,7 +7932,6 @@ "resolved": "https://registry.npmjs.org/vue/-/vue-3.5.21.tgz", "integrity": "sha512-xxf9rum9KtOdwdRkiApWL+9hZEMWE90FHh8yS1+KJAiWYh+iGWV1FquPjoO9VUHQ+VIhsCXNNyZ5Sf4++RVZBA==", "license": "MIT", - "peer": true, "dependencies": { "@vue/compiler-dom": "3.5.21", "@vue/compiler-sfc": "3.5.21", diff --git a/frontend/package.json b/frontend/package.json index 65c0171eb..c9390c277 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -25,8 +25,9 @@ "@fortawesome/vue-fontawesome": "^3.0.8", "@logto/vue": "^3.0.8", "@nethesis/nethesis-light-svg-icons": "github:nethesis/Font-Awesome#ns-light", - "@nethesis/vue-components": "^3.4.0", - "@pinia/colada": "^0.17.6", + "@nethesis/nethesis-solid-svg-icons": "github:nethesis/Font-Awesome#ns-solid", + "@nethesis/vue-components": "^3.6.0", + "@pinia/colada": "^0.21.5", "@tailwindcss/vite": "^4.1.10", "@vueuse/core": "^13.4.0", "axios": "^1.11.0", @@ -40,7 +41,7 @@ "vue-router": "^4.5.0" }, "devDependencies": { - "@pinia/colada-devtools": "^0.1.5", + "@pinia/colada-devtools": "^0.4.3", "@tsconfig/node22": "^22.0.1", "@types/lodash": "^4.17.18", "@types/node": "^22.14.0", diff --git a/frontend/src/App.vue b/frontend/src/App.vue index 98c43e10f..527e61b58 100644 --- a/frontend/src/App.vue +++ b/frontend/src/App.vue @@ -6,13 +6,13 @@ diff --git a/frontend/src/components/DeleteObjectModal.vue b/frontend/src/components/DeleteObjectModal.vue new file mode 100644 index 000000000..b91454370 --- /dev/null +++ b/frontend/src/components/DeleteObjectModal.vue @@ -0,0 +1,106 @@ + + + + + diff --git a/frontend/src/components/systems/SystemNotesModal.vue b/frontend/src/components/NotesModal.vue similarity index 83% rename from frontend/src/components/systems/SystemNotesModal.vue rename to frontend/src/components/NotesModal.vue index 1d9432b83..c94b1fc21 100644 --- a/frontend/src/components/systems/SystemNotesModal.vue +++ b/frontend/src/components/NotesModal.vue @@ -17,7 +17,7 @@ const emit = defineEmits(['close']) diff --git a/frontend/src/components/account/ProfilePanel.vue b/frontend/src/components/account/ProfilePanel.vue index 8a6670349..701101d73 100644 --- a/frontend/src/components/account/ProfilePanel.vue +++ b/frontend/src/components/account/ProfilePanel.vue @@ -9,7 +9,6 @@ import { getValidationIssues, isValidationError } from '@/lib/validation' import { useLoginStore } from '@/stores/login' import { useNotificationsStore } from '@/stores/notifications' import { - NeBadge, NeButton, NeFormItemLabel, NeInlineNotification, @@ -21,8 +20,8 @@ import type { AxiosError } from 'axios' import { ref, useTemplateRef, watch, type ShallowRef } from 'vue' import { useI18n } from 'vue-i18n' import * as v from 'valibot' -import { USERS_KEY } from '@/lib/users' -import { normalize } from '@/lib/common' +import { USERS_KEY } from '@/lib/users/users' +import UserRoleBadge from '../users/UserRoleBadge.vue' const { t } = useI18n() const loginStore = useLoginStore() @@ -171,14 +170,11 @@ function validate(profile: ProfileInfo): boolean { {{ $t('users.roles') }}
- + :role="role" + />
diff --git a/frontend/src/components/applications/ApplicationInfoCard.vue b/frontend/src/components/applications/ApplicationInfoCard.vue new file mode 100644 index 000000000..8009bf7b7 --- /dev/null +++ b/frontend/src/components/applications/ApplicationInfoCard.vue @@ -0,0 +1,218 @@ + + + + + diff --git a/frontend/src/components/applications/ApplicationSystemCard.vue b/frontend/src/components/applications/ApplicationSystemCard.vue new file mode 100644 index 000000000..a8492e2d4 --- /dev/null +++ b/frontend/src/components/applications/ApplicationSystemCard.vue @@ -0,0 +1,70 @@ + + + + + diff --git a/frontend/src/components/applications/ApplicationsTable.vue b/frontend/src/components/applications/ApplicationsTable.vue new file mode 100644 index 000000000..1c11dcb5c --- /dev/null +++ b/frontend/src/components/applications/ApplicationsTable.vue @@ -0,0 +1,498 @@ + + + + + diff --git a/frontend/src/components/applications/AssignOrganizationDrawer.vue b/frontend/src/components/applications/AssignOrganizationDrawer.vue new file mode 100644 index 000000000..e84a5a0e5 --- /dev/null +++ b/frontend/src/components/applications/AssignOrganizationDrawer.vue @@ -0,0 +1,200 @@ + + + + + diff --git a/frontend/src/components/applications/OrganizationLink.vue b/frontend/src/components/applications/OrganizationLink.vue new file mode 100644 index 000000000..6464eab26 --- /dev/null +++ b/frontend/src/components/applications/OrganizationLink.vue @@ -0,0 +1,53 @@ + + + + + diff --git a/frontend/src/components/applications/SetNotesDrawer.vue b/frontend/src/components/applications/SetNotesDrawer.vue new file mode 100644 index 000000000..34fa1e726 --- /dev/null +++ b/frontend/src/components/applications/SetNotesDrawer.vue @@ -0,0 +1,162 @@ + + + + + diff --git a/frontend/src/components/customers/CreateOrEditCustomerDrawer.vue b/frontend/src/components/customers/CreateOrEditCustomerDrawer.vue index 2c5f6aada..341c7b60b 100644 --- a/frontend/src/components/customers/CreateOrEditCustomerDrawer.vue +++ b/frontend/src/components/customers/CreateOrEditCustomerDrawer.vue @@ -10,24 +10,32 @@ import { NeTextInput, focusElement, NeInlineNotification, + NeTextArea, + NeCombobox, + type NeComboboxOption, + getPreference, } from '@nethesis/vue-components' -import { computed, ref, useTemplateRef, watch, type ShallowRef } from 'vue' +import { computed, ref, useTemplateRef, type ShallowRef } from 'vue' import { CreateCustomerSchema, CUSTOMERS_KEY, CUSTOMERS_TOTAL_KEY, - CustomerSchema, + EditCustomerSchema, postCustomer, putCustomer, type CreateCustomer, type Customer, -} from '@/lib/customers' + type EditCustomer, +} from '@/lib/organizations/customers' import * as v from 'valibot' import { useMutation, useQueryCache } from '@pinia/colada' import { useNotificationsStore } from '@/stores/notifications' import { useI18n } from 'vue-i18n' import { getValidationIssues, isValidationError } from '@/lib/validation' import type { AxiosError } from 'axios' +import { getCommonLanguagesOptions } from '@/lib/locale' +import { getBrowserLocale } from '@/i18n' +import { useLoginStore } from '@/stores/login' const { isShown = false, currentCustomer = undefined } = defineProps<{ isShown: boolean @@ -39,6 +47,7 @@ const emit = defineEmits(['close']) const { t } = useI18n() const queryCache = useQueryCache() const notificationsStore = useNotificationsStore() +const loginStore = useLoginStore() const { mutate: createCustomerMutate, @@ -79,7 +88,7 @@ const { reset: editCustomerReset, error: editCustomerError, } = useMutation({ - mutation: (customer: Customer) => { + mutation: (customer: EditCustomer) => { return putCustomer(customer) }, onSuccess(data, vars) { @@ -104,43 +113,77 @@ const { const name = ref('') const nameRef = useTemplateRef('nameRef') -const description = ref('') -const descriptionRef = useTemplateRef('descriptionRef') const vatNumber = ref('') const vatNumberRef = useTemplateRef('vatNumberRef') +const address = ref('') +const addressRef = useTemplateRef('addressRef') +const city = ref('') +const cityRef = useTemplateRef('cityRef') +const mainContact = ref('') +const mainContactRef = useTemplateRef('mainContactRef') +const email = ref('') +const emailRef = useTemplateRef('emailRef') +const phone = ref('') +const phoneRef = useTemplateRef('phoneRef') +const language = ref('it') +const languageRef = useTemplateRef('languageRef') +const notes = ref('') +const notesRef = useTemplateRef('notesRef') const validationIssues = ref>({}) const fieldRefs: Record>> = { name: nameRef, - description: descriptionRef, custom_data_vat: vatNumberRef, + custom_data_address: addressRef, + custom_data_city: cityRef, + custom_data_main_contact: mainContactRef, + custom_data_email: emailRef, + custom_data_phone: phoneRef, + custom_data_language: languageRef, + custom_data_notes: notesRef, } const saving = computed(() => { return createCustomerLoading.value || editCustomerLoading.value }) -watch( - () => isShown, - () => { - if (isShown) { - clearErrors() - focusElement(nameRef) - - if (currentCustomer) { - // editing customer - name.value = currentCustomer.name - description.value = currentCustomer.description || '' - vatNumber.value = currentCustomer.custom_data?.vat || '' - } else { - // creating customer, reset form to defaults - name.value = '' - description.value = '' - vatNumber.value = '' - } - } - }, -) +const languageOptions = computed((): NeComboboxOption[] => { + if (loginStore.userInfo?.email && getPreference('locale', loginStore.userInfo.email)) { + const locale = getPreference('locale', loginStore.userInfo.email) + return getCommonLanguagesOptions(locale) + } else { + return getCommonLanguagesOptions(getBrowserLocale()) + } +}) + +function onShow() { + clearErrors() + focusElement(nameRef) + + if (currentCustomer) { + // editing customer + name.value = currentCustomer.name + vatNumber.value = currentCustomer.custom_data?.vat || '' + address.value = currentCustomer.custom_data?.address || '' + city.value = currentCustomer.custom_data?.city || '' + mainContact.value = currentCustomer.custom_data?.main_contact || '' + email.value = currentCustomer.custom_data?.email || '' + phone.value = currentCustomer.custom_data?.phone || '' + language.value = currentCustomer.custom_data?.language || '' + notes.value = currentCustomer.custom_data?.notes || '' + } else { + // creating customer, reset form to defaults + name.value = '' + vatNumber.value = '' + address.value = '' + city.value = '' + mainContact.value = '' + email.value = '' + phone.value = '' + language.value = 'it' + notes.value = '' + } +} function closeDrawer() { emit('close') @@ -183,9 +226,9 @@ function validateCreate(customer: CreateCustomer): boolean { } } -function validateEdit(customer: Customer): boolean { +function validateEdit(customer: EditCustomer): boolean { validationIssues.value = {} - const validation = v.safeParse(CustomerSchema, customer) + const validation = v.safeParse(EditCustomerSchema, customer) if (validation.success) { // no validation issues @@ -219,18 +262,24 @@ async function saveCustomer() { const customer = { name: name.value, - description: description.value, custom_data: { vat: vatNumber.value, + address: address.value, + city: city.value, + main_contact: mainContact.value, + email: email.value, + phone: phone.value, + language: language.value, + notes: notes.value, }, } - if (currentCustomer?.id) { + if (currentCustomer?.logto_id) { // editing customer - const customerToEdit: Customer = { + const customerToEdit: EditCustomer = { ...customer, - id: currentCustomer.id, + logto_id: currentCustomer.logto_id, } const isValidationOk = validateEdit(customerToEdit) @@ -256,6 +305,7 @@ async function saveCustomer() { :is-shown="isShown" :title="currentCustomer ? $t('customers.edit_customer') : $t('customers.create_customer')" :close-aria-label="$t('common.shell.close_side_drawer')" + @show="onShow" @close="closeDrawer" >
@@ -269,17 +319,6 @@ async function saveCustomer() { :invalid-message="validationIssues.name?.[0] ? $t(validationIssues.name[0]) : ''" :disabled="saving" /> - - + + + + + + + + + + + + + + + + + + diff --git a/frontend/src/components/customers/CustomersTable.vue b/frontend/src/components/customers/CustomersTable.vue index 5b38bbb86..db72e5191 100644 --- a/frontend/src/components/customers/CustomersTable.vue +++ b/frontend/src/components/customers/CustomersTable.vue @@ -4,13 +4,19 @@ --> diff --git a/frontend/src/components/customers/DeleteCustomerModal.vue b/frontend/src/components/customers/DeleteCustomerModal.vue index 5e57368c9..745546cac 100644 --- a/frontend/src/components/customers/DeleteCustomerModal.vue +++ b/frontend/src/components/customers/DeleteCustomerModal.vue @@ -4,12 +4,16 @@ --> diff --git a/frontend/src/components/customers/DestroyCustomerModal.vue b/frontend/src/components/customers/DestroyCustomerModal.vue new file mode 100644 index 000000000..c845c7fcd --- /dev/null +++ b/frontend/src/components/customers/DestroyCustomerModal.vue @@ -0,0 +1,81 @@ + + + + + diff --git a/frontend/src/components/customers/ReactivateCustomerModal.vue b/frontend/src/components/customers/ReactivateCustomerModal.vue new file mode 100644 index 000000000..402738800 --- /dev/null +++ b/frontend/src/components/customers/ReactivateCustomerModal.vue @@ -0,0 +1,93 @@ + + + + + diff --git a/frontend/src/components/customers/RestoreCustomerModal.vue b/frontend/src/components/customers/RestoreCustomerModal.vue new file mode 100644 index 000000000..7b75d4c4c --- /dev/null +++ b/frontend/src/components/customers/RestoreCustomerModal.vue @@ -0,0 +1,93 @@ + + + + + diff --git a/frontend/src/components/customers/SuspendCustomerModal.vue b/frontend/src/components/customers/SuspendCustomerModal.vue new file mode 100644 index 000000000..1d7c8832b --- /dev/null +++ b/frontend/src/components/customers/SuspendCustomerModal.vue @@ -0,0 +1,94 @@ + + + + + diff --git a/frontend/src/components/dashboard/ApplicationsCounterCard.vue b/frontend/src/components/dashboard/ApplicationsCounterCard.vue new file mode 100644 index 000000000..24ee03942 --- /dev/null +++ b/frontend/src/components/dashboard/ApplicationsCounterCard.vue @@ -0,0 +1,35 @@ + + + + + diff --git a/frontend/src/components/dashboard/CustomersCounterCard.vue b/frontend/src/components/dashboard/CustomersCounterCard.vue index 927b3f609..db5013550 100644 --- a/frontend/src/components/dashboard/CustomersCounterCard.vue +++ b/frontend/src/components/dashboard/CustomersCounterCard.vue @@ -7,7 +7,7 @@ import { faBuilding } from '@fortawesome/free-solid-svg-icons' import { useQuery } from '@pinia/colada' import { useLoginStore } from '@/stores/login' -import { CUSTOMERS_TOTAL_KEY, getCustomersTotal } from '@/lib/customers' +import { CUSTOMERS_TOTAL_KEY, getCustomersTotal } from '@/lib/organizations/customers' import CounterCard from '../CounterCard.vue' const loginStore = useLoginStore() diff --git a/frontend/src/components/dashboard/DistributorsCounterCard.vue b/frontend/src/components/dashboard/DistributorsCounterCard.vue index 7e651acf1..608a62214 100644 --- a/frontend/src/components/dashboard/DistributorsCounterCard.vue +++ b/frontend/src/components/dashboard/DistributorsCounterCard.vue @@ -7,7 +7,7 @@ import { faGlobe } from '@fortawesome/free-solid-svg-icons' import { useQuery } from '@pinia/colada' import { useLoginStore } from '@/stores/login' -import { DISTRIBUTORS_TOTAL_KEY, getDistributorsTotal } from '@/lib/distributors' +import { DISTRIBUTORS_TOTAL_KEY, getDistributorsTotal } from '@/lib/organizations/distributors' import CounterCard from '../CounterCard.vue' const loginStore = useLoginStore() diff --git a/frontend/src/components/dashboard/ResellersCounterCard.vue b/frontend/src/components/dashboard/ResellersCounterCard.vue index 1e213285b..71652362f 100644 --- a/frontend/src/components/dashboard/ResellersCounterCard.vue +++ b/frontend/src/components/dashboard/ResellersCounterCard.vue @@ -7,7 +7,7 @@ import { faCity } from '@fortawesome/free-solid-svg-icons' import { useQuery } from '@pinia/colada' import { useLoginStore } from '@/stores/login' -import { getResellersTotal, RESELLERS_TOTAL_KEY } from '@/lib/resellers' +import { getResellersTotal, RESELLERS_TOTAL_KEY } from '@/lib/organizations/resellers' import CounterCard from '../CounterCard.vue' const loginStore = useLoginStore() diff --git a/frontend/src/components/dashboard/UsersCounterCard.vue b/frontend/src/components/dashboard/UsersCounterCard.vue index 00ee42cf6..4088ddd34 100644 --- a/frontend/src/components/dashboard/UsersCounterCard.vue +++ b/frontend/src/components/dashboard/UsersCounterCard.vue @@ -8,7 +8,7 @@ import { faUserGroup } from '@fortawesome/free-solid-svg-icons' import { useQuery } from '@pinia/colada' import { useLoginStore } from '@/stores/login' import CounterCard from '../CounterCard.vue' -import { getUsersTotal, USERS_TOTAL_KEY } from '@/lib/users' +import { getUsersTotal, USERS_TOTAL_KEY } from '@/lib/users/users' const loginStore = useLoginStore() diff --git a/frontend/src/components/distributors/CreateOrEditDistributorDrawer.vue b/frontend/src/components/distributors/CreateOrEditDistributorDrawer.vue index 1e7771b98..1078997d9 100644 --- a/frontend/src/components/distributors/CreateOrEditDistributorDrawer.vue +++ b/frontend/src/components/distributors/CreateOrEditDistributorDrawer.vue @@ -10,24 +10,32 @@ import { NeTextInput, focusElement, NeInlineNotification, + NeTextArea, + NeCombobox, + type NeComboboxOption, + getPreference, } from '@nethesis/vue-components' -import { computed, ref, useTemplateRef, watch, type ShallowRef } from 'vue' +import { computed, ref, useTemplateRef, type ShallowRef } from 'vue' import { CreateDistributorSchema, DISTRIBUTORS_KEY, DISTRIBUTORS_TOTAL_KEY, - DistributorSchema, + EditDistributorSchema, postDistributor, putDistributor, type CreateDistributor, type Distributor, -} from '@/lib/distributors' + type EditDistributor, +} from '@/lib/organizations/distributors' import * as v from 'valibot' import { useMutation, useQueryCache } from '@pinia/colada' import { useNotificationsStore } from '@/stores/notifications' import { useI18n } from 'vue-i18n' import { getValidationIssues, isValidationError } from '@/lib/validation' import type { AxiosError } from 'axios' +import { getCommonLanguagesOptions } from '@/lib/locale' +import { getBrowserLocale } from '@/i18n' +import { useLoginStore } from '@/stores/login' const { isShown = false, currentDistributor = undefined } = defineProps<{ isShown: boolean @@ -39,6 +47,7 @@ const emit = defineEmits(['close']) const { t } = useI18n() const queryCache = useQueryCache() const notificationsStore = useNotificationsStore() +const loginStore = useLoginStore() const { mutate: createDistributorMutate, @@ -79,7 +88,7 @@ const { reset: editDistributorReset, error: editDistributorError, } = useMutation({ - mutation: (distributor: Distributor) => { + mutation: (distributor: EditDistributor) => { return putDistributor(distributor) }, onSuccess(data, vars) { @@ -106,43 +115,77 @@ const { const name = ref('') const nameRef = useTemplateRef('nameRef') -const description = ref('') -const descriptionRef = useTemplateRef('descriptionRef') const vatNumber = ref('') const vatNumberRef = useTemplateRef('vatNumberRef') +const address = ref('') +const addressRef = useTemplateRef('addressRef') +const city = ref('') +const cityRef = useTemplateRef('cityRef') +const mainContact = ref('') +const mainContactRef = useTemplateRef('mainContactRef') +const email = ref('') +const emailRef = useTemplateRef('emailRef') +const phone = ref('') +const phoneRef = useTemplateRef('phoneRef') +const language = ref('it') +const languageRef = useTemplateRef('languageRef') +const notes = ref('') +const notesRef = useTemplateRef('notesRef') const validationIssues = ref>({}) const fieldRefs: Record>> = { name: nameRef, - description: descriptionRef, custom_data_vat: vatNumberRef, + custom_data_address: addressRef, + custom_data_city: cityRef, + custom_data_main_contact: mainContactRef, + custom_data_email: emailRef, + custom_data_phone: phoneRef, + custom_data_language: languageRef, + custom_data_notes: notesRef, } const saving = computed(() => { return createDistributorLoading.value || editDistributorLoading.value }) -watch( - () => isShown, - () => { - if (isShown) { - clearErrors() - focusElement(nameRef) - - if (currentDistributor) { - // editing distributor - name.value = currentDistributor.name - description.value = currentDistributor.description || '' - vatNumber.value = currentDistributor.custom_data?.vat || '' - } else { - // creating distributor, reset form to defaults - name.value = '' - description.value = '' - vatNumber.value = '' - } - } - }, -) +const languageOptions = computed((): NeComboboxOption[] => { + if (loginStore.userInfo?.email && getPreference('locale', loginStore.userInfo.email)) { + const locale = getPreference('locale', loginStore.userInfo.email) + return getCommonLanguagesOptions(locale) + } else { + return getCommonLanguagesOptions(getBrowserLocale()) + } +}) + +function onShow() { + clearErrors() + focusElement(nameRef) + + if (currentDistributor) { + // editing distributor + name.value = currentDistributor.name + vatNumber.value = currentDistributor.custom_data?.vat || '' + address.value = currentDistributor.custom_data?.address || '' + city.value = currentDistributor.custom_data?.city || '' + mainContact.value = currentDistributor.custom_data?.main_contact || '' + email.value = currentDistributor.custom_data?.email || '' + phone.value = currentDistributor.custom_data?.phone || '' + language.value = currentDistributor.custom_data?.language || '' + notes.value = currentDistributor.custom_data?.notes || '' + } else { + // creating distributor, reset form to defaults + name.value = '' + vatNumber.value = '' + address.value = '' + city.value = '' + mainContact.value = '' + email.value = '' + phone.value = '' + language.value = 'it' + notes.value = '' + } +} function closeDrawer() { emit('close') @@ -185,9 +228,9 @@ function validateCreate(distributor: CreateDistributor): boolean { } } -function validateEdit(distributor: Distributor): boolean { +function validateEdit(distributor: EditDistributor): boolean { validationIssues.value = {} - const validation = v.safeParse(DistributorSchema, distributor) + const validation = v.safeParse(EditDistributorSchema, distributor) if (validation.success) { // no validation issues @@ -221,18 +264,24 @@ async function saveDistributor() { const distributor = { name: name.value, - description: description.value, custom_data: { vat: vatNumber.value, + address: address.value, + city: city.value, + main_contact: mainContact.value, + email: email.value, + phone: phone.value, + language: language.value, + notes: notes.value, }, } - if (currentDistributor?.id) { + if (currentDistributor?.logto_id) { // editing distributor - const distributorToEdit: Distributor = { + const distributorToEdit: EditDistributor = { ...distributor, - id: currentDistributor.id, + logto_id: currentDistributor.logto_id, } const isValidationOk = validateEdit(distributorToEdit) @@ -262,6 +311,7 @@ async function saveDistributor() { : $t('distributors.create_distributor') " :close-aria-label="$t('common.shell.close_side_drawer')" + @show="onShow" @close="closeDrawer" > @@ -275,17 +325,6 @@ async function saveDistributor() { :invalid-message="validationIssues.name?.[0] ? $t(validationIssues.name[0]) : ''" :disabled="saving" /> - - + + + + + + + + + + + + + + diff --git a/frontend/src/components/distributors/DestroyDistributorModal.vue b/frontend/src/components/distributors/DestroyDistributorModal.vue new file mode 100644 index 000000000..cafa5b0ef --- /dev/null +++ b/frontend/src/components/distributors/DestroyDistributorModal.vue @@ -0,0 +1,83 @@ + + + + + diff --git a/frontend/src/components/distributors/DistributorInfoCard.vue b/frontend/src/components/distributors/DistributorInfoCard.vue new file mode 100644 index 000000000..0bf61e279 --- /dev/null +++ b/frontend/src/components/distributors/DistributorInfoCard.vue @@ -0,0 +1,179 @@ + + + + + diff --git a/frontend/src/components/distributors/DistributorsTable.vue b/frontend/src/components/distributors/DistributorsTable.vue index 3fa113afa..23d1c8a59 100644 --- a/frontend/src/components/distributors/DistributorsTable.vue +++ b/frontend/src/components/distributors/DistributorsTable.vue @@ -4,13 +4,21 @@ --> diff --git a/frontend/src/components/distributors/ReactivateDistributorModal.vue b/frontend/src/components/distributors/ReactivateDistributorModal.vue new file mode 100644 index 000000000..c1d9684dc --- /dev/null +++ b/frontend/src/components/distributors/ReactivateDistributorModal.vue @@ -0,0 +1,93 @@ + + + + + diff --git a/frontend/src/components/distributors/RestoreDistributorModal.vue b/frontend/src/components/distributors/RestoreDistributorModal.vue new file mode 100644 index 000000000..f3df32119 --- /dev/null +++ b/frontend/src/components/distributors/RestoreDistributorModal.vue @@ -0,0 +1,93 @@ + + + + + diff --git a/frontend/src/components/distributors/SuspendDistributorModal.vue b/frontend/src/components/distributors/SuspendDistributorModal.vue new file mode 100644 index 000000000..e0945ef7a --- /dev/null +++ b/frontend/src/components/distributors/SuspendDistributorModal.vue @@ -0,0 +1,94 @@ + + + + + diff --git a/frontend/src/components/organizations/OrganizationApplicationsCard.vue b/frontend/src/components/organizations/OrganizationApplicationsCard.vue new file mode 100644 index 000000000..f5d5c17b8 --- /dev/null +++ b/frontend/src/components/organizations/OrganizationApplicationsCard.vue @@ -0,0 +1,98 @@ + + + + + diff --git a/frontend/src/components/OrganizationIcon.vue b/frontend/src/components/organizations/OrganizationIcon.vue similarity index 94% rename from frontend/src/components/OrganizationIcon.vue rename to frontend/src/components/organizations/OrganizationIcon.vue index 64632dff6..1a9d5abde 100644 --- a/frontend/src/components/OrganizationIcon.vue +++ b/frontend/src/components/organizations/OrganizationIcon.vue @@ -3,7 +3,7 @@ SPDX-License-Identifier: GPL-3.0-or-later --> + + diff --git a/frontend/src/components/resellers/CreateOrEditResellerDrawer.vue b/frontend/src/components/resellers/CreateOrEditResellerDrawer.vue index ac1ed0187..46b825ee5 100644 --- a/frontend/src/components/resellers/CreateOrEditResellerDrawer.vue +++ b/frontend/src/components/resellers/CreateOrEditResellerDrawer.vue @@ -10,24 +10,32 @@ import { NeTextInput, focusElement, NeInlineNotification, + NeTextArea, + NeCombobox, + type NeComboboxOption, + getPreference, } from '@nethesis/vue-components' -import { computed, ref, useTemplateRef, watch, type ShallowRef } from 'vue' +import { computed, ref, useTemplateRef, type ShallowRef } from 'vue' import { CreateResellerSchema, RESELLERS_KEY, RESELLERS_TOTAL_KEY, - ResellerSchema, + EditResellerSchema, postReseller, putReseller, type CreateReseller, type Reseller, -} from '@/lib/resellers' + type EditReseller, +} from '@/lib/organizations/resellers' import * as v from 'valibot' import { useMutation, useQueryCache } from '@pinia/colada' import { useNotificationsStore } from '@/stores/notifications' import { useI18n } from 'vue-i18n' import { getValidationIssues, isValidationError } from '@/lib/validation' import type { AxiosError } from 'axios' +import { getCommonLanguagesOptions } from '@/lib/locale' +import { getBrowserLocale } from '@/i18n' +import { useLoginStore } from '@/stores/login' const { isShown = false, currentReseller = undefined } = defineProps<{ isShown: boolean @@ -39,6 +47,7 @@ const emit = defineEmits(['close']) const { t } = useI18n() const queryCache = useQueryCache() const notificationsStore = useNotificationsStore() +const loginStore = useLoginStore() const { mutate: createResellerMutate, @@ -79,7 +88,7 @@ const { reset: editResellerReset, error: editResellerError, } = useMutation({ - mutation: (reseller: Reseller) => { + mutation: (reseller: EditReseller) => { return putReseller(reseller) }, onSuccess(data, vars) { @@ -104,43 +113,77 @@ const { const name = ref('') const nameRef = useTemplateRef('nameRef') -const description = ref('') -const descriptionRef = useTemplateRef('descriptionRef') const vatNumber = ref('') const vatNumberRef = useTemplateRef('vatNumberRef') +const address = ref('') +const addressRef = useTemplateRef('addressRef') +const city = ref('') +const cityRef = useTemplateRef('cityRef') +const mainContact = ref('') +const mainContactRef = useTemplateRef('mainContactRef') +const email = ref('') +const emailRef = useTemplateRef('emailRef') +const phone = ref('') +const phoneRef = useTemplateRef('phoneRef') +const language = ref('it') +const languageRef = useTemplateRef('languageRef') +const notes = ref('') +const notesRef = useTemplateRef('notesRef') const validationIssues = ref>({}) const fieldRefs: Record>> = { name: nameRef, - description: descriptionRef, custom_data_vat: vatNumberRef, + custom_data_address: addressRef, + custom_data_city: cityRef, + custom_data_main_contact: mainContactRef, + custom_data_email: emailRef, + custom_data_phone: phoneRef, + custom_data_language: languageRef, + custom_data_notes: notesRef, } const saving = computed(() => { return createResellerLoading.value || editResellerLoading.value }) -watch( - () => isShown, - () => { - if (isShown) { - clearErrors() - focusElement(nameRef) - - if (currentReseller) { - // editing reseller - name.value = currentReseller.name - description.value = currentReseller.description || '' - vatNumber.value = currentReseller.custom_data?.vat || '' - } else { - // creating reseller, reset form to defaults - name.value = '' - description.value = '' - vatNumber.value = '' - } - } - }, -) +const languageOptions = computed((): NeComboboxOption[] => { + if (loginStore.userInfo?.email && getPreference('locale', loginStore.userInfo.email)) { + const locale = getPreference('locale', loginStore.userInfo.email) + return getCommonLanguagesOptions(locale) + } else { + return getCommonLanguagesOptions(getBrowserLocale()) + } +}) + +function onShow() { + clearErrors() + focusElement(nameRef) + + if (currentReseller) { + // editing reseller + name.value = currentReseller.name + vatNumber.value = currentReseller.custom_data?.vat || '' + address.value = currentReseller.custom_data?.address || '' + city.value = currentReseller.custom_data?.city || '' + mainContact.value = currentReseller.custom_data?.main_contact || '' + email.value = currentReseller.custom_data?.email || '' + phone.value = currentReseller.custom_data?.phone || '' + language.value = currentReseller.custom_data?.language || '' + notes.value = currentReseller.custom_data?.notes || '' + } else { + // creating reseller, reset form to defaults + name.value = '' + vatNumber.value = '' + address.value = '' + city.value = '' + mainContact.value = '' + email.value = '' + phone.value = '' + language.value = 'it' + notes.value = '' + } +} function closeDrawer() { emit('close') @@ -183,9 +226,9 @@ function validateCreate(reseller: CreateReseller): boolean { } } -function validateEdit(reseller: Reseller): boolean { +function validateEdit(reseller: EditReseller): boolean { validationIssues.value = {} - const validation = v.safeParse(ResellerSchema, reseller) + const validation = v.safeParse(EditResellerSchema, reseller) if (validation.success) { // no validation issues @@ -219,18 +262,24 @@ async function saveReseller() { const reseller = { name: name.value, - description: description.value, custom_data: { vat: vatNumber.value, + address: address.value, + city: city.value, + main_contact: mainContact.value, + email: email.value, + phone: phone.value, + language: language.value, + notes: notes.value, }, } - if (currentReseller?.id) { + if (currentReseller?.logto_id) { // editing reseller - const resellerToEdit: Reseller = { + const resellerToEdit: EditReseller = { ...reseller, - id: currentReseller.id, + logto_id: currentReseller.logto_id, } const isValidationOk = validateEdit(resellerToEdit) @@ -242,7 +291,6 @@ async function saveReseller() { // creating reseller const resellerToCreate: CreateReseller = reseller - const isValidationOk = validateCreate(resellerToCreate) if (!isValidationOk) { return @@ -257,6 +305,7 @@ async function saveReseller() { :is-shown="isShown" :title="currentReseller ? $t('resellers.edit_reseller') : $t('resellers.create_reseller')" :close-aria-label="$t('common.shell.close_side_drawer')" + @show="onShow" @close="closeDrawer" > @@ -270,17 +319,6 @@ async function saveReseller() { :invalid-message="validationIssues.name?.[0] ? $t(validationIssues.name[0]) : ''" :disabled="saving" /> - - + + + + + + + + + + + + + + diff --git a/frontend/src/components/resellers/DestroyResellerModal.vue b/frontend/src/components/resellers/DestroyResellerModal.vue new file mode 100644 index 000000000..98a06b192 --- /dev/null +++ b/frontend/src/components/resellers/DestroyResellerModal.vue @@ -0,0 +1,81 @@ + + + + + diff --git a/frontend/src/components/resellers/ReactivateResellerModal.vue b/frontend/src/components/resellers/ReactivateResellerModal.vue new file mode 100644 index 000000000..50d114e5f --- /dev/null +++ b/frontend/src/components/resellers/ReactivateResellerModal.vue @@ -0,0 +1,93 @@ + + + + + diff --git a/frontend/src/components/resellers/ResellerInfoCard.vue b/frontend/src/components/resellers/ResellerInfoCard.vue new file mode 100644 index 000000000..a09457ecc --- /dev/null +++ b/frontend/src/components/resellers/ResellerInfoCard.vue @@ -0,0 +1,179 @@ + + + + + diff --git a/frontend/src/components/resellers/ResellersTable.vue b/frontend/src/components/resellers/ResellersTable.vue index a3458deef..ebc3d4373 100644 --- a/frontend/src/components/resellers/ResellersTable.vue +++ b/frontend/src/components/resellers/ResellersTable.vue @@ -4,13 +4,20 @@ --> diff --git a/frontend/src/components/resellers/RestoreResellerModal.vue b/frontend/src/components/resellers/RestoreResellerModal.vue new file mode 100644 index 000000000..d65b4a2a0 --- /dev/null +++ b/frontend/src/components/resellers/RestoreResellerModal.vue @@ -0,0 +1,93 @@ + + + + + diff --git a/frontend/src/components/resellers/SuspendResellerModal.vue b/frontend/src/components/resellers/SuspendResellerModal.vue new file mode 100644 index 000000000..64d2132c2 --- /dev/null +++ b/frontend/src/components/resellers/SuspendResellerModal.vue @@ -0,0 +1,94 @@ + + + + + diff --git a/frontend/src/components/AppShell.vue b/frontend/src/components/shell/AppShell.vue similarity index 100% rename from frontend/src/components/AppShell.vue rename to frontend/src/components/shell/AppShell.vue diff --git a/frontend/src/components/LoggedUserCard.vue b/frontend/src/components/shell/LoggedUserCard.vue similarity index 78% rename from frontend/src/components/LoggedUserCard.vue rename to frontend/src/components/shell/LoggedUserCard.vue index 80767b4ae..3fdbfacc7 100644 --- a/frontend/src/components/LoggedUserCard.vue +++ b/frontend/src/components/shell/LoggedUserCard.vue @@ -4,12 +4,12 @@ --> @@ -35,13 +35,11 @@ const loginStore = useLoginStore()
{{ loginStore.userInfo.name }}
- - {{ $t(`user_roles.${normalize(role)}`) }} - + :role="role" + />
diff --git a/frontend/src/components/NotificationDrawer.vue b/frontend/src/components/shell/NotificationDrawer.vue similarity index 100% rename from frontend/src/components/NotificationDrawer.vue rename to frontend/src/components/shell/NotificationDrawer.vue diff --git a/frontend/src/components/SideMenu.vue b/frontend/src/components/shell/SideMenu.vue similarity index 95% rename from frontend/src/components/SideMenu.vue rename to frontend/src/components/shell/SideMenu.vue index e1e0be2ad..c99102733 100644 --- a/frontend/src/components/SideMenu.vue +++ b/frontend/src/components/shell/SideMenu.vue @@ -22,6 +22,7 @@ import { faUserGroup as fasUserGroup, faServer as fasServer, } from '@fortawesome/free-solid-svg-icons' +import { faGridOne as fasGridOne } from '@nethesis/nethesis-solid-svg-icons' import { faHouse as falHouse, faGlobe as falGlobe, @@ -29,8 +30,10 @@ import { faBuilding as falBuilding, faUserGroup as falUserGroup, faServer as falServer, + faGrid2 as falGrid2, } from '@nethesis/nethesis-light-svg-icons' import { + canReadApplications, canReadCustomers, canReadDistributors, canReadResellers, @@ -70,6 +73,15 @@ const navigation = computed(() => { }) } + if (canReadApplications()) { + menuItems.push({ + name: 'applications.title', + to: 'applications', + solidIcon: fasGridOne, + lightIcon: falGrid2, + }) + } + if (canReadDistributors()) { menuItems.push({ name: 'distributors.title', diff --git a/frontend/src/components/ToastNotificationsArea.vue b/frontend/src/components/shell/ToastNotificationsArea.vue similarity index 92% rename from frontend/src/components/ToastNotificationsArea.vue rename to frontend/src/components/shell/ToastNotificationsArea.vue index 7f4614cb2..e915925f5 100644 --- a/frontend/src/components/ToastNotificationsArea.vue +++ b/frontend/src/components/shell/ToastNotificationsArea.vue @@ -5,8 +5,8 @@ diff --git a/frontend/src/components/systems/DestroySystemModal.vue b/frontend/src/components/systems/DestroySystemModal.vue new file mode 100644 index 000000000..4da17342e --- /dev/null +++ b/frontend/src/components/systems/DestroySystemModal.vue @@ -0,0 +1,76 @@ + + + + + diff --git a/frontend/src/components/systems/ReactivateSystemModal.vue b/frontend/src/components/systems/ReactivateSystemModal.vue new file mode 100644 index 000000000..29fac47f0 --- /dev/null +++ b/frontend/src/components/systems/ReactivateSystemModal.vue @@ -0,0 +1,93 @@ + + + + + diff --git a/frontend/src/components/systems/RestoreSystemModal.vue b/frontend/src/components/systems/RestoreSystemModal.vue new file mode 100644 index 000000000..d48f6225e --- /dev/null +++ b/frontend/src/components/systems/RestoreSystemModal.vue @@ -0,0 +1,90 @@ + + + + + diff --git a/frontend/src/components/systems/SecretRegeneratedModal.vue b/frontend/src/components/systems/SecretRegeneratedModal.vue index 84d6c6b2b..456e84aa8 100644 --- a/frontend/src/components/systems/SecretRegeneratedModal.vue +++ b/frontend/src/components/systems/SecretRegeneratedModal.vue @@ -75,9 +75,7 @@ function onShow() {
{{ newSecret }}
-
- ************************************************************************* -
+
************************
+ + + + diff --git a/frontend/src/components/systems/SystemApplicationsCard.vue b/frontend/src/components/systems/SystemApplicationsCard.vue new file mode 100644 index 000000000..b71b0cb11 --- /dev/null +++ b/frontend/src/components/systems/SystemApplicationsCard.vue @@ -0,0 +1,98 @@ + + + + + diff --git a/frontend/src/components/systems/SystemInfoCard.vue b/frontend/src/components/systems/SystemInfoCard.vue index a83ff3c9c..6003f884e 100644 --- a/frontend/src/components/systems/SystemInfoCard.vue +++ b/frontend/src/components/systems/SystemInfoCard.vue @@ -6,50 +6,63 @@ diff --git a/frontend/src/components/systems/SystemNetworkCard.vue b/frontend/src/components/systems/SystemNetworkCard.vue index 76483e896..cf5c4b702 100644 --- a/frontend/src/components/systems/SystemNetworkCard.vue +++ b/frontend/src/components/systems/SystemNetworkCard.vue @@ -123,9 +123,9 @@ const getNetworkRoleForegroundStyle = (role: string | undefined) => {