diff --git a/src/components/GanttChart.vue b/src/components/GanttChart.vue index f00febe0a..844368e46 100644 --- a/src/components/GanttChart.vue +++ b/src/components/GanttChart.vue @@ -61,7 +61,7 @@ + + + + diff --git a/src/components/ObjectiveLinkCard.vue b/src/components/ObjectiveLinkCard.vue new file mode 100644 index 000000000..7ef618f6c --- /dev/null +++ b/src/components/ObjectiveLinkCard.vue @@ -0,0 +1,229 @@ + + + + + diff --git a/src/components/OkrLinkCard.vue b/src/components/OkrLinkCard.vue deleted file mode 100644 index 9ee24cf34..000000000 --- a/src/components/OkrLinkCard.vue +++ /dev/null @@ -1,152 +0,0 @@ - - - - - diff --git a/src/components/panes/ObjectivePane.vue b/src/components/panes/ObjectivePane.vue index 8a5526175..e5e70d64b 100644 --- a/src/components/panes/ObjectivePane.vue +++ b/src/components/panes/ObjectivePane.vue @@ -34,6 +34,33 @@ :html="activeObjective.description" /> +
+
+

{{ $t('objective.owner') }}

+ + {{ activeObjective.parent.name }} + +
+ +
+

{{ $t('objective.contributors') }}

+ +
+
+ -
-
@@ -120,9 +147,10 @@ import { format } from 'd3-format'; import { mapGetters, mapState } from 'vuex'; import { PktBreadcrumbs, PktButton, PktTag } from '@oslokommune/punkt-vue2'; import draggable from 'vuedraggable'; -import { compareKeyResults } from '@/util/okr'; -import { dateLong, periodDates } from '@/util'; +import { compareKeyResults, contributorTagColor, contributorTagMode } from '@/util/okr'; +import { dateLong, periodDates, uniqueBy } from '@/util'; import { db } from '@/config/firebaseConfig'; +import i18n from '@/locale/i18n'; import KeyResult from '@/db/KeyResult'; import ProgressBar from '@/components/ProgressBar.vue'; import PaneWrapper from '@/components/panes/PaneWrapper.vue'; @@ -130,7 +158,7 @@ import LoadingSmall from '@/components/LoadingSmall.vue'; import WidgetObjectiveDetails from '@/components/widgets/WidgetObjectiveDetails.vue'; import WidgetWeights from '@/components/widgets/WidgetWeights.vue'; import EmptyState from '@/components/EmptyState.vue'; -import OkrLinkCard from '@/components/OkrLinkCard.vue'; +import KeyResultLinkCard from '@/components/KeyResultLinkCard.vue'; import HTMLOutput from '@/components/HTMLOutput.vue'; export default { @@ -140,7 +168,7 @@ export default { draggable, PaneWrapper, EmptyState, - OkrLinkCard, + KeyResultLinkCard, HTMLOutput, LoadingSmall, PktBreadcrumbs, @@ -198,6 +226,13 @@ export default { }); }, }, + + contributors() { + return uniqueBy( + this.keyResults.map((kr) => kr.parent).filter((item) => item.name), + 'id' + ).sort((a, b) => a.name.localeCompare(b.name, i18n.locale)); + }, }, watch: { @@ -221,6 +256,8 @@ export default { }, methods: { + contributorTagColor, + contributorTagMode, dateLong, periodDates, @@ -248,6 +285,36 @@ export default { } } + &__members { + display: flex; + justify-content: space-between; + margin-top: 1rem; + + & h4 { + margin-bottom: 0.25rem; + } + } + + &__contributors { + margin-right: calc(2.5rem + 2px); + } + + &__contributor { + display: flex; + flex-direction: row; + gap: 0.5rem; + align-items: center; + justify-content: space-between; + margin-top: 0.25rem; + } + + &__contributor-tag { + display: block; + width: 1.5rem; + height: 1.5rem; + border-radius: 50%; + } + &__progression { margin-top: 1rem; } @@ -273,7 +340,7 @@ export default { margin-left: 3rem; } - & .okr-link-card { + & .key-result-link-card { position: relative; margin-top: 1rem; diff --git a/src/components/panes/WorkbenchPane.vue b/src/components/panes/WorkbenchPane.vue index f380b5eb2..2bbd08454 100644 --- a/src/components/panes/WorkbenchPane.vue +++ b/src/components/panes/WorkbenchPane.vue @@ -14,9 +14,10 @@
- @@ -39,7 +40,7 @@ import { mapActions, mapGetters, mapState } from 'vuex'; import { PktButton } from '@oslokommune/punkt-vue2'; import PaneWrapper from '@/components/panes/PaneWrapper.vue'; -import OkrLinkCard from '@/components/OkrLinkCard.vue'; +import ObjectiveLinkCard from '@/components/ObjectiveLinkCard.vue'; import ProgressBar from '@/components/ProgressBar.vue'; export default { @@ -47,7 +48,7 @@ export default { components: { PaneWrapper, - OkrLinkCard, + ObjectiveLinkCard, ProgressBar, PktButton, }, diff --git a/src/config/colors.js b/src/config/colors.js new file mode 100644 index 000000000..4b4fb1ac1 --- /dev/null +++ b/src/config/colors.js @@ -0,0 +1,68 @@ +const CONTRIBUTOR_TAG_COLORS = [ + { + name: 'color-beige-dark', + mode: 'dark', + }, + { + name: 'color-beige-light', + mode: 'light', + }, + { + name: 'color-blue', + mode: 'light', + }, + { + name: 'color-blue-40', + mode: 'dark', + }, + { + name: 'color-blue-dark', + mode: 'dark', + }, + { + name: 'color-blue-dark-15', + mode: 'dark', + }, + { + name: 'color-blue-light', + mode: 'light', + }, + { + name: 'color-focus', + mode: 'light', + }, + { + name: 'color-green-40', + mode: 'dark', + }, + { + name: 'color-green-dark', + mode: 'dark', + }, + { + name: 'color-green-dark-15', + mode: 'light', + }, + { + name: 'color-red-100', + mode: 'dark', + }, + { + name: 'color-red-20', + mode: 'light', + }, + { + name: 'color-yellow', + mode: 'dark', + }, + { + name: 'color-yellow-50', + mode: 'light', + }, + { + name: 'color-yellow-80', + mode: 'dark', + }, +]; + +export default CONTRIBUTOR_TAG_COLORS; diff --git a/src/locale/locales/en-US.json b/src/locale/locales/en-US.json index 88b8eef18..c2e479e4c 100644 --- a/src/locale/locales/en-US.json +++ b/src/locale/locales/en-US.json @@ -388,6 +388,8 @@ "objective": { "period": "Period", "progressionTitle": "Objective progression:", + "owner": "Owner", + "contributors": "Contributors", "weight": "Weight", "change": "Change objective", "add": "Add objective", diff --git a/src/locale/locales/nb-NO.json b/src/locale/locales/nb-NO.json index 70cd484f7..4217b9d03 100644 --- a/src/locale/locales/nb-NO.json +++ b/src/locale/locales/nb-NO.json @@ -388,6 +388,8 @@ "objective": { "period": "Arbeidsperiode", "progressionTitle": "Prosentvis fremdrift for målet:", + "owner": "Eier", + "contributors": "Bidragsytere", "weight": "Vekt", "change": "Endre mål", "add": "Legg til mål", diff --git a/src/styles/main.scss b/src/styles/main.scss index e4584d949..ca9bd54d7 100644 --- a/src/styles/main.scss +++ b/src/styles/main.scss @@ -106,7 +106,14 @@ body.using-mouse :not([class^='pkt-'], [class*=' pkt-']):focus { :root { // Colors that are going to be added in Punkt soon. + --color-blue-40: #336084; + --color-blue-dark-15: #b8ccd9; --color-blue-dark-40: #4b5283; + --color-green-40: #207354; + --color-green-dark-15: #b8d9d4; + --color-red-100: #4d0800; + --color-red-20: #fad0cb; + --color-yellow-80: #774c01; // Colors in RGB value - add more if needed. SCSS rgba() function // can read hex numbers with CSS Variables. diff --git a/src/util/hash.js b/src/util/hash.js new file mode 100644 index 000000000..35a339419 --- /dev/null +++ b/src/util/hash.js @@ -0,0 +1,31 @@ +/* eslint no-bitwise: 0 */ + +/** + * cyrb53 (c) 2018 bryc (github.com/bryc) + * + * License: Public domain. + * + * A fast and simple 53-bit string hash function with decent collision + * resistance. + * + * Largely inspired by MurmurHash2/3, but with a focus on speed/simplicity. + */ +function simpleHash(str, seed = 0) { + let h1 = 0xdeadbeef ^ seed; + let h2 = 0x41c6ce57 ^ seed; + + for (let i = 0, ch; i < str.length; i += 1) { + ch = str.charCodeAt(i); + h1 = Math.imul(h1 ^ ch, 2654435761); + h2 = Math.imul(h2 ^ ch, 1597334677); + } + + h1 = Math.imul(h1 ^ (h1 >>> 16), 2246822507); + h1 ^= Math.imul(h2 ^ (h2 >>> 13), 3266489909); + h2 = Math.imul(h2 ^ (h2 >>> 16), 2246822507); + h2 ^= Math.imul(h1 ^ (h1 >>> 13), 3266489909); + + return 4294967296 * (2097151 & h2) + (h1 >>> 0); +} + +export default simpleHash; diff --git a/src/util/index.js b/src/util/index.js index 01a794669..d0cb7885e 100644 --- a/src/util/index.js +++ b/src/util/index.js @@ -13,3 +13,19 @@ export { default as toastArchiveAndRevert } from './toastUtils'; export { default as validateEmail } from './validateEmail'; export const getRandomInt = (max) => Math.floor(Math.random() * max); + +/** + * Return an array of all members of `arr` that are unique with respect to the + * attribute `key`. + */ +export function uniqueBy(arr, key) { + const res = {}; + + arr.forEach((x) => { + if (!(x[key] in res)) { + res[x[key]] = x; + } + }); + + return Object.values(res); +} diff --git a/src/util/okr.js b/src/util/okr.js index 7394d8658..d36c7276b 100644 --- a/src/util/okr.js +++ b/src/util/okr.js @@ -1,4 +1,6 @@ import { dateShort, periodDates } from '@/util'; +import CONTRIBUTOR_TAG_COLORS from '@/config/colors'; +import simpleHash from '@/util/hash'; /** * Return a nicely formatted period range for `objective`'s period. @@ -40,3 +42,21 @@ export function compareKeyResults(a, b) { // Otherwise fall back to ordering by name. return a.name.localeCompare(b.name); } + +/** + * Return the color to use for the contributor tag of the + * organization/department/product called `name`. + */ +export function contributorTagColor(name) { + const c = CONTRIBUTOR_TAG_COLORS[simpleHash(name) % CONTRIBUTOR_TAG_COLORS.length]; + return `var(--${c.name})`; +} + +/** + * Return the color mode to use for the contributor tag of the + * organization/department/product called `name`. + */ +export function contributorTagMode(name) { + const c = CONTRIBUTOR_TAG_COLORS[simpleHash(name) % CONTRIBUTOR_TAG_COLORS.length]; + return c.mode; +}