Commit 8dd77ec9 authored by Jean Rabreau's avatar Jean Rabreau
Browse files

Merge branch 'release/1.0.0'

parents d28303d8 061a46f0
Pipeline #51337 passed with stages
in 5 minutes and 47 seconds
......@@ -3,3 +3,6 @@ VUE_APP_NAME=pount
VUE_APP_LOGO=https://gitlab.com/uploads/-/system/project/avatar/18299282/pount-front-neunoeuil.png
VUE_APP_AXIOS_BASE_URL=https://pount-api-pprd.app.unistra.fr/api/
VUE_APP_SENTRY_TAG=preprod
VUE_APP_MATOMO_SERVER=https://webomat-pprd.unistra.fr
VUE_APP_MATOMO_SITE_ID=16
VUE_APP_MATOMO_DEBUG=false
......@@ -3,6 +3,6 @@ VUE_APP_NAME=pount
VUE_APP_LOGO=https://gitlab.com/uploads/-/system/project/avatar/18299282/pount-front-neunoeuil.png
VUE_APP_AXIOS_BASE_URL=https://pount-api.unistra.fr/api/
VUE_APP_SENTRY_TAG=prod
VUE_APP_MATOMO_SERVER=https://matomo-test.app.unistra.fr
VUE_APP_MATOMO_SERVER=https://webomat.unistra.fr
VUE_APP_MATOMO_SITE_ID=16
VUE_APP_MATOMO_DEBUG=false
This diff is collapsed.
......@@ -18,7 +18,7 @@
"https://medium.com/sapioit/why-having-3-numbers-in-the-version-name-is-bad-92fc1f6bc73c",
"https://gist.github.com/jashkenas/cbd2b088e20279ae2c8e"
],
"version": "0.6.0",
"version": "1.0.0",
"@comment private": [
"This whole project is just documentation.",
"Don't publish it in npm as is."
......@@ -203,7 +203,7 @@
"babel-plugin-istanbul": "^6.0.0",
"chai": "^4.3.4",
"chromedriver": "^89.0.0",
"eslint": "^7.22.0",
"eslint": "^6.8.0",
"eslint-plugin-vue": "^7.8.0",
"geckodriver": "^1.22.2",
"jwt-simple": "^0.5.6",
......
<template>
<v-subheader
v-html="text"
class="d-flex flex-column align-start"
/>
</template>
<script>
export default {
name: "RichTextSubTitle",
props: {
text: {
type: String,
default: ''
}
}
}
</script>
<style lang="scss" scoped>
.v-subheader::v-deep {
height: unset;
p {
margin: 0;
}
}
</style>
<template>
<v-card>
<v-form v-model="valid" ref="form">
<v-card-title>
{{ $t('group.new') }}
</v-card-title>
<v-card-text>
<v-row no-gutters>
<v-col cols="12" sm="4">
<v-text-field
:label="$t('form.field.name')"
v-model="group.name"
:rules="requiredField"
/>
</v-col>
<v-col cols="12" sm="6" :offset="isMobile ? 0 : 2">
<rich-text
:label="$t('form.field.description')"
v-model="group.description"
/>
</v-col>
</v-row>
<v-select
:label="$t('group.members.project')"
v-model="group.userIds"
:items="projectMembers"
item-text="name"
item-value="id"
multiple
/>
<v-select
:label="$t('group.sets')"
v-model="group.setIds"
:items="projectSets"
item-text="title"
item-value="id"
multiple
/>
</v-card-text>
<v-card-actions>
<i-button
b-small
@activate="createGroup"
:aria="$t('btn.validate')"
icon="mdi-check"
color="success"
/>
<v-spacer></v-spacer>
<i-button
b-small
@activate="cancel"
:aria="$t('btn.cancel')"
icon="mdi-close"
color="error"
/>
</v-card-actions>
</v-form>
</v-card>
</template>
<script>
import RulesMixin from '@/mixins/RulesMixin'
import MobileMixin from '@/mixins/MobileMixin'
import RichText from '@/components/common/RichTextInput'
import {mapGetters} from 'vuex'
export default {
name: "GroupCreationCard",
components: {RichText},
mixins: [RulesMixin, MobileMixin],
data: () => ({
valid: true,
group: {
name: '',
description: '',
userIds: [],
setIds: [],
}
}),
computed: {
...mapGetters('group', ['projectMembers']),
...mapGetters('set', ['projectSets']),
},
methods: {
createGroup() {
if (this.$refs.form.validate()) {
this.$store.dispatch('group/createGroup', this.group)
this.cancel()
}
},
cancel() {
this.$refs.form.reset()
this.$emit('close')
}
}
}
</script>
<style lang="scss" scoped>
</style>
<template>
<v-col cols="12" md="6" class="d-flex flex-column pb-2 pr-2">
<v-card class="flex d-flex flex-column">
<v-card-title>
<v-row no-gutters class="ma-0" align="start">
<v-col cols="3">
<avatar :name='group.name' :size=64 />
</v-col>
<v-col cols="9" lg="8" offset-lg="1">
<v-card
v-if="editionMode"
color="transparent"
elevation="0"
>
<v-text-field
:label="$t('form.field.description')"
v-model="edit.name"
:rules="requiredField"
/>
<rich-text
:label="$t('form.field.name')"
v-model="edit.description"
rows="2"
auto-grow
/>
<v-card-actions>
<i-button
b-small
color="success"
@activate="validEdition"
icon="mdi-check"
/>
<v-spacer></v-spacer>
<i-button
b-small
color='error'
@activate="editionMode= false"
icon="mdi-close"
/>
</v-card-actions>
</v-card>
<template v-else>
<h2>
{{ name }}
<v-icon
v-if="isEditable"
@click="startEditing"
>
mdi-pencil-outline
</v-icon>
</h2>
<description v-if="isEditable" :text="group.description" />
</template>
</v-col>
</v-row>
</v-card-title>
<v-card-text>
<i-button
class="mr-1"
b-small
@activate="manageMembers = true"
:aria="$t('group.members.edit')"
icon="mdi-account-supervisor"
/>
{{ $tc('group.members.count', group.users.length) }}
<v-dialog v-model="manageMembers">
<component
:is="UserEditionComponent"
@close="manageMembers = false"
:group="group"
:title="name"
/>
</v-dialog>
</v-card-text>
<v-card-actions v-if="isEditable">
<i-button
b-small
@activate="editRight = true"
:aria="$t('group.manage.permissions')"
icon="mdi-shield-key-outline"
/>
<v-dialog v-model="editRight">
<right-edition
:group-id="group.id"
:contributions="group.sets"
@close="editRight = false"
/>
</v-dialog>
<v-spacer></v-spacer>
<i-button
b-small
color='error'
@activate="deleteConfirm = true"
:aria="$t(`btn.delete`)"
icon="mdi-trash-can-outline"
/>
<confirm-box
:dialog.sync="deleteConfirm"
action="delete"
:object="$t('group.designate', {name})"
@confirm="removeGroup"
/>
</v-card-actions>
</v-card>
</v-col>
</template>
<script>
import { mapActions } from 'vuex'
import Avatar from '@/components/common/Avatar.vue'
import Description from '@/components/common/Description'
import MobileMixin from '@/mixins/MobileMixin'
import RulesMixin from '@/mixins/RulesMixin'
import ConfirmBox from '@/components/common/ConfirmBox'
import {isDefaultMembers} from '@/store/group'
import {pickBy} from 'lodash/fp'
import RichText from '@/components/common/RichTextInput'
const resetField = {
name: '',
description: '',
}
export default {
name: 'GroupDisplayCard',
mixins: [MobileMixin,RulesMixin],
components: {
RichText,
Avatar,
Description,
ConfirmBox,
EditUsers: () => import(/* webpackChunkName "project-manager" */ '@/components/group/EditGoupUsers'),
MembersEdition: () => import(/* webpackChunkName "project-manager" */ '@/components/group/EditProjectMembers'),
RightEdition: () => import(/* webpackChunkName "project-manager" */ '@/components/group/RightEdition'),
},
props: {
group: {
type: Object,
required: true
}
},
data: () => ({
editionMode: false,
edit: { ...resetField },
editRight: false,
deleteConfirm: false,
manageMembers: false
}),
computed: {
isEditable () {
return !this.group.isDefaultGroup
},
UserEditionComponent() {
return isDefaultMembers(this.group) ? 'MembersEdition' : 'EditUsers'
},
name() {
return this.isEditable
? this.group.name
: this.$t(`group.default.${this.group.name.toLowerCase()}`)
},
textareaLabel () {
let label = this.group.description.slice(0,20)
if (this.group.description.length > 20){
label = `${label}...`
}
return label
}
},
methods: {
...mapActions('group', ['updateGroup', 'deleteGroup']),
removeGroup() {
this.deleteGroup(this.group.id).then(() => {
this.deleteConfirm = false
})
},
validEdition() {
const changedFields = pickBy(
(value, key) => value !== this.group[key]
&& (key!=='name' || !!this.edit.name),
this.edit
)
if (Object.keys(changedFields).length) {
this.updateGroup({id: this.group.id, ...changedFields})
}
this.editionMode = false
},
startEditing() {
this.editionMode = true
const { name, description } = this.group
this.edit = { name, description }
}
},
};
</script>
<style lang="scss" scoped>
.v-card{
//overflow-wrap: normal;
.v-card__title {
h2{
word-break: normal;
}
.v-subheader{
line-height: normal;
}
}
.v-card__actions {
padding: 0 16px 16px;
}
}
</style>
<template>
<v-card>
<v-card-title>
<h2>{{ title }}</h2>
</v-card-title>
<v-card-text>
<v-row>
<v-col cols="12" sm="6">
<h2>{{$t('group.members.actual')}}</h2>
<v-subheader v-if="group.users.length">
{{$t('group.members.remove')}}
</v-subheader>
<user-list
:users="group.users"
color="success"
@activate="removeUser"
>
</user-list>
</v-col>
<v-col cols="12" sm="6">
<h2>{{$t('group.members.available')}}</h2>
<v-subheader v-if="availableUsers.length">
{{$t('group.members.add')}}
</v-subheader>
<user-list
:users="availableUsers"
@activate="addUser"
/>
</v-col>
</v-row>
</v-card-text>
<v-card-actions>
<v-spacer></v-spacer>
<i-button
b-small
@activate="$emit('close')"
:aria="$t('btn.cancel')"
icon="mdi-close"
color="error"
/>
</v-card-actions>
</v-card>
</template>
<script>
import {mapActions, mapGetters} from 'vuex'
import UserList from '@/components/group/UserList'
export default {
name: "UsersEditionCard",
components: {
UserList
},
props: {
title: {
type: String,
default: () => this.group.name
},
group: {
type: Object,
required: true
},
},
data: () => ({
}),
created() {
},
computed: {
...mapGetters('group', ['projectMembers']),
availableUsers() {
return this.projectMembers.filter(m => !this.group.users.some(user => m.id === user.id))
},
},
methods: {
...mapActions('group', ['removeGroupUser', 'addGroupUser']),
removeUser(user) {
this.removeGroupUser({group: this.group, userId: user.id})
},
addUser(user) {
this.addGroupUser({group: this.group, user})
}
}
}
</script>
<style lang="scss" scoped>
</style>
<template>
<v-card>
<v-card-title>
<h2>{{ title }}</h2>
</v-card-title>
<v-row no-gutters>
<v-col cols="12" sm="6">
<v-card
color="transparent"
height="100%"
elevation="0"
>
<v-card-title>
<h3>{{$t('group.members.actual')}}</h3>
</v-card-title>
<v-card-subtitle v-if="group.users.length">
{{$t('group.members.remove')}}
</v-card-subtitle>
<v-card-text height="100%">
<user-list
:users="group.users"
color="success"
:must-confirm="true"
@activate="removeUser"
/>
</v-card-text>
</v-card>
</v-col>
<v-col cols="12" sm="6">
<v-card
class="d-flex flex-column"
color="transparent"
height="100%"
elevation="0"
>
<v-card-title>
<h3>{{ $t('invite.title') }}</h3>
</v-card-title>
<v-card-text>
<v-tabs v-model="isExternal">
<v-tab>{{ $t('user.internal') }}</v-tab>
<v-tab>{{ $t('user.external') }}</v-tab>
</v-tabs>
<v-tabs-items :value="0">
<v-tab-item>
<v-form ref="form" lazy-validation v-model="isValid">
<v-text-field
v-model="invitedField"
:label="inviteLabel"
required
:rules="requiredField"
/>
</v-form>
</v-tab-item>
</v-tabs-items>
</v-card-text>
<v-spacer></v-spacer>
<v-card-actions>
<v-btn color="primary" outlined @click="onInvite">
<v-icon left>{{ inviteIcon }}</v-icon>
{{ $t('group.members.invite') }}
</v-btn>
<v-spacer></v-spacer>
<i-button
b-small
@activate="$emit('close')"
:aria="$t('btn.cancel')"
icon="mdi-close"
color="error"
/>
</v-card-actions>
</v-card>
</v-col>
</v-row>
</v-card>
</template>
<script>
import {mapActions, mapGetters} from 'vuex'
import UserList from '@/components/group/UserList'
import rulesMixin from '@/mixins/RulesMixin'
export default {
name: "EditMembers",
mixins: [rulesMixin],
components: {
UserList
},
props: {
title: {
type: String,
default: () => this.group.name
},
group: {
type: Object,
required: true
},
},
data: () => ({
isValid: false,
isExternal: 0,
invitedField: '',
}),
created() {
},
computed: {
...mapGetters('project', ['projectId']),
inviteLabel() {
return this.isExternal ? 'email' : 'username'
},
inviteIcon() {
return `mdi-${this.isExternal?'email-send-outline':'arrow-left'}`
},
},
methods: {
...mapActions('group', ['removeGroupUser']),
removeUser(user) {
this.removeGroupUser({group: this.group, userId: user.id})
},
onInvite(){
if (this.$refs.form.validate()) {
const action = this.isExternal
? 'inviteExternalUser'
: 'inviteInternalUser'
const invitation = Object.fromEntries([
['groupId', this.group.id],
['projectId', this.projectId],
[this.inviteLabel, this.invitedField],
])
this.$store.dispatch(`contrib/${action}`, invitation).then(() => {
this.$refs.form.reset()
})
}
},
}
}