Refine design and functionality of group list and create, edit group

This commit is contained in:
Wolfgang Huß 2022-09-26 10:31:00 +02:00
parent e5acca7329
commit 7b11122bea
11 changed files with 293 additions and 176 deletions

View File

@ -3,12 +3,11 @@
<template #default="{ toggleMenu }">
<slot name="button" :toggleMenu="toggleMenu">
<base-button
data-test="group-menu-button"
icon="ellipsis-v"
size="large"
size="small"
circle
ghost
@click.prevent="toggleMenu()"
data-test="group-menu-button"
/>
</slot>
</template>
@ -36,7 +35,7 @@
import Dropdown from '~/components/Dropdown'
export default {
name: 'ContentMenu',
name: 'GroupContentMenu',
components: {
Dropdown,
},

View File

@ -1,7 +1,5 @@
<template>
<div>
<!-- Wolle: <div v-if="update">add: slug, location, tiptap editor in description</div> -->
<!-- Wolle: <ds-container> -->
<ds-form
class="group-form"
ref="groupForm"
@ -97,6 +95,8 @@
:loading="loadingGeo"
@input.native="handleCityInput"
/>
<!-- TODO: implement clear button -->
<!-- <base-button icon="close" circle ghost size="small" :disabled="formData.locationName.length === 0" @click="clear" /> -->
</ds-space>
<ds-space margin-top="large">
<categories-select
@ -198,50 +198,39 @@ export default {
},
computed: {
submitDisable() {
if (
this.formData.name !== '' &&
this.formData.groupType !== '' &&
this.formData.about !== '' &&
this.formData.description !== '' &&
this.formData.actionRadius !== '' &&
this.formData.locationName !== '' &&
this.formData.categoryIds.length > 0
) {
return false
}
return true
return (
this.formData.name === '' ||
this.formData.groupType === '' ||
// this.formData.about === '' || // not mandatory
this.formData.description === '' ||
this.formData.actionRadius === '' ||
// this.formData.locationName === '' || // not mandatory
this.formData.categoryIds.length === 0
)
},
submitDisableEdit() {
if (
this.formData.name !== this.group.name ||
this.formData.groupType !== this.group.groupType ||
this.formData.about !== this.group.about ||
this.formData.description !== this.group.description ||
this.formData.actionRadius !== this.group.actionRadius ||
this.formData.locationName !== this.group.locationName ||
this.formData.categoryIds.length === 0 ||
!this.sameCategories
) {
return false
}
return true
return (
this.formData.name === this.group.name &&
this.formData.slug === this.group.slug &&
// this.formData.groupType === this.group.groupType && // can not be changed for now
this.formData.about === this.group.about &&
this.formData.description === this.group.description &&
this.formData.actionRadius === this.group.actionRadius &&
this.formData.locationName === (this.group.locationName ? this.group.locationName : '') &&
this.sameCategories
)
},
sameCategories() {
const formDataCategories = this.formData.categoryIds.map((categoryIds) => categoryIds)
const groupDataCategories = this.group.categories.map((category) => category.id)
let result
let each = true
const formDataCategories = this.formData.categoryIds.map((id) => id).sort()
const groupDataCategories = this.group.categories.map((category) => category.id).sort()
let equal = true
if (formDataCategories.length !== groupDataCategories.length) return false
if (JSON.stringify(formDataCategories) !== JSON.stringify(groupDataCategories)) {
formDataCategories.forEach((element) => {
result = groupDataCategories.filter((groupCategorieId) => groupCategorieId === element)
if (result.length === 0) each = false
})
return each
}
return true
formDataCategories.forEach((id, index) => {
equal = equal && id === groupDataCategories[index]
})
return equal
},
},
methods: {
@ -311,7 +300,7 @@ export default {
}
</script>
<style lang="scss">
<style lang="scss" scoped>
.select-label {
margin-bottom: 3pt;
color: $text-color-soft;

View File

@ -1,121 +1,21 @@
<template>
<div>
<!-- Wolle: <ds-container class="group-card"> -->
<!-- Wolle: <ds-space>
<div @click="onlyOwnerGroups(true)" ref="myGruops">
<ds-button>{{ $t('group.showMyCreatedGroups') }}</ds-button>
</div>
<div @click="onlyOwnerGroups(false)" ref="allGruops" hidden>
<ds-button>{{ $t('group.showAllMyGroups') }}</ds-button>
</div>
</ds-space> -->
<ds-space margin-bottom="small" v-for="item in items" :key="item.id">
<!-- Wolle: <ds-card :ref="item.myRole === null ? 'null' : item.myRole"> -->
<ds-card>
<ds-flex>
<ds-flex-item width="90%" centered>
<ds-space margin="large">
<nuxt-link :to="`/group/${item.id}`">
<ds-text size="x-large">{{ item.name }}</ds-text>
</nuxt-link>
<ds-text size="small">
{{ item.groupType }}
<ds-chip v-if="item.myRole === 'owner'" color="inverse">{{ item.myRole }}</ds-chip>
<ds-chip v-if="item.myRole === 'usual' || item.myRole === 'pending'">
{{ item.myRole }}
</ds-chip>
</ds-text>
<ds-space v-if="item.about" margin-top="small">
<ds-text bold>{{ item.about }}</ds-text>
</ds-space>
<ds-space margin-top="small">
<div v-html="item.descriptionExcerpt"></div>
</ds-space>
<ds-space margin-top="small">
<ds-chip v-for="category in item.categories" :key="category.name">
<ds-icon :name="category.icon"></ds-icon>
{{ category.name }}
</ds-chip>
<ds-space margin="x-small">
<div v-if="item.locationName">{{ item.locationName }}</div>
<div v-if="item.actionRadius">{{ item.actionRadius }}</div>
</ds-space>
</ds-space>
</ds-space>
</ds-flex-item>
<ds-flex-item width="10%" centered>
<group-menu
v-if="item.myRole === 'owner'"
resource-type="group"
:resource="item"
:isOwner="item.myRole"
@joinGroup="joinGroup"
/>
</ds-flex-item>
</ds-flex>
</ds-card>
<ds-space margin-bottom="small" v-for="group in groups" :key="group.id">
<group-teaser :group="group" />
</ds-space>
<!-- Wolle: </ds-container> -->
</div>
</template>
<script>
import GroupMenu from '~/components/Group/GroupMenu'
import { joinGroupMutation } from '~/graphql/groups.js'
import GroupTeaser from '~/components/Group/GroupTeaser'
export default {
name: 'GroupList',
components: {
GroupMenu,
GroupTeaser,
},
props: {
items: { type: Array, default: () => [] },
},
methods: {
editGroup(item) {
this.$router.push({ path: `/group/edit/${item.id}` })
},
async joinGroup(value) {
const { id } = value
const variables = { groupId: id, userId: this.$store.getters['auth/user'].id }
try {
await this.$apollo.mutate({
mutation: joinGroupMutation(),
variables,
})
this.$toast.success(this.$t('group.groupCreated'))
} catch (error) {
this.$toast.error(error.message)
}
},
// Wolle: onlyOwnerGroups(bool) {
// this.$refs.myGruops.hidden = bool
// this.$refs.allGruops.hidden = !bool
// if (this.$refs.usual) {
// this.$refs.usual.forEach((element) => {
// element.$el.hidden = bool
// })
// }
// if (this.$refs.null) {
// this.$refs.null.forEach((element) => {
// element.$el.hidden = bool
// })
// }
// if (this.$refs.pending) {
// this.$refs.pending.forEach((element) => {
// element.$el.hidden = bool
// })
// }
// },
groups: { type: Array, default: () => [] },
},
}
</script>
<style scoped>
.border-bottom {
border-bottom: 1px solid #17b53f;
background-color: rgb(255, 255, 255);
padding-left: 20px;
}
</style>

View File

@ -0,0 +1,203 @@
<template>
<nuxt-link
class="group-teaser"
:to="{ name: 'group-id-slug', params: { id: group.id, slug: group.slug } }"
>
<base-card
:class="{
'disabled-content': group.disabled,
}"
>
<h2 class="title hyphenate-text">{{ group.name }}</h2>
<div class="slug-location">
<!-- group slug -->
<div>
<ds-text color="soft">
<base-icon name="at" />
{{ group.slug }}
</ds-text>
</div>
<!-- group location -->
<div class="location-item">
<ds-text v-if="group && group.location" color="soft">
<base-icon name="map-marker" />
{{ group && group.location ? group.location.name : '' }}
</ds-text>
</div>
</div>
<!-- TODO: replace editor content with tiptap render view -->
<!-- eslint-disable-next-line vue/no-v-html -->
<div class="content hyphenate-text" v-html="descriptionExcerpt" />
<footer class="footer">
<div>
<!-- group my role in group -->
<ds-chip color="primary">
{{ group && group.myRole ? $t('group.roles.' + group.myRole) : '' }}
</ds-chip>
<!-- group type -->
<ds-chip color="primary">
{{ group && group.groupType ? $t('group.types.' + group.groupType) : '' }}
</ds-chip>
<!-- group action radius -->
<ds-chip color="primary">
{{ group && group.actionRadius ? $t('group.actionRadii.' + group.actionRadius) : '' }}
</ds-chip>
</div>
<!-- group categories -->
<div class="categories" v-if="categoriesActive">
<category
v-for="category in group.categories"
:key="category.id"
v-tooltip="{
content: $t(`contribution.category.description.${category.slug}`),
placement: 'bottom-start',
}"
:icon="category.icon"
/>
</div>
<div v-else class="categories-placeholder"></div>
<!-- group context menu -->
<client-only>
<group-content-menu
v-if="group.myRole === 'owner'"
resource-type="group"
:resource="group"
:isOwner="group.myRole"
@joinGroup="joinGroup"
/>
</client-only>
</footer>
<footer class="footer">
<!-- group goal -->
<div class="labeled-chip">
<ds-text class="label-text hyphenate-text" color="soft" size="small">
{{ $t('group.goal') }}
</ds-text>
<div class="chip">
<ds-chip v-if="group && group.about">{{ group ? group.about : '' }}</ds-chip>
</div>
</div>
</footer>
</base-card>
</nuxt-link>
</template>
<script>
import Category from '~/components/Category'
import GroupContentMenu from '~/components/Group/GroupContentMenu'
import { joinGroupMutation } from '~/graphql/groups.js'
export default {
name: 'GroupTeaser',
components: {
Category,
GroupContentMenu,
},
props: {
group: {
type: Object,
required: true,
},
width: {
type: Object,
default: () => {},
},
},
data() {
return {
categoriesActive: this.$env.CATEGORIES_ACTIVE,
}
},
computed: {
descriptionExcerpt() {
return this.$filters.removeLinks(this.group.descriptionExcerpt)
},
},
methods: {
editGroup(group) {
this.$router.push({ path: `/group/edit/${group.id}` })
},
async joinGroup(value) {
const { id } = value
const variables = { groupId: id, userId: this.$store.getters['auth/user'].id }
try {
await this.$apollo.mutate({
mutation: joinGroupMutation(),
variables,
})
this.$toast.success(this.$t('group.groupCreated'))
} catch (error) {
this.$toast.error(error.message)
}
},
},
}
</script>
<style lang="scss" scoped>
.group-teaser,
.group-teaser:hover,
.group-teaser:active {
position: relative;
display: block;
height: 100%;
color: $text-color-base;
> .ribbon {
position: absolute;
top: 50%;
right: -7px;
}
}
.group-teaser > .base-card {
display: flex;
flex-direction: column;
height: 100%;
> .title {
font-size: 32px;
}
> .slug-location {
display: flex;
margin-bottom: $space-small;
> .location-item {
margin-left: $space-small;
}
}
> .content {
flex-grow: 1;
margin-bottom: $space-small;
}
> .footer {
display: flex;
justify-content: space-between;
align-items: center;
> .categories-placeholder {
flex-grow: 1;
}
> .labeled-chip {
margin-top: $space-xx-small;
> .chip {
margin-top: -$space-small + $space-x-small;
}
}
> .content-menu {
position: relative;
z-index: $z-index-post-teaser-link;
}
}
.user-teaser {
margin-bottom: $space-small;
}
}
</style>

View File

@ -19,9 +19,8 @@
</client-only>
<h2 class="title hyphenate-text">{{ post.title }}</h2>
<!-- TODO: replace editor content with tiptap render view -->
<!-- eslint-disable vue/no-v-html -->
<!-- eslint-disable-next-line vue/no-v-html -->
<div class="content hyphenate-text" v-html="excerpt" />
<!-- eslint-enable vue/no-v-html -->
<footer
class="footer"
v-observe-visibility="(isVisible, entry) => visibilityChanged(isVisible, entry, post.id)"

View File

@ -31,26 +31,27 @@
/>
</client-only> -->
<ds-space margin="small">
<!-- Group name -->
<!-- group name -->
<ds-heading tag="h3" align="center" no-margin>
{{ groupName }}
</ds-heading>
<!-- Group slug -->
<!-- group slug -->
<ds-text align="center" color="soft">
<base-icon name="at" data-test="at" />
{{ groupSlug }}
</ds-text>
<!-- Group location -->
<!-- group location -->
<ds-text v-if="group && group.location" align="center" color="soft" size="small">
<base-icon name="map-marker" data-test="map-marker" />
{{ group && group.location ? group.location.name : '' }}
</ds-text>
<!-- Group created at -->
<!-- group created at -->
<ds-text align="center" color="soft" size="small">
{{ $t('group.foundation') }} {{ group.createdAt | date('MMMM yyyy') }}
</ds-text>
</ds-space>
<ds-flex v-if="isAllowedSeeingGroupMembers">
<!-- Group members count -->
<!-- group members count -->
<ds-flex-item v-if="isAllowedSeeingGroupMembers">
<client-only>
<ds-number :label="$t('group.membersCount', {}, groupMembers.length)">
@ -110,7 +111,7 @@
</div>
<hr />
<ds-space margin-top="small" margin-bottom="small">
<!-- Group my role in group -->
<!-- group my role in group -->
<template v-if="isGroupMember">
<ds-text class="centered-text hyphenate-text" color="soft" size="small">
{{ $t('group.role') }}
@ -121,7 +122,7 @@
</ds-chip>
</div>
</template>
<!-- Group type -->
<!-- group type -->
<ds-text class="centered-text hyphenate-text" color="soft" size="small">
{{ $t('group.type') }}
</ds-text>
@ -130,7 +131,7 @@
{{ group && group.groupType ? $t('group.types.' + group.groupType) : '' }}
</ds-chip>
</div>
<!-- Group action radius -->
<!-- group action radius -->
<ds-text class="centered-text hyphenate-text" color="soft" size="small">
{{ $t('group.actionRadius') }}
</ds-text>
@ -143,7 +144,7 @@
</div>
<ds-space margin="x-small" />
</ds-space>
<!-- Group categories -->
<!-- group categories -->
<template v-if="categoriesActive">
<hr />
<ds-space margin-top="small" margin-bottom="small">
@ -176,7 +177,7 @@
</div>
</ds-space>
</template>
<!-- Group goal -->
<!-- group goal -->
<template v-if="group && group.about">
<hr />
<ds-space margin-top="small" margin-bottom="small">
@ -230,14 +231,13 @@
<ds-space>
<base-card class="group-description">
<!-- TODO: replace editor content with tiptap render view -->
<!-- eslint-disable vue/no-v-html -->
<!-- eslint-disable-next-line vue/no-v-html -->
<div
v-if="isDescriptionCollapsed"
class="content hyphenate-text"
v-html="groupDescriptionExcerpt"
/>
<content-viewer v-else class="content hyphenate-text" :content="group.description" />
<!-- eslint-enable vue/no-v-html -->
<base-button
class="collaps-button"
size="small"
@ -401,7 +401,7 @@ export default {
},
groupSlug() {
const { slug } = this.group || {}
return slug && `@${slug}`
return slug
},
groupDescriptionExcerpt() {
return this.group ? this.$filters.removeLinks(this.group.descriptionExcerpt) : ''

View File

@ -46,15 +46,23 @@ export default {
locationName,
categoryIds,
}
let responseId, responseSlug
try {
await this.$apollo.mutate({
mutation: createGroupMutation(),
variables,
update: (_store, { data }) => {
const { id: groupId, slug: groupSlug } = data.CreateGroup
responseId = groupId
responseSlug = groupSlug
},
})
this.$toast.success(this.$t('group.groupCreated'))
this.$router.history.push('/my-groups')
// Wolle: refetch groups on '/my-groups'
// seems to work of its own now, because of implementation of vue apollo queries in '/my-groups'
// this.$router.history.push('/my-groups')
this.$router.history.push({
name: 'group-id-slug',
params: { id: responseId, slug: responseSlug },
})
} catch (error) {
this.$toast.error(error.message)
}

View File

@ -7,7 +7,6 @@
</ds-heading>
</ds-space>
<ds-space margin="large" />
<!-- Wolle: <ds-space margin="large"> -->
<ds-flex gutter="small">
<ds-flex-item :width="{ base: '100%', md: '200px' }">
<ds-menu :routes="routes" :is-exact="() => true" />

View File

@ -25,10 +25,9 @@ export default {
},
methods: {
async updateGroup(value) {
const { id, name, about, description, groupType, actionRadius, locationName, categoryIds } =
value
const variables = {
const {
id,
slug,
name,
about,
description,
@ -36,13 +35,34 @@ export default {
actionRadius,
locationName,
categoryIds,
} = value
const variables = {
id,
name,
slug,
about,
description,
groupType,
actionRadius,
locationName,
categoryIds,
}
let responseId, responseSlug
try {
await this.$apollo.mutate({
mutation: updateGroupMutation(),
variables,
update: (_store, { data }) => {
const { id: groupId, slug: groupSlug } = data.UpdateGroup
responseId = groupId
responseSlug = groupSlug
},
})
this.$toast.success(this.$t('group.group-updated'))
this.$router.history.push({
name: 'group-id-slug',
params: { id: responseId, slug: responseSlug },
})
} catch (error) {
this.$toast.error(error.message)
}

View File

@ -7,11 +7,11 @@
<ds-container>
<!-- create group -->
<ds-space centered>
<!-- Wolle: <client-only> -->
<nuxt-link :to="{ name: 'group-create' }">
<base-button
class="group-add-button"
icon="plus"
size="large"
circle
filled
v-tooltip="{
@ -20,10 +20,9 @@
}"
/>
</nuxt-link>
<!-- Wolle: </client-only> -->
</ds-space>
<!-- group list -->
<group-list :items="myGroups" />
<group-list :groups="myGroups" />
</ds-container>
</div>
</template>

View File

@ -31,6 +31,7 @@
{{ userName }}
</ds-heading>
<ds-text align="center" color="soft">
<base-icon name="at" />
{{ userSlug }}
</ds-text>
<ds-text v-if="user.location" align="center" color="soft" size="small">
@ -253,7 +254,7 @@ export default {
},
userSlug() {
const { slug } = this.user || {}
return slug && `@${slug}`
return slug
},
tabOptions() {
return [