diff --git a/CHANGELOG.md b/CHANGELOG.md index 0875f8a52..8a4ec1dc7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,8 +11,17 @@ All notable changes to this project will be documented in this file. The format ### Changed +- Detail views for both objectives and key results are now shown as panes in the + OKR timeline view. The number of simultaneously visible panes depends on the + viewport size (and is otherwise stacked). Clicking objectives in the timeline + now toggles the detail pane rather than adding objectives to a list. To group + objectives in a list (and see combined progression), the meta key must now be + pressed while selecting one or more objectives. +- Key results can now be rearranged by drag and drop. - New key results are now given a start value of 0, a target value of 100, and percentage as unit of measurement by default. +- Objectives can now be "lifted" from a product or department to the level above + (to a department or organization, respectively). - The API authorization mechanism has been reworked. The API now accepts a pair of `okr-client-id` and `okr-client-secret` headers to authorize clients. The interface for managing client credentials can be found in the item @@ -23,6 +32,8 @@ All notable changes to this project will be documented in this file. The format - Fixed Markdown rendering of descriptions on objective, key result, and measurement detail pages. +- Product result indicators and key figures are now correctly included as part + of parent measurements when switching between items. ### Security diff --git a/firestore.indexes.json b/firestore.indexes.json index 7b67aaf65..2d74c9766 100644 --- a/firestore.indexes.json +++ b/firestore.indexes.json @@ -302,6 +302,24 @@ } ] }, + { + "collectionGroup": "objectiveContributors", + "queryScope": "COLLECTION", + "fields": [ + { + "fieldPath": "archived", + "order": "ASCENDING" + }, + { + "fieldPath": "item", + "order": "ASCENDING" + }, + { + "fieldPath": "objective", + "order": "ASCENDING" + } + ] + }, { "collectionGroup": "secrets", "queryScope": "COLLECTION", @@ -501,6 +519,29 @@ "queryScope": "COLLECTION_GROUP" } ] + }, + { + "collectionGroup": "objectiveContributors", + "fieldPath": "auto", + "ttl": false, + "indexes": [ + { + "order": "ASCENDING", + "queryScope": "COLLECTION" + }, + { + "order": "DESCENDING", + "queryScope": "COLLECTION" + }, + { + "arrayConfig": "CONTAINS", + "queryScope": "COLLECTION" + }, + { + "arrayConfig": "CONTAINS", + "queryScope": "COLLECTION_GROUP" + } + ] } ] } diff --git a/firestore.rules b/firestore.rules index c9ac22e9c..d876cfc88 100644 --- a/firestore.rules +++ b/firestore.rules @@ -65,6 +65,96 @@ service cloud.firestore { return isSignedIn() && (isAdminFromOrgOfProdOrDep || isAdminOfParent); } + // + // Return true if the current user is an admin of the organization that the + // document's `item` belongs to *after* performing the action. + // + function isAdminOfItemAfter(document, type) { + let userDoc = getUserDoc(); + let userIsAdmin = isAdmin(); + let item = getAfter(/databases/$(database)/documents/$(type)/$(document)); + let itemDoc = getAfter(item.data.item); + + // Check whether the item of the document the user tries to access has + // an organization – this means that it is a product or department. + let hasOrgDocumentInItem = 'organization' in itemDoc.data; + let isAdminFromOrgOfProdOrDep = userIsAdmin && hasOrgDocumentInItem && getAfter(itemDoc.data.organization).id in userDoc.data.admin; + + // Check whether the user has access to the item, which is an + // organization (given that the first check is false). + let isAdminOfItem = userIsAdmin && !isAdminFromOrgOfProdOrDep && itemDoc.id in userDoc.data.admin; + + return isSignedIn() && (isAdminFromOrgOfProdOrDep || isAdminOfItem); + } + + // + // Return true if the current user is an admin of the organization that the + // document's `item` belongs to *before* performing the action. + // + function isAdminOfItemBefore(document, type) { + let userDoc = getUserDoc(); + let userIsAdmin = isAdmin(); + let item = get(/databases/$(database)/documents/$(type)/$(document)); + let itemDoc = get(item.data.item); + + // Check whether the item of the document the user tries to access has + // an organization – this means that it is a product or department. + let hasOrgDocumentInItem = 'organization' in itemDoc.data; + let isAdminFromOrgOfProdOrDep = userIsAdmin && hasOrgDocumentInItem && get(itemDoc.data.organization).id in userDoc.data.admin; + + // Check whether the user has access to the item, which is an + // organization (given that the first check is false). + let isAdminOfItem = userIsAdmin && !isAdminFromOrgOfProdOrDep && itemDoc.id in userDoc.data.admin; + + return isSignedIn() && (isAdminFromOrgOfProdOrDep || isAdminOfItem); + } + + // + // Return true if the current user is an admin of the parent of the + // document's objective. + // + function isAdminOfObjectiveParent(document, type) { + let userDoc = getUserDoc(); + let userIsAdmin = isAdmin(); + let doc = getAfter(/databases/$(database)/documents/$(type)/$(document)); + let objectiveDoc = getAfter(doc.data.objective); + let parentDoc = getAfter(objectiveDoc.data.parent); + + // Check whether the parent of the document the user tries to access has + // an organization – this means that it is a product or department. + let hasOrgDocumentInParent = 'organization' in parentDoc.data; + let isAdminFromOrgOfProdOrDep = userIsAdmin && hasOrgDocumentInParent && getAfter(parentDoc.data.organization).id in userDoc.data.admin; + + // Check whether the user has access to the parent, which is an + // organization (given that the first check is false). + let isAdminOfParent = userIsAdmin && !isAdminFromOrgOfProdOrDep && parentDoc.id in userDoc.data.admin; + + return isSignedIn() && (isAdminFromOrgOfProdOrDep || isAdminOfParent); + } + + // + // Return true if the current user is an admin of the parent of the + // document's objective *before* performing the action. + // + function isAdminOfObjectiveParentBefore(document, type) { + let userDoc = getUserDoc(); + let userIsAdmin = isAdmin(); + let doc = get(/databases/$(database)/documents/$(type)/$(document)); + let objectiveDoc = get(doc.data.objective); + let parentDoc = get(objectiveDoc.data.parent); + + // Check whether the parent of the document the user tries to access has + // an organization – this means that it is a product or department. + let hasOrgDocumentInParent = 'organization' in parentDoc.data; + let isAdminFromOrgOfProdOrDep = userIsAdmin && hasOrgDocumentInParent && get(parentDoc.data.organization).id in userDoc.data.admin; + + // Check whether the user has access to the parent, which is an + // organization (given that the first check is false). + let isAdminOfParent = userIsAdmin && !isAdminFromOrgOfProdOrDep && parentDoc.id in userDoc.data.admin; + + return isSignedIn() && (isAdminFromOrgOfProdOrDep || isAdminOfParent); + } + // Is user signed in function isSignedIn() { // TODO: Must be a verified (whitelisted) user @@ -90,29 +180,63 @@ service cloud.firestore { } // - // Return true if the current user is a member of the parent of the - // document's objective *before* performing the action. + // Return true if the current user is a member of `document.parent` + // *before* performing the action. // - function isMemberOfObjectiveParentBefore(document, type) { + function isMemberOfParentBefore(document, type) { let userRef = /databases/$(database)/documents/users/$(request.auth.token.email); let doc = get(/databases/$(database)/documents/$(type)/$(document)); - let objectiveDoc = get(doc.data.objective); - let parentDoc = get(objectiveDoc.data.parent); + let parentDoc = get(doc.data.parent); let userIsMemberOfParent = userRef in parentDoc.data.team; return userIsMemberOfParent; } // - // Return true if the current user is a member of the parent of the - // document's objective *after* performing the action. + // Return true if the `objective`'s parent is either `item` or its parent + // *after* performing the action. // - function isMemberOfObjectiveParentAfter(document, type) { - let userRef = /databases/$(database)/documents/users/$(request.auth.token.email); + function objectiveParentIsItemOrItemParent(document, type) { let doc = getAfter(/databases/$(database)/documents/$(type)/$(document)); + + let itemDoc = getAfter(doc.data.item); let objectiveDoc = getAfter(doc.data.objective); - let parentDoc = getAfter(objectiveDoc.data.parent); - let userIsMemberOfParent = userRef in parentDoc.data.team; - return userIsMemberOfParent; + let objectiveParentDoc = getAfter(objectiveDoc.data.parent); + + let isProduct = 'department' in itemDoc.data; + let isDepartment = 'organization' in itemDoc.data; + + let objectiveParentIsItemParent = (isProduct && getAfter(itemDoc.data.department) == objectiveParentDoc) + || (!isProduct && isDepartment && getAfter(itemDoc.data.organization) == objectiveParentDoc); + + return objectiveParentDoc == itemDoc || objectiveParentIsItemParent; + } + + // + // Return true if the current user is a member of `item` *after* performing + // the action. + // + function isMemberOfItemAfter(document, type) { + let userRef = /databases/$(database)/documents/users/$(request.auth.token.email); + let doc = getAfter(/databases/$(database)/documents/$(type)/$(document)); + + let itemDoc = getAfter(doc.data.item); + let isMemberOfItem = userRef in itemDoc.data.team; + + return isMemberOfItem; + } + + // + // Return true if the current user is a member of `item` *before* + // performing the action. + // + function isMemberOfItemBefore(document, type) { + let userRef = /databases/$(database)/documents/users/$(request.auth.token.email); + let doc = get(/databases/$(database)/documents/$(type)/$(document)); + + let itemDoc = get(doc.data.item); + let isMemberOfItem = userRef in itemDoc.data.team; + + return isMemberOfItem; } function isSelf(document) { @@ -180,15 +304,14 @@ service cloud.firestore { match /objectives/{document} { allow read: if isSignedIn(); allow create: if isSuperAdmin() || isMemberOfParent(document, 'objectives') || isAdminOfParent(document, 'objectives'); - allow update: if isSuperAdmin() || isMemberOfParent(document, 'objectives') || isAdminOfParent(document, 'objectives'); + allow update: if isSuperAdmin() || isMemberOfParentBefore(document, 'objectives') || isAdminOfParent(document, 'objectives'); allow delete: if isSuperAdmin(); } - // TODO: Should also allow create/delete by organization admins. match /objectiveContributors/{document} { allow read: if isSignedIn(); - allow create: if isSuperAdmin() || isMemberOfObjectiveParentAfter(document, 'objectiveContributors'); - allow delete: if isSuperAdmin() || isMemberOfObjectiveParentBefore(document, 'objectiveContributors'); + allow create: if isSuperAdmin() || (isMemberOfItemAfter(document, 'objectiveContributors') && objectiveParentIsItemOrItemParent(document, 'objectiveContributors')) || (isAdminOfItemAfter(document, 'objectiveContributors') && isAdminOfObjectiveParent(document, 'objectiveContributors')); + allow delete: if isSuperAdmin() || isMemberOfItemBefore(document, 'objectiveContributors') || (isAdminOfItemBefore(document, 'objectiveContributors') && isAdminOfObjectiveParentBefore(document, 'objectiveContributors')); } match /periods/{document} { diff --git a/package-lock.json b/package-lock.json index 7eaf2e8a7..a8c82a0a4 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9,6 +9,7 @@ "version": "3.10.0", "dependencies": { "@oslokommune/punkt-vue2": "^9.2.6", + "@vueuse/core": "^10.5.0", "d3": "^7.6.1", "d3-array": "^3.1.1", "d3-axis": "^3.0.0", @@ -3783,9 +3784,9 @@ } }, "node_modules/@oslokommune/punkt-assets": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/@oslokommune/punkt-assets/-/punkt-assets-3.1.2.tgz", - "integrity": "sha512-60DaqN9sGev0f68iJk4PHc8H4Meys+6Rv8OOybga6qGXvEPGIj0pRy5Qn0yw00GB7gR7QTJcdrMB0yIb7XQg9A==", + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/@oslokommune/punkt-assets/-/punkt-assets-8.0.0.tgz", + "integrity": "sha512-pUlV6mjqsOxylZ3OdzsbalKZSn59uRR5Xi8YMKbf+a5R6wLTcGBUBmPfjJ3PwjTedGdzfXX6O5E58rbAPpYbOA==", "peer": true, "engines": { "node": ">=16.0.0" @@ -4075,6 +4076,11 @@ "integrity": "sha512-Hl219/BT5fLAaz6NDkSuhzasy49dwQS/DSdu4MdggFB8zcXv7vflBI3xp7FEmkmdDkBUI2bPUNeMttp2knYdxw==", "dev": true }, + "node_modules/@types/web-bluetooth": { + "version": "0.0.18", + "resolved": "https://registry.npmjs.org/@types/web-bluetooth/-/web-bluetooth-0.0.18.tgz", + "integrity": "sha512-v/ZHEj9xh82usl8LMR3GarzFY1IrbXJw5L4QfQhokjRV91q+SelFqxQWSep1ucXEZ22+dSTwLFkXeur25sPIbw==" + }, "node_modules/@types/yargs": { "version": "17.0.24", "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.24.tgz", @@ -4113,6 +4119,89 @@ "source-map": "^0.6.1" } }, + "node_modules/@vueuse/core": { + "version": "10.5.0", + "resolved": "https://registry.npmjs.org/@vueuse/core/-/core-10.5.0.tgz", + "integrity": "sha512-z/tI2eSvxwLRjOhDm0h/SXAjNm8N5ld6/SC/JQs6o6kpJ6Ya50LnEL8g5hoYu005i28L0zqB5L5yAl8Jl26K3A==", + "dependencies": { + "@types/web-bluetooth": "^0.0.18", + "@vueuse/metadata": "10.5.0", + "@vueuse/shared": "10.5.0", + "vue-demi": ">=0.14.6" + }, + "funding": { + "url": "https://github.com/sponsors/antfu" + } + }, + "node_modules/@vueuse/core/node_modules/vue-demi": { + "version": "0.14.6", + "resolved": "https://registry.npmjs.org/vue-demi/-/vue-demi-0.14.6.tgz", + "integrity": "sha512-8QA7wrYSHKaYgUxDA5ZC24w+eHm3sYCbp0EzcDwKqN3p6HqtTCGR/GVsPyZW92unff4UlcSh++lmqDWN3ZIq4w==", + "hasInstallScript": true, + "bin": { + "vue-demi-fix": "bin/vue-demi-fix.js", + "vue-demi-switch": "bin/vue-demi-switch.js" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/antfu" + }, + "peerDependencies": { + "@vue/composition-api": "^1.0.0-rc.1", + "vue": "^3.0.0-0 || ^2.6.0" + }, + "peerDependenciesMeta": { + "@vue/composition-api": { + "optional": true + } + } + }, + "node_modules/@vueuse/metadata": { + "version": "10.5.0", + "resolved": "https://registry.npmjs.org/@vueuse/metadata/-/metadata-10.5.0.tgz", + "integrity": "sha512-fEbElR+MaIYyCkeM0SzWkdoMtOpIwO72x8WsZHRE7IggiOlILttqttM69AS13nrDxosnDBYdyy3C5mR1LCxHsw==", + "funding": { + "url": "https://github.com/sponsors/antfu" + } + }, + "node_modules/@vueuse/shared": { + "version": "10.5.0", + "resolved": "https://registry.npmjs.org/@vueuse/shared/-/shared-10.5.0.tgz", + "integrity": "sha512-18iyxbbHYLst9MqU1X1QNdMHIjks6wC7XTVf0KNOv5es/Ms6gjVFCAAWTVP2JStuGqydg3DT+ExpFORUEi9yhg==", + "dependencies": { + "vue-demi": ">=0.14.6" + }, + "funding": { + "url": "https://github.com/sponsors/antfu" + } + }, + "node_modules/@vueuse/shared/node_modules/vue-demi": { + "version": "0.14.6", + "resolved": "https://registry.npmjs.org/vue-demi/-/vue-demi-0.14.6.tgz", + "integrity": "sha512-8QA7wrYSHKaYgUxDA5ZC24w+eHm3sYCbp0EzcDwKqN3p6HqtTCGR/GVsPyZW92unff4UlcSh++lmqDWN3ZIq4w==", + "hasInstallScript": true, + "bin": { + "vue-demi-fix": "bin/vue-demi-fix.js", + "vue-demi-switch": "bin/vue-demi-switch.js" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/antfu" + }, + "peerDependencies": { + "@vue/composition-api": "^1.0.0-rc.1", + "vue": "^3.0.0-0 || ^2.6.0" + }, + "peerDependenciesMeta": { + "@vue/composition-api": { + "optional": true + } + } + }, "node_modules/abort-controller": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/abort-controller/-/abort-controller-3.0.0.tgz", @@ -21703,9 +21792,9 @@ } }, "@oslokommune/punkt-assets": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/@oslokommune/punkt-assets/-/punkt-assets-3.1.2.tgz", - "integrity": "sha512-60DaqN9sGev0f68iJk4PHc8H4Meys+6Rv8OOybga6qGXvEPGIj0pRy5Qn0yw00GB7gR7QTJcdrMB0yIb7XQg9A==", + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/@oslokommune/punkt-assets/-/punkt-assets-8.0.0.tgz", + "integrity": "sha512-pUlV6mjqsOxylZ3OdzsbalKZSn59uRR5Xi8YMKbf+a5R6wLTcGBUBmPfjJ3PwjTedGdzfXX6O5E58rbAPpYbOA==", "peer": true }, "@oslokommune/punkt-css": { @@ -21976,6 +22065,11 @@ "integrity": "sha512-Hl219/BT5fLAaz6NDkSuhzasy49dwQS/DSdu4MdggFB8zcXv7vflBI3xp7FEmkmdDkBUI2bPUNeMttp2knYdxw==", "dev": true }, + "@types/web-bluetooth": { + "version": "0.0.18", + "resolved": "https://registry.npmjs.org/@types/web-bluetooth/-/web-bluetooth-0.0.18.tgz", + "integrity": "sha512-v/ZHEj9xh82usl8LMR3GarzFY1IrbXJw5L4QfQhokjRV91q+SelFqxQWSep1ucXEZ22+dSTwLFkXeur25sPIbw==" + }, "@types/yargs": { "version": "17.0.24", "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.24.tgz", @@ -22008,6 +22102,46 @@ "source-map": "^0.6.1" } }, + "@vueuse/core": { + "version": "10.5.0", + "resolved": "https://registry.npmjs.org/@vueuse/core/-/core-10.5.0.tgz", + "integrity": "sha512-z/tI2eSvxwLRjOhDm0h/SXAjNm8N5ld6/SC/JQs6o6kpJ6Ya50LnEL8g5hoYu005i28L0zqB5L5yAl8Jl26K3A==", + "requires": { + "@types/web-bluetooth": "^0.0.18", + "@vueuse/metadata": "10.5.0", + "@vueuse/shared": "10.5.0", + "vue-demi": ">=0.14.6" + }, + "dependencies": { + "vue-demi": { + "version": "0.14.6", + "resolved": "https://registry.npmjs.org/vue-demi/-/vue-demi-0.14.6.tgz", + "integrity": "sha512-8QA7wrYSHKaYgUxDA5ZC24w+eHm3sYCbp0EzcDwKqN3p6HqtTCGR/GVsPyZW92unff4UlcSh++lmqDWN3ZIq4w==", + "requires": {} + } + } + }, + "@vueuse/metadata": { + "version": "10.5.0", + "resolved": "https://registry.npmjs.org/@vueuse/metadata/-/metadata-10.5.0.tgz", + "integrity": "sha512-fEbElR+MaIYyCkeM0SzWkdoMtOpIwO72x8WsZHRE7IggiOlILttqttM69AS13nrDxosnDBYdyy3C5mR1LCxHsw==" + }, + "@vueuse/shared": { + "version": "10.5.0", + "resolved": "https://registry.npmjs.org/@vueuse/shared/-/shared-10.5.0.tgz", + "integrity": "sha512-18iyxbbHYLst9MqU1X1QNdMHIjks6wC7XTVf0KNOv5es/Ms6gjVFCAAWTVP2JStuGqydg3DT+ExpFORUEi9yhg==", + "requires": { + "vue-demi": ">=0.14.6" + }, + "dependencies": { + "vue-demi": { + "version": "0.14.6", + "resolved": "https://registry.npmjs.org/vue-demi/-/vue-demi-0.14.6.tgz", + "integrity": "sha512-8QA7wrYSHKaYgUxDA5ZC24w+eHm3sYCbp0EzcDwKqN3p6HqtTCGR/GVsPyZW92unff4UlcSh++lmqDWN3ZIq4w==", + "requires": {} + } + } + }, "abort-controller": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/abort-controller/-/abort-controller-3.0.0.tgz", diff --git a/package.json b/package.json index 0fa11de88..1abfb4391 100644 --- a/package.json +++ b/package.json @@ -23,6 +23,7 @@ }, "dependencies": { "@oslokommune/punkt-vue2": "^9.2.6", + "@vueuse/core": "^10.5.0", "d3": "^7.6.1", "d3-array": "^3.1.1", "d3-axis": "^3.0.0", diff --git a/src/components/AppLayout.vue b/src/components/AppLayout.vue index 41330e59d..f01af5490 100644 --- a/src/components/AppLayout.vue +++ b/src/components/AppLayout.vue @@ -43,6 +43,6 @@ export default { display: flex; flex-direction: column; flex-grow: 1; - overflow-y: scroll; + overflow-y: auto; } diff --git a/src/components/EmptyState.vue b/src/components/EmptyState.vue index 2724529a7..34e00910c 100644 --- a/src/components/EmptyState.vue +++ b/src/components/EmptyState.vue @@ -78,10 +78,10 @@ $-empty-state-skins: ( gap: 1rem; justify-content: center; max-width: map.get(variables.$breakpoints, 'phablet'); - height: 100%; margin-right: auto; margin-left: auto; padding: 2rem 1.5rem; + text-align: center; @include bp('tablet-up') { gap: 1.5rem; diff --git a/src/components/GanttChart.vue b/src/components/GanttChart.vue index eece56eb4..4bfc663bc 100644 --- a/src/components/GanttChart.vue +++ b/src/components/GanttChart.vue @@ -2,14 +2,19 @@
-
+
{{ $t('general.today') }}
{{ dateLongCompact(m) }} @@ -49,28 +54,26 @@ { 'sep__period--clickable': periodObjectives.length }, ]" :style="periodStyle()" - @click="selectPeriodObjectives" + @click="periodObjectivesToWorkbench" >
@@ -80,7 +83,7 @@ + + diff --git a/src/components/KeyResultLinkCard.vue b/src/components/KeyResultLinkCard.vue new file mode 100644 index 000000000..697ac34e4 --- /dev/null +++ b/src/components/KeyResultLinkCard.vue @@ -0,0 +1,109 @@ + + + + + diff --git a/src/components/KeyResultProgressDetails.vue b/src/components/KeyResultProgressDetails.vue deleted file mode 100644 index 64711e575..000000000 --- a/src/components/KeyResultProgressDetails.vue +++ /dev/null @@ -1,54 +0,0 @@ - - - - - diff --git a/src/components/KeyResultRow.vue b/src/components/KeyResultRow.vue deleted file mode 100644 index fa5d2dbbb..000000000 --- a/src/components/KeyResultRow.vue +++ /dev/null @@ -1,116 +0,0 @@ - - - - - diff --git a/src/components/KeyResultValuesList.vue b/src/components/KeyResultValuesList.vue new file mode 100644 index 000000000..2299a5051 --- /dev/null +++ b/src/components/KeyResultValuesList.vue @@ -0,0 +1,133 @@ + + + + + diff --git a/src/components/KeyResultsList.vue b/src/components/KeyResultsList.vue deleted file mode 100644 index 71a99d67b..000000000 --- a/src/components/KeyResultsList.vue +++ /dev/null @@ -1,74 +0,0 @@ - - - - - diff --git a/src/components/KpiWidgetGroup.vue b/src/components/KpiWidgetGroup.vue index 8bd1ae505..ac8c621d6 100644 --- a/src/components/KpiWidgetGroup.vue +++ b/src/components/KpiWidgetGroup.vue @@ -24,7 +24,7 @@
- + diff --git a/src/components/Navigation/SiteHeader.vue b/src/components/Navigation/SiteHeader.vue index b38643d61..44859abf2 100644 --- a/src/components/Navigation/SiteHeader.vue +++ b/src/components/Navigation/SiteHeader.vue @@ -115,6 +115,9 @@ export default { route: { name: 'ItemHome' }, label: this.$t('general.OKRsLong'), shortLabel: this.$t('general.OKRs'), + active: ['ItemHome', 'ObjectiveHome', 'KeyResultHome'].includes( + this.$route.name + ), }, { route: { name: 'ItemMeasurements', query: { view: 'list' } }, @@ -152,7 +155,9 @@ export default { }, showToolbar() { - return ['ItemHome', 'ItemMeasurements'].includes(this.$route.name); + return ['ItemHome', 'ObjectiveHome', 'KeyResultHome', 'ItemMeasurements'].includes( + this.$route.name + ); }, }, diff --git a/src/components/ObjectiveDetailsCard.vue b/src/components/ObjectiveDetailsCard.vue new file mode 100644 index 000000000..a856d99f1 --- /dev/null +++ b/src/components/ObjectiveDetailsCard.vue @@ -0,0 +1,238 @@ + + + + + diff --git a/src/components/ObjectiveLinkCard.vue b/src/components/ObjectiveLinkCard.vue new file mode 100644 index 000000000..ad191805b --- /dev/null +++ b/src/components/ObjectiveLinkCard.vue @@ -0,0 +1,279 @@ + + + + + diff --git a/src/components/ObjectiveRow.vue b/src/components/ObjectiveRow.vue deleted file mode 100644 index 4289040f0..000000000 --- a/src/components/ObjectiveRow.vue +++ /dev/null @@ -1,102 +0,0 @@ - - - - - diff --git a/src/components/ObjectiveWorkbench.vue b/src/components/ObjectiveWorkbench.vue deleted file mode 100644 index 276bb7e17..000000000 --- a/src/components/ObjectiveWorkbench.vue +++ /dev/null @@ -1,151 +0,0 @@ - - - - - diff --git a/src/components/ProgressBar.vue b/src/components/ProgressBar.vue index 791969b65..d5fce9bb4 100644 --- a/src/components/ProgressBar.vue +++ b/src/components/ProgressBar.vue @@ -1,11 +1,62 @@ diff --git a/src/components/drawers/EditKeyResult.vue b/src/components/drawers/EditKeyResult.vue index 22a1995dd..877e0b5c9 100644 --- a/src/components/drawers/EditKeyResult.vue +++ b/src/components/drawers/EditKeyResult.vue @@ -1,16 +1,16 @@ -