mirror of
https://github.com/IT4Change/gradido.git
synced 2025-12-13 07:45:54 +00:00
Merge pull request #2164 from gradido/Contribution-Messages
feat: 🍰 Contribution Messages In Frontend
This commit is contained in:
commit
5b073950fb
@ -0,0 +1,111 @@
|
||||
import { mount } from '@vue/test-utils'
|
||||
import ContributionMessagesFormular from './ContributionMessagesFormular.vue'
|
||||
import { toastErrorSpy, toastSuccessSpy } from '../../../test/testSetup'
|
||||
|
||||
const localVue = global.localVue
|
||||
|
||||
const apolloMutateMock = jest.fn().mockResolvedValue()
|
||||
|
||||
describe('ContributionMessagesFormular', () => {
|
||||
let wrapper
|
||||
|
||||
const propsData = {
|
||||
contributionId: 42,
|
||||
}
|
||||
|
||||
const mocks = {
|
||||
$t: jest.fn((t) => t),
|
||||
$apollo: {
|
||||
mutate: apolloMutateMock,
|
||||
},
|
||||
$i18n: {
|
||||
locale: 'en',
|
||||
},
|
||||
}
|
||||
|
||||
const Wrapper = () => {
|
||||
return mount(ContributionMessagesFormular, {
|
||||
localVue,
|
||||
mocks,
|
||||
propsData,
|
||||
})
|
||||
}
|
||||
|
||||
describe('mount', () => {
|
||||
beforeEach(() => {
|
||||
wrapper = Wrapper()
|
||||
})
|
||||
|
||||
it('has a DIV .contribution-messages-formular', () => {
|
||||
expect(wrapper.find('div.contribution-messages-formular').exists()).toBe(true)
|
||||
})
|
||||
|
||||
describe('on trigger reset', () => {
|
||||
beforeEach(async () => {
|
||||
wrapper.setData({
|
||||
form: {
|
||||
text: 'text form message',
|
||||
},
|
||||
})
|
||||
await wrapper.find('form').trigger('reset')
|
||||
})
|
||||
|
||||
it('form has empty text', () => {
|
||||
expect(wrapper.vm.form).toEqual({
|
||||
text: '',
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe('on trigger submit', () => {
|
||||
beforeEach(async () => {
|
||||
wrapper.setData({
|
||||
form: {
|
||||
text: 'text form message',
|
||||
},
|
||||
})
|
||||
await wrapper.find('form').trigger('submit')
|
||||
})
|
||||
|
||||
it('emitted "get-list-contribution-messages" with data', async () => {
|
||||
expect(wrapper.emitted('get-list-contribution-messages')).toEqual(
|
||||
expect.arrayContaining([expect.arrayContaining([42])]),
|
||||
)
|
||||
})
|
||||
|
||||
it('emitted "update-state" with data', async () => {
|
||||
expect(wrapper.emitted('update-state')).toEqual(
|
||||
expect.arrayContaining([expect.arrayContaining([42])]),
|
||||
)
|
||||
})
|
||||
})
|
||||
|
||||
describe('send contribution message with error', () => {
|
||||
beforeEach(async () => {
|
||||
apolloMutateMock.mockRejectedValue({ message: 'OUCH!' })
|
||||
wrapper = Wrapper()
|
||||
await wrapper.find('form').trigger('submit')
|
||||
})
|
||||
|
||||
it('toasts an error message', () => {
|
||||
expect(toastErrorSpy).toBeCalledWith('OUCH!')
|
||||
})
|
||||
})
|
||||
|
||||
describe('send contribution message with success', () => {
|
||||
beforeEach(async () => {
|
||||
wrapper.setData({
|
||||
form: {
|
||||
text: 'text form message',
|
||||
},
|
||||
})
|
||||
wrapper = Wrapper()
|
||||
await wrapper.find('form').trigger('submit')
|
||||
})
|
||||
|
||||
it('toasts an success message', () => {
|
||||
expect(toastSuccessSpy).toBeCalledWith('message.request')
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
@ -0,0 +1,67 @@
|
||||
<template>
|
||||
<div class="contribution-messages-formular">
|
||||
<div>
|
||||
<b-form @submit.prevent="onSubmit" @reset.prevent="onReset">
|
||||
<b-form-textarea
|
||||
id="textarea"
|
||||
v-model="form.text"
|
||||
:placeholder="$t('contributionLink.memo')"
|
||||
rows="3"
|
||||
max-rows="6"
|
||||
></b-form-textarea>
|
||||
<b-row class="mt-4 mb-6">
|
||||
<b-col>
|
||||
<b-button type="reset" variant="danger">{{ $t('form.cancel') }}</b-button>
|
||||
</b-col>
|
||||
<b-col class="text-right">
|
||||
<b-button type="submit" variant="primary">{{ $t('form.submit') }}</b-button>
|
||||
</b-col>
|
||||
</b-row>
|
||||
</b-form>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<script>
|
||||
import { adminCreateContributionMessage } from '@/graphql/adminCreateContributionMessage'
|
||||
|
||||
export default {
|
||||
name: 'ContributionMessagesFormular',
|
||||
props: {
|
||||
contributionId: {
|
||||
type: Number,
|
||||
required: true,
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
form: {
|
||||
text: '',
|
||||
},
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
onSubmit(event) {
|
||||
this.$apollo
|
||||
.mutate({
|
||||
mutation: adminCreateContributionMessage,
|
||||
variables: {
|
||||
contributionId: this.contributionId,
|
||||
message: this.form.text,
|
||||
},
|
||||
})
|
||||
.then((result) => {
|
||||
this.$emit('get-list-contribution-messages', this.contributionId)
|
||||
this.$emit('update-state', this.contributionId)
|
||||
this.form.text = ''
|
||||
this.toastSuccess(this.$t('message.request'))
|
||||
})
|
||||
.catch((error) => {
|
||||
this.toastError(error.message)
|
||||
})
|
||||
},
|
||||
onReset(event) {
|
||||
this.form.text = ''
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
@ -0,0 +1,56 @@
|
||||
import { mount } from '@vue/test-utils'
|
||||
import ContributionMessagesList from './ContributionMessagesList.vue'
|
||||
|
||||
const localVue = global.localVue
|
||||
|
||||
const apolloQueryMock = jest.fn().mockResolvedValue()
|
||||
|
||||
describe('ContributionMessagesList', () => {
|
||||
let wrapper
|
||||
|
||||
const propsData = {
|
||||
contributionId: 42,
|
||||
}
|
||||
|
||||
const mocks = {
|
||||
$t: jest.fn((t) => t),
|
||||
$i18n: {
|
||||
locale: 'en',
|
||||
},
|
||||
$apollo: {
|
||||
query: apolloQueryMock,
|
||||
},
|
||||
}
|
||||
|
||||
const Wrapper = () => {
|
||||
return mount(ContributionMessagesList, {
|
||||
localVue,
|
||||
mocks,
|
||||
propsData,
|
||||
})
|
||||
}
|
||||
|
||||
describe('mount', () => {
|
||||
beforeEach(() => {
|
||||
wrapper = Wrapper()
|
||||
})
|
||||
|
||||
it('sends query to Apollo when created', () => {
|
||||
expect(apolloQueryMock).toBeCalledWith(
|
||||
expect.objectContaining({
|
||||
variables: {
|
||||
contributionId: propsData.contributionId,
|
||||
},
|
||||
}),
|
||||
)
|
||||
})
|
||||
|
||||
it('has a DIV .contribution-messages-list', () => {
|
||||
expect(wrapper.find('div.contribution-messages-list').exists()).toBe(true)
|
||||
})
|
||||
|
||||
it('has a Component ContributionMessagesFormular', () => {
|
||||
expect(wrapper.findComponent({ name: 'ContributionMessagesFormular' }).exists()).toBe(true)
|
||||
})
|
||||
})
|
||||
})
|
||||
@ -0,0 +1,69 @@
|
||||
<template>
|
||||
<div class="contribution-messages-list">
|
||||
<b-container>
|
||||
{{ messages.lenght }}
|
||||
<div v-for="message in messages" v-bind:key="message.id">
|
||||
<contribution-messages-list-item :message="message" />
|
||||
</div>
|
||||
</b-container>
|
||||
|
||||
<contribution-messages-formular
|
||||
:contributionId="contributionId"
|
||||
@get-list-contribution-messages="getListContributionMessages"
|
||||
@update-state="updateState"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
<script>
|
||||
import ContributionMessagesListItem from './slots/ContributionMessagesListItem.vue'
|
||||
import ContributionMessagesFormular from '../ContributionMessages/ContributionMessagesFormular.vue'
|
||||
import { listContributionMessages } from '../../graphql/listContributionMessages.js'
|
||||
|
||||
export default {
|
||||
name: 'ContributionMessagesList',
|
||||
components: {
|
||||
ContributionMessagesListItem,
|
||||
ContributionMessagesFormular,
|
||||
},
|
||||
props: {
|
||||
contributionId: {
|
||||
type: Number,
|
||||
required: true,
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
messages: [],
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
getListContributionMessages(id) {
|
||||
this.$apollo
|
||||
.query({
|
||||
query: listContributionMessages,
|
||||
variables: {
|
||||
contributionId: id,
|
||||
},
|
||||
fetchPolicy: 'no-cache',
|
||||
})
|
||||
.then((result) => {
|
||||
this.messages = result.data.listContributionMessages.messages
|
||||
})
|
||||
.catch((error) => {
|
||||
this.toastError(error.message)
|
||||
})
|
||||
},
|
||||
updateState(id) {
|
||||
this.$emit('update-state', id)
|
||||
},
|
||||
},
|
||||
created() {
|
||||
this.getListContributionMessages(this.contributionId)
|
||||
},
|
||||
}
|
||||
</script>
|
||||
<style scoped>
|
||||
.temp-message {
|
||||
margin-top: 50px;
|
||||
}
|
||||
</style>
|
||||
@ -0,0 +1,58 @@
|
||||
import { mount } from '@vue/test-utils'
|
||||
import ContributionMessagesListItem from './ContributionMessagesListItem.vue'
|
||||
|
||||
const localVue = global.localVue
|
||||
|
||||
describe('ContributionMessagesListItem', () => {
|
||||
let wrapper
|
||||
|
||||
const mocks = {
|
||||
$t: jest.fn((t) => t),
|
||||
$d: jest.fn((d) => d),
|
||||
$store: {
|
||||
state: {
|
||||
moderator: {
|
||||
id: 107,
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
const propsData = {
|
||||
contributionId: 42,
|
||||
state: 'PENDING0',
|
||||
message: {
|
||||
id: 111,
|
||||
message: 'asd asda sda sda',
|
||||
createdAt: '2022-08-29T12:23:27.000Z',
|
||||
updatedAt: null,
|
||||
type: 'DIALOG',
|
||||
userFirstName: 'Peter',
|
||||
userLastName: 'Lustig',
|
||||
userId: 107,
|
||||
__typename: 'ContributionMessage',
|
||||
},
|
||||
}
|
||||
|
||||
const Wrapper = () => {
|
||||
return mount(ContributionMessagesListItem, {
|
||||
localVue,
|
||||
mocks,
|
||||
propsData,
|
||||
})
|
||||
}
|
||||
|
||||
describe('mount', () => {
|
||||
beforeEach(() => {
|
||||
wrapper = Wrapper()
|
||||
})
|
||||
|
||||
it('has a DIV .contribution-messages-list-item', () => {
|
||||
expect(wrapper.find('div.contribution-messages-list-item').exists()).toBe(true)
|
||||
})
|
||||
|
||||
it('props.message.default', () => {
|
||||
expect(wrapper.vm.$options.props.message.default.call()).toEqual({})
|
||||
})
|
||||
})
|
||||
})
|
||||
@ -0,0 +1,32 @@
|
||||
<template>
|
||||
<div class="contribution-messages-list-item">
|
||||
<is-moderator v-if="isModerator" :message="message"></is-moderator>
|
||||
<is-not-moderator v-else :message="message"></is-not-moderator>
|
||||
</div>
|
||||
</template>
|
||||
<script>
|
||||
import IsModerator from '@/components/ContributionMessages/slots/IsModerator.vue'
|
||||
import IsNotModerator from '@/components/ContributionMessages/slots/IsNotModerator.vue'
|
||||
|
||||
export default {
|
||||
name: 'ContributionMessagesListItem',
|
||||
components: {
|
||||
IsModerator,
|
||||
IsNotModerator,
|
||||
},
|
||||
props: {
|
||||
message: {
|
||||
type: Object,
|
||||
required: true,
|
||||
default() {
|
||||
return {}
|
||||
},
|
||||
},
|
||||
},
|
||||
computed: {
|
||||
isModerator() {
|
||||
return this.$store.state.moderator.id === this.message.userId
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
@ -0,0 +1,49 @@
|
||||
import { mount } from '@vue/test-utils'
|
||||
import IsModerator from './IsModerator.vue'
|
||||
|
||||
const localVue = global.localVue
|
||||
|
||||
describe('IsModerator', () => {
|
||||
let wrapper
|
||||
|
||||
const mocks = {
|
||||
$t: jest.fn((t) => t),
|
||||
$d: jest.fn((d) => d),
|
||||
}
|
||||
|
||||
const propsData = {
|
||||
message: {
|
||||
id: 111,
|
||||
message: 'asd asda sda sda',
|
||||
createdAt: '2022-08-29T12:23:27.000Z',
|
||||
updatedAt: null,
|
||||
type: 'DIALOG',
|
||||
userFirstName: 'Peter',
|
||||
userLastName: 'Lustig',
|
||||
userId: 107,
|
||||
__typename: 'ContributionMessage',
|
||||
},
|
||||
}
|
||||
|
||||
const Wrapper = () => {
|
||||
return mount(IsModerator, {
|
||||
localVue,
|
||||
mocks,
|
||||
propsData,
|
||||
})
|
||||
}
|
||||
|
||||
describe('mount', () => {
|
||||
beforeEach(() => {
|
||||
wrapper = Wrapper()
|
||||
})
|
||||
|
||||
it('has a DIV .slot-is-moderator', () => {
|
||||
expect(wrapper.find('div.slot-is-moderator').exists()).toBe(true)
|
||||
})
|
||||
|
||||
it('props.message.default', () => {
|
||||
expect(wrapper.vm.$options.props.message.default.call()).toEqual({})
|
||||
})
|
||||
})
|
||||
})
|
||||
@ -0,0 +1,37 @@
|
||||
<template>
|
||||
<div class="slot-is-moderator">
|
||||
<div class="text-right">
|
||||
<b-avatar square :text="initialLetters" variant="warning"></b-avatar>
|
||||
<span class="ml-2 mr-2">{{ message.userFirstName }} {{ message.userLastName }}</span>
|
||||
<span class="ml-2">{{ $d(new Date(message.createdAt), 'short') }}</span>
|
||||
<small class="ml-4 text-success">{{ $t('moderator') }}</small>
|
||||
<div class="mt-2 text-bold h4">{{ message.message }}</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<script>
|
||||
export default {
|
||||
props: {
|
||||
message: {
|
||||
type: Object,
|
||||
default() {
|
||||
return {}
|
||||
},
|
||||
},
|
||||
},
|
||||
computed: {
|
||||
initialLetters() {
|
||||
return `${this.message.userFirstName[0]} ${this.message.userLastName[0]}`
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
<style>
|
||||
.slot-is-moderator {
|
||||
clear: both;
|
||||
float: right;
|
||||
width: 75%;
|
||||
margin-top: 20px;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
</style>
|
||||
@ -0,0 +1,49 @@
|
||||
import { mount } from '@vue/test-utils'
|
||||
import IsNotModerator from './IsNotModerator.vue'
|
||||
|
||||
const localVue = global.localVue
|
||||
|
||||
describe('IsNotModerator', () => {
|
||||
let wrapper
|
||||
|
||||
const mocks = {
|
||||
$t: jest.fn((t) => t),
|
||||
$d: jest.fn((d) => d),
|
||||
}
|
||||
|
||||
const propsData = {
|
||||
message: {
|
||||
id: 113,
|
||||
message: 'asda sdad ad asdasd ',
|
||||
createdAt: '2022-08-29T12:25:34.000Z',
|
||||
updatedAt: null,
|
||||
type: 'DIALOG',
|
||||
userFirstName: 'Bibi',
|
||||
userLastName: 'Bloxberg',
|
||||
userId: 108,
|
||||
__typename: 'ContributionMessage',
|
||||
},
|
||||
}
|
||||
|
||||
const Wrapper = () => {
|
||||
return mount(IsNotModerator, {
|
||||
localVue,
|
||||
mocks,
|
||||
propsData,
|
||||
})
|
||||
}
|
||||
|
||||
describe('mount', () => {
|
||||
beforeEach(() => {
|
||||
wrapper = Wrapper()
|
||||
})
|
||||
|
||||
it('has a DIV .slot-is-not-moderator', () => {
|
||||
expect(wrapper.find('div.slot-is-not-moderator').exists()).toBe(true)
|
||||
})
|
||||
|
||||
it('props.message.default', () => {
|
||||
expect(wrapper.vm.$options.props.message.default.call()).toEqual({})
|
||||
})
|
||||
})
|
||||
})
|
||||
@ -0,0 +1,36 @@
|
||||
<template>
|
||||
<div class="slot-is-not-moderator">
|
||||
<div>
|
||||
<b-avatar :text="initialLetters" variant="info"></b-avatar>
|
||||
<span class="ml-2 mr-2 text-bold">
|
||||
{{ message.userFirstName }} {{ message.userLastName }}
|
||||
</span>
|
||||
<span class="ml-2">{{ $d(new Date(message.createdAt), 'short') }}</span>
|
||||
<div class="mt-2 text-bold h4">{{ message.message }}</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<script>
|
||||
export default {
|
||||
props: {
|
||||
message: {
|
||||
type: Object,
|
||||
default() {
|
||||
return {}
|
||||
},
|
||||
},
|
||||
},
|
||||
computed: {
|
||||
initialLetters() {
|
||||
return `${this.message.userFirstName[0]} ${this.message.userLastName[0]}`
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
<style>
|
||||
.slot-is-not-moderator {
|
||||
clear: both;
|
||||
width: 75%;
|
||||
margin-top: 20px;
|
||||
}
|
||||
</style>
|
||||
@ -21,6 +21,19 @@
|
||||
>
|
||||
<b-icon :icon="row.detailsShowing ? 'x' : 'pencil-square'" aria-label="Help"></b-icon>
|
||||
</b-button>
|
||||
<b-button v-else @click="rowToggleDetails(row, 0)">
|
||||
<b-icon icon="chat-dots"></b-icon>
|
||||
<b-icon
|
||||
v-if="row.item.state === 'PENDING' && row.item.messageCount > 0"
|
||||
icon="exclamation-circle-fill"
|
||||
variant="warning"
|
||||
></b-icon>
|
||||
<b-icon
|
||||
v-if="row.item.state === 'IN_PROGRESS' && row.item.messageCount > 0"
|
||||
icon="question-diamond"
|
||||
variant="light"
|
||||
></b-icon>
|
||||
</b-button>
|
||||
</template>
|
||||
<template #cell(confirm)="row">
|
||||
<b-button variant="success" size="md" @click="$emit('show-overlay', row.item)" class="mr-2">
|
||||
@ -33,10 +46,10 @@
|
||||
type="show-creation"
|
||||
slotName="show-creation"
|
||||
:index="0"
|
||||
@row-toggle-details="rowToggleDetails"
|
||||
@row-toggle-details="rowToggleDetails(row, 0)"
|
||||
>
|
||||
<template #show-creation>
|
||||
<div>
|
||||
<div v-if="row.item.moderator">
|
||||
<edit-creation-formular
|
||||
type="singleCreation"
|
||||
:creation="row.item.creation"
|
||||
@ -44,6 +57,12 @@
|
||||
:row="row"
|
||||
:creationUserData="creationUserData"
|
||||
@update-creation-data="updateCreationData"
|
||||
/>
|
||||
</div>
|
||||
<div v-else>
|
||||
<contribution-messages-list
|
||||
:contributionId="row.item.id"
|
||||
@update-state="updateState"
|
||||
@update-user-data="updateUserData"
|
||||
/>
|
||||
</div>
|
||||
@ -58,6 +77,7 @@
|
||||
import { toggleRowDetails } from '../../mixins/toggleRowDetails'
|
||||
import RowDetails from '../RowDetails.vue'
|
||||
import EditCreationFormular from '../EditCreationFormular.vue'
|
||||
import ContributionMessagesList from '../ContributionMessages/ContributionMessagesList.vue'
|
||||
|
||||
export default {
|
||||
name: 'OpenCreationsTable',
|
||||
@ -65,6 +85,7 @@ export default {
|
||||
components: {
|
||||
EditCreationFormular,
|
||||
RowDetails,
|
||||
ContributionMessagesList,
|
||||
},
|
||||
props: {
|
||||
items: {
|
||||
@ -98,6 +119,9 @@ export default {
|
||||
updateUserData(rowItem, newCreation) {
|
||||
rowItem.creation = newCreation
|
||||
},
|
||||
updateState(id) {
|
||||
this.$emit('update-state', id)
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
15
admin/src/graphql/adminCreateContributionMessage.js
Normal file
15
admin/src/graphql/adminCreateContributionMessage.js
Normal file
@ -0,0 +1,15 @@
|
||||
import gql from 'graphql-tag'
|
||||
|
||||
export const adminCreateContributionMessage = gql`
|
||||
mutation ($contributionId: Float!, $message: String!) {
|
||||
adminCreateContributionMessage(contributionId: $contributionId, message: $message) {
|
||||
id
|
||||
message
|
||||
createdAt
|
||||
updatedAt
|
||||
type
|
||||
userFirstName
|
||||
userLastName
|
||||
}
|
||||
}
|
||||
`
|
||||
24
admin/src/graphql/listContributionMessages.js
Normal file
24
admin/src/graphql/listContributionMessages.js
Normal file
@ -0,0 +1,24 @@
|
||||
import gql from 'graphql-tag'
|
||||
|
||||
export const listContributionMessages = gql`
|
||||
query ($contributionId: Float!, $pageSize: Int = 25, $currentPage: Int = 1, $order: Order = ASC) {
|
||||
listContributionMessages(
|
||||
contributionId: $contributionId
|
||||
pageSize: $pageSize
|
||||
currentPage: $currentPage
|
||||
order: $order
|
||||
) {
|
||||
count
|
||||
messages {
|
||||
id
|
||||
message
|
||||
createdAt
|
||||
updatedAt
|
||||
type
|
||||
userFirstName
|
||||
userLastName
|
||||
userId
|
||||
}
|
||||
}
|
||||
}
|
||||
`
|
||||
@ -12,6 +12,8 @@ export const listUnconfirmedContributions = gql`
|
||||
date
|
||||
moderator
|
||||
creation
|
||||
state
|
||||
messageCount
|
||||
}
|
||||
}
|
||||
`
|
||||
|
||||
@ -69,6 +69,10 @@
|
||||
},
|
||||
"short_hash": "({shortHash})"
|
||||
},
|
||||
"form": {
|
||||
"cancel": "Abbrechen",
|
||||
"submit": "Senden"
|
||||
},
|
||||
"GDD": "GDD",
|
||||
"hide_details": "Details verbergen",
|
||||
"lastname": "Nachname",
|
||||
@ -78,6 +82,9 @@
|
||||
"pipe": "|",
|
||||
"plus": "+"
|
||||
},
|
||||
"message": {
|
||||
"request": "Die Anfrage wurde gesendet."
|
||||
},
|
||||
"moderator": "Moderator",
|
||||
"multiple_creation_text": "Bitte wähle ein oder mehrere Mitglieder aus für die du Schöpfen möchtest.",
|
||||
"name": "Name",
|
||||
|
||||
@ -69,6 +69,10 @@
|
||||
},
|
||||
"short_hash": "({shortHash})"
|
||||
},
|
||||
"form": {
|
||||
"cancel": "Cancel",
|
||||
"submit": "Send"
|
||||
},
|
||||
"GDD": "GDD",
|
||||
"hide_details": "Hide details",
|
||||
"lastname": "Lastname",
|
||||
@ -78,6 +82,9 @@
|
||||
"pipe": "|",
|
||||
"plus": "+"
|
||||
},
|
||||
"message": {
|
||||
"request": "Request has been sent."
|
||||
},
|
||||
"moderator": "Moderator",
|
||||
"multiple_creation_text": "Please select one or more members for which you would like to perform creations.",
|
||||
"name": "Name",
|
||||
|
||||
@ -9,6 +9,7 @@
|
||||
:fields="fields"
|
||||
@remove-creation="removeCreation"
|
||||
@show-overlay="showOverlay"
|
||||
@update-state="updateState"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
@ -93,6 +94,10 @@ export default {
|
||||
this.overlay = true
|
||||
this.item = item
|
||||
},
|
||||
updateState(id) {
|
||||
this.pendingCreations.find((obj) => obj.id === id).messagesCount++
|
||||
this.pendingCreations.find((obj) => obj.id === id).state = 'IN_PROGRESS'
|
||||
},
|
||||
},
|
||||
computed: {
|
||||
fields() {
|
||||
|
||||
@ -0,0 +1,111 @@
|
||||
import { mount } from '@vue/test-utils'
|
||||
import ContributionMessagesFormular from './ContributionMessagesFormular.vue'
|
||||
import { toastErrorSpy, toastSuccessSpy } from '../../../test/testSetup'
|
||||
|
||||
const localVue = global.localVue
|
||||
|
||||
const apolloMutateMock = jest.fn().mockResolvedValue()
|
||||
|
||||
describe('ContributionMessagesFormular', () => {
|
||||
let wrapper
|
||||
|
||||
const propsData = {
|
||||
contributionId: 42,
|
||||
}
|
||||
|
||||
const mocks = {
|
||||
$t: jest.fn((t) => t),
|
||||
$apollo: {
|
||||
mutate: apolloMutateMock,
|
||||
},
|
||||
$i18n: {
|
||||
locale: 'en',
|
||||
},
|
||||
}
|
||||
|
||||
const Wrapper = () => {
|
||||
return mount(ContributionMessagesFormular, {
|
||||
localVue,
|
||||
mocks,
|
||||
propsData,
|
||||
})
|
||||
}
|
||||
|
||||
describe('mount', () => {
|
||||
beforeEach(() => {
|
||||
wrapper = Wrapper()
|
||||
})
|
||||
|
||||
it('has a DIV .contribution-messages-formular', () => {
|
||||
expect(wrapper.find('div.contribution-messages-formular').exists()).toBe(true)
|
||||
})
|
||||
|
||||
describe('on trigger reset', () => {
|
||||
beforeEach(async () => {
|
||||
wrapper.setData({
|
||||
form: {
|
||||
text: 'text form message',
|
||||
},
|
||||
})
|
||||
await wrapper.find('form').trigger('reset')
|
||||
})
|
||||
|
||||
it('form has empty text', () => {
|
||||
expect(wrapper.vm.form).toEqual({
|
||||
text: '',
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe('on trigger submit', () => {
|
||||
beforeEach(async () => {
|
||||
wrapper.setData({
|
||||
form: {
|
||||
text: 'text form message',
|
||||
},
|
||||
})
|
||||
await wrapper.find('form').trigger('submit')
|
||||
})
|
||||
|
||||
it('emitted "get-list-contribution-messages" with data', async () => {
|
||||
expect(wrapper.emitted('get-list-contribution-messages')).toEqual(
|
||||
expect.arrayContaining([expect.arrayContaining([42])]),
|
||||
)
|
||||
})
|
||||
|
||||
it('emitted "update-state" with data', async () => {
|
||||
expect(wrapper.emitted('update-state')).toEqual(
|
||||
expect.arrayContaining([expect.arrayContaining([42])]),
|
||||
)
|
||||
})
|
||||
})
|
||||
|
||||
describe('send contribution message with error', () => {
|
||||
beforeEach(async () => {
|
||||
apolloMutateMock.mockRejectedValue({ message: 'OUCH!' })
|
||||
wrapper = Wrapper()
|
||||
await wrapper.find('form').trigger('submit')
|
||||
})
|
||||
|
||||
it('toasts an error message', () => {
|
||||
expect(toastErrorSpy).toBeCalledWith('OUCH!')
|
||||
})
|
||||
})
|
||||
|
||||
describe('send contribution message with success', () => {
|
||||
beforeEach(async () => {
|
||||
wrapper.setData({
|
||||
form: {
|
||||
text: 'text form message',
|
||||
},
|
||||
})
|
||||
wrapper = Wrapper()
|
||||
await wrapper.find('form').trigger('submit')
|
||||
})
|
||||
|
||||
it('toasts an success message', () => {
|
||||
expect(toastSuccessSpy).toBeCalledWith('message.reply')
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
@ -0,0 +1,67 @@
|
||||
<template>
|
||||
<div class="contribution-messages-formular">
|
||||
<div>
|
||||
<b-form @submit.prevent="onSubmit" @reset="onReset">
|
||||
<b-form-textarea
|
||||
id="textarea"
|
||||
v-model="form.text"
|
||||
:placeholder="$t('form.memo')"
|
||||
rows="3"
|
||||
max-rows="6"
|
||||
></b-form-textarea>
|
||||
<b-row class="mt-4 mb-6">
|
||||
<b-col>
|
||||
<b-button type="reset" variant="danger">{{ $t('form.cancel') }}</b-button>
|
||||
</b-col>
|
||||
<b-col class="text-right">
|
||||
<b-button type="submit" variant="primary">{{ $t('form.reply') }}</b-button>
|
||||
</b-col>
|
||||
</b-row>
|
||||
</b-form>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<script>
|
||||
import { createContributionMessage } from '../../graphql/mutations.js'
|
||||
|
||||
export default {
|
||||
name: 'ContributionMessagesFormular',
|
||||
props: {
|
||||
contributionId: {
|
||||
type: Number,
|
||||
required: true,
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
form: {
|
||||
text: '',
|
||||
},
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
onSubmit() {
|
||||
this.$apollo
|
||||
.mutate({
|
||||
mutation: createContributionMessage,
|
||||
variables: {
|
||||
contributionId: this.contributionId,
|
||||
message: this.form.text,
|
||||
},
|
||||
})
|
||||
.then((result) => {
|
||||
this.$emit('get-list-contribution-messages', this.contributionId)
|
||||
this.$emit('update-state', this.contributionId)
|
||||
this.form.text = ''
|
||||
this.toastSuccess(this.$t('message.reply'))
|
||||
})
|
||||
.catch((error) => {
|
||||
this.toastError(error.message)
|
||||
})
|
||||
},
|
||||
onReset() {
|
||||
this.form.text = ''
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
@ -0,0 +1,63 @@
|
||||
import { mount } from '@vue/test-utils'
|
||||
import ContributionMessagesList from './ContributionMessagesList.vue'
|
||||
|
||||
const localVue = global.localVue
|
||||
|
||||
describe('ContributionMessagesList', () => {
|
||||
let wrapper
|
||||
|
||||
const propsData = {
|
||||
contributionId: 42,
|
||||
state: 'IN_PROGRESS',
|
||||
messages: [],
|
||||
}
|
||||
|
||||
const mocks = {
|
||||
$t: jest.fn((t) => t),
|
||||
$i18n: {
|
||||
locale: 'en',
|
||||
},
|
||||
}
|
||||
|
||||
const Wrapper = () => {
|
||||
return mount(ContributionMessagesList, {
|
||||
localVue,
|
||||
mocks,
|
||||
propsData,
|
||||
})
|
||||
}
|
||||
|
||||
describe('mount', () => {
|
||||
beforeEach(() => {
|
||||
wrapper = Wrapper()
|
||||
})
|
||||
|
||||
it('has a DIV .contribution-messages-list', () => {
|
||||
expect(wrapper.find('div.contribution-messages-list').exists()).toBe(true)
|
||||
})
|
||||
|
||||
it('has a Component ContributionMessagesFormular', () => {
|
||||
expect(wrapper.findComponent({ name: 'ContributionMessagesFormular' }).exists()).toBe(true)
|
||||
})
|
||||
|
||||
describe('get List Contribution Messages', () => {
|
||||
beforeEach(() => {
|
||||
wrapper.vm.getListContributionMessages()
|
||||
})
|
||||
|
||||
it('emits getListContributionMessages', async () => {
|
||||
expect(wrapper.vm.$emit('get-list-contribution-messages')).toBeTruthy()
|
||||
})
|
||||
})
|
||||
|
||||
describe('update State', () => {
|
||||
beforeEach(() => {
|
||||
wrapper.vm.updateState()
|
||||
})
|
||||
|
||||
it('emits getListContributionMessages', async () => {
|
||||
expect(wrapper.vm.$emit('update-state')).toBeTruthy()
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
@ -0,0 +1,59 @@
|
||||
<template>
|
||||
<div class="contribution-messages-list">
|
||||
<b-container>
|
||||
<div v-for="message in messages" v-bind:key="message.id">
|
||||
<contribution-messages-list-item :message="message" />
|
||||
</div>
|
||||
</b-container>
|
||||
<contribution-messages-formular
|
||||
v-if="['PENDING', 'IN_PROGRESS'].includes(state)"
|
||||
class="mt-5"
|
||||
:contributionId="contributionId"
|
||||
@get-list-contribution-messages="getListContributionMessages"
|
||||
@update-state="updateState"
|
||||
/>
|
||||
<div v-b-toggle="'collapse' + String(contributionId)" class="text-center pointer h2">
|
||||
<b-icon icon="arrow-up-short"></b-icon>
|
||||
{{ $t('form.close') }}
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<script>
|
||||
import ContributionMessagesListItem from '@/components/ContributionMessages/ContributionMessagesListItem.vue'
|
||||
import ContributionMessagesFormular from '@/components/ContributionMessages/ContributionMessagesFormular.vue'
|
||||
|
||||
export default {
|
||||
name: 'ContributionMessagesList',
|
||||
components: {
|
||||
ContributionMessagesListItem,
|
||||
ContributionMessagesFormular,
|
||||
},
|
||||
props: {
|
||||
contributionId: {
|
||||
type: Number,
|
||||
required: true,
|
||||
},
|
||||
state: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
messages: {
|
||||
type: Array,
|
||||
required: true,
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
getListContributionMessages() {
|
||||
this.$emit('get-list-contribution-messages', this.contributionId)
|
||||
},
|
||||
updateState(id) {
|
||||
this.$emit('update-state', id)
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
<style scoped>
|
||||
.temp-message {
|
||||
margin-top: 50px;
|
||||
}
|
||||
</style>
|
||||
@ -0,0 +1,55 @@
|
||||
import { mount } from '@vue/test-utils'
|
||||
import ContributionMessagesList from './ContributionMessagesList.vue'
|
||||
|
||||
const localVue = global.localVue
|
||||
|
||||
describe('ContributionMessagesList', () => {
|
||||
let wrapper
|
||||
|
||||
const mocks = {
|
||||
$t: jest.fn((t) => t),
|
||||
$d: jest.fn((d) => d),
|
||||
$store: {
|
||||
state: {
|
||||
firstName: 'Peter',
|
||||
lastName: 'Lustig',
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
const propsData = {
|
||||
contributionId: 42,
|
||||
state: 'PENDING0',
|
||||
messages: [
|
||||
{
|
||||
id: 111,
|
||||
message: 'asd asda sda sda',
|
||||
createdAt: '2022-08-29T12:23:27.000Z',
|
||||
updatedAt: null,
|
||||
type: 'DIALOG',
|
||||
userFirstName: 'Peter',
|
||||
userLastName: 'Lustig',
|
||||
userId: 107,
|
||||
__typename: 'ContributionMessage',
|
||||
},
|
||||
],
|
||||
}
|
||||
|
||||
const Wrapper = () => {
|
||||
return mount(ContributionMessagesList, {
|
||||
localVue,
|
||||
mocks,
|
||||
propsData,
|
||||
})
|
||||
}
|
||||
|
||||
describe('mount', () => {
|
||||
beforeEach(() => {
|
||||
wrapper = Wrapper()
|
||||
})
|
||||
|
||||
it('has a DIV .contribution-messages-list-item', () => {
|
||||
expect(wrapper.find('div.contribution-messages-list-item').exists()).toBe(true)
|
||||
})
|
||||
})
|
||||
})
|
||||
@ -0,0 +1,38 @@
|
||||
<template>
|
||||
<div class="contribution-messages-list-item">
|
||||
<is-not-moderator v-if="isNotModerator" :message="message"></is-not-moderator>
|
||||
<is-moderator v-else :message="message"></is-moderator>
|
||||
</div>
|
||||
</template>
|
||||
<script>
|
||||
import IsModerator from '@/components/ContributionMessages/slots/IsModerator.vue'
|
||||
import IsNotModerator from '@/components/ContributionMessages/slots/IsNotModerator.vue'
|
||||
|
||||
export default {
|
||||
name: 'ContributionMessagesListItem',
|
||||
components: {
|
||||
IsModerator,
|
||||
IsNotModerator,
|
||||
},
|
||||
props: {
|
||||
message: {
|
||||
type: Object,
|
||||
required: true,
|
||||
default() {
|
||||
return {}
|
||||
},
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
storeName: `${this.$store.state.firstName} ${this.$store.state.lastName}`,
|
||||
moderationName: `${this.message.userFirstName} ${this.message.userLastName}`,
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
isNotModerator() {
|
||||
return this.storeName === this.moderationName
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
@ -0,0 +1,49 @@
|
||||
import { mount } from '@vue/test-utils'
|
||||
import IsModerator from './IsModerator.vue'
|
||||
|
||||
const localVue = global.localVue
|
||||
|
||||
describe('IsModerator', () => {
|
||||
let wrapper
|
||||
|
||||
const mocks = {
|
||||
$t: jest.fn((t) => t),
|
||||
$d: jest.fn((d) => d),
|
||||
}
|
||||
|
||||
const propsData = {
|
||||
message: {
|
||||
id: 111,
|
||||
message: 'asd asda sda sda',
|
||||
createdAt: '2022-08-29T12:23:27.000Z',
|
||||
updatedAt: null,
|
||||
type: 'DIALOG',
|
||||
userFirstName: 'Peter',
|
||||
userLastName: 'Lustig',
|
||||
userId: 107,
|
||||
__typename: 'ContributionMessage',
|
||||
},
|
||||
}
|
||||
|
||||
const Wrapper = () => {
|
||||
return mount(IsModerator, {
|
||||
localVue,
|
||||
mocks,
|
||||
propsData,
|
||||
})
|
||||
}
|
||||
|
||||
describe('mount', () => {
|
||||
beforeEach(() => {
|
||||
wrapper = Wrapper()
|
||||
})
|
||||
|
||||
it('has a DIV .slot-is-moderator', () => {
|
||||
expect(wrapper.find('div.slot-is-moderator').exists()).toBe(true)
|
||||
})
|
||||
|
||||
it('props.message.default', () => {
|
||||
expect(wrapper.vm.$options.props.message.default.call()).toEqual({})
|
||||
})
|
||||
})
|
||||
})
|
||||
@ -0,0 +1,34 @@
|
||||
<template>
|
||||
<div class="slot-is-moderator">
|
||||
<b-avatar square :text="initialLetters" variant="warning"></b-avatar>
|
||||
<span class="ml-2 mr-2">{{ message.userFirstName }} {{ message.userLastName }}</span>
|
||||
<span class="ml-2">{{ $d(new Date(message.createdAt), 'short') }}</span>
|
||||
<small class="ml-4 text-success">{{ $t('community.moderator') }}</small>
|
||||
<div class="mt-2 h3">{{ message.message }}</div>
|
||||
</div>
|
||||
</template>
|
||||
<script>
|
||||
export default {
|
||||
props: {
|
||||
message: {
|
||||
type: Object,
|
||||
default() {
|
||||
return {}
|
||||
},
|
||||
},
|
||||
},
|
||||
computed: {
|
||||
initialLetters() {
|
||||
return `${this.message.userFirstName[0]} ${this.message.userLastName[0]}`
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
<style>
|
||||
.slot-is-moderator {
|
||||
clear: both;
|
||||
/* background-color: rgb(255, 242, 227); */
|
||||
width: 75%;
|
||||
margin-top: 20px;
|
||||
}
|
||||
</style>
|
||||
@ -0,0 +1,49 @@
|
||||
import { mount } from '@vue/test-utils'
|
||||
import IsNotModerator from './IsNotModerator.vue'
|
||||
|
||||
const localVue = global.localVue
|
||||
|
||||
describe('IsNotModerator', () => {
|
||||
let wrapper
|
||||
|
||||
const mocks = {
|
||||
$t: jest.fn((t) => t),
|
||||
$d: jest.fn((d) => d),
|
||||
}
|
||||
|
||||
const propsData = {
|
||||
message: {
|
||||
id: 113,
|
||||
message: 'asda sdad ad asdasd ',
|
||||
createdAt: '2022-08-29T12:25:34.000Z',
|
||||
updatedAt: null,
|
||||
type: 'DIALOG',
|
||||
userFirstName: 'Bibi',
|
||||
userLastName: 'Bloxberg',
|
||||
userId: 108,
|
||||
__typename: 'ContributionMessage',
|
||||
},
|
||||
}
|
||||
|
||||
const Wrapper = () => {
|
||||
return mount(IsNotModerator, {
|
||||
localVue,
|
||||
mocks,
|
||||
propsData,
|
||||
})
|
||||
}
|
||||
|
||||
describe('mount', () => {
|
||||
beforeEach(() => {
|
||||
wrapper = Wrapper()
|
||||
})
|
||||
|
||||
it('has a DIV .slot-is-not-moderator', () => {
|
||||
expect(wrapper.find('div.slot-is-not-moderator').exists()).toBe(true)
|
||||
})
|
||||
|
||||
it('props.message.default', () => {
|
||||
expect(wrapper.vm.$options.props.message.default.call()).toEqual({})
|
||||
})
|
||||
})
|
||||
})
|
||||
@ -0,0 +1,38 @@
|
||||
<template>
|
||||
<div class="slot-is-not-moderator">
|
||||
<div class="text-right">
|
||||
<b-avatar :text="initialLetters" variant="info"></b-avatar>
|
||||
<span class="ml-2 mr-2 text-bold">
|
||||
{{ message.userFirstName }} {{ message.userLastName }}
|
||||
</span>
|
||||
<span class="ml-2">{{ $d(new Date(message.createdAt), 'short') }}</span>
|
||||
<div class="mt-2 h3">{{ message.message }}</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<script>
|
||||
export default {
|
||||
props: {
|
||||
message: {
|
||||
type: Object,
|
||||
default() {
|
||||
return {}
|
||||
},
|
||||
},
|
||||
},
|
||||
computed: {
|
||||
initialLetters() {
|
||||
return `${this.message.userFirstName[0]} ${this.message.userLastName[0]}`
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
<style>
|
||||
.slot-is-not-moderator {
|
||||
float: right;
|
||||
width: 75%;
|
||||
margin-top: 20px;
|
||||
margin-bottom: 20px;
|
||||
clear: both;
|
||||
}
|
||||
</style>
|
||||
@ -3,8 +3,10 @@
|
||||
<div class="list-group" v-for="item in items" :key="item.id">
|
||||
<contribution-list-item
|
||||
v-bind="item"
|
||||
:contributionId="item.id"
|
||||
@update-contribution-form="updateContributionForm"
|
||||
@delete-contribution="deleteContribution"
|
||||
@update-state="updateState"
|
||||
/>
|
||||
</div>
|
||||
<b-pagination
|
||||
@ -46,6 +48,7 @@ export default {
|
||||
data() {
|
||||
return {
|
||||
currentPage: 1,
|
||||
messages: [],
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
@ -62,6 +65,9 @@ export default {
|
||||
deleteContribution(item) {
|
||||
this.$emit('delete-contribution', item)
|
||||
},
|
||||
updateState(id) {
|
||||
this.$emit('update-state', id)
|
||||
},
|
||||
},
|
||||
computed: {
|
||||
isPaginationVisible() {
|
||||
|
||||
@ -12,6 +12,9 @@ describe('ContributionListItem', () => {
|
||||
}
|
||||
|
||||
const propsData = {
|
||||
contributionId: 42,
|
||||
state: 'PENDING',
|
||||
messagesCount: 2,
|
||||
id: 1,
|
||||
createdAt: '26/07/2022',
|
||||
contributionDate: '07/06/2022',
|
||||
@ -37,22 +40,6 @@ describe('ContributionListItem', () => {
|
||||
expect(wrapper.find('div.contribution-list-item').exists()).toBe(true)
|
||||
})
|
||||
|
||||
describe('contribution type', () => {
|
||||
it('is pending by default', () => {
|
||||
expect(wrapper.vm.type).toBe('pending')
|
||||
})
|
||||
|
||||
it('is deleted when deletedAt is present', async () => {
|
||||
await wrapper.setProps({ deletedAt: new Date().toISOString() })
|
||||
expect(wrapper.vm.type).toBe('deleted')
|
||||
})
|
||||
|
||||
it('is confirmed when confirmedAt is present', async () => {
|
||||
await wrapper.setProps({ confirmedAt: new Date().toISOString() })
|
||||
expect(wrapper.vm.type).toBe('confirmed')
|
||||
})
|
||||
})
|
||||
|
||||
describe('contribution icon', () => {
|
||||
it('is bell-fill by default', () => {
|
||||
expect(wrapper.vm.icon).toBe('bell-fill')
|
||||
@ -83,6 +70,11 @@ describe('ContributionListItem', () => {
|
||||
await wrapper.setProps({ confirmedAt: new Date().toISOString() })
|
||||
expect(wrapper.vm.variant).toBe('success')
|
||||
})
|
||||
|
||||
it('is warning at when state is IN_PROGRESS', async () => {
|
||||
await wrapper.setProps({ state: 'IN_PROGRESS' })
|
||||
expect(wrapper.vm.variant).toBe('warning')
|
||||
})
|
||||
})
|
||||
|
||||
describe('date', () => {
|
||||
@ -133,7 +125,7 @@ describe('ContributionListItem', () => {
|
||||
beforeEach(async () => {
|
||||
spy = jest.spyOn(wrapper.vm.$bvModal, 'msgBoxConfirm')
|
||||
spy.mockImplementation(() => Promise.resolve(false))
|
||||
await wrapper.findAll('div.pointer').at(1).trigger('click')
|
||||
await wrapper.findAll('div.pointer').at(2).trigger('click')
|
||||
})
|
||||
|
||||
it('does not emit delete contribution', () => {
|
||||
|
||||
@ -2,47 +2,100 @@
|
||||
<div class="contribution-list-item">
|
||||
<slot>
|
||||
<div class="border p-3 w-100 mb-1" :class="`border-${variant}`">
|
||||
<div class="d-inline-flex">
|
||||
<div class="mr-2"><b-icon :icon="icon" :variant="variant" class="h2"></b-icon></div>
|
||||
<div v-if="firstName" class="mr-3">{{ firstName }} {{ lastName }}</div>
|
||||
<div class="mr-2" :class="type != 'deleted' ? 'font-weight-bold' : ''">
|
||||
{{ amount | GDD }}
|
||||
<div>
|
||||
<div class="d-inline-flex">
|
||||
<div class="mr-2">
|
||||
<b-icon
|
||||
v-if="state === 'IN_PROGRESS'"
|
||||
icon="question-square"
|
||||
font-scale="2"
|
||||
variant="warning"
|
||||
></b-icon>
|
||||
<b-icon v-else :icon="icon" :variant="variant" class="h2"></b-icon>
|
||||
</div>
|
||||
<div v-if="firstName" class="mr-3">{{ firstName }} {{ lastName }}</div>
|
||||
<div class="mr-2" :class="state !== 'DELETED' ? 'font-weight-bold' : ''">
|
||||
{{ amount | GDD }}
|
||||
</div>
|
||||
{{ $t('math.minus') }}
|
||||
<div class="mx-2">{{ $d(new Date(date), 'short') }}</div>
|
||||
</div>
|
||||
{{ $t('math.minus') }}
|
||||
<div class="mx-2">{{ $d(new Date(date), 'short') }}</div>
|
||||
</div>
|
||||
<div class="mr-2">
|
||||
<span>{{ $t('contribution.date') }}</span>
|
||||
<span>
|
||||
{{ $d(new Date(contributionDate), 'monthAndYear') }}
|
||||
</span>
|
||||
</div>
|
||||
<div class="mr-2">{{ memo }}</div>
|
||||
<div v-if="type === 'pending' && !firstName" class="d-flex flex-row-reverse">
|
||||
<div class="mr-2">
|
||||
<span>{{ $t('contribution.date') }}</span>
|
||||
<span>
|
||||
{{ $d(new Date(contributionDate), 'monthAndYear') }}
|
||||
</span>
|
||||
</div>
|
||||
<div class="mr-2">{{ memo }}</div>
|
||||
<div
|
||||
class="pointer ml-5"
|
||||
@click="
|
||||
$emit('update-contribution-form', {
|
||||
id: id,
|
||||
contributionDate: contributionDate,
|
||||
memo: memo,
|
||||
amount: amount,
|
||||
})
|
||||
"
|
||||
v-if="!['CONFIRMED', 'DELETED'].includes(state) && !firstName"
|
||||
class="d-flex flex-row-reverse"
|
||||
>
|
||||
<b-icon icon="pencil" class="h2"></b-icon>
|
||||
</div>
|
||||
<div class="pointer" @click="deleteContribution({ id })">
|
||||
<b-icon icon="trash" class="h2"></b-icon>
|
||||
<div
|
||||
v-if="!['CONFIRMED', 'DELETED'].includes(state)"
|
||||
class="pointer ml-5"
|
||||
@click="
|
||||
$emit('update-contribution-form', {
|
||||
id: id,
|
||||
contributionDate: contributionDate,
|
||||
memo: memo,
|
||||
amount: amount,
|
||||
})
|
||||
"
|
||||
>
|
||||
<b-icon icon="pencil" class="h2"></b-icon>
|
||||
</div>
|
||||
<div
|
||||
v-if="!['CONFIRMED', 'DELETED'].includes(state)"
|
||||
class="pointer"
|
||||
@click="deleteContribution({ id })"
|
||||
>
|
||||
<b-icon icon="trash" class="h2"></b-icon>
|
||||
</div>
|
||||
<div v-if="messagesCount > 0" class="pointer">
|
||||
<b-icon
|
||||
v-b-toggle="collapsId"
|
||||
icon="chat-dots"
|
||||
class="h2 mr-5"
|
||||
@click="getListContributionMessages"
|
||||
></b-icon>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div v-if="messagesCount > 0">
|
||||
<b-button
|
||||
v-if="state === 'IN_PROGRESS'"
|
||||
v-b-toggle="collapsId"
|
||||
variant="warning"
|
||||
@click="getListContributionMessages"
|
||||
>
|
||||
{{ $t('contribution.alert.answerQuestion') }}
|
||||
</b-button>
|
||||
<b-collapse :id="collapsId" class="mt-2">
|
||||
<b-card>
|
||||
<contribution-messages-list
|
||||
:messages="messages_get"
|
||||
:state="state"
|
||||
:contributionId="contributionId"
|
||||
@get-list-contribution-messages="getListContributionMessages"
|
||||
@update-state="updateState"
|
||||
/>
|
||||
</b-card>
|
||||
</b-collapse>
|
||||
</div>
|
||||
</div>
|
||||
</slot>
|
||||
</div>
|
||||
</template>
|
||||
<script>
|
||||
import ContributionMessagesList from '@/components/ContributionMessages/ContributionMessagesList.vue'
|
||||
import { listContributionMessages } from '../../graphql/queries.js'
|
||||
|
||||
export default {
|
||||
name: 'ContributionListItem',
|
||||
components: {
|
||||
ContributionMessagesList,
|
||||
},
|
||||
props: {
|
||||
id: {
|
||||
type: Number,
|
||||
@ -79,13 +132,26 @@ export default {
|
||||
type: String,
|
||||
required: false,
|
||||
},
|
||||
state: {
|
||||
type: String,
|
||||
required: false,
|
||||
},
|
||||
messagesCount: {
|
||||
type: Number,
|
||||
required: false,
|
||||
},
|
||||
contributionId: {
|
||||
type: Number,
|
||||
required: true,
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
inProcess: true,
|
||||
messages_get: [],
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
type() {
|
||||
if (this.deletedAt) return 'deleted'
|
||||
if (this.confirmedAt) return 'confirmed'
|
||||
return 'pending'
|
||||
},
|
||||
icon() {
|
||||
if (this.deletedAt) return 'x-circle'
|
||||
if (this.confirmedAt) return 'check'
|
||||
@ -94,14 +160,15 @@ export default {
|
||||
variant() {
|
||||
if (this.deletedAt) return 'danger'
|
||||
if (this.confirmedAt) return 'success'
|
||||
if (this.state === 'IN_PROGRESS') return 'warning'
|
||||
return 'primary'
|
||||
},
|
||||
date() {
|
||||
// if (this.deletedAt) return this.deletedAt
|
||||
// if (this.confirmedAt) return this.confirmedAt
|
||||
// return this.contributionDate
|
||||
return this.createdAt
|
||||
},
|
||||
collapsId() {
|
||||
return 'collapse' + String(this.id)
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
deleteContribution(item) {
|
||||
@ -109,6 +176,27 @@ export default {
|
||||
if (value) this.$emit('delete-contribution', item)
|
||||
})
|
||||
},
|
||||
getListContributionMessages() {
|
||||
// console.log('getListContributionMessages', this.contributionId)
|
||||
this.$apollo
|
||||
.query({
|
||||
query: listContributionMessages,
|
||||
variables: {
|
||||
contributionId: this.contributionId,
|
||||
},
|
||||
fetchPolicy: 'no-cache',
|
||||
})
|
||||
.then((result) => {
|
||||
// console.log('result', result.data.listContributionMessages.messages)
|
||||
this.messages_get = result.data.listContributionMessages.messages
|
||||
})
|
||||
.catch((error) => {
|
||||
this.toastError(error.message)
|
||||
})
|
||||
},
|
||||
updateState(id) {
|
||||
this.$emit('update-state', id)
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
@ -122,3 +122,17 @@ export const deleteContribution = gql`
|
||||
deleteContribution(id: $id)
|
||||
}
|
||||
`
|
||||
|
||||
export const createContributionMessage = gql`
|
||||
mutation($contributionId: Float!, $message: String!) {
|
||||
createContributionMessage(contributionId: $contributionId, message: $message) {
|
||||
id
|
||||
message
|
||||
createdAt
|
||||
updatedAt
|
||||
type
|
||||
userFirstName
|
||||
userLastName
|
||||
}
|
||||
}
|
||||
`
|
||||
|
||||
@ -257,3 +257,26 @@ export const searchAdminUsers = gql`
|
||||
}
|
||||
}
|
||||
`
|
||||
|
||||
export const listContributionMessages = gql`
|
||||
query($contributionId: Float!, $pageSize: Int = 25, $currentPage: Int = 1, $order: Order = ASC) {
|
||||
listContributionMessages(
|
||||
contributionId: $contributionId
|
||||
pageSize: $pageSize
|
||||
currentPage: $currentPage
|
||||
order: $order
|
||||
) {
|
||||
count
|
||||
messages {
|
||||
id
|
||||
message
|
||||
createdAt
|
||||
updatedAt
|
||||
type
|
||||
userFirstName
|
||||
userLastName
|
||||
userId
|
||||
}
|
||||
}
|
||||
}
|
||||
`
|
||||
|
||||
@ -22,6 +22,7 @@
|
||||
"continue-to-registration": "Weiter zur Registrierung",
|
||||
"current-community": "Aktuelle Gemeinschaft",
|
||||
"members": "Mitglieder",
|
||||
"moderator": "Moderator",
|
||||
"moderators": "Moderatoren",
|
||||
"myContributions": "Meine Beiträge zum Gemeinwohl",
|
||||
"openContributionLinks": "öffentliche Beitrags-Linkliste",
|
||||
@ -34,10 +35,11 @@
|
||||
"contribution": {
|
||||
"activity": "Tätigkeit",
|
||||
"alert": {
|
||||
"answerQuestion": "Bitte beantworte die Nachfrage",
|
||||
"communityNoteList": "Hier findest du alle eingereichten und bestätigten Beiträge von allen Mitgliedern aus dieser Gemeinschaft.",
|
||||
"confirm": "bestätigt",
|
||||
"in_progress": "Es gibt eine Rückfrage der Moderatoren.",
|
||||
"myContributionNoteList": "Eingereichte Beiträge, die noch nicht bestätigt wurden, kannst du jederzeit bearbeiten oder löschen.",
|
||||
"myContributionNoteSupport": "Es wird bald an dieser Stelle die Möglichkeit geben das ein Dialog zwischen Moderatoren und dir stattfinden kann. Solltest du jetzt Probleme haben bitte nimm Kontakt mit dem Support auf.",
|
||||
"pending": "Eingereicht und wartet auf Bestätigung",
|
||||
"rejected": "abgelehnt"
|
||||
},
|
||||
@ -130,6 +132,7 @@
|
||||
"password_new_repeat": "Neues Passwort wiederholen",
|
||||
"password_old": "Altes Passwort",
|
||||
"recipient": "Empfänger",
|
||||
"reply": "Antworten",
|
||||
"reset": "Zurücksetzen",
|
||||
"save": "Speichern",
|
||||
"scann_code": "<strong>QR Code Scanner</strong> - Scanne den QR Code deines Partners",
|
||||
@ -219,6 +222,7 @@
|
||||
"email": "Wir haben dir eine E-Mail gesendet.",
|
||||
"errorTitle": "Achtung!",
|
||||
"register": "Du bist jetzt registriert, bitte überprüfe deine Emails und klicke auf den Aktivierungslink.",
|
||||
"reply": "Danke, Deine Antwort wurde abgesendet.",
|
||||
"reset": "Dein Passwort wurde geändert.",
|
||||
"title": "Danke!",
|
||||
"unsetPassword": "Dein Passwort wurde noch nicht gesetzt. Bitte setze es neu."
|
||||
|
||||
@ -22,6 +22,7 @@
|
||||
"continue-to-registration": "Continue to registration",
|
||||
"current-community": "Current community",
|
||||
"members": "Members",
|
||||
"moderator": "Moderator",
|
||||
"moderators": "Moderators",
|
||||
"myContributions": "My contributions to the common good",
|
||||
"openContributionLinks": "open Contribution links list",
|
||||
@ -34,10 +35,11 @@
|
||||
"contribution": {
|
||||
"activity": "Activity",
|
||||
"alert": {
|
||||
"answerQuestion": "Please answer the question",
|
||||
"communityNoteList": "Here you will find all submitted and confirmed contributions from all members of this community.",
|
||||
"confirm": "confirmed",
|
||||
"in_progress": "There is a question from the moderators.",
|
||||
"myContributionNoteList": "You can edit or delete entries that have not yet been confirmed at any time.",
|
||||
"myContributionNoteSupport": "Soon there will be the possibility for a dialogue between moderators and you. If you have any problems now, please contact the support.",
|
||||
"pending": "Submitted and waiting for confirmation",
|
||||
"rejected": "deleted"
|
||||
},
|
||||
@ -130,6 +132,7 @@
|
||||
"password_new_repeat": "Repeat new password",
|
||||
"password_old": "Old password",
|
||||
"recipient": "Recipient",
|
||||
"reply": "Reply",
|
||||
"reset": "Reset",
|
||||
"save": "Save",
|
||||
"scann_code": "<strong>QR Code Scanner</strong> - Scan the QR Code of your partner",
|
||||
@ -219,6 +222,7 @@
|
||||
"email": "We have sent you an email.",
|
||||
"errorTitle": "Attention!",
|
||||
"register": "You are registered now, please check your emails and click the activation link.",
|
||||
"reply": "Thank you, your reply has been sent.",
|
||||
"reset": "Your password has been changed.",
|
||||
"title": "Thank you!",
|
||||
"unsetPassword": "Your password has not been set yet. Please set it again."
|
||||
|
||||
@ -22,6 +22,7 @@
|
||||
"continue-to-registration": "Continuar con el registro",
|
||||
"current-community": "Comunidad actual",
|
||||
"members": "Miembros",
|
||||
"moderator": "Moderador",
|
||||
"moderators": "Moderadores",
|
||||
"myContributions": "Mis contribuciones al bien común",
|
||||
"openContributionLinks": "lista de enlaces de contribuciones públicas",
|
||||
@ -34,10 +35,11 @@
|
||||
"contribution": {
|
||||
"activity": "Actividad",
|
||||
"alert": {
|
||||
"answerQuestion": "Por favor, contesta las preguntas",
|
||||
"communityNoteList": "Aquí encontrarás todas las contribuciones enviadas y confirmadas de todos los miembros de esta comunidad.",
|
||||
"confirm": "confirmado",
|
||||
"in_progress": "Hay una pregunta de los moderatores.",
|
||||
"myContributionNoteList": "Puedes editar o eliminar las contribuciones enviadas que aún no han sido confirmadas en cualquier momento.",
|
||||
"myContributionNoteSupport": "Pronto existirá la posibilidad de que puedas dialogar con los moderadores. Si tienes algún problema ahora, ponte en contacto con el equipo de asistencia.",
|
||||
"pending": "Enviado y a la espera de confirmación",
|
||||
"rejected": "rechazado"
|
||||
},
|
||||
@ -130,6 +132,7 @@
|
||||
"password_new_repeat": "Repetir contraseña nueva",
|
||||
"password_old": "contraseña antigua",
|
||||
"recipient": "Destinatario",
|
||||
"reply": "Respuesta",
|
||||
"reset": "Restablecer",
|
||||
"save": "Guardar",
|
||||
"scann_code": "<strong>QR Code Scanner</strong> - Escanea el código QR de tu pareja",
|
||||
@ -219,6 +222,7 @@
|
||||
"email": "Te hemos enviado un correo electrónico.",
|
||||
"errorTitle": "Atención!",
|
||||
"register": "Ya estás registrado, por favor revisa tu correo electrónico y haz clic en el enlace de activación.",
|
||||
"reply": "Gracias, tu respuesta ha sido enviada.",
|
||||
"reset": "Tu contraseña ha sido cambiada.",
|
||||
"title": "Gracias!",
|
||||
"unsetPassword": "Tu contraseña aún no ha sido configurada. Por favor reinícialo."
|
||||
|
||||
@ -22,6 +22,7 @@
|
||||
"continue-to-registration": "Continuez l´inscription",
|
||||
"current-community": "Communauté actuelle",
|
||||
"members": "Membres",
|
||||
"moderator": "Modérateur",
|
||||
"moderators": "Modérateurs",
|
||||
"myContributions": "Mes contributions aux biens communs",
|
||||
"openContributionLinks": "liste de liens de contribution publique",
|
||||
@ -34,10 +35,11 @@
|
||||
"contribution": {
|
||||
"activity": "Activité",
|
||||
"alert": {
|
||||
"answerQuestion": "S'il te plais répond à la question",
|
||||
"communityNoteList": "Vous trouverez ci-contre toutes les contributions versées et certifiées de tous les membres de cette communauté.",
|
||||
"confirm": " Approuvé",
|
||||
"in_progress": "Il y a une question du modérateur.",
|
||||
"myContributionNoteList": "À tout moment vous pouvez éditer ou supprimer les données qui n´ont pas été confirmées.",
|
||||
"myContributionNoteSupport": "Vous aurez bientôt la possibilité de dialoguer avec un médiateur. Si vous rencontrez un problème maintenant, merci de contacter l´aide en ligne.",
|
||||
"pending": "Inscription en attente de validation",
|
||||
"rejected": "supprimé"
|
||||
},
|
||||
@ -130,6 +132,7 @@
|
||||
"password_new_repeat": "Répétez le nouveau mot de passe",
|
||||
"password_old": "Ancien mot de passe",
|
||||
"recipient": "Destinataire",
|
||||
"reply": "Répondre",
|
||||
"reset": "Réinitialiser",
|
||||
"save": "Sauvegarder",
|
||||
"scann_code": "<strong>QR Code Scanner</strong> - Scannez le QR code de votre partenaire",
|
||||
@ -219,6 +222,7 @@
|
||||
"email": "Nous vous avons envoyé un email.",
|
||||
"errorTitle": "Attention!",
|
||||
"register": "Vous êtes enregistré maintenant, merci de vérifier votre boîte mail et cliquer sur le lien d´activation.",
|
||||
"reply": "Merci, ta réponse a été envoyée.",
|
||||
"reset": "Votre mot de passe a été modifié.",
|
||||
"title": "Merci!",
|
||||
"unsetPassword": "Votre mot de passe n´a pas été accepté. Merci de le réinitialiser."
|
||||
|
||||
@ -22,6 +22,7 @@
|
||||
"continue-to-registration": "Verder ter registratie",
|
||||
"current-community": "Actuele gemeenschap",
|
||||
"members": "Leden",
|
||||
"moderator": "Moderator",
|
||||
"moderators": "Moderators",
|
||||
"myContributions": "Mijn bijdragen voor het algemeen belang",
|
||||
"openContributionLinks": "openbare lijst van bijdragen",
|
||||
@ -34,10 +35,11 @@
|
||||
"contribution": {
|
||||
"activity": "Activiteit",
|
||||
"alert": {
|
||||
"answerQuestion": "Please answer the question",
|
||||
"communityNoteList": "Hier vind je alle ingediende en bevestigde bijdragen van alle leden uit deze gemeenschap.",
|
||||
"confirm": "bevestigt",
|
||||
"in_progress": "There is a question from the moderators.",
|
||||
"myContributionNoteList": "Ingediende bijdragen, die nog niet bevestigd zijn, kun je op elk moment wijzigen of verwijderen.",
|
||||
"myContributionNoteSupport": "Hier heb je binnenkort de mogelijkheid een gesprek met een moderator te voeren. Mocht je nu problemen hebben, dan neem alsjeblieft contact op met Support.",
|
||||
"pending": "Ingediend en wacht op bevestiging",
|
||||
"rejected": "afgewezen"
|
||||
},
|
||||
@ -130,6 +132,7 @@
|
||||
"password_new_repeat": "Nieuw wachtwoord herhalen",
|
||||
"password_old": "Oud wachtwoord",
|
||||
"recipient": "Ontvanger",
|
||||
"reply": "Antwoord",
|
||||
"reset": "Resetten",
|
||||
"save": "Opslaan",
|
||||
"scann_code": "<strong>QR Code Scanner</strong> - Scan de QR Code van uw partner",
|
||||
@ -219,6 +222,7 @@
|
||||
"email": "We hebben jou een email gestuurd.",
|
||||
"errorTitle": "Opgelet!",
|
||||
"register": "Je bent nu geregistreerd. Controleer alsjeblieft je emails en klik op de activeringslink.",
|
||||
"reply": "Dank u, uw antwoord is verzonden.",
|
||||
"reset": "Jouw wachtwoord werd gewijzigd.",
|
||||
"title": "Dankjewel!",
|
||||
"unsetPassword": "Jouw wachtwoord werd nog niet ingesteld. Doe het alsjeblieft opnieuw."
|
||||
|
||||
@ -93,9 +93,7 @@ describe('Community', () => {
|
||||
expect(wrapper.findAll('div[role="tabpanel"]')).toHaveLength(3)
|
||||
})
|
||||
|
||||
it('has first tab active by default', () => {
|
||||
expect(wrapper.findAll('div[role="tabpanel"]').at(0).classes('active')).toBe(true)
|
||||
})
|
||||
it.todo('check for correct tabIndex if state is "IN_PROGRESS" or not')
|
||||
})
|
||||
|
||||
describe('API calls after creation', () => {
|
||||
|
||||
@ -2,7 +2,7 @@
|
||||
<div class="community-page">
|
||||
<div>
|
||||
<b-tabs v-model="tabIndex" content-class="mt-3" align="center">
|
||||
<b-tab :title="$t('community.submitContribution')" active>
|
||||
<b-tab :title="$t('community.submitContribution')">
|
||||
<contribution-form
|
||||
@set-contribution="setContribution"
|
||||
@update-contribution="updateContribution"
|
||||
@ -22,6 +22,10 @@
|
||||
<b-icon icon="bell-fill" variant="primary"></b-icon>
|
||||
{{ $t('contribution.alert.pending') }}
|
||||
</li>
|
||||
<li>
|
||||
<b-icon icon="question-square" variant="warning"></b-icon>
|
||||
{{ $t('contribution.alert.in_progress') }}
|
||||
</li>
|
||||
<li>
|
||||
<b-icon icon="check" variant="success"></b-icon>
|
||||
{{ $t('contribution.alert.confirm') }}
|
||||
@ -32,9 +36,6 @@
|
||||
</li>
|
||||
</ul>
|
||||
<hr />
|
||||
<p class="mb-0">
|
||||
{{ $t('contribution.alert.myContributionNoteSupport') }}
|
||||
</p>
|
||||
</b-alert>
|
||||
</div>
|
||||
<contribution-list
|
||||
@ -42,6 +43,7 @@
|
||||
@update-list-contributions="updateListContributions"
|
||||
@update-contribution-form="updateContributionForm"
|
||||
@delete-contribution="deleteContribution"
|
||||
@update-state="updateState"
|
||||
:contributionCount="contributionCount"
|
||||
:showPagination="true"
|
||||
:pageSize="pageSize"
|
||||
@ -226,6 +228,11 @@ export default {
|
||||
} = result
|
||||
this.contributionCount = listContributions.contributionCount
|
||||
this.items = listContributions.contributionList
|
||||
if (this.items.find((item) => item.state === 'IN_PROGRESS')) {
|
||||
this.tabIndex = 1
|
||||
} else {
|
||||
this.tabIndex = 0
|
||||
}
|
||||
})
|
||||
.catch((err) => {
|
||||
this.toastError(err.message)
|
||||
@ -258,6 +265,9 @@ export default {
|
||||
updateTransactions(pagination) {
|
||||
this.$emit('update-transactions', pagination)
|
||||
},
|
||||
updateState(id) {
|
||||
this.items.find((item) => item.id === id).state = 'PENDING'
|
||||
},
|
||||
},
|
||||
created() {
|
||||
// verifyLogin is important at this point so that creation is updated on reload if they are deleted in a session in the admin area.
|
||||
@ -271,6 +281,7 @@ export default {
|
||||
pageSize: this.pageSize,
|
||||
})
|
||||
this.updateTransactions(0)
|
||||
this.tabIndex = 1
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user