Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
32 changes: 31 additions & 1 deletion src/components/AppContent/CircleContent.vue
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@

<template>
<AppContent>
<EmptyContent v-if="!circle" :name="t('contacts', 'Please select a team')">
<EmptyContent v-if="!circle && !userGroup" :name="t('contacts', 'Please select a team')">
<template #icon>
<AccountGroup :size="20" />
</template>
Expand All @@ -17,6 +17,7 @@
</template>
</EmptyContent>

<UserGroupDetails v-else-if="userGroup" :user-group="userGroup" />
<CircleDetails v-else :circle="circle" />
</AppContent>
</template>
Expand All @@ -31,6 +32,9 @@ import AccountGroup from 'vue-material-design-icons/AccountGroupOutline.vue'
import CircleDetails from '../CircleDetails.vue'
import RouterMixin from '../../mixins/RouterMixin.js'
import IsMobileMixin from '../../mixins/IsMobileMixin.ts'
import UserGroupDetails from '../UserGroupDetails.vue'
import useUserGroupStore from '../../store/userGroup.ts'
import { mapStores } from 'pinia'

export default {
name: 'CircleContent',
Expand All @@ -41,6 +45,7 @@ export default {
EmptyContent,
AccountGroup,
IconLoading,
UserGroupDetails,
},

mixins: [IsMobileMixin, RouterMixin],
Expand All @@ -66,6 +71,9 @@ export default {
circle() {
return this.$store.getters.getCircle(this.selectedCircle)
},
userGroup() {
return this.userGroupStore.getUserGroup(this.selectedUserGroup)
},
members() {
return Object.values(this.circle?.members || [])
},
Expand All @@ -78,6 +86,7 @@ export default {
isEmptyCircle() {
return this.members.length === 0
},
...mapStores(useUserGroupStore),
},

watch: {
Expand All @@ -86,12 +95,21 @@ export default {
this.fetchCircleMembers(newCircle.id)
}
},
userGroup(newUserGroup) {
if (newUserGroup?.id) {
this.fetchUserGroupMembers(newUserGroup.id)
}
},
},

beforeMount() {
if (this.circle?.id) {
this.fetchCircleMembers(this.circle.id)
}

if (this.userGroup?.id) {
this.fetchUserGroupMembers(this.userGroup.id)
}
},

methods: {
Expand All @@ -108,6 +126,18 @@ export default {
this.loadingList = false
}
},
async fetchUserGroupMembers(userGroupId) {
this.loadingList = true

try {
await this.userGroupStore.getUserGroupMembers(userGroupId)
} catch (error) {
console.error(error)
showError(t('contacts', 'There was an error fetching the member list'))
} finally {
this.loadingList = false
}
},
},
}
</script>
Expand Down
3 changes: 2 additions & 1 deletion src/components/AppNavigation/CircleNavigationItem.vue
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,7 @@ import AccountGroupOutline from 'vue-material-design-icons/AccountGroupOutline.v

import Circle from '../../models/circle.ts'
import CircleActionsMixin from '../../mixins/CircleActionsMixin.js'
import UserGroup from '../../models/userGroup.ts'

export default {
name: 'CircleNavigationItem',
Expand All @@ -116,7 +117,7 @@ export default {

props: {
circle: {
type: Circle,
type: [Circle, UserGroup],
required: true,
},
},
Expand Down
9 changes: 8 additions & 1 deletion src/components/AppNavigation/RootNavigation.vue
Original file line number Diff line number Diff line change
Expand Up @@ -169,6 +169,7 @@

<script>
import { GROUP_ALL_CONTACTS, CHART_ALL_CONTACTS, GROUP_NO_GROUP_CONTACTS, GROUP_RECENTLY_CONTACTED, ELLIPSIS_COUNT, CIRCLE_DESC, CONTACTS_SETTINGS } from '../../models/constants.ts'
import useUserGroupStore from '../../store/userGroup.ts'

import {
NcActionInput as ActionInput,
Expand All @@ -182,6 +183,7 @@ import {
} from '@nextcloud/vue'

import naturalCompare from 'string-natural-compare'
import { mapStores } from 'pinia'

import CircleNavigationItem from './CircleNavigationItem.vue'
import Cog from 'vue-material-design-icons/CogOutline.vue'
Expand Down Expand Up @@ -282,6 +284,9 @@ export default {
sortedContacts() {
return this.$store.getters.getSortedContacts
},
userGroups() {
return this.userGroupStore.userGroupList
},

// list all the contacts that doesn't have a group
ungroupedContacts() {
Expand Down Expand Up @@ -326,7 +331,8 @@ export default {

// generate circles menu from the circles store
circlesMenu() {
const menu = this.circles || []
const menu = [...(this.circles || []), ...(this.userGroups || [])]

menu.sort((a, b) => {
// If user is member of a and b, sort by level
if (a?.initiator?.level !== b?.initiator?.level && a?.initiator?.level && b?.initiator?.level) {
Expand Down Expand Up @@ -372,6 +378,7 @@ export default {
? t('contacts', 'Show all teams')
: t('contacts', 'Collapse teams')
},
...mapStores(useUserGroupStore),
},

methods: {
Expand Down
163 changes: 163 additions & 0 deletions src/components/UserGroupDetails.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,163 @@
<!--
- SPDX-FileCopyrightText: 2025 Nextcloud GmbH and Nextcloud contributors
- SPDX-License-Identifier: AGPL-3.0-or-later
-->

<template>
<div class="usergroup-details-container">
<div class="usergroup-details-grid">
<div class="usergroup-details__header-wrapper">
<div class="usergroup-details-grid__avatar">
<NcAvatar :disable-tooltip="true"
:display-name="userGroup.displayName"
:is-no-user="true"
:size="75" />
</div>
<div class="usergroup-details__header">
<div class="usergroup-name-wrapper">
<h2 class="usergroup-name">
<span :title="userGroup.displayName">{{ userGroup.displayName }}</span>
</h2>
<div class="usergroup-details__subtitle">
{{ t('contacts', 'This is a read-only group managed by administrators. Group members can only view this group.') }}
</div>
</div>
<div class="actions">
<NcButton variant="secondary" @click.stop.prevent="copyToClipboard(userGroupUrl)">
<template #icon>
<CopyIcon :size="20" />
</template>
{{ t('contacts', 'Copy link') }}
</NcButton>
</div>
</div>
</div>
<div class="usergroup-details__main-content">
<h3 class="usergroup-details__content-heading">
{{ t('contacts', 'Members') }}
</h3>
<div class="usergroup-details__member-grid">
<UserGroupMember v-for="member in userGroup.members"
:key="`user-group-member-${member}`"
:member="member" />
</div>
</div>
</div>
</div>
</template>

<script>
import { NcAvatar, NcButton } from '@nextcloud/vue'
import CopyIcon from 'vue-material-design-icons/ContentCopy.vue'

import CopyToClipboardMixin from '../mixins/CopyToClipboardMixin.js'
import UserGroup from '../models/userGroup.ts'
import UserGroupMember from './UserGroupDetails/UserGroupMember.vue'

export default {
name: 'UserGroupDetails',

components: {
CopyIcon,
NcAvatar,
NcButton,
UserGroupMember,
},

mixins: [CopyToClipboardMixin],

props: {
userGroup: {
type: UserGroup,
required: true,
},
},

computed: {
userGroupUrl() {
return window.location.href
},
},
}
</script>

<style lang="scss" scoped>
.usergroup-details {
&-container {
padding-inline: 20px;
margin-top: 1rem;
}

&-grid {
display: grid;
grid-template-columns: 1fr;
gap: 36px;
max-width: 800px;
margin-inline: auto;

.usergroup-name-wrapper {
width: 100%;
}
}

&__header-wrapper {
display: grid;
grid-template-columns: auto 1fr;
align-items: center;
gap: 24px;
}

&__header {
background-color: transparent;
display: flex;
flex-direction: column;
align-items: flex-start;
gap: 2px;
width: 100%;

.usergroup-name {
font-size: 1.5rem;
font-weight: bold;
margin: 0px;
margin-bottom: 2px;
}
}

&__subtitle {
color: var(--color-text-maxcontrast);
margin-bottom: 8px;
}

&__main-content {
margin-inline-start: 99px;

@media (max-width: 768px) {
margin-inline-start: 0px;
}
}

&__content-heading {
font-weight: bold;
font-size: 1.2rem;
line-height: 0px;
margin-top: 4px;
margin-bottom: 8px;
}

&__member-grid {
display: grid;
grid-template-columns: repeat(2, 1fr);
gap: 8px;
max-width: 500px;

@media (max-width: 768px) {
grid-template-columns: 1fr;
}

@media (min-width: 1200px) {
grid-template-columns: repeat(3, 1fr);
}
}

}
</style>
Loading
Loading